From 8b8ee9b71b0d78d99f196168a0fb073d79d0129b Mon Sep 17 00:00:00 2001 From: Sebijk Date: Sun, 13 Feb 2022 23:22:47 +0100 Subject: [PATCH] update sabredav to 3.0.7 --- src/interface/caldav.php | 12 +- src/interface/carddav.php | 2 +- src/serverlib/3rdparty/SabreDAV/CHANGELOG.md | 2138 +++++++++++++ src/serverlib/3rdparty/SabreDAV/LICENSE | 27 + src/serverlib/3rdparty/SabreDAV/README.md | 30 + .../3rdparty/SabreDAV/bin/generate_vcards | 241 ++ .../3rdparty/SabreDAV/bin/naturalselection | 140 + src/serverlib/3rdparty/SabreDAV/bin/sabredav | 2 + src/serverlib/3rdparty/SabreDAV/bin/vobject | 27 + .../SabreDAV/examples/addressbookserver.php | 57 + .../SabreDAV/examples/calendarserver.php | 76 + .../3rdparty/SabreDAV/examples/fileserver.php | 56 + .../SabreDAV/examples/groupwareserver.php | 92 + .../SabreDAV/examples/simplefsserver.php | 123 + .../examples/sql/mysql.addressbook.sql | 28 + .../SabreDAV/examples/sql/mysql.calendars.sql | 64 + .../SabreDAV/examples/sql/mysql.locks.sql | 12 + .../examples/sql/mysql.principals.sql | 20 + .../examples/sql/mysql.propertystorage.sql | 9 + .../SabreDAV/examples/sql/mysql.users.sql | 9 + .../examples/sql/pgsql.addressbook.sql | 52 + .../SabreDAV/examples/sql/pgsql.calendars.sql | 93 + .../SabreDAV/examples/sql/pgsql.locks.sql | 19 + .../examples/sql/pgsql.principals.sql | 38 + .../examples/sql/pgsql.propertystorage.sql | 13 + .../SabreDAV/examples/sql/pgsql.users.sql | 14 + .../examples/sql/sqlite.addressbooks.sql | 28 + .../examples/sql/sqlite.calendars.sql | 64 + .../SabreDAV/examples/sql/sqlite.locks.sql | 12 + .../examples/sql/sqlite.principals.sql | 20 + .../examples/sql/sqlite.propertystorage.sql | 10 + .../SabreDAV/examples/sql/sqlite.users.sql | 9 + .../examples/webserver/apache2_htaccess.conf | 16 + .../examples/webserver/apache2_vhost.conf | 33 + .../examples/webserver/apache2_vhost_cgi.conf | 21 + .../3rdparty/SabreDAV/vendor/autoload.php | 7 + .../SabreDAV/vendor/composer/ClassLoader.php | 413 +++ .../3rdparty/SabreDAV/vendor/composer/LICENSE | 19 + .../vendor/composer/autoload_classmap.php | 9 + .../vendor/composer/autoload_files.php | 13 + .../vendor/composer/autoload_namespaces.php | 9 + .../vendor/composer/autoload_psr4.php | 18 + .../vendor/composer/autoload_real.php | 59 + .../SabreDAV/vendor/composer/installed.json | 381 +++ .../SabreDAV/vendor/sabre/dav/.gitignore | 40 + .../SabreDAV/vendor/sabre/dav/.travis.yml | 32 + .../SabreDAV/vendor/sabre/dav/CONTRIBUTING.md | 87 + .../SabreDAV/vendor/sabre/dav/bin/build.php | 177 ++ .../vendor/sabre/dav/bin/googlecode_upload.py | 248 ++ .../vendor/sabre/dav/bin/migrateto17.php | 284 ++ .../vendor/sabre/dav/bin/migrateto20.php | 453 +++ .../vendor/sabre/dav/bin/migrateto21.php | 180 ++ .../vendor/sabre/dav/bin/migrateto30.php | 171 ++ .../vendor/sabre/dav/bin/naturalselection | 140 + .../SabreDAV/vendor/sabre/dav/bin/sabredav | 2 + .../vendor/sabre/dav/bin/sabredav.php | 53 + .../SabreDAV/vendor/sabre/dav/composer.json | 66 + .../lib/CalDAV/Backend/AbstractBackend.php | 221 ++ .../lib/CalDAV/Backend/BackendInterface.php | 268 ++ .../CalDAV/Backend/NotificationSupport.php | 46 + .../sabre/dav/lib/CalDAV/Backend/PDO.php | 1207 ++++++++ .../lib/CalDAV/Backend/SchedulingSupport.php | 65 + .../lib}/CalDAV/Backend/SharingSupport.php | 0 .../CalDAV/Backend/SubscriptionSupport.php | 0 .../dav/lib}/CalDAV/Backend/SyncSupport.php | 0 .../vendor/sabre/dav/lib/CalDAV/Calendar.php | 527 ++++ .../sabre/dav/lib/CalDAV/CalendarHome.php | 430 +++ .../sabre/dav/lib/CalDAV/CalendarObject.php | 290 ++ .../dav/lib/CalDAV/CalendarQueryValidator.php | 375 +++ .../sabre/dav/lib/CalDAV/CalendarRoot.php | 80 + .../CalDAV/Exception/InvalidComponentType.php | 35 + .../sabre/dav/lib/CalDAV/ICSExportPlugin.php | 361 +++ .../vendor/sabre/dav/lib/CalDAV/ICalendar.php | 18 + .../sabre/dav/lib/CalDAV/ICalendarObject.php | 21 + .../lib/CalDAV/ICalendarObjectContainer.php | 39 + .../dav/lib}/CalDAV/IShareableCalendar.php | 0 .../sabre/dav/lib}/CalDAV/ISharedCalendar.php | 0 .../lib/CalDAV/Notifications/Collection.php | 173 ++ .../lib}/CalDAV/Notifications/ICollection.php | 0 .../dav/lib}/CalDAV/Notifications/INode.php | 0 .../dav/lib/CalDAV/Notifications/Node.php | 193 ++ .../dav/lib/CalDAV/Notifications/Plugin.php | 180 ++ .../vendor/sabre/dav/lib/CalDAV/Plugin.php | 992 ++++++ .../dav/lib/CalDAV/Principal/Collection.php | 33 + .../dav/lib}/CalDAV/Principal/IProxyRead.php | 0 .../dav/lib}/CalDAV/Principal/IProxyWrite.php | 0 .../dav/lib/CalDAV/Principal/ProxyRead.php | 181 ++ .../dav/lib/CalDAV/Principal/ProxyWrite.php | 181 ++ .../sabre/dav/lib/CalDAV/Principal/User.php | 135 + .../sabre/dav/lib/CalDAV/Schedule/IInbox.php | 15 + .../dav/lib/CalDAV/Schedule/IMipPlugin.php | 190 ++ .../dav/lib}/CalDAV/Schedule/IOutbox.php | 0 .../lib/CalDAV/Schedule/ISchedulingObject.php | 13 + .../sabre/dav/lib/CalDAV/Schedule/Inbox.php | 268 ++ .../sabre/dav/lib/CalDAV/Schedule/Outbox.php | 184 ++ .../sabre/dav/lib/CalDAV/Schedule/Plugin.php | 897 ++++++ .../lib/CalDAV/Schedule/SchedulingObject.php | 165 + .../dav/lib}/CalDAV/ShareableCalendar.php | 0 .../sabre/dav/lib/CalDAV/SharedCalendar.php | 148 + .../sabre/dav/lib/CalDAV/SharingPlugin.php | 426 +++ .../CalDAV/Subscriptions/ISubscription.php | 0 .../dav/lib/CalDAV/Subscriptions/Plugin.php | 120 + .../lib/CalDAV/Subscriptions/Subscription.php | 274 ++ .../lib/CalDAV/Xml/Filter/CalendarData.php | 84 + .../dav/lib/CalDAV/Xml/Filter/CompFilter.php | 97 + .../dav/lib/CalDAV/Xml/Filter/ParamFilter.php | 82 + .../dav/lib/CalDAV/Xml/Filter/PropFilter.php | 98 + .../lib/CalDAV/Xml/Notification/Invite.php | 307 ++ .../CalDAV/Xml/Notification/InviteReply.php | 212 ++ .../Notification/NotificationInterface.php | 45 + .../CalDAV/Xml/Notification/SystemStatus.php | 182 ++ .../Xml/Property/AllowedSharingModes.php | 87 + .../CalDAV/Xml/Property/EmailAddressSet.php | 80 + .../dav/lib/CalDAV/Xml/Property/Invite.php | 252 ++ .../Xml/Property/ScheduleCalendarTransp.php | 140 + .../SupportedCalendarComponentSet.php | 129 + .../Xml/Property/SupportedCalendarData.php | 60 + .../Xml/Property/SupportedCollationSet.php | 57 + .../Xml/Request/CalendarMultiGetReport.php | 124 + .../Xml/Request/CalendarQueryReport.php | 139 + .../Xml/Request/FreeBusyQueryReport.php | 91 + .../lib/CalDAV/Xml/Request/InviteReply.php | 149 + .../dav/lib/CalDAV/Xml/Request/MkCalendar.php | 79 + .../dav/lib/CalDAV/Xml/Request/Share.php | 116 + .../sabre/dav/lib/CardDAV/AddressBook.php | 433 +++ .../sabre/dav/lib/CardDAV/AddressBookHome.php | 263 ++ .../sabre/dav/lib/CardDAV/AddressBookRoot.php | 80 + .../lib}/CardDAV/Backend/AbstractBackend.php | 0 .../lib}/CardDAV/Backend/BackendInterface.php | 0 .../sabre/dav/lib/CardDAV/Backend/PDO.php | 545 ++++ .../dav/lib}/CardDAV/Backend/SyncSupport.php | 0 .../vendor/sabre/dav/lib/CardDAV/Card.php | 263 ++ .../sabre/dav/lib}/CardDAV/IAddressBook.php | 0 .../vendor/sabre/dav/lib/CardDAV/ICard.php | 19 + .../sabre/dav/lib}/CardDAV/IDirectory.php | 0 .../vendor/sabre/dav/lib/CardDAV/Plugin.php | 851 ++++++ .../sabre/dav/lib/CardDAV/VCFExportPlugin.php | 149 + .../lib/CardDAV/Xml/Filter/AddressData.php | 59 + .../lib/CardDAV/Xml/Filter/ParamFilter.php | 89 + .../dav/lib/CardDAV/Xml/Filter/PropFilter.php | 98 + .../Xml/Property/SupportedAddressData.php | 83 + .../Xml/Property/SupportedCollationSet.php | 47 + .../Xml/Request/AddressBookMultiGetReport.php | 113 + .../Xml/Request/AddressBookQueryReport.php | 192 ++ .../lib/DAV/Auth/Backend/AbstractBasic.php | 144 + .../lib/DAV/Auth/Backend/AbstractDigest.php | 162 + .../sabre/dav/lib/DAV/Auth/Backend/Apache.php | 96 + .../lib/DAV/Auth/Backend/BackendInterface.php | 70 + .../lib/DAV/Auth/Backend/BasicCallBack.php | 58 + .../sabre/dav/lib/DAV/Auth/Backend/File.php | 77 + .../sabre/dav/lib/DAV/Auth/Backend/PDO.php | 57 + .../vendor/sabre/dav/lib/DAV/Auth/Plugin.php | 213 ++ .../dav/lib/DAV/Browser/GuessContentType.php | 101 + .../sabre/dav/lib/DAV/Browser/HtmlOutput.php | 34 + .../dav/lib/DAV/Browser/HtmlOutputHelper.php | 117 + .../dav/lib/DAV/Browser/MapGetToPropFind.php | 60 + .../sabre/dav/lib/DAV/Browser/Plugin.php | 797 +++++ .../sabre/dav/lib/DAV/Browser/PropFindAll.php | 132 + .../dav/lib}/DAV/Browser/assets/favicon.ico | Bin .../Browser/assets/openiconic/ICON-LICENSE | 0 .../Browser/assets/openiconic/open-iconic.css | 0 .../Browser/assets/openiconic/open-iconic.eot | Bin .../Browser/assets/openiconic/open-iconic.otf | Bin .../Browser/assets/openiconic/open-iconic.svg | 0 .../Browser/assets/openiconic/open-iconic.ttf | Bin .../assets/openiconic/open-iconic.woff | Bin .../dav/lib/DAV/Browser/assets/sabredav.css | 228 ++ .../dav/lib}/DAV/Browser/assets/sabredav.png | Bin .../vendor/sabre/dav/lib/DAV/Client.php | 417 +++ .../vendor/sabre/dav/lib/DAV/Collection.php | 109 + .../vendor/sabre/dav/lib/DAV/CorePlugin.php | 927 ++++++ .../vendor/sabre/dav/lib/DAV/Exception.php | 57 + .../dav/lib/DAV/Exception/BadRequest.php | 30 + .../sabre/dav/lib/DAV/Exception/Conflict.php | 30 + .../dav/lib/DAV/Exception/ConflictingLock.php | 36 + .../sabre/dav/lib/DAV/Exception/Forbidden.php | 29 + .../lib/DAV/Exception/InsufficientStorage.php | 29 + .../lib/DAV/Exception/InvalidResourceType.php | 33 + .../lib/DAV/Exception/InvalidSyncToken.php | 38 + .../dav/lib}/DAV/Exception/LengthRequired.php | 0 .../Exception/LockTokenMatchesRequestUri.php | 41 + .../sabre/dav/lib/DAV/Exception/Locked.php | 72 + .../lib/DAV/Exception/MethodNotAllowed.php | 47 + .../lib}/DAV/Exception/NotAuthenticated.php | 0 .../sabre/dav/lib/DAV/Exception/NotFound.php | 29 + .../dav/lib/DAV/Exception/NotImplemented.php | 29 + .../lib}/DAV/Exception/PaymentRequired.php | 0 .../lib/DAV/Exception/PreconditionFailed.php | 71 + .../lib/DAV/Exception/ReportNotSupported.php | 32 + .../RequestedRangeNotSatisfiable.php | 30 + .../lib}/DAV/Exception/ServiceUnavailable.php | 0 .../dav/lib/DAV/Exception/TooManyMatches.php | 38 + .../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 + .../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 + .../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 + .../lib/DAV/Locks/Backend/AbstractBackend.php | 18 + .../DAV/Locks/Backend/BackendInterface.php | 50 + .../sabre/dav/lib/DAV/Locks/Backend/File.php | 185 ++ .../sabre/dav/lib/DAV/Locks/Backend/PDO.php | 180 ++ .../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 + .../lib/DAV/PartialUpdate/IPatchSupport.php | 47 + .../dav/lib/DAV/PartialUpdate/Plugin.php | 215 ++ .../vendor/sabre/dav/lib/DAV/PropFind.php | 347 +++ .../vendor/sabre/dav/lib/DAV/PropPatch.php | 366 +++ .../Backend/BackendInterface.php | 80 + .../lib/DAV/PropertyStorage/Backend/PDO.php | 210 ++ .../dav/lib/DAV/PropertyStorage/Plugin.php | 180 ++ .../vendor/sabre/dav/lib/DAV/Server.php | 1611 ++++++++++ .../vendor/sabre/dav/lib/DAV/ServerPlugin.php | 110 + .../sabre/dav/lib/DAV/SimpleCollection.php | 107 + .../vendor/sabre/dav/lib/DAV/SimpleFile.php | 121 + .../vendor/sabre/dav/lib/DAV/StringUtil.php | 91 + .../dav/lib/DAV/Sync/ISyncCollection.php | 88 + .../vendor/sabre/dav/lib/DAV/Sync/Plugin.php | 277 ++ .../dav/lib/DAV/TemporaryFileFilterPlugin.php | 294 ++ .../vendor/sabre/dav/lib/DAV/Tree.php | 340 +++ .../vendor/sabre/dav/lib/DAV/UUIDUtil.php | 64 + .../vendor/sabre/dav/lib/DAV/Version.php | 19 + .../sabre/dav/lib/DAV/Xml/Element/Prop.php | 116 + .../dav/lib/DAV/Xml/Element/Response.php | 253 ++ .../dav/lib/DAV/Xml/Property/Complex.php | 89 + .../lib/DAV/Xml/Property/GetLastModified.php | 110 + .../sabre/dav/lib/DAV/Xml/Property/Href.php | 176 ++ .../lib/DAV/Xml/Property/LockDiscovery.php | 106 + .../dav/lib/DAV/Xml/Property/ResourceType.php | 128 + .../lib/DAV/Xml/Property/SupportedLock.php | 54 + .../DAV/Xml/Property/SupportedMethodSet.php | 126 + .../DAV/Xml/Property/SupportedReportSet.php | 154 + .../sabre/dav/lib/DAV/Xml/Request/Lock.php | 81 + .../sabre/dav/lib/DAV/Xml/Request/MkCol.php | 82 + .../dav/lib/DAV/Xml/Request/PropFind.php | 83 + .../dav/lib/DAV/Xml/Request/PropPatch.php | 118 + .../DAV/Xml/Request/SyncCollectionReport.php | 122 + .../dav/lib/DAV/Xml/Response/MultiStatus.php | 142 + .../vendor/sabre/dav/lib/DAV/Xml/Service.php | 47 + .../DAVACL/AbstractPrincipalCollection.php | 181 ++ .../dav/lib/DAVACL/Exception/AceConflict.php | 35 + .../lib/DAVACL/Exception/NeedPrivileges.php | 82 + .../dav/lib/DAVACL/Exception/NoAbstract.php | 35 + .../Exception/NotRecognizedPrincipal.php | 35 + .../Exception/NotSupportedPrivilege.php | 35 + .../sabre/dav/lib/DAVACL/FS/Collection.php | 153 + .../vendor/sabre/dav/lib/DAVACL/FS/File.php | 123 + .../dav/lib/DAVACL/FS/HomeCollection.php | 188 ++ .../vendor/sabre/dav/lib/DAVACL/IACL.php | 75 + .../sabre/dav/lib}/DAVACL/IPrincipal.php | 0 .../dav/lib}/DAVACL/IPrincipalCollection.php | 0 .../vendor/sabre/dav/lib/DAVACL/Plugin.php | 1322 ++++++++ .../vendor/sabre/dav/lib/DAVACL/Principal.php | 288 ++ .../PrincipalBackend/AbstractBackend.php | 53 + .../PrincipalBackend/BackendInterface.php | 0 .../CreatePrincipalSupport.php | 30 + .../dav/lib/DAVACL/PrincipalBackend/PDO.php | 386 +++ .../dav/lib/DAVACL/PrincipalCollection.php | 151 + .../sabre/dav/lib/DAVACL/Xml/Property/Acl.php | 277 ++ .../DAVACL/Xml/Property/AclRestrictions.php | 45 + .../Xml/Property/CurrentUserPrivilegeSet.php | 159 + .../dav/lib/DAVACL/Xml/Property/Principal.php | 196 ++ .../Xml/Property/SupportedPrivilegeSet.php | 159 + .../Xml/Request/ExpandPropertyReport.php | 103 + .../Request/PrincipalPropertySearchReport.php | 127 + .../PrincipalSearchPropertySetReport.php | 58 + .../Sabre/CalDAV/Backend/AbstractPDOTest.php | 883 ++++++ .../Sabre/CalDAV/Backend/AbstractTest.php | 178 ++ .../dav/tests/Sabre/CalDAV/Backend/Mock.php | 214 ++ .../Sabre/CalDAV/Backend/MockScheduling.php | 93 + .../Sabre/CalDAV/Backend/MockSharing.php | 172 ++ .../Backend/MockSubscriptionSupport.php | 156 + .../Sabre/CalDAV/Backend/PDOMySQLTest.php | 39 + .../Sabre/CalDAV/Backend/PDOSqliteTest.php | 37 + .../CalDAV/CalendarHomeNotificationsTest.php | 53 + .../CalendarHomeSharedCalendarsTest.php | 89 + .../CalDAV/CalendarHomeSubscriptionsTest.php | 87 + .../tests/Sabre/CalDAV/CalendarHomeTest.php | 218 ++ .../tests/Sabre/CalDAV/CalendarObjectTest.php | 395 +++ .../Sabre/CalDAV/CalendarQueryVAlarmTest.php | 122 + .../CalDAV/CalendarQueryValidatorTest.php | 826 +++++ .../dav/tests/Sabre/CalDAV/CalendarTest.php | 289 ++ .../ExpandEventsDTSTARTandDTENDTest.php | 114 + .../ExpandEventsDTSTARTandDTENDbyDayTest.php | 103 + .../CalDAV/ExpandEventsDoubleEventsTest.php | 104 + .../CalDAV/ExpandEventsFloatingTimeTest.php | 210 ++ .../tests/Sabre/CalDAV/FreeBusyReportTest.php | 176 ++ .../Sabre/CalDAV/GetEventsByTimerangeTest.php | 98 + .../Sabre/CalDAV/ICSExportPluginTest.php | 662 ++++ .../dav/tests/Sabre/CalDAV/Issue166Test.php | 63 + .../dav/tests/Sabre/CalDAV/Issue172Test.php | 135 + .../dav/tests/Sabre/CalDAV/Issue203Test.php | 138 + .../dav/tests/Sabre/CalDAV/Issue205Test.php | 97 + .../dav/tests/Sabre/CalDAV/Issue211Test.php | 89 + .../dav/tests/Sabre/CalDAV/Issue220Test.php | 99 + .../dav/tests/Sabre/CalDAV/Issue228Test.php | 78 + .../tests/Sabre/CalDAV/JCalTransformTest.php | 243 ++ .../CalDAV/Notifications/CollectionTest.php | 90 + .../Sabre/CalDAV/Notifications/NodeTest.php | 101 + .../Sabre/CalDAV/Notifications/PluginTest.php | 169 ++ .../dav/tests/Sabre/CalDAV/PluginTest.php | 1145 +++++++ .../Sabre/CalDAV/Principal/CollectionTest.php | 19 + .../Sabre/CalDAV/Principal/ProxyReadTest.php | 101 + .../Sabre/CalDAV/Principal/ProxyWriteTest.php | 39 + .../tests/Sabre/CalDAV/Principal/UserTest.php | 126 + .../CalDAV/Schedule/DeliverNewEventTest.php | 93 + .../CalDAV/Schedule/FreeBusyRequestTest.php | 426 +++ .../Sabre/CalDAV/Schedule/IMip/MockPlugin.php | 50 + .../Sabre/CalDAV/Schedule/IMipPluginTest.php | 221 ++ .../tests/Sabre/CalDAV/Schedule/InboxTest.php | 186 ++ .../Sabre/CalDAV/Schedule/OutboxPostTest.php | 136 + .../Sabre/CalDAV/Schedule/OutboxTest.php | 89 + .../Sabre/CalDAV/Schedule/PluginBasicTest.php | 35 + .../CalDAV/Schedule/PluginPropertiesTest.php | 65 + ...PluginPropertiesWithSharedCalendarTest.php | 75 + .../CalDAV/Schedule/ScheduleDeliverTest.php | 664 +++++ .../CalDAV/Schedule/SchedulingObjectTest.php | 398 +++ .../Sabre/CalDAV/ShareableCalendarTest.php | 60 + .../tests/Sabre/CalDAV/SharedCalendarTest.php | 203 ++ .../tests/Sabre/CalDAV/SharingPluginTest.php | 389 +++ .../Subscriptions/CreateSubscriptionTest.php | 123 + .../Sabre/CalDAV/Subscriptions/PluginTest.php | 50 + .../CalDAV/Subscriptions/SubscriptionTest.php | 141 + .../sabre/dav/tests/Sabre/CalDAV/TestUtil.php | 208 ++ .../tests/Sabre/CalDAV/ValidateICalTest.php | 281 ++ .../Xml/Notification/InviteReplyTest.php | 145 + .../CalDAV/Xml/Notification/InviteTest.php | 236 ++ .../Xml/Notification/SystemStatusTest.php | 67 + .../Xml/Property/AllowedSharingModesTest.php | 38 + .../Xml/Property/EmailAddressSetTest.php | 40 + .../Sabre/CalDAV/Xml/Property/InviteTest.php | 185 ++ .../Property/ScheduleCalendarTranspTest.php | 118 + .../SupportedCalendarComponentSetTest.php | 107 + .../Property/SupportedCalendarDataTest.php | 36 + .../Property/SupportedCollationSetTest.php | 37 + .../Xml/Request/CalendarQueryReportTest.php | 367 +++ .../CalDAV/Xml/Request/InviteReplyTest.php | 78 + .../Sabre/CalDAV/Xml/Request/ShareTest.php | 86 + .../Sabre/CardDAV/AbstractPluginTest.php | 43 + .../Sabre/CardDAV/AddressBookHomeTest.php | 164 + .../Sabre/CardDAV/AddressBookQueryTest.php | 310 ++ .../Sabre/CardDAV/AddressBookRootTest.php | 31 + .../tests/Sabre/CardDAV/AddressBookTest.php | 209 ++ .../Sabre/CardDAV/Backend/AbstractPDOTest.php | 350 +++ .../dav/tests/Sabre/CardDAV/Backend/Mock.php | 148 + .../Sabre/CardDAV/Backend/PDOMySQLTest.php | 36 + .../Sabre/CardDAV/Backend/PDOSqliteTest.php | 45 + .../dav/tests/Sabre/CardDAV/CardTest.php | 215 ++ .../tests/Sabre/CardDAV/IDirectoryTest.php | 30 + .../dav/tests/Sabre/CardDAV/MultiGetTest.php | 99 + .../dav/tests/Sabre/CardDAV/PluginTest.php | 103 + .../CardDAV/SogoStripContentTypeTest.php | 56 + .../dav/tests/Sabre/CardDAV/TestUtil.php | 68 + .../dav/tests/Sabre/CardDAV/VCFExportTest.php | 75 + .../Sabre/CardDAV/ValidateFilterTest.php | 204 ++ .../tests/Sabre/CardDAV/ValidateVCardTest.php | 172 ++ .../Xml/Property/SupportedAddressDataTest.php | 38 + .../Property/SupportedCollationSetTest.php | 38 + .../Request/AddressBookQueryReportTest.php | 302 ++ .../dav/tests/Sabre/DAV/AbstractServer.php | 64 + .../DAV/Auth/Backend/AbstractBasicTest.php | 94 + .../DAV/Auth/Backend/AbstractDigestTest.php | 144 + .../DAV/Auth/Backend/AbstractPDOTest.php | 35 + .../Sabre/DAV/Auth/Backend/ApacheTest.php | 72 + .../DAV/Auth/Backend/BasicCallBackTest.php | 38 + .../tests/Sabre/DAV/Auth/Backend/FileTest.php | 42 + .../dav/tests/Sabre/DAV/Auth/Backend/Mock.php | 87 + .../Sabre/DAV/Auth/Backend/PDOMySQLTest.php | 31 + .../Sabre/DAV/Auth/Backend/PDOSqliteTest.php | 28 + .../dav/tests/Sabre/DAV/Auth/PluginTest.php | 130 + .../dav/tests/Sabre/DAV/BasicNodeTest.php | 235 ++ .../DAV/Browser/GuessContentTypeTest.php | 70 + .../DAV/Browser/MapGetToPropFindTest.php | 44 + .../tests/Sabre/DAV/Browser/PluginTest.php | 186 ++ .../Sabre/DAV/Browser/PropFindAllTest.php | 70 + .../sabre/dav/tests/Sabre/DAV/ClientMock.php | 34 + .../sabre/dav/tests/Sabre/DAV/ClientTest.php | 260 ++ .../sabre/dav/tests/Sabre/DAV/CopyTest.php | 37 + .../tests/Sabre/DAV/Exception/LockedTest.php | 68 + .../DAV/Exception/PaymentRequiredTest.php | 14 + .../DAV/Exception/ServiceUnavailableTest.php | 14 + .../DAV/Exception/TooManyMatchesTest.php | 36 + .../dav/tests/Sabre/DAV/ExceptionTest.php | 30 + .../dav/tests/Sabre/DAV/FSExt/FileTest.php | 112 + .../dav/tests/Sabre/DAV/FSExt/ServerTest.php | 246 ++ .../tests/Sabre/DAV/GetIfConditionsTest.php | 337 +++ .../tests/Sabre/DAV/HTTPPreferParsingTest.php | 188 ++ .../dav/tests/Sabre/DAV/HttpDeleteTest.php | 137 + .../sabre/dav/tests/Sabre/DAV/HttpGetTest.php | 158 + .../dav/tests/Sabre/DAV/HttpHeadTest.php | 97 + .../dav/tests/Sabre/DAV/HttpMoveTest.php | 119 + .../sabre/dav/tests/Sabre/DAV/HttpPutTest.php | 349 +++ .../sabre/dav/tests/Sabre/DAV/Issue33Test.php | 106 + .../Sabre/DAV/Locks/Backend/AbstractTest.php | 196 ++ .../Sabre/DAV/Locks/Backend/FileTest.php | 24 + .../tests/Sabre/DAV/Locks/Backend/Mock.php | 139 + .../Sabre/DAV/Locks/Backend/PDOMySQLTest.php | 32 + .../tests/Sabre/DAV/Locks/Backend/PDOTest.php | 29 + .../dav/tests/Sabre/DAV/Locks/MSWordTest.php | 124 + .../dav/tests/Sabre/DAV/Locks/Plugin2Test.php | 69 + .../dav/tests/Sabre/DAV/Locks/PluginTest.php | 994 ++++++ .../dav/tests/Sabre/DAV/Mock/Collection.php | 156 + .../sabre/dav/tests/Sabre/DAV/Mock/File.php | 141 + .../Sabre/DAV/Mock/PropertiesCollection.php | 94 + .../tests/Sabre/DAV/Mock/StreamingFile.php | 88 + .../dav/tests/Sabre/DAV/Mount/PluginTest.php | 58 + .../dav/tests/Sabre/DAV/ObjectTreeTest.php | 100 + .../Sabre/DAV/PartialUpdate/FileMock.php | 121 + .../Sabre/DAV/PartialUpdate/PluginTest.php | 135 + .../DAV/PartialUpdate/SpecificationTest.php | 89 + .../dav/tests/Sabre/DAV/PropFindTest.php | 76 + .../dav/tests/Sabre/DAV/PropPatchTest.php | 357 +++ .../Backend/AbstractPDOTest.php | 190 ++ .../DAV/PropertyStorage/Backend/Mock.php | 114 + .../PropertyStorage/Backend/PDOMysqlTest.php | 31 + .../PropertyStorage/Backend/PDOSqliteTest.php | 37 + .../Sabre/DAV/PropertyStorage/PluginTest.php | 117 + .../tests/Sabre/DAV/ServerCopyMoveTest.php | 209 ++ .../dav/tests/Sabre/DAV/ServerEventsTest.php | 121 + .../dav/tests/Sabre/DAV/ServerMKCOLTest.php | 346 +++ .../dav/tests/Sabre/DAV/ServerPluginTest.php | 107 + .../Sabre/DAV/ServerPreconditionTest.php | 346 +++ .../DAV/ServerPropsInfiniteDepthTest.php | 166 ++ .../dav/tests/Sabre/DAV/ServerPropsTest.php | 200 ++ .../dav/tests/Sabre/DAV/ServerRangeTest.php | 281 ++ .../dav/tests/Sabre/DAV/ServerSimpleTest.php | 462 +++ .../Sabre/DAV/ServerUpdatePropertiesTest.php | 127 + .../dav/tests/Sabre/DAV/SimpleFileTest.php | 19 + .../dav/tests/Sabre/DAV/StringUtilTest.php | 122 + .../Sabre/DAV/Sync/MockSyncCollection.php | 169 ++ .../dav/tests/Sabre/DAV/Sync/PluginTest.php | 524 ++++ .../tests/Sabre/DAV/SyncTokenPropertyTest.php | 100 + .../Sabre/DAV/TemporaryFileFilterTest.php | 199 ++ .../sabre/dav/tests/Sabre/DAV/TestPlugin.php | 38 + .../sabre/dav/tests/Sabre/DAV/TreeTest.php | 241 ++ .../dav/tests/Sabre/DAV/UUIDUtilTest.php | 25 + .../tests/Sabre/DAV/Xml/Element/PropTest.php | 154 + .../Sabre/DAV/Xml/Element/ResponseTest.php | 313 ++ .../tests/Sabre/DAV/Xml/Property/HrefTest.php | 138 + .../DAV/Xml/Property/LastModifiedTest.php | 59 + .../DAV/Xml/Property/LockDiscoveryTest.php | 86 + .../Xml/Property/SupportedMethodSetTest.php | 62 + .../Xml/Property/SupportedReportSetTest.php | 115 + .../Sabre/DAV/Xml/Request/PropFindTest.php | 48 + .../Sabre/DAV/Xml/Request/PropPatchTest.php | 53 + .../DAV/Xml/Request/SyncCollectionTest.php | 94 + .../sabre/dav/tests/Sabre/DAV/Xml/XmlTest.php | 41 + .../dav/tests/Sabre/DAVACL/ACLMethodTest.php | 324 ++ .../tests/Sabre/DAVACL/AllowAccessTest.php | 131 + .../tests/Sabre/DAVACL/BlockAccessTest.php | 206 ++ .../DAVACL/Exception/AceConflictTest.php | 39 + .../Exception/NeedPrivilegesExceptionTest.php | 49 + .../Sabre/DAVACL/Exception/NoAbstractTest.php | 39 + .../Exception/NotRecognizedPrincipalTest.php | 39 + .../Exception/NotSupportedPrivilegeTest.php | 39 + .../Sabre/DAVACL/ExpandPropertiesTest.php | 310 ++ .../tests/Sabre/DAVACL/FS/CollectionTest.php | 44 + .../dav/tests/Sabre/DAVACL/FS/FileTest.php | 73 + .../Sabre/DAVACL/FS/HomeCollectionTest.php | 121 + .../dav/tests/Sabre/DAVACL/MockACLNode.php | 56 + .../dav/tests/Sabre/DAVACL/MockPrincipal.php | 66 + .../tests/Sabre/DAVACL/PluginAdminTest.php | 85 + .../Sabre/DAVACL/PluginPropertiesTest.php | 352 +++ .../DAVACL/PluginUpdatePropertiesTest.php | 111 + .../PrincipalBackend/AbstractPDOTest.php | 178 ++ .../Sabre/DAVACL/PrincipalBackend/Mock.php | 168 ++ .../DAVACL/PrincipalBackend/PDOMySQLTest.php | 45 + .../DAVACL/PrincipalBackend/PDOSqliteTest.php | 42 + .../Sabre/DAVACL/PrincipalCollectionTest.php | 61 + .../DAVACL/PrincipalPropertySearchTest.php | 396 +++ .../DAVACL/PrincipalSearchPropertySetTest.php | 139 + .../dav/tests/Sabre/DAVACL/PrincipalTest.php | 208 ++ .../tests/Sabre/DAVACL/SimplePluginTest.php | 327 ++ .../Sabre/DAVACL/Xml/Property/ACLTest.php | 342 +++ .../Xml/Property/AclRestrictionsTest.php | 30 + .../Property/CurrentUserPrivilegeSetTest.php | 86 + .../DAVACL/Xml/Property/PrincipalTest.php | 191 ++ .../Property/SupportedPrivilegeSetTest.php | 120 + .../sabre/dav/tests/Sabre/DAVServerTest.php | 239 ++ .../dav/tests/Sabre/HTTP/ResponseMock.php | 22 + .../sabre/dav/tests/Sabre/HTTP/SapiMock.php | 29 + .../vendor/sabre/dav/tests/Sabre/TestUtil.php | 58 + .../vendor/sabre/dav/tests/bootstrap.php | 29 + .../vendor/sabre/dav/tests/phpcs/ruleset.xml | 57 + .../vendor/sabre/dav/tests/phpunit.xml | 47 + .../SabreDAV/vendor/sabre/event/.gitignore | 14 + .../SabreDAV/vendor/sabre/event/.travis.yml | 18 + .../SabreDAV/vendor/sabre/event/CHANGELOG.md | 51 + .../SabreDAV/vendor/sabre/event/LICENSE | 27 + .../SabreDAV/vendor/sabre/event/README.md | 49 + .../SabreDAV/vendor/sabre/event/bin/.empty | 0 .../SabreDAV/vendor/sabre/event/composer.json | 41 + .../vendor/sabre/event/lib}/EventEmitter.php | 0 .../event/lib}/EventEmitterInterface.php | 0 .../sabre/event/lib}/EventEmitterTrait.php | 0 .../vendor/sabre/event/lib}/Promise.php | 0 .../lib}/PromiseAlreadyResolvedException.php | 0 .../vendor/sabre/event/lib}/Version.php | 0 .../vendor/sabre/event/phpunit.xml.dist | 18 + .../event/tests/ContinueCallbackTest.php | 76 + .../sabre/event/tests/EventEmitterTest.php | 318 ++ .../vendor/sabre/event/tests/PromiseTest.php | 226 ++ .../sabre/event/tests/benchmark/bench.php | 116 + .../SabreDAV/vendor/sabre/http/.gitignore | 15 + .../SabreDAV/vendor/sabre/http/.travis.yml | 24 + .../SabreDAV/vendor/sabre/http/CHANGELOG.md | 250 ++ .../SabreDAV/vendor/sabre/http/LICENSE | 27 + .../SabreDAV/vendor/sabre/http/README.md | 746 +++++ .../SabreDAV/vendor/sabre/http/bin/.empty | 0 .../SabreDAV/vendor/sabre/http/composer.json | 43 + .../sabre/http/examples/asyncclient.php | 65 + .../vendor/sabre/http/examples/basicauth.php | 55 + .../vendor/sabre/http/examples/client.php | 38 + .../vendor/sabre/http/examples/digestauth.php | 56 + .../sabre/http/examples/reverseproxy.php | 50 + .../vendor/sabre/http/examples/stringify.php | 51 + .../vendor/sabre/http/lib/Auth/AWS.php | 234 ++ .../sabre/http/lib/Auth/AbstractAuth.php | 73 + .../vendor/sabre/http/lib/Auth/Basic.php | 63 + .../vendor/sabre/http/lib/Auth/Bearer.php | 56 + .../vendor/sabre/http/lib/Auth/Digest.php | 231 ++ .../SabreDAV/vendor/sabre/http/lib/Client.php | 601 ++++ .../vendor/sabre/http/lib/ClientException.php | 15 + .../sabre/http/lib/ClientHttpException.php | 58 + .../vendor/sabre/http/lib/HttpException.php | 30 + .../vendor/sabre/http/lib/Message.php | 314 ++ .../sabre/http/lib/MessageDecoratorTrait.php | 250 ++ .../sabre/http/lib/MessageInterface.php | 177 ++ .../vendor/sabre/http/lib/Request.php | 316 ++ .../sabre/http/lib/RequestDecorator.php | 231 ++ .../sabre/http/lib/RequestInterface.php | 147 + .../vendor/sabre/http/lib/Response.php | 194 ++ .../sabre/http/lib/ResponseDecorator.php | 84 + .../sabre/http/lib/ResponseInterface.php | 45 + .../SabreDAV/vendor/sabre/http/lib/Sapi.php | 194 ++ .../vendor/sabre/http/lib/URLUtil.php | 103 + .../SabreDAV/vendor/sabre/http/lib/Util.php | 74 + .../vendor/sabre/http/lib/Version.php | 19 + .../vendor/sabre/http/lib/functions.php | 445 +++ .../sabre/http/tests/HTTP/Auth/AWSTest.php | 235 ++ .../sabre/http/tests/HTTP/Auth/BasicTest.php | 69 + .../sabre/http/tests/HTTP/Auth/BearerTest.php | 57 + .../sabre/http/tests/HTTP/Auth/DigestTest.php | 191 ++ .../sabre/http/tests/HTTP/ClientTest.php | 474 +++ .../sabre/http/tests/HTTP/FunctionsTest.php | 121 + .../http/tests/HTTP/MessageDecoratorTest.php | 90 + .../sabre/http/tests/HTTP/MessageTest.php | 224 ++ .../http/tests/HTTP/RequestDecoratorTest.php | 112 + .../sabre/http/tests/HTTP/RequestTest.php | 167 ++ .../http/tests/HTTP/ResponseDecoratorTest.php | 37 + .../sabre/http/tests/HTTP/ResponseTest.php | 48 + .../vendor/sabre/http/tests/HTTP/SapiTest.php | 167 ++ .../sabre/http/tests/HTTP/URLUtilTest.php | 187 ++ .../vendor/sabre/http/tests/HTTP/UtilTest.php | 206 ++ .../vendor/sabre/http/tests/bootstrap.php | 8 + .../vendor/sabre/http/tests/phpcs/ruleset.xml | 57 + .../vendor/sabre/http/tests/phpunit.xml | 18 + .../SabreDAV/vendor/sabre/uri/.gitignore | 13 + .../SabreDAV/vendor/sabre/uri/.travis.yml | 14 + .../SabreDAV/vendor/sabre/uri/CHANGELOG.md | 34 + .../SabreDAV/vendor/sabre/uri/LICENSE | 27 + .../SabreDAV/vendor/sabre/uri/README.md | 55 + .../SabreDAV/vendor/sabre/uri/composer.json | 41 + .../SabreDAV/vendor/sabre/uri/lib/Version.php | 19 + .../vendor/sabre/uri/lib/functions.php | 282 ++ .../vendor/sabre/uri/tests/BuildTest.php | 39 + .../vendor/sabre/uri/tests/NormalizeTest.php | 42 + .../vendor/sabre/uri/tests/ParseTest.php | 53 + .../vendor/sabre/uri/tests/ResolveTest.php | 83 + .../vendor/sabre/uri/tests/SplitTest.php | 41 + .../vendor/sabre/uri/tests/phpcs/ruleset.xml | 57 + .../vendor/sabre/uri/tests/phpunit.xml.dist | 18 + .../SabreDAV/vendor/sabre/vobject/.gitignore | 18 + .../SabreDAV/vendor/sabre/vobject/.travis.yml | 19 + .../vendor/sabre/vobject/ChangeLog.md | 610 ++++ .../SabreDAV/vendor/sabre/vobject/LICENSE | 27 + .../SabreDAV/vendor/sabre/vobject/README.md | 50 + .../vendor/sabre/vobject/bin/bench.php | 12 + .../sabre/vobject/bin/fetch_windows_zones.php | 47 + .../vendor/sabre/vobject/bin/generate_vcards | 241 ++ .../vobject/bin/generateicalendardata.php | 91 + .../vendor/sabre/vobject/bin/rrulebench.php | 32 + .../SabreDAV/vendor/sabre/vobject/bin/vobject | 27 + .../vendor/sabre/vobject/composer.json | 50 + .../vendor/sabre/vobject/lib}/Cli.php | 0 .../vendor/sabre/vobject/lib}/Component.php | 0 .../vobject/lib}/Component/Available.php | 0 .../sabre/vobject/lib}/Component/VAlarm.php | 0 .../vobject/lib}/Component/VAvailability.php | 0 .../vobject/lib}/Component/VCalendar.php | 0 .../sabre/vobject/lib}/Component/VCard.php | 0 .../sabre/vobject/lib}/Component/VEvent.php | 0 .../vobject/lib}/Component/VFreeBusy.php | 0 .../sabre/vobject/lib}/Component/VJournal.php | 0 .../vobject/lib}/Component/VTimeZone.php | 0 .../sabre/vobject/lib}/Component/VTodo.php | 0 .../sabre/vobject/lib}/DateTimeParser.php | 0 .../vendor/sabre/vobject/lib}/Document.php | 0 .../vendor/sabre/vobject/lib}/ElementList.php | 0 .../sabre/vobject/lib}/EofException.php | 0 .../sabre/vobject/lib}/FreeBusyGenerator.php | 0 .../vendor/sabre/vobject/lib/ITip/Broker.php | 976 ++++++ .../sabre/vobject/lib}/ITip/ITipException.php | 0 .../sabre/vobject/lib}/ITip/Message.php | 0 ...SameOrganizerForAllComponentsException.php | 0 .../vendor/sabre/vobject/lib}/Node.php | 0 .../vendor/sabre/vobject/lib}/Parameter.php | 0 .../sabre/vobject/lib}/ParseException.php | 0 .../vendor/sabre/vobject/lib}/Parser/Json.php | 0 .../sabre/vobject/lib}/Parser/MimeDir.php | 0 .../sabre/vobject/lib}/Parser/Parser.php | 0 .../vendor/sabre/vobject/lib}/Property.php | 0 .../sabre/vobject/lib}/Property/Binary.php | 0 .../sabre/vobject/lib}/Property/Boolean.php | 0 .../sabre/vobject/lib}/Property/FlatText.php | 0 .../vobject/lib}/Property/FloatValue.php | 0 .../lib}/Property/ICalendar/CalAddress.php | 0 .../vobject/lib}/Property/ICalendar/Date.php | 0 .../lib}/Property/ICalendar/DateTime.php | 0 .../lib}/Property/ICalendar/Duration.php | 0 .../lib}/Property/ICalendar/Period.php | 0 .../vobject/lib}/Property/ICalendar/Recur.php | 0 .../vobject/lib}/Property/IntegerValue.php | 0 .../sabre/vobject/lib}/Property/Text.php | 0 .../sabre/vobject/lib}/Property/Time.php | 0 .../sabre/vobject/lib}/Property/Unknown.php | 0 .../sabre/vobject/lib}/Property/Uri.php | 0 .../sabre/vobject/lib}/Property/UtcOffset.php | 0 .../vobject/lib}/Property/VCard/Date.php | 0 .../lib}/Property/VCard/DateAndOrTime.php | 0 .../vobject/lib}/Property/VCard/DateTime.php | 0 .../lib}/Property/VCard/LanguageTag.php | 0 .../vobject/lib}/Property/VCard/TimeStamp.php | 0 .../vendor/sabre/vobject/lib}/Reader.php | 0 .../sabre/vobject/lib/Recur/EventIterator.php | 496 +++ .../lib}/Recur/NoInstancesException.php | 0 .../vobject/lib}/Recur/RDateIterator.php | 0 .../vobject/lib}/Recur/RRuleIterator.php | 0 .../sabre/vobject/lib}/RecurrenceIterator.php | 0 .../sabre/vobject/lib}/Splitter/ICalendar.php | 0 .../lib}/Splitter/SplitterInterface.php | 0 .../sabre/vobject/lib}/Splitter/VCard.php | 0 .../vendor/sabre/vobject/lib}/StringUtil.php | 0 .../sabre/vobject/lib}/TimeZoneUtil.php | 0 .../vendor/sabre/vobject/lib}/UUIDUtil.php | 0 .../sabre/vobject/lib}/VCardConverter.php | 0 .../vendor/sabre/vobject/lib/Version.php | 19 + .../lib}/timezonedata/exchangezones.php | 0 .../vobject/lib}/timezonedata/lotuszones.php | 0 .../vobject/lib}/timezonedata/php-bc.php | 0 .../lib}/timezonedata/php-workaround.php | 0 .../lib}/timezonedata/windowszones.php | 0 .../vobject/tests/VObject/AttachIssueTest.php | 22 + .../sabre/vobject/tests/VObject/CliTest.php | 650 ++++ .../tests/VObject/Component/VAlarmTest.php | 179 ++ .../VObject/Component/VAvailabilityTest.php | 385 +++ .../tests/VObject/Component/VCalendarTest.php | 697 +++++ .../tests/VObject/Component/VCardTest.php | 288 ++ .../tests/VObject/Component/VEventTest.php | 90 + .../tests/VObject/Component/VFreeBusyTest.php | 66 + .../tests/VObject/Component/VJournalTest.php | 101 + .../tests/VObject/Component/VTimeZoneTest.php | 57 + .../tests/VObject/Component/VTodoTest.php | 180 ++ .../vobject/tests/VObject/ComponentTest.php | 528 ++++ .../tests/VObject/DateTimeParserTest.php | 417 +++ .../vobject/tests/VObject/DocumentTest.php | 70 + .../vobject/tests/VObject/ElementListTest.php | 33 + .../vobject/tests/VObject/EmClientTest.php | 55 + .../tests/VObject/EmptyParameterTest.php | 69 + .../tests/VObject/EmptyValueIssueTest.php | 31 + .../tests/VObject/FreeBusyGeneratorTest.php | 394 +++ .../tests/VObject/GoogleColonEscapingTest.php | 31 + .../VObject/ICalendar/AttachParseTest.php | 31 + .../VObject/ITip/BrokerAttendeeReplyTest.php | 1130 +++++++ .../VObject/ITip/BrokerDeleteEventTest.php | 340 +++ .../tests/VObject/ITip/BrokerNewEventTest.php | 528 ++++ .../VObject/ITip/BrokerProcessMessageTest.php | 168 ++ .../VObject/ITip/BrokerProcessReplyTest.php | 496 +++ .../tests/VObject/ITip/BrokerTester.php | 103 + .../VObject/ITip/BrokerUpdateEventTest.php | 841 ++++++ .../tests/VObject/ITip/EvolutionTest.php | 2653 +++++++++++++++++ .../tests/VObject/ITip/MessageTest.php | 32 + .../vobject/tests/VObject/Issue153Test.php | 14 + .../vobject/tests/VObject/Issue26Test.php | 36 + .../tests/VObject/Issue36WorkAroundTest.php | 39 + .../vobject/tests/VObject/Issue40Test.php | 30 + .../vobject/tests/VObject/Issue64Test.php | 19 + .../vobject/tests/VObject/Issue96Test.php | 24 + .../sabre/vobject/tests/VObject/JCalTest.php | 150 + .../sabre/vobject/tests/VObject/JCardTest.php | 195 ++ .../tests/VObject/LineFoldingIssueTest.php | 23 + .../vobject/tests/VObject/ParameterTest.php | 135 + .../vobject/tests/VObject/Parser/JsonTest.php | 395 +++ .../tests/VObject/Parser/MimeDirTest.php | 21 + .../VObject/Parser/QuotedPrintableTest.php | 108 + .../tests/VObject/Property/BinaryTest.php | 19 + .../tests/VObject/Property/BooleanTest.php | 22 + .../tests/VObject/Property/CompoundTest.php | 50 + .../tests/VObject/Property/FloatTest.php | 30 + .../Property/ICalendar/CalAddressTest.php | 32 + .../Property/ICalendar/DateTimeTest.php | 359 +++ .../Property/ICalendar/DurationTest.php | 20 + .../VObject/Property/ICalendar/RecurTest.php | 46 + .../tests/VObject/Property/TextTest.php | 96 + .../Property/VCard/DateAndOrTimeTest.php | 245 ++ .../Property/VCard/LanguageTagTest.php | 48 + .../vobject/tests/VObject/PropertyTest.php | 411 +++ .../vobject/tests/VObject/ReaderTest.php | 449 +++ .../EventIterator/ByMonthInDailyTest.php | 59 + .../Recur/EventIterator/BySetPosHangTest.php | 61 + .../EventIterator/ExpandFloatingTimesTest.php | 122 + .../EventIterator/FifthTuesdayProblemTest.php | 54 + .../EventIterator/IncorrectExpandTest.php | 64 + .../EventIterator/InfiniteLoopProblemTest.php | 99 + .../Recur/EventIterator/Issue48Test.php | 49 + .../Recur/EventIterator/Issue50Test.php | 128 + .../VObject/Recur/EventIterator/MainTest.php | 1427 +++++++++ .../EventIterator/MissingOverriddenTest.php | 65 + .../Recur/EventIterator/NoInstancesTest.php | 40 + .../EventIterator/OverrideFirstEventTest.php | 122 + .../tests/VObject/Recur/RDateIteratorTest.php | 56 + .../tests/VObject/Recur/RRuleIteratorTest.php | 713 +++++ .../UntilRespectsTimezoneTest.ics | 39 + .../vobject/tests/VObject/SlashRTest.php | 20 + .../tests/VObject/Splitter/ICalendarTest.php | 325 ++ .../tests/VObject/Splitter/VCardTest.php | 195 ++ .../vobject/tests/VObject/StringUtilTest.php | 55 + .../sabre/vobject/tests/VObject/TestCase.php | 50 + .../tests/VObject/TimeZoneUtilTest.php | 369 +++ .../vobject/tests/VObject/UUIDUtilTest.php | 37 + .../vobject/tests/VObject/VCard21Test.php | 52 + .../tests/VObject/VCardConverterTest.php | 531 ++++ .../vobject/tests/VObject/VersionTest.php | 14 + .../sabre/vobject/tests/VObject/issue153.vcf | 352 +++ .../sabre/vobject/tests/VObject/issue64.vcf | 351 +++ .../vendor/sabre/vobject/tests/bootstrap.php | 25 + .../sabre/vobject/tests/phpcs/ruleset.xml | 57 + .../vendor/sabre/vobject/tests/phpunit.xml | 21 + .../SabreDAV/vendor/sabre/xml/.gitignore | 9 + .../SabreDAV/vendor/sabre/xml/.travis.yml | 20 + .../SabreDAV/vendor/sabre/xml/CHANGELOG.md | 208 ++ .../SabreDAV/vendor/sabre/xml/LICENSE | 27 + .../SabreDAV/vendor/sabre/xml/README.md | 25 + .../SabreDAV/vendor/sabre/xml/bin/.empty | 0 .../SabreDAV/vendor/sabre/xml/composer.json | 50 + .../sabre/xml/lib/ContextStackTrait.php | 123 + .../sabre/xml/lib/Deserializer/functions.php | 255 ++ .../SabreDAV/vendor/sabre/xml/lib/Element.php | 20 + .../vendor/sabre/xml/lib/Element/Base.php | 91 + .../vendor/sabre/xml/lib/Element/Cdata.php | 64 + .../vendor/sabre/xml/lib/Element/Elements.php | 108 + .../vendor/sabre/xml/lib/Element/KeyValue.php | 108 + .../vendor/sabre/xml/lib/Element/Uri.php | 104 + .../sabre/xml/lib/Element/XmlFragment.php | 147 + .../vendor/sabre/xml/lib/LibXMLException.php | 53 + .../vendor/sabre/xml/lib/ParseException.php | 17 + .../SabreDAV/vendor/sabre/xml/lib/Reader.php | 308 ++ .../sabre/xml/lib/Serializer/functions.php | 249 ++ .../SabreDAV/vendor/sabre/xml/lib/Service.php | 291 ++ .../SabreDAV/vendor/sabre/xml/lib/Version.php | 19 + .../SabreDAV/vendor/sabre/xml/lib/Writer.php | 266 ++ .../sabre/xml/lib/XmlDeserializable.php | 38 + .../vendor/sabre/xml/lib/XmlSerializable.php | 36 + .../xml/tests/Sabre/Xml/ContextStackTest.php | 44 + .../tests/Sabre/Xml/Deserializer/EnumTest.php | 62 + .../Sabre/Xml/Deserializer/KeyValueTest.php | 63 + .../Deserializer/RepeatingElementsTest.php | 35 + .../Xml/Deserializer/ValueObjectTest.php | 169 ++ .../xml/tests/Sabre/Xml/Element/CDataTest.php | 58 + .../xml/tests/Sabre/Xml/Element/Eater.php | 78 + .../tests/Sabre/Xml/Element/ElementsTest.php | 129 + .../tests/Sabre/Xml/Element/KeyValueTest.php | 210 ++ .../xml/tests/Sabre/Xml/Element/Mock.php | 60 + .../xml/tests/Sabre/Xml/Element/UriTest.php | 76 + .../Sabre/Xml/Element/XmlFragmentTest.php | 143 + .../xml/tests/Sabre/Xml/InfiteLoopTest.php | 50 + .../sabre/xml/tests/Sabre/Xml/ReaderTest.php | 545 ++++ .../tests/Sabre/Xml/Serializer/EnumTest.php | 36 + .../Xml/Serializer/RepeatingElementsTest.php | 35 + .../sabre/xml/tests/Sabre/Xml/ServiceTest.php | 328 ++ .../sabre/xml/tests/Sabre/Xml/WriterTest.php | 439 +++ .../vendor/sabre/xml/tests/bootstrap.php | 17 + .../vendor/sabre/xml/tests/phpcs/ruleset.xml | 56 + .../vendor/sabre/xml/tests/phpunit.xml.dist | 17 + .../sabre/CalDAV/Backend/AbstractBackend.php | 221 -- .../sabre/CalDAV/Backend/BackendInterface.php | 268 -- .../CalDAV/Backend/NotificationSupport.php | 47 - .../3rdparty/sabre/CalDAV/Backend/PDO.php | 1220 -------- .../CalDAV/Backend/SchedulingSupport.php | 65 - .../3rdparty/sabre/CalDAV/Calendar.php | 527 ---- .../3rdparty/sabre/CalDAV/CalendarHome.php | 426 --- .../3rdparty/sabre/CalDAV/CalendarObject.php | 291 -- .../sabre/CalDAV/CalendarQueryParser.php | 299 -- .../sabre/CalDAV/CalendarQueryValidator.php | 375 --- .../3rdparty/sabre/CalDAV/CalendarRoot.php | 80 - .../sabre/CalDAV/CalendarRootNode.php | 18 - .../CalDAV/Exception/InvalidComponentType.php | 35 - .../3rdparty/sabre/CalDAV/ICSExportPlugin.php | 313 -- .../3rdparty/sabre/CalDAV/ICalendar.php | 19 - .../3rdparty/sabre/CalDAV/ICalendarObject.php | 20 - .../sabre/CalDAV/ICalendarObjectContainer.php | 39 - .../sabre/CalDAV/Notifications/Collection.php | 173 -- .../Notifications/INotificationType.php | 44 - .../sabre/CalDAV/Notifications/Node.php | 192 -- .../Notifications/Notification/Invite.php | 324 -- .../Notification/InviteReply.php | 218 -- .../Notification/SystemStatus.php | 182 -- .../sabre/CalDAV/Notifications/Plugin.php | 162 - .../3rdparty/sabre/CalDAV/Plugin.php | 1054 ------- .../sabre/CalDAV/Principal/Collection.php | 32 - .../sabre/CalDAV/Principal/ProxyRead.php | 180 -- .../sabre/CalDAV/Principal/ProxyWrite.php | 180 -- .../3rdparty/sabre/CalDAV/Principal/User.php | 134 - .../CalDAV/Property/AllowedSharingModes.php | 74 - .../sabre/CalDAV/Property/EmailAddressSet.php | 73 - .../3rdparty/sabre/CalDAV/Property/Invite.php | 228 -- .../Property/ScheduleCalendarTransp.php | 103 - .../SupportedCalendarComponentSet.php | 89 - .../CalDAV/Property/SupportedCalendarData.php | 44 - .../CalDAV/Property/SupportedCollationSet.php | 45 - .../3rdparty/sabre/CalDAV/Schedule/IInbox.php | 15 - .../sabre/CalDAV/Schedule/IMipPlugin.php | 177 -- .../CalDAV/Schedule/ISchedulingObject.php | 13 - .../3rdparty/sabre/CalDAV/Schedule/Inbox.php | 268 -- .../3rdparty/sabre/CalDAV/Schedule/Outbox.php | 183 -- .../3rdparty/sabre/CalDAV/Schedule/Plugin.php | 881 ------ .../CalDAV/Schedule/SchedulingObject.php | 165 - .../3rdparty/sabre/CalDAV/SharedCalendar.php | 148 - .../3rdparty/sabre/CalDAV/SharingPlugin.php | 502 ---- .../sabre/CalDAV/Subscriptions/Plugin.php | 85 - .../CalDAV/Subscriptions/Subscription.php | 277 -- .../3rdparty/sabre/CalDAV/UserCalendars.php | 19 - .../3rdparty/sabre/CardDAV/AddressBook.php | 432 --- .../sabre/CardDAV/AddressBookQueryParser.php | 221 -- .../sabre/CardDAV/AddressBookRoot.php | 80 - .../3rdparty/sabre/CardDAV/Backend/PDO.php | 557 ---- src/serverlib/3rdparty/sabre/CardDAV/Card.php | 265 -- .../3rdparty/sabre/CardDAV/ICard.php | 20 - .../3rdparty/sabre/CardDAV/Plugin.php | 913 ------ .../CardDAV/Property/SupportedAddressData.php | 73 - .../Property/SupportedCollationSet.php | 45 - .../sabre/CardDAV/UserAddressBooks.php | 261 -- .../sabre/CardDAV/VCFExportPlugin.php | 111 - .../sabre/DAV/Auth/Backend/AbstractBasic.php | 84 - .../sabre/DAV/Auth/Backend/AbstractDigest.php | 96 - .../sabre/DAV/Auth/Backend/Apache.php | 66 - .../DAV/Auth/Backend/BackendInterface.php | 36 - .../sabre/DAV/Auth/Backend/BasicCallBack.php | 62 - .../3rdparty/sabre/DAV/Auth/Backend/File.php | 77 - .../3rdparty/sabre/DAV/Auth/Backend/PDO.php | 61 - .../3rdparty/sabre/DAV/Auth/Plugin.php | 122 - .../sabre/DAV/Browser/GuessContentType.php | 100 - .../sabre/DAV/Browser/MapGetToPropFind.php | 61 - .../3rdparty/sabre/DAV/Browser/Plugin.php | 675 ----- .../sabre/DAV/Browser/PropFindAll.php | 132 - .../sabre/DAV/Browser/assets/sabredav.css | 222 -- src/serverlib/3rdparty/sabre/DAV/Client.php | 474 --- .../3rdparty/sabre/DAV/Collection.php | 110 - .../3rdparty/sabre/DAV/CorePlugin.php | 899 ------ .../3rdparty/sabre/DAV/Exception.php | 64 - .../sabre/DAV/Exception/BadRequest.php | 28 - .../3rdparty/sabre/DAV/Exception/Conflict.php | 28 - .../sabre/DAV/Exception/ConflictingLock.php | 36 - .../sabre/DAV/Exception/FileNotFound.php | 19 - .../sabre/DAV/Exception/Forbidden.php | 27 - .../DAV/Exception/InsufficientStorage.php | 27 - .../DAV/Exception/InvalidResourceType.php | 33 - .../sabre/DAV/Exception/InvalidSyncToken.php | 38 - .../Exception/LockTokenMatchesRequestUri.php | 41 - .../3rdparty/sabre/DAV/Exception/Locked.php | 73 - .../sabre/DAV/Exception/MethodNotAllowed.php | 45 - .../3rdparty/sabre/DAV/Exception/NotFound.php | 28 - .../sabre/DAV/Exception/NotImplemented.php | 27 - .../DAV/Exception/PreconditionFailed.php | 71 - .../DAV/Exception/ReportNotSupported.php | 32 - .../RequestedRangeNotSatisfiable.php | 31 - .../sabre/DAV/Exception/TooManyMatches.php | 38 - .../DAV/Exception/UnsupportedMediaType.php | 28 - .../3rdparty/sabre/DAV/FS/Directory.php | 140 - src/serverlib/3rdparty/sabre/DAV/FS/File.php | 91 - src/serverlib/3rdparty/sabre/DAV/FS/Node.php | 84 - .../3rdparty/sabre/DAV/FSExt/Directory.php | 196 -- .../3rdparty/sabre/DAV/FSExt/File.php | 143 - .../3rdparty/sabre/DAV/FSExt/Node.php | 226 -- src/serverlib/3rdparty/sabre/DAV/File.php | 85 - .../3rdparty/sabre/DAV/ICollection.php | 77 - .../sabre/DAV/IExtendedCollection.php | 28 - src/serverlib/3rdparty/sabre/DAV/IFile.php | 82 - .../3rdparty/sabre/DAV/IMoveTarget.php | 44 - .../3rdparty/sabre/DAV/IMultiGet.php | 35 - src/serverlib/3rdparty/sabre/DAV/INode.php | 46 - .../3rdparty/sabre/DAV/IProperties.php | 48 - src/serverlib/3rdparty/sabre/DAV/IQuota.php | 27 - .../DAV/Locks/Backend/AbstractBackend.php | 21 - .../DAV/Locks/Backend/BackendInterface.php | 51 - .../3rdparty/sabre/DAV/Locks/Backend/FS.php | 194 -- .../3rdparty/sabre/DAV/Locks/Backend/File.php | 185 -- .../3rdparty/sabre/DAV/Locks/Backend/PDO.php | 185 -- .../3rdparty/sabre/DAV/Locks/LockInfo.php | 81 - .../3rdparty/sabre/DAV/Locks/Plugin.php | 595 ---- .../3rdparty/sabre/DAV/Mount/Plugin.php | 86 - src/serverlib/3rdparty/sabre/DAV/Node.php | 55 - .../sabre/DAV/PartialUpdate/IFile.php | 39 - .../sabre/DAV/PartialUpdate/IPatchSupport.php | 48 - .../sabre/DAV/PartialUpdate/Plugin.php | 232 -- src/serverlib/3rdparty/sabre/DAV/PropFind.php | 336 --- .../3rdparty/sabre/DAV/PropPatch.php | 344 --- src/serverlib/3rdparty/sabre/DAV/Property.php | 32 - .../sabre/DAV/Property/GetLastModified.php | 77 - .../3rdparty/sabre/DAV/Property/Href.php | 100 - .../3rdparty/sabre/DAV/Property/HrefList.php | 106 - .../3rdparty/sabre/DAV/Property/IHref.php | 25 - .../sabre/DAV/Property/LockDiscovery.php | 94 - .../sabre/DAV/Property/ResourceType.php | 124 - .../3rdparty/sabre/DAV/Property/Response.php | 222 -- .../sabre/DAV/Property/ResponseList.php | 134 - .../sabre/DAV/Property/SupportedLock.php | 78 - .../sabre/DAV/Property/SupportedMethodSet.php | 68 - .../sabre/DAV/Property/SupportedReportSet.php | 126 - .../3rdparty/sabre/DAV/PropertyInterface.php | 39 - .../Backend/BackendInterface.php | 77 - .../sabre/DAV/PropertyStorage/Backend/PDO.php | 172 -- .../sabre/DAV/PropertyStorage/Plugin.php | 145 - src/serverlib/3rdparty/sabre/DAV/Server.php | 1741 ----------- .../3rdparty/sabre/DAV/ServerPlugin.php | 90 - .../3rdparty/sabre/DAV/SimpleCollection.php | 107 - .../3rdparty/sabre/DAV/SimpleFile.php | 121 - .../3rdparty/sabre/DAV/StringUtil.php | 91 - .../sabre/DAV/Sync/ISyncCollection.php | 89 - .../3rdparty/sabre/DAV/Sync/Plugin.php | 337 --- .../sabre/DAV/TemporaryFileFilterPlugin.php | 296 -- src/serverlib/3rdparty/sabre/DAV/Tree.php | 341 --- src/serverlib/3rdparty/sabre/DAV/URLUtil.php | 22 - src/serverlib/3rdparty/sabre/DAV/UUIDUtil.php | 64 - src/serverlib/3rdparty/sabre/DAV/Version.php | 19 - src/serverlib/3rdparty/sabre/DAV/XMLUtil.php | 194 -- .../DAVACL/AbstractPrincipalCollection.php | 181 -- .../sabre/DAVACL/Exception/AceConflict.php | 35 - .../sabre/DAVACL/Exception/NeedPrivileges.php | 83 - .../sabre/DAVACL/Exception/NoAbstract.php | 35 - .../Exception/NotRecognizedPrincipal.php | 35 - .../Exception/NotSupportedPrivilege.php | 35 - src/serverlib/3rdparty/sabre/DAVACL/IACL.php | 74 - .../3rdparty/sabre/DAVACL/Plugin.php | 1424 --------- .../3rdparty/sabre/DAVACL/Principal.php | 291 -- .../PrincipalBackend/AbstractBackend.php | 53 - .../sabre/DAVACL/PrincipalBackend/PDO.php | 386 --- .../sabre/DAVACL/PrincipalCollection.php | 33 - .../3rdparty/sabre/DAVACL/Property/Acl.php | 212 -- .../sabre/DAVACL/Property/AclRestrictions.php | 34 - .../Property/CurrentUserPrivilegeSet.php | 136 - .../sabre/DAVACL/Property/Principal.php | 162 - .../DAVACL/Property/SupportedPrivilegeSet.php | 105 - .../3rdparty/sabre/HTTP/Auth/AWS.php | 234 -- .../3rdparty/sabre/HTTP/Auth/AbstractAuth.php | 74 - .../3rdparty/sabre/HTTP/Auth/Basic.php | 57 - .../3rdparty/sabre/HTTP/Auth/Digest.php | 232 -- src/serverlib/3rdparty/sabre/HTTP/Client.php | 589 ---- .../3rdparty/sabre/HTTP/ClientException.php | 15 - .../sabre/HTTP/ClientHttpException.php | 58 - .../3rdparty/sabre/HTTP/HttpException.php | 30 - src/serverlib/3rdparty/sabre/HTTP/Message.php | 309 -- .../sabre/HTTP/MessageDecoratorTrait.php | 250 -- .../3rdparty/sabre/HTTP/MessageInterface.php | 177 -- src/serverlib/3rdparty/sabre/HTTP/Request.php | 312 -- .../3rdparty/sabre/HTTP/RequestDecorator.php | 231 -- .../3rdparty/sabre/HTTP/RequestInterface.php | 147 - .../3rdparty/sabre/HTTP/Response.php | 193 -- .../3rdparty/sabre/HTTP/ResponseDecorator.php | 84 - .../3rdparty/sabre/HTTP/ResponseInterface.php | 45 - src/serverlib/3rdparty/sabre/HTTP/Sapi.php | 176 -- src/serverlib/3rdparty/sabre/HTTP/URLUtil.php | 218 -- src/serverlib/3rdparty/sabre/HTTP/Util.php | 266 -- src/serverlib/3rdparty/sabre/HTTP/Version.php | 19 - .../3rdparty/sabre/VObject/ITip/Broker.php | 981 ------ .../sabre/VObject/Recur/EventIterator.php | 500 ---- .../3rdparty/sabre/VObject/Version.php | 19 - src/serverlib/dav-real.inc.php | 70 +- 990 files changed, 109128 insertions(+), 35011 deletions(-) create mode 100644 src/serverlib/3rdparty/SabreDAV/CHANGELOG.md create mode 100644 src/serverlib/3rdparty/SabreDAV/LICENSE create mode 100644 src/serverlib/3rdparty/SabreDAV/README.md create mode 100755 src/serverlib/3rdparty/SabreDAV/bin/generate_vcards create mode 100755 src/serverlib/3rdparty/SabreDAV/bin/naturalselection create mode 100755 src/serverlib/3rdparty/SabreDAV/bin/sabredav create mode 100755 src/serverlib/3rdparty/SabreDAV/bin/vobject create mode 100644 src/serverlib/3rdparty/SabreDAV/examples/addressbookserver.php create mode 100644 src/serverlib/3rdparty/SabreDAV/examples/calendarserver.php create mode 100644 src/serverlib/3rdparty/SabreDAV/examples/fileserver.php create mode 100644 src/serverlib/3rdparty/SabreDAV/examples/groupwareserver.php create mode 100644 src/serverlib/3rdparty/SabreDAV/examples/simplefsserver.php create mode 100644 src/serverlib/3rdparty/SabreDAV/examples/sql/mysql.addressbook.sql create mode 100644 src/serverlib/3rdparty/SabreDAV/examples/sql/mysql.calendars.sql create mode 100644 src/serverlib/3rdparty/SabreDAV/examples/sql/mysql.locks.sql create mode 100644 src/serverlib/3rdparty/SabreDAV/examples/sql/mysql.principals.sql create mode 100644 src/serverlib/3rdparty/SabreDAV/examples/sql/mysql.propertystorage.sql create mode 100644 src/serverlib/3rdparty/SabreDAV/examples/sql/mysql.users.sql create mode 100644 src/serverlib/3rdparty/SabreDAV/examples/sql/pgsql.addressbook.sql create mode 100644 src/serverlib/3rdparty/SabreDAV/examples/sql/pgsql.calendars.sql create mode 100644 src/serverlib/3rdparty/SabreDAV/examples/sql/pgsql.locks.sql create mode 100644 src/serverlib/3rdparty/SabreDAV/examples/sql/pgsql.principals.sql create mode 100644 src/serverlib/3rdparty/SabreDAV/examples/sql/pgsql.propertystorage.sql create mode 100644 src/serverlib/3rdparty/SabreDAV/examples/sql/pgsql.users.sql create mode 100644 src/serverlib/3rdparty/SabreDAV/examples/sql/sqlite.addressbooks.sql create mode 100644 src/serverlib/3rdparty/SabreDAV/examples/sql/sqlite.calendars.sql create mode 100644 src/serverlib/3rdparty/SabreDAV/examples/sql/sqlite.locks.sql create mode 100644 src/serverlib/3rdparty/SabreDAV/examples/sql/sqlite.principals.sql create mode 100644 src/serverlib/3rdparty/SabreDAV/examples/sql/sqlite.propertystorage.sql create mode 100644 src/serverlib/3rdparty/SabreDAV/examples/sql/sqlite.users.sql create mode 100644 src/serverlib/3rdparty/SabreDAV/examples/webserver/apache2_htaccess.conf create mode 100644 src/serverlib/3rdparty/SabreDAV/examples/webserver/apache2_vhost.conf create mode 100644 src/serverlib/3rdparty/SabreDAV/examples/webserver/apache2_vhost_cgi.conf create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/autoload.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/composer/ClassLoader.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/composer/LICENSE create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/composer/autoload_classmap.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/composer/autoload_files.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/composer/autoload_namespaces.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/composer/autoload_psr4.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/composer/autoload_real.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/composer/installed.json create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/.gitignore create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/.travis.yml create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/CONTRIBUTING.md create mode 100755 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/build.php create mode 100755 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/googlecode_upload.py create mode 100755 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/migrateto17.php create mode 100755 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/migrateto20.php create mode 100755 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/migrateto21.php create mode 100755 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/migrateto30.php create mode 100755 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/naturalselection create mode 100755 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/sabredav create mode 100755 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/sabredav.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/composer.json create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Backend/AbstractBackend.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Backend/BackendInterface.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Backend/NotificationSupport.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Backend/PDO.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Backend/SchedulingSupport.php rename src/serverlib/3rdparty/{sabre => SabreDAV/vendor/sabre/dav/lib}/CalDAV/Backend/SharingSupport.php (100%) rename src/serverlib/3rdparty/{sabre => SabreDAV/vendor/sabre/dav/lib}/CalDAV/Backend/SubscriptionSupport.php (100%) rename src/serverlib/3rdparty/{sabre => SabreDAV/vendor/sabre/dav/lib}/CalDAV/Backend/SyncSupport.php (100%) create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Calendar.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/CalendarHome.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/CalendarObject.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/CalendarQueryValidator.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/CalendarRoot.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Exception/InvalidComponentType.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/ICSExportPlugin.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/ICalendar.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/ICalendarObject.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/ICalendarObjectContainer.php rename src/serverlib/3rdparty/{sabre => SabreDAV/vendor/sabre/dav/lib}/CalDAV/IShareableCalendar.php (100%) rename src/serverlib/3rdparty/{sabre => SabreDAV/vendor/sabre/dav/lib}/CalDAV/ISharedCalendar.php (100%) create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Notifications/Collection.php rename src/serverlib/3rdparty/{sabre => SabreDAV/vendor/sabre/dav/lib}/CalDAV/Notifications/ICollection.php (100%) rename src/serverlib/3rdparty/{sabre => SabreDAV/vendor/sabre/dav/lib}/CalDAV/Notifications/INode.php (100%) create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Notifications/Node.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Notifications/Plugin.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Plugin.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Principal/Collection.php rename src/serverlib/3rdparty/{sabre => SabreDAV/vendor/sabre/dav/lib}/CalDAV/Principal/IProxyRead.php (100%) rename src/serverlib/3rdparty/{sabre => SabreDAV/vendor/sabre/dav/lib}/CalDAV/Principal/IProxyWrite.php (100%) create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Principal/ProxyRead.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Principal/ProxyWrite.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Principal/User.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Schedule/IInbox.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Schedule/IMipPlugin.php rename src/serverlib/3rdparty/{sabre => SabreDAV/vendor/sabre/dav/lib}/CalDAV/Schedule/IOutbox.php (100%) create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Schedule/ISchedulingObject.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Schedule/Inbox.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Schedule/Outbox.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Schedule/Plugin.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Schedule/SchedulingObject.php rename src/serverlib/3rdparty/{sabre => SabreDAV/vendor/sabre/dav/lib}/CalDAV/ShareableCalendar.php (100%) create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/SharedCalendar.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/SharingPlugin.php rename src/serverlib/3rdparty/{sabre => SabreDAV/vendor/sabre/dav/lib}/CalDAV/Subscriptions/ISubscription.php (100%) create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Subscriptions/Plugin.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Subscriptions/Subscription.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Filter/CalendarData.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Filter/CompFilter.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Filter/ParamFilter.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Filter/PropFilter.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Notification/Invite.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Notification/InviteReply.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Notification/NotificationInterface.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Notification/SystemStatus.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Property/AllowedSharingModes.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Property/EmailAddressSet.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Property/Invite.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Property/ScheduleCalendarTransp.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarComponentSet.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarData.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCollationSet.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarMultiGetReport.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarQueryReport.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Request/FreeBusyQueryReport.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Request/InviteReply.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Request/MkCalendar.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Request/Share.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/AddressBook.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/AddressBookHome.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/AddressBookRoot.php rename src/serverlib/3rdparty/{sabre => SabreDAV/vendor/sabre/dav/lib}/CardDAV/Backend/AbstractBackend.php (100%) rename src/serverlib/3rdparty/{sabre => SabreDAV/vendor/sabre/dav/lib}/CardDAV/Backend/BackendInterface.php (100%) create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/Backend/PDO.php rename src/serverlib/3rdparty/{sabre => SabreDAV/vendor/sabre/dav/lib}/CardDAV/Backend/SyncSupport.php (100%) create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/Card.php rename src/serverlib/3rdparty/{sabre => SabreDAV/vendor/sabre/dav/lib}/CardDAV/IAddressBook.php (100%) create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/ICard.php rename src/serverlib/3rdparty/{sabre => SabreDAV/vendor/sabre/dav/lib}/CardDAV/IDirectory.php (100%) create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/Plugin.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/Xml/Filter/AddressData.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/Xml/Filter/ParamFilter.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/Xml/Filter/PropFilter.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedAddressData.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedCollationSet.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookMultiGetReport.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookQueryReport.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBasic.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractDigest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Auth/Backend/Apache.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Auth/Backend/BackendInterface.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Auth/Backend/BasicCallBack.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Auth/Backend/File.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Auth/Backend/PDO.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Auth/Plugin.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/GuessContentType.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/HtmlOutput.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/HtmlOutputHelper.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/MapGetToPropFind.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/Plugin.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/PropFindAll.php rename src/serverlib/3rdparty/{sabre => SabreDAV/vendor/sabre/dav/lib}/DAV/Browser/assets/favicon.ico (100%) rename src/serverlib/3rdparty/{sabre => SabreDAV/vendor/sabre/dav/lib}/DAV/Browser/assets/openiconic/ICON-LICENSE (100%) rename src/serverlib/3rdparty/{sabre => SabreDAV/vendor/sabre/dav/lib}/DAV/Browser/assets/openiconic/open-iconic.css (100%) rename src/serverlib/3rdparty/{sabre => SabreDAV/vendor/sabre/dav/lib}/DAV/Browser/assets/openiconic/open-iconic.eot (100%) rename src/serverlib/3rdparty/{sabre => SabreDAV/vendor/sabre/dav/lib}/DAV/Browser/assets/openiconic/open-iconic.otf (100%) rename src/serverlib/3rdparty/{sabre => SabreDAV/vendor/sabre/dav/lib}/DAV/Browser/assets/openiconic/open-iconic.svg (100%) rename src/serverlib/3rdparty/{sabre => SabreDAV/vendor/sabre/dav/lib}/DAV/Browser/assets/openiconic/open-iconic.ttf (100%) rename src/serverlib/3rdparty/{sabre => SabreDAV/vendor/sabre/dav/lib}/DAV/Browser/assets/openiconic/open-iconic.woff (100%) create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.css rename src/serverlib/3rdparty/{sabre => SabreDAV/vendor/sabre/dav/lib}/DAV/Browser/assets/sabredav.png (100%) create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Client.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Collection.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/CorePlugin.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/BadRequest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/Conflict.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/ConflictingLock.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/Forbidden.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/InsufficientStorage.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/InvalidResourceType.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/InvalidSyncToken.php rename src/serverlib/3rdparty/{sabre => SabreDAV/vendor/sabre/dav/lib}/DAV/Exception/LengthRequired.php (100%) create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/LockTokenMatchesRequestUri.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/Locked.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/MethodNotAllowed.php rename src/serverlib/3rdparty/{sabre => SabreDAV/vendor/sabre/dav/lib}/DAV/Exception/NotAuthenticated.php (100%) create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/NotFound.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/NotImplemented.php rename src/serverlib/3rdparty/{sabre => SabreDAV/vendor/sabre/dav/lib}/DAV/Exception/PaymentRequired.php (100%) create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/PreconditionFailed.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/ReportNotSupported.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/RequestedRangeNotSatisfiable.php rename src/serverlib/3rdparty/{sabre => SabreDAV/vendor/sabre/dav/lib}/DAV/Exception/ServiceUnavailable.php (100%) create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/TooManyMatches.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/UnsupportedMediaType.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/FS/Directory.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/FS/File.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/FS/Node.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/FSExt/Directory.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/FSExt/File.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/File.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/ICollection.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/IExtendedCollection.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/IFile.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/IMoveTarget.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/IMultiGet.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/INode.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/IProperties.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/IQuota.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Locks/Backend/AbstractBackend.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Locks/Backend/BackendInterface.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Locks/Backend/File.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Locks/Backend/PDO.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Locks/LockInfo.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Locks/Plugin.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/MkCol.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Mount/Plugin.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Node.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/PartialUpdate/IPatchSupport.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/PartialUpdate/Plugin.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/PropFind.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/PropPatch.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/BackendInterface.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/PDO.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/PropertyStorage/Plugin.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Server.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/ServerPlugin.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/SimpleCollection.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/SimpleFile.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/StringUtil.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Sync/ISyncCollection.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Sync/Plugin.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/TemporaryFileFilterPlugin.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Tree.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/UUIDUtil.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Version.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Element/Prop.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Element/Response.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Property/Complex.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Property/GetLastModified.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Property/Href.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Property/LockDiscovery.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Property/ResourceType.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedLock.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedMethodSet.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedReportSet.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Request/Lock.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Request/MkCol.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Request/PropFind.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Request/PropPatch.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Request/SyncCollectionReport.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Response/MultiStatus.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Service.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/AbstractPrincipalCollection.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Exception/AceConflict.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Exception/NeedPrivileges.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Exception/NoAbstract.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Exception/NotRecognizedPrincipal.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Exception/NotSupportedPrivilege.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/FS/Collection.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/FS/File.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/FS/HomeCollection.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/IACL.php rename src/serverlib/3rdparty/{sabre => SabreDAV/vendor/sabre/dav/lib}/DAVACL/IPrincipal.php (100%) rename src/serverlib/3rdparty/{sabre => SabreDAV/vendor/sabre/dav/lib}/DAVACL/IPrincipalCollection.php (100%) create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Plugin.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Principal.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/AbstractBackend.php rename src/serverlib/3rdparty/{sabre => SabreDAV/vendor/sabre/dav/lib}/DAVACL/PrincipalBackend/BackendInterface.php (100%) create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/CreatePrincipalSupport.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/PDO.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/PrincipalCollection.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Xml/Property/Acl.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Xml/Property/AclRestrictions.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Xml/Property/CurrentUserPrivilegeSet.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Xml/Property/Principal.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Xml/Property/SupportedPrivilegeSet.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Xml/Request/ExpandPropertyReport.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalPropertySearchReport.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalSearchPropertySetReport.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/AbstractPDOTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/AbstractTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/Mock.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/MockScheduling.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/MockSharing.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/MockSubscriptionSupport.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/PDOMySQLTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/PDOSqliteTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarHomeNotificationsTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarHomeSharedCalendarsTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarHomeSubscriptionsTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarHomeTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarObjectTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarQueryVAlarmTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarQueryValidatorTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/ExpandEventsDTSTARTandDTENDTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/ExpandEventsDTSTARTandDTENDbyDayTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/ExpandEventsDoubleEventsTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/ExpandEventsFloatingTimeTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/FreeBusyReportTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/GetEventsByTimerangeTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/ICSExportPluginTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Issue166Test.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Issue172Test.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Issue203Test.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Issue205Test.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Issue211Test.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Issue220Test.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Issue228Test.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/JCalTransformTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Notifications/CollectionTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Notifications/NodeTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Notifications/PluginTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/PluginTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Principal/CollectionTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Principal/ProxyReadTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Principal/ProxyWriteTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Principal/UserTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/DeliverNewEventTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/FreeBusyRequestTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/IMip/MockPlugin.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/IMipPluginTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/InboxTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/OutboxPostTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/OutboxTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/PluginBasicTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/PluginPropertiesTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/PluginPropertiesWithSharedCalendarTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/ScheduleDeliverTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/SchedulingObjectTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/ShareableCalendarTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/SharedCalendarTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/SharingPluginTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Subscriptions/CreateSubscriptionTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Subscriptions/PluginTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Subscriptions/SubscriptionTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/TestUtil.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/ValidateICalTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Notification/InviteReplyTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Notification/InviteTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Notification/SystemStatusTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/AllowedSharingModesTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/EmailAddressSetTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/InviteTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/ScheduleCalendarTranspTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/SupportedCalendarComponentSetTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/SupportedCalendarDataTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/SupportedCollationSetTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Request/CalendarQueryReportTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Request/InviteReplyTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Request/ShareTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/AbstractPluginTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/AddressBookHomeTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/AddressBookQueryTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/AddressBookRootTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/AddressBookTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/Backend/AbstractPDOTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/Backend/Mock.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/Backend/PDOMySQLTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/Backend/PDOSqliteTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/CardTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/IDirectoryTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/MultiGetTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/PluginTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/SogoStripContentTypeTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/TestUtil.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/VCFExportTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/ValidateFilterTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/ValidateVCardTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/Xml/Property/SupportedAddressDataTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/Xml/Property/SupportedCollationSetTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/Xml/Request/AddressBookQueryReportTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/AbstractServer.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/AbstractBasicTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/AbstractDigestTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/AbstractPDOTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/ApacheTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/BasicCallBackTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/FileTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/Mock.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/PDOMySQLTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/PDOSqliteTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/PluginTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/BasicNodeTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Browser/GuessContentTypeTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Browser/MapGetToPropFindTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Browser/PluginTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Browser/PropFindAllTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ClientMock.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ClientTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/CopyTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Exception/LockedTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Exception/PaymentRequiredTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Exception/ServiceUnavailableTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Exception/TooManyMatchesTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ExceptionTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/FSExt/FileTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/FSExt/ServerTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/GetIfConditionsTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/HTTPPreferParsingTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/HttpDeleteTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/HttpGetTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/HttpHeadTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/HttpMoveTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/HttpPutTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Issue33Test.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/AbstractTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/FileTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/Mock.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/PDOMySQLTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/PDOTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Locks/MSWordTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Locks/Plugin2Test.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Locks/PluginTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Mock/Collection.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Mock/File.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Mock/PropertiesCollection.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Mock/StreamingFile.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Mount/PluginTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ObjectTreeTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PartialUpdate/FileMock.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PartialUpdate/PluginTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PartialUpdate/SpecificationTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PropFindTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PropPatchTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/AbstractPDOTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/Mock.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/PDOMysqlTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/PDOSqliteTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/PluginTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerCopyMoveTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerEventsTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerMKCOLTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerPluginTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerPreconditionTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerPropsInfiniteDepthTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerPropsTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerRangeTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerSimpleTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerUpdatePropertiesTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/SimpleFileTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/StringUtilTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Sync/MockSyncCollection.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Sync/PluginTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/SyncTokenPropertyTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/TemporaryFileFilterTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/TestPlugin.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/TreeTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/UUIDUtilTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Element/PropTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Element/ResponseTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/HrefTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/LastModifiedTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/LockDiscoveryTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/SupportedMethodSetTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/SupportedReportSetTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Request/PropFindTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Request/PropPatchTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Request/SyncCollectionTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/XmlTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/ACLMethodTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/AllowAccessTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/BlockAccessTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/AceConflictTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/NeedPrivilegesExceptionTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/NoAbstractTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/NotRecognizedPrincipalTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/NotSupportedPrivilegeTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/ExpandPropertiesTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/FS/CollectionTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/FS/FileTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/FS/HomeCollectionTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/MockACLNode.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/MockPrincipal.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PluginAdminTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PluginPropertiesTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PluginUpdatePropertiesTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/AbstractPDOTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/Mock.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/PDOMySQLTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/PDOSqliteTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalCollectionTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalPropertySearchTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalSearchPropertySetTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/SimplePluginTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/ACLTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/AclRestrictionsTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/CurrentUserPrivilegeSetTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/PrincipalTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/SupportedPrivilegeSetTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVServerTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/HTTP/ResponseMock.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/HTTP/SapiMock.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/TestUtil.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/bootstrap.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/phpcs/ruleset.xml create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/phpunit.xml create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/.gitignore create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/.travis.yml create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/CHANGELOG.md create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/LICENSE create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/README.md create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/bin/.empty create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/composer.json rename src/serverlib/3rdparty/{sabre/Event => SabreDAV/vendor/sabre/event/lib}/EventEmitter.php (100%) rename src/serverlib/3rdparty/{sabre/Event => SabreDAV/vendor/sabre/event/lib}/EventEmitterInterface.php (100%) rename src/serverlib/3rdparty/{sabre/Event => SabreDAV/vendor/sabre/event/lib}/EventEmitterTrait.php (100%) rename src/serverlib/3rdparty/{sabre/Event => SabreDAV/vendor/sabre/event/lib}/Promise.php (100%) rename src/serverlib/3rdparty/{sabre/Event => SabreDAV/vendor/sabre/event/lib}/PromiseAlreadyResolvedException.php (100%) rename src/serverlib/3rdparty/{sabre/Event => SabreDAV/vendor/sabre/event/lib}/Version.php (100%) create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/phpunit.xml.dist create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/tests/ContinueCallbackTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/tests/EventEmitterTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/tests/PromiseTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/tests/benchmark/bench.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/.gitignore create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/.travis.yml create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/CHANGELOG.md create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/LICENSE create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/README.md create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/bin/.empty create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/composer.json create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/examples/asyncclient.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/examples/basicauth.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/examples/client.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/examples/digestauth.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/examples/reverseproxy.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/examples/stringify.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Auth/AWS.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Auth/AbstractAuth.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Auth/Basic.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Auth/Bearer.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Auth/Digest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Client.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/ClientException.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/ClientHttpException.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/HttpException.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Message.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/MessageDecoratorTrait.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/MessageInterface.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Request.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/RequestDecorator.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/RequestInterface.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Response.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/ResponseDecorator.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/ResponseInterface.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Sapi.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/URLUtil.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Util.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Version.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/functions.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/Auth/AWSTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/Auth/BasicTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/Auth/BearerTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/Auth/DigestTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/ClientTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/FunctionsTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/MessageDecoratorTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/MessageTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/RequestDecoratorTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/RequestTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/ResponseDecoratorTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/ResponseTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/SapiTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/URLUtilTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/UtilTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/bootstrap.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/phpcs/ruleset.xml create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/phpunit.xml create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/.gitignore create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/.travis.yml create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/CHANGELOG.md create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/LICENSE create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/README.md create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/composer.json create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/lib/Version.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/lib/functions.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/tests/BuildTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/tests/NormalizeTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/tests/ParseTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/tests/ResolveTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/tests/SplitTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/tests/phpcs/ruleset.xml create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/tests/phpunit.xml.dist create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/.gitignore create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/.travis.yml create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/ChangeLog.md create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/LICENSE create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/README.md create mode 100755 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/bin/bench.php create mode 100755 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/bin/fetch_windows_zones.php create mode 100755 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/bin/generate_vcards create mode 100755 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/bin/generateicalendardata.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/bin/rrulebench.php create mode 100755 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/bin/vobject create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/composer.json rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Cli.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Component.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Component/Available.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Component/VAlarm.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Component/VAvailability.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Component/VCalendar.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Component/VCard.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Component/VEvent.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Component/VFreeBusy.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Component/VJournal.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Component/VTimeZone.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Component/VTodo.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/DateTimeParser.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Document.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/ElementList.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/EofException.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/FreeBusyGenerator.php (100%) create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/ITip/Broker.php rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/ITip/ITipException.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/ITip/Message.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/ITip/SameOrganizerForAllComponentsException.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Node.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Parameter.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/ParseException.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Parser/Json.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Parser/MimeDir.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Parser/Parser.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Property.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Property/Binary.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Property/Boolean.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Property/FlatText.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Property/FloatValue.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Property/ICalendar/CalAddress.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Property/ICalendar/Date.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Property/ICalendar/DateTime.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Property/ICalendar/Duration.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Property/ICalendar/Period.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Property/ICalendar/Recur.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Property/IntegerValue.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Property/Text.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Property/Time.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Property/Unknown.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Property/Uri.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Property/UtcOffset.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Property/VCard/Date.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Property/VCard/DateAndOrTime.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Property/VCard/DateTime.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Property/VCard/LanguageTag.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Property/VCard/TimeStamp.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Reader.php (100%) create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Recur/EventIterator.php rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Recur/NoInstancesException.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Recur/RDateIterator.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Recur/RRuleIterator.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/RecurrenceIterator.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Splitter/ICalendar.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Splitter/SplitterInterface.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/Splitter/VCard.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/StringUtil.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/TimeZoneUtil.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/UUIDUtil.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/VCardConverter.php (100%) create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Version.php rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/timezonedata/exchangezones.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/timezonedata/lotuszones.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/timezonedata/php-bc.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/timezonedata/php-workaround.php (100%) rename src/serverlib/3rdparty/{sabre/VObject => SabreDAV/vendor/sabre/vobject/lib}/timezonedata/windowszones.php (100%) create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/AttachIssueTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/CliTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VAlarmTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VAvailabilityTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VCalendarTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VCardTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VEventTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VFreeBusyTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VJournalTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VTimeZoneTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VTodoTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ComponentTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/DateTimeParserTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/DocumentTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ElementListTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/EmClientTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/EmptyParameterTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/EmptyValueIssueTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/FreeBusyGeneratorTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/GoogleColonEscapingTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ICalendar/AttachParseTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/BrokerAttendeeReplyTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/BrokerDeleteEventTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/BrokerNewEventTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/BrokerProcessMessageTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/BrokerProcessReplyTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/BrokerTester.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/BrokerUpdateEventTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/EvolutionTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/MessageTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Issue153Test.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Issue26Test.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Issue36WorkAroundTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Issue40Test.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Issue64Test.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Issue96Test.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/JCalTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/JCardTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/LineFoldingIssueTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ParameterTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Parser/JsonTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Parser/MimeDirTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Parser/QuotedPrintableTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/BinaryTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/BooleanTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/CompoundTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/FloatTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/ICalendar/CalAddressTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/ICalendar/DateTimeTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/ICalendar/DurationTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/ICalendar/RecurTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/TextTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/VCard/DateAndOrTimeTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/VCard/LanguageTagTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/PropertyTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ReaderTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/ByMonthInDailyTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/BySetPosHangTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/ExpandFloatingTimesTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/FifthTuesdayProblemTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/IncorrectExpandTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/InfiniteLoopProblemTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/Issue48Test.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/Issue50Test.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/MainTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/MissingOverriddenTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/NoInstancesTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/OverrideFirstEventTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/RDateIteratorTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/RRuleIteratorTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/RecurrenceIterator/UntilRespectsTimezoneTest.ics create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/SlashRTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Splitter/ICalendarTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Splitter/VCardTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/StringUtilTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/TestCase.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/TimeZoneUtilTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/UUIDUtilTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/VCard21Test.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/VCardConverterTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/VersionTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/issue153.vcf create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/issue64.vcf create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/bootstrap.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/phpcs/ruleset.xml create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/phpunit.xml create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/.gitignore create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/.travis.yml create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/CHANGELOG.md create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/LICENSE create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/README.md create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/bin/.empty create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/composer.json create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/ContextStackTrait.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Deserializer/functions.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Element.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Element/Base.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Element/Cdata.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Element/Elements.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Element/KeyValue.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Element/Uri.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Element/XmlFragment.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/LibXMLException.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/ParseException.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Reader.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Serializer/functions.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Service.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Version.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Writer.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/XmlDeserializable.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/XmlSerializable.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/ContextStackTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Deserializer/EnumTest.php create mode 100755 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Deserializer/KeyValueTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Deserializer/RepeatingElementsTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Deserializer/ValueObjectTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Element/CDataTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Element/Eater.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Element/ElementsTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Element/KeyValueTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Element/Mock.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Element/UriTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Element/XmlFragmentTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/InfiteLoopTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/ReaderTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Serializer/EnumTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Serializer/RepeatingElementsTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/ServiceTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/WriterTest.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/bootstrap.php create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/phpcs/ruleset.xml create mode 100644 src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/phpunit.xml.dist delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/Backend/AbstractBackend.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/Backend/BackendInterface.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/Backend/NotificationSupport.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/Backend/PDO.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/Backend/SchedulingSupport.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/Calendar.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/CalendarHome.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/CalendarObject.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/CalendarQueryParser.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/CalendarQueryValidator.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/CalendarRoot.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/CalendarRootNode.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/Exception/InvalidComponentType.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/ICSExportPlugin.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/ICalendar.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/ICalendarObject.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/ICalendarObjectContainer.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/Notifications/Collection.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/Notifications/INotificationType.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/Notifications/Node.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/Notifications/Notification/Invite.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/Notifications/Notification/InviteReply.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/Notifications/Notification/SystemStatus.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/Notifications/Plugin.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/Plugin.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/Principal/Collection.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/Principal/ProxyRead.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/Principal/ProxyWrite.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/Principal/User.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/Property/AllowedSharingModes.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/Property/EmailAddressSet.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/Property/Invite.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/Property/ScheduleCalendarTransp.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/Property/SupportedCalendarComponentSet.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/Property/SupportedCalendarData.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/Property/SupportedCollationSet.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/Schedule/IInbox.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/Schedule/IMipPlugin.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/Schedule/ISchedulingObject.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/Schedule/Inbox.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/Schedule/Outbox.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/Schedule/Plugin.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/Schedule/SchedulingObject.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/SharedCalendar.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/SharingPlugin.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/Subscriptions/Plugin.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/Subscriptions/Subscription.php delete mode 100644 src/serverlib/3rdparty/sabre/CalDAV/UserCalendars.php delete mode 100644 src/serverlib/3rdparty/sabre/CardDAV/AddressBook.php delete mode 100644 src/serverlib/3rdparty/sabre/CardDAV/AddressBookQueryParser.php delete mode 100644 src/serverlib/3rdparty/sabre/CardDAV/AddressBookRoot.php delete mode 100644 src/serverlib/3rdparty/sabre/CardDAV/Backend/PDO.php delete mode 100644 src/serverlib/3rdparty/sabre/CardDAV/Card.php delete mode 100644 src/serverlib/3rdparty/sabre/CardDAV/ICard.php delete mode 100644 src/serverlib/3rdparty/sabre/CardDAV/Plugin.php delete mode 100644 src/serverlib/3rdparty/sabre/CardDAV/Property/SupportedAddressData.php delete mode 100644 src/serverlib/3rdparty/sabre/CardDAV/Property/SupportedCollationSet.php delete mode 100644 src/serverlib/3rdparty/sabre/CardDAV/UserAddressBooks.php delete mode 100644 src/serverlib/3rdparty/sabre/CardDAV/VCFExportPlugin.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Auth/Backend/AbstractBasic.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Auth/Backend/AbstractDigest.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Auth/Backend/Apache.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Auth/Backend/BackendInterface.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Auth/Backend/BasicCallBack.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Auth/Backend/File.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Auth/Backend/PDO.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Auth/Plugin.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Browser/GuessContentType.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Browser/MapGetToPropFind.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Browser/Plugin.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Browser/PropFindAll.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Browser/assets/sabredav.css delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Client.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Collection.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/CorePlugin.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Exception.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Exception/BadRequest.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Exception/Conflict.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Exception/ConflictingLock.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Exception/FileNotFound.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Exception/Forbidden.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Exception/InsufficientStorage.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Exception/InvalidResourceType.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Exception/InvalidSyncToken.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Exception/LockTokenMatchesRequestUri.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Exception/Locked.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Exception/MethodNotAllowed.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Exception/NotFound.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Exception/NotImplemented.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Exception/PreconditionFailed.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Exception/ReportNotSupported.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Exception/RequestedRangeNotSatisfiable.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Exception/TooManyMatches.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Exception/UnsupportedMediaType.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/FS/Directory.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/FS/File.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/FS/Node.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/FSExt/Directory.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/FSExt/File.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/FSExt/Node.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/File.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/ICollection.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/IExtendedCollection.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/IFile.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/IMoveTarget.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/IMultiGet.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/INode.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/IProperties.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/IQuota.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Locks/Backend/AbstractBackend.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Locks/Backend/BackendInterface.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Locks/Backend/FS.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Locks/Backend/File.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Locks/Backend/PDO.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Locks/LockInfo.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Locks/Plugin.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Mount/Plugin.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Node.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/PartialUpdate/IFile.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/PartialUpdate/IPatchSupport.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/PartialUpdate/Plugin.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/PropFind.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/PropPatch.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Property.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Property/GetLastModified.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Property/Href.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Property/HrefList.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Property/IHref.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Property/LockDiscovery.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Property/ResourceType.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Property/Response.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Property/ResponseList.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Property/SupportedLock.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Property/SupportedMethodSet.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Property/SupportedReportSet.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/PropertyInterface.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/PropertyStorage/Backend/BackendInterface.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/PropertyStorage/Backend/PDO.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/PropertyStorage/Plugin.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Server.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/ServerPlugin.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/SimpleCollection.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/SimpleFile.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/StringUtil.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Sync/ISyncCollection.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Sync/Plugin.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/TemporaryFileFilterPlugin.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Tree.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/URLUtil.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/UUIDUtil.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/Version.php delete mode 100644 src/serverlib/3rdparty/sabre/DAV/XMLUtil.php delete mode 100644 src/serverlib/3rdparty/sabre/DAVACL/AbstractPrincipalCollection.php delete mode 100644 src/serverlib/3rdparty/sabre/DAVACL/Exception/AceConflict.php delete mode 100644 src/serverlib/3rdparty/sabre/DAVACL/Exception/NeedPrivileges.php delete mode 100644 src/serverlib/3rdparty/sabre/DAVACL/Exception/NoAbstract.php delete mode 100644 src/serverlib/3rdparty/sabre/DAVACL/Exception/NotRecognizedPrincipal.php delete mode 100644 src/serverlib/3rdparty/sabre/DAVACL/Exception/NotSupportedPrivilege.php delete mode 100644 src/serverlib/3rdparty/sabre/DAVACL/IACL.php delete mode 100644 src/serverlib/3rdparty/sabre/DAVACL/Plugin.php delete mode 100644 src/serverlib/3rdparty/sabre/DAVACL/Principal.php delete mode 100644 src/serverlib/3rdparty/sabre/DAVACL/PrincipalBackend/AbstractBackend.php delete mode 100644 src/serverlib/3rdparty/sabre/DAVACL/PrincipalBackend/PDO.php delete mode 100644 src/serverlib/3rdparty/sabre/DAVACL/PrincipalCollection.php delete mode 100644 src/serverlib/3rdparty/sabre/DAVACL/Property/Acl.php delete mode 100644 src/serverlib/3rdparty/sabre/DAVACL/Property/AclRestrictions.php delete mode 100644 src/serverlib/3rdparty/sabre/DAVACL/Property/CurrentUserPrivilegeSet.php delete mode 100644 src/serverlib/3rdparty/sabre/DAVACL/Property/Principal.php delete mode 100644 src/serverlib/3rdparty/sabre/DAVACL/Property/SupportedPrivilegeSet.php delete mode 100644 src/serverlib/3rdparty/sabre/HTTP/Auth/AWS.php delete mode 100644 src/serverlib/3rdparty/sabre/HTTP/Auth/AbstractAuth.php delete mode 100644 src/serverlib/3rdparty/sabre/HTTP/Auth/Basic.php delete mode 100644 src/serverlib/3rdparty/sabre/HTTP/Auth/Digest.php delete mode 100644 src/serverlib/3rdparty/sabre/HTTP/Client.php delete mode 100644 src/serverlib/3rdparty/sabre/HTTP/ClientException.php delete mode 100644 src/serverlib/3rdparty/sabre/HTTP/ClientHttpException.php delete mode 100644 src/serverlib/3rdparty/sabre/HTTP/HttpException.php delete mode 100644 src/serverlib/3rdparty/sabre/HTTP/Message.php delete mode 100644 src/serverlib/3rdparty/sabre/HTTP/MessageDecoratorTrait.php delete mode 100644 src/serverlib/3rdparty/sabre/HTTP/MessageInterface.php delete mode 100644 src/serverlib/3rdparty/sabre/HTTP/Request.php delete mode 100644 src/serverlib/3rdparty/sabre/HTTP/RequestDecorator.php delete mode 100644 src/serverlib/3rdparty/sabre/HTTP/RequestInterface.php delete mode 100644 src/serverlib/3rdparty/sabre/HTTP/Response.php delete mode 100644 src/serverlib/3rdparty/sabre/HTTP/ResponseDecorator.php delete mode 100644 src/serverlib/3rdparty/sabre/HTTP/ResponseInterface.php delete mode 100644 src/serverlib/3rdparty/sabre/HTTP/Sapi.php delete mode 100644 src/serverlib/3rdparty/sabre/HTTP/URLUtil.php delete mode 100644 src/serverlib/3rdparty/sabre/HTTP/Util.php delete mode 100644 src/serverlib/3rdparty/sabre/HTTP/Version.php delete mode 100644 src/serverlib/3rdparty/sabre/VObject/ITip/Broker.php delete mode 100644 src/serverlib/3rdparty/sabre/VObject/Recur/EventIterator.php delete mode 100644 src/serverlib/3rdparty/sabre/VObject/Version.php diff --git a/src/interface/caldav.php b/src/interface/caldav.php index 0cbb0a6..56efd11 100644 --- a/src/interface/caldav.php +++ b/src/interface/caldav.php @@ -46,7 +46,7 @@ class BMCalDAVBackend extends Sabre\CalDAV\Backend\AbstractBackend 'uri' => 'calendar', 'principaluri' => $os->getPrincipalURI(), '{DAV:}displayname' => 'Calendar', - '{' . Sabre\CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => new Sabre\CalDAV\Property\SupportedCalendarComponentSet(array('VEVENT')) + '{' . Sabre\CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => new Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet(array('VEVENT')) ); $groupList = $os->calendar->GetGroups(); @@ -60,7 +60,7 @@ class BMCalDAVBackend extends Sabre\CalDAV\Backend\AbstractBackend 'uri' => !empty($group['dav_uri']) ? $group['dav_uri'] : 'calendar-' . $key, 'principaluri' => $os->getPrincipalURI(), '{DAV:}displayname' => $group['title'], - '{' . Sabre\CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => new Sabre\CalDAV\Property\SupportedCalendarComponentSet(array('VEVENT')) + '{' . Sabre\CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => new Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet(array('VEVENT')) ); } @@ -72,7 +72,7 @@ class BMCalDAVBackend extends Sabre\CalDAV\Backend\AbstractBackend 'uri' => !empty($list['dav_uri']) ? $list['dav_uri'] : 'tasklist-' . $list['tasklistid'], 'principaluri' => $os->getPrincipalURI(), '{DAV:}displayname' => $list['title'], - '{' . Sabre\CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => new Sabre\CalDAV\Property\SupportedCalendarComponentSet(array('VTODO')) + '{' . Sabre\CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => new Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet(array('VTODO')) ); } @@ -85,8 +85,8 @@ class BMCalDAVBackend extends Sabre\CalDAV\Backend\AbstractBackend $key = '{' . Sabre\CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set'; - if(!isset($properties[$key]) || !($properties[$key] instanceof Sabre\CalDAV\Property\SupportedCalendarComponentSet)) - throw new Sabre\DAV\Exception('The ' . $key . ' property must be of type: \Sabre\CalDAV\Property\SupportedCalendarComponentSet'); + if(!isset($properties[$key]) || !($properties[$key] instanceof Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet)) + throw new Sabre\DAV\Exception('The ' . $key . ' property must be of type: \Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet'); $type = strtoupper(implode(',', $properties[$key]->getValue())); @@ -892,7 +892,7 @@ $caldavBackend = new BMCalDAVBackend; $nodes = array( new \Sabre\CalDAV\Principal\Collection($principalBackend), - new \Sabre\CalDAV\CalendarRootNode($principalBackend, $caldavBackend) + new \Sabre\CalDAV\CalendarRoot($principalBackend, $caldavBackend) ); $server = new DAV\Server($nodes); diff --git a/src/interface/carddav.php b/src/interface/carddav.php index bc55ecb..3f4da48 100644 --- a/src/interface/carddav.php +++ b/src/interface/carddav.php @@ -41,7 +41,7 @@ class BMCardDAVBackend extends Sabre\CardDAV\Backend\AbstractBackend 'uri' => 'main', 'principaluri' => $os->getPrincipalURI(), '{DAV:}displayname' => 'Addressbook', - '{' . Sabre\CardDAV\Plugin::NS_CARDDAV . '}supported-address-data' => new Sabre\CardDAV\Property\SupportedAddressData() + '{' . Sabre\CardDAV\Plugin::NS_CARDDAV . '}supported-address-data' => new Sabre\CardDAV\Xml\Property\SupportedAddressData() ); return($result); diff --git a/src/serverlib/3rdparty/SabreDAV/CHANGELOG.md b/src/serverlib/3rdparty/SabreDAV/CHANGELOG.md new file mode 100644 index 0000000..1739f40 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/CHANGELOG.md @@ -0,0 +1,2138 @@ +ChangeLog +========= + +3.0.9 (2016-04-06) +------------------ + +* Set minimum libxml version to 2.7.0 in `composer.json`. +* #727: Added another workaround to make CalDAV work for Windows 10 clients. +* #805: It wasn't possible to create calendars that hold events, journals and + todos using MySQL, because the `components` column was 1 byte too small. +* The zip release ships with [sabre/vobject 3.5.1][vobj], + [sabre/http 4.2.1][http], [sabre/event 2.0.2][evnt], + [sabre/uri 1.1.0][uri] and [sabre/xml 1.4.1][xml]. + + +3.0.8 (2016-03-12) +------------------ + +* #784: Sync logs for address books were not correctly cleaned up after + deleting them. +* #787: Cannot use non-seekable stream-wrappers with range requests. +* Faster XML parsing and generating due to sabre/xml update. +* The zip release ships with [sabre/vobject 3.5.0][vobj], + [sabre/http 4.2.1][http], [sabre/event 2.0.2][evnt], + [sabre/uri 1.1.0][uri] and [sabre/xml 1.4.1][xml]. + + +3.0.7 (2016-01-12) +------------------ + +* #752: PHP 7 support for 3.0 branch. (@DeepDiver1975) +* The zip release ships with [sabre/vobject 3.5.0][vobj], + [sabre/http 4.2.1][http], [sabre/event 2.0.2][evnt], + [sabre/uri 1.0.1][uri] and [sabre/xml 1.3.0][xml]. + + +3.0.6 (2016-01-04) +------------------ + +* #730: Switched all mysql tables to `utf8mb4` character set, allowing you to + use emoji in some tables where you couldn't before. +* #729: Not all calls to `Sabre\DAV\Tree::getChildren()` were properly cached. +* #734: Return `418 I'm a Teapot` when generating a multistatus response that + has resources with no returned properties. +* #740: Bugs in `migrate20.php` script. +* The zip release ships with [sabre/vobject 3.4.8][vobj], + [sabre/http 4.1.0][http], [sabre/event 2.0.2][evnt], + [sabre/uri 1.0.1][uri] and [sabre/xml 1.3.0][xml]. + + +3.0.5 (2015-09-15) +------------------ + +* #704: Fixed broken uri encoding in multistatus responses. This affected + at least CyberDuck, but probably also others. +* The zip release ships with [sabre/vobject 3.4.7][vobj], + [sabre/http 4.1.0][http], [sabre/event 2.0.2][evnt], + [sabre/uri 1.0.1][uri] and [sabre/xml 1.2.0][xml]. + + +3.0.4 (2015-09-06) +------------------ + +* #703: PropPatch in client is not correctly encoded. +* #709: Throw exception when running into empty + `supported-calendar-component-set`. +* #711: Don't trigger deserializers for empty elements in `{DAV:}prop`. This + fixes issues when using sabre/dav as a client. +* #705: A `MOVE` request that gets prevented from deleting the source resource + will still remove the target resource. Now all events are triggered before + any destructive operations. +* The zip release ships with [sabre/vobject 3.4.7][vobj], + [sabre/http 4.1.0][http], [sabre/event 2.0.2][evnt], + [sabre/uri 1.0.1][uri] and [sabre/xml 1.2.0][xml]. + + +3.0.3 (2015-08-06) +------------------ + +* #700: Digest Auth fails on `HEAD` requests. +* Fixed example files to no longer use now-deprecated realm argument. +* The zip release ships with [sabre/vobject 3.4.6][vobj], + [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt], + [sabre/uri 1.0.1][uri] and [sabre/xml 1.1.0][xml]. + + +3.0.2 (2015-07-21) +------------------ + +* #657: Migration script would break when coming a cross an iCalendar object + with no UID. +* #691: Workaround for broken Windows Phone client. +* Fixed a whole bunch of incorrect php docblocks. +* The zip release ships with [sabre/vobject 3.4.5][vobj], + [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt], + [sabre/uri 1.0.1][uri] and [sabre/xml 1.1.0][xml]. + + +3.0.1 (2015-07-02) +------------------ + +* #674: Postgres sql file fixes. (@davesouthey) +* #677: Resources with the name '0' would not get retrieved when using + `Depth: infinity` in a `PROPFIND` request. +* #680: Fix 'autoprefixing' of dead `{DAV:}href` properties. +* #675: NTLM support in DAV\Client. (@k42b3) +* The zip release ships with [sabre/vobject 3.4.5][vobj], + [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt], + [sabre/uri 1.0.1][uri] and [sabre/xml 1.1.0][xml]. + + +3.0.0 (2015-06-02) +------------------ + +* No changes since last beta. +* The zip release ships with [sabre/vobject 3.4.5][vobj], + [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt], + [sabre/uri 1.0.1][uri] and [sabre/xml 1.0.0][xml]. + + +3.0.0-beta3 (2015-05-29) +------------------------ + +* Fixed deserializing href properties with no value. +* Fixed deserializing `{DAV:}propstat` without a `{DAV:}prop`. +* #668: More information about vcf-export-plugin in browser plugin. +* #669: Add export button to browser plugin for address books. (@mgee) +* #670: multiget report hrefs were not decoded. +* The zip release ships with [sabre/vobject 3.4.4][vobj], + [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt], + [sabre/uri 1.0.1][uri] and [sabre/xml 1.0.0][xml]. + + +3.0.0-beta2 (2015-05-27) +------------------------ + +* A node's properties should not overwrite properties that were already set. +* Some uris were not correctly encoded in notifications. +* The zip release ships with [sabre/vobject 3.4.4][vobj], + [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt], + [sabre/uri 1.0.1][uri] and [sabre/xml 1.0.0][xml]. + + +3.0.0-beta1 (2015-05-25) +------------------------ + +* `migrate22.php` is now called `migrate30.php`. +* Using php-cs-fixer for automated coding standards enforcement and fixing. +* #660: principals could break html output. +* #662: Fixed several bugs in the `share` request parser. +* #665: Fix a bug in serialization of complex properties in the proppatch + request in the client. +* #666: expand-property report did not correctly prepend the base uri when + generating uris, this caused delegation to break. +* #659: Don't throw errors when when etag-related checks are done on + collections. +* Fully supporting the updated `Prefer` header syntax, as defined in + [rfc7240][rfc7240]. +* The zip release ships with [sabre/vobject 3.4.3][vobj], + [sabre/http 4.0.0][http], [sabre/event 2.0.2][evnt], + [sabre/uri 1.0.1][uri] and [sabre/xml 1.0.0][xml]. + + +3.0.0-alpha1 (2015-05-19) +------------------------- + +* It's now possible to get all property information from files using the + browser plugin. +* Browser plugin will now show a 'calendar export' button when the + ics-export plugin is enabled. +* Some nodes that by default showed the current time as their last + modification time, now no longer has a last modification time. +* CardDAV namespace was missing from default namespaceMap. +* #646: Properties can now control their own HTML output in the browser plugin. +* #646: Nicer HTML output for the `{DAV:}acl` property. +* Browser plugin no longer shows a few properties that take up a lot of space, + but are likely not really interesting for most users. +* #654: Added a collection, `Sabre\DAVACL\FS\HomeCollection` for automatically + creating a private home collection per-user. +* Changed all MySQL columns from `VARCHAR` to `VARBINARY` where possible. +* Improved older migration scripts a bit to allow easier testing. +* The zip release ships with [sabre/vobject 3.4.3][vobj], + [sabre/http 4.0.0-alpha3][http], [sabre/event 2.0.2][evnt], + [sabre/uri 1.0.1][uri] and [sabre/xml 0.4.3][xml]. + + +2.2.0-alpha4 (2015-04-13) +------------------------- + +* Complete rewrite of the XML system. We now use our own [sabre/xml][xml], + which has a much smarter XML Reader and Writer. +* BC Break: It's no longer possible to instantiate the Locks plugin without + a locks backend. I'm not sure why this ever made sense. +* Simplified the Locking system and fixed a bug related to if tokens checking + locks unrelated to the current request. +* `FSExt` Directory and File no longer do custom property storage. This + functionality is already covered pretty well by the `PropertyStorage` plugin, + so please switch. +* Renamed `Sabre\CardDAV\UserAddressBooks` to `Sabre\CardDAV\AddressBookHome` + to be more consistent with `CalendarHome` as well as the CardDAV + specification. +* `Sabre\DAV\IExtendedCollection` now receives a `Sabre\DAV\MkCol` object as + its second argument, and no longer receives seperate properties and + resourcetype arguments. +* `MKCOL` now integrates better with propertystorage plugins. +* The zip release ships with [sabre/vobject 3.4.2][vobj], + [sabre/http 4.0.0-alpha1][http], [sabre/event 2.0.1][evnt], + [sabre/uri 1.0.0][uri] and [sabre/xml 0.4.3][xml]. + + +2.2.0-alpha3 (2015-02-25) +------------------------- + +* Contains all the changes introduced between 2.1.2 and 2.1.3. +* The zip release ships with [sabre/vobject 3.4.2][vobj], + [sabre/http 4.0.0-alpha1][http], [sabre/event 2.0.1][evnt] and + [sabre/uri 1.0.0][uri]. + + +2.2.0-alpha2 (2015-01-09) +------------------------- + +* Renamed `Sabre\DAV\Auth\Backend\BackendInterface::requireAuth` to + `challenge`, which is a more correct and better sounding name. +* The zip release ships with [sabre/vobject 3.3.5][vobj], + [sabre/http 3.0.4][http], [sabre/event 2.0.1][evnt]. + + +2.2.0-alpha1 (2014-12-10) +------------------------- + +* The browser plugin now has a new page with information about your sabredav + server, and shows information about every plugin that's loaded in the + system. +* #191: The Authentication system can now support multiple authentication + backends. +* Removed: all `$tableName` arguments from every PDO backend. This was already + deprecated, but has now been fully removed. All of these have been replaced + with public properties. +* Deleted several classes that were already deprecated much earlier: + * `Sabre\CalDAV\CalendarRootNode` + * `Sabre\CalDAV\UserCalendars` + * `Sabre\DAV\Exception\FileNotFound` + * `Sabre\DAV\Locks\Backend\FS` + * `Sabre\DAV\PartialUpdate\IFile` + * `Sabre\DAV\URLUtil` +* Removed: `Sabre\DAV\Client::addTrustedCertificates` and + `Sabre\DAV\Client::setVerifyPeer`. +* Removed: `Sabre\DAV\Plugin::getPlugin()` can now no longer return plugins + based on its class name. +* Removed: `Sabre\DAVACL\Plugin::getPrincipalByEmail()`. +* #560: GuessContentType plugin will now set content-type to + `application/octet-stream` if a better content-type could not be determined. +* #568: Added a `componentType` argument to `ICSExportPlugin`, allowing you to + specifically fetch `VEVENT`, `VTODO` or `VJOURNAL`. +* #582: Authentication backend interface changed to be stateless. If you + implemented your own authentication backend, make sure you upgrade your class + to the latest API! +* #582: `Sabre\DAV\Auth\Plugin::getCurrentUser()` is now deprecated. Use + `Sabre\DAV\Auth\Plugin::getCurrentPrincipal()` instead. +* #193: Fix `Sabre\DAV\FSExt\Directory::getQuotaInfo()` on windows. + + +2.1.11 (2016-??-??) +------------------- + +* #805: It wasn't possible to create calendars that hold events, journals and + todos using MySQL, because the `components` column was 1 byte too small. + + +2.1.10 (2016-03-10) +------------------- + +* #784: Sync logs for address books were not correctly cleaned up after + deleting them. + + +2.1.9 (2016-01-25) +------------------ + +* #674: PHP7 support (@DeepDiver1975). +* The zip release ships with [sabre/vobject 3.5.0][vobj], + [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt]. + + +2.1.8 (2016-01-04) +------------------ + +* #729: Fixed a caching problem in the Tree object. +* #740: Bugs in `migrate20.php` script. +* The zip release ships with [sabre/vobject 3.4.8][vobj], + [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt]. + + +2.1.7 (2015-09-05) +------------------ + +* #705: A `MOVE` request that gets prevented from deleting the source resource + will still remove the target resource. Now all events are triggered before + any destructive operations. +* The zip release ships with [sabre/vobject 3.4.7][vobj], + [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt]. + + +2.1.6 (2015-07-21) +------------------ + +* #657: Migration script would break when coming a cross an iCalendar object + with no UID. +* #691: Workaround for broken Windows Phone client. +* The zip release ships with [sabre/vobject 3.4.5][vobj], + [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt]. + + +2.1.5 (2015-07-11) +------------------ + +* #677: Resources with the name '0' would not get retrieved when using + `Depth: infinity` in a `PROPFIND` request. +* The zip release ships with [sabre/vobject 3.4.5][vobj], + [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt]. + + +2.1.4 (2015-05-25) +------------------ + +* #651: Double-encoded path in the browser plugin. Should fix a few broken + links in some setups. +* #650: Correctly cleaning up change info after deleting calendars (@ErrOrnAmE). +* #658: Updating `schedule-calendar-default-URL` does not work well, so we're + disabling it until there's a better fix. +* The zip release ships with [sabre/vobject 3.4.3][vobj], + [sabre/http 3.0.5][http], and [sabre/event 2.0.2][evnt]. + + +2.1.3 (2015-02-25) +------------------ + +* #586: `SCHEDULE-STATUS` should not contain a reason-phrase. +* #539: Fixed a bug related to scheduling in shared calendars. +* #595: Support for calendar-timezone in iCalendar exports. +* #581: findByUri would send empty prefixes to the principal backend (@soydeedo) +* #611: Escaping a bit more HTML output in the browser plugin. (@LukasReschke) +* #610: Don't allow discovery of arbitrary files using `..` in the browser + plugin (@LukasReschke). +* Browser plugin now shows quota properties. +* #612: PropertyStorage didn't delete properties from nodes when a node's + parents get deleted. +* #581: Fixed problems related to finding attendee information during + scheduling. +* The zip release ships with [sabre/vobject 3.4.2][vobj], + [sabre/http 3.0.4][http], and [sabre/event 2.0.1][evnt]. + + +2.1.2 (2014-12-10) +------------------ + +* #566: Another issue related to the migration script, which would cause + scheduling to not work well for events that were already added before the + migration. +* #567: Doing freebusy requests on accounts that had 0 calendars would throw + a `E_NOTICE`. +* #572: `HEAD` requests trigger a PHP warning. +* #579: Browser plugin can throw exception for a few resourcetypes that didn't + have an icon defined. +* The zip release ships with [sabre/vobject 3.3.4][vobj], + [sabre/http 3.0.4][http], and [sabre/event 2.0.1][evnt]. + + +2.1.1 (2014-11-22) +------------------ + +* #561: IMip Plugin didn't strip mailto: from email addresses. +* #566: Migration process had 2 problems related to adding the `uid` field + to the `calendarobjects` table. +* The zip release ships with [sabre/vobject 3.3.4][vobj], + [sabre/http 3.0.2][http], and [sabre/event 2.0.1][evnt]. + + +2.1.0 (2014-11-19) +------------------ + +* #541: CalDAV PDO backend didn't respect overridden PDO table names. +* #550: Scheduling invites are no longer delivered into shared calendars. +* #554: `calendar-multiget` `REPORT` did not work on inbox items. +* #555: The `calendar-timezone` property is now respected for floating times + and all-day events in the `calendar-query`, `calendar-multiget` and + `free-busy-query` REPORTs. +* #555: The `calendar-timezone` property is also respected for scheduling + free-busy requests. +* #547: CalDAV system too aggressively 'corrects' incoming iCalendar data, and + as a result doesn't return an etag for common cases. +* The zip release ships with [sabre/vobject 3.3.4][vobj], + [sabre/http 3.0.2][http], and [sabre/event 2.0.1][evnt]. + + +2.1.0-alpha2 (2014-10-23) +------------------------- + +* Added: calendar-user-address-set to default principal search properties + list. This should fix iOS attendee autocomplete support. +* Changed: Moved all 'notifications' functionality from `Sabre\CalDAV\Plugin` + to a new plugin: `Sabre\CalDAV\Notifications\Plugin`. If you want to use + notifications-related functionality, just add this plugin. +* Changed: Accessing the caldav inbox, outbox or notification collection no + longer triggers getCalendarsForUser() on backends. +* #533: New invites are no longer delivered to taks-only calendars. +* #538: Added `calendarObjectChange` event. +* Scheduling speedups. +* #539: added `afterResponse` event. (@joserobleda) +* Deprecated: All the "tableName" constructor arguments for all the PDO + backends are now deprecated. They still work, but will be removed in the + next major sabredav version. Every argument that is now deprecated can now + be accessed as a public property on the respective backends. +* #529: Added getCalendarObjectByUID to PDO backend, speeding up scheduling + operations on large calendars. +* The zip release ships with [sabre/vobject 3.3.3][vobj], + [sabre/http 3.0.2][http], and [sabre/event 2.0.1][evnt]. + + +2.1.0-alpha1 (2014-09-23) +------------------------- + +* Added: Support for [rfc6638][rfc6638], also known as CalDAV Scheduling. +* Added: Automatically converting between vCard 3, 4 and jCard using the + `Accept:` header, in CardDAV reports, and automatically converting from + jCard to vCard upon `PUT`. It's important to note that your backends _may_ + now recieve both vCard 3.0 and 4.0. +* Added: #444. Collections can now opt-in to support high-speed `MOVE`. +* Changed: PropertyStorage backends now have a `move` method. +* Added: `beforeMove`, and `afterMove` events. +* Changed: A few database changes for the CalDAV PDO backend. Make sure you + run `bin/migrate21.php` to upgrade your database schema. +* Changed: CalDAV backends have a new method: `getCalendarObjectByUID`. This + method MUST be implemented by all backends, but the `AbstractBackend` has a + simple default implementation for this. +* Changed: `Sabre\CalDAV\UserCalendars` has been renamed to + `Sabre\CalDAV\CalendarHome`. +* Changed: `Sabre\CalDAV\CalendarRootNode` has been renamed to + `Sabre\CalDAV\CalendarRoot`. +* Changed: The IMipHandler has been completely removed. With CalDAV scheduling + support, it is no longer needed. It's functionality has been replaced by + `Sabre\CalDAV\Schedule\IMipPlugin`, which can now send emails for clients + other than iCal. +* Removed: `Sabre\DAV\ObjectTree` and `Sabre\DAV\Tree\FileSystem`. All this + functionality has been merged into `Sabre\DAV\Tree`. +* Changed: PrincipalBackend now has a findByUri method. +* Changed: `PrincipalBackend::searchPrincipals` has a new optional `test` + argument. +* Added: Support for the `{http://calendarserver.org/ns/}email-address-set` + property. +* #460: PropertyStorage must move properties during `MOVE` requests. +* Changed: Restructured the zip distribution to be a little bit more lean + and consistent. +* #524: Full support for the `test="anyof"` attribute in principal-search + `REPORT`. +* #472: Always returning lock tokens in the lockdiscovery property. +* Directory entries in the Browser plugin are sorted by type and name. + (@aklomp) +* #486: It's now possible to return additional properties when an 'allprop' + PROPFIND request is being done. (@aklomp) +* Changed: Now return HTTP errors when an addressbook-query REPORT is done + on a uri that's not a vcard. This should help with debugging this common + mistake. +* Changed: `PUT` requests with a `Content-Range` header now emit a 400 status + instead of 501, as per RFC7231. +* Added: Browser plugin can now display the contents of the + `{DAV:}supported-privilege-set` property. +* Added: Now reporting `CALDAV:max-resource-size`, but we're not actively + restricting it yet. +* Changed: CalDAV plugin is now responsible for reporting + `CALDAV:supported-collation-set` and `CALDAV:supported-calendar-data` + properties. +* Added: Now reporting `CARDDAV:max-resource-size`, but we're not actively + restricting it yet. +* Added: Support for `CARDDAV:supported-collation-set`. +* Changed: CardDAV plugin is now responsible for reporting + `CARDDAV:supported-address-data`. This functionality has been removed from + the CardDAV PDO backend. +* When a REPORT is not supported, we now emit HTTP error 415, instead of 403. +* #348: `HEAD` requests now work wherever `GET` also works. +* Changed: Lower priority for the iMip plugins `schedule` event listener. +* Added: #523 Custom CalDAV backends can now mark any calendar as read-only. +* The zip release ships with [sabre/vobject 3.3.3][vobj], + [sabre/http 3.0.0][http], and [sabre/event 2.0.0][evnt]. + + +2.0.9 (2015-09-04) +------------------ + +* #705: A `MOVE` request that gets prevented from deleting the source resource + will still remove the target resource. Now all events are triggered before + any destructive operations. +* The zip release ships with [sabre/vobject 3.4.6][vobj], + [sabre/http 2.0.4][http], and [sabre/event 1.0.1][evnt]. + + + +2.0.8 (2015-07-11) +------------------ + +* #677: Resources with the name '0' would not get retrieved when using + `Depth: infinity` in a `PROPFIND` request. +* The zip release ships with [sabre/vobject 3.3.5][vobj], + [sabre/http 2.0.4][http], and [sabre/event 1.0.1][evnt]. + + +2.0.7 (2015-05-25) +------------------ + +* #650: Correctly cleaning up change info after deleting calendars (@ErrOrnAmE). +* The zip release ships with [sabre/vobject 3.3.4][vobj], + [sabre/http 2.0.4][http], and [sabre/event 1.0.1][evnt]. + + +2.0.6 (2014-12-10) +------------------ + +* Added `Sabre\CalDAV\CalendarRoot` as an alias for + `Sabre\CalDAV\CalendarRootNode`. The latter is going to be deprecated in 2.1, + so this makes it slightly easier to write code that works in both branches. +* #497: Making sure we're initializing the sync-token field with a value after + migration. +* The zip release ships with [sabre/vobject 3.3.4][vobj], + [sabre/http 2.0.4][http], and [sabre/event 1.0.1][evnt]. + + +2.0.5 (2014-10-14) +------------------ + +* #514: CalDAV PDO backend didn't work when overriding the 'calendar changes' + database table name. +* #515: 304 status code was not being sent when checking preconditions. +* The zip release ships with [sabre/vobject 3.3.3][vobj], + [sabre/http 2.0.4][http], and [sabre/event 1.0.1][evnt]. + + +2.0.4 (2014-08-27) +------------------ + +* #483: typo in calendars creation for PostgreSQL. +* #487: Locks are now automatically removed after a node has been deleted. +* #496: Improve CalDAV and CardDAV sync when there is no webdav-sync support. +* Added: Automatically mapping internal sync-tokens to getctag. +* The zip release ships with [sabre/vobject 3.3.1][vobj], + [sabre/http 2.0.4][http], and [sabre/event 1.0.1][evnt]. + + +2.0.3 (2014-07-14) +------------------ + +* #474: Fixed PropertyStorage `pathFilter()`. +* #476: CSP policy incorrect, causing stylesheets to not load in the browser + plugin. +* #475: Href properties in the browser plugin sometimes included a backslash. +* #478: `TooMuchMatches` exception never worked. This was fixed, and we also + took this opportunity to rename it to `TooManyMatches`. +* The zip release ships with [sabre/vobject 3.2.4][vobj], + [sabre/http 2.0.4][http], and [sabre/event 1.0.1][evnt]. + + +2.0.2 (2014-06-12) +------------------ + +* #470: Fixed compatibility with PHP < 5.4.14. +* #467: Fixed a problem in `examples/calendarserver.php`. +* #466: All the postgresql sample files have been updated. +* Fixed: An error would be thrown if a client did a propfind on a node the + user didn't have access to. +* Removed: Old and broken example code from the `examples/` directory. +* The zip release ships with [sabre/vobject 3.2.3][vobj], + [sabre/http 2.0.3][http], and [sabre/event 1.0.1][evnt]. + + +2.0.1 (2014-05-28) +------------------ + +* #459: PROPFIND requests on Files with no Depth header would return a fatal + error. +* #464: A PROPFIND allprops request should not return properties with status + 404. +* The zip release ships with [sabre/vobject 3.2.2][vobj], + [sabre/http 2.0.3][http], and [sabre/event 1.0.0][evnt]. + + +2.0.0 (2014-05-22) +------------------ + +* The zip release ships with [sabre/vobject 3.2.2][vobj], + [sabre/http 2.0.3][http], and [sabre/event 1.0.0][evnt]. +* Fixed: #456: Issue in sqlite migration script. +* Updated: MySQL database schema optimized by using more efficient column types. +* Cleaned up browser design. + + +2.0.0-beta1 (2014-05-15) +------------------------- + +* The zip release ships with [sabre/vobject 3.2.2][vobj], + [sabre/http 2.0.3][http], and [sabre/event 1.0.0][evnt]. +* BC Break: Property updating and fetching got refactored. Read the [migration + document][mi20] for more information. This allows for creation of a generic + property storage, and other property-related functionality that was not + possible before. +* BC Break: Removed `propertyUpdate`, `beforeGetProperties` and + `afterGetProperties` events. +* Fixed: #413: Memory optimizations for the CardDAV PDO backend. +* Updated: Brand new browser plugin with more debugging features and a design + that is slightly less painful. +* Added: Support for the `{DAV:}supported-method-set` property server-wide. +* Making it easier for implementors to override how the CardDAV addressbook + home is located. +* Fixed: Issue #422 Preconditions were not being set on PUT on non-existant + files. Not really a chance for data-loss, but incorrect nevertheless. +* Fixed: Issue #428: Etag check with `If:` fails if the target is a collection. +* Fixed: Issues #430, #431, #433: Locks plugin didn't not properly release + filesystem based locks. +* Fixed: #443. Support for creating new calendar subscriptions for OS X 10.9.2 + and up. +* Removed: `Sabre\DAV\Server::NODE_*` constants. +* Moved all precondition checking into a central place, instead of having to + think about it on a per-method basis. +* jCal transformation for calendar-query REPORT now works again. +* Switched to PSR-4 +* Fixed: #175. Returning ETag header upon a failed `If-Match` or + `If-None-Match` check. +* Removed: `lib/Sabre/autoload.php`. Use `vendor/autoload.php` instead. +* Removed: all the rfc documentation from the sabre/dav source. This made the + package needlessly larger. +* Updated: Issue #439. Lots of updates in PATCH support. The + Sabre_DAV_PartialUpdate_IFile interface is now deprecated and will be + removed in a future version. +* Added: `Sabre\DAV\Exception\LengthRequired`. + +1.9.0-alpha2 (2014-01-14) +------------------------- + +* The zip release ships with sabre/vobject 3.1.3, sabre/http 2.0.1, and + sabre/event 1.0.0. +* Added: Browser can now inspect any node, if ?sabreaction=browser is appended. +* Fixed: Issue #178. Support for multiple items in the Timeout header. +* Fixed: Issue #382. Stricter checking if calendar-query is allowed to run. +* Added: Depth: Infinity support for PROPFIND request. Thanks Thomas Müller and + Markus Goetz. + + +1.9.0-alpha1 (2013-11-07) +------------------------- + +* The zip release ships with sabre/vobject 3.1.3, sabre/http 2.0.0alpha5, and + sabre/event 1.0.0. +* BC Break: The CardDAV and CalDAV BackendInterface each have a new method: + getMultipleCards and getMultipleCalendarObjects. The Abstract and PDO backends + have default implementations, but if you implement that interface directly, + this method is now required. +* BC Break: XML property classes now receive an extra argument in their + unserialize method ($propertyMap). This allows for recursively parsing + properties, if needed. +* BC Break: Now using sabre/event for event emitting/subscription. For plugin + authors this means Server::subscribeEvent is now Server::on, and + Server::broadcastEvent is now Server::emit. +* BC Break: Almost all core functionality moved into a CorePlugin. +* BC Break: Most events triggered by the server got an overhaul. +* Changed: Sabre\HTTP now moved into a dedicated sabre/http package. +* Added: Support for WebDAV-sync (rfc6578). +* Added: Support for caldav-subscriptions, which is an easy way for caldav + clients to manage a list of subscriptions on the server. +* Added: Support for emitting and receiving jCal instead of iCalendar for + CalDAV. +* Added: BasicCallback authenticaton backend, for creating simple authentication + systems without having to define any classes. +* Added: A $transactionType property on the server class. This can be used for + logging and performance measuring purposes. +* Fixed: If event handlers modify the request body from a PUT request, an ETag + is no longer sent back. +* Added: Sabre\DAV\IMultiGet to optimize requests that retrieve information + about lists of resources. +* Added: MultiGet support to default CalDAV and CardDAV backends, speeding up + the multiget and sync reports quite a bit! +* Added: ICSExportPlugin can now generate jCal, filter on time-ranges and expand + recurrences. +* Fixed: Read-only access to calendars still allows the sharee to modify basic + calendar properties, such as the displayname and color. +* Changed: The default supportedPrivilegeSet has changed. Most privileges are no + longer marked as abstract. +* Changed: More elegant ACL management for CalendarObject and Card nodes. +* Added: Browser plugin now marks a carddav directory as type Directory, and a + shared calendar as 'Shared'. +* Added: When debugExceptions is turned on, all previous exceptions are also + traversed. +* Removed: Got rid of the Version classes for CalDAV, CardDAV, HTTP, and DAVACL. + Now that there's no separate packages anymore, this makes a bit more sense. +* Added: Generalized the multistatus response parser a bit more, for better + re-use. +* Added: Sabre\DAV\Client now has support for complex properties for PROPPATCH. + (Issue #299). +* Added: Sabre\DAV\Client has support for gzip and deflate encoding. +* Added: Sabre\DAV\Client now has support for sending objects as streams. +* Added: Deserializer for {DAV:}current-user-privilege-set. +* Added: Addressbooks or backends can now specify custom acl rules when creating + cards. +* Added: The ability for plugins to validate custom tokens in If: headers. +* Changed: Completely refactored the Lock plugin to deal with the new If: header + system. +* Added: Checking preconditions for MOVE, COPY, DELETE and PROPPATCH methods. +* Added: has() method on DAV\Property\SupportedReportSet. +* Added: If header now gets checked (with ETag) all the time. Before the dealing + with the If-header was a responsibility of the Locking plugin. +* Fixed: Outbox access for delegates. +* Added: Issue 333: It's now possible to override the calendar-home in the + CalDAV plugin. +* Added: A negotiateContentType to HTTP\Request. A convenience method. +* Fixed: Issue 349: Denying copying or moving a resource into it's own subtree. +* Fixed: SabreDAV catches every exception again. +* Added: Issue #358, adding a component=vevent parameter to the content-types + for calendar objects, if the caldav backend provides this info. + + +1.8.12-stable (2015-01-21) +-------------------------- + +* The zip release ships with sabre/vobject 2.1.7. +* #568: Support empty usernames and passwords in basic auth. + + +1.8.11 (2014-12-10) +------------------- + +* The zip release ships with sabre/vobject 2.1.6. +* Updated: MySQL database schema optimized by using more efficient column types. +* #516: The DAV client will now only redirect to HTTP and HTTPS urls. + + +1.8.10 (2014-05-15) +------------------- + +* The zip release ships with sabre/vobject 2.1.4. +* includes changes from version 1.7.12. + + +1.8.9 (2014-02-26) +------------------ + +* The zip release ships with sabre/vobject 2.1.3. +* includes changes from version 1.7.11. + + +1.8.8 (2014-02-09) +------------------ + +* includes changes from version 1.7.10. +* The zip release ships with sabre/vobject 2.1.3. + +1.8.7 (2013-10-02) +------------------ + +* the zip release ships with sabre/vobject 2.1.3. +* includes changes from version 1.7.9. + + +1.8.6 (2013-06-18) +------------------ + +* The zip release ships with sabre/vobject 2.1.0. +* Includes changes from version 1.7.8. + + +1.8.5 (2013-04-11) +------------------ + +* The zip release ships with sabre/vobject 2.0.7. +* Includes changes from version 1.7.7. + + +1.8.4 (2013-04-08) +------------------ + +* The zip release ships with sabre/vobject 2.0.7. +* Includes changes from version 1.7.6. + + +1.8.3 (2013-03-01) +------------------ + +* The zip release ships with sabre/vobject 2.0.6. +* Includes changes from version 1.7.5. +* Fixed: organizer email-address for shared calendars is now prefixed with + mailto:, as it should. + + +1.8.2 (2013-01-19) +------------------ + +* The zip release ships with sabre/vobject 2.0.5. +* Includes changes from version 1.7.4. + + +1.8.1 (2012-12-01) +------------------ + +* The zip release ships with sabre/vobject 2.0.5. +* Includes changes from version 1.7.3. +* Fixed: Typo in 1.7 migration script caused it to fail. + + +1.8.0 (2012-11-08) +------------------ + +* The zip release ships with sabre/vobject 2.0.5. +* BC Break: Moved the entire codebase to PHP namespaces. +* BC Break: Every backend package (CalDAV, CardDAV, Auth, Locks, Principals) now + has consistent naming conventions. There's a BackendInterface, and an + AbstractBackend class. +* BC Break: Changed a bunch of constructor signatures in the CalDAV package, to + reduce dependencies on the ACL package. +* BC Break: Sabre_CalDAV_ISharedCalendar now also has a getShares method, so + sharees can figure out who is also on a shared calendar. +* Added: Sabre_DAVACL_IPrincipalCollection interface, to advertise support for + principal-property-search on any node. +* Added: Simple console script to fire up a fileserver in the current directory + using PHP 5.4's built-in webserver. +* Added: Sharee's can now also read out the list of invites for a shared + calendar. +* Added: The Proxy principal classes now both implement an interface, for + greater flexiblity. + + +1.7.13 (2014-07-28) +------------------- + +* The zip release ships with sabre/vobject 2.1.4. +* Changed: Removed phing and went with a custom build script for now. + + +1.7.12 (2014-05-15) +------------------- + +* The zip release ships with sabre/vobject 2.1.4. +* Updated: Issue #439. Lots of updates in PATCH support. The + Sabre_DAV_PartialUpdate_IFile interface is now deprecated and will be removed + in a future version. +* Fixed: Restoring old setting after changing libxml_disable_entity_loader. +* Fixed: Issue #422: Preconditions were not being set on PUT on non-existant + files. Not really a chance for data-loss, but incorrect nevertheless. +* Fixed: Issue #427: Now checking preconditions on DELETE requests. +* Fixed: Issue #428: Etag check with If: fails if the target is a collection. +* Fixed: Issue #393: PATCH request with missing end-range was handled + incorrectly. +* Added: Sabre_DAV_Exception_LengthRequired to omit 411 errors. + + +1.7.11 (2014-02-26) +------------------- + +* The zip release ships with sabre/vobject 2.1.3. +* Fixed: Issue #407: large downloads failed. +* Fixed: Issue #414: XXE security problem on older PHP versions. + + +1.7.10 (2014-02-09) +------------------- + +* Fixed: Issue #374: Don't urlescape colon (:) when it's not required. +* Fixed: Potential security vulnerability in the http client. + + +1.7.9 (2013-10-02) +------------------ + +* The zip release ships with sabre/vobject 2.1.3. +* Fixed: Issue #365. Incorrect output when principal urls have spaces in them. +* Added: Issue #367: Automatically adding a UID to vcards that don't have them. + + +1.7.8 (2013-06-17) +------------------ + +* The zip release ships with sabre/vobject 2.1.0. +* Changed: Sabre\DAV\Client::verifyPeer is now a protected property (instead of + private). +* Fixed: Text was incorrectly escaped in the Href and HrefList properties, + disallowing urls with ampersands (&) in them. +* Added: deserializer for Sabre\DAVACL\Property\CurrentUserPrivilegeSet. +* Fixed: Issue 335: Client only deserializes properties with status 200. +* Fixed: Issue 341: Escaping xml in 423 Locked error responses. +* Added: Issue 339: beforeGetPropertiesForPath event. + + +1.7.7 (2013-04-11) +------------------ + +* The zip release ships with sabre/vobject 2.0.7. +* Fixed: Assets in the browser plugins were not being served on windows + machines. + + +1.7.6 (2013-04-08) +------------------ + +* The zip release ships with sabre/vobject 2.0.7. +* Fixed: vcardurl in database schema can now hold 255 characters instead of 80 + (which is often way to small). +* Fixed: The browser plugin potentially allowed people to open any arbitrary + file on windows servers (CVE-2013-1939). + + +1.7.5 (2013-03-01) +------------------ + +* The zip release ships with sabre/vobject 2.0.6. +* Change: No longer advertising support for 4.0 vcards. iOS and OS X address + book don't handle this well, and just advertising 3.0 support seems like the + most logical course of action. +* Added: ->setVerifyPeers to Sabre_DAV_Client (greatly resisting against it, + don't use this..). + + +1.7.4 (2013-01-19) +------------------ + +* The zip release ships with sabre/vobject 2.0.5. +* Changed: To be compatibile with MS Office 2011 for Mac, a workaround was + removed that was added to support old versions of Windows XP (pre-SP3). + Indeed! We needed a crazy workaround to work with one MS product in the past, + and we can't keep that workaround to be compatible with another MS product. +* Fixed: expand-properties REPORT had incorrect values for the href element. +* Fixed: Range requests now work for non-seekable streams. (Thanks Alfred + Klomp). +* Fixed: Changed serialization of {DAV:}getlastmodified and {DAV:}supportedlock + to improve compatiblity with MS Office 2011 for Mac. +* Changed: reverted the automatic translation of 'DAV:' xml namespaces to + 'urn:DAV' when parsing files. Issues were reported with libxml 2.6.32, on a + relatively recent debian release, so we'll wait till 2015 to take this one out + again. +* Added: Sabre_DAV_Exception_ServiceUnavailable, for emitting 503's. + + +1.7.3 (2012-12-01) +------------------ + +* The zip release ships with sabre/vobject 2.0.5. +* Fixed: Removing double slashes from getPropertiesForPath. +* Change: Marked a few more properties in the CardDAV as protected, instead of + private. +* Fixed: SharingPlugin now plays nicer with other plugins with similar + functionality. +* Fixed: Issue 174. Sending back HTTP/1.0 for requests with this version. + + +1.7.2 (2012-11-08) +------------------ + +* The zip release ships with sabre/vobject 2.0.5. +* Added: ACL plugin advertises support for 'calendarserver-principal- + property-search'. +* Fixed: [#153] Allowing for relative http principals in iMip requests. +* Added: Support for cs:first-name and cs:last-name properties in sharing + invites. +* Fixed: Made a bunch of properties protected, where they were private before. +* Added: Some non-standard properties for sharing to improve compatibility. +* Fixed: some bugfixes in postgres sql script. +* Fixed: When requesting some properties using PROPFIND, they could show up as + both '200 Ok' and '403 Forbidden'. +* Fixed: calendar-proxy principals were not checked for deeper principal + membership than 1 level. +* Fixed: setGroupMemberSet argument now correctly receives relative principal + urls, instead of the absolute ones. +* Fixed: Server class will filter out any bonus properties if any extra were + returned. This means the implementor of the IProperty class can be a bit + lazier when implementing. Note: bug numbers after this line refer to Google + Code tickets. We're using github now. + + +1.7.1 (2012-10-07) +------------------ + +* Fixed: include path problem in the migration script. + + +1.7.0 (2012-10-06) +------------------ + +* BC Break: The calendarobjects database table has a bunch of new fields, and a + migration script is required to ensure everything will keep working. Read the + wiki for more details. +* BC Break: The ICalendar interface now has a new method: calendarQuery. +* BC Break: In this version a number of classes have been deleted, that have + been previously deprecated. Namely: - Sabre_DAV_Directory (now: + Sabre_DAV_Collection) - Sabre_DAV_SimpleDirectory (now: + Sabre_DAV_SimpleCollection) +* BC Break: Sabre_CalDAV_Schedule_IMip::sendMessage now has an extra argument. + If you extended this class, you should fix this method. It's only used for + informational purposes. +* BC Break: The DAV: namespace is no longer converted to urn:DAV. This was a + workaround for a bug in older PHP versions (pre-5.3). +* Removed: Sabre.includes.php was deprecated, and is now removed. +* Removed: Sabre_CalDAV_Server was deprecated, and is now removed. Please use + Sabre_DAV_Server and check the examples in the examples/ directory. +* Changed: The Sabre_VObject library now spawned into it's own project! The + VObject library is still included in the SabreDAV zip package. +* Added: Experimental interfaces to allow implementation of caldav-sharing. Note + that no implementation is provided yet, just the api hooks. +* Added: Free-busy reporting compliant with the caldav-scheduling standard. This + allows iCal and other clients to fetch other users' free-busy data. +* Added: Experimental NotificationSupport interface to add caldav notifications. +* Added: VCF Export plugin. If enabled, it can generate an export of an entire + addressbook. +* Added: Support for PATCH using a SabreDAV format, to live-patch files. +* Added: Support for Prefer: return-minimal and Brief: t headers for PROPFIND + and PROPPATCH requests. +* Changed: Responsibility for dealing with the calendar-query is now moved from + the CalDAV plugin to the CalDAV backends. This allows for heavy optimizations. +* Changed: The CalDAV PDO backend is now a lot faster for common calendar + queries. +* Changed: We are now using the composer autoloader. +* Changed: The CalDAV backend now all implement an interface. +* Changed: Instead of Sabre_DAV_Property, Sabre_DAV_PropertyInterface is now the + basis of every property class. +* Update: Caching results for principal lookups. This should cut down queries + and performance for a number of heavy requests. +* Update: ObjectTree caches lookups much more aggresively, which will help + especially speeding up a bunch of REPORT queries. +* Added: Support for the schedule-calendar-transp property. +* Fixed: Marking both the text/calendar and text/x-vcard as UTF-8 encoded. +* Fixed: Workaround for the SOGO connector, as it doesn't understand receiving + "text/x-vcard; charset=utf-8" for a contenttype. +* Added: Sabre_DAV_Client now throws more specific exceptions in cases where we + already has an exception class. +* Added: Sabre_DAV_PartialUpdate. This plugin allows you to use the PATCH method + to update parts of a file. +* Added: Tons of timezone name mappings for Microsoft Exchange. +* Added: Support for an 'exception' event in the server class. +* Fixed: Uploaded VCards without a UID are now rejected. (thanks Dominik!) +* Fixed: Rejecting calendar objects if they are not in the + supported-calendar-component list. (thanks Armin!) +* Fixed: Issue 219: serialize() now reorders correctly. +* Fixed: Sabre_DAV_XMLUtil no longer returns empty $dom->childNodes if there is + whitespace in $dom. +* Fixed: Returning 409 Conflict instead of 500 when an attempt is made to create + a file as a child of something that's not a collection. +* Fixed: Issue 237: xml-encoding values in SabreDAV error responses. +* Fixed: Returning 403, instead of 501 when an unknown REPORT is requested. +* Fixed: Postfixing slash on {DAV:}owner properties. +* Fixed: Several embarrassing spelling mistakes in docblocks. + + +1.6.10 (2013-06-17) +------------------- + +* Fixed: Text was incorrectly escaped in the Href and HrefList properties, + disallowing urls with ampersands (&) in them. +* Fixed: Issue 341: Escaping xml in 423 Locked error responses. + + +1.6.9 (2013-04-11) +------------------ + +* Fixed: Assets in the browser plugins were not being served on windows + machines. + + +1.6.8 (2013-04-08) +------------------ + +* Fixed: vcardurl in database schema can now hold 255 characters instead of 80 + (which is often way to small). +* Fixed: The browser plugin potentially allowed people to open any arbitrary + file on windows servers. (CVE-2013-1939). + + +1.6.7 (2013-03-01) +------------------ + +* Change: No longer advertising support for 4.0 vcards. iOS and OS X address + book don't handle this well, and just advertising 3.0 support seems like the + most logical course of action. +* Added: ->setVerifyPeers to Sabre_DAV_Client (greatly resisting against it, + don't use this..). + + +1.6.6 (2013-01-19) +------------------ + +* Fixed: Backported a fix for broken XML serialization in error responses. + (Thanks @DeepDiver1975!) + + +1.6.5 (2012-10-04) +------------------ + +* Fixed: Workaround for line-ending bug OS X 10.8 addressbook has. +* Added: Ability to allow users to set SSL certificates for the Client class. + (Thanks schiesbn!). +* Fixed: Directory indexes with lots of nodes should be a lot faster. +* Fixed: Issue 235: E_NOTICE thrown when doing a propfind request with + Sabre_DAV_Client, and no valid properties are returned. +* Fixed: Issue with filtering on alarms in tasks. + + +1.6.4 (2012-08-02) +------------------ + +* Fixed: Issue 220: Calendar-query filters may fail when filtering on alarms, if + an overridden event has it's alarm removed. +* Fixed: Compatibility for OS/X 10.8 iCal in the IMipHandler. +* Fixed: Issue 222: beforeWriteContent shouldn't be called for lock requests. +* Fixed: Problem with POST requests to the outbox if mailto: was not lower + cased. +* Fixed: Yearly recurrence rule expansion on leap-days no behaves correctly. +* Fixed: Correctly checking if recurring, all-day events with no dtstart fall in + a timerange if the start of the time-range exceeds the start of the instance + of an event, but not the end. +* Fixed: All-day recurring events wouldn't match if an occurence ended exactly + on the start of a time-range. +* Fixed: HTTP basic auth did not correctly deal with passwords containing colons + on some servers. +* Fixed: Issue 228: DTEND is now non-inclusive for all-day events in the + calendar-query REPORT and free-busy calculations. + + +1.6.3 (2012-06-12) +------------------ + +* Added: It's now possible to specify in Sabre_DAV_Client which type of + authentication is to be used. +* Fixed: Issue 206: Sabre_DAV_Client PUT requests are fixed. +* Fixed: Issue 205: Parsing an iCalendar 0-second date interval. +* Fixed: Issue 112: Stronger validation of iCalendar objects. Now making sure + every iCalendar object only contains 1 component, and disallowing vcards, + forcing every component to have a UID. +* Fixed: Basic validation for vcards in the CardDAV plugin. +* Fixed: Issue 213: Workaround for an Evolution bug, that prevented it from + updating events. +* Fixed: Issue 211: A time-limit query on a non-relative alarm trigger in a + recurring event could result in an endless loop. +* Fixed: All uri fields are now a maximum of 200 characters. The Bynari outlook + plugin used much longer strings so this should improve compatibility. +* Fixed: Added a workaround for a bug in KDE 4.8.2 contact syncing. See + https://bugs.kde.org/show_bug.cgi?id=300047 +* Fixed: Issue 217: Sabre_DAV_Tree_FileSystem was pretty broken. + + +1.6.2 (2012-04-16) +------------------ + +* Fixed: Sabre_VObject_Node::$parent should have been public. +* Fixed: Recurrence rules of events are now taken into consideration when doing + time-range queries on alarms. +* Fixed: Added a workaround for the fact that php's DateInterval cannot parse + weeks and days at the same time. +* Added: Sabre_DAV_Server::$exposeVersion, allowing you to hide SabreDAV's + version number from various outputs. +* Fixed: DTSTART values would be incorrect when expanding events. +* Fixed: DTSTART and DTEND would be incorrect for expansion of WEEKLY BYDAY + recurrences. +* Fixed: Issue 203: A problem with overridden events hitting the exact date and + time of a subsequent event in the recurrence set. +* Fixed: There was a problem with recurrence rules, for example the 5th tuesday + of the month, if this day did not exist. +* Added: New HTTP status codes from draft-nottingham-http-new-status-04. + + +1.6.1 (2012-03-05) +------------------ + +* Added: createFile and put() can now return an ETag. +* Added: Sending back an ETag on for operations on CardDAV backends. This should + help with OS X 10.6 Addressbook compatibility. +* Fixed: Fixed a bug where an infinite loop could occur in the recurrence + iterator if the recurrence was YEARLY, with a BYMONTH rule, and either BYDAY + or BYMONTHDAY match the first day of the month. +* Fixed: Events that are excluded using EXDATE are still counted in the COUNT= + parameter in the RRULE property. +* Added: Support for time-range filters on VALARM components. +* Fixed: Correctly filtering all-day events. +* Fixed: Sending back correct mimetypes from the browser plugin (thanks + Jürgen). +* Fixed: Issue 195: Sabre_CardDAV pear package had an incorrect dependency. +* Fixed: Calendardata would be destroyed when performing a MOVE request. + + +1.6.0 (2012-02-22) +------------------ + +* BC Break: Now requires PHP 5.3 +* BC Break: Any node that implemented Sabre_DAVACL_IACL must now also implement + the getSupportedPrivilegeSet method. See website for details. +* BC Break: Moved functions from Sabre_CalDAV_XMLUtil to + Sabre_VObject_DateTimeParser. +* BC Break: The Sabre_DAVACL_IPrincipalCollection now has two new methods: + 'searchPrincipals' and 'updatePrincipal'. +* BC Break: Sabre_DAV_ILockable is removed and all related per-node locking + functionality. +* BC Break: Sabre_DAV_Exception_FileNotFound is now deprecated in favor of + Sabre_DAV_Exception_NotFound. The former will be removed in a later version. +* BC Break: Removed Sabre_CalDAV_ICalendarUtil, use Sabre_VObject instead. +* BC Break: Sabre_CalDAV_Server is now deprecated, check out the documentation + on how to setup a caldav server with just Sabre_DAV_Server. +* BC Break: Default Principals PDO backend now needs a new field in the + 'principals' table. See the website for details. +* Added: Ability to create new calendars and addressbooks from within the + browser plugin. +* Added: Browser plugin: icons for various nodes. +* Added: Support for FREEBUSY reports! +* Added: Support for creating principals with admin-level privileges. +* Added: Possibility to let server send out invitation emails on behalf of + CalDAV client, using Sabre_CalDAV_Schedule_IMip. +* Changed: beforeCreateFile event now passes data argument by reference. +* Changed: The 'propertyMap' property from Sabre_VObject_Reader, must now be + specified in Sabre_VObject_Property::$classMap. +* Added: Ability for plugins to tell the ACL plugin which principal plugins are + searchable. +* Added: [DAVACL] Per-node overriding of supported privileges. This allows for + custom privileges where needed. +* Added: [DAVACL] Public 'principalSearch' method on the DAVACL plugin, which + allows for easy searching for principals, based on their properties. +* Added: Sabre_VObject_Component::getComponents() to return a list of only + components and not properties. +* Added: An includes.php file in every sub-package (CalDAV, CardDAV, DAV, + DAVACL, HTTP, VObject) as an alternative to the autoloader. This often works + much faster. +* Added: Support for the 'Me card', which allows Addressbook.app users specify + which vcard is their own. +* Added: Support for updating principal properties in the DAVACL principal + backends. +* Changed: Major refactoring in the calendar-query REPORT code. Should make + things more flexible and correct. +* Changed: The calendar-proxy-[read|write] principals will now only appear in + the tree, if they actually exist in the Principal backend. This should reduce + some problems people have been having with this. +* Changed: Sabre_VObject_Element_* classes are now renamed to + Sabre_VObject_Property. Old classes are retained for backwards compatibility, + but this will be removed in the future. +* Added: Sabre_VObject_FreeBusyGenerator to generate free-busy reports based on + lists of events. +* Added: Sabre_VObject_RecurrenceIterator to find all the dates and times for + recurring events. +* Fixed: Issue 97: Correctly handling RRULE for the calendar-query REPORT. +* Fixed: Issue 154: Encoding of VObject parameters with no value was incorrect. +* Added: Support for {DAV:}acl-restrictions property from RFC3744. +* Added: The contentlength for calendar objects can now be supplied by a CalDAV + backend, allowing for more optimizations. +* Fixed: Much faster implementation of Sabre_DAV_URLUtil::encodePath. +* Fixed: {DAV:}getcontentlength may now be not specified. +* Fixed: Issue 66: Using rawurldecode instead of urldecode to decode paths from + clients. This means that + will now be treated as a literal rather than a + space, and this should improve compatibility with the Windows built-in client. +* Added: Sabre_DAV_Exception_PaymentRequired exception, to emit HTTP 402 status + codes. +* Added: Some mysql unique constraints to example files. +* Fixed: Correctly formatting HTTP dates. +* Fixed: Issue 94: Sending back Last-Modified header for 304 responses. +* Added: Sabre_VObject_Component_VEvent, Sabre_VObject_Component_VJournal, + Sabre_VObject_Component_VTodo and Sabre_VObject_Component_VCalendar. +* Changed: Properties are now also automatically mapped to their appropriate + classes, if they are created using the add() or __set() methods. +* Changed: Cloning VObject objects now clones the entire tree, rather than just + the default shallow copy. +* Added: Support for recurrence expansion in the CALDAV:calendar-multiget and + CALDAV:calendar-query REPORTS. +* Changed: CalDAV PDO backend now sorts calendars based on the internal + 'calendarorder' field. +* Added: Issue 181: Carddav backends may no optionally not supply the carddata + in getCards, if etag and size are specified. This may speed up certain + requests. +* Added: More arguments to beforeWriteContent and beforeCreateFile (see + WritingPlugins wiki document). +* Added: Hook for iCalendar validation. This allows us to validate iCalendar + objects when they're uploaded. At the moment we're just validating syntax. +* Added: VObject now support Windows Timezone names correctly (thanks mrpace2). +* Added: If a timezonename could not be detected, we fall back on the default + PHP timezone. +* Added: Now a Composer package (thanks willdurand). +* Fixed: Support for \N as a newline character in the VObject reader. +* Added: afterWriteContent, afterCreateFile and afterUnbind events. +* Added: Postgresql example files. Not part of the unittests though, so use at + your own risk. +* Fixed: Issue 182: Removed backticks from sql queries, so it will work with + Postgres. + + +1.5.9 (2012-04-16) +------------------ + +* Fixed: Issue with parsing timezone identifiers that were surrounded by quotes. + (Fixes emClient compatibility). + + +1.5.8 (2012-02-22) +------------------ + +* Fixed: Issue 95: Another timezone parsing issue, this time in calendar-query. + + +1.5.7 (2012-02-19) +------------------ + +* Fixed: VObject properties are now always encoded before components. +* Fixed: Sabre_DAVACL had issues with multiple levels of privilege aggregration. +* Changed: Added 'GuessContentType' plugin to fileserver.php example. +* Fixed: The Browser plugin will now trigger the correct events when creating + files. +* Fixed: The ICSExportPlugin now considers ACL's. +* Added: Made it optional to supply carddata from an Addressbook backend when + requesting getCards. This can make some operations much faster, and could + result in much lower memory use. +* Fixed: Issue 187: Sabre_DAV_UUIDUtil was missing from includes file. +* Fixed: Issue 191: beforeUnlock was triggered twice. + + +1.5.6 (2012-01-07) +------------------ + +* Fixed: Issue 174: VObject could break UTF-8 characters. +* Fixed: pear package installation issues. + + +1.5.5 (2011-12-16) +------------------ + +* Fixed: CalDAV time-range filter workaround for recurring events. +* Fixed: Bug in Sabre_DAV_Locks_Backend_File that didn't allow multiple files to + be locked at the same time. + + +1.5.4 (2011-10-28) +------------------ + +* Fixed: GuessContentType plugin now supports mixed case file extensions. +* Fixed: DATE-TIME encoding was wrong in VObject. (we used 'DATETIME'). +* Changed: Sending back HTTP 204 after a PUT request on an existing resource + instead of HTTP 200. This should fix Evolution CardDAV client compatibility. +* Fixed: Issue 95: Parsing X-LIC-LOCATION if it's available. +* Added: All VObject elements now have a reference to their parent node. + + +1.5.3 (2011-09-28) +------------------ + +* Fixed: Sabre_DAV_Collection was missing from the includes file. +* Fixed: Issue 152. iOS 1.4.2 apparantly requires HTTP/1.1 200 OK to be in + uppercase. +* Fixed: Issue 153: Support for files with mixed newline styles in + Sabre_VObject. +* Fixed: Issue 159: Automatically converting any vcard and icalendardata to + UTF-8. +* Added: Sabre_DAV_SimpleFile class for easy static file creation. +* Added: Issue 158: Support for the CARDDAV:supported-address-data property. + + +1.5.2 (2011-09-21) +------------------ + +* Fixed: carddata and calendardata MySQL fields are now of type 'mediumblob'. + 'TEXT' was too small sometimes to hold all the data. +* Fixed: {DAV:}supported-report-set is now correctly reporting the reports for + IAddressBook. +* Added: Sabre_VObject_Property::add() to add duplicate parameters to + properties. +* Added: Issue 151: Sabre_CalDAV_ICalendar and Sabre_CalDAV_ICalendarObject + interfaces. +* Fixed: Issue 140: Not returning 201 Created if an event cancelled the creation + of a file. +* Fixed: Issue 150: Faster URLUtil::encodePath() implementation. +* Fixed: Issue 144: Browser plugin could interfere with + TemporaryFileFilterPlugin if it was loaded first. +* Added: It's not possible to specify more 'alternate uris' in principal + backends. + + +1.5.1 (2011-08-24) +------------------ + +* Fixed: Issue 137. Hiding action interface in HTML browser for non-collections. +* Fixed: addressbook-query is now correctly returned from the + {DAV:}supported-report-set property. +* Fixed: Issue 142: Bugs in groupwareserver.php example. +* Fixed: Issue 139: Rejecting PUT requests with Content-Range. + + +1.5.0 (2011-08-12) +------------------ + +* Added: CardDAV support. +* Added: An experimental WebDAV client. +* Added: MIME-Directory grouping support in the VObject library. This is very + useful for people attempting to parse vcards. +* BC Break: Adding parameters with the VObject libraries now overwrites the + previous parameter, rather than just add it. This makes more sense for 99% of + the cases. +* BC Break: lib/Sabre.autoload.php is now removed in favor of + lib/Sabre/autoload.php. +* Deprecated: Sabre_DAV_Directory is now deprecated and will be removed in a + future version. Use Sabre_DAV_Collection instead. +* Deprecated: Sabre_DAV_SimpleDirectory is now deprecated and will be removed in + a future version. Use Sabre_DAV_SimpleCollection instead. +* Fixed: Problem with overriding tablenames for the CalDAV backend. +* Added: Clark-notation parser to XML utility. +* Added: unset() support to VObject components. +* Fixed: Refactored CalDAV property fetching to be faster and simpler. +* Added: Central string-matcher for CalDAV and CardDAV plugins. +* Added: i;unicode-casemap support +* Fixed: VObject bug: wouldn't parse parameters if they weren't specified in + uppercase. +* Fixed: VObject bug: Parameters now behave more like Properties. +* Fixed: VObject bug: Parameters with no value are now correctly parsed. +* Changed: If calendars don't specify which components they allow, 'all' + components are assumed (e.g.: VEVENT, VTODO, VJOURNAL). +* Changed: Browser plugin now uses POST variable 'sabreAction' instead of + 'action' to reduce the chance of collisions. + + +1.4.4 (2011-07-07) +------------------ + +* Fixed: Issue 131: Custom CalDAV backends could break in certain cases. +* Added: The option to override the default tablename all PDO backends use. + (Issue 60). +* Fixed: Issue 124: 'File' authentication backend now takes realm into + consideration. +* Fixed: Sabre_DAV_Property_HrefList now properly deserializes. This allows + users to update the {DAV:}group-member-set property. +* Added: Helper functions for DateTime-values in Sabre_VObject package. +* Added: VObject library can now automatically map iCalendar properties to + custom classes. + + +1.4.3 (2011-04-25) +------------------ + +* Fixed: Issue 123: Added workaround for Windows 7 UNLOCK bug. +* Fixed: datatype of lastmodified field in mysql.calendars.sql. Please change + the DATETIME field to an INT to ensure this field will work correctly. +* Change: Sabre_DAV_Property_Principal is now renamed to + Sabre_DAVACL_Property_Principal. +* Added: API level support for ACL HTTP method. +* Fixed: Bug in serializing {DAV:}acl property. +* Added: deserializer for {DAV:}resourcetype property. +* Added: deserializer for {DAV:}acl property. +* Added: deserializer for {DAV:}principal property. + + +1.4.2-beta (2011-04-01) +----------------------- + +* Added: It's not possible to disable listing of nodes that are denied read + access by ACL. +* Fixed: Changed a few properties in CalDAV classes from private to protected. +* Fixed: Issue 119: Terrible things could happen when relying on guessBaseUri, + the server was running on the root of the domain and a user tried to access a + file ending in .php. This is a slight BC break. +* Fixed: Issue 118: Lock tokens in If headers without a uri should be treated as + the request uri, not 'all relevant uri's. +* Fixed: Issue 120: PDO backend was incorrectly fetching too much locks in cases + where there were similar named locked files in a directory. + + +1.4.1-beta (2011-02-26) +----------------------- + +* Fixed: Sabre_DAV_Locks_Backend_PDO returned too many locks. +* Fixed: Sabre_HTTP_Request::getHeader didn't return Content-Type when running + on apache, so a few workarounds were added. +* Change: Slightly changed CalDAV Backend API's, to allow for heavy + optimizations. This is non-bc breaking. + + +1.4.0-beta (2011-02-12) +----------------------- + +* Added: Partly RFC3744 ACL support. +* Added: Calendar-delegation (caldav-proxy) support. +* BC break: In order to fix Issue 99, a new argument had to be added to + Sabre_DAV_Locks_Backend_*::getLocks classes. Consult the classes for details. +* Deprecated: Sabre_DAV_Locks_Backend_FS is now deprecated and will be removed + in a later version. Use PDO or the new File class instead. +* Deprecated: The Sabre_CalDAV_ICalendarUtil class is now marked deprecated, and + will be removed in a future version. Please use Sabre_VObject instead. +* Removed: All principal-related functionality has been removed from the + Sabre_DAV_Auth_Plugin, and moved to the Sabre_DAVACL_Plugin. +* Added: VObject library, for easy vcard/icalendar parsing using a natural + interface. +* Added: Ability to automatically generate full .ics feeds off calendars. To + use: Add the Sabre_CalDAV_ICSExportPlugin, and add ?export to your calendar + url. +* Added: Plugins can now specify a pluginname, for easy access using + Sabre_DAV_Server::getPlugin(). +* Added: beforeGetProperties event. +* Added: updateProperties event. +* Added: Principal listings and calendar-access can now be done privately, + disallowing users from accessing or modifying other users' data. +* Added: You can now pass arrays to the Sabre_DAV_Server constructor. If it's an + array with node-objects, a Root collection will automatically be created, and + the nodes are used as top-level children. +* Added: The principal base uri is now customizable. It used to be hardcoded to + 'principals/[user]'. +* Added: getSupportedReportSet method in ServerPlugin class. This allows you to + easily specify which reports you're implementing. +* Added: A '..' link to the HTML browser. +* Fixed: Issue 99: Locks on child elements were ignored when their parent nodes + were deleted. +* Fixed: Issue 90: lockdiscovery property and LOCK response now include a + {DAV}lockroot element. +* Fixed: Issue 96: support for 'default' collation in CalDAV text-match filters. +* Fixed: Issue 102: Ensuring that copy and move with identical source and + destination uri's fails. +* Fixed: Issue 105: Supporting MKCALENDAR with no body. +* Fixed: Issue 109: Small fixes in Sabre_HTTP_Util. +* Fixed: Issue 111: Properly catching the ownername in a lock (if it's a string) +* Fixed: Sabre_DAV_ObjectTree::nodeExist always returned false for the root + node. +* Added: Global way to easily supply new resourcetypes for certain node classes. +* Fixed: Issue 59: Allowing the user to override the authentication realm in + Sabre_CalDAV_Server. +* Update: Issue 97: Looser time-range checking if there's a recurrence rule in + an event. This fixes 'missing recurring events'. + + +1.3.0 (2010-10-14) +------------------ + +* Added: childExists method to Sabre_DAV_ICollection. This is an api break, so + if you implement Sabre_DAV_ICollection directly, add the method. +* Changed: Almost all HTTP method implementations now take a uri argument, + including events. This allows for internal rerouting of certain calls. If you + have custom plugins, make sure they use this argument. If they don't, they + will likely still work, but it might get in the way of future changes. +* Changed: All getETag methods MUST now surround the etag with double-quotes. + This was a mistake made in all previous SabreDAV versions. If you don't do + this, any If-Match, If-None-Match and If: headers using Etags will work + incorrectly. (Issue 85). +* Added: Sabre_DAV_Auth_Backend_AbstractBasic class, which can be used to easily + implement basic authentication. +* Removed: Sabre_DAV_PermissionDenied class. Use Sabre_DAV_Forbidden instead. +* Removed: Sabre_DAV_IDirectory interface, use Sabre_DAV_ICollection instead. +* Added: Browser plugin now uses {DAV:}displayname if this property is + available. +* Added: Cache layer in the ObjectTree. +* Added: Tree classes now have a delete and getChildren method. +* Fixed: If-Modified-Since and If-Unmodified-Since would be incorrect if the + date is an exact match. +* Fixed: Support for multiple ETags in If-Match and If-None-Match headers. +* Fixed: Improved baseUrl handling. +* Fixed: Issue 67: Non-seekable stream support in ::put()/::get(). +* Fixed: Issue 65: Invalid dates are now ignored. +* Updated: Refactoring in Sabre_CalDAV to make everything a bit more ledgable. +* Fixed: Issue 88, Issue 89: Fixed compatibility for running SabreDAV on + Windows. +* Fixed: Issue 86: Fixed Content-Range top-boundary from 'file size' to 'file + size'-1. + + +1.2.5 (2010-08-18) +------------------ + +* Fixed: Issue 73: guessBaseUrl fails for some servers. +* Fixed: Issue 67: SabreDAV works better with non-seekable streams. +* Fixed: If-Modified-Since and If-Unmodified-Since would be incorrect if + the date is an exact match. + + +1.2.4 (2010-07-13) +------------------ + +* Fixed: Issue 62: Guessing baseUrl fails when url contains a query-string. +* Added: Apache configuration sample for CGI/FastCGI setups. +* Fixed: Issue 64: Only returning calendar-data when it was actually requested. + + +1.2.3 (2010-06-26) +------------------ + +* Fixed: Issue 57: Supporting quotes around etags in If-Match and If-None-Match + + +1.2.2 (2010-06-21) +------------------ + +* Updated: SabreDAV now attempts to guess the BaseURI if it's not set. +* Updated: Better compatibility with BitKinex +* Fixed: Issue 56: Incorrect behaviour for If-None-Match headers and GET + requests. +* Fixed: Issue with certain encoded paths in Browser Plugin. + + +1.2.1 (2010-06-07) +------------------ + +* Fixed: Issue 50, patch by Mattijs Hoitink. +* Fixed: Issue 51, Adding windows 7 lockfiles to TemporaryFileFilter. +* Fixed: Issue 38, Allowing custom filters to be added to TemporaryFileFilter. +* Fixed: Issue 53, ETags in the If: header were always failing. This behaviour + is now corrected. +* Added: Apache Authentication backend, in case authentication through .htaccess + is desired. +* Updated: Small improvements to example files. + + +1.2.0 (2010-05-24) +------------------ + +* Fixed: Browser plugin now displays international characters. +* Changed: More properties in CalDAV classes are now protected instead of + private. + + +1.2.0beta3 (2010-05-14) +----------------------- + +* Fixed: Custom properties were not properly sent back for allprops requests. +* Fixed: Issue 49, incorrect parsing of PROPPATCH, affecting Office 2007. +* Changed: Removed CalDAV items from includes.php, and added a few missing ones. + + +1.2.0beta2 (2010-05-04) +----------------------- + +* Fixed: Issue 46: Fatal error for some non-existent nodes. +* Updated: some example sql to include email address. +* Added: 208 and 508 statuscodes from RFC5842. +* Added: Apache2 configuration examples + + +1.2.0beta1 (2010-04-28) +----------------------- + +* Fixed: redundant namespace declaration in resourcetypes. +* Fixed: 2 locking bugs triggered by litmus when no Sabre_DAV_ILockable + interface is used. +* Changed: using http://sabredav.org/ns for all custom xml properties. +* Added: email address property to principals. +* Updated: CalendarObject validation. + + +1.2.0alpha4 (2010-04-24) +------------------------ + +* Added: Support for If-Range, If-Match, If-None-Match, If-Modified-Since, + If-Unmodified-Since. +* Changed: Brand new build system. Functionality is split up between Sabre, + Sabre_HTTP, Sabre_DAV and Sabre_CalDAV packages. In addition to that a new + non-pear package will be created with all this functionality combined. +* Changed: Autoloader moved to Sabre/autoload.php. +* Changed: The Allow: header is now more accurate, with appropriate HTTP methods + per uri. +* Changed: Now throwing back Sabre_DAV_Exception_MethodNotAllowed on a few + places where Sabre_DAV_Exception_NotImplemented was used. + + +1.2.0alpha3 (2010-04-20) +------------------------ + +* Update: Complete rewrite of property updating. Now easier to use and atomic. +* Fixed: Issue 16, automatically adding trailing / to baseUri. +* Added: text/plain is used for .txt files in GuessContentType plugin. +* Added: support for principal-property-search and principal-search-property-set + reports. +* Added: Issue 31: Hiding exception information by default. Can be turned on + with the Sabre_DAV_Server::$debugExceptions property. + + +1.2.0alpha2 (2010-04-08) +------------------------ + +* Added: Calendars are now private and can only be read by the owner. +* Fixed: double namespace declaration in multistatus responses. +* Added: MySQL database dumps. MySQL is now also supported next to SQLite. +* Added: expand-properties REPORT from RFC 3253. +* Added: Sabre_DAV_Property_IHref interface for properties exposing urls. +* Added: Issue 25: Throwing error on broken Finder behaviour. +* Changed: Authentication backend is now aware of current user. + + +1.2.0alpha1 (2010-03-31) +------------------------ + +* Fixed: Issue 26: Workaround for broken GVFS behaviour with encoded special + characters. +* Fixed: Issue 34: Incorrect Lock-Token response header for LOCK. Fixes Office + 2010 compatibility. +* Added: Issue 35: SabreDAV version to header to OPTIONS response to ease + debugging. +* Fixed: Issue 36: Incorrect variable name, throwing error in some requests. +* Fixed: Issue 37: Incorrect smultron regex in temporary filefilter. +* Fixed: Issue 33: Converting ISO-8859-1 characters to UTF-8. +* Fixed: Issue 39 & Issue 40: Basename fails on non-utf-8 locales. +* Added: More unittests. +* Added: SabreDAV version to all error responses. +* Added: URLUtil class for decoding urls. +* Changed: Now using pear.sabredav.org pear channel. +* Changed: Sabre_DAV_Server::getCopyAndMoveInfo is now a public method. + + +1.1.2-alpha (2010-03-18) +------------------------ + +* Added: RFC5397 - current-user-principal support. +* Fixed: Issue 27: encoding entities in property responses. +* Added: naturalselection script now allows the user to specify a 'minimum + number of bytes' for deletion. This should reduce load due to less crawling +* Added: Full support for the calendar-query report. +* Added: More unittests. +* Added: Support for complex property deserialization through the static + ::unserialize() method. +* Added: Support for modifying calendar-component-set +* Fixed: Issue 29: Added TIMEOUT_INFINITE constant + + +1.1.1-alpha (2010-03-11) +------------------------ + +* Added: RFC5689 - Extended MKCOL support. +* Fixed: Evolution support for CalDAV. +* Fixed: PDO-locks backend was pretty much completely broken. This is 100% + unittested now. +* Added: support for ctags. +* Fixed: Comma's between HTTP methods in 'Allow' method. +* Changed: default argument for Sabre_DAV_Locks_Backend_FS. This means a + datadirectory must always be specified from now on. +* Changed: Moved Sabre_DAV_Server::parseProps to + Sabre_DAV_XMLUtil::parseProperties. +* Changed: Sabre_DAV_IDirectory is now Sabre_DAV_ICollection. +* Changed: Sabre_DAV_Exception_PermissionDenied is now + Sabre_DAV_Exception_Forbidden. +* Changed: Sabre_CalDAV_ICalendarCollection is removed. +* Added: Sabre_DAV_IExtendedCollection. +* Added: Many more unittests. +* Added: support for calendar-timezone property. + + +1.1.0-alpha (2010-03-01) +------------------------ + +* Note: This version is forked from version 1.0.5, so release dates may be out + of order. +* Added: CalDAV - RFC 4791 +* Removed: Sabre_PHP_Exception. PHP has a built-in ErrorException for this. +* Added: PDO authentication backend. +* Added: Example sql for auth, caldav, locks for sqlite. +* Added: Sabre_DAV_Browser_GuessContentType plugin +* Changed: Authentication plugin refactored, making it possible to implement + non-digest authentication. +* Fixed: Better error display in browser plugin. +* Added: Support for {DAV:}supported-report-set +* Added: XML utility class with helper functions for the WebDAV protocol. +* Added: Tons of unittests +* Added: PrincipalCollection and Principal classes +* Added: Sabre_DAV_Server::getProperties for easy property retrieval +* Changed: {DAV:}resourceType defaults to 0 +* Changed: Any non-null resourceType now gets a / appended to the href value. + Before this was just for {DAV:}collection's, but this is now also the case for + for example {DAV:}principal. +* Changed: The Href property class can now optionally create non-relative uri's. +* Changed: Sabre_HTTP_Response now returns false if headers are already sent and + header-methods are called. +* Fixed: Issue 19: HEAD requests on Collections +* Fixed: Issue 21: Typo in Sabre_DAV_Property_Response +* Fixed: Issue 18: Doesn't work with Evolution Contacts + + +1.0.15 (2010-05-28) +------------------- + +* Added: Issue 31: Hiding exception information by default. Can be turned on + with the Sabre_DAV_Server::$debugExceptions property. +* Added: Moved autoload from lib/ to lib/Sabre/autoload.php. This is also the + case in the upcoming 1.2.0, so it will improve future compatibility. + + +1.0.14 (2010-04-15) +------------------- + +* Fixed: double namespace declaration in multistatus responses. + + +1.0.13 (2010-03-30) +------------------- + +* Fixed: Issue 40: Last references to basename/dirname + + +1.0.12 (2010-03-30) +------------------- + +* Fixed: Issue 37: Incorrect smultron regex in temporary filefilter. +* Fixed: Issue 26: Workaround for broken GVFS behaviour with encoded special + characters. +* Fixed: Issue 33: Converting ISO-8859-1 characters to UTF-8. +* Fixed: Issue 39: Basename fails on non-utf-8 locales. +* Added: More unittests. +* Added: SabreDAV version to all error responses. +* Added: URLUtil class for decoding urls. +* Updated: Now using pear.sabredav.org pear channel. + + +1.0.11 (2010-03-23) +------------------- + +* Non-public release. This release is identical to 1.0.10, but it is used to + test releasing packages to pear.sabredav.org. + + +1.0.10 (2010-03-22) +------------------- + +* Fixed: Issue 34: Invalid Lock-Token header response. +* Added: Issue 35: Addign SabreDAV version to HTTP OPTIONS responses. + + +1.0.9 (2010-03-19) +------------------ + +* Fixed: Issue 27: Entities not being encoded in PROPFIND responses. +* Fixed: Issue 29: Added missing TIMEOUT_INFINITE constant. + + +1.0.8 (2010-03-03) +------------------ + +* Fixed: Issue 21: typos causing errors +* Fixed: Issue 23: Comma's between methods in Allow header. +* Added: Sabre_DAV_ICollection interface, to aid in future compatibility. +* Added: Sabre_DAV_Exception_Forbidden exception. This will replace + Sabre_DAV_Exception_PermissionDenied in the future, and can already be used to + ensure future compatibility. + + +1.0.7 (2010-02-24) +------------------ + +* Fixed: Issue 19 regression for MS Office + + +1.0.6 (2010-02-23) +------------------ + +* Fixed: Issue 19: HEAD requests on Collections + + +1.0.5 (2010-01-22) +------------------ + +* Fixed: Fatal error when a malformed url was used for unlocking, in conjuction + with Sabre.autoload.php due to a incorrect filename. +* Fixed: Improved unittests and build system + + +1.0.4 (2010-01-11) +------------------ + +* Fixed: needed 2 different releases. One for googlecode and one for pearfarm. + This is to retain the old method to install SabreDAV until pearfarm becomes + the standard installation method. + + +1.0.3 (2010-01-11) +------------------ + +* Added: RFC4709 support (davmount) +* Added: 6 unittests +* Added: naturalselection. A tool to keep cache directories below a specified + theshold. +* Changed: Now using pearfarm.org channel server. + + +1.0.1 (2009-12-22) +------------------ + +* Fixed: Issue 15: typos in examples +* Fixed: Minor pear installation issues + + +1.0.0 (2009-11-02) +------------------ + +* Added: SimpleDirectory class. This class allows creating static directory + structures with ease. +* Changed: Custom complex properties and exceptions now get an instance of + Sabre_DAV_Server as their first argument in serialize() +* Changed: Href complex property now prepends server's baseUri +* Changed: delete before an overwriting copy/move is now handles by server class + instead of tree classes +* Changed: events must now explicitly return false to stop execution. Before, + execution would be stopped by anything loosely evaluating to false. +* Changed: the getPropertiesForPath method now takes a different set of + arguments, and returns a different response. This allows plugin developers to + return statuses for properties other than 200 and 404. The hrefs are now also + always calculated relative to the baseUri, and not the uri of the request. +* Changed: generatePropFindResponse is renamed to generateMultiStatus, and now + takes a list of properties similar to the response of getPropertiesForPath. + This was also needed to improve flexibility for plugin development. +* Changed: Auth plugins are no longer included. They were not yet stable + quality, so they will probably be reintroduced in a later version. +* Changed: PROPPATCH also used generateMultiStatus now. +* Removed: unknownProperties event. This is replaced by the afterGetProperties + event, which should provide more flexibility. +* Fixed: Only calling getSize() on IFile instances in httpHead() +* Added: beforeBind event. This is invoked upon file or directory creation +* Added: beforeWriteContent event, this is invoked by PUT and LOCK on an + existing resource. +* Added: beforeUnbind event. This is invoked right before deletion of any + resource. +* Added: afterGetProperties event. This event can be used to make modifications + to property responses. +* Added: beforeLock and beforeUnlock events. +* Added: afterBind event. +* Fixed: Copy and Move could fail in the root directory. This is now fixed. +* Added: Plugins can now be retrieved by their classname. This is useful for + inter-plugin communication. +* Added: The Auth backend can now return usernames and user-id's. +* Added: The Auth backend got a getUsers method +* Added: Sabre_DAV_FSExt_Directory now returns quota info + + +0.12.1-beta (2009-09-11) +------------------------ + +* Fixed: UNLOCK bug. Unlock didn't work at all + + +0.12-beta (2009-09-10) +---------------------- + +* Updated: Browser plugin now shows multiple {DAV:}resourcetype values if + available. +* Added: Experimental PDO backend for Locks Manager +* Fixed: Sending Content-Length: 0 for every empty response. This improves NGinx + compatibility. +* Fixed: Last modification time is reported in UTC timezone. This improves + Finder compatibility. + + +0.11-beta (2009-08-11) +---------------------- + +* Updated: Now in Beta +* Updated: Pear package no longer includes docs/ directory. These just contained + rfc's, which are publically available. This reduces the package from ~800k to + ~60k +* Added: generatePropfindResponse now takes a baseUri argument +* Added: ResourceType property can now contain multiple resourcetypes. +* Fixed: Issue 13. + + +0.10-alpha (2009-08-03) +----------------------- + +* Added: Plugin to automatically map GET requests to non-files to PROPFIND + (Sabre_DAV_Browser_MapGetToPropFind). This should allow easier debugging of + complicated WebDAV setups. +* Added: Sabre_DAV_Property_Href class. For future use. +* Added: Ability to choose to use auth-int, auth or both for HTTP Digest + authentication. (Issue 11) +* Changed: Made more methods in Sabre_DAV_Server public. +* Fixed: TemporaryFileFilter plugin now intercepts HTTP LOCK requests to + non-existent files. (Issue 12) +* Added: Central list of defined xml namespace prefixes. This can reduce + Bandwidth and legibility for xml bodies with user-defined namespaces. +* Added: now a PEAR-compatible package again, thanks to Michael Gauthier +* Changed: moved default copy and move logic from ObjectTree to Tree class + +0.9a-alpha (2009-07-21) +---------------------- + +* Fixed: Broken release + +0.9-alpha (2009-07-21) +---------------------- + +* Changed: Major refactoring, removed most of the logic from the Tree objects. + The Server class now directly works with the INode, IFile and IDirectory + objects. If you created your own Tree objects, this will most likely break in + this release. +* Changed: Moved all the Locking logic from the Tree and Server classes into a + separate plugin. +* Changed: TemporaryFileFilter is now a plugin. +* Added: Comes with an autoloader script. This can be used instead of the + includer script, and is preferred by some people. +* Added: AWS Authentication class. +* Added: simpleserversetup.py script. This will quickly get a fileserver up and + running. +* Added: When subscribing to events, it is now possible to supply a priority. + This is for example needed to ensure that the Authentication Plugin is used + before any other Plugin. +* Added: 22 new tests. +* Added: Users-manager plugin for .htdigest files. Experimental and subject to + change. +* Added: RFC 2324 HTTP 418 status code +* Fixed: Exclusive locks could in some cases be picked up as shared locks +* Fixed: Digest auth for non-apache servers had a bug (still not actually tested + this well). + + +0.8-alpha (2009-05-30) +---------------------- + +* Changed: Renamed all exceptions! This is a compatibility break. Every + Exception now follows Sabre_DAV_Exception_FileNotFound convention instead of + Sabre_DAV_FileNotFoundException. +* Added: Browser plugin now allows uploading and creating directories straight + from the browser. +* Added: 12 more unittests +* Fixed: Locking bug, which became prevalent on Windows Vista. +* Fixed: Netdrive support +* Fixed: TemporaryFileFilter filtered out too many files. Fixed some of the + regexes. +* Fixed: Added README and ChangeLog to package + + +0.7-alpha (2009-03-29) +---------------------- + +* Added: System to return complex properties from PROPFIND. +* Added: support for {DAV:}supportedlock. +* Added: support for {DAV:}lockdiscovery. +* Added: 6 new tests. +* Added: New plugin system. +* Added: Simple HTML directory plugin, for browser access. +* Added: Server class now sends back standard pre-condition error xml bodies. + This was new since RFC4918. +* Added: Sabre_DAV_Tree_Aggregrate, which can 'host' multiple Tree objects into + one. +* Added: simple basis for HTTP REPORT method. This method is not used yet, but + can be used by plugins to add reports. +* Changed: ->getSize is only called for files, no longer for collections. r303 +* Changed: Sabre_DAV_FilterTree is now Sabre_DAV_Tree_Filter +* Changed: Sabre_DAV_TemporaryFileFilter is now called + Sabre_DAV_Tree_TemporaryFileFilter. +* Changed: removed functions (get(/set)HTTPRequest(/Response)) from Server + class, and using a public property instead. +* Fixed: bug related to parsing proppatch and propfind requests. Didn't show up + in most clients, but it needed fixing regardless. (r255) +* Fixed: auth-int is now properly supported within HTTP Digest. +* Fixed: Using application/xml for a mimetype vs. text/xml as per RFC4918 sec + 8.2. +* Fixed: TemporaryFileFilter now lets through GET's if they actually exist on + the backend. (r274) +* FIxed: Some methods didn't get passed through in the FilterTree (r283). +* Fixed: LockManager is now slightly more complex, Tree classes slightly less. + (r287) + + +0.6-alpha (2009-02-16) +---------------------- + +* Added: Now uses streams for files, instead of strings. This means it won't + require to hold entire files in memory, which can be an issue if you're + dealing with big files. Note that this breaks compatibility for put() and + createFile methods. +* Added: HTTP Digest Authentication helper class. +* Added: Support for HTTP Range header +* Added: Support for ETags within If: headers +* Added: The API can now return ETags and override the default Content-Type +* Added: starting with basic framework for unittesting, using PHPUnit. +* Added: 49 unittests. +* Added: Abstraction for the HTTP request. +* Updated: Using Clark Notation for tags in properties. This means tags are + serialized as {namespace}tagName instead of namespace#tagName +* Fixed: HTTP_BasicAuth class now works as expected. +* Fixed: DAV_Server uses / for a default baseUrl. +* Fixed: Last modification date is no longer ignored in PROPFIND. +* Fixed: PROPFIND now sends back information about the requestUri even when + "Depth: 1" is specified. + + +0.5-alpha (2009-01-14) +---------------------- + +* Added: Added a very simple example for implementing a mapping to PHP file + streams. This should allow easy implementation of for example a WebDAV to FTP + proxy. +* Added: HTTP Basic Authentication helper class. +* Added: Sabre_HTTP_Response class. This centralizes HTTP operations and will be + a start towards the creating of a testing framework. +* Updated: Backwards compatibility break: all require_once() statements are + removed from all the files. It is now recommended to use autoloading of + classes, or just including lib/Sabre.includes.php. This fix was made to allow + easier integration into applications not using this standard inclusion model. +* Updated: Better in-file documentation. +* Updated: Sabre_DAV_Tree can now work with Sabre_DAV_LockManager. +* Updated: Fixes a shared-lock bug. +* Updated: Removed ?> from the bottom of each php file. +* Updated: Split up some operations from Sabre_DAV_Server to + Sabre_HTTP_Response. +* Fixed: examples are now actually included in the pear package. + + +0.4-alpha (2008-11-05) +---------------------- + +* Passes all litmus tests! +* Added: more examples +* Added: Custom property support +* Added: Shared lock support +* Added: Depth support to locks +* Added: Locking on unmapped urls (non-existent nodes) +* Fixed: Advertising as WebDAV class 3 support + + +0.3-alpha (2008-06-29) +---------------------- + +* Fully working in MS Windows clients. +* Added: temporary file filter: support for smultron files. +* Added: Phing build scripts +* Added: PEAR package +* Fixed: MOVE bug identified using finder. +* Fixed: Using gzuncompress instead of gzdecode in the temporary file filter. + This seems more common. + + +0.2-alpha (2008-05-27) +---------------------- + +* Somewhat working in Windows clients +* Added: Working PROPPATCH method (doesn't support custom properties yet) +* Added: Temporary filename handling system +* Added: Sabre_DAV_IQuota to return quota information +* Added: PROPFIND now reads the request body and only supplies the requested + properties + + +0.1-alpha (2008-04-04) +---------------------- + +* First release! +* Passes litmus: basic, http and copymove test. +* Fully working in Finder and DavFSv2 Project started: 2007-12-13 + + +[vobj]: http://sabre.io/vobject/ +[evnt]: http://sabre.io/event/ +[http]: http://sabre.io/http/ +[uri]: http://sabre.io/uri/ +[xml]: http://sabre.io/xml/ +[mi20]: http://sabre.io/dav/upgrade/1.8-to-2.0/ +[rfc6638]: http://tools.ietf.org/html/rfc6638 "CalDAV Scheduling" +[rfc7240]: http://tools.ietf.org/html/rfc7240 diff --git a/src/serverlib/3rdparty/SabreDAV/LICENSE b/src/serverlib/3rdparty/SabreDAV/LICENSE new file mode 100644 index 0000000..fd3539e --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/LICENSE @@ -0,0 +1,27 @@ +Copyright (C) 2007-2016 fruux GmbH (https://fruux.com/). + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of SabreDAV nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. diff --git a/src/serverlib/3rdparty/SabreDAV/README.md b/src/serverlib/3rdparty/SabreDAV/README.md new file mode 100644 index 0000000..024a80e --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/README.md @@ -0,0 +1,30 @@ +![sabre's logo](http://sabre.io/img/logo.png) SabreDAV +====================================================== + +Introduction +------------ + +SabreDAV is the most popular WebDAV framework for PHP. Use it to create WebDAV, CalDAV and CardDAV servers. + +Full documentation can be found on the website: + +http://sabre.io/ + +Build status +------------ + +| branch | status | +| ------------ | ------ | +| master | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=master)](https://travis-ci.org/fruux/sabre-dav) | +| 3.0 | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=3.0)](https://travis-ci.org/fruux/sabre-dav) | +| 2.1 | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=2.1)](https://travis-ci.org/fruux/sabre-dav) | +| 2.0 | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=2.0)](https://travis-ci.org/fruux/sabre-dav) | +| 1.8 | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=1.8)](https://travis-ci.org/fruux/sabre-dav) | +| 1.7 | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=1.7)](https://travis-ci.org/fruux/sabre-dav) | +| 1.6 | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=1.6)](https://travis-ci.org/fruux/sabre-dav) | +| xml-rewrite2 | [![Build Status](https://travis-ci.org/fruux/sabre-dav.svg?branch=xml-rewrite2)](https://travis-ci.org/fruux/sabre-dav) | + +Made at fruux +------------- + +SabreDAV is being developed by [fruux](https://fruux.com/). Drop us a line for commercial services or enterprise support. diff --git a/src/serverlib/3rdparty/SabreDAV/bin/generate_vcards b/src/serverlib/3rdparty/SabreDAV/bin/generate_vcards new file mode 100755 index 0000000..638f63b --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/bin/generate_vcards @@ -0,0 +1,241 @@ +#!/usr/bin/env php + testdata.vcf + +HI; + + fwrite(STDERR, $help); + exit(2); +} + +$count = (int)$argv[1]; +if ($count < 1) { + fwrite(STDERR, "Count must be at least 1\n"); + exit(2); +} + +fwrite(STDERR, "sabre/vobject " . Version::VERSION . "\n"); +fwrite(STDERR, "Generating " . $count . " vcards in vCard 4.0 format\n"); + +/** + * The following list is just some random data we compiled from various + * sources online. + * + * Very little thought went into compiling this list, and certainly nothing + * political or ethical. + * + * We would _love_ more additions to this to add more variation to this list. + * + * Send us PR's and don't be shy adding your own first and last name for fun. + */ + +$sets = array( + "nl" => array( + "country" => "Netherlands", + "boys" => array( + "Anno", + "Bram", + "Daan", + "Evert", + "Finn", + "Jayden", + "Jens", + "Jesse", + "Levi", + "Lucas", + "Luuk", + "Milan", + "René", + "Sem", + "Sibrand", + "Willem", + ), + "girls" => array( + "Celia", + "Emma", + "Fenna", + "Geke", + "Inge", + "Julia", + "Lisa", + "Lotte", + "Mila", + "Sara", + "Sophie", + "Tess", + "Zoë", + ), + "last" => array( + "Bakker", + "Bos", + "De Boer", + "De Groot", + "De Jong", + "De Vries", + "Jansen", + "Janssen", + "Meyer", + "Mulder", + "Peters", + "Smit", + "Van Dijk", + "Van den Berg", + "Visser", + "Vos", + ), + ), + "us" => array( + "country" => "United States", + "boys" => array( + "Aiden", + "Alexander", + "Charles", + "David", + "Ethan", + "Jacob", + "James", + "Jayden", + "John", + "Joseph", + "Liam", + "Mason", + "Michael", + "Noah", + "Richard", + "Robert", + "Thomas", + "William", + ), + "girls" => array( + "Ava", + "Barbara", + "Chloe", + "Dorothy", + "Elizabeth", + "Emily", + "Emma", + "Isabella", + "Jennifer", + "Lily", + "Linda", + "Margaret", + "Maria", + "Mary", + "Mia", + "Olivia", + "Patricia", + "Roxy", + "Sophia", + "Susan", + "Zoe", + ), + "last" => array( + "Smith", + "Johnson", + "Williams", + "Jones", + "Brown", + "Davis", + "Miller", + "Wilson", + "Moore", + "Taylor", + "Anderson", + "Thomas", + "Jackson", + "White", + "Harris", + "Martin", + "Thompson", + "Garcia", + "Martinez", + "Robinson", + ), + ), +); + +$current = 0; + +$r = function($arr) { + + return $arr[mt_rand(0,count($arr)-1)]; + +}; + +$bdayStart = strtotime('-85 years'); +$bdayEnd = strtotime('-20 years'); + +while($current < $count) { + + $current++; + fwrite(STDERR, "\033[100D$current/$count"); + + $country = array_rand($sets); + $gender = mt_rand(0,1)?'girls':'boys'; + + $vcard = new Component\VCard(array( + 'VERSION' => '4.0', + 'FN' => $r($sets[$country][$gender]) . ' ' . $r($sets[$country]['last']), + 'UID' => UUIDUtil::getUUID(), + )); + + $bdayRatio = mt_rand(0,9); + + if($bdayRatio < 2) { + // 20% has a birthday property with a full date + $dt = new \DateTime('@' . mt_rand($bdayStart, $bdayEnd)); + $vcard->add('BDAY', $dt->format('Ymd')); + + } elseif ($bdayRatio < 3) { + // 10% we only know the month and date of + $dt = new \DateTime('@' . mt_rand($bdayStart, $bdayEnd)); + $vcard->add('BDAY', '--' . $dt->format('md')); + } + if ($result = $vcard->validate()) { + ob_start(); + echo "\nWe produced an invalid vcard somehow!\n"; + foreach($result as $message) { + echo " " . $message['message'] . "\n"; + } + fwrite(STDERR, ob_get_clean()); + } + echo $vcard->serialize(); + +} + +fwrite(STDERR,"\nDone.\n"); diff --git a/src/serverlib/3rdparty/SabreDAV/bin/naturalselection b/src/serverlib/3rdparty/SabreDAV/bin/naturalselection new file mode 100755 index 0000000..52720e3 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/bin/naturalselection @@ -0,0 +1,140 @@ +#!/usr/bin/env python + +# +# Copyright (c) 2009-2010 Evert Pot +# All rights reserved. +# http://www.rooftopsolutions.nl/ +# +# This utility is distributed along with SabreDAV +# license: http://sabre.io/license/ Modified BSD License + +import os +from optparse import OptionParser +import time + +def getfreespace(path): + stat = os.statvfs(path) + return stat.f_frsize * stat.f_bavail + +def getbytesleft(path,threshold): + return getfreespace(path)-threshold + +def run(cacheDir, threshold, sleep=5, simulate=False, min_erase = 0): + + bytes = getbytesleft(cacheDir,threshold) + if (bytes>0): + print "Bytes to go before we hit threshold:", bytes + else: + print "Threshold exceeded with:", -bytes, "bytes" + dir = os.listdir(cacheDir) + dir2 = [] + for file in dir: + path = cacheDir + '/' + file + dir2.append({ + "path" : path, + "atime": os.stat(path).st_atime, + "size" : os.stat(path).st_size + }) + + dir2.sort(lambda x,y: int(x["atime"]-y["atime"])) + + filesunlinked = 0 + gainedspace = 0 + + # Left is the amount of bytes that need to be freed up + # The default is the 'min_erase setting' + left = min_erase + + # If the min_erase setting is lower than the amount of bytes over + # the threshold, we use that number instead. + if left < -bytes : + left = -bytes + + print "Need to delete at least:", left; + + for file in dir2: + + # Only deleting files if we're not simulating + if not simulate: os.unlink(file["path"]) + left = int(left - file["size"]) + gainedspace = gainedspace + file["size"] + filesunlinked = filesunlinked + 1 + + if(left<0): + break + + print "%d files deleted (%d bytes)" % (filesunlinked, gainedspace) + + + time.sleep(sleep) + + + +def main(): + parser = OptionParser( + version="naturalselection v0.3", + description="Cache directory manager. Deletes cache entries based on accesstime and free space thresholds.\n" + + "This utility is distributed alongside SabreDAV.", + usage="usage: %prog [options] cacheDirectory", + ) + parser.add_option( + '-s', + dest="simulate", + action="store_true", + help="Don't actually make changes, but just simulate the behaviour", + ) + parser.add_option( + '-r','--runs', + help="How many times to check before exiting. -1 is infinite, which is the default", + type="int", + dest="runs", + default=-1 + ) + parser.add_option( + '-n','--interval', + help="Sleep time in seconds (default = 5)", + type="int", + dest="sleep", + default=5 + ) + parser.add_option( + '-l','--threshold', + help="Threshold in bytes (default = 10737418240, which is 10GB)", + type="int", + dest="threshold", + default=10737418240 + ) + parser.add_option( + '-m', '--min-erase', + help="Minimum number of bytes to erase when the threshold is reached. " + + "Setting this option higher will reduce the amount of times the cache directory will need to be scanned. " + + "(the default is 1073741824, which is 1GB.)", + type="int", + dest="min_erase", + default=1073741824 + ) + + options,args = parser.parse_args() + if len(args)<1: + parser.error("This utility requires at least 1 argument") + cacheDir = args[0] + + print "Natural Selection" + print "Cache directory:", cacheDir + free = getfreespace(cacheDir); + print "Current free disk space:", free + + runs = options.runs; + while runs!=0 : + run( + cacheDir, + sleep=options.sleep, + simulate=options.simulate, + threshold=options.threshold, + min_erase=options.min_erase + ) + if runs>0: + runs = runs - 1 + +if __name__ == '__main__' : + main() diff --git a/src/serverlib/3rdparty/SabreDAV/bin/sabredav b/src/serverlib/3rdparty/SabreDAV/bin/sabredav new file mode 100755 index 0000000..032371b --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/bin/sabredav @@ -0,0 +1,2 @@ +#!/bin/sh +php -S 0.0.0.0:8080 `dirname $0`/sabredav.php diff --git a/src/serverlib/3rdparty/SabreDAV/bin/vobject b/src/serverlib/3rdparty/SabreDAV/bin/vobject new file mode 100755 index 0000000..e52b4fb --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/bin/vobject @@ -0,0 +1,27 @@ +#!/usr/bin/env php +main($argv)); + diff --git a/src/serverlib/3rdparty/SabreDAV/examples/addressbookserver.php b/src/serverlib/3rdparty/SabreDAV/examples/addressbookserver.php new file mode 100644 index 0000000..6d1c9b2 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/examples/addressbookserver.php @@ -0,0 +1,57 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + +//Mapping PHP errors to exceptions +function exception_error_handler($errno, $errstr, $errfile, $errline) { + throw new ErrorException($errstr, 0, $errno, $errfile, $errline); +} +set_error_handler("exception_error_handler"); + +// Autoloader +require_once 'vendor/autoload.php'; + +// Backends +$authBackend = new Sabre\DAV\Auth\Backend\PDO($pdo); +$principalBackend = new Sabre\DAVACL\PrincipalBackend\PDO($pdo); +$carddavBackend = new Sabre\CardDAV\Backend\PDO($pdo); +//$caldavBackend = new Sabre\CalDAV\Backend\PDO($pdo); + +// Setting up the directory tree // +$nodes = [ + new Sabre\DAVACL\PrincipalCollection($principalBackend), +// new Sabre\CalDAV\CalendarRoot($authBackend, $caldavBackend), + new Sabre\CardDAV\AddressBookRoot($principalBackend, $carddavBackend), +]; + +// The object tree needs in turn to be passed to the server class +$server = new Sabre\DAV\Server($nodes); +$server->setBaseUri($baseUri); + +// Plugins +$server->addPlugin(new Sabre\DAV\Auth\Plugin($authBackend)); +$server->addPlugin(new Sabre\DAV\Browser\Plugin()); +//$server->addPlugin(new Sabre\CalDAV\Plugin()); +$server->addPlugin(new Sabre\CardDAV\Plugin()); +$server->addPlugin(new Sabre\DAVACL\Plugin()); +$server->addPlugin(new Sabre\DAV\Sync\Plugin()); + +// And off we go! +$server->exec(); diff --git a/src/serverlib/3rdparty/SabreDAV/examples/calendarserver.php b/src/serverlib/3rdparty/SabreDAV/examples/calendarserver.php new file mode 100644 index 0000000..e5f9f33 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/examples/calendarserver.php @@ -0,0 +1,76 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + +//Mapping PHP errors to exceptions +function exception_error_handler($errno, $errstr, $errfile, $errline) { + throw new ErrorException($errstr, 0, $errno, $errfile, $errline); +} +set_error_handler("exception_error_handler"); + +// Files we need +require_once 'vendor/autoload.php'; + +// Backends +$authBackend = new Sabre\DAV\Auth\Backend\PDO($pdo); +$calendarBackend = new Sabre\CalDAV\Backend\PDO($pdo); +$principalBackend = new Sabre\DAVACL\PrincipalBackend\PDO($pdo); + +// Directory structure +$tree = [ + new Sabre\CalDAV\Principal\Collection($principalBackend), + new Sabre\CalDAV\CalendarRoot($principalBackend, $calendarBackend), +]; + +$server = new Sabre\DAV\Server($tree); + +if (isset($baseUri)) + $server->setBaseUri($baseUri); + +/* Server Plugins */ +$authPlugin = new Sabre\DAV\Auth\Plugin($authBackend); +$server->addPlugin($authPlugin); + +$aclPlugin = new Sabre\DAVACL\Plugin(); +$server->addPlugin($aclPlugin); + +/* CalDAV support */ +$caldavPlugin = new Sabre\CalDAV\Plugin(); +$server->addPlugin($caldavPlugin); + +/* Calendar subscription support */ +$server->addPlugin( + new Sabre\CalDAV\Subscriptions\Plugin() +); + +/* Calendar scheduling support */ +$server->addPlugin( + new Sabre\CalDAV\Schedule\Plugin() +); + +/* WebDAV-Sync plugin */ +$server->addPlugin(new Sabre\DAV\Sync\Plugin()); + +// Support for html frontend +$browser = new Sabre\DAV\Browser\Plugin(); +$server->addPlugin($browser); + +// And off we go! +$server->exec(); diff --git a/src/serverlib/3rdparty/SabreDAV/examples/fileserver.php b/src/serverlib/3rdparty/SabreDAV/examples/fileserver.php new file mode 100644 index 0000000..59a453f --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/examples/fileserver.php @@ -0,0 +1,56 @@ +setBaseUri($baseUri); + +// Support for LOCK and UNLOCK +$lockBackend = new \Sabre\DAV\Locks\Backend\File($tmpDir . '/locksdb'); +$lockPlugin = new \Sabre\DAV\Locks\Plugin($lockBackend); +$server->addPlugin($lockPlugin); + +// Support for html frontend +$browser = new \Sabre\DAV\Browser\Plugin(); +$server->addPlugin($browser); + +// Automatically guess (some) contenttypes, based on extesion +$server->addPlugin(new \Sabre\DAV\Browser\GuessContentType()); + +// Authentication backend +$authBackend = new \Sabre\DAV\Auth\Backend\File('.htdigest'); +$auth = new \Sabre\DAV\Auth\Plugin($authBackend); +$server->addPlugin($auth); + +// Temporary file filter +$tempFF = new \Sabre\DAV\TemporaryFileFilterPlugin($tmpDir); +$server->addPlugin($tempFF); + +// And off we go! +$server->exec(); diff --git a/src/serverlib/3rdparty/SabreDAV/examples/groupwareserver.php b/src/serverlib/3rdparty/SabreDAV/examples/groupwareserver.php new file mode 100644 index 0000000..fa6feb0 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/examples/groupwareserver.php @@ -0,0 +1,92 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + +/** + * Mapping PHP errors to exceptions. + * + * While this is not strictly needed, it makes a lot of sense to do so. If an + * E_NOTICE or anything appears in your code, this allows SabreDAV to intercept + * the issue and send a proper response back to the client (HTTP/1.1 500). + */ +function exception_error_handler($errno, $errstr, $errfile, $errline) { + throw new ErrorException($errstr, 0, $errno, $errfile, $errline); +} +set_error_handler("exception_error_handler"); + +// Autoloader +require_once 'vendor/autoload.php'; + +/** + * The backends. Yes we do really need all of them. + * + * This allows any developer to subclass just any of them and hook into their + * own backend systems. + */ +$authBackend = new \Sabre\DAV\Auth\Backend\PDO($pdo); +$principalBackend = new \Sabre\DAVACL\PrincipalBackend\PDO($pdo); +$carddavBackend = new \Sabre\CardDAV\Backend\PDO($pdo); +$caldavBackend = new \Sabre\CalDAV\Backend\PDO($pdo); + +/** + * The directory tree + * + * Basically this is an array which contains the 'top-level' directories in the + * WebDAV server. + */ +$nodes = [ + // /principals + new \Sabre\CalDAV\Principal\Collection($principalBackend), + // /calendars + new \Sabre\CalDAV\CalendarRoot($principalBackend, $caldavBackend), + // /addressbook + new \Sabre\CardDAV\AddressBookRoot($principalBackend, $carddavBackend), +]; + +// The object tree needs in turn to be passed to the server class +$server = new \Sabre\DAV\Server($nodes); +if (isset($baseUri)) $server->setBaseUri($baseUri); + +// Plugins +$server->addPlugin(new \Sabre\DAV\Auth\Plugin($authBackend)); +$server->addPlugin(new \Sabre\DAV\Browser\Plugin()); +$server->addPlugin(new \Sabre\CalDAV\Plugin()); +$server->addPlugin(new \Sabre\CardDAV\Plugin()); +$server->addPlugin(new \Sabre\DAVACL\Plugin()); +$server->addPlugin(new \Sabre\DAV\Sync\Plugin()); + +// And off we go! +$server->exec(); diff --git a/src/serverlib/3rdparty/SabreDAV/examples/simplefsserver.php b/src/serverlib/3rdparty/SabreDAV/examples/simplefsserver.php new file mode 100644 index 0000000..ad0679b --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/examples/simplefsserver.php @@ -0,0 +1,123 @@ +myPath = $myPath; + + } + + function getChildren() { + + $children = []; + // Loop through the directory, and create objects for each node + foreach (scandir($this->myPath) as $node) { + + // Ignoring files staring with . + if ($node[0] === '.') continue; + + $children[] = $this->getChild($node); + + } + + return $children; + + } + + function getChild($name) { + + $path = $this->myPath . '/' . $name; + + // We have to throw a NotFound exception if the file didn't exist + if (!file_exists($this->myPath)) throw new \Sabre\DAV\Exception\NotFound('The file with name: ' . $name . ' could not be found'); + // Some added security + + if ($name[0] == '.') throw new \Sabre\DAV\Exception\Forbidden('Access denied'); + + if (is_dir($path)) { + + return new \MyCollection($name); + + } else { + + return new \MyFile($path); + + } + + } + + function getName() { + + return basename($this->myPath); + + } + +} + +class MyFile extends \Sabre\DAV\File { + + private $myPath; + + function __construct($myPath) { + + $this->myPath = $myPath; + + } + + function getName() { + + return basename($this->myPath); + + } + + function get() { + + return fopen($this->myPath, 'r'); + + } + + function getSize() { + + return filesize($this->myPath); + + } + +} + +// Make sure there is a directory in your current directory named 'public'. We will be exposing that directory to WebDAV +$rootNode = new \MyCollection($publicDir); + +// The rootNode needs to be passed to the server object. +$server = new \Sabre\DAV\Server($rootNode); + +// And off we go! +$server->exec(); diff --git a/src/serverlib/3rdparty/SabreDAV/examples/sql/mysql.addressbook.sql b/src/serverlib/3rdparty/SabreDAV/examples/sql/mysql.addressbook.sql new file mode 100644 index 0000000..9ec88ba --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/examples/sql/mysql.addressbook.sql @@ -0,0 +1,28 @@ +CREATE TABLE addressbooks ( + id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + principaluri VARBINARY(255), + displayname VARCHAR(255), + uri VARBINARY(200), + description TEXT, + synctoken INT(11) UNSIGNED NOT NULL DEFAULT '1', + UNIQUE(principaluri(100), uri(100)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE cards ( + id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + addressbookid INT(11) UNSIGNED NOT NULL, + carddata MEDIUMBLOB, + uri VARBINARY(200), + lastmodified INT(11) UNSIGNED, + etag VARBINARY(32), + size INT(11) UNSIGNED NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE addressbookchanges ( + id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + uri VARBINARY(200) NOT NULL, + synctoken INT(11) UNSIGNED NOT NULL, + addressbookid INT(11) UNSIGNED NOT NULL, + operation TINYINT(1) NOT NULL, + INDEX addressbookid_synctoken (addressbookid, synctoken) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/src/serverlib/3rdparty/SabreDAV/examples/sql/mysql.calendars.sql b/src/serverlib/3rdparty/SabreDAV/examples/sql/mysql.calendars.sql new file mode 100644 index 0000000..d41f110 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/examples/sql/mysql.calendars.sql @@ -0,0 +1,64 @@ +CREATE TABLE calendarobjects ( + id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + calendardata MEDIUMBLOB, + uri VARBINARY(200), + calendarid INTEGER UNSIGNED NOT NULL, + lastmodified INT(11) UNSIGNED, + etag VARBINARY(32), + size INT(11) UNSIGNED NOT NULL, + componenttype VARBINARY(8), + firstoccurence INT(11) UNSIGNED, + lastoccurence INT(11) UNSIGNED, + uid VARBINARY(200), + UNIQUE(calendarid, uri) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE calendars ( + id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + principaluri VARBINARY(100), + displayname VARCHAR(100), + uri VARBINARY(200), + synctoken INTEGER UNSIGNED NOT NULL DEFAULT '1', + description TEXT, + calendarorder INT(11) UNSIGNED NOT NULL DEFAULT '0', + calendarcolor VARBINARY(10), + timezone TEXT, + components VARBINARY(21), + transparent TINYINT(1) NOT NULL DEFAULT '0', + UNIQUE(principaluri, uri) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE calendarchanges ( + id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + uri VARBINARY(200) NOT NULL, + synctoken INT(11) UNSIGNED NOT NULL, + calendarid INT(11) UNSIGNED NOT NULL, + operation TINYINT(1) NOT NULL, + INDEX calendarid_synctoken (calendarid, synctoken) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE calendarsubscriptions ( + id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + uri VARBINARY(200) NOT NULL, + principaluri VARBINARY(100) NOT NULL, + source TEXT, + displayname VARCHAR(100), + refreshrate VARCHAR(10), + calendarorder INT(11) UNSIGNED NOT NULL DEFAULT '0', + calendarcolor VARBINARY(10), + striptodos TINYINT(1) NULL, + stripalarms TINYINT(1) NULL, + stripattachments TINYINT(1) NULL, + lastmodified INT(11) UNSIGNED, + UNIQUE(principaluri, uri) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE schedulingobjects ( + id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + principaluri VARBINARY(255), + calendardata MEDIUMBLOB, + uri VARBINARY(200), + lastmodified INT(11) UNSIGNED, + etag VARBINARY(32), + size INT(11) UNSIGNED NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/src/serverlib/3rdparty/SabreDAV/examples/sql/mysql.locks.sql b/src/serverlib/3rdparty/SabreDAV/examples/sql/mysql.locks.sql new file mode 100644 index 0000000..96a3a88 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/examples/sql/mysql.locks.sql @@ -0,0 +1,12 @@ +CREATE TABLE locks ( + id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + owner VARCHAR(100), + timeout INTEGER UNSIGNED, + created INTEGER, + token VARBINARY(100), + scope TINYINT, + depth TINYINT, + uri VARBINARY(1000), + INDEX(token), + INDEX(uri(100)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/src/serverlib/3rdparty/SabreDAV/examples/sql/mysql.principals.sql b/src/serverlib/3rdparty/SabreDAV/examples/sql/mysql.principals.sql new file mode 100644 index 0000000..ea0d16a --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/examples/sql/mysql.principals.sql @@ -0,0 +1,20 @@ +CREATE TABLE principals ( + id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + uri VARBINARY(200) NOT NULL, + email VARBINARY(80), + displayname VARCHAR(80), + UNIQUE(uri) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE groupmembers ( + id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + principal_id INTEGER UNSIGNED NOT NULL, + member_id INTEGER UNSIGNED NOT NULL, + UNIQUE(principal_id, member_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +INSERT INTO principals (uri,email,displayname) VALUES +('principals/admin', 'admin@example.org','Administrator'), +('principals/admin/calendar-proxy-read', null, null), +('principals/admin/calendar-proxy-write', null, null); + diff --git a/src/serverlib/3rdparty/SabreDAV/examples/sql/mysql.propertystorage.sql b/src/serverlib/3rdparty/SabreDAV/examples/sql/mysql.propertystorage.sql new file mode 100644 index 0000000..1b5ca5a --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/examples/sql/mysql.propertystorage.sql @@ -0,0 +1,9 @@ +CREATE TABLE propertystorage ( + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + path VARBINARY(1024) NOT NULL, + name VARBINARY(100) NOT NULL, + valuetype INT UNSIGNED, + value MEDIUMBLOB +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE UNIQUE INDEX path_property ON propertystorage (path(600), name(100)); diff --git a/src/serverlib/3rdparty/SabreDAV/examples/sql/mysql.users.sql b/src/serverlib/3rdparty/SabreDAV/examples/sql/mysql.users.sql new file mode 100644 index 0000000..22ac312 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/examples/sql/mysql.users.sql @@ -0,0 +1,9 @@ +CREATE TABLE users ( + id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + username VARBINARY(50), + digesta1 VARBINARY(32), + UNIQUE(username) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +INSERT INTO users (username,digesta1) VALUES +('admin', '87fd274b7b6c01e48d7c2f965da8ddf7'); diff --git a/src/serverlib/3rdparty/SabreDAV/examples/sql/pgsql.addressbook.sql b/src/serverlib/3rdparty/SabreDAV/examples/sql/pgsql.addressbook.sql new file mode 100644 index 0000000..ef2cc5b --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/examples/sql/pgsql.addressbook.sql @@ -0,0 +1,52 @@ +CREATE TABLE addressbooks ( + id SERIAL NOT NULL, + principaluri VARCHAR(255), + displayname VARCHAR(255), + uri VARCHAR(200), + description TEXT, + synctoken INTEGER NOT NULL DEFAULT 1 +); + +ALTER TABLE ONLY addressbooks + ADD CONSTRAINT addressbooks_pkey PRIMARY KEY (id); + +CREATE UNIQUE INDEX addressbooks_ukey + ON addressbooks USING btree (principaluri, uri); + +CREATE TABLE cards ( + id SERIAL NOT NULL, + addressbookid INTEGER NOT NULL, + carddata TEXT, + uri VARCHAR(200), + lastmodified INTEGER, + etag VARCHAR(32), + size INTEGER NOT NULL +); + +ALTER TABLE ONLY cards + ADD CONSTRAINT cards_pkey PRIMARY KEY (id); + +CREATE UNIQUE INDEX cards_ukey + ON cards USING btree (addressbookid, uri); + +ALTER TABLE ONLY cards + ADD CONSTRAINT cards_addressbookid_fkey FOREIGN KEY (addressbookid) REFERENCES addressbooks(id) + ON DELETE CASCADE; + +CREATE TABLE addressbookchanges ( + id SERIAL NOT NULL, + uri VARCHAR(200) NOT NULL, + synctoken INTEGER NOT NULL, + addressbookid INTEGER NOT NULL, + operation SMALLINT NOT NULL +); + +ALTER TABLE ONLY addressbookchanges + ADD CONSTRAINT addressbookchanges_pkey PRIMARY KEY (id); + +CREATE INDEX addressbookchanges_addressbookid_synctoken_ix + ON addressbookchanges USING btree (addressbookid, synctoken); + +ALTER TABLE ONLY addressbookchanges + ADD CONSTRAINT addressbookchanges_addressbookid_fkey FOREIGN KEY (addressbookid) REFERENCES addressbooks(id) + ON DELETE CASCADE; diff --git a/src/serverlib/3rdparty/SabreDAV/examples/sql/pgsql.calendars.sql b/src/serverlib/3rdparty/SabreDAV/examples/sql/pgsql.calendars.sql new file mode 100644 index 0000000..d31084b --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/examples/sql/pgsql.calendars.sql @@ -0,0 +1,93 @@ +CREATE TABLE calendars ( + id SERIAL NOT NULL, + principaluri VARCHAR(100), + displayname VARCHAR(100), + uri VARCHAR(200), + synctoken INTEGER NOT NULL DEFAULT 1, + description TEXT, + calendarorder INTEGER NOT NULL DEFAULT 0, + calendarcolor VARCHAR(10), + timezone TEXT, + components VARCHAR(20), + uid VARCHAR(200), + transparent SMALLINT NOT NULL DEFAULT '0' +); + +ALTER TABLE ONLY calendars + ADD CONSTRAINT calendars_pkey PRIMARY KEY (id); + +CREATE UNIQUE INDEX calendars_ukey + ON calendars USING btree (principaluri, uri); + +CREATE TABLE calendarobjects ( + id SERIAL NOT NULL, + calendardata TEXT, + uri VARCHAR(200), + calendarid INTEGER NOT NULL, + lastmodified INTEGER, + etag VARCHAR(32), + size INTEGER NOT NULL, + componenttype VARCHAR(8), + firstoccurence INTEGER, + lastoccurence INTEGER, + uid VARCHAR(200) +); + +ALTER TABLE ONLY calendarobjects + ADD CONSTRAINT calendarobjects_pkey PRIMARY KEY (id); + +CREATE UNIQUE INDEX calendarobjects_ukey + ON calendarobjects USING btree (calendarid, uri); + +ALTER TABLE ONLY calendarobjects + ADD CONSTRAINT calendarobjects_calendarid_fkey FOREIGN KEY (calendarid) REFERENCES calendars(id) + ON DELETE CASCADE; + +CREATE TABLE calendarsubscriptions ( + id SERIAL NOT NULL, + uri VARCHAR(200) NOT NULL, + principaluri VARCHAR(100) NOT NULL, + source TEXT, + displayname VARCHAR(100), + refreshrate VARCHAR(10), + calendarorder INTEGER NOT NULL DEFAULT 0, + calendarcolor VARCHAR(10), + striptodos SMALLINT NULL, + stripalarms SMALLINT NULL, + stripattachments SMALLINT NULL, + lastmodified INTEGER +); + +ALTER TABLE ONLY calendarsubscriptions + ADD CONSTRAINT calendarsubscriptions_pkey PRIMARY KEY (id); + +CREATE UNIQUE INDEX calendarsubscriptions_ukey + ON calendarsubscriptions USING btree (principaluri, uri); + +CREATE TABLE calendarchanges ( + id SERIAL NOT NULL, + uri VARCHAR(200) NOT NULL, + synctoken INTEGER NOT NULL, + calendarid INTEGER NOT NULL, + operation SMALLINT NOT NULL DEFAULT 0 +); + +ALTER TABLE ONLY calendarchanges + ADD CONSTRAINT calendarchanges_pkey PRIMARY KEY (id); + +CREATE INDEX calendarchanges_calendarid_synctoken_ix + ON calendarchanges USING btree (calendarid, synctoken); + +ALTER TABLE ONLY calendarchanges + ADD CONSTRAINT calendarchanges_calendar_fk FOREIGN KEY (calendarid) REFERENCES calendars(id) + ON DELETE CASCADE; + +CREATE TABLE schedulingobjects ( + id SERIAL NOT NULL, + principaluri VARCHAR(255), + calendardata BYTEA, + uri VARCHAR(200), + lastmodified INTEGER, + etag VARCHAR(32), + size INTEGER NOT NULL +); diff --git a/src/serverlib/3rdparty/SabreDAV/examples/sql/pgsql.locks.sql b/src/serverlib/3rdparty/SabreDAV/examples/sql/pgsql.locks.sql new file mode 100644 index 0000000..0290528 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/examples/sql/pgsql.locks.sql @@ -0,0 +1,19 @@ +CREATE TABLE locks ( + id SERIAL NOT NULL, + owner VARCHAR(100), + timeout INTEGER, + created INTEGER, + token VARCHAR(100), + scope SMALLINT, + depth SMALLINT, + uri TEXT +); + +ALTER TABLE ONLY locks + ADD CONSTRAINT locks_pkey PRIMARY KEY (id); + +CREATE INDEX locks_token_ix + ON locks USING btree (token); + +CREATE INDEX locks_uri_ix + ON locks USING btree (uri); diff --git a/src/serverlib/3rdparty/SabreDAV/examples/sql/pgsql.principals.sql b/src/serverlib/3rdparty/SabreDAV/examples/sql/pgsql.principals.sql new file mode 100644 index 0000000..9157acd --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/examples/sql/pgsql.principals.sql @@ -0,0 +1,38 @@ +CREATE TABLE principals ( + id SERIAL NOT NULL, + uri VARCHAR(200) NOT NULL, + email VARCHAR(80), + displayname VARCHAR(80) +); + +ALTER TABLE ONLY principals + ADD CONSTRAINT principals_pkey PRIMARY KEY (id); + +CREATE UNIQUE INDEX principals_ukey + ON principals USING btree (uri); + +CREATE TABLE groupmembers ( + id SERIAL NOT NULL, + principal_id INTEGER NOT NULL, + member_id INTEGER NOT NULL +); + +ALTER TABLE ONLY groupmembers + ADD CONSTRAINT groupmembers_pkey PRIMARY KEY (id); + +CREATE UNIQUE INDEX groupmembers_ukey + ON groupmembers USING btree (principal_id, member_id); + +ALTER TABLE ONLY groupmembers + ADD CONSTRAINT groupmembers_principal_id_fkey FOREIGN KEY (principal_id) REFERENCES principals(id) + ON DELETE CASCADE; + +ALTER TABLE ONLY groupmembers + ADD CONSTRAINT groupmembers_member_id_id_fkey FOREIGN KEY (member_id) REFERENCES principals(id) + ON DELETE CASCADE; + +INSERT INTO principals (uri,email,displayname) VALUES +('principals/admin', 'admin@example.org','Administrator'), +('principals/admin/calendar-proxy-read', null, null), +('principals/admin/calendar-proxy-write', null, null); + diff --git a/src/serverlib/3rdparty/SabreDAV/examples/sql/pgsql.propertystorage.sql b/src/serverlib/3rdparty/SabreDAV/examples/sql/pgsql.propertystorage.sql new file mode 100644 index 0000000..1642e22 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/examples/sql/pgsql.propertystorage.sql @@ -0,0 +1,13 @@ +CREATE TABLE propertystorage ( + id SERIAL NOT NULL, + path VARCHAR(1024) NOT NULL, + name VARCHAR(100) NOT NULL, + valuetype INT, + value TEXT +); + +ALTER TABLE ONLY propertystorage + ADD CONSTRAINT propertystorage_pkey PRIMARY KEY (id); + +CREATE UNIQUE INDEX propertystorage_ukey + ON propertystorage (path, name); diff --git a/src/serverlib/3rdparty/SabreDAV/examples/sql/pgsql.users.sql b/src/serverlib/3rdparty/SabreDAV/examples/sql/pgsql.users.sql new file mode 100644 index 0000000..9d6047b --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/examples/sql/pgsql.users.sql @@ -0,0 +1,14 @@ +CREATE TABLE users ( + id SERIAL NOT NULL, + username VARCHAR(50), + digesta1 VARCHAR(32) +); + +ALTER TABLE ONLY users + ADD CONSTRAINT users_pkey PRIMARY KEY (id); + +CREATE UNIQUE INDEX users_ukey + ON users USING btree (username); + +INSERT INTO users (username,digesta1) VALUES +('admin', '87fd274b7b6c01e48d7c2f965da8ddf7'); diff --git a/src/serverlib/3rdparty/SabreDAV/examples/sql/sqlite.addressbooks.sql b/src/serverlib/3rdparty/SabreDAV/examples/sql/sqlite.addressbooks.sql new file mode 100644 index 0000000..8a889aa --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/examples/sql/sqlite.addressbooks.sql @@ -0,0 +1,28 @@ +CREATE TABLE addressbooks ( + id integer primary key asc, + principaluri text, + displayname text, + uri text, + description text, + synctoken integer +); + +CREATE TABLE cards ( + id integer primary key asc, + addressbookid integer, + carddata blob, + uri text, + lastmodified integer, + etag text, + size integer +); + +CREATE TABLE addressbookchanges ( + id integer primary key asc, + uri text, + synctoken integer, + addressbookid integer, + operation integer +); + +CREATE INDEX addressbookid_synctoken ON addressbookchanges (addressbookid, synctoken); diff --git a/src/serverlib/3rdparty/SabreDAV/examples/sql/sqlite.calendars.sql b/src/serverlib/3rdparty/SabreDAV/examples/sql/sqlite.calendars.sql new file mode 100644 index 0000000..4a17f0d --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/examples/sql/sqlite.calendars.sql @@ -0,0 +1,64 @@ +CREATE TABLE calendarobjects ( + id integer primary key asc, + calendardata blob, + uri text, + calendarid integer, + lastmodified integer, + etag text, + size integer, + componenttype text, + firstoccurence integer, + lastoccurence integer, + uid text +); + +CREATE TABLE calendars ( + id integer primary key asc, + principaluri text, + displayname text, + uri text, + synctoken integer, + description text, + calendarorder integer, + calendarcolor text, + timezone text, + components text, + transparent bool +); + +CREATE TABLE calendarchanges ( + id integer primary key asc, + uri text, + synctoken integer, + calendarid integer, + operation integer +); + +CREATE INDEX calendarid_synctoken ON calendarchanges (calendarid, synctoken); + +CREATE TABLE calendarsubscriptions ( + id integer primary key asc, + uri text, + principaluri text, + source text, + displayname text, + refreshrate text, + calendarorder integer, + calendarcolor text, + striptodos bool, + stripalarms bool, + stripattachments bool, + lastmodified int +); + +CREATE TABLE schedulingobjects ( + id integer primary key asc, + principaluri text, + calendardata blob, + uri text, + lastmodified integer, + etag text, + size integer +); + +CREATE INDEX principaluri_uri ON calendarsubscriptions (principaluri, uri); diff --git a/src/serverlib/3rdparty/SabreDAV/examples/sql/sqlite.locks.sql b/src/serverlib/3rdparty/SabreDAV/examples/sql/sqlite.locks.sql new file mode 100644 index 0000000..fd89b41 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/examples/sql/sqlite.locks.sql @@ -0,0 +1,12 @@ +BEGIN TRANSACTION; +CREATE TABLE locks ( + id integer primary key asc, + owner text, + timeout integer, + created integer, + token text, + scope integer, + depth integer, + uri text +); +COMMIT; diff --git a/src/serverlib/3rdparty/SabreDAV/examples/sql/sqlite.principals.sql b/src/serverlib/3rdparty/SabreDAV/examples/sql/sqlite.principals.sql new file mode 100644 index 0000000..247252f --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/examples/sql/sqlite.principals.sql @@ -0,0 +1,20 @@ +CREATE TABLE principals ( + id INTEGER PRIMARY KEY ASC, + uri TEXT, + email TEXT, + displayname TEXT, + UNIQUE(uri) +); + +CREATE TABLE groupmembers ( + id INTEGER PRIMARY KEY ASC, + principal_id INTEGER, + member_id INTEGER, + UNIQUE(principal_id, member_id) +); + + +INSERT INTO principals (uri,email,displayname) VALUES ('principals/admin', 'admin@example.org','Administrator'); +INSERT INTO principals (uri,email,displayname) VALUES ('principals/admin/calendar-proxy-read', null, null); +INSERT INTO principals (uri,email,displayname) VALUES ('principals/admin/calendar-proxy-write', null, null); + diff --git a/src/serverlib/3rdparty/SabreDAV/examples/sql/sqlite.propertystorage.sql b/src/serverlib/3rdparty/SabreDAV/examples/sql/sqlite.propertystorage.sql new file mode 100644 index 0000000..4518179 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/examples/sql/sqlite.propertystorage.sql @@ -0,0 +1,10 @@ +CREATE TABLE propertystorage ( + id integer primary key asc, + path text, + name text, + valuetype integer, + value string +); + + +CREATE UNIQUE INDEX path_property ON propertystorage (path, name); diff --git a/src/serverlib/3rdparty/SabreDAV/examples/sql/sqlite.users.sql b/src/serverlib/3rdparty/SabreDAV/examples/sql/sqlite.users.sql new file mode 100644 index 0000000..f4b2c16 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/examples/sql/sqlite.users.sql @@ -0,0 +1,9 @@ +CREATE TABLE users ( + id integer primary key asc, + username TEXT, + digesta1 TEXT, + UNIQUE(username) +); + +INSERT INTO users (username,digesta1) VALUES +('admin', '87fd274b7b6c01e48d7c2f965da8ddf7'); diff --git a/src/serverlib/3rdparty/SabreDAV/examples/webserver/apache2_htaccess.conf b/src/serverlib/3rdparty/SabreDAV/examples/webserver/apache2_htaccess.conf new file mode 100644 index 0000000..c5f29ba --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/examples/webserver/apache2_htaccess.conf @@ -0,0 +1,16 @@ +RewriteEngine On +# This makes every request go to server.php +RewriteRule (.*) server.php [L] + +# Output buffering needs to be off, to prevent high memory usage +php_flag output_buffering off + +# This is also to prevent high memory usage +php_flag always_populate_raw_post_data off + +# This is almost a given, but magic quotes is *still* on on some +# linux distributions +php_flag magic_quotes_gpc off + +# SabreDAV is not compatible with mbstring function overloading +php_flag mbstring.func_overload off diff --git a/src/serverlib/3rdparty/SabreDAV/examples/webserver/apache2_vhost.conf b/src/serverlib/3rdparty/SabreDAV/examples/webserver/apache2_vhost.conf new file mode 100644 index 0000000..bb374eb --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/examples/webserver/apache2_vhost.conf @@ -0,0 +1,33 @@ +# This is a sample configuration to setup a dedicated Apache vhost for WebDAV. +# +# The main thing that should be configured is the servername, and the path to +# your SabreDAV installation (DocumentRoot). +# +# This configuration assumed mod_php5 is used, as it sets a few default php +# settings as well. + + + # Don't forget to change the server name + # ServerName dav.example.org + + # The DocumentRoot is also required + # DocumentRoot /home/sabredav/ + + RewriteEngine On + # This makes every request go to server.php + RewriteRule ^/(.*)$ /server.php [L] + + # Output buffering needs to be off, to prevent high memory usage + php_flag output_buffering off + + # This is also to prevent high memory usage + php_flag always_populate_raw_post_data off + + # This is almost a given, but magic quotes is *still* on on some + # linux distributions + php_flag magic_quotes_gpc off + + # SabreDAV is not compatible with mbstring function overloading + php_flag mbstring.func_overload off + + diff --git a/src/serverlib/3rdparty/SabreDAV/examples/webserver/apache2_vhost_cgi.conf b/src/serverlib/3rdparty/SabreDAV/examples/webserver/apache2_vhost_cgi.conf new file mode 100644 index 0000000..607254c --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/examples/webserver/apache2_vhost_cgi.conf @@ -0,0 +1,21 @@ +# This is a sample configuration to setup a dedicated Apache vhost for WebDAV. +# +# The main thing that should be configured is the servername, and the path to +# your SabreDAV installation (DocumentRoot). +# +# This configuration assumes CGI or FastCGI is used. + + + # Don't forget to change the server name + # ServerName dav.example.org + + # The DocumentRoot is also required + # DocumentRoot /home/sabredav/ + + # This makes every request go to server.php. This also makes sure + # the Authentication information is available. If your server script is + # not called server.php, be sure to change it. + RewriteEngine On + RewriteRule ^/(.*)$ /server.php [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + + diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/autoload.php b/src/serverlib/3rdparty/SabreDAV/vendor/autoload.php new file mode 100644 index 0000000..e67ef9e --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/autoload.php @@ -0,0 +1,7 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see http://www.php-fig.org/psr/psr-0/ + * @see http://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + + private $classMapAuthoritative = false; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731 + if ('\\' == $class[0]) { + $class = substr($class, 1); + } + + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative) { + return false; + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if ($file === null && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if ($file === null) { + // Remember that this class does not exist. + return $this->classMap[$class] = false; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) { + if (0 === strpos($class, $prefix)) { + foreach ($this->prefixDirsPsr4[$prefix] as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/composer/LICENSE b/src/serverlib/3rdparty/SabreDAV/vendor/composer/LICENSE new file mode 100644 index 0000000..cc78956 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/composer/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015 Nils Adermann, Jordi Boggiano + +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. diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/composer/autoload_classmap.php b/src/serverlib/3rdparty/SabreDAV/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..7a91153 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/composer/autoload_classmap.php @@ -0,0 +1,9 @@ + $vendorDir . '/sabre/uri/lib/functions.php', + 'ebdb698ed4152ae445614b69b5e4bb6a' => $vendorDir . '/sabre/http/lib/functions.php', + '3569eecfeed3bcf0bad3c998a494ecb8' => $vendorDir . '/sabre/xml/lib/Deserializer/functions.php', + '93aa591bc4ca510c520999e34229ee79' => $vendorDir . '/sabre/xml/lib/Serializer/functions.php', +); diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/composer/autoload_namespaces.php b/src/serverlib/3rdparty/SabreDAV/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..b7fc012 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($vendorDir . '/sabre/xml/lib'), + 'Sabre\\VObject\\' => array($vendorDir . '/sabre/vobject/lib'), + 'Sabre\\Uri\\' => array($vendorDir . '/sabre/uri/lib'), + 'Sabre\\HTTP\\' => array($vendorDir . '/sabre/http/lib'), + 'Sabre\\Event\\' => array($vendorDir . '/sabre/event/lib'), + 'Sabre\\DAV\\' => array($vendorDir . '/sabre/dav/lib/DAV'), + 'Sabre\\DAVACL\\' => array($vendorDir . '/sabre/dav/lib/DAVACL'), + 'Sabre\\CardDAV\\' => array($vendorDir . '/sabre/dav/lib/CardDAV'), + 'Sabre\\CalDAV\\' => array($vendorDir . '/sabre/dav/lib/CalDAV'), +); diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/composer/autoload_real.php b/src/serverlib/3rdparty/SabreDAV/vendor/composer/autoload_real.php new file mode 100644 index 0000000..cb33a4c --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/composer/autoload_real.php @@ -0,0 +1,59 @@ + $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + + $loader->register(true); + + $includeFiles = require __DIR__ . '/autoload_files.php'; + foreach ($includeFiles as $fileIdentifier => $file) { + composerRequired98aa8eda4187f8aba381f2d1a6f1d7d($fileIdentifier, $file); + } + + return $loader; + } +} + +function composerRequired98aa8eda4187f8aba381f2d1a6f1d7d($fileIdentifier, $file) +{ + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + require $file; + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/composer/installed.json b/src/serverlib/3rdparty/SabreDAV/vendor/composer/installed.json new file mode 100644 index 0000000..c29ff52 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/composer/installed.json @@ -0,0 +1,381 @@ +[ + { + "name": "sabre/uri", + "version": "1.1.0", + "version_normalized": "1.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/fruux/sabre-uri.git", + "reference": "9012116434d84ef6e5e37a89dfdbfbe2204a8704" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fruux/sabre-uri/zipball/9012116434d84ef6e5e37a89dfdbfbe2204a8704", + "reference": "9012116434d84ef6e5e37a89dfdbfbe2204a8704", + "shasum": "" + }, + "require": { + "php": ">=5.4.7" + }, + "require-dev": { + "phpunit/phpunit": "*", + "sabre/cs": "~0.0.1" + }, + "time": "2016-03-08 02:29:27", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "lib/functions.php" + ], + "psr-4": { + "Sabre\\Uri\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Evert Pot", + "email": "me@evertpot.com", + "homepage": "http://evertpot.com/", + "role": "Developer" + } + ], + "description": "Functions for making sense out of URIs.", + "homepage": "http://sabre.io/uri/", + "keywords": [ + "rfc3986", + "uri", + "url" + ] + }, + { + "name": "sabre/event", + "version": "2.0.2", + "version_normalized": "2.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/fruux/sabre-event.git", + "reference": "337b6f5e10ea6e0b21e22c7e5788dd3883ae73ff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fruux/sabre-event/zipball/337b6f5e10ea6e0b21e22c7e5788dd3883ae73ff", + "reference": "337b6f5e10ea6e0b21e22c7e5788dd3883ae73ff", + "shasum": "" + }, + "require": { + "php": ">=5.4.1" + }, + "require-dev": { + "phpunit/phpunit": "*", + "sabre/cs": "~0.0.1" + }, + "time": "2015-05-19 10:24:22", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Sabre\\Event\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Evert Pot", + "email": "me@evertpot.com", + "homepage": "http://evertpot.com/", + "role": "Developer" + } + ], + "description": "sabre/event is a library for lightweight event-based programming", + "homepage": "http://sabre.io/event/", + "keywords": [ + "EventEmitter", + "events", + "hooks", + "plugin", + "promise", + "signal" + ] + }, + { + "name": "sabre/http", + "version": "4.2.1", + "version_normalized": "4.2.1.0", + "source": { + "type": "git", + "url": "https://github.com/fruux/sabre-http.git", + "reference": "2e93bc8321524c67be4ca5b8415daebd4c8bf85e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fruux/sabre-http/zipball/2e93bc8321524c67be4ca5b8415daebd4c8bf85e", + "reference": "2e93bc8321524c67be4ca5b8415daebd4c8bf85e", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.4", + "sabre/event": ">=1.0.0,<4.0.0", + "sabre/uri": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.3", + "sabre/cs": "~0.0.1" + }, + "suggest": { + "ext-curl": " to make http requests with the Client class" + }, + "time": "2016-01-06 23:00:08", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "lib/functions.php" + ], + "psr-4": { + "Sabre\\HTTP\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Evert Pot", + "email": "me@evertpot.com", + "homepage": "http://evertpot.com/", + "role": "Developer" + } + ], + "description": "The sabre/http library provides utilities for dealing with http requests and responses. ", + "homepage": "https://github.com/fruux/sabre-http", + "keywords": [ + "http" + ] + }, + { + "name": "sabre/xml", + "version": "1.4.1", + "version_normalized": "1.4.1.0", + "source": { + "type": "git", + "url": "https://github.com/fruux/sabre-xml.git", + "reference": "59998046db252634259a878baf1af18159f508f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fruux/sabre-xml/zipball/59998046db252634259a878baf1af18159f508f3", + "reference": "59998046db252634259a878baf1af18159f508f3", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlreader": "*", + "ext-xmlwriter": "*", + "lib-libxml": ">=2.6.20", + "php": ">=5.4.1", + "sabre/uri": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "*", + "sabre/cs": "~0.0.2" + }, + "time": "2016-03-12 22:23:16", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Sabre\\Xml\\": "lib/" + }, + "files": [ + "lib/Deserializer/functions.php", + "lib/Serializer/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Evert Pot", + "email": "me@evertpot.com", + "homepage": "http://evertpot.com/", + "role": "Developer" + }, + { + "name": "Markus Staab", + "email": "markus.staab@redaxo.de", + "role": "Developer" + } + ], + "description": "sabre/xml is an XML library that you may not hate.", + "homepage": "https://sabre.io/xml/", + "keywords": [ + "XMLReader", + "XMLWriter", + "dom", + "xml" + ] + }, + { + "name": "sabre/vobject", + "version": "3.5.1", + "version_normalized": "3.5.1.0", + "source": { + "type": "git", + "url": "https://github.com/fruux/sabre-vobject.git", + "reference": "5b92c27399a31c860877b9c29f4fcbb241069679" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fruux/sabre-vobject/zipball/5b92c27399a31c860877b9c29f4fcbb241069679", + "reference": "5b92c27399a31c860877b9c29f4fcbb241069679", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.3.1" + }, + "require-dev": { + "phpunit/phpunit": "*", + "squizlabs/php_codesniffer": "*" + }, + "time": "2016-04-07 00:12:31", + "bin": [ + "bin/vobject", + "bin/generate_vcards" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Sabre\\VObject\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Evert Pot", + "email": "me@evertpot.com", + "homepage": "http://evertpot.com/", + "role": "Developer" + }, + { + "name": "Dominik Tobschall", + "email": "dominik@fruux.com", + "homepage": "http://tobschall.de/", + "role": "Developer" + } + ], + "description": "The VObject library for PHP allows you to easily parse and manipulate iCalendar and vCard objects", + "homepage": "http://sabre.io/vobject/", + "keywords": [ + "VObject", + "iCalendar", + "jCal", + "jCard", + "vCard" + ] + }, + { + "name": "sabre/dav", + "version": "3.0.9", + "version_normalized": "3.0.9.0", + "source": { + "type": "git", + "url": "https://github.com/fruux/sabre-dav.git", + "reference": "b42593965211de1ce99f73bd3aede99c41258e08" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fruux/sabre-dav/zipball/b42593965211de1ce99f73bd3aede99c41258e08", + "reference": "b42593965211de1ce99f73bd3aede99c41258e08", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-date": "*", + "ext-dom": "*", + "ext-iconv": "*", + "ext-mbstring": "*", + "ext-pcre": "*", + "ext-simplexml": "*", + "ext-spl": "*", + "lib-libxml": ">=2.7.0", + "php": ">=5.4.1", + "sabre/event": "~2.0", + "sabre/http": "~4.0", + "sabre/uri": "~1.0", + "sabre/vobject": "^3.3.4", + "sabre/xml": "~1.0" + }, + "require-dev": { + "evert/phpdoc-md": "~0.1.0", + "phpunit/phpunit": "~4.2", + "sabre/cs": "~0.0.2" + }, + "suggest": { + "ext-curl": "*", + "ext-pdo": "*" + }, + "time": "2016-04-07 00:32:57", + "bin": [ + "bin/sabredav", + "bin/naturalselection" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Sabre\\DAV\\": "lib/DAV/", + "Sabre\\DAVACL\\": "lib/DAVACL/", + "Sabre\\CalDAV\\": "lib/CalDAV/", + "Sabre\\CardDAV\\": "lib/CardDAV/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Evert Pot", + "email": "me@evertpot.com", + "homepage": "http://evertpot.com/", + "role": "Developer" + } + ], + "description": "WebDAV Framework for PHP", + "homepage": "http://sabre.io/", + "keywords": [ + "CalDAV", + "CardDAV", + "WebDAV", + "framework", + "iCalendar" + ] + } +] diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/.gitignore b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/.gitignore new file mode 100644 index 0000000..aff6e27 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/.gitignore @@ -0,0 +1,40 @@ +# Unit tests +tests/temp +tests/.sabredav +tests/cov + +# ViM +*.swp + +# Composer +composer.lock +vendor + +# Composer binaries +bin/phing +bin/phpunit +bin/vobject +bin/generate_vcards +bin/phpdocmd +bin/phpunit +bin/php-cs-fixer +bin/sabre-cs-fixer + +# Assuming every .php file in the root is for testing +/*.php + +# Other testing stuff +/tmpdata +/data +/public + +# Build +build +build.properties + +# Docs +docs/api +docs/wikidocs + +# Mac +.DS_Store diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/.travis.yml b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/.travis.yml new file mode 100644 index 0000000..3845353 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/.travis.yml @@ -0,0 +1,32 @@ +language: php +php: + - 5.4 + - 5.5 + - 5.6 + - 7 + - hhvm + +matrix: + fast_finish: true + +env: + matrix: + - LOWEST_DEPS="" TEST_DEPS="" + - LOWEST_DEPS="--prefer-lowest" TEST_DEPS="tests/Sabre/" + +services: + - mysql + +sudo: false + +cache: vendor + +before_script: + - mysql -e 'create database sabredav' + # - composer self-update + - composer update --prefer-source $LOWEST_DEPS + +script: + - ./bin/phpunit --configuration tests/phpunit.xml $TEST_DEPS + - ./bin/sabre-cs-fixer fix lib/ --dry-run --diff + diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/CONTRIBUTING.md b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/CONTRIBUTING.md new file mode 100644 index 0000000..425ee19 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/CONTRIBUTING.md @@ -0,0 +1,87 @@ +Contributing to sabre projects +============================== + +Want to contribute to sabre/dav? Here are some guidelines to ensure your patch +gets accepted. + + +Building a new feature? Contact us first +---------------------------------------- + +We may not want to accept every feature that comes our way. Sometimes +features are out of scope for our projects. + +We don't want to waste your time, so by having a quick chat with us first, +you may find out quickly if the feature makes sense to us, and we can give +some tips on how to best build the feature. + +If we don't accept the feature, it could be for a number of reasons. For +instance, we've rejected features in the past because we felt uncomfortable +assuming responsibility for maintaining the feature. + +In those cases, it's often possible to keep the feature separate from the +sabre projects. sabre/dav for instance has a plugin system, and there's no +reason the feature can't live in a project you own. + +In that case, definitely let us know about your plugin as well, so we can +feature it on [sabre.io][4]. + +We are often on [IRC][5], in the #sabredav channel on freenode. If there's +no one there, post a message on the [mailing list][6]. + + +Coding standards +---------------- + +sabre projects follow: + +1. [PSR-1][1] +2. [PSR-4][2] + +sabre projects don't follow [PSR-2][3]. + +In addition to that, here's a list of basic rules: + +1. PHP 5.4 array syntax must be used every where. This means you use `[` and + `]` instead of `array(` and `)`. +2. Use PHP namespaces everywhere. +3. Use 4 spaces for indentation. +4. Try to keep your lines under 80 characters. This is not a hard rule, as + there are many places in the source where it felt more sensibile to not + do so. In particular, function declarations are never split over multiple + lines. +5. Opening braces (`{`) are _always_ on the same line as the `class`, `if`, + `function`, etc. they belong to. +6. `public` must be omitted from method declarations. It must also be omitted + for static properties. +7. All files should use unix-line endings (`\n`). +8. Files must omit the closing php tag (`?>`). +9. `true`, `false` and `null` are always lower-case. +10. Constants are always upper-case. +11. Any of the rules stated before may be broken where this is the pragmatic + thing to do. + + +Unit test requirements +---------------------- + +Any new feature or change requires unittests. We use [PHPUnit][7] for all our +tests. + +Adding unittests will greatly increase the likelyhood of us quickly accepting +your pull request. If unittests are not included though for whatever reason, +we'd still _love_ your pull request. + +We may have to write the tests ourselves, which can increase the time it takes +to accept the patch, but we'd still really like your contribution! + +To run the testsuite jump into the directory `cd tests` and trigger `phpunit`. +Make sure you did a `composer install` beforehand. + +[1]: http://www.php-fig.org/psr/psr-1/ +[2]: http://www.php-fig.org/psr/psr-4/ +[3]: http://www.php-fig.org/psr/psr-2/ +[4]: http://sabre.io/ +[5]: irc://freenode.net/#sabredav +[6]: http://groups.google.com/group/sabredav-discuss +[7]: http://phpunit.de/ diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/build.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/build.php new file mode 100755 index 0000000..8b78d88 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/build.php @@ -0,0 +1,177 @@ +#!/usr/bin/env php + [ + 'init', 'test', 'clean', + ], + 'markrelease' => [ + 'init', 'test', 'clean', + ], + 'clean' => [], + 'test' => [ + 'composerupdate', + ], + 'init' => [], + 'composerupdate' => [], + ]; + +$default = 'buildzip'; + +$baseDir = __DIR__ . '/../'; +chdir($baseDir); + +$currentTask = $default; +if ($argc > 1) $currentTask = $argv[1]; +$version = null; +if ($argc > 2) $version = $argv[2]; + +if (!isset($tasks[$currentTask])) { + echo "Task not found: ", $currentTask, "\n"; + die(1); +} + +// Creating the dependency graph +$newTaskList = []; +$oldTaskList = [$currentTask => true]; + +while(count($oldTaskList)>0) { + + foreach($oldTaskList as $task=>$foo) { + + if (!isset($tasks[$task])) { + echo "Dependency not found: " . $task, "\n"; + die(1); + } + $dependencies = $tasks[$task]; + + $fullFilled = true; + foreach($dependencies as $dependency) { + if (isset($newTaskList[$dependency])) { + // Already in the fulfilled task list. + continue; + } else { + $oldTaskList[$dependency] = true; + $fullFilled = false; + } + + } + if ($fullFilled) { + unset($oldTaskList[$task]); + $newTaskList[$task] = 1; + } + + } + +} + +foreach(array_keys($newTaskList) as $task) { + + echo "task: " . $task, "\n"; + call_user_func($task); + echo "\n"; + +} + +function init() { + + global $version; + if (!$version) { + include __DIR__ . '/../vendor/autoload.php'; + $version = Sabre\DAV\Version::VERSION; + } + + echo " Building sabre/dav " . $version, "\n"; + +} + +function clean() { + + global $baseDir; + echo " Removing build files\n"; + $outputDir = $baseDir . '/build/SabreDAV'; + if (is_dir($outputDir)) { + system('rm -r ' . $baseDir . '/build/SabreDAV'); + } + +} + +function composerupdate() { + + global $baseDir; + echo " Updating composer packages to latest version\n\n"; + system('cd ' . $baseDir . '; composer update'); +} + +function test() { + + global $baseDir; + + echo " Running all unittests.\n"; + echo " This may take a while.\n\n"; + system(__DIR__ . '/phpunit --configuration ' . $baseDir . '/tests/phpunit.xml --stop-on-failure', $code); + if ($code != 0) { + echo "PHPUnit reported error code $code\n"; + die(1); + } + +} + +function buildzip() { + + global $baseDir, $version; + echo " Generating composer.json\n"; + + $input = json_decode(file_get_contents(__DIR__ . '/../composer.json'), true); + $newComposer = [ + "require" => $input['require'], + "config" => [ + "bin-dir" => "./bin", + ], + "prefer-stable" => true, + "minimum-stability" => "alpha", + ]; + unset( + $newComposer['require']['sabre/vobject'], + $newComposer['require']['sabre/http'], + $newComposer['require']['sabre/uri'], + $newComposer['require']['sabre/event'] + ); + $newComposer['require']['sabre/dav'] = $version; + mkdir('build/SabreDAV'); + file_put_contents('build/SabreDAV/composer.json', json_encode($newComposer, JSON_PRETTY_PRINT)); + + echo " Downloading dependencies\n"; + system("cd build/SabreDAV; composer install -n", $code); + if ($code!==0) { + echo "Composer reported error code $code\n"; + die(1); + } + + echo " Removing pointless files\n"; + unlink('build/SabreDAV/composer.json'); + unlink('build/SabreDAV/composer.lock'); + + echo " Moving important files to the root of the project\n"; + + $fileNames = [ + 'CHANGELOG.md', + 'LICENSE', + 'README.md', + 'examples', + ]; + foreach($fileNames as $fileName) { + echo " $fileName\n"; + rename('build/SabreDAV/vendor/sabre/dav/' . $fileName, 'build/SabreDAV/' . $fileName); + } + + // + + echo "\n"; + echo "Zipping the sabredav distribution\n\n"; + system('cd build; zip -qr sabredav-' . $version . '.zip SabreDAV'); + + echo "Done."; + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/googlecode_upload.py b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/googlecode_upload.py new file mode 100755 index 0000000..caafd5d --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/googlecode_upload.py @@ -0,0 +1,248 @@ +#!/usr/bin/env python +# +# Copyright 2006, 2007 Google Inc. All Rights Reserved. +# Author: danderson@google.com (David Anderson) +# +# Script for uploading files to a Google Code project. +# +# This is intended to be both a useful script for people who want to +# streamline project uploads and a reference implementation for +# uploading files to Google Code projects. +# +# To upload a file to Google Code, you need to provide a path to the +# file on your local machine, a small summary of what the file is, a +# project name, and a valid account that is a member or owner of that +# project. You can optionally provide a list of labels that apply to +# the file. The file will be uploaded under the same name that it has +# in your local filesystem (that is, the "basename" or last path +# component). Run the script with '--help' to get the exact syntax +# and available options. +# +# Note that the upload script requests that you enter your +# googlecode.com password. This is NOT your Gmail account password! +# This is the password you use on googlecode.com for committing to +# Subversion and uploading files. You can find your password by going +# to http://code.google.com/hosting/settings when logged in with your +# Gmail account. If you have already committed to your project's +# Subversion repository, the script will automatically retrieve your +# credentials from there (unless disabled, see the output of '--help' +# for details). +# +# If you are looking at this script as a reference for implementing +# your own Google Code file uploader, then you should take a look at +# the upload() function, which is the meat of the uploader. You +# basically need to build a multipart/form-data POST request with the +# right fields and send it to https://PROJECT.googlecode.com/files . +# Authenticate the request using HTTP Basic authentication, as is +# shown below. +# +# Licensed under the terms of the Apache Software License 2.0: +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Questions, comments, feature requests and patches are most welcome. +# Please direct all of these to the Google Code users group: +# http://groups.google.com/group/google-code-hosting + +"""Google Code file uploader script. +""" + +__author__ = 'danderson@google.com (David Anderson)' + +import httplib +import os.path +import optparse +import getpass +import base64 +import sys + + +def upload(file, project_name, user_name, password, summary, labels=None): + """Upload a file to a Google Code project's file server. + + Args: + file: The local path to the file. + project_name: The name of your project on Google Code. + user_name: Your Google account name. + password: The googlecode.com password for your account. + Note that this is NOT your global Google Account password! + summary: A small description for the file. + labels: an optional list of label strings with which to tag the file. + + Returns: a tuple: + http_status: 201 if the upload succeeded, something else if an + error occurred. + http_reason: The human-readable string associated with http_status + file_url: If the upload succeeded, the URL of the file on Google + Code, None otherwise. + """ + # The login is the user part of user@gmail.com. If the login provided + # is in the full user@domain form, strip it down. + if user_name.endswith('@gmail.com'): + user_name = user_name[:user_name.index('@gmail.com')] + + form_fields = [('summary', summary)] + if labels is not None: + form_fields.extend([('label', l.strip()) for l in labels]) + + content_type, body = encode_upload_request(form_fields, file) + + upload_host = '%s.googlecode.com' % project_name + upload_uri = '/files' + auth_token = base64.b64encode('%s:%s'% (user_name, password)) + headers = { + 'Authorization': 'Basic %s' % auth_token, + 'User-Agent': 'Googlecode.com uploader v0.9.4', + 'Content-Type': content_type, + } + + server = httplib.HTTPSConnection(upload_host) + server.request('POST', upload_uri, body, headers) + resp = server.getresponse() + server.close() + + if resp.status == 201: + location = resp.getheader('Location', None) + else: + location = None + return resp.status, resp.reason, location + + +def encode_upload_request(fields, file_path): + """Encode the given fields and file into a multipart form body. + + fields is a sequence of (name, value) pairs. file is the path of + the file to upload. The file will be uploaded to Google Code with + the same file name. + + Returns: (content_type, body) ready for httplib.HTTP instance + """ + BOUNDARY = '----------Googlecode_boundary_reindeer_flotilla' + CRLF = '\r\n' + + body = [] + + # Add the metadata about the upload first + for key, value in fields: + body.extend( + ['--' + BOUNDARY, + 'Content-Disposition: form-data; name="%s"' % key, + '', + value, + ]) + + # Now add the file itself + file_name = os.path.basename(file_path) + f = open(file_path, 'rb') + file_content = f.read() + f.close() + + body.extend( + ['--' + BOUNDARY, + 'Content-Disposition: form-data; name="filename"; filename="%s"' + % file_name, + # The upload server determines the mime-type, no need to set it. + 'Content-Type: application/octet-stream', + '', + file_content, + ]) + + # Finalize the form body + body.extend(['--' + BOUNDARY + '--', '']) + + return 'multipart/form-data; boundary=%s' % BOUNDARY, CRLF.join(body) + + +def upload_find_auth(file_path, project_name, summary, labels=None, + user_name=None, password=None, tries=3): + """Find credentials and upload a file to a Google Code project's file server. + + file_path, project_name, summary, and labels are passed as-is to upload. + + Args: + file_path: The local path to the file. + project_name: The name of your project on Google Code. + summary: A small description for the file. + labels: an optional list of label strings with which to tag the file. + config_dir: Path to Subversion configuration directory, 'none', or None. + user_name: Your Google account name. + tries: How many attempts to make. + """ + + while tries > 0: + if user_name is None: + # Read username if not specified or loaded from svn config, or on + # subsequent tries. + sys.stdout.write('Please enter your googlecode.com username: ') + sys.stdout.flush() + user_name = sys.stdin.readline().rstrip() + if password is None: + # Read password if not loaded from svn config, or on subsequent tries. + print 'Please enter your googlecode.com password.' + print '** Note that this is NOT your Gmail account password! **' + print 'It is the password you use to access Subversion repositories,' + print 'and can be found here: http://code.google.com/hosting/settings' + password = getpass.getpass() + + status, reason, url = upload(file_path, project_name, user_name, password, + summary, labels) + # Returns 403 Forbidden instead of 401 Unauthorized for bad + # credentials as of 2007-07-17. + if status in [httplib.FORBIDDEN, httplib.UNAUTHORIZED]: + # Rest for another try. + user_name = password = None + tries = tries - 1 + else: + # We're done. + break + + return status, reason, url + + +def main(): + parser = optparse.OptionParser(usage='googlecode-upload.py -s SUMMARY ' + '-p PROJECT [options] FILE') + parser.add_option('-s', '--summary', dest='summary', + help='Short description of the file') + parser.add_option('-p', '--project', dest='project', + help='Google Code project name') + parser.add_option('-u', '--user', dest='user', + help='Your Google Code username') + parser.add_option('-w', '--password', dest='password', + help='Your Google Code password') + parser.add_option('-l', '--labels', dest='labels', + help='An optional list of comma-separated labels to attach ' + 'to the file') + + options, args = parser.parse_args() + + if not options.summary: + parser.error('File summary is missing.') + elif not options.project: + parser.error('Project name is missing.') + elif len(args) < 1: + parser.error('File to upload not provided.') + elif len(args) > 1: + parser.error('Only one file may be specified.') + + file_path = args[0] + + if options.labels: + labels = options.labels.split(',') + else: + labels = None + + status, reason, url = upload_find_auth(file_path, options.project, + options.summary, labels, + options.user, options.password) + if url: + print 'The file was uploaded successfully.' + print 'URL: %s' % url + return 0 + else: + print 'An error occurred. Your file was not uploaded.' + print 'Google Code upload server said: %s (%s)' % (reason, status) + return 1 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/migrateto17.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/migrateto17.php new file mode 100755 index 0000000..3ccf0a3 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/migrateto17.php @@ -0,0 +1,284 @@ +#!/usr/bin/env php +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); +$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); + +echo "Validating existing table layout\n"; + +// The only cross-db way to do this, is to just fetch a single record. +$row = $pdo->query("SELECT * FROM calendarobjects LIMIT 1")->fetch(); + +if (!$row) { + echo "Error: This database did not have any records in the calendarobjects table, you should just recreate the table.\n"; + exit(-1); +} + +$requiredFields = array( + 'id', + 'calendardata', + 'uri', + 'calendarid', + 'lastmodified', +); + +foreach($requiredFields as $requiredField) { + if (!array_key_exists($requiredField,$row)) { + echo "Error: The current 'calendarobjects' table was missing a field we expected to exist.\n"; + echo "For safety reasons, this process is stopped.\n"; + exit(-1); + } +} + +$fields17 = array( + 'etag', + 'size', + 'componenttype', + 'firstoccurence', + 'lastoccurence', +); + +$found = 0; +foreach($fields17 as $field) { + if (array_key_exists($field, $row)) { + $found++; + } +} + +if ($found === 0) { + echo "The database had the 1.6 schema. Table will now be altered.\n"; + echo "This may take some time for large tables\n"; + + switch($pdo->getAttribute(PDO::ATTR_DRIVER_NAME)) { + + case 'mysql' : + + $pdo->exec(<<exec('ALTER TABLE calendarobjects ADD etag text'); + $pdo->exec('ALTER TABLE calendarobjects ADD size integer'); + $pdo->exec('ALTER TABLE calendarobjects ADD componenttype TEXT'); + $pdo->exec('ALTER TABLE calendarobjects ADD firstoccurence integer'); + $pdo->exec('ALTER TABLE calendarobjects ADD lastoccurence integer'); + break; + + default : + die('This upgrade script does not support this driver (' . $pdo->getAttribute(PDO::ATTR_DRIVER_NAME) . ")\n"); + + } + echo "Database schema upgraded.\n"; + +} elseif ($found === 5) { + + echo "Database already had the 1.7 schema\n"; + +} else { + + echo "The database had $found out of 5 from the changes for 1.7. This is scary and unusual, so we have to abort.\n"; + echo "You can manually try to upgrade the schema, and then run this script again.\n"; + exit(-1); + +} + +echo "Now, we need to parse every record and pull out some information.\n"; + +$result = $pdo->query('SELECT id, calendardata FROM calendarobjects'); +$stmt = $pdo->prepare('UPDATE calendarobjects SET etag = ?, size = ?, componenttype = ?, firstoccurence = ?, lastoccurence = ? WHERE id = ?'); + +echo "Total records found: " . $result->rowCount() . "\n"; +$done = 0; +$total = $result->rowCount(); +while($row = $result->fetch()) { + + try { + $newData = getDenormalizedData($row['calendardata']); + } catch (Exception $e) { + echo "===\nException caught will trying to parser calendarobject.\n"; + echo "Error message: " . $e->getMessage() . "\n"; + echo "Record id: " . $row['id'] . "\n"; + echo "This record is ignored, you should inspect it to see if there's anything wrong.\n===\n"; + continue; + } + $stmt->execute(array( + $newData['etag'], + $newData['size'], + $newData['componentType'], + $newData['firstOccurence'], + $newData['lastOccurence'], + $row['id'], + )); + $done++; + + if ($done % 500 === 0) { + echo "Completed: $done / $total\n"; + } +} +echo "Completed: $done / $total\n"; + +echo "Checking the calendars table needs changes.\n"; +$row = $pdo->query("SELECT * FROM calendars LIMIT 1")->fetch(); + +if (array_key_exists('transparent', $row)) { + + echo "The calendars table is already up to date\n"; + +} else { + + echo "Adding the 'transparent' field to the calendars table\n"; + + switch($pdo->getAttribute(PDO::ATTR_DRIVER_NAME)) { + + case 'mysql' : + $pdo->exec("ALTER TABLE calendars ADD transparent TINYINT(1) NOT NULL DEFAULT '0'"); + break; + case 'sqlite' : + $pdo->exec("ALTER TABLE calendars ADD transparent bool"); + break; + + default : + die('This upgrade script does not support this driver (' . $pdo->getAttribute(PDO::ATTR_DRIVER_NAME) . ")\n"); + + } + +} + +echo "Process completed!\n"; + +/** + * Parses some information from calendar objects, used for optimized + * calendar-queries. + * + * Blantently copied from Sabre\CalDAV\Backend\PDO + * + * Returns an array with the following keys: + * * etag + * * size + * * componentType + * * firstOccurence + * * lastOccurence + * + * @param string $calendarData + * @return array + */ +function getDenormalizedData($calendarData) { + + $vObject = \Sabre\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(\Sabre\VObject\DateTimeParser::parse($component->DURATION->value)); + $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 \Sabre\VObject\Recur\EventIterator($vObject, (string)$component->UID); + $maxDate = new DateTime(\Sabre\CalDAV\Backend\PDO::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, + ); + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/migrateto20.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/migrateto20.php new file mode 100755 index 0000000..3118104 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/migrateto20.php @@ -0,0 +1,453 @@ +#!/usr/bin/env php +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); +$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); + +$driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME); + +switch($driver) { + + case 'mysql' : + echo "Detected MySQL.\n"; + break; + case 'sqlite' : + echo "Detected SQLite.\n"; + break; + default : + echo "Error: unsupported driver: " . $driver . "\n"; + die(-1); +} + +foreach(['calendar', 'addressbook'] as $itemType) { + + $tableName = $itemType . 's'; + $tableNameOld = $tableName . '_old'; + $changesTable = $itemType . 'changes'; + + echo "Upgrading '$tableName'\n"; + + // The only cross-db way to do this, is to just fetch a single record. + $row = $pdo->query("SELECT * FROM $tableName LIMIT 1")->fetch(); + + if (!$row) { + + echo "No records were found in the '$tableName' table.\n"; + echo "\n"; + echo "We're going to rename the old table to $tableNameOld (just in case).\n"; + echo "and re-create the new table.\n"; + + switch($driver) { + + case 'mysql' : + $pdo->exec("RENAME TABLE $tableName TO $tableNameOld"); + switch($itemType) { + case 'calendar' : + $pdo->exec(" + CREATE TABLE calendars ( + id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + principaluri VARCHAR(100), + displayname VARCHAR(100), + uri VARCHAR(200), + synctoken INT(11) UNSIGNED NOT NULL DEFAULT '1', + description TEXT, + calendarorder INT(11) UNSIGNED NOT NULL DEFAULT '0', + calendarcolor VARCHAR(10), + timezone TEXT, + components VARCHAR(20), + transparent TINYINT(1) NOT NULL DEFAULT '0', + UNIQUE(principaluri, uri) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + "); + break; + case 'addressbook' : + $pdo->exec(" + CREATE TABLE addressbooks ( + id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + principaluri VARCHAR(255), + displayname VARCHAR(255), + uri VARCHAR(200), + description TEXT, + synctoken INT(11) UNSIGNED NOT NULL DEFAULT '1', + UNIQUE(principaluri, uri) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + "); + break; + } + break; + + case 'sqlite' : + + $pdo->exec("ALTER TABLE $tableName RENAME TO $tableNameOld"); + + switch($itemType) { + case 'calendar' : + $pdo->exec(" + CREATE TABLE calendars ( + id integer primary key asc, + principaluri text, + displayname text, + uri text, + synctoken integer, + description text, + calendarorder integer, + calendarcolor text, + timezone text, + components text, + transparent bool + ); + "); + break; + case 'addressbook' : + $pdo->exec(" + CREATE TABLE addressbooks ( + id integer primary key asc, + principaluri text, + displayname text, + uri text, + description text, + synctoken integer + ); + "); + + break; + } + break; + + } + echo "Creation of 2.0 $tableName table is complete\n"; + + } else { + + // Checking if there's a synctoken field already. + if (array_key_exists('synctoken', $row)) { + echo "The 'synctoken' field already exists in the $tableName table.\n"; + echo "It's likely you already upgraded, so we're simply leaving\n"; + echo "the $tableName table alone\n"; + } else { + + echo "1.8 table schema detected\n"; + switch($driver) { + + case 'mysql' : + $pdo->exec("ALTER TABLE $tableName ADD synctoken INT(11) UNSIGNED NOT NULL DEFAULT '1'"); + $pdo->exec("ALTER TABLE $tableName DROP ctag"); + $pdo->exec("UPDATE $tableName SET synctoken = '1'"); + break; + case 'sqlite' : + $pdo->exec("ALTER TABLE $tableName ADD synctoken integer"); + $pdo->exec("UPDATE $tableName SET synctoken = '1'"); + echo "Note: there's no easy way to remove fields in sqlite.\n"; + echo "The ctag field is no longer used, but it's kept in place\n"; + break; + + } + + echo "Upgraded '$tableName' to 2.0 schema.\n"; + + } + + } + + try { + $pdo->query("SELECT * FROM $changesTable LIMIT 1"); + + echo "'$changesTable' already exists. Assuming that this part of the\n"; + echo "upgrade was already completed.\n"; + + } catch (Exception $e) { + echo "Creating '$changesTable' table.\n"; + + switch($driver) { + + case 'mysql' : + $pdo->exec(" + CREATE TABLE $changesTable ( + id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + uri VARCHAR(200) NOT NULL, + synctoken INT(11) UNSIGNED NOT NULL, + {$itemType}id INT(11) UNSIGNED NOT NULL, + operation TINYINT(1) NOT NULL, + INDEX {$itemType}id_synctoken ({$itemType}id, synctoken) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + + "); + break; + case 'sqlite' : + $pdo->exec(" + + CREATE TABLE $changesTable ( + id integer primary key asc, + uri text, + synctoken integer, + {$itemType}id integer, + operation bool + ); + + "); + $pdo->exec("CREATE INDEX {$itemType}id_synctoken ON $changesTable ({$itemType}id, synctoken);"); + break; + + } + + } + +} + +try { + $pdo->query("SELECT * FROM calendarsubscriptions LIMIT 1"); + + echo "'calendarsubscriptions' already exists. Assuming that this part of the\n"; + echo "upgrade was already completed.\n"; + +} catch (Exception $e) { + echo "Creating calendarsubscriptions table.\n"; + + switch($driver) { + + case 'mysql' : + $pdo->exec(" +CREATE TABLE calendarsubscriptions ( + id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + uri VARCHAR(200) NOT NULL, + principaluri VARCHAR(100) NOT NULL, + source TEXT, + displayname VARCHAR(100), + refreshrate VARCHAR(10), + calendarorder INT(11) UNSIGNED NOT NULL DEFAULT '0', + calendarcolor VARCHAR(10), + striptodos TINYINT(1) NULL, + stripalarms TINYINT(1) NULL, + stripattachments TINYINT(1) NULL, + lastmodified INT(11) UNSIGNED, + UNIQUE(principaluri, uri) +); + "); + break; + case 'sqlite' : + $pdo->exec(" + +CREATE TABLE calendarsubscriptions ( + id integer primary key asc, + uri text, + principaluri text, + source text, + displayname text, + refreshrate text, + calendarorder integer, + calendarcolor text, + striptodos bool, + stripalarms bool, + stripattachments bool, + lastmodified int +); + "); + + $pdo->exec("CREATE INDEX principaluri_uri ON calendarsubscriptions (principaluri, uri);"); + break; + + } + +} + +try { + $pdo->query("SELECT * FROM propertystorage LIMIT 1"); + + echo "'propertystorage' already exists. Assuming that this part of the\n"; + echo "upgrade was already completed.\n"; + +} catch (Exception $e) { + echo "Creating propertystorage table.\n"; + + switch($driver) { + + case 'mysql' : + $pdo->exec(" +CREATE TABLE propertystorage ( + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + path VARBINARY(1024) NOT NULL, + name VARBINARY(100) NOT NULL, + value MEDIUMBLOB +); + "); + $pdo->exec(" +CREATE UNIQUE INDEX path_property ON propertystorage (path(600), name(100)); + "); + break; + case 'sqlite' : + $pdo->exec(" +CREATE TABLE propertystorage ( + id integer primary key asc, + path TEXT, + name TEXT, + value TEXT +); + "); + $pdo->exec(" +CREATE UNIQUE INDEX path_property ON propertystorage (path, name); + "); + + break; + + } + +} + +echo "Upgrading cards table to 2.0 schema\n"; + +try { + + $create = false; + $row = $pdo->query("SELECT * FROM cards LIMIT 1")->fetch(); + if (!$row) { + $random = mt_rand(1000,9999); + echo "There was no data in the cards table, so we're re-creating it\n"; + echo "The old table will be renamed to cards_old$random, just in case.\n"; + + $create = true; + + switch($driver) { + case 'mysql' : + $pdo->exec("RENAME TABLE cards TO cards_old$random"); + break; + case 'sqlite' : + $pdo->exec("ALTER TABLE cards RENAME TO cards_old$random"); + break; + + } + } + +} catch (Exception $e) { + + echo "Exception while checking cards table. Assuming that the table does not yet exist.\n"; + echo "Debug: ", $e->getMessage(), "\n"; + $create = true; + +} + +if ($create) { + switch($driver) { + case 'mysql' : + $pdo->exec(" +CREATE TABLE cards ( + id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + addressbookid INT(11) UNSIGNED NOT NULL, + carddata MEDIUMBLOB, + uri VARCHAR(200), + lastmodified INT(11) UNSIGNED, + etag VARBINARY(32), + size INT(11) UNSIGNED NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + + "); + break; + + case 'sqlite' : + + $pdo->exec(" +CREATE TABLE cards ( + id integer primary key asc, + addressbookid integer, + carddata blob, + uri text, + lastmodified integer, + etag text, + size integer +); + "); + break; + + } +} else { + switch($driver) { + case 'mysql' : + $pdo->exec(" + ALTER TABLE cards + ADD etag VARBINARY(32), + ADD size INT(11) UNSIGNED NOT NULL; + "); + break; + + case 'sqlite' : + + $pdo->exec(" + ALTER TABLE cards ADD etag text; + ALTER TABLE cards ADD size integer; + "); + break; + + } + echo "Reading all old vcards and populating etag and size fields.\n"; + $result = $pdo->query('SELECT id, carddata FROM cards'); + $stmt = $pdo->prepare('UPDATE cards SET etag = ?, size = ? WHERE id = ?'); + while($row = $result->fetch(\PDO::FETCH_ASSOC)) { + $stmt->execute([ + md5($row['carddata']), + strlen($row['carddata']), + $row['id'] + ]); + } + + +} + +echo "Upgrade to 2.0 schema completed.\n"; diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/migrateto21.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/migrateto21.php new file mode 100755 index 0000000..9a5a315 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/migrateto21.php @@ -0,0 +1,180 @@ +#!/usr/bin/env php +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); +$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); + +$driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME); + +switch($driver) { + + case 'mysql' : + echo "Detected MySQL.\n"; + break; + case 'sqlite' : + echo "Detected SQLite.\n"; + break; + default : + echo "Error: unsupported driver: " . $driver . "\n"; + die(-1); +} + +echo "Upgrading 'calendarobjects'\n"; +$addUid = false; +try { + $result = $pdo->query('SELECT * FROM calendarobjects LIMIT 1'); + $row = $result->fetch(\PDO::FETCH_ASSOC); + + if (!$row) { + echo "No data in table. Going to try to add the uid field anyway.\n"; + $addUid = true; + } elseif (array_key_exists('uid', $row)) { + echo "uid field exists. Assuming that this part of the migration has\n"; + echo "Already been completed.\n"; + } else { + echo "2.0 schema detected.\n"; + $addUid = true; + } + +} catch (Exception $e) { + echo "Could not find a calendarobjects table. Skipping this part of the\n"; + echo "upgrade.\n"; +} + +if ($addUid) { + + switch($driver) { + case 'mysql' : + $pdo->exec('ALTER TABLE calendarobjects ADD uid VARCHAR(200)'); + break; + case 'sqlite' : + $pdo->exec('ALTER TABLE calendarobjects ADD uid TEXT'); + break; + } + + $result = $pdo->query('SELECT id, calendardata FROM calendarobjects'); + $stmt = $pdo->prepare('UPDATE calendarobjects SET uid = ? WHERE id = ?'); + $counter = 0; + + while($row = $result->fetch(\PDO::FETCH_ASSOC)) { + + try { + $vobj = \Sabre\VObject\Reader::read($row['calendardata']); + } catch (\Exception $e) { + echo "Warning! Item with id $row[id] could not be parsed!\n"; + continue; + } + $uid = null; + $item = $vobj->getBaseComponent(); + if (!isset($item->UID)) { + echo "Warning! Item with id $item[id] does NOT have a UID property and this is required.\n"; + continue; + } + $uid = (string)$item->UID; + $stmt->execute([$uid, $row['id']]); + $counter++; + + } + +} + +echo "Creating 'schedulingobjects'\n"; + +switch($driver) { + + case 'mysql' : + $pdo->exec('CREATE TABLE IF NOT EXISTS schedulingobjects +( + id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + principaluri VARCHAR(255), + calendardata MEDIUMBLOB, + uri VARCHAR(200), + lastmodified INT(11) UNSIGNED, + etag VARCHAR(32), + size INT(11) UNSIGNED NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + '); + break; + + + case 'sqlite' : + $pdo->exec('CREATE TABLE IF NOT EXISTS schedulingobjects ( + id integer primary key asc, + principaluri text, + calendardata blob, + uri text, + lastmodified integer, + etag text, + size integer +) +'); + break; + $pdo->exec(' + CREATE INDEX principaluri_uri ON calendarsubscriptions (principaluri, uri); + '); + break; +} + +echo "Done.\n"; + +echo "Upgrade to 2.1 schema completed.\n"; diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/migrateto30.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/migrateto30.php new file mode 100755 index 0000000..5c2179a --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/migrateto30.php @@ -0,0 +1,171 @@ +#!/usr/bin/env php +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); +$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); + +$driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME); + +switch($driver) { + + case 'mysql' : + echo "Detected MySQL.\n"; + break; + case 'sqlite' : + echo "Detected SQLite.\n"; + break; + default : + echo "Error: unsupported driver: " . $driver . "\n"; + die(-1); +} + +echo "Upgrading 'propertystorage'\n"; +$addValueType = false; +try { + $result = $pdo->query('SELECT * FROM propertystorage LIMIT 1'); + $row = $result->fetch(\PDO::FETCH_ASSOC); + + if (!$row) { + echo "No data in table. Going to re-create the table.\n"; + $random = mt_rand(1000,9999); + echo "Renaming propertystorage -> propertystorage_old$random and creating new table.\n"; + + switch($driver) { + + case 'mysql' : + $pdo->exec('RENAME TABLE propertystorage TO propertystorage_old' . $random); + $pdo->exec(' + CREATE TABLE propertystorage ( + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + path VARBINARY(1024) NOT NULL, + name VARBINARY(100) NOT NULL, + valuetype INT UNSIGNED, + value MEDIUMBLOB + ); + '); + $pdo->exec('CREATE UNIQUE INDEX path_property_' . $random . ' ON propertystorage (path(600), name(100));'); + break; + case 'sqlite' : + $pdo->exec('ALTER TABLE propertystorage RENAME TO propertystorage_old' . $random); + $pdo->exec(' +CREATE TABLE propertystorage ( + id integer primary key asc, + path text, + name text, + valuetype integer, + value blob +);'); + + $pdo->exec('CREATE UNIQUE INDEX path_property_' . $random . ' ON propertystorage (path, name);'); + break; + + } + } elseif (array_key_exists('valuetype', $row)) { + echo "valuetype field exists. Assuming that this part of the migration has\n"; + echo "Already been completed.\n"; + } else { + echo "2.1 schema detected. Going to perform upgrade.\n"; + $addValueType = true; + } + +} catch (Exception $e) { + echo "Could not find a propertystorage table. Skipping this part of the\n"; + echo "upgrade.\n"; + echo $e->getMessage(), "\n"; +} + +if ($addValueType) { + + switch($driver) { + case 'mysql' : + $pdo->exec('ALTER TABLE propertystorage ADD valuetype INT UNSIGNED'); + break; + case 'sqlite' : + $pdo->exec('ALTER TABLE propertystorage ADD valuetype INT'); + + break; + } + + $pdo->exec('UPDATE propertystorage SET valuetype = 1 WHERE valuetype IS NULL '); + +} + +echo "Migrating vcardurl\n"; + +$result = $pdo->query('SELECT id, uri, vcardurl FROM principals WHERE vcardurl IS NOT NULL'); +$stmt1 = $pdo->prepare('INSERT INTO propertystorage (path, name, valuetype, value) VALUES (?, ?, 3, ?)'); + +while($row = $result->fetch(\PDO::FETCH_ASSOC)) { + + // Inserting the new record + $stmt1->execute([ + 'addressbooks/' . basename($row['uri']), + '{http://calendarserver.org/ns/}me-card', + serialize(new Sabre\DAV\Xml\Property\Href($row['vcardurl'])) + ]); + + echo serialize(new Sabre\DAV\Xml\Property\Href($row['vcardurl'])); + +} + +echo "Done.\n"; +echo "Upgrade to 3.0 schema completed.\n"; diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/naturalselection b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/naturalselection new file mode 100755 index 0000000..52720e3 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/naturalselection @@ -0,0 +1,140 @@ +#!/usr/bin/env python + +# +# Copyright (c) 2009-2010 Evert Pot +# All rights reserved. +# http://www.rooftopsolutions.nl/ +# +# This utility is distributed along with SabreDAV +# license: http://sabre.io/license/ Modified BSD License + +import os +from optparse import OptionParser +import time + +def getfreespace(path): + stat = os.statvfs(path) + return stat.f_frsize * stat.f_bavail + +def getbytesleft(path,threshold): + return getfreespace(path)-threshold + +def run(cacheDir, threshold, sleep=5, simulate=False, min_erase = 0): + + bytes = getbytesleft(cacheDir,threshold) + if (bytes>0): + print "Bytes to go before we hit threshold:", bytes + else: + print "Threshold exceeded with:", -bytes, "bytes" + dir = os.listdir(cacheDir) + dir2 = [] + for file in dir: + path = cacheDir + '/' + file + dir2.append({ + "path" : path, + "atime": os.stat(path).st_atime, + "size" : os.stat(path).st_size + }) + + dir2.sort(lambda x,y: int(x["atime"]-y["atime"])) + + filesunlinked = 0 + gainedspace = 0 + + # Left is the amount of bytes that need to be freed up + # The default is the 'min_erase setting' + left = min_erase + + # If the min_erase setting is lower than the amount of bytes over + # the threshold, we use that number instead. + if left < -bytes : + left = -bytes + + print "Need to delete at least:", left; + + for file in dir2: + + # Only deleting files if we're not simulating + if not simulate: os.unlink(file["path"]) + left = int(left - file["size"]) + gainedspace = gainedspace + file["size"] + filesunlinked = filesunlinked + 1 + + if(left<0): + break + + print "%d files deleted (%d bytes)" % (filesunlinked, gainedspace) + + + time.sleep(sleep) + + + +def main(): + parser = OptionParser( + version="naturalselection v0.3", + description="Cache directory manager. Deletes cache entries based on accesstime and free space thresholds.\n" + + "This utility is distributed alongside SabreDAV.", + usage="usage: %prog [options] cacheDirectory", + ) + parser.add_option( + '-s', + dest="simulate", + action="store_true", + help="Don't actually make changes, but just simulate the behaviour", + ) + parser.add_option( + '-r','--runs', + help="How many times to check before exiting. -1 is infinite, which is the default", + type="int", + dest="runs", + default=-1 + ) + parser.add_option( + '-n','--interval', + help="Sleep time in seconds (default = 5)", + type="int", + dest="sleep", + default=5 + ) + parser.add_option( + '-l','--threshold', + help="Threshold in bytes (default = 10737418240, which is 10GB)", + type="int", + dest="threshold", + default=10737418240 + ) + parser.add_option( + '-m', '--min-erase', + help="Minimum number of bytes to erase when the threshold is reached. " + + "Setting this option higher will reduce the amount of times the cache directory will need to be scanned. " + + "(the default is 1073741824, which is 1GB.)", + type="int", + dest="min_erase", + default=1073741824 + ) + + options,args = parser.parse_args() + if len(args)<1: + parser.error("This utility requires at least 1 argument") + cacheDir = args[0] + + print "Natural Selection" + print "Cache directory:", cacheDir + free = getfreespace(cacheDir); + print "Current free disk space:", free + + runs = options.runs; + while runs!=0 : + run( + cacheDir, + sleep=options.sleep, + simulate=options.simulate, + threshold=options.threshold, + min_erase=options.min_erase + ) + if runs>0: + runs = runs - 1 + +if __name__ == '__main__' : + main() diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/sabredav b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/sabredav new file mode 100755 index 0000000..032371b --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/sabredav @@ -0,0 +1,2 @@ +#!/bin/sh +php -S 0.0.0.0:8080 `dirname $0`/sabredav.php diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/sabredav.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/sabredav.php new file mode 100755 index 0000000..34a674f --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/bin/sabredav.php @@ -0,0 +1,53 @@ +stream = fopen('php://stdout','w'); + + } + + function log($msg) { + fwrite($this->stream, $msg . "\n"); + } + +} + +$log = new CliLog(); + +if (php_sapi_name()!=='cli-server') { + die("This script is intended to run on the built-in php webserver"); +} + +// Finding composer + + +$paths = array( + __DIR__ . '/../vendor/autoload.php', + __DIR__ . '/../../../autoload.php', +); + +foreach($paths as $path) { + if (file_exists($path)) { + include $path; + break; + } +} + +use Sabre\DAV; + +// Root +$root = new DAV\FS\Directory(getcwd()); + +// Setting up server. +$server = new DAV\Server($root); + +// Browser plugin +$server->addPlugin(new DAV\Browser\Plugin()); + +$server->exec(); diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/composer.json b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/composer.json new file mode 100644 index 0000000..9994605 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/composer.json @@ -0,0 +1,66 @@ +{ + "name": "sabre/dav", + "type": "library", + "description": "WebDAV Framework for PHP", + "keywords": ["Framework", "WebDAV", "CalDAV", "CardDAV", "iCalendar"], + "homepage": "http://sabre.io/", + "license" : "BSD-3-Clause", + "authors": [ + { + "name": "Evert Pot", + "email": "me@evertpot.com", + "homepage" : "http://evertpot.com/", + "role" : "Developer" + } + ], + "require": { + "php": ">=5.4.1", + "sabre/vobject": "^3.3.4", + "sabre/event" : "~2.0", + "sabre/xml" : "~1.0", + "sabre/http" : "~4.0", + "sabre/uri" : "~1.0", + "ext-dom": "*", + "ext-pcre": "*", + "ext-spl": "*", + "ext-simplexml": "*", + "ext-mbstring" : "*", + "ext-ctype" : "*", + "ext-date" : "*", + "ext-iconv" : "*", + "lib-libxml" : ">=2.7.0" + }, + "require-dev" : { + "phpunit/phpunit" : "~4.2", + "evert/phpdoc-md" : "~0.1.0", + "sabre/cs" : "~0.0.2" + }, + "suggest" : { + "ext-curl" : "*", + "ext-pdo" : "*" + }, + "autoload": { + "psr-4" : { + "Sabre\\DAV\\" : "lib/DAV/", + "Sabre\\DAVACL\\" : "lib/DAVACL/", + "Sabre\\CalDAV\\" : "lib/CalDAV/", + "Sabre\\CardDAV\\" : "lib/CardDAV/" + } + }, + "support" : { + "forum" : "https://groups.google.com/group/sabredav-discuss", + "source" : "https://github.com/fruux/sabre-dav" + }, + "bin" : [ + "bin/sabredav", + "bin/naturalselection" + ], + "config" : { + "bin-dir" : "./bin" + }, + "extra" : { + "branch-alias": { + "dev-master": "3.0.0-dev" + } + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Backend/AbstractBackend.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Backend/AbstractBackend.php new file mode 100644 index 0000000..e1e5c36 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Backend/AbstractBackend.php @@ -0,0 +1,221 @@ +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(); + return $validator->validate($vObject, $filters); + + } + + /** + * 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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Backend/BackendInterface.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Backend/BackendInterface.php new file mode 100644 index 0000000..7513fb6 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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->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\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(); + } + + } + } + + 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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Backend/SchedulingSupport.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Backend/SchedulingSupport.php new file mode 100644 index 0000000..6ec0bf0 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Backend/SchedulingSupport.php @@ -0,0 +1,65 @@ +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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/CalendarHome.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/CalendarHome.php new file mode 100644 index 0000000..a53f829 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/CalendarObject.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/CalendarObject.php new file mode 100644 index 0000000..393ca4c --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/CalendarQueryValidator.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/CalendarQueryValidator.php new file mode 100644 index 0000000..f3c7524 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/CalendarRoot.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/CalendarRoot.php new file mode 100644 index 0000000..0ac50e4 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Exception/InvalidComponentType.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Exception/InvalidComponentType.php new file mode 100644 index 0000000..5ce8a93 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/ICSExportPlugin.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/ICSExportPlugin.php new file mode 100644 index 0000000..17aceab --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/ICSExportPlugin.php @@ -0,0 +1,361 @@ +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(); + unset($vtimezoneObj); + } else { + // Defaulting to UTC. + $calendarTimeZone = new DateTimeZone('UTC'); + } + + $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[] = $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; + + } + + /** + * 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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/ICalendar.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/ICalendar.php new file mode 100644 index 0000000..7cf4b12 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/sabre/CalDAV/Notifications/ICollection.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Notifications/ICollection.php similarity index 100% rename from src/serverlib/3rdparty/sabre/CalDAV/Notifications/ICollection.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Notifications/ICollection.php diff --git a/src/serverlib/3rdparty/sabre/CalDAV/Notifications/INode.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Notifications/INode.php similarity index 100% rename from src/serverlib/3rdparty/sabre/CalDAV/Notifications/INode.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Notifications/INode.php diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Notifications/Node.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Notifications/Node.php new file mode 100644 index 0000000..47e78d5 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Notifications/Node.php @@ -0,0 +1,193 @@ +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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Notifications/Plugin.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Notifications/Plugin.php new file mode 100644 index 0000000..546bf92 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Plugin.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Plugin.php new file mode 100644 index 0000000..50b0077 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Plugin.php @@ -0,0 +1,992 @@ +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. + * + * @param string $principalUrl + * @return string + */ + function getCalendarHomeForPrincipal($principalUrl) { + + // The default is a bit naive, but it can be overwritten. + list(, $nodeName) = Uri\split($principalUrl); + + return self::CALENDAR_ROOT . '/' . $nodeName; + + } + + /** + * 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 . '}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) . '/'; + 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->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(); + } + } + + $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(); + unset($vtimezoneObj); + } 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->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]; + + } + + } + + } + + 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->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(); + } + } + $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(); + } 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; + } + + } + + } + + + /** + * 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); + + $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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Principal/Collection.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Principal/Collection.php new file mode 100644 index 0000000..e19719a --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Principal/Collection.php @@ -0,0 +1,33 @@ +principalBackend, $principalInfo); + + } + +} diff --git a/src/serverlib/3rdparty/sabre/CalDAV/Principal/IProxyRead.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Principal/IProxyRead.php similarity index 100% rename from src/serverlib/3rdparty/sabre/CalDAV/Principal/IProxyRead.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Principal/IProxyRead.php diff --git a/src/serverlib/3rdparty/sabre/CalDAV/Principal/IProxyWrite.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Principal/IProxyWrite.php similarity index 100% rename from src/serverlib/3rdparty/sabre/CalDAV/Principal/IProxyWrite.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Principal/IProxyWrite.php diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Principal/ProxyRead.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Principal/ProxyRead.php new file mode 100644 index 0000000..93f0fe0 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Principal/ProxyRead.php @@ -0,0 +1,181 @@ +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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Principal/ProxyWrite.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Principal/ProxyWrite.php new file mode 100644 index 0000000..8124c05 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Principal/User.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Principal/User.php new file mode 100644 index 0000000..6e97e7c --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Schedule/IInbox.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Schedule/IInbox.php new file mode 100644 index 0000000..c9fd77d --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/sabre/CalDAV/Schedule/IOutbox.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Schedule/IOutbox.php similarity index 100% rename from src/serverlib/3rdparty/sabre/CalDAV/Schedule/IOutbox.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Schedule/IOutbox.php diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Schedule/ISchedulingObject.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Schedule/ISchedulingObject.php new file mode 100644 index 0000000..b37cb40 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Schedule/ISchedulingObject.php @@ -0,0 +1,13 @@ +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' => $this->getOwner(), + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}write-properties', + 'principal' => $this->getOwner(), + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}unbind', + 'principal' => $this->getOwner(), + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->getOwner() . '/calendar-proxy-read', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => $this->getOwner() . '/calendar-proxy-write', + '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']; + } + } + return $result; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Schedule/Outbox.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Schedule/Outbox.php new file mode 100644 index 0000000..dabaee2 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Schedule/Plugin.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Schedule/Plugin.php new file mode 100644 index 0000000..d13a309 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Schedule/Plugin.php @@ -0,0 +1,897 @@ +server = $server; + $server->on('method:POST', [$this, 'httpPost']); + $server->on('propFind', [$this, 'propFind']); + $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) return; + + $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); + $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); + $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); + + $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'; + + }); + + } + + /** + * 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); + + } + + /** + * 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); + + } 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 \DateTime $start + * @param \DateTime $end + * @param VObject\Component $request + * @return array + */ + protected function getFreeBusyForEmail($email, \DateTime $start, \DateTime $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', + '{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, + ]; + } + $homeSet = $result[0][200][$caldavNS . 'calendar-home-set']->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(); + } + + // 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); + + } + + $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); + + $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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Schedule/SchedulingObject.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Schedule/SchedulingObject.php new file mode 100644 index 0000000..a36646e --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/sabre/CalDAV/ShareableCalendar.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/ShareableCalendar.php similarity index 100% rename from src/serverlib/3rdparty/sabre/CalDAV/ShareableCalendar.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/ShareableCalendar.php diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/SharedCalendar.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/SharedCalendar.php new file mode 100644 index 0000000..7973eff --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/SharingPlugin.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/SharingPlugin.php new file mode 100644 index 0000000..5154fb1 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/sabre/CalDAV/Subscriptions/ISubscription.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Subscriptions/ISubscription.php similarity index 100% rename from src/serverlib/3rdparty/sabre/CalDAV/Subscriptions/ISubscription.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Subscriptions/ISubscription.php diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Subscriptions/Plugin.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Subscriptions/Plugin.php new file mode 100644 index 0000000..7abbfb1 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Subscriptions/Plugin.php @@ -0,0 +1,120 @@ +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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Subscriptions/Subscription.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Subscriptions/Subscription.php new file mode 100644 index 0000000..ee53da2 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Filter/CalendarData.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Filter/CalendarData.php new file mode 100644 index 0000000..9babcf1 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Filter/CompFilter.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Filter/CompFilter.php new file mode 100644 index 0000000..c9b27db --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Filter/ParamFilter.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Filter/ParamFilter.php new file mode 100644 index 0000000..eb7f564 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Filter/PropFilter.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Filter/PropFilter.php new file mode 100644 index 0000000..4c2e1b1 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Notification/Invite.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Notification/Invite.php new file mode 100644 index 0000000..7fb022e --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Notification/InviteReply.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Notification/InviteReply.php new file mode 100644 index 0000000..945323f --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Notification/NotificationInterface.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Notification/NotificationInterface.php new file mode 100644 index 0000000..1c08f12 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Property/AllowedSharingModes.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Property/AllowedSharingModes.php new file mode 100644 index 0000000..c2a2d56 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Property/EmailAddressSet.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Property/EmailAddressSet.php new file mode 100644 index 0000000..f577a99 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Property/Invite.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Property/Invite.php new file mode 100644 index 0000000..3ee0532 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Property/ScheduleCalendarTransp.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Property/ScheduleCalendarTransp.php new file mode 100644 index 0000000..4a253e0 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarComponentSet.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarComponentSet.php new file mode 100644 index 0000000..7a26e76 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarData.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCalendarData.php new file mode 100644 index 0000000..b0c407f --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCollationSet.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCollationSet.php new file mode 100644 index 0000000..71de25a --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Property/SupportedCollationSet.php @@ -0,0 +1,57 @@ +writeElement('{' . Plugin::NS_CALDAV . '}supported-collation', $collation); + } + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarMultiGetReport.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarMultiGetReport.php new file mode 100644 index 0000000..79b3bb3 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarQueryReport.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Request/CalendarQueryReport.php new file mode 100644 index 0000000..848a4dc --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Request/FreeBusyQueryReport.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Request/FreeBusyQueryReport.php new file mode 100644 index 0000000..e3b27d0 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Request/InviteReply.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Request/InviteReply.php new file mode 100644 index 0000000..ec62715 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Request/MkCalendar.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Request/MkCalendar.php new file mode 100644 index 0000000..7b745db --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Request/Share.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CalDAV/Xml/Request/Share.php new file mode 100644 index 0000000..dacc5dc --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/AddressBook.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/AddressBook.php new file mode 100644 index 0000000..70bec87 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/AddressBookHome.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/AddressBookHome.php new file mode 100644 index 0000000..ebc2518 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/AddressBookRoot.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/AddressBookRoot.php new file mode 100644 index 0000000..4a33df4 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/sabre/CardDAV/Backend/AbstractBackend.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/Backend/AbstractBackend.php similarity index 100% rename from src/serverlib/3rdparty/sabre/CardDAV/Backend/AbstractBackend.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/Backend/AbstractBackend.php diff --git a/src/serverlib/3rdparty/sabre/CardDAV/Backend/BackendInterface.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/Backend/BackendInterface.php similarity index 100% rename from src/serverlib/3rdparty/sabre/CardDAV/Backend/BackendInterface.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/Backend/BackendInterface.php diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/Backend/PDO.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/Backend/PDO.php new file mode 100644 index 0000000..040a5bb --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/Backend/PDO.php @@ -0,0 +1,545 @@ +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 void + */ + 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/src/serverlib/3rdparty/sabre/CardDAV/Backend/SyncSupport.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/Backend/SyncSupport.php similarity index 100% rename from src/serverlib/3rdparty/sabre/CardDAV/Backend/SyncSupport.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/Backend/SyncSupport.php diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/Card.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/Card.php new file mode 100644 index 0000000..8da6725 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/sabre/CardDAV/IAddressBook.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/IAddressBook.php similarity index 100% rename from src/serverlib/3rdparty/sabre/CardDAV/IAddressBook.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/IAddressBook.php diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/ICard.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/ICard.php new file mode 100644 index 0000000..a974cbd --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/ICard.php @@ -0,0 +1,19 @@ +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 + * @return bool + */ + function report($reportName, $dom) { + + 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; + } + + } + + + /** + * 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) { + + $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 = []; + 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) { + 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 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); + return $data->serialize(); + case 'vcard4' : + $data = $data->convert(VObject\Document::VCARD40); + return $data->serialize(); + case 'jcard' : + $data = $data->convert(VObject\Document::VCARD40); + return json_encode($data->jsonSerialize()); + + // @codeCoverageIgnoreStart + } + // @codeCoverageIgnoreEnd + + } + + /** + * 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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php new file mode 100644 index 0000000..b48abcb --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php @@ -0,0 +1,149 @@ +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. + $output .= + VObject\Reader::read($nodeData)->serialize(); + + } + + 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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/Xml/Filter/AddressData.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/Xml/Filter/AddressData.php new file mode 100644 index 0000000..34028db --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/Xml/Filter/ParamFilter.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/Xml/Filter/ParamFilter.php new file mode 100644 index 0000000..9646ae3 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/Xml/Filter/PropFilter.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/Xml/Filter/PropFilter.php new file mode 100644 index 0000000..c162da1 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedAddressData.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedAddressData.php new file mode 100644 index 0000000..6ff57b6 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedCollationSet.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/Xml/Property/SupportedCollationSet.php new file mode 100644 index 0000000..1fc0649 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookMultiGetReport.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookMultiGetReport.php new file mode 100644 index 0000000..c97c5eb --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookQueryReport.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/CardDAV/Xml/Request/AddressBookQueryReport.php new file mode 100644 index 0000000..a68ac58 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBasic.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractBasic.php new file mode 100644 index 0000000..4563b4e --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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($request); + if (!$userpass) { + return [false, "No 'Authorization: Basic' header found. Either the client didn't send one, or the server is mis-configured"]; + } + 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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractDigest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Auth/Backend/AbstractDigest.php new file mode 100644 index 0000000..4d72e6e --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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 mis-configured"]; + } + + $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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Auth/Backend/Apache.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Auth/Backend/Apache.php new file mode 100644 index 0000000..e203d26 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Auth/Backend/BackendInterface.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Auth/Backend/BackendInterface.php new file mode 100644 index 0000000..0fb2210 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Auth/Backend/BasicCallBack.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Auth/Backend/BasicCallBack.php new file mode 100644 index 0000000..7ad8f48 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Auth/Backend/File.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Auth/Backend/File.php new file mode 100644 index 0000000..6756e68 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Auth/Backend/PDO.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Auth/Backend/PDO.php new file mode 100644 index 0000000..76ad893 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Auth/Plugin.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Auth/Plugin.php new file mode 100644 index 0000000..818d8a4 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/GuessContentType.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/GuessContentType.php new file mode 100644 index 0000000..01cddc2 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/HtmlOutput.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/HtmlOutput.php new file mode 100644 index 0000000..f4be6b3 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/MapGetToPropFind.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/MapGetToPropFind.php new file mode 100644 index 0000000..38ee63b --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/Plugin.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/Plugin.php new file mode 100644 index 0000000..4a6299b --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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 ($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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/PropFindAll.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/PropFindAll.php new file mode 100644 index 0000000..1ac4396 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/sabre/DAV/Browser/assets/favicon.ico b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/assets/favicon.ico similarity index 100% rename from src/serverlib/3rdparty/sabre/DAV/Browser/assets/favicon.ico rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/assets/favicon.ico diff --git a/src/serverlib/3rdparty/sabre/DAV/Browser/assets/openiconic/ICON-LICENSE b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/ICON-LICENSE similarity index 100% rename from src/serverlib/3rdparty/sabre/DAV/Browser/assets/openiconic/ICON-LICENSE rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/ICON-LICENSE diff --git a/src/serverlib/3rdparty/sabre/DAV/Browser/assets/openiconic/open-iconic.css b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.css similarity index 100% rename from src/serverlib/3rdparty/sabre/DAV/Browser/assets/openiconic/open-iconic.css rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.css diff --git a/src/serverlib/3rdparty/sabre/DAV/Browser/assets/openiconic/open-iconic.eot b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.eot similarity index 100% rename from src/serverlib/3rdparty/sabre/DAV/Browser/assets/openiconic/open-iconic.eot rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.eot diff --git a/src/serverlib/3rdparty/sabre/DAV/Browser/assets/openiconic/open-iconic.otf b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.otf similarity index 100% rename from src/serverlib/3rdparty/sabre/DAV/Browser/assets/openiconic/open-iconic.otf rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.otf diff --git a/src/serverlib/3rdparty/sabre/DAV/Browser/assets/openiconic/open-iconic.svg b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.svg similarity index 100% rename from src/serverlib/3rdparty/sabre/DAV/Browser/assets/openiconic/open-iconic.svg rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.svg diff --git a/src/serverlib/3rdparty/sabre/DAV/Browser/assets/openiconic/open-iconic.ttf b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.ttf similarity index 100% rename from src/serverlib/3rdparty/sabre/DAV/Browser/assets/openiconic/open-iconic.ttf rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.ttf diff --git a/src/serverlib/3rdparty/sabre/DAV/Browser/assets/openiconic/open-iconic.woff b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.woff similarity index 100% rename from src/serverlib/3rdparty/sabre/DAV/Browser/assets/openiconic/open-iconic.woff rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/assets/openiconic/open-iconic.woff diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.css b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.css new file mode 100644 index 0000000..c9ab2c7 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/sabre/DAV/Browser/assets/sabredav.png b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.png similarity index 100% rename from src/serverlib/3rdparty/sabre/DAV/Browser/assets/sabredav.png rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Browser/assets/sabredav.png diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Client.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Client.php new file mode 100644 index 0000000..741ceee --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Client.php @@ -0,0 +1,417 @@ +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->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 Exception('HTTP error: ' . $response->getStatus()); + } + + $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 void + */ + 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); + $this->send($request); + } + + /** + * 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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Collection.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Collection.php new file mode 100644 index 0000000..a46bcc3 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/CorePlugin.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/CorePlugin.php new file mode 100644 index 0000000..4cb1370 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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, 0); + + 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 ($copyInfo['destinationExists']) { + if (!$this->server->emit('beforeUnbind', [$copyInfo['destination']])) return false; + $this->server->tree->delete($copyInfo['destination']); + + } + if (!$this->server->emit('beforeBind', [$copyInfo['destination']])) return false; + $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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception.php new file mode 100644 index 0000000..14f5bab --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/Forbidden.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/Forbidden.php new file mode 100644 index 0000000..77df7ca --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/Forbidden.php @@ -0,0 +1,29 @@ +ownerDocument->createElementNS('DAV:', 'd:valid-resourcetype'); + $errorNode->appendChild($error); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/InvalidSyncToken.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/InvalidSyncToken.php new file mode 100644 index 0000000..51a253b --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/sabre/DAV/Exception/LengthRequired.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/LengthRequired.php similarity index 100% rename from src/serverlib/3rdparty/sabre/DAV/Exception/LengthRequired.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/LengthRequired.php diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/LockTokenMatchesRequestUri.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/LockTokenMatchesRequestUri.php new file mode 100644 index 0000000..5f8c318 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/LockTokenMatchesRequestUri.php @@ -0,0 +1,41 @@ +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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/Locked.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/Locked.php new file mode 100644 index 0000000..8176db4 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/MethodNotAllowed.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/MethodNotAllowed.php new file mode 100644 index 0000000..30c1c25 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/MethodNotAllowed.php @@ -0,0 +1,47 @@ +getAllowedMethods($server->getRequestUri()); + + return [ + 'Allow' => strtoupper(implode(', ', $methods)), + ]; + + } + +} diff --git a/src/serverlib/3rdparty/sabre/DAV/Exception/NotAuthenticated.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/NotAuthenticated.php similarity index 100% rename from src/serverlib/3rdparty/sabre/DAV/Exception/NotAuthenticated.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/NotAuthenticated.php diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/NotFound.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/NotFound.php new file mode 100644 index 0000000..b039744 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/NotFound.php @@ -0,0 +1,29 @@ +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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/ReportNotSupported.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/ReportNotSupported.php new file mode 100644 index 0000000..a836956 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/ReportNotSupported.php @@ -0,0 +1,32 @@ +ownerDocument->createElementNS('DAV:', 'd:supported-report'); + $errorNode->appendChild($error); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/RequestedRangeNotSatisfiable.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/RequestedRangeNotSatisfiable.php new file mode 100644 index 0000000..c8ccfc0 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/RequestedRangeNotSatisfiable.php @@ -0,0 +1,30 @@ +ownerDocument->createElementNS('DAV:', 'd:number-of-matches-within-limits'); + $errorNode->appendChild($error); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/UnsupportedMediaType.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Exception/UnsupportedMediaType.php new file mode 100644 index 0000000..f3d9284 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/FS/File.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/FS/File.php new file mode 100644 index 0000000..4fc5af0 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/FS/Node.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/FS/Node.php new file mode 100644 index 0000000..831c119 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/FSExt/Directory.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/FSExt/Directory.php new file mode 100644 index 0000000..648079e --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/FSExt/File.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/FSExt/File.php new file mode 100644 index 0000000..eb5ae19 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/File.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/File.php new file mode 100644 index 0000000..e0a0391 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Locks/Backend/PDO.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Locks/Backend/PDO.php new file mode 100644 index 0000000..a8a27bd --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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) > CAST(? AS UNSIGNED INTEGER)) 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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Locks/LockInfo.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Locks/LockInfo.php new file mode 100644 index 0000000..2c8cca0 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/MkCol.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/MkCol.php new file mode 100644 index 0000000..c790554 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Mount/Plugin.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Mount/Plugin.php new file mode 100644 index 0000000..8e06acb --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Node.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Node.php new file mode 100644 index 0000000..ba270e8 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/PropFind.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/PropFind.php new file mode 100644 index 0000000..8ae6b6c --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/PropPatch.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/PropPatch.php new file mode 100644 index 0000000..d778988 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/PropPatch.php @@ -0,0 +1,366 @@ +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. + * + * @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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/BackendInterface.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/BackendInterface.php new file mode 100644 index 0000000..31ecafd --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/PDO.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/PDO.php new file mode 100644 index 0000000..7be5af9 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/PropertyStorage/Backend/PDO.php @@ -0,0 +1,210 @@ +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 propertystorage 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 propertystorage (path, name, valuetype, value) VALUES (?, ?, ?, ?)"); + $deleteStmt = $this->pdo->prepare("DELETE FROM propertystorage 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 propertystorage 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 propertystorage WHERE path = ? OR path LIKE ?'); + $select->execute([$source, $source . '/%']); + + $update = $this->pdo->prepare('UPDATE propertystorage 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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/PropertyStorage/Plugin.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/PropertyStorage/Plugin.php new file mode 100644 index 0000000..0c28b78 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Server.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Server.php new file mode 100644 index 0000000..da467c8 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Server.php @@ -0,0 +1,1611 @@ + '{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])) { + // Unsupported method + throw new Exception\NotImplemented('There was no handler found for this "' . $method . '" method'); + } + } + + if (!$this->emit('afterMethod:' . $method, [$request, $response])) return; + if (!$this->emit('afterMethod', [$request, $response])) return; + + 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. + * + * @param string $path + * @param array $propertyNames + */ + 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 + */ + 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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/ServerPlugin.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/ServerPlugin.php new file mode 100644 index 0000000..b2c468a --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/ServerPlugin.php @@ -0,0 +1,110 @@ + $this->getPluginName(), + 'description' => null, + 'link' => null, + ]; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/SimpleCollection.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/SimpleCollection.php new file mode 100644 index 0000000..998cfcb --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/SimpleFile.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/SimpleFile.php new file mode 100644 index 0000000..bcad786 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/StringUtil.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/StringUtil.php new file mode 100644 index 0000000..10eeceb --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Sync/Plugin.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Sync/Plugin.php new file mode 100644 index 0000000..4a141c7 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/TemporaryFileFilterPlugin.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/TemporaryFileFilterPlugin.php new file mode 100644 index 0000000..a4f15f3 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/TemporaryFileFilterPlugin.php @@ -0,0 +1,294 @@ +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 + * @return bool + */ + 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; + + } + + /** + * 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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Tree.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Tree.php new file mode 100644 index 0000000..4563f7c --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/UUIDUtil.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/UUIDUtil.php new file mode 100644 index 0000000..177adaf --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Element/Response.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Element/Response.php new file mode 100644 index 0000000..a19e8e9 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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. There are 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 singlular 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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Property/Complex.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Property/Complex.php new file mode 100644 index 0000000..1d92020 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Property/GetLastModified.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Property/GetLastModified.php new file mode 100644 index 0000000..2db4726 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Property/Href.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Property/Href.php new file mode 100644 index 0000000..538e98d --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Property/LockDiscovery.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Property/LockDiscovery.php new file mode 100644 index 0000000..f4b6922 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Property/ResourceType.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Property/ResourceType.php new file mode 100644 index 0000000..3028883 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedLock.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedLock.php new file mode 100644 index 0000000..f6d01aa --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedMethodSet.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedMethodSet.php new file mode 100644 index 0000000..56b418d --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedReportSet.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Property/SupportedReportSet.php new file mode 100644 index 0000000..ebf2730 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Request/Lock.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Request/Lock.php new file mode 100644 index 0000000..76df98d --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Request/MkCol.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Request/MkCol.php new file mode 100644 index 0000000..5db2390 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Request/PropFind.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Request/PropFind.php new file mode 100644 index 0000000..ad3ad7c --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Request/PropPatch.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Request/PropPatch.php new file mode 100644 index 0000000..07a05f8 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Request/SyncCollectionReport.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Request/SyncCollectionReport.php new file mode 100644 index 0000000..3092ada --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Response/MultiStatus.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Response/MultiStatus.php new file mode 100644 index 0000000..16a3d4a --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Service.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAV/Xml/Service.php new file mode 100644 index 0000000..f41ed98 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/AbstractPrincipalCollection.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/AbstractPrincipalCollection.php new file mode 100644 index 0000000..460f789 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Exception/AceConflict.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Exception/AceConflict.php new file mode 100644 index 0000000..22450b4 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Exception/NeedPrivileges.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Exception/NeedPrivileges.php new file mode 100644 index 0000000..5624fd2 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Exception/NoAbstract.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Exception/NoAbstract.php new file mode 100644 index 0000000..a2363b1 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Exception/NotRecognizedPrincipal.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Exception/NotRecognizedPrincipal.php new file mode 100644 index 0000000..4349bf1 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Exception/NotSupportedPrivilege.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Exception/NotSupportedPrivilege.php new file mode 100644 index 0000000..73b8119 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/FS/Collection.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/FS/Collection.php new file mode 100644 index 0000000..5fab476 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/FS/File.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/FS/File.php new file mode 100644 index 0000000..0d54952 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/FS/HomeCollection.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/FS/HomeCollection.php new file mode 100644 index 0000000..c276167 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/IACL.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/IACL.php new file mode 100644 index 0000000..81908d0 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/IACL.php @@ -0,0 +1,75 @@ + '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 + * @return bool + */ + function report($reportName, $report) { + + 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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Principal.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Principal.php new file mode 100644 index 0000000..16375d3 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/AbstractBackend.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/AbstractBackend.php new file mode 100644 index 0000000..9bf9ba4 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/sabre/DAVACL/PrincipalBackend/BackendInterface.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/BackendInterface.php similarity index 100% rename from src/serverlib/3rdparty/sabre/DAVACL/PrincipalBackend/BackendInterface.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/BackendInterface.php diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/CreatePrincipalSupport.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/CreatePrincipalSupport.php new file mode 100644 index 0000000..e354a69 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/PrincipalBackend/CreatePrincipalSupport.php @@ -0,0 +1,30 @@ + [ + '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') { + + $query = 'SELECT uri FROM ' . $this->tableName . ' WHERE 1=1 '; + $values = []; + 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 []; + + } + + } + $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; + + } + + /** + * 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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/PrincipalCollection.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/PrincipalCollection.php new file mode 100644 index 0000000..54911e7 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Xml/Property/Acl.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Xml/Property/Acl.php new file mode 100644 index 0000000..9f5e40d --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Xml/Property/AclRestrictions.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Xml/Property/AclRestrictions.php new file mode 100644 index 0000000..f669cc5 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Xml/Property/CurrentUserPrivilegeSet.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Xml/Property/CurrentUserPrivilegeSet.php new file mode 100644 index 0000000..0a95eb2 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Xml/Property/Principal.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Xml/Property/Principal.php new file mode 100644 index 0000000..d32249d --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Xml/Property/SupportedPrivilegeSet.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Xml/Property/SupportedPrivilegeSet.php new file mode 100644 index 0000000..7198946 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Xml/Request/ExpandPropertyReport.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Xml/Request/ExpandPropertyReport.php new file mode 100644 index 0000000..3f535e3 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalPropertySearchReport.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalPropertySearchReport.php new file mode 100644 index 0000000..1e7aa44 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalSearchPropertySetReport.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/lib/DAVACL/Xml/Request/PrincipalSearchPropertySetReport.php new file mode 100644 index 0000000..ade157b --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/AbstractPDOTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/AbstractPDOTest.php new file mode 100644 index 0000000..9dcfdea --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/AbstractPDOTest.php @@ -0,0 +1,883 @@ +pdo); + $this->assertTrue($backend instanceof PDO); + + } + + /** + * @depends testConstruct + */ + function testGetCalendarsForUserNoCalendars() { + + $backend = new PDO($this->pdo); + $calendars = $backend->getCalendarsForUser('principals/user2'); + $this->assertEquals(array(),$calendars); + + } + + /** + * @depends testConstruct + */ + function testCreateCalendarAndFetch() { + + $backend = new PDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2','somerandomid',array( + '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set' => new CalDAV\Xml\Property\SupportedCalendarComponentSet(array('VEVENT')), + '{DAV:}displayname' => 'Hello!', + '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp' => new CalDAV\Xml\Property\ScheduleCalendarTransp('transparent'), + )); + $calendars = $backend->getCalendarsForUser('principals/user2'); + + $elementCheck = array( + 'id' => $returnedId, + 'uri' => 'somerandomid', + '{DAV:}displayname' => 'Hello!', + '{urn:ietf:params:xml:ns:caldav}calendar-description' => '', + '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp' => new CalDAV\Xml\Property\ScheduleCalendarTransp('transparent'), + ); + + $this->assertInternalType('array',$calendars); + $this->assertEquals(1,count($calendars)); + + foreach($elementCheck as $name=>$value) { + + $this->assertArrayHasKey($name, $calendars[0]); + $this->assertEquals($value,$calendars[0][$name]); + + } + + } + + /** + * @depends testConstruct + */ + function testUpdateCalendarAndFetch() { + + $backend = new PDO($this->pdo); + + //Creating a new calendar + $newId = $backend->createCalendar('principals/user2','somerandomid',array()); + + $propPatch = new PropPatch([ + '{DAV:}displayname' => 'myCalendar', + '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp' => new CalDAV\Xml\Property\ScheduleCalendarTransp('transparent'), + ]); + + // Updating the calendar + $backend->updateCalendar($newId, $propPatch); + $result = $propPatch->commit(); + + // Verifying the result of the update + $this->assertTrue($result); + + // Fetching all calendars from this user + $calendars = $backend->getCalendarsForUser('principals/user2'); + + // Checking if all the information is still correct + $elementCheck = array( + 'id' => $newId, + 'uri' => 'somerandomid', + '{DAV:}displayname' => 'myCalendar', + '{urn:ietf:params:xml:ns:caldav}calendar-description' => '', + '{urn:ietf:params:xml:ns:caldav}calendar-timezone' => '', + '{http://calendarserver.org/ns/}getctag' => 'http://sabre.io/ns/sync/2', + '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp' => new CalDAV\Xml\Property\ScheduleCalendarTransp('transparent'), + ); + + $this->assertInternalType('array',$calendars); + $this->assertEquals(1,count($calendars)); + + foreach($elementCheck as $name=>$value) { + + $this->assertArrayHasKey($name, $calendars[0]); + $this->assertEquals($value,$calendars[0][$name]); + + } + + } + + /** + * @depends testUpdateCalendarAndFetch + */ + function testUpdateCalendarUnknownProperty() { + + $backend = new PDO($this->pdo); + + //Creating a new calendar + $newId = $backend->createCalendar('principals/user2','somerandomid',array()); + + $propPatch = new PropPatch([ + '{DAV:}displayname' => 'myCalendar', + '{DAV:}yourmom' => 'wittycomment', + ]); + + // Updating the calendar + $backend->updateCalendar($newId, $propPatch); + $propPatch->commit(); + + // Verifying the result of the update + $this->assertEquals([ + '{DAV:}yourmom' => 403, + '{DAV:}displayname' => 424, + ], $propPatch->getResult()); + + } + + /** + * @depends testCreateCalendarAndFetch + */ + function testDeleteCalendar() { + + $backend = new PDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2','somerandomid',array( + '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set' => new CalDAV\Xml\Property\SupportedCalendarComponentSet(array('VEVENT')), + '{DAV:}displayname' => 'Hello!', + )); + + $backend->deleteCalendar($returnedId); + + $calendars = $backend->getCalendarsForUser('principals/user2'); + $this->assertEquals(array(),$calendars); + + } + + /** + * @depends testCreateCalendarAndFetch + * @expectedException \Sabre\DAV\Exception + */ + function testCreateCalendarIncorrectComponentSet() {; + + $backend = new PDO($this->pdo); + + //Creating a new calendar + $newId = $backend->createCalendar('principals/user2','somerandomid',array( + '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set' => 'blabla', + )); + + } + + function testCreateCalendarObject() { + + $backend = new PDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2','somerandomid',array()); + + $object = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE:20120101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; + + $backend->createCalendarObject($returnedId, 'random-id', $object); + + $result = $this->pdo->query('SELECT etag, size, calendardata, firstoccurence, lastoccurence, componenttype FROM calendarobjects WHERE uri = "random-id"'); + $this->assertEquals(array( + 'etag' => md5($object), + 'size' => strlen($object), + 'calendardata' => $object, + 'firstoccurence' => strtotime('20120101'), + 'lastoccurence' => strtotime('20120101')+(3600*24), + 'componenttype' => 'VEVENT', + ), $result->fetch(\PDO::FETCH_ASSOC)); + + } + function testGetMultipleObjects() { + + $backend = new PDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2','somerandomid',array()); + + $object = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE:20120101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; + + $backend->createCalendarObject($returnedId, 'id-1', $object); + $backend->createCalendarObject($returnedId, 'id-2', $object); + + $check = [ + [ + 'id' => 1, + 'etag' => '"' . md5($object) . '"', + 'uri' => 'id-1', + 'size' => strlen($object), + 'calendardata' => $object, + 'lastmodified' => null, + 'calendarid' => $returnedId, + ], + [ + 'id' => 2, + 'etag' => '"' . md5($object) . '"', + 'uri' => 'id-2', + 'size' => strlen($object), + 'calendardata' => $object, + 'lastmodified' => null, + 'calendarid' => $returnedId, + ], + ]; + + $result = $backend->getMultipleCalendarObjects($returnedId, [ 'id-1', 'id-2' ]); + + foreach($check as $index => $props) { + + foreach($props as $key=>$value) { + + if ($key!=='lastmodified') { + $this->assertEquals($value, $result[$index][$key]); + } else { + $this->assertTrue(isset($result[$index][$key])); + } + + } + + } + + } + + /** + * @expectedException Sabre\DAV\Exception\BadRequest + * @depends testCreateCalendarObject + */ + function testCreateCalendarObjectNoComponent() { + + $backend = new PDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2','somerandomid',array()); + + $object = "BEGIN:VCALENDAR\r\nBEGIN:VTIMEZONE\r\nEND:VTIMEZONE\r\nEND:VCALENDAR\r\n"; + + $backend->createCalendarObject($returnedId, 'random-id', $object); + + } + + /** + * @depends testCreateCalendarObject + */ + function testCreateCalendarObjectDuration() { + + $backend = new PDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2','somerandomid',array()); + + $object = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE:20120101\r\nDURATION:P2D\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; + + $backend->createCalendarObject($returnedId, 'random-id', $object); + + $result = $this->pdo->query('SELECT etag, size, calendardata, firstoccurence, lastoccurence, componenttype FROM calendarobjects WHERE uri = "random-id"'); + $this->assertEquals(array( + 'etag' => md5($object), + 'size' => strlen($object), + 'calendardata' => $object, + 'firstoccurence' => strtotime('20120101'), + 'lastoccurence' => strtotime('20120101')+(3600*48), + 'componenttype' => 'VEVENT', + ), $result->fetch(\PDO::FETCH_ASSOC)); + + } + + /** + * @depends testCreateCalendarObject + */ + function testCreateCalendarObjectNoDTEND() { + + $backend = new PDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2','somerandomid',array()); + + $object = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE-TIME:20120101T100000Z\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; + + $backend->createCalendarObject($returnedId, 'random-id', $object); + + $result = $this->pdo->query('SELECT etag, size, calendardata, firstoccurence, lastoccurence, componenttype FROM calendarobjects WHERE uri = "random-id"'); + $this->assertEquals(array( + 'etag' => md5($object), + 'size' => strlen($object), + 'calendardata' => $object, + 'firstoccurence' => strtotime('2012-01-01 10:00:00'), + 'lastoccurence' => strtotime('2012-01-01 10:00:00'), + 'componenttype' => 'VEVENT', + ), $result->fetch(\PDO::FETCH_ASSOC)); + + } + + /** + * @depends testCreateCalendarObject + */ + function testCreateCalendarObjectWithDTEND() { + + $backend = new PDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2','somerandomid',array()); + + $object = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE-TIME:20120101T100000Z\r\nDTEND:20120101T110000Z\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; + + $backend->createCalendarObject($returnedId, 'random-id', $object); + + $result = $this->pdo->query('SELECT etag, size, calendardata, firstoccurence, lastoccurence, componenttype FROM calendarobjects WHERE uri = "random-id"'); + $this->assertEquals(array( + 'etag' => md5($object), + 'size' => strlen($object), + 'calendardata' => $object, + 'firstoccurence' => strtotime('2012-01-01 10:00:00'), + 'lastoccurence' => strtotime('2012-01-01 11:00:00'), + 'componenttype' => 'VEVENT', + ), $result->fetch(\PDO::FETCH_ASSOC)); + + } + + /** + * @depends testCreateCalendarObject + */ + function testCreateCalendarObjectInfiniteReccurence() { + + $backend = new PDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2','somerandomid',array()); + + $object = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE-TIME:20120101T100000Z\r\nRRULE:FREQ=DAILY\r\nUID:foo\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; + + $backend->createCalendarObject($returnedId, 'random-id', $object); + + $result = $this->pdo->query('SELECT etag, size, calendardata, firstoccurence, lastoccurence, componenttype FROM calendarobjects WHERE uri = "random-id"'); + $this->assertEquals(array( + 'etag' => md5($object), + 'size' => strlen($object), + 'calendardata' => $object, + 'firstoccurence' => strtotime('2012-01-01 10:00:00'), + 'lastoccurence' => strtotime(PDO::MAX_DATE), + 'componenttype' => 'VEVENT', + ), $result->fetch(\PDO::FETCH_ASSOC)); + + } + + /** + * @depends testCreateCalendarObject + */ + function testCreateCalendarObjectEndingReccurence() { + + $backend = new PDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2','somerandomid',array()); + + $object = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE-TIME:20120101T100000Z\r\nDTEND;VALUE=DATE-TIME:20120101T110000Z\r\nUID:foo\r\nRRULE:FREQ=DAILY;COUNT=1000\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; + + $backend->createCalendarObject($returnedId, 'random-id', $object); + + $result = $this->pdo->query('SELECT etag, size, calendardata, firstoccurence, lastoccurence, componenttype FROM calendarobjects WHERE uri = "random-id"'); + $this->assertEquals(array( + 'etag' => md5($object), + 'size' => strlen($object), + 'calendardata' => $object, + 'firstoccurence' => strtotime('2012-01-01 10:00:00'), + 'lastoccurence' => strtotime('2012-01-01 11:00:00') + (3600 * 24 * 999), + 'componenttype' => 'VEVENT', + ), $result->fetch(\PDO::FETCH_ASSOC)); + + } + + /** + * @depends testCreateCalendarObject + */ + function testCreateCalendarObjectTask() { + + $backend = new PDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2','somerandomid',array()); + + $object = "BEGIN:VCALENDAR\r\nBEGIN:VTODO\r\nDUE;VALUE=DATE-TIME:20120101T100000Z\r\nEND:VTODO\r\nEND:VCALENDAR\r\n"; + + $backend->createCalendarObject($returnedId, 'random-id', $object); + + $result = $this->pdo->query('SELECT etag, size, calendardata, firstoccurence, lastoccurence, componenttype FROM calendarobjects WHERE uri = "random-id"'); + $this->assertEquals(array( + 'etag' => md5($object), + 'size' => strlen($object), + 'calendardata' => $object, + 'firstoccurence' => null, + 'lastoccurence' => null, + 'componenttype' => 'VTODO', + ), $result->fetch(\PDO::FETCH_ASSOC)); + + } + + /** + * @depends testCreateCalendarObject + */ + function testGetCalendarObjects() { + + $backend = new PDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2','somerandomid',array()); + + $object = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE:20120101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; + $backend->createCalendarObject($returnedId, 'random-id', $object); + + $data = $backend->getCalendarObjects($returnedId,'random-id'); + + $this->assertEquals(1, count($data)); + $data = $data[0]; + + $this->assertEquals($returnedId, $data['calendarid']); + $this->assertEquals('random-id', $data['uri']); + $this->assertEquals(strlen($object),$data['size']); + + + } + /** + * @depends testCreateCalendarObject + */ + function testGetCalendarObjectByUID() { + + $backend = new PDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2','somerandomid',[]); + + $object = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nUID:foo\r\nDTSTART;VALUE=DATE:20120101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; + $backend->createCalendarObject($returnedId, 'random-id', $object); + + $this->assertNull( + $backend->getCalendarObjectByUID('principals/user2', 'bar') + ); + $this->assertEquals( + 'somerandomid/random-id', + $backend->getCalendarObjectByUID('principals/user2', 'foo') + ); + + } + + /** + * @depends testCreateCalendarObject + */ + function testUpdateCalendarObject() { + + $backend = new PDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2','somerandomid',array()); + + $object = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE:20120101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; + $object2 = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE:20130101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; + $backend->createCalendarObject($returnedId, 'random-id', $object); + $backend->updateCalendarObject($returnedId, 'random-id', $object2); + + $data = $backend->getCalendarObject($returnedId,'random-id'); + + $this->assertEquals($object2, $data['calendardata']); + $this->assertEquals($returnedId, $data['calendarid']); + $this->assertEquals('random-id', $data['uri']); + + + } + + /** + * @depends testCreateCalendarObject + */ + function testDeleteCalendarObject() { + + $backend = new PDO($this->pdo); + $returnedId = $backend->createCalendar('principals/user2','somerandomid',array()); + + $object = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE:20120101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; + $backend->createCalendarObject($returnedId, 'random-id', $object); + $backend->deleteCalendarObject($returnedId, 'random-id'); + + $data = $backend->getCalendarObject($returnedId,'random-id'); + $this->assertNull($data); + + } + + function testCalendarQueryNoResult() { + + $abstract = new PDO($this->pdo); + $filters = array( + 'name' => 'VCALENDAR', + 'comp-filters' => array( + array( + 'name' => 'VJOURNAL', + 'comp-filters' => array(), + 'prop-filters' => array(), + 'is-not-defined' => false, + 'time-range' => null, + ), + ), + 'prop-filters' => array(), + 'is-not-defined' => false, + 'time-range' => null, + ); + + $this->assertEquals(array( + ), $abstract->calendarQuery(1, $filters)); + + } + + function testCalendarQueryTodo() { + + $backend = new PDO($this->pdo); + $backend->createCalendarObject(1, "todo", "BEGIN:VCALENDAR\r\nBEGIN:VTODO\r\nEND:VTODO\r\nEND:VCALENDAR\r\n"); + $backend->createCalendarObject(1, "event", "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART:20120101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"); + + $filters = array( + 'name' => 'VCALENDAR', + 'comp-filters' => array( + array( + 'name' => 'VTODO', + 'comp-filters' => array(), + 'prop-filters' => array(), + 'is-not-defined' => false, + 'time-range' => null, + ), + ), + 'prop-filters' => array(), + 'is-not-defined' => false, + 'time-range' => null, + ); + + $this->assertEquals(array( + "todo", + ), $backend->calendarQuery(1, $filters)); + + } + function testCalendarQueryTodoNotMatch() { + + $backend = new PDO($this->pdo); + $backend->createCalendarObject(1, "todo", "BEGIN:VCALENDAR\r\nBEGIN:VTODO\r\nEND:VTODO\r\nEND:VCALENDAR\r\n"); + $backend->createCalendarObject(1, "event", "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART:20120101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"); + + $filters = array( + 'name' => 'VCALENDAR', + 'comp-filters' => array( + array( + 'name' => 'VTODO', + 'comp-filters' => array(), + 'prop-filters' => array( + array( + 'name' => 'summary', + 'text-match' => null, + 'time-range' => null, + 'param-filters' => array(), + 'is-not-defined' => false, + ), + ), + 'is-not-defined' => false, + 'time-range' => null, + ), + ), + 'prop-filters' => array(), + 'is-not-defined' => false, + 'time-range' => null, + ); + + $this->assertEquals(array( + ), $backend->calendarQuery(1, $filters)); + + } + + function testCalendarQueryNoFilter() { + + $backend = new PDO($this->pdo); + $backend->createCalendarObject(1, "todo", "BEGIN:VCALENDAR\r\nBEGIN:VTODO\r\nEND:VTODO\r\nEND:VCALENDAR\r\n"); + $backend->createCalendarObject(1, "event", "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART:20120101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"); + + $filters = array( + 'name' => 'VCALENDAR', + 'comp-filters' => array(), + 'prop-filters' => array(), + 'is-not-defined' => false, + 'time-range' => null, + ); + + $result = $backend->calendarQuery(1, $filters); + $this->assertTrue(in_array('todo', $result)); + $this->assertTrue(in_array('event', $result)); + + } + + function testCalendarQueryTimeRange() { + + $backend = new PDO($this->pdo); + $backend->createCalendarObject(1, "todo", "BEGIN:VCALENDAR\r\nBEGIN:VTODO\r\nEND:VTODO\r\nEND:VCALENDAR\r\n"); + $backend->createCalendarObject(1, "event", "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE:20120101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"); + $backend->createCalendarObject(1, "event2", "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE:20120103\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"); + + $filters = array( + 'name' => 'VCALENDAR', + 'comp-filters' => array( + array( + 'name' => 'VEVENT', + 'comp-filters' => array(), + 'prop-filters' => array(), + 'is-not-defined' => false, + 'time-range' => array( + 'start' => new \DateTime('20120103'), + 'end' => new \DateTime('20120104'), + ), + ), + ), + 'prop-filters' => array(), + 'is-not-defined' => false, + 'time-range' => null, + ); + + $this->assertEquals(array( + "event2", + ), $backend->calendarQuery(1, $filters)); + + } + function testCalendarQueryTimeRangeNoEnd() { + + $backend = new PDO($this->pdo); + $backend->createCalendarObject(1, "todo", "BEGIN:VCALENDAR\r\nBEGIN:VTODO\r\nEND:VTODO\r\nEND:VCALENDAR\r\n"); + $backend->createCalendarObject(1, "event", "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART:20120101\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"); + $backend->createCalendarObject(1, "event2", "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTART:20120103\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"); + + $filters = array( + 'name' => 'VCALENDAR', + 'comp-filters' => array( + array( + 'name' => 'VEVENT', + 'comp-filters' => array(), + 'prop-filters' => array(), + 'is-not-defined' => false, + 'time-range' => array( + 'start' => new \DateTime('20120102'), + 'end' => null, + ), + ), + ), + 'prop-filters' => array(), + 'is-not-defined' => false, + 'time-range' => null, + ); + + $this->assertEquals(array( + "event2", + ), $backend->calendarQuery(1, $filters)); + + } + + function testGetChanges() { + + $backend = new PDO($this->pdo); + $id = $backend->createCalendar( + 'principals/user1', + 'bla', + [] + ); + $result = $backend->getChangesForCalendar($id, null, 1); + + $this->assertEquals([ + 'syncToken' => 1, + 'modified' => [], + 'deleted' => [], + 'added' => [], + ], $result); + + $currentToken = $result['syncToken']; + + $dummyTodo = "BEGIN:VCALENDAR\r\nBEGIN:VTODO\r\nEND:VTODO\r\nEND:VCALENDAR\r\n"; + + $backend->createCalendarObject($id, "todo1.ics", $dummyTodo); + $backend->createCalendarObject($id, "todo2.ics", $dummyTodo); + $backend->createCalendarObject($id, "todo3.ics", $dummyTodo); + $backend->updateCalendarObject($id, "todo1.ics", $dummyTodo); + $backend->deleteCalendarObject($id, "todo2.ics"); + + $result = $backend->getChangesForCalendar($id, $currentToken, 1); + + $this->assertEquals([ + 'syncToken' => 6, + 'modified' => ["todo1.ics"], + 'deleted' => ["todo2.ics"], + 'added' => ["todo3.ics"], + ], $result); + + $result = $backend->getChangesForCalendar($id, null, 1); + + $this->assertEquals([ + 'syncToken' => 6, + 'modified' => [], + 'deleted' => [], + 'added' => ["todo1.ics", "todo3.ics"], + ], $result); + } + + function testCreateSubscriptions() { + + $props = [ + '{http://calendarserver.org/ns/}source' => new \Sabre\DAV\Xml\Property\Href('http://example.org/cal.ics', false), + '{DAV:}displayname' => 'cal', + '{http://apple.com/ns/ical/}refreshrate' => 'P1W', + '{http://apple.com/ns/ical/}calendar-color' => '#FF00FFFF', + '{http://calendarserver.org/ns/}subscribed-strip-todos' => true, + //'{http://calendarserver.org/ns/}subscribed-strip-alarms' => true, + '{http://calendarserver.org/ns/}subscribed-strip-attachments' => true, + ]; + + $backend = new PDO($this->pdo); + $backend->createSubscription('principals/user1', 'sub1', $props); + + $subs = $backend->getSubscriptionsForUser('principals/user1'); + + $expected = $props; + $expected['id'] = 1; + $expected['uri'] = 'sub1'; + $expected['principaluri'] = 'principals/user1'; + + unset($expected['{http://calendarserver.org/ns/}source']); + $expected['source'] = 'http://example.org/cal.ics'; + + $this->assertEquals(1, count($subs)); + foreach($expected as $k=>$v) { + $this->assertEquals($subs[0][$k], $expected[$k]); + } + + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + */ + function testCreateSubscriptionFail() { + + $props = [ + ]; + + $backend = new PDO($this->pdo); + $backend->createSubscription('principals/user1', 'sub1', $props); + + } + + function testUpdateSubscriptions() { + + $props = [ + '{http://calendarserver.org/ns/}source' => new \Sabre\DAV\Xml\Property\Href('http://example.org/cal.ics', false), + '{DAV:}displayname' => 'cal', + '{http://apple.com/ns/ical/}refreshrate' => 'P1W', + '{http://apple.com/ns/ical/}calendar-color' => '#FF00FFFF', + '{http://calendarserver.org/ns/}subscribed-strip-todos' => true, + //'{http://calendarserver.org/ns/}subscribed-strip-alarms' => true, + '{http://calendarserver.org/ns/}subscribed-strip-attachments' => true, + ]; + + $backend = new PDO($this->pdo); + $backend->createSubscription('principals/user1', 'sub1', $props); + + $newProps = [ + '{DAV:}displayname' => 'new displayname', + '{http://calendarserver.org/ns/}source' => new \Sabre\DAV\Xml\Property\Href('http://example.org/cal2.ics', false), + ]; + + $propPatch = new DAV\PropPatch($newProps); + $backend->updateSubscription(1, $propPatch); + $result = $propPatch->commit(); + + $this->assertTrue($result); + + $subs = $backend->getSubscriptionsForUser('principals/user1'); + + $expected = array_merge($props, $newProps); + $expected['id'] = 1; + $expected['uri'] = 'sub1'; + $expected['principaluri'] = 'principals/user1'; + + unset($expected['{http://calendarserver.org/ns/}source']); + $expected['source'] = 'http://example.org/cal2.ics'; + + $this->assertEquals(1, count($subs)); + foreach($expected as $k=>$v) { + $this->assertEquals($subs[0][$k], $expected[$k]); + } + + } + + function testUpdateSubscriptionsFail() { + + $props = [ + '{http://calendarserver.org/ns/}source' => new \Sabre\DAV\Xml\Property\Href('http://example.org/cal.ics', false), + '{DAV:}displayname' => 'cal', + '{http://apple.com/ns/ical/}refreshrate' => 'P1W', + '{http://apple.com/ns/ical/}calendar-color' => '#FF00FFFF', + '{http://calendarserver.org/ns/}subscribed-strip-todos' => true, + //'{http://calendarserver.org/ns/}subscribed-strip-alarms' => true, + '{http://calendarserver.org/ns/}subscribed-strip-attachments' => true, + ]; + + $backend = new PDO($this->pdo); + $backend->createSubscription('principals/user1', 'sub1', $props); + + $propPatch = new DAV\PropPatch([ + '{DAV:}displayname' => 'new displayname', + '{http://calendarserver.org/ns/}source' => new \Sabre\DAV\Xml\Property\Href('http://example.org/cal2.ics', false), + '{DAV:}unknown' => 'foo', + ]); + + $backend->updateSubscription(1, $propPatch); + $propPatch->commit(); + + $this->assertEquals([ + '{DAV:}unknown' => 403, + '{DAV:}displayname' => 424, + '{http://calendarserver.org/ns/}source' => 424, + ], $propPatch->getResult()); + + } + + function testDeleteSubscriptions() { + + $props = [ + '{http://calendarserver.org/ns/}source' => new \Sabre\DAV\Xml\Property\Href('http://example.org/cal.ics', false), + '{DAV:}displayname' => 'cal', + '{http://apple.com/ns/ical/}refreshrate' => 'P1W', + '{http://apple.com/ns/ical/}calendar-color' => '#FF00FFFF', + '{http://calendarserver.org/ns/}subscribed-strip-todos' => true, + //'{http://calendarserver.org/ns/}subscribed-strip-alarms' => true, + '{http://calendarserver.org/ns/}subscribed-strip-attachments' => true, + ]; + + $backend = new PDO($this->pdo); + $backend->createSubscription('principals/user1', 'sub1', $props); + + $newProps = [ + '{DAV:}displayname' => 'new displayname', + '{http://calendarserver.org/ns/}source' => new \Sabre\DAV\Xml\Property\Href('http://example.org/cal2.ics', false), + ]; + + $backend->deleteSubscription(1); + + $subs = $backend->getSubscriptionsForUser('principals/user1'); + $this->assertEquals(0, count($subs)); + } + + function testSchedulingMethods() { + + $backend = new PDO($this->pdo); + + $calData = "BEGIN:VCALENDAR\r\nEND:VCALENDAR\r\n"; + + $backend->createSchedulingObject( + 'principals/user1', + 'schedule1.ics', + $calData + ); + + $expected = [ + 'calendardata' => $calData, + 'uri' => 'schedule1.ics', + 'etag' => '"' . md5($calData) . '"', + 'size' => strlen($calData) + ]; + + $result = $backend->getSchedulingObject('principals/user1', 'schedule1.ics'); + foreach($expected as $k=>$v) { + $this->assertArrayHasKey($k, $result); + $this->assertEquals($v, $result[$k]); + } + + $results = $backend->getSchedulingObjects('principals/user1'); + + $this->assertEquals(1, count($results)); + $result = $results[0]; + foreach($expected as $k=>$v) { + $this->assertEquals($v, $result[$k]); + } + + $backend->deleteSchedulingObject('principals/user1', 'schedule1.ics'); + $result = $backend->getSchedulingObject('principals/user1', 'schedule1.ics'); + + $this->assertNull($result); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/AbstractTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/AbstractTest.php new file mode 100644 index 0000000..106ce1f --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/AbstractTest.php @@ -0,0 +1,178 @@ + 'anything'] ); + + $abstract->updateCalendar('randomid', $propPatch); + $result = $propPatch->commit(); + + $this->assertFalse($result); + + } + + function testCalendarQuery() { + + $abstract = new AbstractMock(); + $filters = array( + 'name' => 'VCALENDAR', + 'comp-filters' => array( + array( + 'name' => 'VEVENT', + 'comp-filters' => array(), + 'prop-filters' => array(), + 'is-not-defined' => false, + 'time-range' => null, + ), + ), + 'prop-filters' => array(), + 'is-not-defined' => false, + 'time-range' => null, + ); + + $this->assertEquals(array( + 'event1.ics', + ), $abstract->calendarQuery(1, $filters)); + + } + + function testGetCalendarObjectByUID() { + + $abstract = new AbstractMock(); + $this->assertNull( + $abstract->getCalendarObjectByUID('principal1', 'zim') + ); + $this->assertEquals( + 'cal1/event1.ics', + $abstract->getCalendarObjectByUID('principal1', 'foo') + ); + $this->assertNull( + $abstract->getCalendarObjectByUID('principal3', 'foo') + ); + $this->assertNull( + $abstract->getCalendarObjectByUID('principal1', 'shared') + ); + + } + + function testGetMultipleCalendarObjects() { + + $abstract = new AbstractMock(); + $result = $abstract->getMultipleCalendarObjects(1, [ + 'event1.ics', + 'task1.ics', + ]); + + $expected = [ + array( + 'id' => 1, + 'calendarid' => 1, + 'uri' => 'event1.ics', + 'calendardata' => "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nUID:foo\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", + ), + array( + 'id' => 2, + 'calendarid' => 1, + 'uri' => 'task1.ics', + 'calendardata' => "BEGIN:VCALENDAR\r\nBEGIN:VTODO\r\nEND:VTODO\r\nEND:VCALENDAR\r\n", + ), + ]; + + $this->assertEquals($expected, $result); + + + } + +} + +class AbstractMock extends AbstractBackend { + + function getCalendarsForUser($principalUri) { + + return array( + array( + 'id' => 1, + 'principaluri' => 'principal1', + 'uri' => 'cal1', + ), + array( + 'id' => 2, + 'principaluri' => 'principal1', + '{http://sabredav.org/ns}owner-principal' => 'principal2', + 'uri' => 'cal1', + ), + ); + + } + function createCalendar($principalUri,$calendarUri,array $properties) { } + function deleteCalendar($calendarId) { } + function getCalendarObjects($calendarId) { + + switch($calendarId) { + case 1: + return [ + [ + 'id' => 1, + 'calendarid' => 1, + 'uri' => 'event1.ics', + ], + [ + 'id' => 2, + 'calendarid' => 1, + 'uri' => 'task1.ics', + ], + ]; + case 2: + return [ + [ + 'id' => 3, + 'calendarid' => 2, + 'uri' => 'shared-event.ics', + ] + ]; + } + + } + + function getCalendarObject($calendarId, $objectUri) { + + switch($objectUri) { + + case 'event1.ics' : + return array( + 'id' => 1, + 'calendarid' => 1, + 'uri' => 'event1.ics', + 'calendardata' => "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nUID:foo\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", + ); + case 'task1.ics' : + return array( + 'id' => 2, + 'calendarid' => 1, + 'uri' => 'task1.ics', + 'calendardata' => "BEGIN:VCALENDAR\r\nBEGIN:VTODO\r\nEND:VTODO\r\nEND:VCALENDAR\r\n", + ); + case 'shared-event.ics' : + return array( + 'id' => 3, + 'calendarid' => 2, + 'uri' => 'event1.ics', + 'calendardata' => "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nUID:shared\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", + ); + + } + + } + function createCalendarObject($calendarId,$objectUri,$calendarData) { } + function updateCalendarObject($calendarId,$objectUri,$calendarData) { } + function deleteCalendarObject($calendarId,$objectUri) { } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/Mock.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/Mock.php new file mode 100644 index 0000000..ea9384c --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/Mock.php @@ -0,0 +1,214 @@ +calendars = $calendars; + $this->calendarData = $calendarData; + + } + + /** + * 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 + */ + function getCalendarsForUser($principalUri) { + + $r = array(); + foreach($this->calendars as $row) { + if ($row['principaluri'] == $principalUri) { + $r[] = $row; + } + } + + return $r; + + } + + /** + * 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. + * + * This function must return a server-wide unique id that can be used + * later to reference the calendar. + * + * @param string $principalUri + * @param string $calendarUri + * @param array $properties + * @return string|int + */ + function createCalendar($principalUri,$calendarUri,array $properties) { + + $id = DAV\UUIDUtil::getUUID(); + $this->calendars[] = array_merge([ + 'id' => $id, + 'principaluri' => $principalUri, + 'uri' => $calendarUri, + '{' . CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => new CalDAV\Xml\Property\SupportedCalendarComponentSet(['VEVENT','VTODO']), + ], $properties); + + return $id; + + } + + /** + * Delete a calendar and all it's objects + * + * @param string $calendarId + * @return void + */ + public function deleteCalendar($calendarId) { + + foreach($this->calendars as $k=>$calendar) { + if ($calendar['id'] === $calendarId) { + unset($this->calendars[$k]); + } + } + + } + + /** + * Returns all calendar objects within a calendar object. + * + * 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. + * + * 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. + * + * @param string $calendarId + * @return array + */ + public function getCalendarObjects($calendarId) { + + if (!isset($this->calendarData[$calendarId])) + return array(); + + $objects = $this->calendarData[$calendarId]; + + foreach($objects as $uri => &$object) { + $object['calendarid'] = $calendarId; + $object['uri'] = $uri; + $object['lastmodified'] = null; + } + return $objects; + + } + + /** + * 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. + * + * @param string $calendarId + * @param string $objectUri + * @return array + */ + function getCalendarObject($calendarId,$objectUri) { + + if (!isset($this->calendarData[$calendarId][$objectUri])) { + throw new DAV\Exception\NotFound('Object could not be found'); + } + $object = $this->calendarData[$calendarId][$objectUri]; + $object['calendarid'] = $calendarId; + $object['uri'] = $objectUri; + $object['lastmodified'] = null; + return $object; + + } + + /** + * Creates a new calendar object. + * + * @param string $calendarId + * @param string $objectUri + * @param string $calendarData + * @return void + */ + function createCalendarObject($calendarId,$objectUri,$calendarData) { + + $this->calendarData[$calendarId][$objectUri] = array( + 'calendardata' => $calendarData, + 'calendarid' => $calendarId, + 'uri' => $objectUri, + ); + return '"' . md5($calendarData) . '"'; + + } + + /** + * Updates an existing calendarobject, based on it's uri. + * + * @param string $calendarId + * @param string $objectUri + * @param string $calendarData + * @return void + */ + function updateCalendarObject($calendarId,$objectUri,$calendarData) { + + $this->calendarData[$calendarId][$objectUri] = array( + 'calendardata' => $calendarData, + 'calendarid' => $calendarId, + 'uri' => $objectUri, + ); + return '"' . md5($calendarData) . '"'; + + } + + /** + * Deletes an existing calendar object. + * + * @param string $calendarId + * @param string $objectUri + * @return void + */ + function deleteCalendarObject($calendarId,$objectUri) { + + throw new Exception('Not implemented'); + + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/MockScheduling.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/MockScheduling.php new file mode 100644 index 0000000..52aad32 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/MockScheduling.php @@ -0,0 +1,93 @@ +schedulingObjects[$principalUri][$objectUri])) { + return $this->schedulingObjects[$principalUri][$objectUri]; + } + + } + + /** + * 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 + */ + public function getSchedulingObjects($principalUri) { + + if (isset($this->schedulingObjects[$principalUri])) { + return array_values($this->schedulingObjects[$principalUri]); + } + return []; + + } + + /** + * Deletes a scheduling object + * + * @param string $principalUri + * @param string $objectUri + * @return void + */ + public function deleteSchedulingObject($principalUri, $objectUri) { + + if (isset($this->schedulingObjects[$principalUri][$objectUri])) { + unset($this->schedulingObjects[$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 + */ + public function createSchedulingObject($principalUri, $objectUri, $objectData) { + + if (!isset($this->schedulingObjects[$principalUri])) { + $this->schedulingObjects[$principalUri] = []; + } + $this->schedulingObjects[$principalUri][$objectUri] = [ + 'uri' => $objectUri, + 'calendardata' => $objectData, + 'lastmodified' => null, + 'etag' => '"' . md5($objectData) . '"', + 'size' => strlen($objectData) + ]; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/MockSharing.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/MockSharing.php new file mode 100644 index 0000000..2156627 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/MockSharing.php @@ -0,0 +1,172 @@ +notifications = $notifications; + + } + + /** + * Returns a list of notifications for a given principal url. + * + * The returned array should only consist of implementations of + * Sabre\CalDAV\Notifications\INotificationType. + * + * @param string $principalUri + * @return array + */ + function getNotificationsForPrincipal($principalUri) { + + if (isset($this->notifications[$principalUri])) { + return $this->notifications[$principalUri]; + } + return []; + + } + + /** + * This deletes a specific notifcation. + * + * This may be called by a client once it deems a notification handled. + * + * @param string $principalUri + * @param NotificationInterface $notification + * @return void + */ + function deleteNotification($principalUri, NotificationInterface $notification) { + + foreach($this->notifications[$principalUri] as $key=>$value) { + if ($notification === $value) { + unset($this->notifications[$principalUri][$key]); + } + } + + } + + /** + * Updates the list of shares. + * + * The first array is a list of people that are to be added to the + * calendar. + * + * Every element in the add array has the following properties: + * * href - A url. Usually a mailto: address + * * commonName - Usually a first and last name, or false + * * summary - A description of the share, can also be false + * * readOnly - A boolean value + * + * Every element in the remove array is just the address string. + * + * Note that if the calendar is currently marked as 'not shared' by and + * this method is called, the calendar should be 'upgraded' to a shared + * calendar. + * + * @param mixed $calendarId + * @param array $add + * @param array $remove + * @return void + */ + function updateShares($calendarId, array $add, array $remove) { + + if (!isset($this->shares[$calendarId])) { + $this->shares[$calendarId] = []; + } + + foreach($add as $val) { + $val['status'] = CalDAV\SharingPlugin::STATUS_NORESPONSE; + $this->shares[$calendarId][] = $val; + } + + foreach($this->shares[$calendarId] as $k=>$share) { + + if (in_array($share['href'], $remove)) { + unset($this->shares[$calendarId][$k]); + } + + } + + // Re-numbering keys + $this->shares[$calendarId] = array_values($this->shares[$calendarId]); + + } + + /** + * 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 + * + * @param mixed $calendarId + * @return array + */ + function getShares($calendarId) { + + if (!isset($this->shares[$calendarId])) { + return []; + } + + return $this->shares[$calendarId]; + + } + + /** + * This method is called when a user replied to a request to share. + * + * @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 void + */ + function shareReply($href, $status, $calendarUri, $inReplyTo, $summary = null) { + + // This operation basically doesn't do anything yet + if ($status === CalDAV\SharingPlugin::STATUS_ACCEPTED) { + return 'calendars/blabla/calendar'; + } + + } + + /** + * Publishes a calendar + * + * @param mixed $calendarId + * @param bool $value + * @return void + */ + function setPublishStatus($calendarId, $value) { + + foreach($this->calendars as $k=>$cal) { + if ($cal['id'] === $calendarId) { + if (!$value) { + unset($cal['{http://calendarserver.org/ns/}publish-url']); + } else { + $cal['{http://calendarserver.org/ns/}publish-url'] = 'http://example.org/public/ ' . $calendarId . '.ics'; + } + return; + } + } + + throw new DAV\Exception('Calendar with id "' . $calendarId . '" not found'); + + } + +} + diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/MockSubscriptionSupport.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/MockSubscriptionSupport.php new file mode 100644 index 0000000..cab6265 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/MockSubscriptionSupport.php @@ -0,0 +1,156 @@ +subs[$principalUri])) { + return $this->subs[$principalUri]; + } + return []; + + } + + /** + * 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 + */ + public function createSubscription($principalUri, $uri, array $properties) { + + $properties['uri'] = $uri; + $properties['principaluri'] = $principalUri; + $properties['source'] = $properties['{http://calendarserver.org/ns/}source']->getHref(); + + if (!isset($this->subs[$principalUri])) { + $this->subs[$principalUri] = []; + } + + $id = [$principalUri, count($this->subs[$principalUri])+1]; + + $properties['id'] = $id; + + $this->subs[$principalUri][] = array_merge($properties, [ + 'id' => $id, + ]); + + return $id; + + } + + /** + * 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 + */ + public function updateSubscription($subscriptionId, DAV\PropPatch $propPatch) { + + $found = null; + foreach($this->subs[$subscriptionId[0]] as &$sub) { + + if ($sub['id'][1] === $subscriptionId[1]) { + $found =& $sub; + break; + } + + } + + if (!$found) return; + + $propPatch->handleRemaining(function($mutations) use (&$found) { + foreach($mutations as $k=>$v) { + $found[$k] = $v; + } + return true; + }); + + } + + /** + * Deletes a subscription + * + * @param mixed $subscriptionId + * @return void + */ + public function deleteSubscription($subscriptionId) { + + $found = null; + foreach($this->subs[$subscriptionId[0]] as $index=>$sub) { + + if ($sub['id'][1] === $subscriptionId[1]) { + unset($this->subs[$subscriptionId[0]][$index]); + return true; + } + + } + + return false; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/PDOMySQLTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/PDOMySQLTest.php new file mode 100644 index 0000000..fafb151 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/PDOMySQLTest.php @@ -0,0 +1,39 @@ +markTestSkipped('MySQL driver is not available, or not properly configured'); + $pdo = \Sabre\TestUtil::getMySQLDB(); + if (!$pdo) $this->markTestSkipped('Could not connect to mysql database'); + + $pdo->query('DROP TABLE IF EXISTS calendarobjects, calendars, calendarchanges, calendarsubscriptions, schedulingobjects'); + + $queries = explode( + ';', + file_get_contents(__DIR__ . '/../../../../examples/sql/mysql.calendars.sql') + ); + + foreach($queries as $query) { + $query = trim($query," \r\n\t"); + if ($query) + $pdo->exec($query); + } + $this->pdo = $pdo; + + } + + function teardown() { + + $this->pdo = null; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/PDOSqliteTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/PDOSqliteTest.php new file mode 100644 index 0000000..c8319c4 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Backend/PDOSqliteTest.php @@ -0,0 +1,37 @@ +markTestSkipped('SQLite driver is not available'); + + if (file_exists(SABRE_TEMPDIR . '/testdb.sqlite')) + unlink(SABRE_TEMPDIR . '/testdb.sqlite'); + + $pdo = new \PDO('sqlite:' . SABRE_TEMPDIR . '/testdb.sqlite'); + $pdo->setAttribute(\PDO::ATTR_ERRMODE,\PDO::ERRMODE_EXCEPTION); + + // Yup this is definitely not 'fool proof', but good enough for now. + $queries = explode(';', file_get_contents(__DIR__ . '/../../../../examples/sql/sqlite.calendars.sql')); + foreach($queries as $query) { + $pdo->exec($query); + } + $this->pdo = $pdo; + + } + + function teardown() { + + $this->pdo = null; + unlink(SABRE_TEMPDIR . '/testdb.sqlite'); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarHomeNotificationsTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarHomeNotificationsTest.php new file mode 100644 index 0000000..a0880d8 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarHomeNotificationsTest.php @@ -0,0 +1,53 @@ + 'principals/user']); + + $this->assertEquals( + [], + $calendarHome->getChildren() + ); + + } + + /** + * @expectedException \Sabre\DAV\Exception\NotFound + */ + function testGetChildNoSupport() { + + $backend = new Backend\Mock(); + $calendarHome = new CalendarHome($backend,['uri' => 'principals/user']); + $calendarHome->getChild('notifications'); + + } + + function testGetChildren() { + + $backend = new Backend\MockSharing(); + $calendarHome = new CalendarHome($backend,['uri' => 'principals/user']); + + $result = $calendarHome->getChildren(); + $this->assertEquals('notifications', $result[0]->getName()); + + } + + function testGetChild() { + + $backend = new Backend\MockSharing(); + $calendarHome = new CalendarHome($backend,['uri' => 'principals/user']); + $result = $calendarHome->getChild('notifications'); + $this->assertEquals('notifications', $result->getName()); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarHomeSharedCalendarsTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarHomeSharedCalendarsTest.php new file mode 100644 index 0000000..bfb0eae --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarHomeSharedCalendarsTest.php @@ -0,0 +1,89 @@ + 1, + 'principaluri' => 'principals/user1', + ), + array( + 'id' => 2, + '{http://calendarserver.org/ns/}shared-url' => 'calendars/owner/cal1', + '{http://sabredav.org/ns}owner-principal' => 'principal/owner', + '{http://sabredav.org/ns}read-only' => false, + 'principaluri' => 'principals/user1', + ), + ); + + $this->backend = new Backend\MockSharing( + $calendars, + array(), + array() + ); + + return new CalendarHome($this->backend, array( + 'uri' => 'principals/user1' + )); + + } + + function testSimple() { + + $instance = $this->getInstance(); + $this->assertEquals('user1', $instance->getName()); + + } + + function testGetChildren() { + + $instance = $this->getInstance(); + $children = $instance->getChildren(); + $this->assertEquals(3, count($children)); + + // Testing if we got all the objects back. + $hasShareable = false; + $hasShared = false; + $hasOutbox = false; + $hasNotifications = false; + + foreach($children as $child) { + + if ($child instanceof IShareableCalendar) { + $hasShareable = true; + } + if ($child instanceof ISharedCalendar) { + $hasShared = true; + } + if ($child instanceof Notifications\ICollection) { + $hasNotifications = true; + } + + } + if (!$hasShareable) $this->fail('Missing node!'); + if (!$hasShared) $this->fail('Missing node!'); + if (!$hasNotifications) $this->fail('Missing node!'); + + } + + function testShareReply() { + + $instance = $this->getInstance(); + $result = $instance->shareReply('uri', SharingPlugin::STATUS_DECLINED, 'curi', '1'); + $this->assertNull($result); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarHomeSubscriptionsTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarHomeSubscriptionsTest.php new file mode 100644 index 0000000..9610ed3 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarHomeSubscriptionsTest.php @@ -0,0 +1,87 @@ + 'baz', + '{http://calendarserver.org/ns/}source' => new \Sabre\DAV\Xml\Property\Href('http://example.org/test.ics'), + ]; + $principal = [ + 'uri' => 'principals/user1' + ]; + $this->backend = new Backend\MockSubscriptionSupport([], []); + $this->backend->createSubscription('principals/user1', 'uri', $props); + + return new CalendarHome($this->backend, $principal); + + } + + function testSimple() { + + $instance = $this->getInstance(); + $this->assertEquals('user1', $instance->getName()); + + } + + function testGetChildren() { + + $instance = $this->getInstance(); + $children = $instance->getChildren(); + $this->assertEquals(1, count($children)); + foreach($children as $child) { + if ($child instanceof Subscriptions\Subscription) { + return; + } + } + $this->fail('There were no subscription nodes in the calendar home'); + + } + + function testCreateSubscription() { + + $instance = $this->getInstance(); + $rt = ['{DAV:}collection', '{http://calendarserver.org/ns/}subscribed']; + + $props = [ + '{DAV:}displayname' => 'baz', + '{http://calendarserver.org/ns/}source' => new \Sabre\DAV\Xml\Property\Href('http://example.org/test2.ics'), + ]; + $instance->createExtendedCollection('sub2', new MkCol($rt, $props)); + + $children = $instance->getChildren(); + $this->assertEquals(2, count($children)); + + } + + /** + * @expectedException \Sabre\DAV\Exception\InvalidResourceType + */ + function testNoSubscriptionSupport() { + + $principal = [ + 'uri' => 'principals/user1' + ]; + $backend = new Backend\Mock([], []); + $uC = new CalendarHome($backend, $principal); + + $rt = ['{DAV:}collection', '{http://calendarserver.org/ns/}subscribed']; + + $props = [ + '{DAV:}displayname' => 'baz', + '{http://calendarserver.org/ns/}source' => new \Sabre\DAV\Xml\Property\Href('http://example.org/test2.ics'), + ]; + $uC->createExtendedCollection('sub2', new MkCol($rt, $props)); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarHomeTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarHomeTest.php new file mode 100644 index 0000000..518cc99 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarHomeTest.php @@ -0,0 +1,218 @@ +markTestSkipped('SQLite driver is not available'); + $this->backend = TestUtil::getBackend(); + $this->usercalendars = new CalendarHome($this->backend, array( + 'uri' => 'principals/user1' + )); + + } + + function testSimple() { + + $this->assertEquals('user1',$this->usercalendars->getName()); + + } + + /** + * @expectedException Sabre\DAV\Exception\NotFound + * @depends testSimple + */ + function testGetChildNotFound() { + + $this->usercalendars->getChild('randomname'); + + } + + function testChildExists() { + + $this->assertFalse($this->usercalendars->childExists('foo')); + $this->assertTrue($this->usercalendars->childExists('UUID-123467')); + + } + + function testGetOwner() { + + $this->assertEquals('principals/user1', $this->usercalendars->getOwner()); + + } + + function testGetGroup() { + + $this->assertNull($this->usercalendars->getGroup()); + + } + + function testGetACL() { + + $expected = array( + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}write', + 'principal' => 'principals/user1', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1/calendar-proxy-write', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}write', + 'principal' => 'principals/user1/calendar-proxy-write', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1/calendar-proxy-read', + 'protected' => true, + ), + ); + $this->assertEquals($expected, $this->usercalendars->getACL()); + + } + + /** + * @expectedException Sabre\DAV\Exception\MethodNotAllowed + */ + function testSetACL() { + + $this->usercalendars->setACL(array()); + + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + * @depends testSimple + */ + function testSetName() { + + $this->usercalendars->setName('bla'); + + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + * @depends testSimple + */ + function testDelete() { + + $this->usercalendars->delete(); + + } + + /** + * @depends testSimple + */ + function testGetLastModified() { + + $this->assertNull($this->usercalendars->getLastModified()); + + } + + /** + * @expectedException Sabre\DAV\Exception\MethodNotAllowed + * @depends testSimple + */ + function testCreateFile() { + + $this->usercalendars->createFile('bla'); + + } + + + /** + * @expectedException Sabre\DAV\Exception\MethodNotAllowed + * @depends testSimple + */ + function testCreateDirectory() { + + $this->usercalendars->createDirectory('bla'); + + } + + /** + * @depends testSimple + */ + function testCreateExtendedCollection() { + + $mkCol = new MkCol( + ['{DAV:}collection', '{urn:ietf:params:xml:ns:caldav}calendar'], + [] + ); + $result = $this->usercalendars->createExtendedCollection('newcalendar', $mkCol); + $this->assertNull($result); + $cals = $this->backend->getCalendarsForUser('principals/user1'); + $this->assertEquals(3,count($cals)); + + } + + /** + * @expectedException Sabre\DAV\Exception\InvalidResourceType + * @depends testSimple + */ + function testCreateExtendedCollectionBadResourceType() { + + $mkCol = new MkCol( + ['{DAV:}collection', '{DAV:}blabla'], + [] + ); + $this->usercalendars->createExtendedCollection('newcalendar', $mkCol); + + } + + /** + * @expectedException Sabre\DAV\Exception\InvalidResourceType + * @depends testSimple + */ + function testCreateExtendedCollectionNotACalendar() { + + $mkCol = new MkCol( + ['{DAV:}collection'], + [] + ); + $this->usercalendars->createExtendedCollection('newcalendar', $mkCol); + + } + + function testGetSupportedPrivilegesSet() { + + $this->assertNull($this->usercalendars->getSupportedPrivilegeSet()); + + } + + /** + * @expectedException Sabre\DAV\Exception\NotImplemented + */ + function testShareReplyFail() { + + $this->usercalendars->shareReply('uri', SharingPlugin::STATUS_DECLINED, 'curi', '1'); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarObjectTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarObjectTest.php new file mode 100644 index 0000000..9fc1eee --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarObjectTest.php @@ -0,0 +1,395 @@ +markTestSkipped('SQLite driver is not available'); + $this->backend = TestUtil::getBackend(); + + $calendars = $this->backend->getCalendarsForUser('principals/user1'); + $this->assertEquals(2,count($calendars)); + $this->calendar = new Calendar($this->backend, $calendars[0]); + + } + + function teardown() { + + unset($this->calendar); + unset($this->backend); + + } + + function testSetup() { + + $children = $this->calendar->getChildren(); + $this->assertTrue($children[0] instanceof CalendarObject); + + $this->assertInternalType('string',$children[0]->getName()); + $this->assertInternalType('string',$children[0]->get()); + $this->assertInternalType('string',$children[0]->getETag()); + $this->assertEquals('text/calendar; charset=utf-8; component=vevent', $children[0]->getContentType()); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testInvalidArg1() { + + $obj = new CalendarObject( + new Backend\Mock(array(),array()), + array(), + array() + ); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testInvalidArg2() { + + $obj = new CalendarObject( + new Backend\Mock(array(),array()), + array(), + array('calendarid' => '1') + ); + + } + + /** + * @depends testSetup + */ + function testPut() { + + $children = $this->calendar->getChildren(); + $this->assertTrue($children[0] instanceof CalendarObject); + $newData = TestUtil::getTestCalendarData(); + + $children[0]->put($newData); + $this->assertEquals($newData, $children[0]->get()); + + } + + /** + * @depends testSetup + */ + function testPutStream() { + + $children = $this->calendar->getChildren(); + $this->assertTrue($children[0] instanceof CalendarObject); + $newData = TestUtil::getTestCalendarData(); + + $stream = fopen('php://temp','r+'); + fwrite($stream, $newData); + rewind($stream); + $children[0]->put($stream); + $this->assertEquals($newData, $children[0]->get()); + + } + + + /** + * @depends testSetup + */ + function testDelete() { + + $children = $this->calendar->getChildren(); + $this->assertTrue($children[0] instanceof CalendarObject); + + $obj = $children[0]; + $obj->delete(); + + $children2 = $this->calendar->getChildren(); + $this->assertEquals(count($children)-1, count($children2)); + + } + + /** + * @depends testSetup + */ + function testGetLastModified() { + + $children = $this->calendar->getChildren(); + $this->assertTrue($children[0] instanceof CalendarObject); + + $obj = $children[0]; + + $lastMod = $obj->getLastModified(); + $this->assertTrue(is_int($lastMod) || ctype_digit($lastMod)); + + } + + /** + * @depends testSetup + */ + function testGetSize() { + + $children = $this->calendar->getChildren(); + $this->assertTrue($children[0] instanceof CalendarObject); + + $obj = $children[0]; + + $size = $obj->getSize(); + $this->assertInternalType('int', $size); + + } + + function testGetOwner() { + + $children = $this->calendar->getChildren(); + $this->assertTrue($children[0] instanceof CalendarObject); + + $obj = $children[0]; + $this->assertEquals('principals/user1', $obj->getOwner()); + + } + + function testGetGroup() { + + $children = $this->calendar->getChildren(); + $this->assertTrue($children[0] instanceof CalendarObject); + + $obj = $children[0]; + $this->assertNull($obj->getGroup()); + + } + + function testGetACL() { + + $expected = array( + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1/calendar-proxy-write', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1/calendar-proxy-read', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}write', + 'principal' => 'principals/user1', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}write', + 'principal' => 'principals/user1/calendar-proxy-write', + 'protected' => true, + ), + ); + + $children = $this->calendar->getChildren(); + $this->assertTrue($children[0] instanceof CalendarObject); + + $obj = $children[0]; + $this->assertEquals($expected, $obj->getACL()); + + } + + function testDefaultACL() { + + $backend = new Backend\Mock([], []); + $calendarObject = new CalendarObject($backend, ['principaluri' => 'principals/user1'], ['calendarid' => 1, 'uri' => 'foo']); + $expected = array( + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}write', + 'principal' => 'principals/user1', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1/calendar-proxy-write', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}write', + 'principal' => 'principals/user1/calendar-proxy-write', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1/calendar-proxy-read', + 'protected' => true, + ), + ); + $this->assertEquals($expected, $calendarObject->getACL()); + + + } + + /** + * @expectedException Sabre\DAV\Exception\MethodNotAllowed + */ + function testSetACL() { + + $children = $this->calendar->getChildren(); + $this->assertTrue($children[0] instanceof CalendarObject); + + $obj = $children[0]; + $obj->setACL(array()); + + } + + function testGet() { + + $children = $this->calendar->getChildren(); + $this->assertTrue($children[0] instanceof CalendarObject); + + $obj = $children[0]; + + $expected = "BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Apple Inc.//iCal 4.0.1//EN +CALSCALE:GREGORIAN +BEGIN:VTIMEZONE +TZID:Asia/Seoul +BEGIN:DAYLIGHT +TZOFFSETFROM:+0900 +RRULE:FREQ=YEARLY;UNTIL=19880507T150000Z;BYMONTH=5;BYDAY=2SU +DTSTART:19870510T000000 +TZNAME:GMT+09:00 +TZOFFSETTO:+1000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:+1000 +DTSTART:19881009T000000 +TZNAME:GMT+09:00 +TZOFFSETTO:+0900 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +CREATED:20100225T154229Z +UID:39A6B5ED-DD51-4AFE-A683-C35EE3749627 +TRANSP:TRANSPARENT +SUMMARY:Something here +DTSTAMP:20100228T130202Z +DTSTART;TZID=Asia/Seoul:20100223T060000 +DTEND;TZID=Asia/Seoul:20100223T070000 +ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:lisa@example.com +SEQUENCE:2 +END:VEVENT +END:VCALENDAR"; + + + + $this->assertEquals($expected, $obj->get()); + + } + + function testGetRefetch() { + + $backend = new Backend\Mock(array(), array( + 1 => array( + 'foo' => array( + 'calendardata' => 'foo', + 'uri' => 'foo' + ), + ) + )); + $obj = new CalendarObject($backend, array('id' => 1), array('uri' => 'foo')); + + $this->assertEquals('foo', $obj->get()); + + } + + function testGetEtag1() { + + $objectInfo = array( + 'calendardata' => 'foo', + 'uri' => 'foo', + 'etag' => 'bar', + 'calendarid' => 1 + ); + + $backend = new Backend\Mock(array(), array()); + $obj = new CalendarObject($backend, array(), $objectInfo); + + $this->assertEquals('bar', $obj->getETag()); + + } + + function testGetEtag2() { + + $objectInfo = array( + 'calendardata' => 'foo', + 'uri' => 'foo', + 'calendarid' => 1 + ); + + $backend = new Backend\Mock(array(), array()); + $obj = new CalendarObject($backend, array(), $objectInfo); + + $this->assertEquals('"' . md5('foo') . '"', $obj->getETag()); + + } + + function testGetSupportedPrivilegesSet() { + + $objectInfo = array( + 'calendardata' => 'foo', + 'uri' => 'foo', + 'calendarid' => 1 + ); + + $backend = new Backend\Mock(array(), array()); + $obj = new CalendarObject($backend, array(), $objectInfo); + $this->assertNull($obj->getSupportedPrivilegeSet()); + + } + + function testGetSize1() { + + $objectInfo = array( + 'calendardata' => 'foo', + 'uri' => 'foo', + 'calendarid' => 1 + ); + + $backend = new Backend\Mock(array(), array()); + $obj = new CalendarObject($backend, array(), $objectInfo); + $this->assertEquals(3, $obj->getSize()); + + } + + function testGetSize2() { + + $objectInfo = array( + 'uri' => 'foo', + 'calendarid' => 1, + 'size' => 4, + ); + + $backend = new Backend\Mock(array(), array()); + $obj = new CalendarObject($backend, array(), $objectInfo); + $this->assertEquals(4, $obj->getSize()); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarQueryVAlarmTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarQueryVAlarmTest.php new file mode 100644 index 0000000..9de24d3 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarQueryVAlarmTest.php @@ -0,0 +1,122 @@ +createComponent('VEVENT'); + $vevent->RRULE = 'FREQ=MONTHLY'; + $vevent->DTSTART = '20120101T120000Z'; + $vevent->UID = 'bla'; + + $valarm = $vcalendar->createComponent('VALARM'); + $valarm->TRIGGER = '-P15D'; + $vevent->add($valarm); + + + $vcalendar->add($vevent); + + $filter = array( + 'name' => 'VCALENDAR', + 'is-not-defined' => false, + 'time-range' => null, + 'prop-filters' => array(), + 'comp-filters' => array( + array( + 'name' => 'VEVENT', + 'is-not-defined' => false, + 'time-range' => null, + 'prop-filters' => array(), + 'comp-filters' => array( + array( + 'name' => 'VALARM', + 'is-not-defined' => false, + 'prop-filters' => array(), + 'comp-filters' => array(), + 'time-range' => array( + 'start' => new \DateTime('2012-05-10'), + 'end' => new \DateTime('2012-05-20'), + ), + ), + ), + ), + ), + ); + + $validator = new CalendarQueryValidator(); + $this->assertTrue($validator->validate($vcalendar, $filter)); + + $vcalendar = new VObject\Component\VCalendar(); + + // A limited recurrence rule, should return false + $vevent = $vcalendar->createComponent('VEVENT'); + $vevent->RRULE = 'FREQ=MONTHLY;COUNT=1'; + $vevent->DTSTART = '20120101T120000Z'; + $vevent->UID = 'bla'; + + $valarm = $vcalendar->createComponent('VALARM'); + $valarm->TRIGGER = '-P15D'; + $vevent->add($valarm); + + $vcalendar->add($vevent); + + $this->assertFalse($validator->validate($vcalendar, $filter)); + } + + function testAlarmWayBefore() { + + $vcalendar = new VObject\Component\VCalendar(); + + $vevent = $vcalendar->createComponent('VEVENT'); + $vevent->DTSTART = '20120101T120000Z'; + $vevent->UID = 'bla'; + + $valarm = $vcalendar->createComponent('VALARM'); + $valarm->TRIGGER = '-P2W1D'; + $vevent->add($valarm); + + $vcalendar->add($vevent); + + $filter = array( + 'name' => 'VCALENDAR', + 'is-not-defined' => false, + 'time-range' => null, + 'prop-filters' => array(), + 'comp-filters' => array( + array( + 'name' => 'VEVENT', + 'is-not-defined' => false, + 'time-range' => null, + 'prop-filters' => array(), + 'comp-filters' => array( + array( + 'name' => 'VALARM', + 'is-not-defined' => false, + 'prop-filters' => array(), + 'comp-filters' => array(), + 'time-range' => array( + 'start' => new \DateTime('2011-12-10'), + 'end' => new \DateTime('2011-12-20'), + ), + ), + ), + ), + ), + ); + + $validator = new CalendarQueryValidator(); + $this->assertTrue($validator->validate($vcalendar, $filter)); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarQueryValidatorTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarQueryValidatorTest.php new file mode 100644 index 0000000..9822e82 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarQueryValidatorTest.php @@ -0,0 +1,826 @@ +assertFalse($validator->validate($vcal, ['name' => 'VFOO'])); + + } + + /** + * @dataProvider provider + */ + function testValid($icalObject, $filters, $outcome) { + + $validator = new CalendarQueryValidator(); + + // Wrapping filter in a VCALENDAR component filter, as this is always + // there anyway. + $filters = array( + 'name' => 'VCALENDAR', + 'comp-filters' => array($filters), + 'prop-filters' => array(), + 'is-not-defined' => false, + 'time-range' => null, + ); + + $vObject = VObject\Reader::read($icalObject); + + switch($outcome) { + case 0 : + $this->assertFalse($validator->validate($vObject, $filters)); + break; + case 1 : + $this->assertTrue($validator->validate($vObject, $filters)); + break; + case -1 : + try { + $validator->validate($vObject, $filters); + $this->fail('This test was supposed to fail'); + } catch (\Exception $e) { + // We need to test something to be valid for phpunit strict + // mode. + $this->assertTrue(true); + } catch (\Throwable $e) { + // PHP7 + $this->assertTrue(true); + } + break; + + } + + } + + function provider() { + + $blob1 = << 'VEVENT', + 'comp-filters' => array(), + 'prop-filters' => array(), + 'is-not-defined' => false, + 'time-range' => null, + ); + $filter2 = $filter1; + $filter2['name'] = 'VTODO'; + + $filter3 = $filter1; + $filter3['is-not-defined'] = true; + + $filter4 = $filter1; + $filter4['name'] = 'VTODO'; + $filter4['is-not-defined'] = true; + + $filter5 = $filter1; + $filter5['comp-filters'] = array( + array( + 'name' => 'VALARM', + 'is-not-defined' => false, + 'comp-filters' => array(), + 'prop-filters' => array(), + 'time-range' => null, + ), + ); + $filter6 = $filter1; + $filter6['prop-filters'] = array( + array( + 'name' => 'SUMMARY', + 'is-not-defined' => false, + 'param-filters' => array(), + 'time-range' => null, + 'text-match' => null, + ), + ); + $filter7 = $filter6; + $filter7['prop-filters'][0]['name'] = 'DESCRIPTION'; + + $filter8 = $filter6; + $filter8['prop-filters'][0]['is-not-defined'] = true; + + $filter9 = $filter7; + $filter9['prop-filters'][0]['is-not-defined'] = true; + + $filter10 = $filter5; + $filter10['prop-filters'] = $filter6['prop-filters']; + + // Param filters + $filter11 = $filter1; + $filter11['prop-filters'] = array( + array( + 'name' => 'DTSTART', + 'is-not-defined' => false, + 'param-filters' => array( + array( + 'name' => 'VALUE', + 'is-not-defined' => false, + 'text-match' => null, + ), + ), + 'time-range' => null, + 'text-match' => null, + ), + ); + + $filter12 = $filter11; + $filter12['prop-filters'][0]['param-filters'][0]['name'] = 'TZID'; + + $filter13 = $filter11; + $filter13['prop-filters'][0]['param-filters'][0]['is-not-defined'] = true; + + $filter14 = $filter12; + $filter14['prop-filters'][0]['param-filters'][0]['is-not-defined'] = true; + + // Param text filter + $filter15 = $filter11; + $filter15['prop-filters'][0]['param-filters'][0]['text-match'] = array( + 'collation' => 'i;ascii-casemap', + 'value' => 'dAtE', + 'negate-condition' => false, + ); + $filter16 = $filter15; + $filter16['prop-filters'][0]['param-filters'][0]['text-match']['collation'] = 'i;octet'; + + $filter17 = $filter15; + $filter17['prop-filters'][0]['param-filters'][0]['text-match']['negate-condition'] = true; + + $filter18 = $filter15; + $filter18['prop-filters'][0]['param-filters'][0]['text-match']['negate-condition'] = true; + $filter18['prop-filters'][0]['param-filters'][0]['text-match']['collation'] = 'i;octet'; + + // prop + text + $filter19 = $filter5; + $filter19['comp-filters'][0]['prop-filters'] = array( + array( + 'name' => 'action', + 'is-not-defined' => false, + 'time-range' => null, + 'param-filters' => array(), + 'text-match' => array( + 'collation' => 'i;ascii-casemap', + 'value' => 'display', + 'negate-condition' => false, + ), + ), + ); + + // Time range + $filter20 = array( + 'name' => 'VEVENT', + 'comp-filters' => array(), + 'prop-filters' => array(), + 'is-not-defined' => false, + 'time-range' => array( + 'start' => new \DateTime('2011-01-01 10:00:00', new \DateTimeZone('GMT')), + 'end' => new \DateTime('2011-01-01 13:00:00', new \DateTimeZone('GMT')), + ), + ); + // Time range, no end date + $filter21 = $filter20; + $filter21['time-range']['end'] = null; + + // Time range, no start date + $filter22 = $filter20; + $filter22['time-range']['start'] = null; + + // Time range, other dates + $filter23 = $filter20; + $filter23['time-range'] = array( + 'start' => new \DateTime('2011-02-01 10:00:00', new \DateTimeZone('GMT')), + 'end' => new \DateTime('2011-02-01 13:00:00', new \DateTimeZone('GMT')), + ); + // Time range + $filter24 = array( + 'name' => 'VTODO', + 'comp-filters' => array(), + 'prop-filters' => array(), + 'is-not-defined' => false, + 'time-range' => array( + 'start' => new \DateTime('2011-01-01 12:45:00', new \DateTimeZone('GMT')), + 'end' => new \DateTime('2011-01-01 13:15:00', new \DateTimeZone('GMT')), + ), + ); + // Time range, other dates (1 month in the future) + $filter25 = $filter24; + $filter25['time-range'] = array( + 'start' => new \DateTime('2011-02-01 10:00:00', new \DateTimeZone('GMT')), + 'end' => new \DateTime('2011-02-01 13:00:00', new \DateTimeZone('GMT')), + ); + $filter26 = $filter24; + $filter26['time-range'] = array( + 'start' => new \DateTime('2011-01-01 11:45:00', new \DateTimeZone('GMT')), + 'end' => new \DateTime('2011-01-01 12:15:00', new \DateTimeZone('GMT')), + ); + + // Time range for VJOURNAL + $filter27 = array( + 'name' => 'VJOURNAL', + 'comp-filters' => array(), + 'prop-filters' => array(), + 'is-not-defined' => false, + 'time-range' => array( + 'start' => new \DateTime('2011-01-01 12:45:00', new \DateTimeZone('GMT')), + 'end' => new \DateTime('2011-01-01 13:15:00', new \DateTimeZone('GMT')), + ), + ); + $filter28 = $filter27; + $filter28['time-range'] = array( + 'start' => new \DateTime('2011-01-01 11:45:00', new \DateTimeZone('GMT')), + 'end' => new \DateTime('2011-01-01 12:15:00', new \DateTimeZone('GMT')), + ); + // Time range for VFREEBUSY + $filter29 = array( + 'name' => 'VFREEBUSY', + 'comp-filters' => array(), + 'prop-filters' => array(), + 'is-not-defined' => false, + 'time-range' => array( + 'start' => new \DateTime('2011-01-01 12:45:00', new \DateTimeZone('GMT')), + 'end' => new \DateTime('2011-01-01 13:15:00', new \DateTimeZone('GMT')), + ), + ); + // Time range filter on property + $filter30 = array( + 'name' => 'VEVENT', + 'comp-filters' => array(), + 'prop-filters' => array( + array( + 'name' => 'DTSTART', + 'is-not-defined' => false, + 'param-filters' => array(), + 'time-range' => array( + 'start' => new \DateTime('2011-01-01 10:00:00', new \DateTimeZone('GMT')), + 'end' => new \DateTime('2011-01-01 13:00:00', new \DateTimeZone('GMT')), + ), + 'text-match' => null, + ), + ), + 'is-not-defined' => false, + 'time-range' => null, + ); + + // Time range for alarm + $filter31 = array( + 'name' => 'VEVENT', + 'prop-filters' => array(), + 'comp-filters' => array( + array( + 'name' => 'VALARM', + 'is-not-defined' => false, + 'comp-filters' => array(), + 'prop-filters' => array(), + 'time-range' => array( + 'start' => new \DateTime('2011-01-01 10:45:00', new \DateTimeZone('GMT')), + 'end' => new \DateTime('2011-01-01 11:15:00', new \DateTimeZone('GMT')), + ), + 'text-match' => null, + ), + ), + 'is-not-defined' => false, + 'time-range' => null, + ); + $filter32 = $filter31; + $filter32['comp-filters'][0]['time-range'] = array( + 'start' => new \DateTime('2011-01-01 11:45:00', new \DateTimeZone('GMT')), + 'end' => new \DateTime('2011-01-01 12:15:00', new \DateTimeZone('GMT')), + ); + + $filter33 = $filter31; + $filter33['name'] = 'VTODO'; + $filter34 = $filter32; + $filter34['name'] = 'VTODO'; + $filter35 = $filter31; + $filter35['name'] = 'VJOURNAL'; + $filter36 = $filter32; + $filter36['name'] = 'VJOURNAL'; + + // Time range filter on non-datetime property + $filter37 = array( + 'name' => 'VEVENT', + 'comp-filters' => array(), + 'prop-filters' => array( + array( + 'name' => 'SUMMARY', + 'is-not-defined' => false, + 'param-filters' => array(), + 'time-range' => array( + 'start' => new \DateTime('2011-01-01 10:00:00', new \DateTimeZone('GMT')), + 'end' => new \DateTime('2011-01-01 13:00:00', new \DateTimeZone('GMT')), + ), + 'text-match' => null, + ), + ), + 'is-not-defined' => false, + 'time-range' => null, + ); + + $filter38 = array( + 'name' => 'VEVENT', + 'comp-filters' => array(), + 'prop-filters' => array(), + 'is-not-defined' => false, + 'time-range' => array( + 'start' => new \DateTime('2012-07-01 00:00:00', new \DateTimeZone('UTC')), + 'end' => new \DateTime('2012-08-01 00:00:00', new \DateTimeZone('UTC')), + ) + ); + $filter39 = array( + 'name' => 'VEVENT', + 'comp-filters' => array( + array( + 'name' => 'VALARM', + 'comp-filters' => array(), + 'prop-filters' => array(), + 'is-not-defined' => false, + 'time-range' => array( + 'start' => new \DateTime('2012-09-01 00:00:00', new \DateTimeZone('UTC')), + 'end' => new \DateTime('2012-10-01 00:00:00', new \DateTimeZone('UTC')), + ) + ), + ), + 'prop-filters' => array(), + 'is-not-defined' => false, + 'time-range' => null, + ); + + return array( + + // Component check + + array($blob1, $filter1, 1), + array($blob1, $filter2, 0), + array($blob1, $filter3, 0), + array($blob1, $filter4, 1), + + // Subcomponent check (4) + array($blob1, $filter5, 0), + array($blob2, $filter5, 1), + + // Property checki (6) + array($blob1, $filter6, 1), + array($blob1, $filter7, 0), + array($blob1, $filter8, 0), + array($blob1, $filter9, 1), + + // Subcomponent + property (10) + array($blob2, $filter10, 1), + + // Param filter (11) + array($blob3, $filter11, 1), + array($blob3, $filter12, 0), + array($blob3, $filter13, 0), + array($blob3, $filter14, 1), + + // Param + text (15) + array($blob3, $filter15, 1), + array($blob3, $filter16, 0), + array($blob3, $filter17, 0), + array($blob3, $filter18, 1), + + // Prop + text (19) + array($blob2, $filter19, 1), + + // Incorrect object (vcard) (20) + array($blob4, $filter1, -1), + + // Time-range for event (21) + array($blob5, $filter20, 1), + array($blob6, $filter20, 1), + array($blob7, $filter20, 1), + array($blob8, $filter20, 1), + + array($blob5, $filter21, 1), + array($blob5, $filter22, 1), + + array($blob5, $filter23, 0), + array($blob6, $filter23, 0), + array($blob7, $filter23, 0), + array($blob8, $filter23, 0), + + // Time-range for todo (31) + array($blob9, $filter24, 1), + array($blob9, $filter25, 0), + array($blob9, $filter26, 1), + array($blob10, $filter24, 1), + array($blob10, $filter25, 0), + array($blob10, $filter26, 1), + + array($blob11, $filter24, 0), + array($blob11, $filter25, 0), + array($blob11, $filter26, 1), + + array($blob12, $filter24, 1), + array($blob12, $filter25, 0), + array($blob12, $filter26, 0), + + array($blob13, $filter24, 1), + array($blob13, $filter25, 0), + array($blob13, $filter26, 1), + + array($blob14, $filter24, 1), + array($blob14, $filter25, 0), + array($blob14, $filter26, 0), + + array($blob15, $filter24, 1), + array($blob15, $filter25, 1), + array($blob15, $filter26, 1), + + array($blob16, $filter24, 1), + array($blob16, $filter25, 1), + array($blob16, $filter26, 1), + + // Time-range for journals (55) + array($blob17, $filter27, 0), + array($blob17, $filter28, 0), + array($blob18, $filter27, 0), + array($blob18, $filter28, 1), + array($blob19, $filter27, 1), + array($blob19, $filter28, 1), + + // Time-range for free-busy (61) + array($blob20, $filter29, -1), + + // Time-range on property (62) + array($blob5, $filter30, 1), + array($blob3, $filter37, -1), + array($blob3, $filter30, 0), + + // Time-range on alarm in vevent (65) + array($blob21, $filter31, 1), + array($blob21, $filter32, 0), + array($blob22, $filter31, 1), + array($blob22, $filter32, 0), + array($blob23, $filter31, 1), + array($blob23, $filter32, 0), + array($blob24, $filter31, 1), + array($blob24, $filter32, 0), + array($blob25, $filter31, 1), + array($blob25, $filter32, 0), + array($blob26, $filter31, 1), + array($blob26, $filter32, 0), + + // Time-range on alarm for vtodo (77) + array($blob27, $filter33, 1), + array($blob27, $filter34, 0), + + // Time-range on alarm for vjournal (79) + array($blob28, $filter35, -1), + array($blob28, $filter36, -1), + + // Time-range on alarm with duration (81) + array($blob29, $filter31, 1), + array($blob29, $filter32, 0), + array($blob30, $filter31, 0), + array($blob30, $filter32, 0), + + // Time-range with RRULE (85) + array($blob31, $filter20, 1), + array($blob32, $filter20, 0), + + // Bug reported on mailing list, related to all-day events (87) + //array($blob33, $filter38, 1), + + // Event in timerange, but filtered alarm is in the far future (88). + array($blob34, $filter39, 0), + ); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarTest.php new file mode 100644 index 0000000..ea744d2 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/CalendarTest.php @@ -0,0 +1,289 @@ +markTestSkipped('SQLite driver is not available'); + + $this->backend = TestUtil::getBackend(); + + $this->calendars = $this->backend->getCalendarsForUser('principals/user1'); + $this->assertEquals(2, count($this->calendars)); + $this->calendar = new Calendar($this->backend, $this->calendars[0]); + + + } + + function teardown() { + + unset($this->backend); + + } + + function testSimple() { + + $this->assertEquals($this->calendars[0]['uri'], $this->calendar->getName()); + + } + + /** + * @depends testSimple + */ + function testUpdateProperties() { + + $propPatch = new PropPatch([ + '{DAV:}displayname' => 'NewName', + ]); + + $result = $this->calendar->propPatch($propPatch); + $result = $propPatch->commit(); + + $this->assertEquals(true, $result); + + $calendars2 = $this->backend->getCalendarsForUser('principals/user1'); + $this->assertEquals('NewName',$calendars2[0]['{DAV:}displayname']); + + } + + /** + * @depends testSimple + */ + function testGetProperties() { + + $question = array( + '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set', + ); + + $result = $this->calendar->getProperties($question); + + foreach($question as $q) $this->assertArrayHasKey($q,$result); + + $this->assertEquals(array('VEVENT','VTODO'), $result['{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set']->getValue()); + + } + + /** + * @expectedException Sabre\DAV\Exception\NotFound + * @depends testSimple + */ + function testGetChildNotFound() { + + $this->calendar->getChild('randomname'); + + } + + /** + * @depends testSimple + */ + function testGetChildren() { + + $children = $this->calendar->getChildren(); + $this->assertEquals(1,count($children)); + + $this->assertTrue($children[0] instanceof CalendarObject); + + } + + /** + * @depends testGetChildren + */ + function testChildExists() { + + $this->assertFalse($this->calendar->childExists('foo')); + + $children = $this->calendar->getChildren(); + $this->assertTrue($this->calendar->childExists($children[0]->getName())); + } + + + + /** + * @expectedException Sabre\DAV\Exception\MethodNotAllowed + */ + function testCreateDirectory() { + + $this->calendar->createDirectory('hello'); + + } + + /** + * @expectedException Sabre\DAV\Exception\MethodNotAllowed + */ + function testSetName() { + + $this->calendar->setName('hello'); + + } + + function testGetLastModified() { + + $this->assertNull($this->calendar->getLastModified()); + + } + + function testCreateFile() { + + $file = fopen('php://memory','r+'); + fwrite($file,TestUtil::getTestCalendarData()); + rewind($file); + + $this->calendar->createFile('hello',$file); + + $file = $this->calendar->getChild('hello'); + $this->assertTrue($file instanceof CalendarObject); + + } + + function testCreateFileNoSupportedComponents() { + + $file = fopen('php://memory','r+'); + fwrite($file,TestUtil::getTestCalendarData()); + rewind($file); + + $calendar = new Calendar($this->backend, $this->calendars[1]); + $calendar->createFile('hello',$file); + + $file = $calendar->getChild('hello'); + $this->assertTrue($file instanceof CalendarObject); + + } + + function testDelete() { + + $this->calendar->delete(); + + $calendars = $this->backend->getCalendarsForUser('principals/user1'); + $this->assertEquals(1, count($calendars)); + } + + function testGetOwner() { + + $this->assertEquals('principals/user1',$this->calendar->getOwner()); + + } + + function testGetGroup() { + + $this->assertNull($this->calendar->getGroup()); + + } + + function testGetACL() { + + $expected = array( + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1/calendar-proxy-write', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1/calendar-proxy-read', + 'protected' => true, + ), + array( + 'privilege' => '{' . Plugin::NS_CALDAV . '}read-free-busy', + 'principal' => '{DAV:}authenticated', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}write', + 'principal' => 'principals/user1', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}write', + 'principal' => 'principals/user1/calendar-proxy-write', + 'protected' => true, + ), + ); + $this->assertEquals($expected, $this->calendar->getACL()); + + } + + /** + * @expectedException Sabre\DAV\Exception\MethodNotAllowed + */ + function testSetACL() { + + $this->calendar->setACL(array()); + + } + + function testGetSupportedPrivilegesSet() { + + $result = $this->calendar->getSupportedPrivilegeSet(); + + $this->assertEquals( + '{' . Plugin::NS_CALDAV . '}read-free-busy', + $result['aggregates'][0]['aggregates'][2]['privilege'] + ); + + } + + function testGetSyncToken() { + + $this->assertEquals(2, $this->calendar->getSyncToken()); + + } + function testGetSyncToken2() { + + $calendar = new Calendar(new Backend\Mock([],[]), [ + '{DAV:}sync-token' => 2 + ]); + $this->assertEquals(2, $this->calendar->getSyncToken()); + + } + + function testGetSyncTokenNoSyncSupport() { + + $calendar = new Calendar(new Backend\Mock([],[]), []); + $this->assertNull($calendar->getSyncToken()); + + } + + function testGetChanges() { + + $this->assertEquals([ + 'syncToken' => 2, + 'modified' => [], + 'deleted' => [], + 'added' => ['UUID-2345'], + ], $this->calendar->getChanges(1, 1)); + + } + + function testGetChangesNoSyncSupport() { + + $calendar = new Calendar(new Backend\Mock([],[]), []); + $this->assertNull($calendar->getChanges(1,null)); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/ExpandEventsDTSTARTandDTENDTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/ExpandEventsDTSTARTandDTENDTest.php new file mode 100644 index 0000000..ada32c6 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/ExpandEventsDTSTARTandDTENDTest.php @@ -0,0 +1,114 @@ + 1, + 'name' => 'Calendar', + 'principaluri' => 'principals/user1', + 'uri' => 'calendar1', + ) + ); + + protected $caldavCalendarObjects = array( + 1 => array( + 'event.ics' => array( + 'calendardata' => 'BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +DTEND;TZID=Europe/Berlin:20120207T191500 +RRULE:FREQ=DAILY;INTERVAL=1;COUNT=3 +SUMMARY:RecurringEvents 3 times +DTSTART;TZID=Europe/Berlin:20120207T181500 +END:VEVENT +BEGIN:VEVENT +CREATED:20120207T111900Z +UID:foobar +DTEND;TZID=Europe/Berlin:20120208T191500 +SUMMARY:RecurringEvents 3 times OVERWRITTEN +DTSTART;TZID=Europe/Berlin:20120208T181500 +RECURRENCE-ID;TZID=Europe/Berlin:20120208T181500 +END:VEVENT +END:VCALENDAR +', + ), + ), + ); + + function testExpand() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_CONTENT_TYPE' => 'application/xml', + 'REQUEST_URI' => '/calendars/user1/calendar1', + 'HTTP_DEPTH' => '1', + ]); + + $request->setBody(' + + + + + + + + + + + + + + +'); + + $response = $this->request($request); + + // Everts super awesome xml parser. + $body = substr( + $response->body, + $start = strpos($response->body, 'BEGIN:VCALENDAR'), + strpos($response->body, 'END:VCALENDAR') - $start + 13 + ); + $body = str_replace(' ','',$body); + + try { + $vObject = VObject\Reader::read($body); + } catch (VObject\ParseException $e) { + $this->fail('Could not parse object. Error:' . $e->getMessage(). ' full object: ' . $response->getBodyAsString()); + } + + // check if DTSTARTs and DTENDs are correct + foreach ($vObject->VEVENT as $vevent) { + /** @var $vevent Sabre\VObject\Component\VEvent */ + foreach ($vevent->children as $child) { + /** @var $child Sabre\VObject\Property */ + + if ($child->name == 'DTSTART') { + // DTSTART has to be one of three valid values + $this->assertContains($child->getValue(), ['20120207T171500Z', '20120208T171500Z', '20120209T171500Z'], 'DTSTART is not a valid value: '.$child->getValue()); + } elseif ($child->name == 'DTEND') { + // DTEND has to be one of three valid values + $this->assertContains($child->getValue(), ['20120207T181500Z', '20120208T181500Z', '20120209T181500Z'], 'DTEND is not a valid value: '.$child->getValue()); + } + } + } + } + +} + diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/ExpandEventsDTSTARTandDTENDbyDayTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/ExpandEventsDTSTARTandDTENDbyDayTest.php new file mode 100644 index 0000000..01a2e31 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/ExpandEventsDTSTARTandDTENDbyDayTest.php @@ -0,0 +1,103 @@ + 1, + 'name' => 'Calendar', + 'principaluri' => 'principals/user1', + 'uri' => 'calendar1', + ) + ); + + protected $caldavCalendarObjects = array( + 1 => array( + 'event.ics' => array( + 'calendardata' => 'BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +DTEND;TZID=Europe/Berlin:20120207T191500 +RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=TU,TH +SUMMARY:RecurringEvents on tuesday and thursday +DTSTART;TZID=Europe/Berlin:20120207T181500 +END:VEVENT +END:VCALENDAR +', + ), + ), + ); + + function testExpandRecurringByDayEvent() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_CONTENT_TYPE' => 'application/xml', + 'REQUEST_URI' => '/calendars/user1/calendar1', + 'HTTP_DEPTH' => '1', + )); + + $request->setBody(' + + + + + + + + + + + + + + +'); + + $response = $this->request($request); + + // Everts super awesome xml parser. + $body = substr( + $response->body, + $start = strpos($response->body, 'BEGIN:VCALENDAR'), + strpos($response->body, 'END:VCALENDAR') - $start + 13 + ); + $body = str_replace(' ','',$body); + + $vObject = VObject\Reader::read($body); + + $this->assertEquals(2, count($vObject->VEVENT)); + + // check if DTSTARTs and DTENDs are correct + foreach ($vObject->VEVENT as $vevent) { + /** @var $vevent Sabre\VObject\Component\VEvent */ + foreach ($vevent->children as $child) { + /** @var $child Sabre\VObject\Property */ + + if ($child->name == 'DTSTART') { + // DTSTART has to be one of two valid values + $this->assertContains($child->getValue(), array('20120214T171500Z', '20120216T171500Z'), 'DTSTART is not a valid value: '.$child->getValue()); + } elseif ($child->name == 'DTEND') { + // DTEND has to be one of two valid values + $this->assertContains($child->getValue(), array('20120214T181500Z', '20120216T181500Z'), 'DTEND is not a valid value: '.$child->getValue()); + } + } + } + } + +} + diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/ExpandEventsDoubleEventsTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/ExpandEventsDoubleEventsTest.php new file mode 100644 index 0000000..b64fb12 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/ExpandEventsDoubleEventsTest.php @@ -0,0 +1,104 @@ + 1, + 'name' => 'Calendar', + 'principaluri' => 'principals/user1', + 'uri' => 'calendar1', + ) + ); + + protected $caldavCalendarObjects = array( + 1 => array( + 'event.ics' => array( + 'calendardata' => 'BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +DTEND;TZID=Europe/Berlin:20120207T191500 +RRULE:FREQ=DAILY;INTERVAL=1;COUNT=3 +SUMMARY:RecurringEvents 3 times +DTSTART;TZID=Europe/Berlin:20120207T181500 +END:VEVENT +BEGIN:VEVENT +CREATED:20120207T111900Z +UID:foobar +DTEND;TZID=Europe/Berlin:20120208T191500 +SUMMARY:RecurringEvents 3 times OVERWRITTEN +DTSTART;TZID=Europe/Berlin:20120208T181500 +RECURRENCE-ID;TZID=Europe/Berlin:20120208T181500 +END:VEVENT +END:VCALENDAR +', + ), + ), + ); + + function testExpand() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_CONTENT_TYPE' => 'application/xml', + 'REQUEST_URI' => '/calendars/user1/calendar1', + 'HTTP_DEPTH' => '1', + ]); + + $request->setBody(' + + + + + + + + + + + + + + +'); + + $response = $this->request($request); + + // Everts super awesome xml parser. + $body = substr( + $response->body, + $start = strpos($response->body, 'BEGIN:VCALENDAR'), + strpos($response->body, 'END:VCALENDAR') - $start + 13 + ); + $body = str_replace(' ','',$body); + + $vObject = VObject\Reader::read($body); + + // We only expect 3 events + $this->assertEquals(3, count($vObject->VEVENT),'We got 6 events instead of 3. Output: ' . $body); + + // TZID should be gone + $this->assertFalse(isset($vObject->VEVENT->DTSTART['TZID'])); + + } + +} + diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/ExpandEventsFloatingTimeTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/ExpandEventsFloatingTimeTest.php new file mode 100644 index 0000000..906f2ef --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/ExpandEventsFloatingTimeTest.php @@ -0,0 +1,210 @@ + 1, + 'name' => 'Calendar', + 'principaluri' => 'principals/user1', + 'uri' => 'calendar1', + '{urn:ietf:params:xml:ns:caldav}calendar-timezone' => 'BEGIN:VCALENDAR +VERSION:2.0 +CALSCALE:GREGORIAN +BEGIN:VTIMEZONE +TZID:Europe/Berlin +BEGIN:DAYLIGHT +TZOFFSETFROM:+0100 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU +DTSTART:19810329T020000 +TZNAME:GMT+2 +TZOFFSETTO:+0200 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:+0200 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU +DTSTART:19961027T030000 +TZNAME:GMT+1 +TZOFFSETTO:+0100 +END:STANDARD +END:VTIMEZONE +END:VCALENDAR', + ) + ); + + protected $caldavCalendarObjects = array( + 1 => array( + 'event.ics' => array( + 'calendardata' => 'BEGIN:VCALENDAR +VERSION:2.0 +CALSCALE:GREGORIAN +BEGIN:VEVENT +CREATED:20140701T143658Z +UID:dba46fe8-1631-4d98-a575-97963c364dfe +DTEND:20141108T073000 +TRANSP:OPAQUE +SUMMARY:Floating Time event, starting 05:30am Europe/Berlin +DTSTART:20141108T053000 +DTSTAMP:20140701T143706Z +SEQUENCE:1 +END:VEVENT +END:VCALENDAR +', + ), + ), + ); + + function testExpandCalendarQuery() { + + $request = new HTTP\Request('REPORT', '/calendars/user1/calendar1', [ + 'Depth' => 1, + 'Content-Type' => 'application/xml', + ]); + + $request->setBody(' + + + + + + + + + + + + + + +'); + + $response = $this->request($request); + + // Everts super awesome xml parser. + $body = substr( + $response->body, + $start = strpos($response->body, 'BEGIN:VCALENDAR'), + strpos($response->body, 'END:VCALENDAR') - $start + 13 + ); + $body = str_replace(' ','',$body); + + $vObject = VObject\Reader::read($body); + + // check if DTSTARTs and DTENDs are correct + foreach ($vObject->VEVENT as $vevent) { + /** @var $vevent Sabre\VObject\Component\VEvent */ + foreach ($vevent->children as $child) { + /** @var $child Sabre\VObject\Property */ + + if ($child->name == 'DTSTART') { + // DTSTART should be the UTC equivalent of given floating time + $this->assertEquals($child->getValue(), '20141108T043000Z'); + } elseif ($child->name == 'DTEND') { + // DTEND should be the UTC equivalent of given floating time + $this->assertEquals($child->getValue(), '20141108T063000Z'); + } + } + } + } + + function testExpandMultiGet() { + + $request = new HTTP\Request('REPORT', '/calendars/user1/calendar1', [ + 'Depth' => 1, + 'Content-Type' => 'application/xml', + ]); + + $request->setBody(' + + + + + + + + /calendars/user1/calendar1/event.ics +'); + + $response = $this->request($request); + + $this->assertEquals(207, $response->getStatus()); + + // Everts super awesome xml parser. + $body = substr( + $response->body, + $start = strpos($response->body, 'BEGIN:VCALENDAR'), + strpos($response->body, 'END:VCALENDAR') - $start + 13 + ); + $body = str_replace(' ','',$body); + + $vObject = VObject\Reader::read($body); + + // check if DTSTARTs and DTENDs are correct + foreach ($vObject->VEVENT as $vevent) { + /** @var $vevent Sabre\VObject\Component\VEvent */ + foreach ($vevent->children as $child) { + /** @var $child Sabre\VObject\Property */ + + if ($child->name == 'DTSTART') { + // DTSTART should be the UTC equivalent of given floating time + $this->assertEquals($child->getValue(), '20141108T043000Z'); + } elseif ($child->name == 'DTEND') { + // DTEND should be the UTC equivalent of given floating time + $this->assertEquals($child->getValue(), '20141108T063000Z'); + } + } + } + } + + function testExpandExport() { + + $request = new HTTP\Request('GET', '/calendars/user1/calendar1?export&start=1&end=2000000000&expand=1', [ + 'Depth' => 1, + 'Content-Type' => 'application/xml', + ]); + + $response = $this->request($request); + + $this->assertEquals(200, $response->getStatus()); + + // Everts super awesome xml parser. + $body = substr( + $response->body, + $start = strpos($response->body, 'BEGIN:VCALENDAR'), + strpos($response->body, 'END:VCALENDAR') - $start + 13 + ); + $body = str_replace(' ','',$body); + + $vObject = VObject\Reader::read($body); + + // check if DTSTARTs and DTENDs are correct + foreach ($vObject->VEVENT as $vevent) { + /** @var $vevent Sabre\VObject\Component\VEvent */ + foreach ($vevent->children as $child) { + /** @var $child Sabre\VObject\Property */ + + if ($child->name == 'DTSTART') { + // DTSTART should be the UTC equivalent of given floating time + $this->assertEquals($child->getValue(), '20141108T043000Z'); + } elseif ($child->name == 'DTEND') { + // DTEND should be the UTC equivalent of given floating time + $this->assertEquals($child->getValue(), '20141108T063000Z'); + } + } + } + } + +} + diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/FreeBusyReportTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/FreeBusyReportTest.php new file mode 100644 index 0000000..84f05f3 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/FreeBusyReportTest.php @@ -0,0 +1,176 @@ + [ + 'obj1' => [ + 'calendarid' => 1, + 'uri' => 'event1.ics', + 'calendardata' => $obj1, + ], + 'obj2' => [ + 'calendarid' => 1, + 'uri' => 'event2.ics', + 'calendardata' => $obj2 + ], + 'obj3' => [ + 'calendarid' => 1, + 'uri' => 'event3.ics', + 'calendardata' => $obj3 + ] + ], + ]; + + + $caldavBackend = new Backend\Mock([], $calendarData); + + $calendar = new Calendar($caldavBackend, [ + 'id' => 1, + 'uri' => 'calendar', + 'principaluri' => 'principals/user1', + '{' . Plugin::NS_CALDAV . '}calendar-timezone' => "BEGIN:VCALENDAR\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/Berlin\r\nEND:VTIMEZONE\r\nEND:VCALENDAR", + ]); + + $this->server = new DAV\Server([$calendar]); + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_URI' => '/calendar', + ]); + $this->server->httpRequest = $request; + $this->server->httpResponse = new HTTP\ResponseMock(); + + $this->plugin = new Plugin(); + $this->server->addPlugin($this->plugin); + + } + + function testFreeBusyReport() { + + $reportXML = << + + + +XML; + + $report = $this->server->xml->parse($reportXML, null, $rootElem); + $this->plugin->report($rootElem, $report); + + $this->assertEquals(200, $this->server->httpResponse->status); + $this->assertEquals('text/calendar', $this->server->httpResponse->getHeader('Content-Type')); + $this->assertTrue(strpos($this->server->httpResponse->body, 'BEGIN:VFREEBUSY')!==false); + $this->assertTrue(strpos($this->server->httpResponse->body, '20111005T120000Z/20111005T130000Z')!==false); + $this->assertTrue(strpos($this->server->httpResponse->body, '20111006T100000Z/20111006T110000Z')!==false); + + } + + /** + * @expectedException Sabre\DAV\Exception\BadRequest + */ + function testFreeBusyReportNoTimeRange() { + + $reportXML = << + + +XML; + + $report = $this->server->xml->parse($reportXML, null, $rootElem); + $this->plugin->report($rootElem, $report); + + } + + /** + * @expectedException Sabre\DAV\Exception\NotImplemented + */ + function testFreeBusyReportWrongNode() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_URI' => '/', + )); + $this->server->httpRequest = $request; + + $reportXML = << + + + +XML; + + $report = $this->server->xml->parse($reportXML, null, $rootElem); + $this->plugin->report($rootElem, $report); + + } + + /** + * @expectedException Sabre\DAV\Exception + */ + function testFreeBusyReportNoACLPlugin() { + + $this->server = new DAV\Server(); + $this->plugin = new Plugin(); + $this->server->addPlugin($this->plugin); + + $reportXML = << + + + +XML; + + $report = $this->server->xml->parse($reportXML, null, $rootElem); + $this->plugin->report($rootElem, $report); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/GetEventsByTimerangeTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/GetEventsByTimerangeTest.php new file mode 100644 index 0000000..23ba079 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/GetEventsByTimerangeTest.php @@ -0,0 +1,98 @@ + 1, + 'name' => 'Calendar', + 'principaluri' => 'principals/user1', + 'uri' => 'calendar1', + ) + ); + + protected $caldavCalendarObjects = array( + 1 => array( + 'event.ics' => array( + 'calendardata' => 'BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +CREATED:20120313T142342Z +UID:171EBEFC-C951-499D-B234-7BA7D677B45D +DTEND;TZID=Europe/Berlin:20120227T010000 +TRANSP:OPAQUE +SUMMARY:Monday 0h +DTSTART;TZID=Europe/Berlin:20120227T000000 +DTSTAMP:20120313T142416Z +SEQUENCE:4 +END:VEVENT +END:VCALENDAR +', + ), + ), + ); + + function testQueryTimerange() { + + $request = new HTTP\Request( + 'REPORT', + '/calendars/user1/calendar1', + [ + 'Content-Type' => 'application/xml', + 'Depth' => '1', + ] + ); + + $request->setBody(' + + + + + + + + + + + + + + +'); + + $response = $this->request($request); + + if (strpos($response->body, 'BEGIN:VCALENDAR') === false) { + $this->fail('Got no events instead of 1. Output: '.$response->body); + } + + // Everts super awesome xml parser. + $body = substr( + $response->body, + $start = strpos($response->body, 'BEGIN:VCALENDAR'), + strpos($response->body, 'END:VCALENDAR') - $start + 13 + ); + $body = str_replace(' ','',$body); + + $vObject = VObject\Reader::read($body); + + // We expect 1 event + $this->assertEquals(1, count($vObject->VEVENT), 'We got 0 events instead of 1. Output: ' . $body); + + } + +} + diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/ICSExportPluginTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/ICSExportPluginTest.php new file mode 100644 index 0000000..f7e4432 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/ICSExportPluginTest.php @@ -0,0 +1,662 @@ +markTestSkipped('SQLite driver is not available'); + + } + + function testInit() { + + $p = new ICSExportPlugin(); + $s = new DAV\Server(); + $s->addPlugin($p); + $this->assertEquals($p, $s->getPlugin('ics-export')); + + } + + function testBeforeMethod() { + + $cbackend = TestUtil::getBackend(); + + $props = [ + 'uri'=>'UUID-123467', + 'principaluri' => 'admin', + 'id' => 1, + '{DAV:}displayname' => 'Hello!', + '{http://apple.com/ns/ical/}calendar-color' => '#AA0000FF', + ]; + $tree = [ + new Calendar($cbackend,$props), + ]; + + $p = new ICSExportPlugin(); + + $s = new DAV\Server($tree); + $s->addPlugin($p); + $s->addPlugin(new Plugin()); + + $h = HTTP\Sapi::createFromServerArray([ + 'REQUEST_URI' => '/UUID-123467?export', + 'REQUEST_METHOD' => 'GET', + ]); + + $s->httpRequest = $h; + $s->httpResponse = new HTTP\ResponseMock(); + + $this->assertFalse($p->httpGet($h, $s->httpResponse)); + + $this->assertEquals(200, $s->httpResponse->status); + $this->assertEquals([ + 'Content-Type' => ['text/calendar'], + ], $s->httpResponse->getHeaders()); + + $obj = VObject\Reader::read($s->httpResponse->body); + + $this->assertEquals(7,count($obj->children())); + $this->assertEquals(1,count($obj->VERSION)); + $this->assertEquals(1,count($obj->CALSCALE)); + $this->assertEquals(1,count($obj->PRODID)); + $this->assertTrue(strpos((string)$obj->PRODID, DAV\Version::VERSION)!==false); + $this->assertEquals(1,count($obj->VTIMEZONE)); + $this->assertEquals(1,count($obj->VEVENT)); + $this->assertEquals("Hello!", $obj->{"X-WR-CALNAME"}); + $this->assertEquals("#AA0000FF", $obj->{"X-APPLE-CALENDAR-COLOR"}); + + } + function testBeforeMethodNoVersion() { + + if (!SABRE_HASSQLITE) $this->markTestSkipped('SQLite driver is not available'); + $cbackend = TestUtil::getBackend(); + + $props = [ + 'uri'=>'UUID-123467', + 'principaluri' => 'admin', + 'id' => 1, + ]; + $tree = [ + new Calendar($cbackend,$props), + ]; + + $p = new ICSExportPlugin(); + + $s = new DAV\Server($tree); + + $s->addPlugin($p); + $s->addPlugin(new Plugin()); + + $h = HTTP\Sapi::createFromServerArray([ + 'REQUEST_URI' => '/UUID-123467?export', + 'REQUEST_METHOD' => 'GET', + ]); + + $s->httpRequest = $h; + $s->httpResponse = new HTTP\ResponseMock(); + + DAV\Server::$exposeVersion = false; + $this->assertFalse($p->httpGet($h, $s->httpResponse)); + DAV\Server::$exposeVersion = true; + + $this->assertEquals(200, $s->httpResponse->status); + $this->assertEquals([ + 'Content-Type' => ['text/calendar'], + ], $s->httpResponse->getHeaders()); + + $obj = VObject\Reader::read($s->httpResponse->body); + + $this->assertEquals(5,count($obj->children())); + $this->assertEquals(1,count($obj->VERSION)); + $this->assertEquals(1,count($obj->CALSCALE)); + $this->assertEquals(1,count($obj->PRODID)); + $this->assertFalse(strpos((string)$obj->PRODID, DAV\Version::VERSION)!==false); + $this->assertEquals(1,count($obj->VTIMEZONE)); + $this->assertEquals(1,count($obj->VEVENT)); + + } + + function testBeforeMethodNoExport() { + + $p = new ICSExportPlugin(); + + $s = new DAV\Server(); + $s->addPlugin($p); + + $h = HTTP\Sapi::createFromServerArray([ + 'REQUEST_URI' => '/UUID-123467', + 'REQUEST_METHOD' => 'GET', + ]); + $this->assertNull($p->httpGet($h, $s->httpResponse)); + + } + + function testACLIntegrationBlocked() { + + $cbackend = TestUtil::getBackend(); + + $props = array( + 'uri'=>'UUID-123467', + 'principaluri' => 'admin', + 'id' => 1, + ); + $tree = array( + new Calendar($cbackend,$props), + ); + + $p = new ICSExportPlugin(); + + $s = new DAV\Server($tree); + $s->addPlugin($p); + $s->addPlugin(new Plugin()); + $s->addPlugin(new DAVACL\Plugin()); + + $h = HTTP\Sapi::createFromServerArray([ + 'REQUEST_URI' => '/UUID-123467?export', + ]); + + $s->httpRequest = $h; + $s->httpResponse = new HTTP\ResponseMock(); + + $p->httpGet($h, $s->httpResponse); + + // If the ACL system blocked this request, the effect will be that + // there's no response, because the calendar information could not be + // fetched. + $this->assertNull($s->httpResponse->getStatus()); + + } + + function testACLIntegrationNotBlocked() { + + $cbackend = TestUtil::getBackend(); + $pbackend = new DAVACL\PrincipalBackend\Mock(); + + $props = array( + 'uri'=>'UUID-123467', + 'principaluri' => 'admin', + 'id' => 1, + ); + $tree = array( + new Calendar($cbackend,$props), + new DAVACL\PrincipalCollection($pbackend), + ); + + $p = new ICSExportPlugin(); + + $s = new DAV\Server($tree); + $s->sapi = new HTTP\SapiMock(); + $s->addPlugin($p); + $s->addPlugin(new Plugin()); + $s->addPlugin(new DAVACL\Plugin()); + $s->addPlugin(new DAV\Auth\Plugin(new DAV\Auth\Backend\Mock(),'SabreDAV')); + + // Forcing login + $s->getPlugin('acl')->adminPrincipals = array('principals/admin'); + + + $h = HTTP\Sapi::createFromServerArray([ + 'REQUEST_URI' => '/UUID-123467?export', + 'REQUEST_METHOD' => 'GET', + ]); + + $s->httpRequest = $h; + $s->httpResponse = new HTTP\ResponseMock(); + + $s->exec(); + + $this->assertEquals(200, $s->httpResponse->status,'Invalid status received. Response body: '. $s->httpResponse->body); + $this->assertEquals(array( + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['text/calendar'], + ), $s->httpResponse->getHeaders()); + + $obj = VObject\Reader::read($s->httpResponse->body); + + $this->assertEquals(5,count($obj->children())); + $this->assertEquals(1,count($obj->VERSION)); + $this->assertEquals(1,count($obj->CALSCALE)); + $this->assertEquals(1,count($obj->PRODID)); + $this->assertEquals(1,count($obj->VTIMEZONE)); + $this->assertEquals(1,count($obj->VEVENT)); + + } + + function testBadStartParam() { + + $cbackend = TestUtil::getBackend(); + $pbackend = new DAVACL\PrincipalBackend\Mock(); + + $props = array( + 'uri'=>'UUID-123467', + 'principaluri' => 'admin', + 'id' => 1, + ); + $tree = array( + new Calendar($cbackend,$props), + new DAVACL\PrincipalCollection($pbackend), + ); + + $p = new ICSExportPlugin(); + + $s = new DAV\Server($tree); + $s->sapi = new HTTP\SapiMock(); + $s->addPlugin($p); + $s->addPlugin(new Plugin()); + + $h = HTTP\Sapi::createFromServerArray([ + 'REQUEST_URI' => '/UUID-123467?export&start=foo', + 'REQUEST_METHOD' => 'GET', + ]); + + $s->httpRequest = $h; + $s->httpResponse = new HTTP\ResponseMock(); + + $s->exec(); + + $this->assertEquals(400, $s->httpResponse->status,'Invalid status received. Response body: '. $s->httpResponse->body); + + } + + function testBadEndParam() { + + $cbackend = TestUtil::getBackend(); + $pbackend = new DAVACL\PrincipalBackend\Mock(); + + $props = array( + 'uri'=>'UUID-123467', + 'principaluri' => 'admin', + 'id' => 1, + ); + $tree = array( + new Calendar($cbackend,$props), + new DAVACL\PrincipalCollection($pbackend), + ); + + $p = new ICSExportPlugin(); + + $s = new DAV\Server($tree); + $s->sapi = new HTTP\SapiMock(); + $s->addPlugin($p); + $s->addPlugin(new Plugin()); + + $h = HTTP\Sapi::createFromServerArray([ + 'REQUEST_URI' => '/UUID-123467?export&end=foo', + 'REQUEST_METHOD' => 'GET', + ]); + + $s->httpRequest = $h; + $s->httpResponse = new HTTP\ResponseMock(); + + $s->exec(); + + $this->assertEquals(400, $s->httpResponse->status,'Invalid status received. Response body: '. $s->httpResponse->body); + + } + + function testFilterStartEnd() { + + $cbackend = TestUtil::getBackend(); + $pbackend = new DAVACL\PrincipalBackend\Mock(); + + $props = array( + 'uri'=>'UUID-123467', + 'principaluri' => 'admin', + 'id' => 1, + ); + $tree = array( + new Calendar($cbackend,$props), + new DAVACL\PrincipalCollection($pbackend), + ); + + $p = new ICSExportPlugin(); + + $s = new DAV\Server($tree); + $s->sapi = new HTTP\SapiMock(); + $s->addPlugin($p); + $s->addPlugin(new Plugin()); + + $h = HTTP\Sapi::createFromServerArray([ + 'REQUEST_URI' => '/UUID-123467?export&start=1&end=2', + 'REQUEST_METHOD' => 'GET', + ]); + + $s->httpRequest = $h; + $s->httpResponse = new HTTP\ResponseMock(); + + $s->exec(); + + $this->assertEquals(200, $s->httpResponse->status,'Invalid status received. Response body: '. $s->httpResponse->body); + $obj = VObject\Reader::read($s->httpResponse->body); + + $this->assertEquals(0,count($obj->VTIMEZONE)); + $this->assertEquals(0,count($obj->VEVENT)); + + } + + function testExpandNoStart() { + + $cbackend = TestUtil::getBackend(); + $pbackend = new DAVACL\PrincipalBackend\Mock(); + + $props = array( + 'uri'=>'UUID-123467', + 'principaluri' => 'admin', + 'id' => 1, + ); + $tree = array( + new Calendar($cbackend,$props), + new DAVACL\PrincipalCollection($pbackend), + ); + + $p = new ICSExportPlugin(); + + $s = new DAV\Server($tree); + $s->sapi = new HTTP\SapiMock(); + $s->addPlugin($p); + $s->addPlugin(new Plugin()); + + $h = HTTP\Sapi::createFromServerArray([ + 'REQUEST_URI' => '/UUID-123467?export&expand=1&end=1', + 'REQUEST_METHOD' => 'GET', + ]); + + $s->httpRequest = $h; + $s->httpResponse = new HTTP\ResponseMock(); + + $s->exec(); + + $this->assertEquals(400, $s->httpResponse->status,'Invalid status received. Response body: '. $s->httpResponse->body); + + } + + function testExpand() { + + $cbackend = TestUtil::getBackend(); + $pbackend = new DAVACL\PrincipalBackend\Mock(); + + $props = array( + 'uri'=>'UUID-123467', + 'principaluri' => 'admin', + 'id' => 1, + ); + $tree = array( + new Calendar($cbackend,$props), + new DAVACL\PrincipalCollection($pbackend), + ); + + $p = new ICSExportPlugin(); + + $s = new DAV\Server($tree); + $s->sapi = new HTTP\SapiMock(); + $s->addPlugin($p); + $s->addPlugin(new Plugin()); + + $h = HTTP\Sapi::createFromServerArray([ + 'REQUEST_URI' => '/UUID-123467?export&start=1&end=2000000000&expand=1', + 'REQUEST_METHOD' => 'GET', + ]); + + $s->httpRequest = $h; + $s->httpResponse = new HTTP\ResponseMock(); + + $s->exec(); + + $this->assertEquals(200, $s->httpResponse->status,'Invalid status received. Response body: '. $s->httpResponse->body); + $obj = VObject\Reader::read($s->httpResponse->body); + + $this->assertEquals(0,count($obj->VTIMEZONE)); + $this->assertEquals(1,count($obj->VEVENT)); + + } + + function testJCal() { + + $cbackend = TestUtil::getBackend(); + $pbackend = new DAVACL\PrincipalBackend\Mock(); + + $props = array( + 'uri'=>'UUID-123467', + 'principaluri' => 'admin', + 'id' => 1, + ); + $tree = array( + new Calendar($cbackend,$props), + new DAVACL\PrincipalCollection($pbackend), + ); + + $p = new ICSExportPlugin(); + + $s = new DAV\Server($tree); + $s->sapi = new HTTP\SapiMock(); + $s->addPlugin($p); + $s->addPlugin(new Plugin()); + + $h = HTTP\Sapi::createFromServerArray([ + 'REQUEST_URI' => '/UUID-123467?export', + 'REQUEST_METHOD' => 'GET', + 'HTTP_ACCEPT' => 'application/calendar+json', + ]); + + $s->httpRequest = $h; + $s->httpResponse = new HTTP\ResponseMock(); + + $s->exec(); + + $this->assertEquals(200, $s->httpResponse->status,'Invalid status received. Response body: '. $s->httpResponse->body); + $this->assertEquals('application/calendar+json', $s->httpResponse->getHeader('Content-Type')); + + } + + function testJCalInUrl() { + + $cbackend = TestUtil::getBackend(); + $pbackend = new DAVACL\PrincipalBackend\Mock(); + + $props = array( + 'uri'=>'UUID-123467', + 'principaluri' => 'admin', + 'id' => 1, + ); + $tree = array( + new Calendar($cbackend,$props), + new DAVACL\PrincipalCollection($pbackend), + ); + + $p = new ICSExportPlugin(); + + $s = new DAV\Server($tree); + $s->sapi = new HTTP\SapiMock(); + $s->addPlugin($p); + $s->addPlugin(new Plugin()); + + $h = HTTP\Sapi::createFromServerArray([ + 'REQUEST_URI' => '/UUID-123467?export&accept=jcal', + 'REQUEST_METHOD' => 'GET', + ]); + + $s->httpRequest = $h; + $s->httpResponse = new HTTP\ResponseMock(); + + $s->exec(); + + $this->assertEquals(200, $s->httpResponse->status,'Invalid status received. Response body: '. $s->httpResponse->body); + $this->assertEquals('application/calendar+json', $s->httpResponse->getHeader('Content-Type')); + + } + + function testNegotiateDefault() { + + $cbackend = TestUtil::getBackend(); + $pbackend = new DAVACL\PrincipalBackend\Mock(); + + $props = array( + 'uri'=>'UUID-123467', + 'principaluri' => 'admin', + 'id' => 1, + ); + $tree = array( + new Calendar($cbackend,$props), + new DAVACL\PrincipalCollection($pbackend), + ); + + $p = new ICSExportPlugin(); + + $s = new DAV\Server($tree); + $s->sapi = new HTTP\SapiMock(); + $s->addPlugin($p); + $s->addPlugin(new Plugin()); + + $h = HTTP\Sapi::createFromServerArray([ + 'REQUEST_URI' => '/UUID-123467?export', + 'REQUEST_METHOD' => 'GET', + 'HTTP_ACCEPT' => 'text/plain', + ]); + + $s->httpRequest = $h; + $s->httpResponse = new HTTP\ResponseMock(); + + $s->exec(); + + $this->assertEquals(200, $s->httpResponse->status,'Invalid status received. Response body: '. $s->httpResponse->body); + $this->assertEquals('text/calendar', $s->httpResponse->getHeader('Content-Type')); + + } + + function testFilterComponentVEVENT() { + + $cbackend = TestUtil::getBackend(); + $pbackend = new DAVACL\PrincipalBackend\Mock(); + + $props = array( + 'uri'=>'UUID-123467', + 'principaluri' => 'admin', + 'id' => 1, + ); + // add a todo to the calendar (see /tests/Sabre/TestUtil) + $cbackend->createCalendarObject(1, 'UUID-3456', TestUtil::getTestTODO()); + + $tree = array( + new Calendar($cbackend,$props), + new DAVACL\PrincipalCollection($pbackend), + ); + + $p = new ICSExportPlugin(); + + $s = new DAV\Server($tree); + $s->sapi = new HTTP\SapiMock(); + $s->addPlugin($p); + $s->addPlugin(new Plugin()); + + $h = HTTP\Sapi::createFromServerArray([ + 'REQUEST_URI' => '/UUID-123467?export&componentType=VEVENT', + 'REQUEST_METHOD' => 'GET', + ]); + + $s->httpRequest = $h; + $s->httpResponse = new HTTP\ResponseMock(); + + $s->exec(); + + $this->assertEquals(200, $s->httpResponse->status,'Invalid status received. Response body: '. $s->httpResponse->body); + $obj = VObject\Reader::read($s->httpResponse->body); + + $this->assertEquals(1,count($obj->VTIMEZONE)); + $this->assertEquals(1,count($obj->VEVENT)); + $this->assertEquals(0,count($obj->VTODO)); + + } + + function testFilterComponentVTODO() { + + $cbackend = TestUtil::getBackend(); + $pbackend = new DAVACL\PrincipalBackend\Mock(); + + $props = [ + 'uri'=>'UUID-123467', + 'principaluri' => 'admin', + 'id' => 1, + ]; + // add a todo to the calendar (see /tests/Sabre/TestUtil) + $cbackend->createCalendarObject(1, 'UUID-3456', TestUtil::getTestTODO()); + + $tree = [ + new Calendar($cbackend,$props), + new DAVACL\PrincipalCollection($pbackend), + ]; + + $p = new ICSExportPlugin(); + + $s = new DAV\Server($tree); + $s->sapi = new HTTP\SapiMock(); + $s->addPlugin($p); + $s->addPlugin(new Plugin()); + + $h = HTTP\Sapi::createFromServerArray([ + 'REQUEST_URI' => '/UUID-123467?export&componentType=VTODO', + 'REQUEST_METHOD' => 'GET', + ]); + + $s->httpRequest = $h; + $s->httpResponse = new HTTP\ResponseMock(); + + $s->exec(); + + $this->assertEquals(200, $s->httpResponse->status,'Invalid status received. Response body: '. $s->httpResponse->body); + $obj = VObject\Reader::read($s->httpResponse->body); + + $this->assertEquals(0,count($obj->VTIMEZONE)); + $this->assertEquals(0,count($obj->VEVENT)); + $this->assertEquals(1,count($obj->VTODO)); + + } + + function testFilterComponentBadComponent() { + + $cbackend = TestUtil::getBackend(); + $pbackend = new DAVACL\PrincipalBackend\Mock(); + + $props = [ + 'uri'=>'UUID-123467', + 'principaluri' => 'admin', + 'id' => 1, + ]; + // add a todo to the calendar (see /tests/Sabre/TestUtil) + $cbackend->createCalendarObject(1, 'UUID-3456', TestUtil::getTestTODO()); + + $tree = [ + new Calendar($cbackend,$props), + new DAVACL\PrincipalCollection($pbackend), + ]; + + $p = new ICSExportPlugin(); + + $s = new DAV\Server($tree); + $s->sapi = new HTTP\SapiMock(); + $s->addPlugin($p); + $s->addPlugin(new Plugin()); + + $h = HTTP\Sapi::createFromServerArray([ + 'REQUEST_URI' => '/UUID-123467?export&componentType=VVOODOO', + 'REQUEST_METHOD' => 'GET', + ]); + + $s->httpRequest = $h; + $s->httpResponse = new HTTP\ResponseMock(); + + $s->exec(); + + $this->assertEquals(400, $s->httpResponse->status,'Invalid status received. Response body: '. $s->httpResponse->body); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Issue166Test.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Issue166Test.php new file mode 100644 index 0000000..f925224 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Issue166Test.php @@ -0,0 +1,63 @@ + 'VCALENDAR', + 'comp-filters' => array( + array( + 'name' => 'VEVENT', + 'comp-filters' => array(), + 'prop-filters' => array(), + 'is-not-defined' => false, + 'time-range' => array( + 'start' => new \DateTime('2011-12-01'), + 'end' => new \DateTime('2012-02-01'), + ), + ), + ), + 'prop-filters' => array(), + 'is-not-defined' => false, + 'time-range' => null, + ); + $input = VObject\Reader::read($input); + $this->assertTrue($validator->validate($input,$filters)); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Issue172Test.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Issue172Test.php new file mode 100644 index 0000000..ce6d364 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Issue172Test.php @@ -0,0 +1,135 @@ + 'VCALENDAR', + 'comp-filters' => array( + array( + 'name' => 'VEVENT', + 'comp-filters' => array(), + 'prop-filters' => array(), + 'is-not-defined' => false, + 'time-range' => array( + 'start' => new \DateTime('2012-01-18 21:00:00 GMT-08:00'), + 'end' => new \DateTime('2012-01-18 21:00:00 GMT-08:00'), + ), + ), + ), + 'prop-filters' => array(), + ); + $input = VObject\Reader::read($input); + $this->assertTrue($validator->validate($input,$filters)); + } + + // Pacific Standard Time, translates to America/Los_Angeles (GMT-8 in January) + function testOutlookTimezoneName() { + $input = << 'VCALENDAR', + 'comp-filters' => array( + array( + 'name' => 'VEVENT', + 'comp-filters' => array(), + 'prop-filters' => array(), + 'is-not-defined' => false, + 'time-range' => array( + 'start' => new \DateTime('2012-01-13 10:30:00 GMT-08:00'), + 'end' => new \DateTime('2012-01-13 10:30:00 GMT-08:00'), + ), + ), + ), + 'prop-filters' => array(), + ); + $input = VObject\Reader::read($input); + $this->assertTrue($validator->validate($input,$filters)); + } + + // X-LIC-LOCATION, translates to America/Los_Angeles (GMT-8 in January) + function testLibICalLocationName() { + $input = << 'VCALENDAR', + 'comp-filters' => array( + array( + 'name' => 'VEVENT', + 'comp-filters' => array(), + 'prop-filters' => array(), + 'is-not-defined' => false, + 'time-range' => array( + 'start' => new \DateTime('2012-01-13 10:30:00 GMT-08:00'), + 'end' => new \DateTime('2012-01-13 10:30:00 GMT-08:00'), + ), + ), + ), + 'prop-filters' => array(), + ); + $input = VObject\Reader::read($input); + $this->assertTrue($validator->validate($input,$filters)); + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Issue203Test.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Issue203Test.php new file mode 100644 index 0000000..9850359 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Issue203Test.php @@ -0,0 +1,138 @@ + 1, + 'name' => 'Calendar', + 'principaluri' => 'principals/user1', + 'uri' => 'calendar1', + ) + ); + + protected $caldavCalendarObjects = array( + 1 => array( + 'event.ics' => array( + 'calendardata' => 'BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:20120330T155305CEST-6585fBUVgV +DTSTAMP:20120330T135305Z +DTSTART;TZID=Europe/Berlin:20120326T155200 +DTEND;TZID=Europe/Berlin:20120326T165200 +RRULE:FREQ=DAILY;COUNT=2;INTERVAL=1 +SUMMARY:original summary +TRANSP:OPAQUE +END:VEVENT +BEGIN:VEVENT +UID:20120330T155305CEST-6585fBUVgV +DTSTAMP:20120330T135352Z +DESCRIPTION: +DTSTART;TZID=Europe/Berlin:20120328T155200 +DTEND;TZID=Europe/Berlin:20120328T165200 +RECURRENCE-ID;TZID=Europe/Berlin:20120327T155200 +SEQUENCE:1 +SUMMARY:overwritten summary +TRANSP:OPAQUE +END:VEVENT +END:VCALENDAR +', + ), + ), + ); + + function testIssue203() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_CONTENT_TYPE' => 'application/xml', + 'REQUEST_URI' => '/calendars/user1/calendar1', + 'HTTP_DEPTH' => '1', + )); + + $request->setBody(' + + + + + + + + + + + + + + +'); + + $response = $this->request($request); + + // Everts super awesome xml parser. + $body = substr( + $response->body, + $start = strpos($response->body, 'BEGIN:VCALENDAR'), + strpos($response->body, 'END:VCALENDAR') - $start + 13 + ); + $body = str_replace(' ','',$body); + + $vObject = VObject\Reader::read($body); + + $this->assertEquals(2, count($vObject->VEVENT)); + + + $expectedEvents = array( + array( + 'DTSTART' => '20120326T135200Z', + 'DTEND' => '20120326T145200Z', + 'SUMMARY' => 'original summary', + ), + array( + 'DTSTART' => '20120328T135200Z', + 'DTEND' => '20120328T145200Z', + 'SUMMARY' => 'overwritten summary', + 'RECURRENCE-ID' => '20120327T135200Z', + ) + ); + + // try to match agains $expectedEvents array + foreach ($expectedEvents as $expectedEvent) { + + $matching = false; + + foreach ($vObject->VEVENT as $vevent) { + /** @var $vevent Sabre\VObject\Component\VEvent */ + + foreach ($vevent->children as $child) { + /** @var $child Sabre\VObject\Property */ + + if (isset($expectedEvent[$child->name])) { + if ($expectedEvent[$child->name] != $child->getValue()) { + continue 2; + } + } + } + + $matching = true; + break; + } + + $this->assertTrue($matching, 'Did not find the following event in the response: '.var_export($expectedEvent, true)); + } + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Issue205Test.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Issue205Test.php new file mode 100644 index 0000000..4a53fcb --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Issue205Test.php @@ -0,0 +1,97 @@ + 1, + 'name' => 'Calendar', + 'principaluri' => 'principals/user1', + 'uri' => 'calendar1', + ) + ); + + protected $caldavCalendarObjects = array( + 1 => array( + 'event.ics' => array( + 'calendardata' => 'BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:20120330T155305CEST-6585fBUVgV +DTSTAMP:20120330T135305Z +DTSTART;TZID=Europe/Berlin:20120326T155200 +DTEND;TZID=Europe/Berlin:20120326T165200 +SUMMARY:original summary +TRANSP:OPAQUE +BEGIN:VALARM +ACTION:AUDIO +ATTACH;VALUE=URI:Basso +TRIGGER:PT0S +END:VALARM +END:VEVENT +END:VCALENDAR +', + ), + ), + ); + + function testIssue205() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_CONTENT_TYPE' => 'application/xml', + 'REQUEST_URI' => '/calendars/user1/calendar1', + 'HTTP_DEPTH' => '1', + )); + + $request->setBody(' + + + + + + + + + + + + + + + + +'); + + $response = $this->request($request); + + $this->assertFalse(strpos($response->body, 'Exception'), 'Exception occurred: ' . $response->body); + $this->assertFalse(strpos($response->body, 'Unknown or bad format'), 'DateTime unknown format Exception: ' . $response->body); + + // Everts super awesome xml parser. + $body = substr( + $response->body, + $start = strpos($response->body, 'BEGIN:VCALENDAR'), + strpos($response->body, 'END:VCALENDAR') - $start + 13 + ); + $body = str_replace(' ','',$body); + + $vObject = VObject\Reader::read($body); + + $this->assertEquals(1, count($vObject->VEVENT)); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Issue211Test.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Issue211Test.php new file mode 100644 index 0000000..f291e5e --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Issue211Test.php @@ -0,0 +1,89 @@ + 1, + 'name' => 'Calendar', + 'principaluri' => 'principals/user1', + 'uri' => 'calendar1', + ) + ); + + protected $caldavCalendarObjects = array( + 1 => array( + 'event.ics' => array( + 'calendardata' => 'BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:20120418T172519CEST-3510gh1hVw +DTSTAMP:20120418T152519Z +DTSTART;VALUE=DATE:20120330 +DTEND;VALUE=DATE:20120531 +EXDATE;TZID=Europe/Berlin:20120330T000000 +RRULE:FREQ=YEARLY;INTERVAL=1 +SEQUENCE:1 +SUMMARY:Birthday +TRANSP:TRANSPARENT +BEGIN:VALARM +ACTION:EMAIL +ATTENDEE:MAILTO:xxx@domain.de +DESCRIPTION:Dies ist eine Kalender Erinnerung +SUMMARY:Kalender Alarm Erinnerung +TRIGGER;VALUE=DATE-TIME:20120329T060000Z +END:VALARM +END:VEVENT +END:VCALENDAR +', + ), + ), + ); + + function testIssue211() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_CONTENT_TYPE' => 'application/xml', + 'REQUEST_URI' => '/calendars/user1/calendar1', + 'HTTP_DEPTH' => '1', + )); + + $request->setBody(' + + + + + + + + + + + + + + +'); + + $response = $this->request($request); + + // if this assert is reached, the endless loop is gone + // There should be no matching events + $this->assertFalse(strpos('BEGIN:VEVENT', $response->body)); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Issue220Test.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Issue220Test.php new file mode 100644 index 0000000..7b5dbfe --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Issue220Test.php @@ -0,0 +1,99 @@ + 1, + 'name' => 'Calendar', + 'principaluri' => 'principals/user1', + 'uri' => 'calendar1', + ) + ); + + protected $caldavCalendarObjects = array( + 1 => array( + 'event.ics' => array( + 'calendardata' => 'BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +DTSTART;TZID=Europe/Berlin:20120601T180000 +SUMMARY:Brot backen +RRULE:FREQ=DAILY;INTERVAL=1;WKST=MO +TRANSP:OPAQUE +DURATION:PT20M +LAST-MODIFIED:20120601T064634Z +CREATED:20120601T064634Z +DTSTAMP:20120601T064634Z +UID:b64f14c5-dccc-4eda-947f-bdb1f763fbcd +BEGIN:VALARM +TRIGGER;VALUE=DURATION:-PT5M +ACTION:DISPLAY +DESCRIPTION:Default Event Notification +X-WR-ALARMUID:cd952c1b-b3d6-41fb-b0a6-ec3a1a5bdd58 +END:VALARM +END:VEVENT +BEGIN:VEVENT +DTSTART;TZID=Europe/Berlin:20120606T180000 +SUMMARY:Brot backen +TRANSP:OPAQUE +STATUS:CANCELLED +DTEND;TZID=Europe/Berlin:20120606T182000 +LAST-MODIFIED:20120605T094310Z +SEQUENCE:1 +RECURRENCE-ID:20120606T160000Z +UID:b64f14c5-dccc-4eda-947f-bdb1f763fbcd +END:VEVENT +END:VCALENDAR +', + ), + ), + ); + + function testIssue220() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_CONTENT_TYPE' => 'application/xml', + 'REQUEST_URI' => '/calendars/user1/calendar1', + 'HTTP_DEPTH' => '1', + )); + + $request->setBody(' + + + + + + + + + + + + + + +'); + + $response = $this->request($request); + + $this->assertFalse(strpos($response->body, 'PHPUnit_Framework_Error_Warning'), 'Error Warning occurred: ' . $response->body); + $this->assertFalse(strpos($response->body, 'Invalid argument supplied for foreach()'), 'Invalid argument supplied for foreach(): ' . $response->body); + + $this->assertEquals(207, $response->status); + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Issue228Test.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Issue228Test.php new file mode 100644 index 0000000..ccc6b30 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Issue228Test.php @@ -0,0 +1,78 @@ + 1, + 'name' => 'Calendar', + 'principaluri' => 'principals/user1', + 'uri' => 'calendar1', + ) + ); + + protected $caldavCalendarObjects = array( + 1 => array( + 'event.ics' => array( + 'calendardata' => 'BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:20120730T113415CEST-6804EGphkd@xxxxxx.de +DTSTAMP:20120730T093415Z +DTSTART;VALUE=DATE:20120729 +DTEND;VALUE=DATE:20120730 +SUMMARY:sunday event +TRANSP:TRANSPARENT +END:VEVENT +END:VCALENDAR +', + ), + ), + ); + + function testIssue228() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_CONTENT_TYPE' => 'application/xml', + 'REQUEST_URI' => '/calendars/user1/calendar1', + 'HTTP_DEPTH' => '1', + )); + + $request->setBody(' + + + + + + + + + + + + + + +'); + + $response = $this->request($request); + + // We must check if absolutely nothing was returned from this query. + $this->assertFalse(strpos($response->body, 'BEGIN:VCALENDAR')); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/JCalTransformTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/JCalTransformTest.php new file mode 100644 index 0000000..7bc4725 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/JCalTransformTest.php @@ -0,0 +1,243 @@ + 1, + 'principaluri' => 'principals/user1', + 'uri' => 'foo', + ] + ]; + protected $caldavCalendarObjects = [ + 1 => [ + 'bar.ics' => [ + 'uri' => 'bar.ics', + 'calendarid' => 1, + 'calendardata' => "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", + 'lastmodified' => null + ] + ], + ]; + + function testGet() { + + $headers = [ + 'Accept' => 'application/calendar+json', + ]; + $request = new Request('GET', '/calendars/user1/foo/bar.ics', $headers); + + $response = $this->request($request); + + $body = $response->getBodyAsString(); + $this->assertEquals(200, $response->getStatus(), "Incorrect status code: " . $body); + + $response = json_decode($body,true); + if (json_last_error() !== JSON_ERROR_NONE) { + $this->fail('Json decoding error: ' . json_last_error_msg()); + } + $this->assertEquals( + [ + 'vcalendar', + [], + [ + [ + 'vevent', + [], + [], + ], + ], + ], + $response + ); + + } + + function testMultiGet() { + + $xml = << + + + + + /calendars/user1/foo/bar.ics + +XML; + + $headers = []; + $request = new Request('REPORT', '/calendars/user1/foo', $headers, $xml); + + $response = $this->request($request); + + $this->assertEquals(207, $response->getStatus(), 'Full rsponse: ' . $response->getBodyAsString()); + + $multiStatus = $this->server->xml->parse( + $response->getBodyAsString() + ); + + $responses = $multiStatus->getResponses(); + $this->assertEquals(1, count($responses)); + + $response = $responses[0]->getResponseProperties()[200]["{urn:ietf:params:xml:ns:caldav}calendar-data"]; + + $jresponse = json_decode($response,true); + if (json_last_error()) { + $this->fail('Json decoding error: ' . json_last_error_msg() . '. Full response: ' . $response); + } + $this->assertEquals( + [ + 'vcalendar', + [], + [ + [ + 'vevent', + [], + [], + ], + ], + ], + $jresponse + ); + + } + + function testCalendarQueryDepth1() { + + $xml = << + + + + + + + + +XML; + + $headers = [ + 'Depth' => '1', + ]; + $request = new Request('REPORT', '/calendars/user1/foo', $headers, $xml); + + $response = $this->request($request); + + $this->assertEquals(207, $response->getStatus(), "Invalid response code. Full body: " . $response->getBodyAsString()); + + $multiStatus = $this->server->xml->parse( + $response->getBodyAsString() + ); + + $responses = $multiStatus->getResponses(); + + $this->assertEquals(1, count($responses)); + + $response = $responses[0]->getResponseProperties()[200]["{urn:ietf:params:xml:ns:caldav}calendar-data"]; + $response = json_decode($response,true); + if (json_last_error()) { + $this->fail('Json decoding error: ' . json_last_error_msg()); + } + $this->assertEquals( + [ + 'vcalendar', + [], + [ + [ + 'vevent', + [], + [], + ], + ], + ], + $response + ); + + } + + function testCalendarQueryDepth0() { + + $xml = << + + + + + + + + +XML; + + $headers = [ + 'Depth' => '0', + ]; + $request = new Request('REPORT', '/calendars/user1/foo/bar.ics', $headers, $xml); + + $response = $this->request($request); + + $this->assertEquals(207, $response->getStatus(), "Invalid response code. Full body: " . $response->getBodyAsString()); + + $multiStatus = $this->server->xml->parse( + $response->getBodyAsString() + ); + + $responses = $multiStatus->getResponses(); + + $this->assertEquals(1, count($responses)); + + $response = $responses[0]->getResponseProperties()[200]["{urn:ietf:params:xml:ns:caldav}calendar-data"]; + $response = json_decode($response,true); + if (json_last_error()) { + $this->fail('Json decoding error: ' . json_last_error_msg()); + } + $this->assertEquals( + [ + 'vcalendar', + [], + [ + [ + 'vevent', + [], + [], + ], + ], + ], + $response + ); + + } + + function testValidateICalendar() { + + $input = [ + 'vcalendar', + [], + [ + [ + 'vevent', + [ + ['uid', (object)[], 'text', 'foo'], + ], + [], + ], + ], + ]; + $input = json_encode($input); + $this->caldavPlugin->beforeWriteContent( + 'calendars/user1/foo/bar.ics', + $this->server->tree->getNodeForPath('calendars/user1/foo/bar.ics'), + $input, + $modified + ); + + $this->assertEquals("BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nUID:foo\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", $input); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Notifications/CollectionTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Notifications/CollectionTest.php new file mode 100644 index 0000000..6803518 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Notifications/CollectionTest.php @@ -0,0 +1,90 @@ +principalUri = 'principals/user1'; + + $this->notification = new CalDAV\Xml\Notification\SystemStatus(1,'"1"'); + + $this->caldavBackend = new CalDAV\Backend\MockSharing(array(),array(), array( + 'principals/user1' => array( + $this->notification + ) + )); + + return new Collection($this->caldavBackend, $this->principalUri); + + } + + function testGetChildren() { + + $col = $this->getInstance(); + $this->assertEquals('notifications', $col->getName()); + + $this->assertEquals(array( + new Node($this->caldavBackend, $this->principalUri, $this->notification) + ), $col->getChildren()); + + } + + function testGetOwner() { + + $col = $this->getInstance(); + $this->assertEquals('principals/user1', $col->getOwner()); + + } + + function testGetGroup() { + + $col = $this->getInstance(); + $this->assertNull($col->getGroup()); + + } + + function testGetACL() { + + $col = $this->getInstance(); + $expected = array( + array( + 'privilege' => '{DAV:}read', + 'principal' => $this->principalUri, + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}write', + 'principal' => $this->principalUri, + 'protected' => true, + ), + ); + + $this->assertEquals($expected, $col->getACL()); + + } + + /** + * @expectedException Sabre\DAV\Exception\NotImplemented + */ + function testSetACL() { + + $col = $this->getInstance(); + $col->setACL(array()); + + } + + function testGetSupportedPrivilegeSet() { + + $col = $this->getInstance(); + $this->assertNull($col->getSupportedPrivilegeSet()); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Notifications/NodeTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Notifications/NodeTest.php new file mode 100644 index 0000000..d546116 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Notifications/NodeTest.php @@ -0,0 +1,101 @@ +systemStatus = new CalDAV\Xml\Notification\SystemStatus(1,'"1"'); + + $this->caldavBackend = new CalDAV\Backend\MockSharing([], [], [ + 'principals/user1' => [ + $this->systemStatus + ] + ]); + + $node = new Node($this->caldavBackend, 'principals/user1', $this->systemStatus); + return $node; + + } + + function testGetId() { + + $node = $this->getInstance(); + $this->assertEquals($this->systemStatus->getId() . '.xml', $node->getName()); + + } + + function testGetEtag() { + + $node = $this->getInstance(); + $this->assertEquals('"1"', $node->getETag()); + + } + + function testGetNotificationType() { + + $node = $this->getInstance(); + $this->assertEquals($this->systemStatus, $node->getNotificationType()); + + } + + function testDelete() { + + $node = $this->getInstance(); + $node->delete(); + $this->assertEquals(array(), $this->caldavBackend->getNotificationsForPrincipal('principals/user1')); + + } + + function testGetGroup() { + + $node = $this->getInstance(); + $this->assertNull($node->getGroup()); + + } + + function testGetACL() { + + $node = $this->getInstance(); + $expected = array( + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}write', + 'principal' => 'principals/user1', + 'protected' => true, + ), + ); + + $this->assertEquals($expected, $node->getACL()); + + } + + /** + * @expectedException Sabre\DAV\Exception\NotImplemented + */ + function testSetACL() { + + $node = $this->getInstance(); + $node->setACL(array()); + + } + + function testGetSupportedPrivilegeSet() { + + $node = $this->getInstance(); + $this->assertNull($node->getSupportedPrivilegeSet()); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Notifications/PluginTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Notifications/PluginTest.php new file mode 100644 index 0000000..a1ef69f --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Notifications/PluginTest.php @@ -0,0 +1,169 @@ +caldavBackend = new CalDAV\Backend\MockSharing(); + $principalBackend = new DAVACL\PrincipalBackend\Mock(); + $calendars = new CalDAV\CalendarRoot($principalBackend,$this->caldavBackend); + $principals = new CalDAV\Principal\Collection($principalBackend); + + $root = new DAV\SimpleCollection('root'); + $root->addChild($calendars); + $root->addChild($principals); + + $this->server = new DAV\Server($root); + $this->server->sapi = new HTTP\SapiMock(); + $this->server->debugExceptions = true; + $this->server->setBaseUri('/'); + $this->plugin = new Plugin(); + $this->server->addPlugin($this->plugin); + + + // Adding ACL plugin + $this->server->addPlugin(new DAVACL\Plugin()); + + // CalDAV is also required. + $this->server->addPlugin(new CalDAV\Plugin()); + // Adding Auth plugin, and ensuring that we are logged in. + $authBackend = new DAV\Auth\Backend\Mock(); + $authBackend->defaultUser = 'user1'; + $authPlugin = new DAV\Auth\Plugin($authBackend, 'SabreDAV'); + $this->server->addPlugin($authPlugin); + + // This forces a login + $authPlugin->beforeMethod(new HTTP\Request(), new HTTP\Response()); + + $this->response = new HTTP\ResponseMock(); + $this->server->httpResponse = $this->response; + + } + + function testSimple() { + + $this->assertEquals([], $this->plugin->getFeatures()); + $this->assertEquals('notifications', $this->plugin->getPluginName()); + $this->assertEquals( + 'notifications', + $this->plugin->getPluginInfo()['name'] + ); + + } + + function testPrincipalProperties() { + + $httpRequest = new Request('GET', '/', ['Host' => 'sabredav.org']); + $this->server->httpRequest = $httpRequest; + + $props = $this->server->getPropertiesForPath('/principals/user1',[ + '{' . Plugin::NS_CALENDARSERVER . '}notification-URL', + ]); + + $this->assertArrayHasKey(0,$props); + $this->assertArrayHasKey(200,$props[0]); + + + $this->assertArrayHasKey('{'.Plugin::NS_CALENDARSERVER .'}notification-URL',$props[0][200]); + $prop = $props[0][200]['{'.Plugin::NS_CALENDARSERVER .'}notification-URL']; + $this->assertTrue($prop instanceof DAV\Xml\Property\Href); + $this->assertEquals('calendars/user1/notifications/', $prop->getHref()); + + } + + function testNotificationProperties() { + + $notification = new Node( + $this->caldavBackend, + 'principals/user1', + new SystemStatus('foo','"1"') + ); + $propFind = new DAV\PropFind('calendars/user1/notifications', [ + '{' . Plugin::NS_CALENDARSERVER . '}notificationtype', + ]); + + $this->plugin->propFind($propFind, $notification); + + $this->assertEquals( + $notification->getNotificationType(), + $propFind->get('{' . Plugin::NS_CALENDARSERVER . '}notificationtype') + ); + + } + + function testNotificationGet() { + + $notification = new Node( + $this->caldavBackend, + 'principals/user1', + new SystemStatus('foo','"1"') + ); + + $server = new DAV\Server([$notification]); + $caldav = new Plugin(); + + $server->httpRequest = new Request('GET', '/foo.xml'); + $httpResponse = new HTTP\ResponseMock(); + $server->httpResponse = $httpResponse; + + $server->addPlugin($caldav); + + $caldav->httpGet($server->httpRequest, $server->httpResponse); + + $this->assertEquals(200, $httpResponse->status); + $this->assertEquals([ + 'Content-Type' => ['application/xml'], + 'ETag' => ['"1"'], + ], $httpResponse->getHeaders()); + + $expected = +' + + + +'; + + $this->assertXmlStringEqualsXmlString($expected, $httpResponse->getBodyAsString()); + + } + + function testGETPassthrough() { + + $server = new DAV\Server(); + $caldav = new Plugin(); + + $httpResponse = new HTTP\ResponseMock(); + $server->httpResponse = $httpResponse; + + $server->addPlugin($caldav); + + $this->assertNull($caldav->httpGet(new HTTP\Request('GET','/foozz'), $server->httpResponse)); + + } + + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/PluginTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/PluginTest.php new file mode 100644 index 0000000..0d7c705 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/PluginTest.php @@ -0,0 +1,1145 @@ +caldavBackend = new Backend\Mock(array( + array( + 'id' => 1, + 'uri' => 'UUID-123467', + 'principaluri' => 'principals/user1', + '{DAV:}displayname' => 'user1 calendar', + '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'Calendar description', + '{http://apple.com/ns/ical/}calendar-order' => '1', + '{http://apple.com/ns/ical/}calendar-color' => '#FF0000', + '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set' => new Xml\Property\SupportedCalendarComponentSet(['VEVENT','VTODO']), + ), + array( + 'id' => 2, + 'uri' => 'UUID-123468', + 'principaluri' => 'principals/user1', + '{DAV:}displayname' => 'user1 calendar2', + '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'Calendar description', + '{http://apple.com/ns/ical/}calendar-order' => '1', + '{http://apple.com/ns/ical/}calendar-color' => '#FF0000', + '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set' => new Xml\Property\SupportedCalendarComponentSet(['VEVENT','VTODO']), + ) + ), array( + 1 => array( + 'UUID-2345' => array( + 'calendardata' => TestUtil::getTestCalendarData(), + ) + ) + )); + $principalBackend = new DAVACL\PrincipalBackend\Mock(); + $principalBackend->setGroupMemberSet('principals/admin/calendar-proxy-read',array('principals/user1')); + $principalBackend->setGroupMemberSet('principals/admin/calendar-proxy-write',array('principals/user1')); + $principalBackend->addPrincipal(array( + 'uri' => 'principals/admin/calendar-proxy-read', + )); + $principalBackend->addPrincipal(array( + 'uri' => 'principals/admin/calendar-proxy-write', + )); + + $calendars = new CalendarRoot($principalBackend,$this->caldavBackend); + $principals = new Principal\Collection($principalBackend); + + $root = new DAV\SimpleCollection('root'); + $root->addChild($calendars); + $root->addChild($principals); + + $this->server = new DAV\Server($root); + $this->server->sapi = new HTTP\SapiMock(); + $this->server->debugExceptions = true; + $this->server->setBaseUri('/'); + $this->plugin = new Plugin(); + $this->server->addPlugin($this->plugin); + + // Adding ACL plugin + $this->server->addPlugin(new DAVACL\Plugin()); + + // Adding Auth plugin, and ensuring that we are logged in. + $authBackend = new DAV\Auth\Backend\Mock(); + $authBackend->setPrincipal('principals/user1'); + $authPlugin = new DAV\Auth\Plugin($authBackend, 'SabreDAV'); + $authPlugin->beforeMethod(new \Sabre\HTTP\Request(), new \Sabre\HTTP\Response()); + $this->server->addPlugin($authPlugin); + + // This forces a login + $authPlugin->beforeMethod(new HTTP\Request(), new HTTP\Response()); + + $this->response = new HTTP\ResponseMock(); + $this->server->httpResponse = $this->response; + + } + + function testSimple() { + + $this->assertEquals(array('MKCALENDAR'), $this->plugin->getHTTPMethods('calendars/user1/randomnewcalendar')); + $this->assertEquals(array('calendar-access','calendar-proxy'), $this->plugin->getFeatures()); + $this->assertEquals( + 'caldav', + $this->plugin->getPluginInfo()['name'] + ); + + } + + function testUnknownMethodPassThrough() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'MKBREAKFAST', + 'REQUEST_URI' => '/', + )); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(501, $this->response->status,'Incorrect status returned. Full response body:' . $this->response->body); + + } + + function testReportPassThrough() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_CONTENT_TYPE' => 'application/xml', + 'REQUEST_URI' => '/', + )); + $request->setBody(''); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(415, $this->response->status); + + } + + function testMkCalendarBadLocation() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'MKCALENDAR', + 'REQUEST_URI' => '/blabla', + )); + + $body = ' + + + + Lisa\'s Events + Calendar restricted to events. + + + + + + + '; + + $request->setBody($body); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(403, $this->response->status); + + } + + function testMkCalendarNoParentNode() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'MKCALENDAR', + 'REQUEST_URI' => '/doesntexist/calendar', + )); + + $body = ' + + + + Lisa\'s Events + Calendar restricted to events. + + + + + + + '; + + $request->setBody($body); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(409, $this->response->status); + + } + + function testMkCalendarExistingCalendar() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'MKCALENDAR', + 'REQUEST_URI' => '/calendars/user1/UUID-123467', + )); + + $body = ' + + + + Lisa\'s Events + Calendar restricted to events. + + + + + + + '; + + $request->setBody($body); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(405, $this->response->status); + + } + + function testMkCalendarSucceed() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'MKCALENDAR', + 'REQUEST_URI' => '/calendars/user1/NEWCALENDAR', + )); + + $timezone = 'BEGIN:VCALENDAR +PRODID:-//Example Corp.//CalDAV Client//EN +VERSION:2.0 +BEGIN:VTIMEZONE +TZID:US-Eastern +LAST-MODIFIED:19870101T000000Z +BEGIN:STANDARD +DTSTART:19671029T020000 +RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +TZNAME:Eastern Standard Time (US & Canada) +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:19870405T020000 +RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +TZNAME:Eastern Daylight Time (US & Canada) +END:DAYLIGHT +END:VTIMEZONE +END:VCALENDAR'; + + $body = ' + + + + Lisa\'s Events + Calendar restricted to events. + + + + + + + '; + + $request->setBody($body); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(201, $this->response->status,'Invalid response code received. Full response body: ' .$this->response->body); + + $calendars = $this->caldavBackend->getCalendarsForUser('principals/user1'); + $this->assertEquals(3, count($calendars)); + + $newCalendar = null; + foreach($calendars as $calendar) { + if ($calendar['uri'] === 'NEWCALENDAR') { + $newCalendar = $calendar; + break; + } + } + + $this->assertInternalType('array',$newCalendar); + + $keys = array( + 'uri' => 'NEWCALENDAR', + 'id' => null, + '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'Calendar restricted to events.', + '{urn:ietf:params:xml:ns:caldav}calendar-timezone' => $timezone, + '{DAV:}displayname' => 'Lisa\'s Events', + '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set' => null, + ); + + foreach($keys as $key=>$value) { + + $this->assertArrayHasKey($key, $newCalendar); + + if (is_null($value)) continue; + $this->assertEquals($value, $newCalendar[$key]); + + } + $sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set'; + $this->assertTrue($newCalendar[$sccs] instanceof Xml\Property\SupportedCalendarComponentSet); + $this->assertEquals(array('VEVENT'),$newCalendar[$sccs]->getValue()); + + } + + function testMkCalendarEmptyBodySucceed() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'MKCALENDAR', + 'REQUEST_URI' => '/calendars/user1/NEWCALENDAR', + )); + + $request->setBody(''); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(201, $this->response->status,'Invalid response code received. Full response body: ' .$this->response->body); + + $calendars = $this->caldavBackend->getCalendarsForUser('principals/user1'); + $this->assertEquals(3, count($calendars)); + + $newCalendar = null; + foreach($calendars as $calendar) { + if ($calendar['uri'] === 'NEWCALENDAR') { + $newCalendar = $calendar; + break; + } + } + + $this->assertInternalType('array',$newCalendar); + + $keys = array( + 'uri' => 'NEWCALENDAR', + 'id' => null, + '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set' => null, + ); + + foreach($keys as $key=>$value) { + + $this->assertArrayHasKey($key, $newCalendar); + + if (is_null($value)) continue; + $this->assertEquals($value, $newCalendar[$key]); + + } + $sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set'; + $this->assertTrue($newCalendar[$sccs] instanceof Xml\Property\SupportedCalendarComponentSet); + $this->assertEquals(array('VEVENT','VTODO'),$newCalendar[$sccs]->getValue()); + + } + + function testMkCalendarBadXml() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'MKCALENDAR', + 'REQUEST_URI' => '/blabla', + )); + + $body = 'This is not xml'; + + $request->setBody($body); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(400, $this->response->status); + + } + + function testPrincipalProperties() { + + $httpRequest = HTTP\Sapi::createFromServerArray(array( + 'HTTP_HOST' => 'sabredav.org', + )); + $this->server->httpRequest = $httpRequest; + + $props = $this->server->getPropertiesForPath('/principals/user1',array( + '{' . Plugin::NS_CALDAV . '}calendar-home-set', + '{' . Plugin::NS_CALENDARSERVER . '}calendar-proxy-read-for', + '{' . Plugin::NS_CALENDARSERVER . '}calendar-proxy-write-for', + '{' . Plugin::NS_CALENDARSERVER . '}notification-URL', + '{' . Plugin::NS_CALENDARSERVER . '}email-address-set', + )); + + $this->assertArrayHasKey(0,$props); + $this->assertArrayHasKey(200,$props[0]); + + + $this->assertArrayHasKey('{urn:ietf:params:xml:ns:caldav}calendar-home-set',$props[0][200]); + $prop = $props[0][200]['{urn:ietf:params:xml:ns:caldav}calendar-home-set']; + $this->assertInstanceOf('Sabre\\DAV\\Xml\\Property\\Href', $prop); + $this->assertEquals('calendars/user1/',$prop->getHref()); + + $this->assertArrayHasKey('{http://calendarserver.org/ns/}calendar-proxy-read-for', $props[0][200]); + $prop = $props[0][200]['{http://calendarserver.org/ns/}calendar-proxy-read-for']; + $this->assertInstanceOf('Sabre\\DAV\\Xml\\Property\\Href', $prop); + $this->assertEquals(array('principals/admin/'), $prop->getHrefs()); + + $this->assertArrayHasKey('{http://calendarserver.org/ns/}calendar-proxy-write-for', $props[0][200]); + $prop = $props[0][200]['{http://calendarserver.org/ns/}calendar-proxy-write-for']; + $this->assertInstanceOf('Sabre\\DAV\\Xml\\Property\\Href', $prop); + $this->assertEquals(array('principals/admin/'), $prop->getHrefs()); + + $this->assertArrayHasKey('{' . Plugin::NS_CALENDARSERVER . '}email-address-set',$props[0][200]); + $prop = $props[0][200]['{' . Plugin::NS_CALENDARSERVER . '}email-address-set']; + $this->assertInstanceOf('Sabre\\CalDAV\\Xml\\Property\\EmailAddressSet', $prop); + $this->assertEquals(['user1.sabredav@sabredav.org'],$prop->getValue()); + + } + + function testSupportedReportSetPropertyNonCalendar() { + + $props = $this->server->getPropertiesForPath('/calendars/user1',array( + '{DAV:}supported-report-set', + )); + + $this->assertArrayHasKey(0,$props); + $this->assertArrayHasKey(200,$props[0]); + $this->assertArrayHasKey('{DAV:}supported-report-set',$props[0][200]); + + $prop = $props[0][200]['{DAV:}supported-report-set']; + + $this->assertInstanceOf('\\Sabre\\DAV\\Xml\\Property\\SupportedReportSet', $prop); + $value = array( + '{DAV:}expand-property', + '{DAV:}principal-property-search', + '{DAV:}principal-search-property-set' + ); + $this->assertEquals($value,$prop->getValue()); + + } + + /** + * @depends testSupportedReportSetPropertyNonCalendar + */ + function testSupportedReportSetProperty() { + + $props = $this->server->getPropertiesForPath('/calendars/user1/UUID-123467',array( + '{DAV:}supported-report-set', + )); + + $this->assertArrayHasKey(0,$props); + $this->assertArrayHasKey(200,$props[0]); + $this->assertArrayHasKey('{DAV:}supported-report-set',$props[0][200]); + + $prop = $props[0][200]['{DAV:}supported-report-set']; + + $this->assertInstanceOf('\\Sabre\\DAV\\Xml\\Property\\SupportedReportSet', $prop); + $value = array( + '{urn:ietf:params:xml:ns:caldav}calendar-multiget', + '{urn:ietf:params:xml:ns:caldav}calendar-query', + '{urn:ietf:params:xml:ns:caldav}free-busy-query', + '{DAV:}expand-property', + '{DAV:}principal-property-search', + '{DAV:}principal-search-property-set' + ); + $this->assertEquals($value,$prop->getValue()); + + } + + function testSupportedReportSetUserCalendars() { + + $this->server->addPlugin(new \Sabre\DAV\Sync\Plugin()); + + $props = $this->server->getPropertiesForPath('/calendars/user1',array( + '{DAV:}supported-report-set', + )); + + $this->assertArrayHasKey(0,$props); + $this->assertArrayHasKey(200,$props[0]); + $this->assertArrayHasKey('{DAV:}supported-report-set',$props[0][200]); + + $prop = $props[0][200]['{DAV:}supported-report-set']; + + $this->assertInstanceOf('\\Sabre\\DAV\\Xml\\Property\\SupportedReportSet', $prop); + $value = array( + '{DAV:}sync-collection', + '{DAV:}expand-property', + '{DAV:}principal-property-search', + '{DAV:}principal-search-property-set', + ); + $this->assertEquals($value,$prop->getValue()); + + } + + /** + * @depends testSupportedReportSetProperty + */ + function testCalendarMultiGetReport() { + + $body = + '' . + '' . + '' . + ' ' . + ' ' . + '' . + '/calendars/user1/UUID-123467/UUID-2345' . + ''; + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/calendars/user1', + 'HTTP_DEPTH' => '1', + )); + $request->setBody($body); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(207, $this->response->status,'Invalid HTTP status received. Full response body'); + + $expectedIcal = TestUtil::getTestCalendarData(); + + $expected = << + + + /calendars/user1/UUID-123467/UUID-2345 + + + $expectedIcal + "e207e33c10e5fb9c12cfb35b5d9116e1" + + HTTP/1.1 200 OK + + + +XML; + + $this->assertXmlStringEqualsXmlString($expected, $this->response->getBodyAsString()); + + } + + /** + * @depends testCalendarMultiGetReport + */ + function testCalendarMultiGetReportExpand() { + + $body = + '' . + '' . + '' . + ' ' . + ' ' . + ' ' . + ' ' . + '' . + '/calendars/user1/UUID-123467/UUID-2345' . + ''; + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/calendars/user1', + 'HTTP_DEPTH' => '1', + )); + $request->setBody($body); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(207, $this->response->status,'Invalid HTTP status received. Full response body: ' . $this->response->body); + + $expectedIcal = TestUtil::getTestCalendarData(); + $expectedIcal = \Sabre\VObject\Reader::read($expectedIcal); + $expectedIcal->expand( + new DateTime('2011-01-01 00:00:00', new DateTimeZone('UTC')), + new DateTime('2011-12-31 23:59:59', new DateTimeZone('UTC')) + ); + $expectedIcal = str_replace("\r\n", " \n", $expectedIcal->serialize()); + + $expected = << + + + /calendars/user1/UUID-123467/UUID-2345 + + + $expectedIcal + "e207e33c10e5fb9c12cfb35b5d9116e1" + + HTTP/1.1 200 OK + + + +XML; + + $this->assertXmlStringEqualsXmlString($expected, $this->response->getBodyAsString()); + + } + + /** + * @depends testSupportedReportSetProperty + * @depends testCalendarMultiGetReport + */ + function testCalendarQueryReport() { + + $body = + '' . + '' . + '' . + ' ' . + ' ' . + ' ' . + ' ' . + '' . + '' . + ' ' . + ' ' . + ' ' . + '' . + ''; + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/calendars/user1/UUID-123467', + 'HTTP_DEPTH' => '1', + )); + $request->setBody($body); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(207, $this->response->status,'Received an unexpected status. Full response body: ' . $this->response->body); + + $expectedIcal = TestUtil::getTestCalendarData(); + $expectedIcal = \Sabre\VObject\Reader::read($expectedIcal); + $expectedIcal->expand( + new DateTime('2000-01-01 00:00:00', new DateTimeZone('UTC')), + new DateTime('2010-12-31 23:59:59', new DateTimeZone('UTC')) + ); + $expectedIcal = str_replace("\r\n", " \n", $expectedIcal->serialize()); + + $expected = << + + + /calendars/user1/UUID-123467/UUID-2345 + + + $expectedIcal + "e207e33c10e5fb9c12cfb35b5d9116e1" + + HTTP/1.1 200 OK + + + +XML; + + $this->assertXmlStringEqualsXmlString($expected, $this->response->getBodyAsString()); + + } + + /** + * @depends testSupportedReportSetProperty + * @depends testCalendarMultiGetReport + */ + function testCalendarQueryReportWindowsPhone() { + + $body = + '' . + '' . + '' . + ' ' . + ' ' . + ' ' . + ' ' . + '' . + '' . + ' ' . + ' ' . + ' ' . + '' . + ''; + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/calendars/user1/UUID-123467', + 'HTTP_USER_AGENT' => 'MSFT-WP/8.10.14219 (gzip)', + 'HTTP_DEPTH' => '0', + )); + $request->setBody($body); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(207, $this->response->status,'Received an unexpected status. Full response body: ' . $this->response->body); + + $expectedIcal = TestUtil::getTestCalendarData(); + $expectedIcal = \Sabre\VObject\Reader::read($expectedIcal); + $expectedIcal->expand( + new \DateTime('2000-01-01 00:00:00', new \DateTimeZone('UTC')), + new \DateTime('2010-12-31 23:59:59', new \DateTimeZone('UTC')) + ); + $expectedIcal = str_replace("\r\n", " \n", $expectedIcal->serialize()); + + $expected = << + + + /calendars/user1/UUID-123467/UUID-2345 + + + $expectedIcal + "e207e33c10e5fb9c12cfb35b5d9116e1" + + HTTP/1.1 200 OK + + + +XML; + + $this->assertXmlStringEqualsXmlString($expected, $this->response->getBodyAsString()); + + } + + /** + * @depends testSupportedReportSetProperty + * @depends testCalendarMultiGetReport + */ + function testCalendarQueryReportBadDepth() { + + $body = + '' . + '' . + '' . + ' ' . + ' ' . + ' ' . + ' ' . + '' . + '' . + ' ' . + ' ' . + ' ' . + '' . + ''; + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/calendars/user1/UUID-123467', + 'HTTP_DEPTH' => '0', + )); + $request->setBody($body); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(400, $this->response->status,'Received an unexpected status. Full response body: ' . $this->response->body); + + } + + /** + * @depends testCalendarQueryReport + */ + function testCalendarQueryReportNoCalData() { + + $body = + '' . + '' . + '' . + ' ' . + '' . + '' . + ' ' . + ' ' . + ' ' . + '' . + ''; + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/calendars/user1//UUID-123467', + 'HTTP_DEPTH' => '1', + )); + $request->setBody($body); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(207, $this->response->status,'Received an unexpected status. Full response body: ' . $this->response->body); + + $expected = << + + + /calendars/user1/UUID-123467/UUID-2345 + + + "e207e33c10e5fb9c12cfb35b5d9116e1" + + HTTP/1.1 200 OK + + + +XML; + + $this->assertXmlStringEqualsXmlString($expected, $this->response->getBodyAsString()); + + } + + /** + * @depends testCalendarQueryReport + */ + function testCalendarQueryReportNoFilters() { + + $body = + '' . + '' . + '' . + ' ' . + ' ' . + '' . + ''; + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/calendars/user1//UUID-123467', + )); + $request->setBody($body); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(400, $this->response->status,'Received an unexpected status. Full response body: ' . $this->response->body); + + } + + /** + * @depends testSupportedReportSetProperty + * @depends testCalendarMultiGetReport + */ + function testCalendarQueryReport1Object() { + + $body = + '' . + '' . + '' . + ' ' . + ' ' . + ' ' . + ' ' . + '' . + '' . + ' ' . + ' ' . + ' ' . + '' . + ''; + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/calendars/user1/UUID-123467/UUID-2345', + 'HTTP_DEPTH' => '0', + )); + $request->setBody($body); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(207, $this->response->status,'Received an unexpected status. Full response body: ' . $this->response->body); + + $expectedIcal = TestUtil::getTestCalendarData(); + $expectedIcal = \Sabre\VObject\Reader::read($expectedIcal); + $expectedIcal->expand( + new DateTime('2000-01-01 00:00:00', new DateTimeZone('UTC')), + new DateTime('2010-12-31 23:59:59', new DateTimeZone('UTC')) + ); + $expectedIcal = str_replace("\r\n", " \n", $expectedIcal->serialize()); + + $expected = << + + + /calendars/user1/UUID-123467/UUID-2345 + + + $expectedIcal + "e207e33c10e5fb9c12cfb35b5d9116e1" + + HTTP/1.1 200 OK + + + +XML; + + $this->assertXmlStringEqualsXmlString($expected, $this->response->getBodyAsString()); + + } + + /** + * @depends testSupportedReportSetProperty + * @depends testCalendarMultiGetReport + */ + function testCalendarQueryReport1ObjectNoCalData() { + + $body = + '' . + '' . + '' . + ' ' . + '' . + '' . + ' ' . + ' ' . + ' ' . + '' . + ''; + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/calendars/user1/UUID-123467/UUID-2345', + 'HTTP_DEPTH' => '0', + )); + $request->setBody($body); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(207, $this->response->status,'Received an unexpected status. Full response body: ' . $this->response->body); + + $expected = << + + + /calendars/user1/UUID-123467/UUID-2345 + + + "e207e33c10e5fb9c12cfb35b5d9116e1" + + HTTP/1.1 200 OK + + + +XML; + + $this->assertXmlStringEqualsXmlString($expected, $this->response->getBodyAsString()); + + } + + function testHTMLActionsPanel() { + + $output = ''; + $r = $this->server->emit('onHTMLActionsPanel', [$this->server->tree->getNodeForPath('calendars/user1'), &$output]); + $this->assertFalse($r); + + $this->assertTrue(!!strpos($output,'Display name')); + + } + + /** + * @depends testCalendarMultiGetReport + */ + function testCalendarMultiGetReportNoEnd() { + + $body = + '' . + '' . + '' . + ' ' . + ' ' . + ' ' . + ' ' . + '' . + '/calendars/user1/UUID-123467/UUID-2345' . + ''; + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/calendars/user1', + 'HTTP_DEPTH' => '1', + )); + $request->setBody($body); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(400, $this->response->status,'Invalid HTTP status received. Full response body: ' . $this->response->body); + + } + + /** + * @depends testCalendarMultiGetReport + */ + function testCalendarMultiGetReportNoStart() { + + $body = + '' . + '' . + '' . + ' ' . + ' ' . + ' ' . + ' ' . + '' . + '/calendars/user1/UUID-123467/UUID-2345' . + ''; + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/calendars/user1', + 'HTTP_DEPTH' => '1', + )); + $request->setBody($body); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(400, $this->response->status,'Invalid HTTP status received. Full response body: ' . $this->response->body); + + } + + /** + * @depends testCalendarMultiGetReport + */ + function testCalendarMultiGetReportEndBeforeStart() { + + $body = + '' . + '' . + '' . + ' ' . + ' ' . + ' ' . + ' ' . + '' . + '/calendars/user1/UUID-123467/UUID-2345' . + ''; + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/calendars/user1', + 'HTTP_DEPTH' => '1', + )); + $request->setBody($body); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(400, $this->response->status,'Invalid HTTP status received. Full response body: ' . $this->response->body); + + } + + /** + * @depends testSupportedReportSetPropertyNonCalendar + */ + function testCalendarProperties() { + + $ns = '{urn:ietf:params:xml:ns:caldav}'; + $props = $this->server->getProperties('calendars/user1/UUID-123467', [ + $ns . 'max-resource-size', + $ns . 'supported-calendar-data', + $ns . 'supported-collation-set', + ]); + + $this->assertEquals([ + $ns . 'max-resource-size' => 10000000, + $ns . 'supported-calendar-data' => new Xml\Property\SupportedCalendarData(), + $ns . 'supported-collation-set' => new Xml\Property\SupportedCollationSet(), + ], $props); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Principal/CollectionTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Principal/CollectionTest.php new file mode 100644 index 0000000..625f642 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Principal/CollectionTest.php @@ -0,0 +1,19 @@ +getChildForPrincipal(array( + 'uri' => 'principals/admin', + )); + $this->assertInstanceOf('Sabre\\CalDAV\\Principal\\User', $r); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Principal/ProxyReadTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Principal/ProxyReadTest.php new file mode 100644 index 0000000..1ee999a --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Principal/ProxyReadTest.php @@ -0,0 +1,101 @@ + 'principal/user', + )); + $this->backend = $backend; + return $principal; + + } + + function testGetName() { + + $i = $this->getInstance(); + $this->assertEquals('calendar-proxy-read', $i->getName()); + + } + function testGetDisplayName() { + + $i = $this->getInstance(); + $this->assertEquals('calendar-proxy-read', $i->getDisplayName()); + + } + + function testGetLastModified() { + + $i = $this->getInstance(); + $this->assertNull($i->getLastModified()); + + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + */ + function testDelete() { + + $i = $this->getInstance(); + $i->delete(); + + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + */ + function testSetName() { + + $i = $this->getInstance(); + $i->setName('foo'); + + } + + function testGetAlternateUriSet() { + + $i = $this->getInstance(); + $this->assertEquals(array(), $i->getAlternateUriSet()); + + } + + function testGetPrincipalUri() { + + $i = $this->getInstance(); + $this->assertEquals('principal/user/calendar-proxy-read', $i->getPrincipalUrl()); + + } + + function testGetGroupMemberSet() { + + $i = $this->getInstance(); + $this->assertEquals(array(), $i->getGroupMemberSet()); + + } + + function testGetGroupMembership() { + + $i = $this->getInstance(); + $this->assertEquals(array(), $i->getGroupMembership()); + + } + + function testSetGroupMemberSet() { + + $i = $this->getInstance(); + $i->setGroupMemberSet(array('principals/foo')); + + $expected = array( + $i->getPrincipalUrl() => array('principals/foo') + ); + + $this->assertEquals($expected, $this->backend->groupMembers); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Principal/ProxyWriteTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Principal/ProxyWriteTest.php new file mode 100644 index 0000000..c0186ff --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Principal/ProxyWriteTest.php @@ -0,0 +1,39 @@ + 'principal/user', + )); + $this->backend = $backend; + return $principal; + + } + + function testGetName() { + + $i = $this->getInstance(); + $this->assertEquals('calendar-proxy-write', $i->getName()); + + } + function testGetDisplayName() { + + $i = $this->getInstance(); + $this->assertEquals('calendar-proxy-write', $i->getDisplayName()); + + } + + function testGetPrincipalUri() { + + $i = $this->getInstance(); + $this->assertEquals('principal/user/calendar-proxy-write', $i->getPrincipalUrl()); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Principal/UserTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Principal/UserTest.php new file mode 100644 index 0000000..37b5eae --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Principal/UserTest.php @@ -0,0 +1,126 @@ +addPrincipal(array( + 'uri' => 'principals/user/calendar-proxy-read', + )); + $backend->addPrincipal(array( + 'uri' => 'principals/user/calendar-proxy-write', + )); + $backend->addPrincipal(array( + 'uri' => 'principals/user/random', + )); + return new User($backend, array( + 'uri' => 'principals/user', + )); + + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + */ + function testCreateFile() { + + $u = $this->getInstance(); + $u->createFile('test'); + + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + */ + function testCreateDirectory() { + + $u = $this->getInstance(); + $u->createDirectory('test'); + + } + + function testGetChildProxyRead() { + + $u = $this->getInstance(); + $child = $u->getChild('calendar-proxy-read'); + $this->assertInstanceOf('Sabre\\CalDAV\\Principal\\ProxyRead', $child); + + } + + function testGetChildProxyWrite() { + + $u = $this->getInstance(); + $child = $u->getChild('calendar-proxy-write'); + $this->assertInstanceOf('Sabre\\CalDAV\\Principal\\ProxyWrite', $child); + + } + + /** + * @expectedException Sabre\DAV\Exception\NotFound + */ + function testGetChildNotFound() { + + $u = $this->getInstance(); + $child = $u->getChild('foo'); + + } + + /** + * @expectedException Sabre\DAV\Exception\NotFound + */ + function testGetChildNotFound2() { + + $u = $this->getInstance(); + $child = $u->getChild('random'); + + } + + function testGetChildren() { + + $u = $this->getInstance(); + $children = $u->getChildren(); + $this->assertEquals(2, count($children)); + $this->assertInstanceOf('Sabre\\CalDAV\\Principal\\ProxyRead', $children[0]); + $this->assertInstanceOf('Sabre\\CalDAV\\Principal\\ProxyWrite', $children[1]); + + } + + function testChildExist() { + + $u = $this->getInstance(); + $this->assertTrue($u->childExists('calendar-proxy-read')); + $this->assertTrue($u->childExists('calendar-proxy-write')); + $this->assertFalse($u->childExists('foo')); + + } + + function testGetACL() { + + $expected = array( + array( + 'privilege' => '{DAV:}read', + 'principal' => '{DAV:}authenticated', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user/calendar-proxy-read', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user/calendar-proxy-write', + 'protected' => true, + ), + ); + + $u = $this->getInstance(); + $this->assertEquals($expected, $u->getACL()); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/DeliverNewEventTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/DeliverNewEventTest.php new file mode 100644 index 0000000..d240e75 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/DeliverNewEventTest.php @@ -0,0 +1,93 @@ +caldavBackend->createCalendar( + 'principals/user1', + 'default', + [ + + ] + ); + $this->caldavBackend->createCalendar( + 'principals/user2', + 'default', + [ + + ] + ); + + } + + function testDelivery() { + + $request = new Request('PUT', '/calendars/user1/default/foo.ics'); + $request->setBody(<<server->on('schedule', function($message) use (&$messages) { + $messages[] = $message; + }); + + $response = $this->request($request); + + $this->assertEquals(201, $response->getStatus(), 'Incorrect status code received. Response body:' . $response->getBodyAsString()); + + $result = $this->request(new Request('GET', '/calendars/user1/default/foo.ics'))->getBody(); + $resultVObj = VObject\Reader::read($result); + + $this->assertEquals( + '1.2', + $resultVObj->VEVENT->ATTENDEE[1]['SCHEDULE-STATUS']->getValue() + ); + + $this->assertEquals(1, count($messages)); + $message = $messages[0]; + + $this->assertInstanceOf('\Sabre\VObject\ITip\Message', $message); + $this->assertEquals('mailto:user2.sabredav@sabredav.org', $message->recipient); + $this->assertEquals('Roxy Kesh', $message->recipientName); + $this->assertEquals('mailto:user1.sabredav@sabredav.org', $message->sender); + $this->assertEquals('Administrator', $message->senderName); + $this->assertEquals('REQUEST', $message->method); + + $this->assertEquals('REQUEST', $message->message->METHOD->getValue()); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/FreeBusyRequestTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/FreeBusyRequestTest.php new file mode 100644 index 0000000..d2bb515 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/FreeBusyRequestTest.php @@ -0,0 +1,426 @@ + 'principals/user2', + 'id' => 1, + 'uri' => 'calendar1', + '{' . CalDAV\Plugin::NS_CALDAV . '}calendar-timezone' => "BEGIN:VCALENDAR\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/Berlin\r\nEND:VTIMEZONE\r\nEND:VCALENDAR", + ], + [ + 'principaluri' => 'principals/user2', + 'id' => 2, + 'uri' => 'calendar2', + '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp(ScheduleCalendarTransp::TRANSPARENT), + ], + ]; + $calendarobjects = [ + 1 => [ '1.ics' => [ + 'uri' => '1.ics', + 'calendardata' => 'BEGIN:VCALENDAR +BEGIN:VEVENT +DTSTART:20110101T130000 +DURATION:PT1H +END:VEVENT +END:VCALENDAR', + 'calendarid' => 1, + ]], + 2 => [ '2.ics' => [ + 'uri' => '2.ics', + 'calendardata' => 'BEGIN:VCALENDAR +BEGIN:VEVENT +DTSTART:20110101T080000 +DURATION:PT1H +END:VEVENT +END:VCALENDAR', + 'calendarid' => 2, + ]] + + ]; + + $principalBackend = new DAVACL\PrincipalBackend\Mock(); + $this->caldavBackend = new CalDAV\Backend\MockScheduling($calendars, $calendarobjects); + + $tree = [ + new DAVACL\PrincipalCollection($principalBackend), + new CalDAV\CalendarRoot($principalBackend, $this->caldavBackend), + ]; + + $this->request = HTTP\Sapi::createFromServerArray([ + 'CONTENT_TYPE' => 'text/calendar', + ]); + $this->response = new HTTP\ResponseMock(); + + $this->server = new DAV\Server($tree); + $this->server->httpRequest = $this->request; + $this->server->httpResponse = $this->response; + + $this->aclPlugin = new DAVACL\Plugin(); + $this->server->addPlugin($this->aclPlugin); + + $authBackend = new DAV\Auth\Backend\Mock(); + $authBackend->setPrincipal('principals/user1'); + $this->authPlugin = new DAV\Auth\Plugin($authBackend,'SabreDAV'); + // Forcing authentication to work. + $this->authPlugin->beforeMethod($this->request, $this->response); + $this->server->addPlugin($this->authPlugin); + + // CalDAV plugin + $this->plugin = new CalDAV\Plugin(); + $this->server->addPlugin($this->plugin); + + // Scheduling plugin + $this->plugin = new Plugin(); + $this->server->addPlugin($this->plugin); + + } + + function testWrongContentType() { + + $this->server->httpRequest = new HTTP\Request( + 'POST', + '/calendars/user1/outbox', + ['Content-Type' => 'text/plain'] + ); + + $this->assertNull( + $this->plugin->httpPost($this->server->httpRequest, $this->server->httpResponse) + ); + + } + + function testNotFound() { + + $this->server->httpRequest = new HTTP\Request( + 'POST', + '/calendars/user1/blabla', + ['Content-Type' => 'text/calendar'] + ); + + $this->assertNull( + $this->plugin->httpPost($this->server->httpRequest, $this->server->httpResponse) + ); + + } + + function testNotOutbox() { + + $this->server->httpRequest = new HTTP\Request( + 'POST', + '/calendars/user1/inbox', + ['Content-Type' => 'text/calendar'] + ); + + $this->assertNull( + $this->plugin->httpPost($this->server->httpRequest, $this->server->httpResponse) + ); + + } + + /** + * @expectedException Sabre\DAV\Exception\BadRequest + */ + function testNoItipMethod() { + + $this->server->httpRequest = new HTTP\Request( + 'POST', + '/calendars/user1/outbox', + ['Content-Type' => 'text/calendar'] + ); + + $body = <<server->httpRequest->setBody($body); + $this->plugin->httpPost($this->server->httpRequest, $this->server->httpResponse); + + } + + /** + * @expectedException \Sabre\DAV\Exception\NotImplemented + */ + function testNoVFreeBusy() { + + $this->server->httpRequest = new HTTP\Request( + 'POST', + '/calendars/user1/outbox', + ['Content-Type' => 'text/calendar'] + ); + + $body = <<server->httpRequest->setBody($body); + $this->plugin->httpPost($this->server->httpRequest, $this->server->httpResponse); + + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + */ + function testIncorrectOrganizer() { + + $this->server->httpRequest = new HTTP\Request( + 'POST', + '/calendars/user1/outbox', + ['Content-Type' => 'text/calendar'] + ); + + + $body = <<server->httpRequest->setBody($body); + $this->plugin->httpPost($this->server->httpRequest, $this->server->httpResponse); + + } + + /** + * @expectedException Sabre\DAV\Exception\BadRequest + */ + function testNoAttendees() { + + $this->server->httpRequest = new HTTP\Request( + 'POST', + '/calendars/user1/outbox', + ['Content-Type' => 'text/calendar'] + ); + + $body = <<server->httpRequest->setBody($body); + $this->plugin->httpPost($this->server->httpRequest, $this->server->httpResponse); + + } + + /** + * @expectedException Sabre\DAV\Exception\BadRequest + */ + function testNoDTStart() { + + $this->server->httpRequest = new HTTP\Request( + 'POST', + '/calendars/user1/outbox', + ['Content-Type' => 'text/calendar'] + ); + + $body = <<server->httpRequest->setBody($body); + $this->plugin->httpPost($this->server->httpRequest, $this->server->httpResponse); + + } + + function testSucceed() { + + $this->server->httpRequest = new HTTP\Request( + 'POST', + '/calendars/user1/outbox', + ['Content-Type' => 'text/calendar'] + ); + + $body = <<server->httpRequest->setBody($body); + + // Lazily making the current principal an admin. + $this->aclPlugin->adminPrincipals[] = 'principals/user1'; + + $this->assertFalse( + $this->plugin->httpPost($this->server->httpRequest, $this->response) + ); + + $this->assertEquals(200, $this->response->status); + $this->assertEquals(array( + 'Content-Type' => ['application/xml'], + ), $this->response->getHeaders()); + + $strings = array( + 'mailto:user2.sabredav@sabredav.org', + 'mailto:user3.sabredav@sabredav.org', + '2.0;Success', + '3.7;Could not find principal', + 'FREEBUSY;FBTYPE=BUSY:20110101T120000Z/20110101T130000Z', + ); + + foreach($strings as $string) { + $this->assertTrue( + strpos($this->response->body, $string)!==false, + 'The response body did not contain: ' . $string .'Full response: ' . $this->response->body + ); + } + + $this->assertTrue( + strpos($this->response->body, 'FREEBUSY;FBTYPE=BUSY:20110101T080000Z/20110101T090000Z')==false, + 'The response body did contain free busy info from a transparent calendar.' + ); + + } + + /** + * Testing if the freebusy request still works, even if there are no + * calendars in the target users' account. + */ + function testSucceedNoCalendars() { + + // Deleting calendars + $this->caldavBackend->deleteCalendar(1); + $this->caldavBackend->deleteCalendar(2); + + $this->server->httpRequest = new HTTP\Request( + 'POST', + '/calendars/user1/outbox', + ['Content-Type' => 'text/calendar'] + ); + + $body = <<server->httpRequest->setBody($body); + + // Lazily making the current principal an admin. + $this->aclPlugin->adminPrincipals[] = 'principals/user1'; + + $this->assertFalse( + $this->plugin->httpPost($this->server->httpRequest, $this->response) + ); + + $this->assertEquals(200, $this->response->status); + $this->assertEquals(array( + 'Content-Type' => ['application/xml'], + ), $this->response->getHeaders()); + + $strings = array( + 'mailto:user2.sabredav@sabredav.org', + '2.0;Success', + ); + + foreach($strings as $string) { + $this->assertTrue( + strpos($this->response->body, $string)!==false, + 'The response body did not contain: ' . $string .'Full response: ' . $this->response->body + ); + } + + } + + /* + function testNoPrivilege() { + + $this->markTestIncomplete('Currently there\'s no "no privilege" situation'); + + $this->server->httpRequest = HTTP\Sapi::createFromServerArray(array( + 'CONTENT_TYPE' => 'text/calendar', + 'REQUEST_METHOD' => 'POST', + 'REQUEST_URI' => '/calendars/user1/outbox', + )); + + $body = <<server->httpRequest->setBody($body); + + $this->assertFalse( + $this->plugin->httpPost($this->server->httpRequest, $this->response) + ); + + $this->assertEquals(200, $this->response->status); + $this->assertEquals([ + 'Content-Type' => 'application/xml', + ], $this->response->getHeaders()); + + $strings = [ + 'mailto:user2.sabredav@sabredav.org', + '3.7;No calendar-home-set property found', + ]; + + foreach($strings as $string) { + $this->assertTrue( + strpos($this->response->body, $string)!==false, + 'The response body did not contain: ' . $string .'Full response: ' . $this->response->body + ); + } + + + }*/ + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/IMip/MockPlugin.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/IMip/MockPlugin.php new file mode 100644 index 0000000..9d714c2 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/IMip/MockPlugin.php @@ -0,0 +1,50 @@ +emails[] = array( + 'to' => $to, + 'subject' => $subject, + 'body' => $body, + 'headers' => $headers, + ); + + } + + public function getSentEmails() { + + return $this->emails; + + } + + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/IMipPluginTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/IMipPluginTest.php new file mode 100644 index 0000000..ecaa16c --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/IMipPluginTest.php @@ -0,0 +1,221 @@ +assertEquals( + 'imip', + $plugin->getPluginInfo()['name'] + ); + + } + + function testDeliverReply() { + + $message = new Message(); + $message->sender = 'mailto:sender@example.org'; + $message->senderName = 'Sender'; + $message->recipient = 'mailto:recipient@example.org'; + $message->recipientName = 'Recipient'; + $message->method = 'REPLY'; + + $ics = <<message = Reader::read($ics); + + $result = $this->schedule($message); + + $expected = [ + [ + 'to' => 'Recipient ', + 'subject' => 'Re: Birthday party', + 'body' => $ics, + 'headers' => [ + 'Reply-To: Sender ', + 'From: system@example.org', + 'Content-Type: text/calendar; charset=UTF-8; method=REPLY', + 'X-Sabre-Version: ' . \Sabre\DAV\Version::VERSION, + ], + ] + ]; + + $this->assertEquals($expected, $result); + + } + + function testDeliverReplyNoMailto() { + + $message = new Message(); + $message->sender = 'mailto:sender@example.org'; + $message->senderName = 'Sender'; + $message->recipient = 'http://example.org/recipient'; + $message->recipientName = 'Recipient'; + $message->method = 'REPLY'; + + $ics = <<message = Reader::read($ics); + + $result = $this->schedule($message); + + $expected = []; + + $this->assertEquals($expected, $result); + + } + + function testDeliverRequest() { + + $message = new Message(); + $message->sender = 'mailto:sender@example.org'; + $message->senderName = 'Sender'; + $message->recipient = 'mailto:recipient@example.org'; + $message->recipientName = 'Recipient'; + $message->method = 'REQUEST'; + + $ics = <<message = Reader::read($ics); + + $result = $this->schedule($message); + + $expected = [ + [ + 'to' => 'Recipient ', + 'subject' => 'Birthday party', + 'body' => $ics, + 'headers' => [ + 'Reply-To: Sender ', + 'From: system@example.org', + 'Content-Type: text/calendar; charset=UTF-8; method=REQUEST', + 'X-Sabre-Version: ' . \Sabre\DAV\Version::VERSION, + ], + ] + ]; + + $this->assertEquals($expected, $result); + + } + + function testDeliverCancel() { + + $message = new Message(); + $message->sender = 'mailto:sender@example.org'; + $message->senderName = 'Sender'; + $message->recipient = 'mailto:recipient@example.org'; + $message->recipientName = 'Recipient'; + $message->method = 'CANCEL'; + + $ics = <<message = Reader::read($ics); + + $result = $this->schedule($message); + + $expected = [ + [ + 'to' => 'Recipient ', + 'subject' => 'Cancelled: Birthday party', + 'body' => $ics, + 'headers' => [ + 'Reply-To: Sender ', + 'From: system@example.org', + 'Content-Type: text/calendar; charset=UTF-8; method=CANCEL', + 'X-Sabre-Version: ' . \Sabre\DAV\Version::VERSION, + ], + ] + ]; + + $this->assertEquals($expected, $result); + $this->assertEquals('1.1', substr($message->scheduleStatus, 0, 3)); + + } + + function schedule(Message $message) { + + $plugin = new IMip\MockPlugin('system@example.org'); + + $server = new Server(); + $server->addPlugin($plugin); + $server->emit('schedule', [$message]); + + return $plugin->getSentEmails(); + + } + + function testDeliverInsignificantRequest() { + + $message = new Message(); + $message->sender = 'mailto:sender@example.org'; + $message->senderName = 'Sender'; + $message->recipient = 'mailto:recipient@example.org'; + $message->recipientName = 'Recipient'; + $message->method = 'REQUEST'; + $message->significantChange = false; + + $ics = <<message = Reader::read($ics); + + $result = $this->schedule($message); + + $expected = []; + $this->assertEquals($expected, $result); + $this->assertEquals('1.0', $message->getScheduleStatus()[0]); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/InboxTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/InboxTest.php new file mode 100644 index 0000000..73d8feb --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/InboxTest.php @@ -0,0 +1,186 @@ +assertEquals('inbox', $inbox->getName()); + $this->assertEquals(array(), $inbox->getChildren()); + $this->assertEquals('principals/user1', $inbox->getOwner()); + $this->assertEquals(null, $inbox->getGroup()); + + $this->assertEquals(array( + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}write-properties', + 'principal' => 'principals/user1', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}unbind', + 'principal' => 'principals/user1', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1/calendar-proxy-read', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1/calendar-proxy-write', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}unbind', + 'principal' => 'principals/user1/calendar-proxy-write', + 'protected' => true, + ), + array( + 'privilege' => '{urn:ietf:params:xml:ns:caldav}schedule-deliver-invite', + 'principal' => '{DAV:}authenticated', + 'protected' => true, + ), + array( + 'privilege' => '{urn:ietf:params:xml:ns:caldav}schedule-deliver-reply', + 'principal' => '{DAV:}authenticated', + 'protected' => true, + ), + ), $inbox->getACL()); + + $ok = false; + try { + $inbox->setACL(array()); + } catch (DAV\Exception\MethodNotAllowed $e) { + $ok = true; + } + if (!$ok) { + $this->fail('Exception was not emitted'); + } + + } + + function testGetSupportedPrivilegeSet() { + + $inbox = new Inbox( + new CalDAV\Backend\MockScheduling(), + 'principals/user1' + ); + $r = $inbox->getSupportedPrivilegeSet(); + + $ok = 0; + foreach($r['aggregates'] as $priv) { + + if ($priv['privilege'] == '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-deliver') { + $ok++; + foreach($priv['aggregates'] as $subpriv) { + if ($subpriv['privilege'] == '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-deliver-invite') { + $ok++; + } + if ($subpriv['privilege'] == '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-deliver-reply') { + $ok++; + } + } + } + } + + $this->assertEquals(3, $ok, "We're missing one or more privileges"); + + } + + /** + * @depends testSetup + */ + function testGetChildren() { + + $backend = new CalDAV\Backend\MockScheduling(); + $inbox = new Inbox( + $backend, + 'principals/user1' + ); + + $this->assertEquals( + 0, + count($inbox->getChildren()) + ); + $backend->createSchedulingObject('principals/user1', 'schedule1.ics', "BEGIN:VCALENDAR\r\nEND:VCALENDAR"); + $this->assertEquals( + 1, + count($inbox->getChildren()) + ); + $this->assertInstanceOf('Sabre\CalDAV\Schedule\SchedulingObject', $inbox->getChildren()[0]); + $this->assertEquals( + 'schedule1.ics', + $inbox->getChildren()[0]->getName() + ); + + } + + /** + * @depends testGetChildren + */ + function testCreateFile() { + + $backend = new CalDAV\Backend\MockScheduling(); + $inbox = new Inbox( + $backend, + 'principals/user1' + ); + + $this->assertEquals( + 0, + count($inbox->getChildren()) + ); + $inbox->createFile('schedule1.ics', "BEGIN:VCALENDAR\r\nEND:VCALENDAR"); + $this->assertEquals( + 1, + count($inbox->getChildren()) + ); + $this->assertInstanceOf('Sabre\CalDAV\Schedule\SchedulingObject', $inbox->getChildren()[0]); + $this->assertEquals( + 'schedule1.ics', + $inbox->getChildren()[0]->getName() + ); + + } + + /** + * @depends testSetup + */ + function testCalendarQuery() { + + $backend = new CalDAV\Backend\MockScheduling(); + $inbox = new Inbox( + $backend, + 'principals/user1' + ); + + $this->assertEquals( + 0, + count($inbox->getChildren()) + ); + $backend->createSchedulingObject('principals/user1', 'schedule1.ics', "BEGIN:VCALENDAR\r\nEND:VCALENDAR"); + $this->assertEquals( + ['schedule1.ics'], + $inbox->calendarQuery([ + 'name' => 'VCALENDAR', + 'comp-filters' => [], + 'prop-filters' => [], + 'is-not-defined' => false + ]) + ); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/OutboxPostTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/OutboxPostTest.php new file mode 100644 index 0000000..a6bea13 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/OutboxPostTest.php @@ -0,0 +1,136 @@ + 'POST', + 'REQUEST_URI' => '/notfound', + 'HTTP_CONTENT_TYPE' => 'text/calendar', + )); + + $this->assertHTTPStatus(501, $req); + + } + + function testPostPassThruNotTextCalendar() { + + $req = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'POST', + 'REQUEST_URI' => '/calendars/user1/outbox', + )); + + $this->assertHTTPStatus(501, $req); + + } + + function testPostPassThruNoOutBox() { + + $req = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'POST', + 'REQUEST_URI' => '/calendars', + 'HTTP_CONTENT_TYPE' => 'text/calendar', + )); + + $this->assertHTTPStatus(501, $req); + + } + + function testInvalidIcalBody() { + + $req = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'POST', + 'REQUEST_URI' => '/calendars/user1/outbox', + 'HTTP_ORIGINATOR' => 'mailto:user1.sabredav@sabredav.org', + 'HTTP_RECIPIENT' => 'mailto:user2@example.org', + 'HTTP_CONTENT_TYPE' => 'text/calendar', + )); + $req->setBody('foo'); + + $this->assertHTTPStatus(400, $req); + + } + + function testNoVEVENT() { + + $req = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'POST', + 'REQUEST_URI' => '/calendars/user1/outbox', + 'HTTP_ORIGINATOR' => 'mailto:user1.sabredav@sabredav.org', + 'HTTP_RECIPIENT' => 'mailto:user2@example.org', + 'HTTP_CONTENT_TYPE' => 'text/calendar', + )); + + $body = array( + 'BEGIN:VCALENDAR', + 'BEGIN:VTIMEZONE', + 'END:VTIMEZONE', + 'END:VCALENDAR', + ); + + $req->setBody(implode("\r\n",$body)); + + $this->assertHTTPStatus(400, $req); + + } + + function testNoMETHOD() { + + $req = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'POST', + 'REQUEST_URI' => '/calendars/user1/outbox', + 'HTTP_ORIGINATOR' => 'mailto:user1.sabredav@sabredav.org', + 'HTTP_RECIPIENT' => 'mailto:user2@example.org', + 'HTTP_CONTENT_TYPE' => 'text/calendar', + )); + + $body = array( + 'BEGIN:VCALENDAR', + 'BEGIN:VEVENT', + 'END:VEVENT', + 'END:VCALENDAR', + ); + + $req->setBody(implode("\r\n",$body)); + + $this->assertHTTPStatus(400, $req); + + } + + function testUnsupportedMethod() { + + $req = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'POST', + 'REQUEST_URI' => '/calendars/user1/outbox', + 'HTTP_ORIGINATOR' => 'mailto:user1.sabredav@sabredav.org', + 'HTTP_RECIPIENT' => 'mailto:user2@example.org', + 'HTTP_CONTENT_TYPE' => 'text/calendar', + )); + + $body = array( + 'BEGIN:VCALENDAR', + 'METHOD:PUBLISH', + 'BEGIN:VEVENT', + 'END:VEVENT', + 'END:VCALENDAR', + ); + + $req->setBody(implode("\r\n",$body)); + + $this->assertHTTPStatus(501, $req); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/OutboxTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/OutboxTest.php new file mode 100644 index 0000000..933c715 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/OutboxTest.php @@ -0,0 +1,89 @@ +assertEquals('outbox', $outbox->getName()); + $this->assertEquals(array(), $outbox->getChildren()); + $this->assertEquals('principals/user1', $outbox->getOwner()); + $this->assertEquals(null, $outbox->getGroup()); + + $this->assertEquals(array( + array( + 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-query-freebusy', + 'principal' => 'principals/user1', + 'protected' => true, + ), + + array( + 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-post-vevent', + 'principal' => 'principals/user1', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1', + 'protected' => true, + ), + array( + 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-query-freebusy', + 'principal' => 'principals/user1/calendar-proxy-write', + 'protected' => true, + ), + array( + 'privilege' => '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-post-vevent', + 'principal' => 'principals/user1/calendar-proxy-write', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1/calendar-proxy-read', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1/calendar-proxy-write', + 'protected' => true, + ), + ), $outbox->getACL()); + + $ok = false; + try { + $outbox->setACL(array()); + } catch (DAV\Exception\MethodNotAllowed $e) { + $ok = true; + } + if (!$ok) { + $this->fail('Exception was not emitted'); + } + + } + + function testGetSupportedPrivilegeSet() { + + $outbox = new Outbox('principals/user1'); + $r = $outbox->getSupportedPrivilegeSet(); + + $ok = 0; + foreach($r['aggregates'] as $priv) { + + if ($priv['privilege'] == '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-query-freebusy') { + $ok++; + } + if ($priv['privilege'] == '{' . CalDAV\Plugin::NS_CALDAV . '}schedule-post-vevent') { + $ok++; + } + } + + $this->assertEquals(2, $ok, "We're missing one or more privileges"); + + } + + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/PluginBasicTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/PluginBasicTest.php new file mode 100644 index 0000000..375e6e9 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/PluginBasicTest.php @@ -0,0 +1,35 @@ +assertEquals( + 'caldav-schedule', + $plugin->getPluginInfo()['name'] + ); + + } + + function testOptions() { + + $plugin = new Plugin(); + $this->assertEquals(['calendar-auto-schedule'], $plugin->getFeatures()); + + } + + function testGetHTTPMethods() { + + $this->assertEquals([], $this->caldavSchedulePlugin->getHTTPMethods('notfound')); + $this->assertEquals([], $this->caldavSchedulePlugin->getHTTPMethods('calendars/user1')); + $this->assertEquals(['POST'], $this->caldavSchedulePlugin->getHTTPMethods('calendars/user1/outbox')); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/PluginPropertiesTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/PluginPropertiesTest.php new file mode 100644 index 0000000..b9a2e6c --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/PluginPropertiesTest.php @@ -0,0 +1,65 @@ +caldavBackend->createCalendar( + 'principals/user1', + 'default', + [ + + ] + ); + + } + + function testPrincipalProperties() { + + $props = $this->server->getPropertiesForPath('/principals/user1',array( + '{urn:ietf:params:xml:ns:caldav}schedule-inbox-URL', + '{urn:ietf:params:xml:ns:caldav}schedule-outbox-URL', + '{urn:ietf:params:xml:ns:caldav}calendar-user-address-set', + '{urn:ietf:params:xml:ns:caldav}calendar-user-type', + '{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL', + )); + + $this->assertArrayHasKey(0,$props); + $this->assertArrayHasKey(200,$props[0]); + + $this->assertArrayHasKey('{urn:ietf:params:xml:ns:caldav}schedule-outbox-URL',$props[0][200]); + $prop = $props[0][200]['{urn:ietf:params:xml:ns:caldav}schedule-outbox-URL']; + $this->assertTrue($prop instanceof DAV\Xml\Property\Href); + $this->assertEquals('calendars/user1/outbox/',$prop->getHref()); + + $this->assertArrayHasKey('{urn:ietf:params:xml:ns:caldav}schedule-inbox-URL',$props[0][200]); + $prop = $props[0][200]['{urn:ietf:params:xml:ns:caldav}schedule-inbox-URL']; + $this->assertTrue($prop instanceof DAV\Xml\Property\Href); + $this->assertEquals('calendars/user1/inbox/',$prop->getHref()); + + $this->assertArrayHasKey('{urn:ietf:params:xml:ns:caldav}calendar-user-address-set',$props[0][200]); + $prop = $props[0][200]['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set']; + $this->assertTrue($prop instanceof DAV\Xml\Property\Href); + $this->assertEquals(array('mailto:user1.sabredav@sabredav.org','/principals/user1/'),$prop->getHrefs()); + + $this->assertArrayHasKey('{urn:ietf:params:xml:ns:caldav}calendar-user-type',$props[0][200]); + $prop = $props[0][200]['{urn:ietf:params:xml:ns:caldav}calendar-user-type']; + $this->assertEquals('INDIVIDUAL',$prop); + + $this->assertArrayHasKey('{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL',$props[0][200]); + $prop = $props[0][200]['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL']; + $this->assertEquals('calendars/user1/default/',$prop->getHref()); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/PluginPropertiesWithSharedCalendarTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/PluginPropertiesWithSharedCalendarTest.php new file mode 100644 index 0000000..b20d817 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/PluginPropertiesWithSharedCalendarTest.php @@ -0,0 +1,75 @@ +caldavBackend->createCalendar( + 'principals/user1', + 'shared', + [ + '{http://calendarserver.org/ns/}shared-url' => new DAV\Xml\Property\Href('calendars/user2/default/'), + '{http://sabredav.org/ns}read-only' => false, + '{http://sabredav.org/ns}owner-principal' => 'principals/user2', + ] + ); + $this->caldavBackend->createCalendar( + 'principals/user1', + 'default', + [ + + ] + ); + + } + + function testPrincipalProperties() { + + $props = $this->server->getPropertiesForPath('/principals/user1',array( + '{urn:ietf:params:xml:ns:caldav}schedule-inbox-URL', + '{urn:ietf:params:xml:ns:caldav}schedule-outbox-URL', + '{urn:ietf:params:xml:ns:caldav}calendar-user-address-set', + '{urn:ietf:params:xml:ns:caldav}calendar-user-type', + '{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL', + )); + + $this->assertArrayHasKey(0,$props); + $this->assertArrayHasKey(200,$props[0]); + + $this->assertArrayHasKey('{urn:ietf:params:xml:ns:caldav}schedule-outbox-URL',$props[0][200]); + $prop = $props[0][200]['{urn:ietf:params:xml:ns:caldav}schedule-outbox-URL']; + $this->assertTrue($prop instanceof DAV\Xml\Property\Href); + $this->assertEquals('calendars/user1/outbox/',$prop->getHref()); + + $this->assertArrayHasKey('{urn:ietf:params:xml:ns:caldav}schedule-inbox-URL',$props[0][200]); + $prop = $props[0][200]['{urn:ietf:params:xml:ns:caldav}schedule-inbox-URL']; + $this->assertTrue($prop instanceof DAV\Xml\Property\Href); + $this->assertEquals('calendars/user1/inbox/',$prop->getHref()); + + $this->assertArrayHasKey('{urn:ietf:params:xml:ns:caldav}calendar-user-address-set',$props[0][200]); + $prop = $props[0][200]['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set']; + $this->assertTrue($prop instanceof DAV\Xml\Property\Href); + $this->assertEquals(array('mailto:user1.sabredav@sabredav.org','/principals/user1/'),$prop->getHrefs()); + + $this->assertArrayHasKey('{urn:ietf:params:xml:ns:caldav}calendar-user-type',$props[0][200]); + $prop = $props[0][200]['{urn:ietf:params:xml:ns:caldav}calendar-user-type']; + $this->assertEquals('INDIVIDUAL',$prop); + + $this->assertArrayHasKey('{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL',$props[0][200]); + $prop = $props[0][200]['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL']; + $this->assertEquals('calendars/user1/default/',$prop->getHref()); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/ScheduleDeliverTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/ScheduleDeliverTest.php new file mode 100644 index 0000000..8b23c92 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/ScheduleDeliverTest.php @@ -0,0 +1,664 @@ + 'principals/user1', + 'uri' => 'cal', + ], + [ + 'principaluri' => 'principals/user2', + 'uri' => 'cal', + ], + ]; + + function setUp() { + + $this->calendarObjectUri = '/calendars/user1/cal/object.ics'; + + parent::setUp(); + + } + + function testNewInvite() { + + $newObject = <<deliver(null, $newObject); + $this->assertItemsInInbox('user2', 1); + + $expected = <<assertVObjEquals( + $expected, + $newObject + ); + + } + + function testNewOnWrongCollection() { + + $newObject = <<calendarObjectUri = '/calendars/user1/object.ics'; + $this->deliver(null, $newObject); + $this->assertItemsInInbox('user2', 0); + + + } + function testNewInviteSchedulingDisabled() { + + $newObject = <<deliver(null, $newObject, true); + $this->assertItemsInInbox('user2', 0); + + } + function testUpdatedInvite() { + + $newObject = <<deliver($oldObject, $newObject); + $this->assertItemsInInbox('user2', 1); + + $expected = <<assertVObjEquals( + $expected, + $newObject + ); + + + } + function testUpdatedInviteSchedulingDisabled() { + + $newObject = <<deliver($oldObject, $newObject, true); + $this->assertItemsInInbox('user2', 0); + + } + + function testUpdatedInviteWrongPath() { + + $newObject = <<calendarObjectUri = '/calendars/user1/inbox/foo.ics'; + $this->deliver($oldObject, $newObject); + $this->assertItemsInInbox('user2', 0); + + } + + function testDeletedInvite() { + + $newObject = null; + + $oldObject = <<deliver($oldObject, $newObject); + $this->assertItemsInInbox('user2', 1); + + } + + function testDeletedInviteSchedulingDisabled() { + + $newObject = null; + + $oldObject = <<deliver($oldObject, $newObject, true); + $this->assertItemsInInbox('user2', 0); + + } + + /** + * A MOVE request will trigger an unbind on a scheduling resource. + * + * However, we must not treat it as a cancellation, it just got moved to a + * different calendar. + */ + function testUnbindIgnoredOnMove() { + + $newObject = null; + + $oldObject = <<server->httpRequest->setMethod('MOVE'); + $this->deliver($oldObject, $newObject); + $this->assertItemsInInbox('user2', 0); + + } + + function testDeletedInviteWrongUrl() { + + $newObject = null; + + $oldObject = <<calendarObjectUri = '/calendars/user1/inbox/foo.ics'; + $this->deliver($oldObject, $newObject); + $this->assertItemsInInbox('user2', 0); + + } + + function testReply() { + + $oldObject = <<putPath('calendars/user2/cal/foo.ics', $oldObject); + + $this->deliver($oldObject, $newObject); + $this->assertItemsInInbox('user2', 1); + $this->assertItemsInInbox('user1', 0); + + $expected = <<assertVObjEquals( + $expected, + $newObject + ); + + } + + + + function testInviteUnknownUser() { + + $newObject = <<deliver(null, $newObject); + + $expected = <<assertVObjEquals( + $expected, + $newObject + ); + + } + + function testInviteNoInboxUrl() { + + $newObject = <<server->on('propFind', function($propFind) { + $propFind->set('{' . Plugin::NS_CALDAV . '}schedule-inbox-URL', null, 403); + }); + $this->deliver(null, $newObject); + + $expected = <<assertVObjEquals( + $expected, + $newObject + ); + + } + + function testInviteNoCalendarHomeSet() { + + $newObject = <<server->on('propFind', function($propFind) { + $propFind->set('{' . Plugin::NS_CALDAV . '}calendar-home-set', null, 403); + }); + $this->deliver(null, $newObject); + + $expected = <<assertVObjEquals( + $expected, + $newObject + ); + + } + function testInviteNoDefaultCalendar() { + + $newObject = <<server->on('propFind', function($propFind) { + $propFind->set('{' . Plugin::NS_CALDAV . '}schedule-default-calendar-URL', null, 403); + }); + $this->deliver(null, $newObject); + + $expected = <<assertVObjEquals( + $expected, + $newObject + ); + + } + function testInviteNoScheduler() { + + $newObject = <<server->removeAllListeners('schedule'); + $this->deliver(null, $newObject); + + $expected = <<assertVObjEquals( + $expected, + $newObject + ); + + } + function testInviteNoACLPlugin() { + + $this->setupACL = false; + parent::setUp(); + + $newObject = <<deliver(null, $newObject); + + $expected = <<assertVObjEquals( + $expected, + $newObject + ); + + } + + protected $calendarObjectUri; + + function deliver($oldObject, &$newObject, $disableScheduling = false) { + + $this->server->httpRequest->setUrl($this->calendarObjectUri); + if ($disableScheduling) { + $this->server->httpRequest->setHeader('Schedule-Reply','F'); + } + + if ($oldObject && $newObject) { + // update + $this->putPath($this->calendarObjectUri, $oldObject); + + $stream = fopen('php://memory','r+'); + fwrite($stream, $newObject); + rewind($stream); + $modified = false; + + $this->server->emit('beforeWriteContent', [ + $this->calendarObjectUri, + $this->server->tree->getNodeForPath($this->calendarObjectUri), + &$stream, + &$modified + ]); + if ($modified) { + $newObject = $stream; + } + + } elseif ($oldObject && !$newObject) { + // delete + $this->putPath($this->calendarObjectUri, $oldObject); + + $this->caldavSchedulePlugin->beforeUnbind( + $this->calendarObjectUri + ); + } else { + + // create + $stream = fopen('php://memory','r+'); + fwrite($stream, $newObject); + rewind($stream); + $modified = false; + $this->server->emit('beforeCreateFile', [ + $this->calendarObjectUri, + &$stream, + $this->server->tree->getNodeForPath(dirname($this->calendarObjectUri)), + &$modified + ]); + + if ($modified) { + $newObject = $stream; + } + } + + } + + + /** + * Creates or updates a node at the specified path. + * + * This circumvents sabredav's internal server apis, so all events and + * access control is skipped. + * + * @param string $path + * @param string $data + * @return void + */ + function putPath($path, $data) { + + list($parent, $base) = \Sabre\HTTP\UrlUtil::splitPath($path); + $parentNode = $this->server->tree->getNodeForPath($parent); + + /* + if ($parentNode->childExists($base)) { + $childNode = $parentNode->getChild($base); + $childNode->put($data); + } else {*/ + $parentNode->createFile($base, $data); + //} + + } + + function assertItemsInInbox($user, $count) { + + $inboxNode = $this->server->tree->getNodeForPath('calendars/'.$user.'/inbox'); + $this->assertEquals($count, count($inboxNode->getChildren())); + + } + + function assertVObjEquals($expected, $actual) { + + $format = function($data) { + + $data = trim($data, "\r\n"); + $data = str_replace("\r","", $data); + // Unfolding lines. + $data = str_replace("\n ", "", $data); + + return $data; + + }; + + $this->assertEquals( + $format($expected), + $format($actual) + ); + + } + +} + diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/SchedulingObjectTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/SchedulingObjectTest.php new file mode 100644 index 0000000..d163d07 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Schedule/SchedulingObjectTest.php @@ -0,0 +1,398 @@ +markTestSkipped('SQLite driver is not available'); + $this->backend = new Backend\MockScheduling(); + + $this->data = <<data = <<inbox = new Inbox($this->backend, 'principals/user1'); + $this->inbox->createFile('item1.ics', $this->data); + + } + + function teardown() { + + unset($this->inbox); + unset($this->backend); + + } + + function testSetup() { + + $children = $this->inbox->getChildren(); + $this->assertTrue($children[0] instanceof SchedulingObject); + + $this->assertInternalType('string',$children[0]->getName()); + $this->assertInternalType('string',$children[0]->get()); + $this->assertInternalType('string',$children[0]->getETag()); + $this->assertEquals('text/calendar; charset=utf-8', $children[0]->getContentType()); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testInvalidArg1() { + + $obj = new SchedulingObject( + new Backend\MockScheduling(array(),array()), + array(), + array() + ); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testInvalidArg2() { + + $obj = new SchedulingObject( + new Backend\MockScheduling(array(),array()), + array(), + array('calendarid' => '1') + ); + + } + + /** + * @depends testSetup + * @expectedException \Sabre\DAV\Exception\MethodNotAllowed + */ + function testPut() { + + $children = $this->inbox->getChildren(); + $this->assertTrue($children[0] instanceof SchedulingObject); + + $children[0]->put(''); + + } + + /** + * @depends testSetup + */ + function testDelete() { + + $children = $this->inbox->getChildren(); + $this->assertTrue($children[0] instanceof SchedulingObject); + + $obj = $children[0]; + $obj->delete(); + + $children2 = $this->inbox->getChildren(); + $this->assertEquals(count($children)-1, count($children2)); + + } + + /** + * @depends testSetup + */ + function testGetLastModified() { + + $children = $this->inbox->getChildren(); + $this->assertTrue($children[0] instanceof SchedulingObject); + + $obj = $children[0]; + + $lastMod = $obj->getLastModified(); + $this->assertTrue(is_int($lastMod) || ctype_digit($lastMod) || is_null($lastMod)); + + } + + /** + * @depends testSetup + */ + function testGetSize() { + + $children = $this->inbox->getChildren(); + $this->assertTrue($children[0] instanceof SchedulingObject); + + $obj = $children[0]; + + $size = $obj->getSize(); + $this->assertInternalType('int', $size); + + } + + function testGetOwner() { + + $children = $this->inbox->getChildren(); + $this->assertTrue($children[0] instanceof SchedulingObject); + + $obj = $children[0]; + $this->assertEquals('principals/user1', $obj->getOwner()); + + } + + function testGetGroup() { + + $children = $this->inbox->getChildren(); + $this->assertTrue($children[0] instanceof SchedulingObject); + + $obj = $children[0]; + $this->assertNull($obj->getGroup()); + + } + + function testGetACL() { + + $expected = array( + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}write', + 'principal' => 'principals/user1', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1/calendar-proxy-write', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}write', + 'principal' => 'principals/user1/calendar-proxy-write', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1/calendar-proxy-read', + 'protected' => true, + ), + ); + + $children = $this->inbox->getChildren(); + $this->assertTrue($children[0] instanceof SchedulingObject); + + $obj = $children[0]; + $this->assertEquals($expected, $obj->getACL()); + + } + + function testDefaultACL() { + + $backend = new Backend\MockScheduling([], []); + $calendarObject = new SchedulingObject($backend, ['calendarid' => 1, 'uri' => 'foo', 'principaluri' => 'principals/user1' ]); + $expected = array( + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}write', + 'principal' => 'principals/user1', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1/calendar-proxy-write', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}write', + 'principal' => 'principals/user1/calendar-proxy-write', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1/calendar-proxy-read', + 'protected' => true, + ), + ); + $this->assertEquals($expected, $calendarObject->getACL()); + + + } + + /** + * @expectedException Sabre\DAV\Exception\MethodNotAllowed + */ + function testSetACL() { + + $children = $this->inbox->getChildren(); + $this->assertTrue($children[0] instanceof SchedulingObject); + + $obj = $children[0]; + $obj->setACL(array()); + + } + + function testGet() { + + $children = $this->inbox->getChildren(); + $this->assertTrue($children[0] instanceof SchedulingObject); + + $obj = $children[0]; + + $this->assertEquals($this->data, $obj->get()); + + } + + function testGetRefetch() { + + $backend = new Backend\MockScheduling(); + $backend->createSchedulingObject('principals/user1', 'foo', 'foo'); + + $obj = new SchedulingObject($backend, array( + 'calendarid' => 1, + 'uri' => 'foo', + 'principaluri' => 'principals/user1', + )); + + $this->assertEquals('foo', $obj->get()); + + } + + function testGetEtag1() { + + $objectInfo = array( + 'calendardata' => 'foo', + 'uri' => 'foo', + 'etag' => 'bar', + 'calendarid' => 1 + ); + + $backend = new Backend\MockScheduling(array(), array()); + $obj = new SchedulingObject($backend, $objectInfo); + + $this->assertEquals('bar', $obj->getETag()); + + } + + function testGetEtag2() { + + $objectInfo = array( + 'calendardata' => 'foo', + 'uri' => 'foo', + 'calendarid' => 1 + ); + + $backend = new Backend\MockScheduling(array(), array()); + $obj = new SchedulingObject($backend, $objectInfo); + + $this->assertEquals('"' . md5('foo') . '"', $obj->getETag()); + + } + + function testGetSupportedPrivilegesSet() { + + $objectInfo = array( + 'calendardata' => 'foo', + 'uri' => 'foo', + 'calendarid' => 1 + ); + + $backend = new Backend\MockScheduling(array(), array()); + $obj = new SchedulingObject($backend, $objectInfo); + $this->assertNull($obj->getSupportedPrivilegeSet()); + + } + + function testGetSize1() { + + $objectInfo = array( + 'calendardata' => 'foo', + 'uri' => 'foo', + 'calendarid' => 1 + ); + + $backend = new Backend\MockScheduling(array(), array()); + $obj = new SchedulingObject($backend, $objectInfo); + $this->assertEquals(3, $obj->getSize()); + + } + + function testGetSize2() { + + $objectInfo = array( + 'uri' => 'foo', + 'calendarid' => 1, + 'size' => 4, + ); + + $backend = new Backend\MockScheduling(array(), array()); + $obj = new SchedulingObject($backend, $objectInfo); + $this->assertEquals(4, $obj->getSize()); + + } + + function testGetContentType() { + + $objectInfo = array( + 'uri' => 'foo', + 'calendarid' => 1, + ); + + $backend = new Backend\MockScheduling(array(), array()); + $obj = new SchedulingObject($backend, $objectInfo); + $this->assertEquals('text/calendar; charset=utf-8', $obj->getContentType()); + + } + + function testGetContentType2() { + + $objectInfo = array( + 'uri' => 'foo', + 'calendarid' => 1, + 'component' => 'VEVENT', + ); + + $backend = new Backend\MockScheduling(array(), array()); + $obj = new SchedulingObject($backend, $objectInfo); + $this->assertEquals('text/calendar; charset=utf-8; component=VEVENT', $obj->getContentType()); + + } + function testGetACL2() { + + $objectInfo = array( + 'uri' => 'foo', + 'calendarid' => 1, + 'acl' => [], + ); + + $backend = new Backend\MockScheduling(array(), array()); + $obj = new SchedulingObject($backend, $objectInfo); + $this->assertEquals([], $obj->getACL()); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/ShareableCalendarTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/ShareableCalendarTest.php new file mode 100644 index 0000000..15b869d --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/ShareableCalendarTest.php @@ -0,0 +1,60 @@ + 1, + ); + + $this->backend = new Backend\MockSharing( + array($props) + ); + $this->backend->updateShares(1, array( + array( + 'href' => 'mailto:removeme@example.org', + 'commonName' => 'To be removed', + 'readOnly' => true, + ), + ), array()); + + $this->instance = new ShareableCalendar($this->backend, $props); + + } + + function testUpdateShares() { + + $this->instance->updateShares(array( + array( + 'href' => 'mailto:test@example.org', + 'commonName' => 'Foo Bar', + 'summary' => 'Booh', + 'readOnly' => false, + ), + ), array('mailto:removeme@example.org')); + + $this->assertEquals(array(array( + 'href' => 'mailto:test@example.org', + 'commonName' => 'Foo Bar', + 'summary' => 'Booh', + 'readOnly' => false, + 'status' => SharingPlugin::STATUS_NORESPONSE, + )), $this->instance->getShares()); + + } + + function testPublish() { + + $this->assertNull($this->instance->setPublishStatus(true)); + $this->assertNull($this->instance->setPublishStatus(false)); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/SharedCalendarTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/SharedCalendarTest.php new file mode 100644 index 0000000..337b658 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/SharedCalendarTest.php @@ -0,0 +1,203 @@ + 1, + '{http://calendarserver.org/ns/}shared-url' => 'calendars/owner/original', + '{http://sabredav.org/ns}owner-principal' => 'principals/owner', + '{http://sabredav.org/ns}read-only' => false, + 'principaluri' => 'principals/sharee', + ); + } + + $this->backend = new Backend\MockSharing( + array($props), + array(), + array() + ); + $this->backend->updateShares(1, array( + array( + 'href' => 'mailto:removeme@example.org', + 'commonName' => 'To be removed', + 'readOnly' => true, + ), + ), array()); + + return new SharedCalendar($this->backend, $props); + + } + + function testGetSharedUrl() { + $this->assertEquals('calendars/owner/original', $this->getInstance()->getSharedUrl()); + } + + function testGetShares() { + + $this->assertEquals(array(array( + 'href' => 'mailto:removeme@example.org', + 'commonName' => 'To be removed', + 'readOnly' => true, + 'status' => SharingPlugin::STATUS_NORESPONSE, + )), $this->getInstance()->getShares()); + + } + + function testGetOwner() { + $this->assertEquals('principals/owner', $this->getInstance()->getOwner()); + } + + function testGetACL() { + + $expected = array( + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/owner', + 'protected' => true, + ), + + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/owner/calendar-proxy-write', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/owner/calendar-proxy-read', + 'protected' => true, + ), + array( + 'privilege' => '{' . Plugin::NS_CALDAV . '}read-free-busy', + 'principal' => '{DAV:}authenticated', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}write', + 'principal' => 'principals/owner', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}write', + 'principal' => 'principals/owner/calendar-proxy-write', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/sharee', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}write', + 'principal' => 'principals/sharee', + 'protected' => true, + ), + ); + + $this->assertEquals($expected, $this->getInstance()->getACL()); + + } + + function testGetChildACL() { + + $expected = array( + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/owner', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/owner/calendar-proxy-write', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/owner/calendar-proxy-read', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}write', + 'principal' => 'principals/owner', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}write', + 'principal' => 'principals/owner/calendar-proxy-write', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/sharee', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}write', + 'principal' => 'principals/sharee', + 'protected' => true, + ), + ); + + $this->assertEquals($expected, $this->getInstance()->getChildACL()); + + } + + function testGetChildACLReadOnly() { + + $expected = array( + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/owner', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/owner/calendar-proxy-write', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/owner/calendar-proxy-read', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/sharee', + 'protected' => true, + ), + ); + + $props = array( + 'id' => 1, + '{http://calendarserver.org/ns/}shared-url' => 'calendars/owner/original', + '{http://sabredav.org/ns}owner-principal' => 'principals/owner', + '{http://sabredav.org/ns}read-only' => true, + 'principaluri' => 'principals/sharee', + ); + $this->assertEquals($expected, $this->getInstance($props)->getChildACL()); + + } + + /** + * @expectedException InvalidArgumentException + */ + public function testCreateInstanceMissingArg() { + + $this->getInstance(array( + 'id' => 1, + '{http://calendarserver.org/ns/}shared-url' => 'calendars/owner/original', + '{http://sabredav.org/ns}read-only' => false, + 'principaluri' => 'principals/sharee', + )); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/SharingPluginTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/SharingPluginTest.php new file mode 100644 index 0000000..b4270da --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/SharingPluginTest.php @@ -0,0 +1,389 @@ +caldavCalendars = array( + array( + 'principaluri' => 'principals/user1', + 'id' => 1, + 'uri' => 'cal1', + ), + array( + 'principaluri' => 'principals/user1', + 'id' => 2, + 'uri' => 'cal2', + '{' . Plugin::NS_CALENDARSERVER . '}shared-url' => 'calendars/user1/cal2', + '{http://sabredav.org/ns}owner-principal' => 'principals/user2', + '{http://sabredav.org/ns}read-only' => 'true', + ), + array( + 'principaluri' => 'principals/user1', + 'id' => 3, + 'uri' => 'cal3', + ), + ); + + parent::setUp(); + + // Making the logged in user an admin, for full access: + $this->aclPlugin->adminPrincipals[] = 'principals/user1'; + $this->aclPlugin->adminPrincipals[] = 'principals/user2'; + + } + + function testSimple() { + + $this->assertInstanceOf('Sabre\\CalDAV\\SharingPlugin', $this->server->getPlugin('caldav-sharing')); + $this->assertEquals( + 'caldav-sharing', + $this->caldavSharingPlugin->getPluginInfo()['name'] + ); + + } + + function testGetFeatures() { + + $this->assertEquals(array('calendarserver-sharing'), $this->caldavSharingPlugin->getFeatures()); + + } + + function testBeforeGetShareableCalendar() { + + // Forcing the server to authenticate: + $this->authPlugin->beforeMethod(new HTTP\Request(), new HTTP\Response()); + $props = $this->server->getProperties('calendars/user1/cal1', array( + '{' . Plugin::NS_CALENDARSERVER . '}invite', + '{' . Plugin::NS_CALENDARSERVER . '}allowed-sharing-modes', + )); + + $this->assertInstanceOf('Sabre\\CalDAV\\Xml\\Property\\Invite', $props['{' . Plugin::NS_CALENDARSERVER . '}invite']); + $this->assertInstanceOf('Sabre\\CalDAV\\Xml\\Property\\AllowedSharingModes', $props['{' . Plugin::NS_CALENDARSERVER . '}allowed-sharing-modes']); + + } + + function testBeforeGetSharedCalendar() { + + $props = $this->server->getProperties('calendars/user1/cal2', array( + '{' . Plugin::NS_CALENDARSERVER . '}shared-url', + '{' . Plugin::NS_CALENDARSERVER . '}invite', + )); + + $this->assertInstanceOf('Sabre\\CalDAV\\Xml\\Property\\Invite', $props['{' . Plugin::NS_CALENDARSERVER . '}invite']); + $this->assertInstanceOf('Sabre\\DAV\\Xml\\Property\\Href', $props['{' . Plugin::NS_CALENDARSERVER . '}shared-url']); + + } + + function testUpdateProperties() { + + $this->caldavBackend->updateShares(1, + array( + array( + 'href' => 'mailto:joe@example.org', + ), + ), + array() + ); + $result = $this->server->updateProperties('calendars/user1/cal1', array( + '{DAV:}resourcetype' => new DAV\Xml\Property\ResourceType(['{DAV:}collection']) + )); + + $this->assertEquals([ + '{DAV:}resourcetype' => 200 + ], $result); + + $this->assertEquals(0, count($this->caldavBackend->getShares(1))); + + } + + function testUpdatePropertiesPassThru() { + + $result = $this->server->updateProperties('calendars/user1/cal3', array( + '{DAV:}foo' => 'bar', + )); + + $this->assertEquals(array( + '{DAV:}foo' => 403, + ), $result); + + } + + function testUnknownMethodNoPOST() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'PATCH', + 'REQUEST_URI' => '/', + )); + + $response = $this->request($request); + + $this->assertEquals(501, $response->status, $response->body); + + } + + function testUnknownMethodNoXML() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'POST', + 'REQUEST_URI' => '/', + 'CONTENT_TYPE' => 'text/plain', + )); + + $response = $this->request($request); + + $this->assertEquals(501, $response->status, $response->body); + + } + + function testUnknownMethodNoNode() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'POST', + 'REQUEST_URI' => '/foo', + 'CONTENT_TYPE' => 'text/xml', + )); + + $response = $this->request($request); + + $this->assertEquals(501, $response->status, $response->body); + + } + + function testShareRequest() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'POST', + 'REQUEST_URI' => '/calendars/user1/cal1', + 'CONTENT_TYPE' => 'text/xml', + )); + + $xml = << + + + mailto:joe@example.org + Joe Shmoe + + + + mailto:nancy@example.org + + +RRR; + + $request->setBody($xml); + + $response = $this->request($request); + $this->assertEquals(200, $response->status, $response->body); + + $this->assertEquals(array(array( + 'href' => 'mailto:joe@example.org', + 'commonName' => 'Joe Shmoe', + 'readOnly' => false, + 'status' => SharingPlugin::STATUS_NORESPONSE, + 'summary' => '', + )), $this->caldavBackend->getShares(1)); + + // Verifying that the calendar is now marked shared. + $props = $this->server->getProperties('calendars/user1/cal1', array('{DAV:}resourcetype')); + $this->assertTrue( + $props['{DAV:}resourcetype']->is('{http://calendarserver.org/ns/}shared-owner') + ); + + } + + function testShareRequestNoShareableCalendar() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'POST', + 'REQUEST_URI' => '/calendars/user1/cal2', + 'CONTENT_TYPE' => 'text/xml', + )); + + $xml = ' + + + mailto:joe@example.org + Joe Shmoe + + + + mailto:nancy@example.org + + +'; + + $request->setBody($xml); + + $response = $this->request($request); + $this->assertEquals(501, $response->status, $response->body); + + } + + function testInviteReply() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'POST', + 'REQUEST_URI' => '/calendars/user1', + 'CONTENT_TYPE' => 'text/xml', + )); + + $xml = ' + + /principals/owner + + +'; + + $request->setBody($xml); + $response = $this->request($request); + $this->assertEquals(200, $response->status, $response->body); + + } + + function testInviteBadXML() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'POST', + 'REQUEST_URI' => '/calendars/user1', + 'CONTENT_TYPE' => 'text/xml', + )); + + $xml = ' + + +'; + $request->setBody($xml); + $response = $this->request($request); + $this->assertEquals(400, $response->status, $response->body); + + } + + function testInviteWrongUrl() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'POST', + 'REQUEST_URI' => '/calendars/user1/cal1', + 'CONTENT_TYPE' => 'text/xml', + )); + + $xml = ' + + /principals/owner + +'; + $request->setBody($xml); + $response = $this->request($request); + $this->assertEquals(501, $response->status, $response->body); + + // If the plugin did not handle this request, it must ensure that the + // body is still accessible by other plugins. + $this->assertEquals($xml, $request->getBody(true)); + + } + + function testPublish() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'POST', + 'REQUEST_URI' => '/calendars/user1/cal1', + 'CONTENT_TYPE' => 'text/xml', + )); + + $xml = ' + +'; + + $request->setBody($xml); + + $response = $this->request($request); + $this->assertEquals(202, $response->status, $response->body); + + } + + function testUnpublish() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'POST', + 'REQUEST_URI' => '/calendars/user1/cal1', + 'CONTENT_TYPE' => 'text/xml', + )); + + $xml = ' + +'; + + $request->setBody($xml); + + $response = $this->request($request); + $this->assertEquals(200, $response->status, $response->body); + + } + + function testPublishWrongUrl() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'POST', + 'REQUEST_URI' => '/calendars/user1/cal2', + 'CONTENT_TYPE' => 'text/xml', + )); + + $xml = ' + +'; + + $request->setBody($xml); + + $response = $this->request($request); + $this->assertEquals(501, $response->status, $response->body); + + } + + function testUnpublishWrongUrl() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'POST', + 'REQUEST_URI' => '/calendars/user1/cal2', + 'CONTENT_TYPE' => 'text/xml', + )); + + $xml = ' + +'; + + $request->setBody($xml); + + $response = $this->request($request); + $this->assertEquals(501, $response->status, $response->body); + + } + + function testUnknownXmlDoc() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'POST', + 'REQUEST_URI' => '/calendars/user1/cal2', + 'CONTENT_TYPE' => 'text/xml', + )); + + $xml = ' +'; + + $request->setBody($xml); + + $response = $this->request($request); + $this->assertEquals(501, $response->status, $response->body); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Subscriptions/CreateSubscriptionTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Subscriptions/CreateSubscriptionTest.php new file mode 100644 index 0000000..8ad0f8a --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Subscriptions/CreateSubscriptionTest.php @@ -0,0 +1,123 @@ + + + + + + + + + + #1C4587FF + Jewish holidays + Foo + 19 + + webcal://www.example.org/ + + P1W + + + + +XML; + + $headers = [ + 'Content-Type' => 'application/xml', + ]; + $request = new Request('MKCOL', '/calendars/user1/subscription1', $headers, $body); + + $response = $this->request($request); + $this->assertEquals(201, $response->getStatus()); + $subscriptions = $this->caldavBackend->getSubscriptionsForUser('principals/user1'); + $this->assertSubscription($subscriptions[0]); + + + } + /** + * OS X 10.9.2 and up + */ + function testMKCALENDAR() { + + $body = << + + + + + + + + + + + + P1W + + webcal://www.example.org/ + + #1C4587FF + 19 + Foo + + Jewish holidays + + + +XML; + + $headers = [ + 'Content-Type' => 'application/xml', + ]; + $request = new Request('MKCALENDAR', '/calendars/user1/subscription1', $headers, $body); + + $response = $this->request($request); + $this->assertEquals(201, $response->getStatus()); + $subscriptions = $this->caldavBackend->getSubscriptionsForUser('principals/user1'); + $this->assertSubscription($subscriptions[0]); + + // Also seeing if it works when calling this as a PROPFIND. + $this->assertEquals([ + '{http://calendarserver.org/ns/}subscribed-strip-alarms' => '', + ], + $this->server->getProperties('calendars/user1/subscription1', ['{http://calendarserver.org/ns/}subscribed-strip-alarms']) + ); + + + } + + function assertSubscription($subscription) { + + $this->assertEquals('', $subscription['{http://calendarserver.org/ns/}subscribed-strip-attachments']); + $this->assertEquals('', $subscription['{http://calendarserver.org/ns/}subscribed-strip-todos']); + $this->assertEquals('#1C4587FF', $subscription['{http://apple.com/ns/ical/}calendar-color']); + $this->assertEquals('Jewish holidays', $subscription['{DAV:}displayname']); + $this->assertEquals('Foo', $subscription['{urn:ietf:params:xml:ns:caldav}calendar-description']); + $this->assertEquals('19', $subscription['{http://apple.com/ns/ical/}calendar-order']); + $this->assertEquals('webcal://www.example.org/', $subscription['{http://calendarserver.org/ns/}source']->getHref()); + $this->assertEquals('P1W', $subscription['{http://apple.com/ns/ical/}refreshrate']); + $this->assertEquals('subscription1', $subscription['uri']); + $this->assertEquals('principals/user1', $subscription['principaluri']); + $this->assertEquals('webcal://www.example.org/', $subscription['source']); + $this->assertEquals(['principals/user1', 1], $subscription['id']); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Subscriptions/PluginTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Subscriptions/PluginTest.php new file mode 100644 index 0000000..beed601 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Subscriptions/PluginTest.php @@ -0,0 +1,50 @@ +addPlugin($plugin); + + $this->assertEquals( + '{http://calendarserver.org/ns/}subscribed', + $server->resourceTypeMapping['Sabre\\CalDAV\\Subscriptions\\ISubscription'] + ); + $this->assertEquals( + 'Sabre\\DAV\\Xml\\Property\\Href', + $server->xml->elementMap['{http://calendarserver.org/ns/}source'] + ); + + $this->assertEquals( + ['calendarserver-subscribed'], + $plugin->getFeatures() + ); + + $this->assertEquals( + 'subscriptions', + $plugin->getPluginInfo()['name'] + ); + + } + + function testPropFind() { + + $propName = '{http://calendarserver.org/ns/}subscribed-strip-alarms'; + $propFind = new PropFind('foo', [$propName]); + $propFind->set($propName,null,200); + + $plugin = new Plugin(); + $plugin->propFind($propFind, new \Sabre\DAV\SimpleCollection('hi')); + + $this->assertFalse(is_null($propFind->get($propName))); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Subscriptions/SubscriptionTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Subscriptions/SubscriptionTest.php new file mode 100644 index 0000000..832b3cf --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Subscriptions/SubscriptionTest.php @@ -0,0 +1,141 @@ + new Href('http://example.org/src', false), + 'lastmodified' => date('2013-04-06 11:40:00'), // tomorrow is my birthday! + '{DAV:}displayname' => 'displayname', + ]; + + + $id = $caldavBackend->createSubscription('principals/user1', 'uri', array_merge($info, $override)); + $subInfo = $caldavBackend->getSubscriptionsForUser('principals/user1'); + + $this->assertEquals(1, count($subInfo)); + $subscription = new Subscription($caldavBackend, $subInfo[0]); + + $this->backend = $caldavBackend; + return $subscription; + + } + + function testValues() { + + $sub = $this->getSub(); + + $this->assertEquals('uri', $sub->getName()); + $this->assertEquals(date('2013-04-06 11:40:00'), $sub->getLastModified()); + $this->assertEquals([], $sub->getChildren()); + + $this->assertEquals( + [ + '{DAV:}displayname' => 'displayname', + '{http://calendarserver.org/ns/}source' => new Href('http://example.org/src',false), + ], + $sub->getProperties(['{DAV:}displayname', '{http://calendarserver.org/ns/}source']) + ); + + $this->assertEquals('principals/user1', $sub->getOwner()); + $this->assertNull($sub->getGroup()); + + $acl = [ + [ + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}write', + 'principal' => 'principals/user1', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}write', + 'principal' => 'principals/user1/calendar-proxy-write', + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1/calendar-proxy-read', + 'protected' => true, + ] + ]; + $this->assertEquals($acl, $sub->getACL()); + + $this->assertNull($sub->getSupportedPrivilegeSet()); + + } + + function testValues2() { + + $sub = $this->getSub([ + 'lastmodified' => null, + ]); + + $this->assertEquals(null, $sub->getLastModified()); + + } + + /** + * @expectedException \Sabre\DAV\Exception\MethodNotAllowed + */ + function testSetACL() { + + $sub = $this->getSub(); + $sub->setACL([]); + + } + + function testDelete() { + + $sub = $this->getSub(); + $sub->delete(); + + $this->assertEquals([], $this->backend->getSubscriptionsForUser('principals1/user1')); + + } + + function testUpdateProperties() { + + $sub = $this->getSub(); + $propPatch = new PropPatch([ + '{DAV:}displayname' => 'foo', + ]); + $sub->propPatch($propPatch); + $this->assertTrue($propPatch->commit()); + + $this->assertEquals( + 'foo', + $this->backend->getSubscriptionsForUser('principals/user1')[0]['{DAV:}displayname'] + ); + + } + + /** + * @expectedException \InvalidArgumentException + */ + function testBadConstruct() { + + $caldavBackend = new \Sabre\CalDAV\Backend\MockSubscriptionSupport([],[]); + new Subscription($caldavBackend, []); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/TestUtil.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/TestUtil.php new file mode 100644 index 0000000..19acea2 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/TestUtil.php @@ -0,0 +1,208 @@ +setAttribute(\PDO::ATTR_ERRMODE,\PDO::ERRMODE_EXCEPTION); + + // Yup this is definitely not 'fool proof', but good enough for now. + $queries = explode(';', file_get_contents(__DIR__ . '/../../../examples/sql/sqlite.calendars.sql')); + foreach($queries as $query) { + $pdo->exec($query); + } + // Inserting events through a backend class. + $backend = new Backend\PDO($pdo); + $calendarId = $backend->createCalendar( + 'principals/user1', + 'UUID-123467', + array( + '{DAV:}displayname' => 'user1 calendar', + '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'Calendar description', + '{http://apple.com/ns/ical/}calendar-order' => '1', + '{http://apple.com/ns/ical/}calendar-color' => '#FF0000', + ) + ); + $backend->createCalendar( + 'principals/user1', + 'UUID-123468', + array( + '{DAV:}displayname' => 'user1 calendar2', + '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'Calendar description', + '{http://apple.com/ns/ical/}calendar-order' => '1', + '{http://apple.com/ns/ical/}calendar-color' => '#FF0000', + ) + ); + $backend->createCalendarObject($calendarId, 'UUID-2345', self::getTestCalendarData()); + return $pdo; + + } + + static function getTestCalendarData($type = 1) { + + $calendarData = 'BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Apple Inc.//iCal 4.0.1//EN +CALSCALE:GREGORIAN +BEGIN:VTIMEZONE +TZID:Asia/Seoul +BEGIN:DAYLIGHT +TZOFFSETFROM:+0900 +RRULE:FREQ=YEARLY;UNTIL=19880507T150000Z;BYMONTH=5;BYDAY=2SU +DTSTART:19870510T000000 +TZNAME:GMT+09:00 +TZOFFSETTO:+1000 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:+1000 +DTSTART:19881009T000000 +TZNAME:GMT+09:00 +TZOFFSETTO:+0900 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +CREATED:20100225T154229Z +UID:39A6B5ED-DD51-4AFE-A683-C35EE3749627 +TRANSP:TRANSPARENT +SUMMARY:Something here +DTSTAMP:20100228T130202Z'; + + switch($type) { + case 1 : + $calendarData.="\nDTSTART;TZID=Asia/Seoul:20100223T060000\nDTEND;TZID=Asia/Seoul:20100223T070000\n"; + break; + case 2 : + $calendarData.="\nDTSTART:20100223T060000\nDTEND:20100223T070000\n"; + break; + case 3 : + $calendarData.="\nDTSTART;VALUE=DATE:20100223\nDTEND;VALUE=DATE:20100223\n"; + break; + case 4 : + $calendarData.="\nDTSTART;TZID=Asia/Seoul:20100223T060000\nDURATION:PT1H\n"; + break; + case 5 : + $calendarData.="\nDTSTART;TZID=Asia/Seoul:20100223T060000\nDURATION:-P5D\n"; + break; + case 6 : + $calendarData.="\nDTSTART;VALUE=DATE:20100223\n"; + break; + case 7 : + $calendarData.="\nDTSTART;VALUE=DATETIME:20100223T060000\n"; + break; + + // No DTSTART, so intentionally broken + case 'X' : + $calendarData.="\n"; + break; + } + + + $calendarData.='ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:lisa@example.com +SEQUENCE:2 +END:VEVENT +END:VCALENDAR'; + + return $calendarData; + + } + + static function getTestTODO($type = 'due') { + + switch($type) { + + case 'due' : + $extra = "DUE:20100104T000000Z"; + break; + case 'due2' : + $extra = "DUE:20060104T000000Z"; + break; + case 'due_date' : + $extra = "DUE;VALUE=DATE:20060104"; + break; + case 'due_tz' : + $extra = "DUE;TZID=Asia/Seoul:20060104T000000Z"; + break; + case 'due_dtstart' : + $extra = "DTSTART:20050223T060000Z\nDUE:20060104T000000Z"; + break; + case 'due_dtstart2' : + $extra = "DTSTART:20090223T060000Z\nDUE:20100104T000000Z"; + break; + case 'dtstart' : + $extra = 'DTSTART:20100223T060000Z'; + break; + case 'dtstart2' : + $extra = 'DTSTART:20060223T060000Z'; + break; + case 'dtstart_date' : + $extra = 'DTSTART;VALUE=DATE:20100223'; + break; + case 'dtstart_tz' : + $extra = 'DTSTART;TZID=Asia/Seoul:20100223T060000Z'; + break; + case 'dtstart_duration' : + $extra = "DTSTART:20061023T060000Z\nDURATION:PT1H"; + break; + case 'dtstart_duration2' : + $extra = "DTSTART:20101023T060000Z\nDURATION:PT1H"; + break; + case 'completed' : + $extra = 'COMPLETED:20060601T000000Z'; + break; + case 'completed2' : + $extra = 'COMPLETED:20090601T000000Z'; + break; + case 'created' : + $extra = 'CREATED:20060601T000000Z'; + break; + case 'created2' : + $extra = 'CREATED:20090601T000000Z'; + break; + case 'completedcreated' : + $extra = "CREATED:20060601T000000Z\nCOMPLETED:20070101T000000Z"; + break; + case 'completedcreated2' : + $extra = "CREATED:20090601T000000Z\nCOMPLETED:20100101T000000Z"; + break; + case 'notime' : + $extra = 'X-FILLER:oh hello'; + break; + default : + throw new Exception('Unknown type: ' . $type); + + } + + $todo = 'BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Example Corp.//CalDAV Client//EN +BEGIN:VTODO +DTSTAMP:20060205T235335Z +' . $extra . ' +STATUS:NEEDS-ACTION +SUMMARY:Task #1 +UID:DDDEEB7915FA61233B861457@example.com +BEGIN:VALARM +ACTION:AUDIO +TRIGGER;RELATED=START:-PT10M +END:VALARM +END:VTODO +END:VCALENDAR'; + + return $todo; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/ValidateICalTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/ValidateICalTest.php new file mode 100644 index 0000000..be166d9 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/ValidateICalTest.php @@ -0,0 +1,281 @@ + 'calendar1', + 'principaluri' => 'principals/admin', + 'uri' => 'calendar1', + '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set' => new Xml\Property\SupportedCalendarComponentSet( ['VEVENT','VTODO','VJOURNAL'] ), + ), + array( + 'id' => 'calendar2', + 'principaluri' => 'principals/admin', + 'uri' => 'calendar2', + '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set' => new Xml\Property\SupportedCalendarComponentSet( ['VTODO','VJOURNAL'] ), + ) + ); + + $this->calBackend = new Backend\Mock($calendars, []); + $principalBackend = new DAVACL\PrincipalBackend\Mock(); + + $tree = [ + new CalendarRoot($principalBackend, $this->calBackend), + ]; + + $this->server = new DAV\Server($tree); + $this->server->sapi = new HTTP\SapiMock(); + $this->server->debugExceptions = true; + + $plugin = new Plugin(); + $this->server->addPlugin($plugin); + + $response = new HTTP\ResponseMock(); + $this->server->httpResponse = $response; + + } + + function request(HTTP\Request $request) { + + $this->server->httpRequest = $request; + $this->server->exec(); + + return $this->server->httpResponse; + + } + + function testCreateFile() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/calendars/admin/calendar1/blabla.ics', + )); + + $response = $this->request($request); + + $this->assertEquals(415, $response->status); + + } + + function testCreateFileValid() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/calendars/admin/calendar1/blabla.ics', + )); + $request->setBody("BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nUID:foo\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"); + + $response = $this->request($request); + + $this->assertEquals(201, $response->status, 'Incorrect status returned! Full response body: ' . $response->body); + $this->assertEquals(array( + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Length' => ['0'], + 'ETag' => ['"' . md5("BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nUID:foo\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n") . '"'], + ), $response->getHeaders()); + + $expected = array( + 'uri' => 'blabla.ics', + 'calendardata' => "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nUID:foo\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", + 'calendarid' => 'calendar1', + 'lastmodified' => null, + ); + + $this->assertEquals($expected, $this->calBackend->getCalendarObject('calendar1','blabla.ics')); + + } + + function testCreateFileNoComponents() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/calendars/admin/calendar1/blabla.ics', + )); + $request->setBody("BEGIN:VCALENDAR\r\nEND:VCALENDAR\r\n"); + + $response = $this->request($request); + + $this->assertEquals(400, $response->status, 'Incorrect status returned! Full response body: ' . $response->body); + + } + + function testCreateFileNoUID() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/calendars/admin/calendar1/blabla.ics', + )); + $request->setBody("BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"); + + $response = $this->request($request); + + $this->assertEquals(400, $response->status, 'Incorrect status returned! Full response body: ' . $response->body); + + } + + function testCreateFileVCard() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/calendars/admin/calendar1/blabla.ics', + )); + $request->setBody("BEGIN:VCARD\r\nEND:VCARD\r\n"); + + $response = $this->request($request); + + $this->assertEquals(415, $response->status, 'Incorrect status returned! Full response body: ' . $response->body); + + } + + function testCreateFile2Components() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/calendars/admin/calendar1/blabla.ics', + )); + $request->setBody("BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nUID:foo\r\nEND:VEVENT\r\nBEGIN:VJOURNAL\r\nUID:foo\r\nEND:VJOURNAL\r\nEND:VCALENDAR\r\n"); + + $response = $this->request($request); + + $this->assertEquals(400, $response->status, 'Incorrect status returned! Full response body: ' . $response->body); + + } + + function testCreateFile2UIDS() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/calendars/admin/calendar1/blabla.ics', + )); + $request->setBody("BEGIN:VCALENDAR\r\nBEGIN:VTIMEZONE\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nUID:foo\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nUID:bar\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"); + + $response = $this->request($request); + + $this->assertEquals(400, $response->status, 'Incorrect status returned! Full response body: ' . $response->body); + + } + + function testCreateFileWrongComponent() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/calendars/admin/calendar1/blabla.ics', + )); + $request->setBody("BEGIN:VCALENDAR\r\nBEGIN:VTIMEZONE\r\nEND:VTIMEZONE\r\nBEGIN:VFREEBUSY\r\nUID:foo\r\nEND:VFREEBUSY\r\nEND:VCALENDAR\r\n"); + + $response = $this->request($request); + + $this->assertEquals(400, $response->status, 'Incorrect status returned! Full response body: ' . $response->body); + + } + + function testUpdateFile() { + + $this->calBackend->createCalendarObject('calendar1','blabla.ics','foo'); + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/calendars/admin/calendar1/blabla.ics', + )); + + $response = $this->request($request); + + $this->assertEquals(415, $response->status); + + } + + function testUpdateFileParsableBody() { + + $this->calBackend->createCalendarObject('calendar1','blabla.ics','foo'); + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/calendars/admin/calendar1/blabla.ics', + )); + $body = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nUID:foo\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"; + $request->setBody($body); + + $response = $this->request($request); + + $this->assertEquals(204, $response->status); + + $expected = array( + 'uri' => 'blabla.ics', + 'calendardata' => $body, + 'calendarid' => 'calendar1', + 'lastmodified' => null, + ); + + $this->assertEquals($expected, $this->calBackend->getCalendarObject('calendar1','blabla.ics')); + + } + + function testCreateFileInvalidComponent() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/calendars/admin/calendar2/blabla.ics', + )); + $request->setBody("BEGIN:VCALENDAR\r\nBEGIN:VTIMEZONE\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nUID:foo\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"); + + $response = $this->request($request); + + $this->assertEquals(403, $response->status, 'Incorrect status returned! Full response body: ' . $response->body); + + } + + function testUpdateFileInvalidComponent() { + + $this->calBackend->createCalendarObject('calendar2','blabla.ics','foo'); + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/calendars/admin/calendar2/blabla.ics', + )); + $request->setBody("BEGIN:VCALENDAR\r\nBEGIN:VTIMEZONE\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nUID:foo\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"); + + $response = $this->request($request); + + $this->assertEquals(403, $response->status, 'Incorrect status returned! Full response body: ' . $response->body); + + } + + /** + * What we are testing here, is if we send in a latin1 character, the + * server should automatically transform this into UTF-8. + * + * More importantly. If any transformation happens, the etag must no longer + * be returned by the server. + */ + function testCreateFileModified() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/calendars/admin/calendar1/blabla.ics', + )); + $request->setBody("BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nUID:foo\r\nSUMMARY:Meeting in M\xfcnster\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"); + + $response = $this->request($request); + + $this->assertEquals(201, $response->status, 'Incorrect status returned! Full response body: ' . $response->body); + $this->assertNull($response->getHeader('ETag')); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Notification/InviteReplyTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Notification/InviteReplyTest.php new file mode 100644 index 0000000..50e31a8 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Notification/InviteReplyTest.php @@ -0,0 +1,145 @@ +assertEquals('foo', $notification->getId()); + $this->assertEquals('"1"', $notification->getETag()); + + $simpleExpected = '' . "\n" . ''; + + $writer = new Writer(); + $writer->namespaceMap = [ + 'http://calendarserver.org/ns/' => 'cs', + ]; + $writer->openMemory(); + $writer->startDocument('1.0', 'UTF-8'); + $writer->startElement('{http://calendarserver.org/ns/}root'); + $writer->write($notification); + $writer->endElement(); + + $this->assertEquals($simpleExpected, $writer->outputMemory()); + + $writer = new Writer(); + $writer->contextUri = '/'; + $writer->namespaceMap = [ + 'http://calendarserver.org/ns/' => 'cs', + 'DAV:' => 'd', + ]; + $writer->openMemory(); + $writer->startDocument('1.0', 'UTF-8'); + $writer->startElement('{http://calendarserver.org/ns/}root'); + $notification->xmlSerializeFull($writer); + $writer->endElement(); + + $this->assertXmlStringEqualsXmlString($expected, $writer->outputMemory()); + + + } + + function dataProvider() { + + $dtStamp = new \DateTime('2012-01-01 00:00:00 GMT'); + return array( + array( + array( + 'id' => 'foo', + 'dtStamp' => $dtStamp, + 'etag' => '"1"', + 'inReplyTo' => 'bar', + 'href' => 'mailto:foo@example.org', + 'type' => CalDAV\SharingPlugin::STATUS_ACCEPTED, + 'hostUrl' => 'calendar' + ), +<< + + 20120101T000000Z + + foo + bar + mailto:foo@example.org + + + /calendar + + + + +FOO + ), + array( + array( + 'id' => 'foo', + 'dtStamp' => $dtStamp, + 'etag' => '"1"', + 'inReplyTo' => 'bar', + 'href' => 'mailto:foo@example.org', + 'type' => CalDAV\SharingPlugin::STATUS_DECLINED, + 'hostUrl' => 'calendar', + 'summary' => 'Summary!' + ), +<< + + 20120101T000000Z + + foo + bar + mailto:foo@example.org + + + /calendar + + Summary! + + + +FOO + ), + + ); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testMissingArg() { + + new InviteReply(array()); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testUnknownArg() { + + new InviteReply(array( + 'foo-i-will-break' => true, + + 'id' => 1, + 'etag' => '"bla"', + 'href' => 'abc', + 'dtStamp' => 'def', + 'inReplyTo' => 'qrs', + 'type' => 'ghi', + 'hostUrl' => 'jkl', + )); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Notification/InviteTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Notification/InviteTest.php new file mode 100644 index 0000000..54b9aae --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Notification/InviteTest.php @@ -0,0 +1,236 @@ +assertEquals('foo', $notification->getId()); + $this->assertEquals('"1"', $notification->getETag()); + + $simpleExpected = '' . "\n"; + $this->namespaceMap['http://calendarserver.org/ns/'] = 'cs'; + + $xml = $this->write($notification); + + $this->assertXmlStringEqualsXmlString($simpleExpected, $xml); + + $this->namespaceMap['urn:ietf:params:xml:ns:caldav'] = 'cal'; + $xml = $this->writeFull($notification); + + $this->assertXmlStringEqualsXmlString($expected, $xml); + + + } + + function dataProvider() { + + $dtStamp = new \DateTime('2012-01-01 00:00:00', new \DateTimeZone('GMT')); + return array( + array( + array( + 'id' => 'foo', + 'dtStamp' => $dtStamp, + 'etag' => '"1"', + 'href' => 'mailto:foo@example.org', + 'type' => CalDAV\SharingPlugin::STATUS_ACCEPTED, + 'readOnly' => true, + 'hostUrl' => 'calendar', + 'organizer' => 'principal/user1', + 'commonName' => 'John Doe', + 'summary' => 'Awesome stuff!' + ), +<< + + 20120101T000000Z + + foo + mailto:foo@example.org + + + /calendar + + Awesome stuff! + + + + + /principal/user1 + John Doe + + John Doe + + + +FOO + ), + array( + array( + 'id' => 'foo', + 'dtStamp' => $dtStamp, + 'etag' => '"1"', + 'href' => 'mailto:foo@example.org', + 'type' => CalDAV\SharingPlugin::STATUS_DECLINED, + 'readOnly' => true, + 'hostUrl' => 'calendar', + 'organizer' => 'principal/user1', + 'commonName' => 'John Doe', + ), +<< + + 20120101T000000Z + + foo + mailto:foo@example.org + + + /calendar + + + + + + /principal/user1 + John Doe + + John Doe + + +FOO + ), + array( + array( + 'id' => 'foo', + 'dtStamp' => $dtStamp, + 'etag' => '"1"', + 'href' => 'mailto:foo@example.org', + 'type' => CalDAV\SharingPlugin::STATUS_NORESPONSE, + 'readOnly' => true, + 'hostUrl' => 'calendar', + 'organizer' => 'principal/user1', + 'firstName' => 'Foo', + 'lastName' => 'Bar', + ), +<< + + 20120101T000000Z + + foo + mailto:foo@example.org + + + /calendar + + + + + + /principal/user1 + Foo + Bar + + Foo + Bar + + + +FOO + ), + array( + array( + 'id' => 'foo', + 'dtStamp' => $dtStamp, + 'etag' => '"1"', + 'href' => 'mailto:foo@example.org', + 'type' => CalDAV\SharingPlugin::STATUS_DELETED, + 'readOnly' => false, + 'hostUrl' => 'calendar', + 'organizer' => 'mailto:user1@fruux.com', + 'supportedComponents' => new CalDAV\Xml\Property\SupportedCalendarComponentSet(['VEVENT','VTODO']), + ), +<< + + 20120101T000000Z + + foo + mailto:foo@example.org + + + /calendar + + + + + + mailto:user1@fruux.com + + + + + + + + +FOO + ), + + ); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testMissingArg() { + + new Invite(array()); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testUnknownArg() { + + new Invite(array( + 'foo-i-will-break' => true, + + 'id' => 1, + 'etag' => '"bla"', + 'href' => 'abc', + 'dtStamp' => 'def', + 'type' => 'ghi', + 'readOnly' => true, + 'hostUrl' => 'jkl', + 'organizer' => 'mno', + )); + + } + + function writeFull($input) { + + $writer = new Writer(); + $writer->contextUri = '/'; + $writer->namespaceMap = $this->namespaceMap; + $writer->openMemory(); + $writer->startElement('{http://calendarserver.org/ns/}root'); + $input->xmlSerializeFull($writer); + $writer->endElement(); + return $writer->outputMemory(); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Notification/SystemStatusTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Notification/SystemStatusTest.php new file mode 100644 index 0000000..54251e0 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Notification/SystemStatusTest.php @@ -0,0 +1,67 @@ +assertEquals('foo', $notification->getId()); + $this->assertEquals('"1"', $notification->getETag()); + + $writer = new Writer(); + $writer->namespaceMap = [ + 'http://calendarserver.org/ns/' => 'cs', + ]; + $writer->openMemory(); + $writer->startDocument('1.0', 'UTF-8'); + $writer->startElement('{http://calendarserver.org/ns/}root'); + $writer->write($notification); + $writer->endElement(); + $this->assertXmlStringEqualsXmlString($expected1, $writer->outputMemory()); + + $writer = new Writer(); + $writer->namespaceMap = [ + 'http://calendarserver.org/ns/' => 'cs', + 'DAV:' => 'd', + ]; + $writer->openMemory(); + $writer->startDocument('1.0', 'UTF-8'); + $writer->startElement('{http://calendarserver.org/ns/}root'); + $notification->xmlSerializeFull($writer);; + $writer->endElement(); + $this->assertXmlStringEqualsXmlString($expected2, $writer->outputMemory()); + + } + + function dataProvider() { + + return [ + + [ + new SystemStatus('foo', '"1"'), + '' . "\n" . '' . "\n", + '' . "\n" . '' . "\n", + ], + [ + new SystemStatus('foo', '"1"', SystemStatus::TYPE_MEDIUM,'bar'), + '' . "\n" . '' . "\n", + '' . "\n" . 'bar' . "\n", + ], + [ + new SystemStatus('foo', '"1"', SystemStatus::TYPE_LOW,null,'http://example.org/'), + '' . "\n" . '' . "\n", + '' . "\n" . 'http://example.org/' . "\n", + ] + ]; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/AllowedSharingModesTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/AllowedSharingModesTest.php new file mode 100644 index 0000000..375d7fe --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/AllowedSharingModesTest.php @@ -0,0 +1,38 @@ +assertInstanceOf('Sabre\CalDAV\Xml\Property\AllowedSharingModes', $sccs); + + } + + /** + * @depends testSimple + */ + function testSerialize() { + + $property = new AllowedSharingModes(true,true); + + $this->namespaceMap[CalDAV\Plugin::NS_CALDAV] = 'cal'; + $this->namespaceMap[CalDAV\Plugin::NS_CALENDARSERVER] = 'cs'; + $xml = $this->write(['{DAV:}root' => $property]); + + $this->assertXmlStringEqualsXmlString( +' + + + + +', $xml); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/EmailAddressSetTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/EmailAddressSetTest.php new file mode 100644 index 0000000..c3d8c40 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/EmailAddressSetTest.php @@ -0,0 +1,40 @@ + 'cs', + 'DAV:' => 'd', + ]; + + function testSimple() { + + $eas = new EmailAddressSet(['foo@example.org']); + $this->assertEquals(['foo@example.org'], $eas->getValue()); + + } + + /** + * @depends testSimple + */ + function testSerialize() { + + $property = new EmailAddressSet(['foo@example.org']); + + $xml = $this->write([ + '{DAV:}root' => $property + ]); + + $this->assertXmlStringEqualsXmlString( +' + +foo@example.org +', $xml); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/InviteTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/InviteTest.php new file mode 100644 index 0000000..64102c4 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/InviteTest.php @@ -0,0 +1,185 @@ +namespaceMap[CalDAV\Plugin::NS_CALDAV] = 'cal'; + $this->namespaceMap[CalDAV\Plugin::NS_CALENDARSERVER] = 'cs'; + + + } + + function testSimple() { + + $sccs = new Invite([]); + $this->assertInstanceOf('Sabre\CalDAV\Xml\Property\Invite', $sccs); + + } + + /** + * @depends testSimple + */ + function testSerialize() { + + $property = new Invite([ + [ + 'href' => 'mailto:user1@example.org', + 'status' => CalDAV\SharingPlugin::STATUS_ACCEPTED, + 'readOnly' => false, + ], + [ + 'href' => 'mailto:user2@example.org', + 'commonName' => 'John Doe', + 'status' => CalDAV\SharingPlugin::STATUS_DECLINED, + 'readOnly' => true, + ], + [ + 'href' => 'mailto:user3@example.org', + 'commonName' => 'Joe Shmoe', + 'status' => CalDAV\SharingPlugin::STATUS_NORESPONSE, + 'readOnly' => true, + 'summary' => 'Something, something', + ], + [ + 'href' => 'mailto:user4@example.org', + 'commonName' => 'Hoe Boe', + 'status' => CalDAV\SharingPlugin::STATUS_INVALID, + 'readOnly' => true, + ], + ], [ + 'href' => 'mailto:thedoctor@example.org', + 'commonName' => 'The Doctor', + 'firstName' => 'The', + 'lastName' => 'Doctor', + ]); + + $xml = $this->write(['{DAV:}root' => $property]); + + $this->assertXmlStringEqualsXmlString( +' + + + mailto:thedoctor@example.org + The Doctor + The + Doctor + + + mailto:user1@example.org + + + + + + + mailto:user2@example.org + John Doe + + + + + + + mailto:user3@example.org + Joe Shmoe + + + + + Something, something + + + mailto:user4@example.org + Hoe Boe + + + + + + +', $xml); + + } + + /** + * @depends testSerialize + */ + function testUnserialize() { + + $input = [ + [ + 'href' => 'mailto:user1@example.org', + 'status' => CalDAV\SharingPlugin::STATUS_ACCEPTED, + 'readOnly' => false, + 'commonName' => '', + 'summary' => '', + ], + [ + 'href' => 'mailto:user2@example.org', + 'commonName' => 'John Doe', + 'status' => CalDAV\SharingPlugin::STATUS_DECLINED, + 'readOnly' => true, + 'summary' => '', + ], + [ + 'href' => 'mailto:user3@example.org', + 'commonName' => 'Joe Shmoe', + 'status' => CalDAV\SharingPlugin::STATUS_NORESPONSE, + 'readOnly' => true, + 'summary' => 'Something, something', + ], + [ + 'href' => 'mailto:user4@example.org', + 'commonName' => 'Hoe Boe', + 'status' => CalDAV\SharingPlugin::STATUS_INVALID, + 'readOnly' => true, + 'summary' => '', + ], + ]; + + // Creating the xml + $inputProperty = new Invite($input); + $xml = $this->write(['{DAV:}root' => $inputProperty]); + // Parsing it again + + $doc2 = $this->parse( + $xml, + ['{DAV:}root' => 'Sabre\\CalDAV\\Xml\\Property\\Invite'] + ); + + $outputProperty = $doc2['value']; + + $this->assertEquals($input, $outputProperty->getValue()); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testUnserializeNoStatus() { + +$xml = ' + + + mailto:user1@example.org + + + + + +'; + + $this->parse( + $xml, + ['{DAV:}root' => 'Sabre\\CalDAV\\Xml\\Property\\Invite'] + ); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/ScheduleCalendarTranspTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/ScheduleCalendarTranspTest.php new file mode 100644 index 0000000..729db45 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/ScheduleCalendarTranspTest.php @@ -0,0 +1,118 @@ +namespaceMap[CalDAV\Plugin::NS_CALDAV] = 'cal'; + $this->namespaceMap[CalDAV\Plugin::NS_CALENDARSERVER] = 'cs'; + + + } + + function testSimple() { + + $prop = new ScheduleCalendarTransp(ScheduleCalendarTransp::OPAQUE); + $this->assertEquals( + ScheduleCalendarTransp::OPAQUE, + $prop->getValue() + ); + + } + + /** + * @expectedException \InvalidArgumentException + */ + function testBadValue() { + + new ScheduleCalendarTransp('ahhh'); + + } + + /** + * @depends testSimple + */ + function testSerializeOpaque() { + + $property = new ScheduleCalendarTransp(ScheduleCalendarTransp::OPAQUE); + $xml = $this->write(['{DAV:}root' => $property]); + + $this->assertXmlStringEqualsXmlString( +' + + + +', $xml); + + } + + /** + * @depends testSimple + */ + function testSerializeTransparent() { + + $property = new ScheduleCalendarTransp(ScheduleCalendarTransp::TRANSPARENT); + $xml = $this->write(['{DAV:}root' => $property]); + + $this->assertXmlStringEqualsXmlString( +' + + + +', $xml); + + } + + function testUnserializeTransparent() { + + $cal = CalDAV\Plugin::NS_CALDAV; + $cs = CalDAV\Plugin::NS_CALENDARSERVER; + +$xml = << + + + +XML; + + $result = $this->parse( + $xml, + ['{DAV:}root' => 'Sabre\\CalDAV\\Xml\\Property\\ScheduleCalendarTransp'] + ); + + $this->assertEquals( + new ScheduleCalendarTransp(ScheduleCalendarTransp::TRANSPARENT), + $result['value'] + ); + + } + + function testUnserializeOpaque() { + + $cal = CalDAV\Plugin::NS_CALDAV; + $cs = CalDAV\Plugin::NS_CALENDARSERVER; + +$xml = << + + + +XML; + + $result = $this->parse( + $xml, + ['{DAV:}root' => 'Sabre\\CalDAV\\Xml\\Property\\ScheduleCalendarTransp'] + ); + + $this->assertEquals( + new ScheduleCalendarTransp(ScheduleCalendarTransp::OPAQUE), + $result['value'] + ); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/SupportedCalendarComponentSetTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/SupportedCalendarComponentSetTest.php new file mode 100644 index 0000000..44a7b88 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/SupportedCalendarComponentSetTest.php @@ -0,0 +1,107 @@ +namespaceMap[CalDAV\Plugin::NS_CALDAV] = 'cal'; + $this->namespaceMap[CalDAV\Plugin::NS_CALENDARSERVER] = 'cs'; + + } + + function testSimple() { + + $prop = new SupportedCalendarComponentSet(['VEVENT']); + $this->assertEquals( + ['VEVENT'], + $prop->getValue() + ); + + } + + function testMultiple() { + + $prop = new SupportedCalendarComponentSet(['VEVENT', 'VTODO']); + $this->assertEquals( + ['VEVENT', 'VTODO'], + $prop->getValue() + ); + + } + + /** + * @depends testSimple + * @depends testMultiple + */ + function testSerialize() { + + $property = new SupportedCalendarComponentSet(['VEVENT', 'VTODO']); + $xml = $this->write(['{DAV:}root' => $property]); + + $this->assertXmlStringEqualsXmlString( +' + + + + +', $xml); + + } + + function testUnserialize() { + + $cal = CalDAV\Plugin::NS_CALDAV; + $cs = CalDAV\Plugin::NS_CALENDARSERVER; + +$xml = << + + + + +XML; + + $result = $this->parse( + $xml, + ['{DAV:}root' => 'Sabre\\CalDAV\\Xml\\Property\\SupportedCalendarComponentSet'] + ); + + $this->assertEquals( + new SupportedCalendarComponentSet(['VEVENT', 'VTODO']), + $result['value'] + ); + + } + + /** + * @expectedException \Sabre\Xml\ParseException + */ + function testUnserializeEmpty() { + + $cal = CalDAV\Plugin::NS_CALDAV; + $cs = CalDAV\Plugin::NS_CALENDARSERVER; + +$xml = << + + +XML; + + $result = $this->parse( + $xml, + ['{DAV:}root' => 'Sabre\\CalDAV\\Xml\\Property\\SupportedCalendarComponentSet'] + ); + + $this->assertEquals( + new SupportedCalendarComponentSet([]), + $result['value'] + ); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/SupportedCalendarDataTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/SupportedCalendarDataTest.php new file mode 100644 index 0000000..442b6a0 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/SupportedCalendarDataTest.php @@ -0,0 +1,36 @@ +assertInstanceOf('Sabre\CalDAV\Xml\Property\SupportedCalendarData', $sccs); + + } + + /** + * @depends testSimple + */ + function testSerialize() { + + $this->namespaceMap[CalDAV\Plugin::NS_CALDAV] = 'cal'; + $property = new SupportedCalendarData(); + + $xml = $this->write(['{DAV:}root' => $property]); + + $this->assertXmlStringEqualsXmlString( +' + + + +', $xml); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/SupportedCollationSetTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/SupportedCollationSetTest.php new file mode 100644 index 0000000..e009fb6 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Property/SupportedCollationSetTest.php @@ -0,0 +1,37 @@ +assertInstanceOf('Sabre\CalDAV\Xml\Property\SupportedCollationSet', $scs); + + } + + /** + * @depends testSimple + */ + function testSerialize() { + + $property = new SupportedCollationSet(); + + $this->namespaceMap[CalDAV\Plugin::NS_CALDAV] = 'cal'; + $xml = $this->write(['{DAV:}root' => $property]); + + $this->assertXmlStringEqualsXmlString( +' + +i;ascii-casemap +i;octet +i;unicode-casemap +', $xml); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Request/CalendarQueryReportTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Request/CalendarQueryReportTest.php new file mode 100644 index 0000000..05e91e5 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Request/CalendarQueryReportTest.php @@ -0,0 +1,367 @@ + 'Sabre\\CalDAV\\Xml\\Request\CalendarQueryReport', + ]; + + function testDeserialize() { + + $xml = << + + + + + + + + +XML; + + $result = $this->parse($xml); + $calendarQueryReport = new CalendarQueryReport(); + $calendarQueryReport->properties = [ + '{DAV:}getetag', + ]; + $calendarQueryReport->filters = [ + 'name' => 'VCALENDAR', + 'is-not-defined' => false, + 'comp-filters' => [], + 'prop-filters' => [], + 'time-range' => false, + ]; + + $this->assertEquals( + $calendarQueryReport, + $result['value'] + ); + + } + + /** + * @expectedException Sabre\DAV\Exception\BadRequest + */ + function testDeserializeNoFilter() { + + $xml = << + + + + + +XML; + + $this->parse($xml); + + } + + function testDeserializeComplex() { + + $xml = << + + + + + + + + + + + + + + + + + + + + + + hi + + + + + + + + + + Hello + + + + + +XML; + + $result = $this->parse($xml); + $calendarQueryReport = new CalendarQueryReport(); + $calendarQueryReport->version = '2.0'; + $calendarQueryReport->contentType = 'application/json+calendar'; + $calendarQueryReport->properties = [ + '{DAV:}getetag', + '{urn:ietf:params:xml:ns:caldav}calendar-data', + ]; + $calendarQueryReport->expand = [ + 'start' => new \DateTime('2015-01-01 00:00:00', new \DateTimeZone('UTC')), + 'end' => new \DateTime('2016-01-01 00:00:00', new \DateTimeZone('UTC')), + ]; + $calendarQueryReport->filters = [ + 'name' => 'VCALENDAR', + 'is-not-defined' => false, + 'comp-filters' => [ + [ + 'name' => 'VEVENT', + 'is-not-defined' => false, + 'comp-filters' => [ + [ + 'name' => 'VALARM', + 'is-not-defined' => true, + 'comp-filters' => [], + 'prop-filters' => [], + 'time-range' => false, + ], + ], + 'prop-filters' => [ + [ + 'name' => 'UID', + 'is-not-defined' => false, + 'time-range' => false, + 'text-match' => null, + 'param-filters' => [], + ], + [ + 'name' => 'X-PROP', + 'is-not-defined' => false, + 'time-range' => false, + 'text-match' => null, + 'param-filters' => [ + [ + 'name' => 'X-PARAM', + 'is-not-defined' => false, + 'text-match' => null, + ], + [ + 'name' => 'X-PARAM2', + 'is-not-defined' => true, + 'text-match' => null, + ], + [ + 'name' => 'X-PARAM3', + 'is-not-defined' => false, + 'text-match' => [ + 'negate-condition' => true, + 'collation' => 'i;ascii-casemap', + 'value' => 'hi', + ], + ], + ], + ], + [ + 'name' => 'X-PROP2', + 'is-not-defined' => true, + 'time-range' => false, + 'text-match' => null, + 'param-filters' => [], + ], + [ + 'name' => 'X-PROP3', + 'is-not-defined' => false, + 'time-range' => [ + 'start' => new \DateTime('2015-01-01 00:00:00', new \DateTimeZone('UTC')), + 'end' => new \DateTime('2016-01-01 00:00:00', new \DateTimeZone('UTC')), + ], + 'text-match' => null, + 'param-filters' => [], + ], + [ + 'name' => 'X-PROP4', + 'is-not-defined' => false, + 'time-range' => false, + 'text-match' => [ + 'negate-condition' => false, + 'collation' => 'i;ascii-casemap', + 'value' => 'Hello', + ], + 'param-filters' => [], + ], + ], + 'time-range' => [ + 'start' => new \DateTime('2015-01-01 00:00:00', new \DateTimeZone('UTC')), + 'end' => new \DateTime('2016-01-01 00:00:00', new \DateTimeZone('UTC')), + ] + ], + ], + 'prop-filters' => [], + 'time-range' => false, + ]; + + $this->assertEquals( + $calendarQueryReport, + $result['value'] + ); + + } + + /** + * @expectedException \Sabre\DAV\Exception\BadRequest + */ + function testDeserializeDoubleTopCompFilter() { + + $xml = << + + + + + + + + + + + + +XML; + + $this->parse($xml); + + } + + /** + * @expectedException \Sabre\DAV\Exception\BadRequest + */ + function testDeserializeMissingExpandEnd() { + + $xml = << + + + + + + + + + + + +XML; + + $this->parse($xml); + + } + + /** + * @expectedException \Sabre\DAV\Exception\BadRequest + */ + function testDeserializeExpandEndBeforeStart() { + + $xml = << + + + + + + + + + + + +XML; + + $this->parse($xml); + + } + + /** + * @expectedException \Sabre\DAV\Exception\BadRequest + */ + function testDeserializeTimeRangeOnVCALENDAR() { + + $xml = << + + + + + + + + + + + +XML; + + $this->parse($xml); + + } + + /** + * @expectedException \Sabre\DAV\Exception\BadRequest + */ + function testDeserializeTimeRangeEndBeforeStart() { + + $xml = << + + + + + + + + + + + + + +XML; + + $this->parse($xml); + + } + + /** + * @expectedException \Sabre\DAV\Exception\BadRequest + */ + function testDeserializeTimeRangePropEndBeforeStart() { + + $xml = << + + + + + + + + + + + + + + + +XML; + + $this->parse($xml); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Request/InviteReplyTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Request/InviteReplyTest.php new file mode 100644 index 0000000..eca35c9 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Request/InviteReplyTest.php @@ -0,0 +1,78 @@ + 'Sabre\\CalDAV\\Xml\\Request\\InviteReply', + ]; + + function testDeserialize() { + + $xml = << + + /principal/1 + /calendar/1 + + blabla + Summary + +XML; + + $result = $this->parse($xml); + $inviteReply = new InviteReply('/principal/1', '/calendar/1', 'blabla', 'Summary', SharingPlugin::STATUS_ACCEPTED); + + $this->assertEquals( + $inviteReply, + $result['value'] + ); + + } + + function testDeserializeDeclined() { + + $xml = << + + /principal/1 + /calendar/1 + + blabla + Summary + +XML; + + $result = $this->parse($xml); + $inviteReply = new InviteReply('/principal/1', '/calendar/1', 'blabla', 'Summary', SharingPlugin::STATUS_DECLINED); + + $this->assertEquals( + $inviteReply, + $result['value'] + ); + + } + + /** + * @expectedException \Sabre\DAV\Exception\BadRequest + */ + function testDeserializeNoHostUrl() { + + $xml = << + + /principal/1 + + blabla + Summary + +XML; + + $this->parse($xml); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Request/ShareTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Request/ShareTest.php new file mode 100644 index 0000000..ad9e130 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CalDAV/Xml/Request/ShareTest.php @@ -0,0 +1,86 @@ + 'Sabre\\CalDAV\\Xml\\Request\\Share', + ]; + + function testDeserialize() { + + $xml = << + + + mailto:eric@example.com + Eric York + Shared workspace + + + + mailto:foo@bar + + +XML; + + $result = $this->parse($xml); + $share = new Share( + [ + [ + 'href' => 'mailto:eric@example.com', + 'commonName' => 'Eric York', + 'summary' => 'Shared workspace', + 'readOnly' => false, + ] + ], + [ + 'mailto:foo@bar', + ] + ); + + $this->assertEquals( + $share, + $result['value'] + ); + + } + + function testDeserializeMininal() { + + $xml = << + + + mailto:eric@example.com + + + +XML; + + $result = $this->parse($xml); + $share = new Share( + [ + [ + 'href' => 'mailto:eric@example.com', + 'commonName' => null, + 'summary' => null, + 'readOnly' => true, + ] + ], + [] + ); + + $this->assertEquals( + $share, + $result['value'] + ); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/AbstractPluginTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/AbstractPluginTest.php new file mode 100644 index 0000000..a123099 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/AbstractPluginTest.php @@ -0,0 +1,43 @@ +backend = new Backend\Mock(); + $principalBackend = new DAVACL\PrincipalBackend\Mock(); + + $tree = array( + new AddressBookRoot($principalBackend, $this->backend), + new DAVACL\PrincipalCollection($principalBackend) + ); + + $this->plugin = new Plugin(); + $this->plugin->directories = array('directory'); + $this->server = new DAV\Server($tree); + $this->server->sapi = new HTTP\SapiMock(); + $this->server->addPlugin($this->plugin); + $this->server->debugExceptions = true; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/AddressBookHomeTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/AddressBookHomeTest.php new file mode 100644 index 0000000..18ec255 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/AddressBookHomeTest.php @@ -0,0 +1,164 @@ +backend = new Backend\Mock(); + $this->s = new AddressBookHome( + $this->backend, + 'principals/user1' + ); + + } + + function testGetName() { + + $this->assertEquals('user1', $this->s->getName()); + + } + + /** + * @expectedException Sabre\DAV\Exception\MethodNotAllowed + */ + function testSetName() { + + $this->s->setName('user2'); + + } + + /** + * @expectedException Sabre\DAV\Exception\MethodNotAllowed + */ + function testDelete() { + + $this->s->delete(); + + } + + function testGetLastModified() { + + $this->assertNull($this->s->getLastModified()); + + } + + /** + * @expectedException Sabre\DAV\Exception\MethodNotAllowed + */ + function testCreateFile() { + + $this->s->createFile('bla'); + + } + + /** + * @expectedException Sabre\DAV\Exception\MethodNotAllowed + */ + function testCreateDirectory() { + + $this->s->createDirectory('bla'); + + } + + function testGetChild() { + + $child = $this->s->getChild('book1'); + $this->assertInstanceOf('Sabre\\CardDAV\\AddressBook', $child); + $this->assertEquals('book1', $child->getName()); + + } + + /** + * @expectedException Sabre\DAV\Exception\NotFound + */ + function testGetChild404() { + + $this->s->getChild('book2'); + + } + + function testGetChildren() { + + $children = $this->s->getChildren(); + $this->assertEquals(1, count($children)); + $this->assertInstanceOf('Sabre\\CardDAV\\AddressBook', $children[0]); + $this->assertEquals('book1', $children[0]->getName()); + + } + + function testCreateExtendedCollection() { + + $resourceType = [ + '{' . Plugin::NS_CARDDAV . '}addressbook', + '{DAV:}collection', + ]; + $this->s->createExtendedCollection('book2', new MkCol($resourceType, ['{DAV:}displayname' => 'a-book 2'])); + + $this->assertEquals(array( + 'id' => 'book2', + 'uri' => 'book2', + '{DAV:}displayname' => 'a-book 2', + 'principaluri' => 'principals/user1', + ), $this->backend->addressBooks[1]); + + } + + /** + * @expectedException Sabre\DAV\Exception\InvalidResourceType + */ + function testCreateExtendedCollectionInvalid() { + + $resourceType = array( + '{DAV:}collection', + ); + $this->s->createExtendedCollection('book2', new MkCol($resourceType, array('{DAV:}displayname' => 'a-book 2'))); + + } + + + function testACLMethods() { + + $this->assertEquals('principals/user1', $this->s->getOwner()); + $this->assertNull($this->s->getGroup()); + $this->assertEquals(array( + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}write', + 'principal' => 'principals/user1', + 'protected' => true, + ), + ), $this->s->getACL()); + + } + + /** + * @expectedException Sabre\DAV\Exception\MethodNotAllowed + */ + function testSetACL() { + + $this->s->setACL(array()); + + } + + function testGetSupportedPrivilegeSet() { + + $this->assertNull( + $this->s->getSupportedPrivilegeSet() + ); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/AddressBookQueryTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/AddressBookQueryTest.php new file mode 100644 index 0000000..478f6be --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/AddressBookQueryTest.php @@ -0,0 +1,310 @@ + 'REPORT', + 'REQUEST_URI' => '/addressbooks/user1/book1', + 'HTTP_DEPTH' => '1', + )); + + $request->setBody( +' + + + + + + + +' + ); + + $response = new HTTP\ResponseMock(); + + $this->server->httpRequest = $request; + $this->server->httpResponse = $response; + + $this->server->exec(); + + $this->assertEquals(207, $response->status, 'Incorrect status code. Full response body:' . $response->body); + + // using the client for parsing + $client = new DAV\Client(array('baseUri'=>'/')); + + $result = $client->parseMultiStatus($response->body); + + $this->assertEquals(array( + '/addressbooks/user1/book1/card1' => array( + 200 => array( + '{DAV:}getetag' => '"' . md5("BEGIN:VCARD\nVERSION:3.0\nUID:12345\nEND:VCARD") . '"', + ), + ), + '/addressbooks/user1/book1/card2' => array( + 404 => array( + '{DAV:}getetag' => null, + ), + ) + ), $result); + + + } + + function testQueryDepth0() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/addressbooks/user1/book1/card1', + 'HTTP_DEPTH' => '0', + )); + + $request->setBody( +' + + + + + + + +' + ); + + $response = new HTTP\ResponseMock(); + + $this->server->httpRequest = $request; + $this->server->httpResponse = $response; + + $this->server->exec(); + + $this->assertEquals(207, $response->status, 'Incorrect status code. Full response body:' . $response->body); + + // using the client for parsing + $client = new DAV\Client(array('baseUri'=>'/')); + + $result = $client->parseMultiStatus($response->body); + + $this->assertEquals(array( + '/addressbooks/user1/book1/card1' => array( + 200 => array( + '{DAV:}getetag' => '"' . md5("BEGIN:VCARD\nVERSION:3.0\nUID:12345\nEND:VCARD") . '"', + ), + ), + ), $result); + + + } + + function testQueryNoMatch() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/addressbooks/user1/book1', + 'HTTP_DEPTH' => '1', + )); + + $request->setBody( +' + + + + + + + +' + ); + + $response = new HTTP\ResponseMock(); + + $this->server->httpRequest = $request; + $this->server->httpResponse = $response; + + $this->server->exec(); + + $this->assertEquals(207, $response->status, 'Incorrect status code. Full response body:' . $response->body); + + // using the client for parsing + $client = new DAV\Client(array('baseUri'=>'/')); + + $result = $client->parseMultiStatus($response->body); + + $this->assertEquals(array(), $result); + + } + + function testQueryLimit() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/addressbooks/user1/book1', + 'HTTP_DEPTH' => '1', + )); + + $request->setBody( +' + + + + + + + + 1 +' + ); + + $response = new HTTP\ResponseMock(); + + $this->server->httpRequest = $request; + $this->server->httpResponse = $response; + + $this->server->exec(); + + $this->assertEquals(207, $response->status, 'Incorrect status code. Full response body:' . $response->body); + + // using the client for parsing + $client = new DAV\Client(array('baseUri'=>'/')); + + $result = $client->parseMultiStatus($response->body); + + $this->assertEquals(array( + '/addressbooks/user1/book1/card1' => array( + 200 => array( + '{DAV:}getetag' => '"' . md5("BEGIN:VCARD\nVERSION:3.0\nUID:12345\nEND:VCARD"). '"', + ), + ), + ), $result); + + + } + + function testJson() { + + $request = new HTTP\Request( + 'REPORT', + '/addressbooks/user1/book1/card1', + ['Depth' => '0'] + ); + + $request->setBody( +' + + + + + +' + ); + + $response = new HTTP\ResponseMock(); + + $this->server->httpRequest = $request; + $this->server->httpResponse = $response; + + $this->server->exec(); + + $this->assertEquals(207, $response->status, 'Incorrect status code. Full response body:' . $response->body); + + // using the client for parsing + $client = new DAV\Client(array('baseUri'=>'/')); + + $result = $client->parseMultiStatus($response->body); + + $vobjVersion = \Sabre\VObject\Version::VERSION; + + $this->assertEquals(array( + '/addressbooks/user1/book1/card1' => array( + 200 => array( + '{DAV:}getetag' => '"' . md5("BEGIN:VCARD\nVERSION:3.0\nUID:12345\nEND:VCARD"). '"', + '{urn:ietf:params:xml:ns:carddav}address-data' => '["vcard",[["version",{},"text","4.0"],["prodid",{},"text","-\/\/Sabre\/\/Sabre VObject ' . $vobjVersion . '\/\/EN"],["uid",{},"text","12345"]]]', + ), + ), + ), $result); + + } + + function testVCard4() { + + $request = new HTTP\Request( + 'REPORT', + '/addressbooks/user1/book1/card1', + ['Depth' => '0'] + ); + + $request->setBody( +' + + + + + +' + ); + + $response = new HTTP\ResponseMock(); + + $this->server->httpRequest = $request; + $this->server->httpResponse = $response; + + $this->server->exec(); + + $this->assertEquals(207, $response->status, 'Incorrect status code. Full response body:' . $response->body); + + // using the client for parsing + $client = new DAV\Client(array('baseUri'=>'/')); + + $result = $client->parseMultiStatus($response->body); + + $vobjVersion = \Sabre\VObject\Version::VERSION; + + $this->assertEquals(array( + '/addressbooks/user1/book1/card1' => array( + 200 => array( + '{DAV:}getetag' => '"' . md5("BEGIN:VCARD\nVERSION:3.0\nUID:12345\nEND:VCARD"). '"', + '{urn:ietf:params:xml:ns:carddav}address-data' => "BEGIN:VCARD\r\nVERSION:4.0\r\nPRODID:-//Sabre//Sabre VObject $vobjVersion//EN\r\nUID:12345\r\nEND:VCARD\r\n", + ), + ), + ), $result); + + } + + function testAddressBookDepth0() { + + $request = new HTTP\Request( + 'REPORT', + '/addressbooks/user1/book1', + ['Depth' => '0'] + ); + + $request->setBody( +' + + + + + +' + ); + + $response = new HTTP\ResponseMock(); + + $this->server->httpRequest = $request; + $this->server->httpResponse = $response; + + $this->server->exec(); + + $this->assertEquals(415, $response->status, 'Incorrect status code. Full response body:' . $response->body); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/AddressBookRootTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/AddressBookRootTest.php new file mode 100644 index 0000000..fc20480 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/AddressBookRootTest.php @@ -0,0 +1,31 @@ +assertEquals('addressbooks', $root->getName()); + + } + + function testGetChildForPrincipal() { + + $pBackend = new DAVACL\PrincipalBackend\Mock(); + $cBackend = new Backend\Mock(); + $root = new AddressBookRoot($pBackend, $cBackend); + + $children = $root->getChildren(); + $this->assertEquals(3, count($children)); + + $this->assertInstanceOf('Sabre\\CardDAV\\AddressBookHome', $children[0]); + $this->assertEquals('user1', $children[0]->getName()); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/AddressBookTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/AddressBookTest.php new file mode 100644 index 0000000..397e728 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/AddressBookTest.php @@ -0,0 +1,209 @@ +backend = new Backend\Mock(); + $this->ab = new AddressBook( + $this->backend, + array( + 'uri' => 'book1', + 'id' => 'foo', + '{DAV:}displayname' => 'd-name', + 'principaluri' => 'principals/user1', + ) + ); + + } + + function testGetName() { + + $this->assertEquals('book1', $this->ab->getName()); + + } + + function testGetChild() { + + $card = $this->ab->getChild('card1'); + $this->assertInstanceOf('Sabre\\CardDAV\\Card', $card); + $this->assertEquals('card1', $card->getName()); + + } + + /** + * @expectedException Sabre\DAV\Exception\NotFound + */ + function testGetChildNotFound() { + + $card = $this->ab->getChild('card3'); + + } + + function testGetChildren() { + + $cards = $this->ab->getChildren(); + $this->assertEquals(2, count($cards)); + + $this->assertEquals('card1', $cards[0]->getName()); + $this->assertEquals('card2', $cards[1]->getName()); + + } + + /** + * @expectedException Sabre\DAV\Exception\MethodNotAllowed + */ + function testCreateDirectory() { + + $this->ab->createDirectory('name'); + + } + + function testCreateFile() { + + $file = fopen('php://memory','r+'); + fwrite($file,'foo'); + rewind($file); + $this->ab->createFile('card2',$file); + + $this->assertEquals('foo', $this->backend->cards['foo']['card2']); + + } + + function testDelete() { + + $this->ab->delete(); + $this->assertEquals(array(), $this->backend->addressBooks); + + } + + /** + * @expectedException Sabre\DAV\Exception\MethodNotAllowed + */ + function testSetName() { + + $this->ab->setName('foo'); + + } + + function testGetLastModified() { + + $this->assertNull($this->ab->getLastModified()); + + } + + function testUpdateProperties() { + + $propPatch = new PropPatch([ + '{DAV:}displayname' => 'barrr', + ]); + $this->ab->propPatch($propPatch); + $this->assertTrue($propPatch->commit()); + + $this->assertEquals('barrr', $this->backend->addressBooks[0]['{DAV:}displayname']); + + } + + function testGetProperties() { + + $props = $this->ab->getProperties(array('{DAV:}displayname')); + $this->assertEquals(array( + '{DAV:}displayname' => 'd-name', + ), $props); + + } + + function testACLMethods() { + + $this->assertEquals('principals/user1', $this->ab->getOwner()); + $this->assertNull($this->ab->getGroup()); + $this->assertEquals(array( + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}write', + 'principal' => 'principals/user1', + 'protected' => true, + ), + ), $this->ab->getACL()); + + } + + /** + * @expectedException Sabre\DAV\Exception\MethodNotAllowed + */ + function testSetACL() { + + $this->ab->setACL(array()); + + } + + function testGetSupportedPrivilegeSet() { + + $this->assertNull( + $this->ab->getSupportedPrivilegeSet() + ); + + } + + function testGetSyncTokenNoSyncSupport() { + + $this->assertNull(null, $this->ab->getSyncToken()); + + } + function testGetChangesNoSyncSupport() { + + $this->assertNull($this->ab->getChanges(1,null)); + + } + + function testGetSyncToken() { + + if (!SABRE_HASSQLITE) { + $this->markTestSkipped('Sqlite is required for this test to run'); + } + $ab = new AddressBook(TestUtil::getBackend(), [ 'id' => 1, '{DAV:}sync-token' => 2]); + $this->assertEquals(2, $ab->getSyncToken()); + } + + function testGetSyncToken2() { + + if (!SABRE_HASSQLITE) { + $this->markTestSkipped('Sqlite is required for this test to run'); + } + $ab = new AddressBook(TestUtil::getBackend(), [ 'id' => 1, '{http://sabredav.org/ns}sync-token' => 2]); + $this->assertEquals(2, $ab->getSyncToken()); + } + + function testGetChanges() { + + if (!SABRE_HASSQLITE) { + $this->markTestSkipped('Sqlite is required for this test to run'); + } + $ab = new AddressBook(TestUtil::getBackend(), [ 'id' => 1, '{DAV:}sync-token' => 2]); + $this->assertEquals([ + 'syncToken' => 2, + 'modified' => [], + 'deleted' => [], + 'added' => ['UUID-2345'], + ], $ab->getChanges(1, 1)); + + } + + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/Backend/AbstractPDOTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/Backend/AbstractPDOTest.php new file mode 100644 index 0000000..2537745 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/Backend/AbstractPDOTest.php @@ -0,0 +1,350 @@ +getPDO(); + $this->backend = new PDO($pdo); + $pdo->exec('INSERT INTO addressbooks (principaluri, displayname, uri, description, synctoken) VALUES ("principals/user1", "book1", "book1", "addressbook 1", 1)'); + $pdo->exec('INSERT INTO cards (addressbookid, carddata, uri, lastmodified, etag, size) VALUES (1, "card1", "card1", 0, "' . md5('card1') . '", 5)'); + + } + + public function testGetAddressBooksForUser() { + + $result = $this->backend->getAddressBooksForUser('principals/user1'); + + $expected = array( + array( + 'id' => 1, + 'uri' => 'book1', + 'principaluri' => 'principals/user1', + '{DAV:}displayname' => 'book1', + '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' => 'addressbook 1', + '{http://calendarserver.org/ns/}getctag' => 1, + '{http://sabredav.org/ns}sync-token' => "1" + ) + ); + + $this->assertEquals($expected, $result); + + } + + public function testUpdateAddressBookInvalidProp() { + + $propPatch = new PropPatch([ + '{DAV:}displayname' => 'updated', + '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' => 'updated', + '{DAV:}foo' => 'bar', + ]); + + $this->backend->updateAddressBook(1, $propPatch); + $result = $propPatch->commit(); + + $this->assertFalse($result); + + $result = $this->backend->getAddressBooksForUser('principals/user1'); + + $expected = array( + array( + 'id' => 1, + 'uri' => 'book1', + 'principaluri' => 'principals/user1', + '{DAV:}displayname' => 'book1', + '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' => 'addressbook 1', + '{http://calendarserver.org/ns/}getctag' => 1, + '{http://sabredav.org/ns}sync-token' => 1 + ) + ); + + $this->assertEquals($expected, $result); + + } + + public function testUpdateAddressBookNoProps() { + + $propPatch = new PropPatch([ + ]); + + $this->backend->updateAddressBook(1, $propPatch); + $result = $propPatch->commit(); + $this->assertTrue($result); + + $result = $this->backend->getAddressBooksForUser('principals/user1'); + + $expected = array( + array( + 'id' => 1, + 'uri' => 'book1', + 'principaluri' => 'principals/user1', + '{DAV:}displayname' => 'book1', + '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' => 'addressbook 1', + '{http://calendarserver.org/ns/}getctag' => 1, + '{http://sabredav.org/ns}sync-token' => 1 + ) + ); + + $this->assertEquals($expected, $result); + + + } + + public function testUpdateAddressBookSuccess() { + + $propPatch = new PropPatch([ + '{DAV:}displayname' => 'updated', + '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' => 'updated', + ]); + + $this->backend->updateAddressBook(1, $propPatch); + $result = $propPatch->commit(); + + $this->assertTrue($result); + + $result = $this->backend->getAddressBooksForUser('principals/user1'); + + $expected = array( + array( + 'id' => 1, + 'uri' => 'book1', + 'principaluri' => 'principals/user1', + '{DAV:}displayname' => 'updated', + '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' => 'updated', + '{http://calendarserver.org/ns/}getctag' => 2, + '{http://sabredav.org/ns}sync-token' => 2 + ) + ); + + $this->assertEquals($expected, $result); + + + } + + public function testDeleteAddressBook() { + + $this->backend->deleteAddressBook(1); + + $this->assertEquals(array(), $this->backend->getAddressBooksForUser('principals/user1')); + + } + + /** + * @expectedException Sabre\DAV\Exception\BadRequest + */ + public function testCreateAddressBookUnsupportedProp() { + + $this->backend->createAddressBook('principals/user1','book2', array( + '{DAV:}foo' => 'bar', + )); + + } + + public function testCreateAddressBookSuccess() { + + $this->backend->createAddressBook('principals/user1','book2', array( + '{DAV:}displayname' => 'book2', + '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' => 'addressbook 2', + )); + + $expected = array( + array( + 'id' => 1, + 'uri' => 'book1', + 'principaluri' => 'principals/user1', + '{DAV:}displayname' => 'book1', + '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' => 'addressbook 1', + '{http://calendarserver.org/ns/}getctag' => 1, + '{http://sabredav.org/ns}sync-token' => 1, + ), + array( + 'id' => 2, + 'uri' => 'book2', + 'principaluri' => 'principals/user1', + '{DAV:}displayname' => 'book2', + '{' . CardDAV\Plugin::NS_CARDDAV . '}addressbook-description' => 'addressbook 2', + '{http://calendarserver.org/ns/}getctag' => 1, + '{http://sabredav.org/ns}sync-token' => 1, + ) + ); + $result = $this->backend->getAddressBooksForUser('principals/user1'); + $this->assertEquals($expected, $result); + + } + + public function testGetCards() { + + $result = $this->backend->getCards(1); + + $expected = array( + array( + 'id' => 1, + 'uri' => 'card1', + 'lastmodified' => 0, + 'etag' => '"' . md5('card1') . '"', + 'size' => 5 + ) + ); + + $this->assertEquals($expected, $result); + + } + + public function testGetCard() { + + $result = $this->backend->getCard(1,'card1'); + + $expected = array( + 'id' => 1, + 'uri' => 'card1', + 'carddata' => 'card1', + 'lastmodified' => 0, + 'etag' => '"' . md5('card1') . '"', + 'size' => 5 + ); + + $this->assertEquals($expected, $result); + + } + + /** + * @depends testGetCard + */ + public function testCreateCard() { + + $result = $this->backend->createCard(1, 'card2', 'data2'); + $this->assertEquals('"' . md5('data2') . '"', $result); + $result = $this->backend->getCard(1,'card2'); + $this->assertEquals(2, $result['id']); + $this->assertEquals('card2', $result['uri']); + $this->assertEquals('data2', $result['carddata']); + + } + + /** + * @depends testCreateCard + */ + public function testGetMultiple() { + + $result = $this->backend->createCard(1, 'card2', 'data2'); + $result = $this->backend->createCard(1, 'card3', 'data3'); + $check = [ + [ + 'id' => 1, + 'uri' => 'card1', + 'carddata' => 'card1', + 'lastmodified' => 0, + ], + [ + 'id' => 2, + 'uri' => 'card2', + 'carddata' => 'data2', + 'lastmodified' => time(), + ], + [ + 'id' => 3, + 'uri' => 'card3', + 'carddata' => 'data3', + 'lastmodified' => time(), + ], + ]; + + $result = $this->backend->getMultipleCards(1, ['card1','card2','card3']); + + foreach($check as $index=>$node) { + + foreach($node as $k=>$v) { + + if ($k!=='lastmodified') { + $this->assertEquals($v, $result[$index][$k]); + } else { + $this->assertTrue(isset($result[$index][$k])); + } + + } + + } + + + } + + /** + * @depends testGetCard + */ + public function testUpdateCard() { + + $result = $this->backend->updateCard(1, 'card1', 'newdata'); + $this->assertEquals('"' . md5('newdata') . '"', $result); + + $result = $this->backend->getCard(1,'card1'); + $this->assertEquals(1, $result['id']); + $this->assertEquals('newdata', $result['carddata']); + + } + + /** + * @depends testGetCard + */ + public function testDeleteCard() { + + $this->backend->deleteCard(1, 'card1'); + $result = $this->backend->getCard(1,'card1'); + $this->assertFalse($result); + + } + + function testGetChanges() { + + $backend = $this->backend; + $id = $backend->createAddressBook( + 'principals/user1', + 'bla', + [] + ); + $result = $backend->getChangesForAddressBook($id, null, 1); + + $this->assertEquals([ + 'syncToken' => 1, + "added" => [], + 'modified' => [], + 'deleted' => [], + ], $result); + + $currentToken = $result['syncToken']; + + $dummyCard = "BEGIN:VCARD\r\nEND:VCARD\r\n"; + + $backend->createCard($id, "card1.ics", $dummyCard); + $backend->createCard($id, "card2.ics", $dummyCard); + $backend->createCard($id, "card3.ics", $dummyCard); + $backend->updateCard($id, "card1.ics", $dummyCard); + $backend->deleteCard($id, "card2.ics"); + + $result = $backend->getChangesForAddressBook($id, $currentToken, 1); + + $this->assertEquals([ + 'syncToken' => 6, + 'modified' => ["card1.ics"], + 'deleted' => ["card2.ics"], + "added" => ["card3.ics"], + ], $result); + + } +} + diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/Backend/Mock.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/Backend/Mock.php new file mode 100644 index 0000000..3f96d3c --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/Backend/Mock.php @@ -0,0 +1,148 @@ +addressBooks = $addressBooks; + $this->cards = $cards; + + if (is_null($this->addressBooks)) { + $this->addressBooks = array( + array( + 'id' => 'foo', + 'uri' => 'book1', + 'principaluri' => 'principals/user1', + '{DAV:}displayname' => 'd-name', + ), + ); + + $card2 = fopen('php://memory','r+'); + fwrite($card2,"BEGIN:VCARD\nVERSION:3.0\nUID:45678\nEND:VCARD"); + rewind($card2); + $this->cards = array( + 'foo' => array( + 'card1' => "BEGIN:VCARD\nVERSION:3.0\nUID:12345\nEND:VCARD", + 'card2' => $card2, + ), + ); + } + + } + + + function getAddressBooksForUser($principalUri) { + + $books = array(); + foreach($this->addressBooks as $book) { + if ($book['principaluri'] === $principalUri) { + $books[] = $book; + } + } + return $books; + + } + + /** + * 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 + */ + public function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch) { + + foreach($this->addressBooks as &$book) { + if ($book['id'] !== $addressBookId) + continue; + + $propPatch->handleRemaining(function($mutations) use (&$book) { + foreach($mutations as $key=>$value) { + $book[$key] = $value; + } + return true; + }); + + } + + } + + function createAddressBook($principalUri, $url, array $properties) { + + $this->addressBooks[] = array_merge($properties, array( + 'id' => $url, + 'uri' => $url, + 'principaluri' => $principalUri, + )); + + } + + function deleteAddressBook($addressBookId) { + + foreach($this->addressBooks as $key=>$value) { + if ($value['id'] === $addressBookId) + unset($this->addressBooks[$key]); + } + unset($this->cards[$addressBookId]); + + } + + function getCards($addressBookId) { + + $cards = array(); + foreach($this->cards[$addressBookId] as $uri=>$data) { + $cards[] = array( + 'uri' => $uri, + 'carddata' => $data, + ); + } + return $cards; + + } + + function getCard($addressBookId, $cardUri) { + + if (!isset($this->cards[$addressBookId][$cardUri])) { + return false; + } + + return array( + 'uri' => $cardUri, + 'carddata' => $this->cards[$addressBookId][$cardUri], + ); + + } + + function createCard($addressBookId, $cardUri, $cardData) { + + $this->cards[$addressBookId][$cardUri] = $cardData; + + } + + function updateCard($addressBookId, $cardUri, $cardData) { + + $this->cards[$addressBookId][$cardUri] = $cardData; + + } + + function deleteCard($addressBookId, $cardUri) { + + unset($this->cards[$addressBookId][$cardUri]); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/Backend/PDOMySQLTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/Backend/PDOMySQLTest.php new file mode 100644 index 0000000..38cb655 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/Backend/PDOMySQLTest.php @@ -0,0 +1,36 @@ +markTestSkipped('MySQL driver is not available, or not properly configured'); + + $pdo = \Sabre\TestUtil::getMySQLDB(); + if (!$pdo) $this->markTestSkipped('Could not connect to MySQL database'); + + $pdo->query("DROP TABLE IF EXISTS addressbooks, cards, addressbookchanges"); + + $queries = explode( + ';', + file_get_contents(__DIR__ . '/../../../../examples/sql/mysql.addressbook.sql') + ); + + foreach($queries as $query) { + $query = trim($query," \r\n\t"); + if ($query) + $pdo->exec($query); + } + return $pdo; + + } + +} + diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/Backend/PDOSqliteTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/Backend/PDOSqliteTest.php new file mode 100644 index 0000000..bbcb162 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/Backend/PDOSqliteTest.php @@ -0,0 +1,45 @@ +markTestSkipped('SQLite driver is not available'); + $pdo = new \PDO('sqlite:'.SABRE_TEMPDIR.'/pdobackend'); + $pdo->setAttribute(\PDO::ATTR_ERRMODE,\PDO::ERRMODE_EXCEPTION); + + $pdo->query("DROP TABLE IF EXISTS addressbooks"); + $pdo->query("DROP TABLE IF EXISTS addressbookchanges"); + $pdo->query("DROP TABLE IF EXISTS cards"); + + $queries = explode( + ';', + file_get_contents(__DIR__ . '/../../../../examples/sql/sqlite.addressbooks.sql') + ); + + foreach($queries as $query) { + $query = trim($query," \r\n\t"); + if ($query) + $pdo->exec($query); + } + + return $pdo; + + } + +} + diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/CardTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/CardTest.php new file mode 100644 index 0000000..cf8dbab --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/CardTest.php @@ -0,0 +1,215 @@ +backend = new Backend\Mock(); + $this->card = new Card( + $this->backend, + array( + 'uri' => 'book1', + 'id' => 'foo', + 'principaluri' => 'principals/user1', + ), + array( + 'uri' => 'card1', + 'addressbookid' => 'foo', + 'carddata' => 'card', + ) + ); + + } + + function testGet() { + + $result = $this->card->get(); + $this->assertEquals('card', $result); + + } + function testGet2() { + + $this->card = new Card( + $this->backend, + array( + 'uri' => 'book1', + 'id' => 'foo', + 'principaluri' => 'principals/user1', + ), + array( + 'uri' => 'card1', + 'addressbookid' => 'foo', + ) + ); + $result = $this->card->get(); + $this->assertEquals("BEGIN:VCARD\nVERSION:3.0\nUID:12345\nEND:VCARD", $result); + + } + + + /** + * @depends testGet + */ + function testPut() { + + $file = fopen('php://memory','r+'); + fwrite($file, 'newdata'); + rewind($file); + $this->card->put($file); + $result = $this->card->get(); + $this->assertEquals('newdata', $result); + + } + + + function testDelete() { + + $this->card->delete(); + $this->assertEquals(1, count($this->backend->cards['foo'])); + + } + + function testGetContentType() { + + $this->assertEquals('text/vcard; charset=utf-8', $this->card->getContentType()); + + } + + function testGetETag() { + + $this->assertEquals('"' . md5('card') . '"' , $this->card->getETag()); + + } + + function testGetETag2() { + + $card = new Card( + $this->backend, + array( + 'uri' => 'book1', + 'id' => 'foo', + 'principaluri' => 'principals/user1', + ), + array( + 'uri' => 'card1', + 'addressbookid' => 'foo', + 'carddata' => 'card', + 'etag' => '"blabla"', + ) + ); + $this->assertEquals('"blabla"' , $card->getETag()); + + } + + function testGetLastModified() { + + $this->assertEquals(null, $this->card->getLastModified()); + + } + + function testGetSize() { + + $this->assertEquals(4, $this->card->getSize()); + $this->assertEquals(4, $this->card->getSize()); + + } + + function testGetSize2() { + + $card = new Card( + $this->backend, + array( + 'uri' => 'book1', + 'id' => 'foo', + 'principaluri' => 'principals/user1', + ), + array( + 'uri' => 'card1', + 'addressbookid' => 'foo', + 'etag' => '"blabla"', + 'size' => 4, + ) + ); + $this->assertEquals(4, $card->getSize()); + + } + + function testACLMethods() { + + $this->assertEquals('principals/user1', $this->card->getOwner()); + $this->assertNull($this->card->getGroup()); + $this->assertEquals(array( + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1', + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}write', + 'principal' => 'principals/user1', + 'protected' => true, + ), + ), $this->card->getACL()); + + } + function testOverrideACL() { + + $card = new Card( + $this->backend, + array( + 'uri' => 'book1', + 'id' => 'foo', + 'principaluri' => 'principals/user1', + ), + array( + 'uri' => 'card1', + 'addressbookid' => 'foo', + 'carddata' => 'card', + 'acl' => array( + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1', + 'protected' => true, + ), + ), + ) + ); + $this->assertEquals(array( + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/user1', + 'protected' => true, + ), + ), $card->getACL()); + + } + + /** + * @expectedException Sabre\DAV\Exception\MethodNotAllowed + */ + function testSetACL() { + + $this->card->setACL(array()); + + } + + function testGetSupportedPrivilegeSet() { + + $this->assertNull( + $this->card->getSupportedPrivilegeSet() + ); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/IDirectoryTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/IDirectoryTest.php new file mode 100644 index 0000000..431cd25 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/IDirectoryTest.php @@ -0,0 +1,30 @@ +addPlugin($plugin); + + $props = $server->getProperties('directory', array('{DAV:}resourcetype')); + $this->assertTrue($props['{DAV:}resourcetype']->is('{' . Plugin::NS_CARDDAV . '}directory')); + + } + +} + +class DirectoryMock extends DAV\SimpleCollection implements IDirectory { + + + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/MultiGetTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/MultiGetTest.php new file mode 100644 index 0000000..b0ee458 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/MultiGetTest.php @@ -0,0 +1,99 @@ + 'REPORT', + 'REQUEST_URI' => '/addressbooks/user1/book1', + )); + + $request->setBody( +' + + + + + + /addressbooks/user1/book1/card1 +' + ); + + $response = new HTTP\ResponseMock(); + + $this->server->httpRequest = $request; + $this->server->httpResponse = $response; + + $this->server->exec(); + + $this->assertEquals(207, $response->status, 'Incorrect status code. Full response body:' . $response->body); + + // using the client for parsing + $client = new DAV\Client(array('baseUri'=>'/')); + + $result = $client->parseMultiStatus($response->body); + + $this->assertEquals(array( + '/addressbooks/user1/book1/card1' => array( + 200 => array( + '{DAV:}getetag' => '"' . md5("BEGIN:VCARD\nVERSION:3.0\nUID:12345\nEND:VCARD") . '"', + '{urn:ietf:params:xml:ns:carddav}address-data' => "BEGIN:VCARD\r\nVERSION:3.0\r\nUID:12345\r\nEND:VCARD\r\n", + ) + ) + ), $result); + + } + + function testMultiGetVCard4() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/addressbooks/user1/book1', + )); + + $request->setBody( +' + + + + + + /addressbooks/user1/book1/card1 +' + ); + + $response = new HTTP\ResponseMock(); + + $this->server->httpRequest = $request; + $this->server->httpResponse = $response; + + $this->server->exec(); + + $this->assertEquals(207, $response->status, 'Incorrect status code. Full response body:' . $response->body); + + // using the client for parsing + $client = new DAV\Client(array('baseUri'=>'/')); + + $result = $client->parseMultiStatus($response->body); + + $prodId = "PRODID:-//Sabre//Sabre VObject " . \Sabre\VObject\Version::VERSION . "//EN"; + + $this->assertEquals(array( + '/addressbooks/user1/book1/card1' => array( + 200 => array( + '{DAV:}getetag' => '"' . md5("BEGIN:VCARD\nVERSION:3.0\nUID:12345\nEND:VCARD") . '"', + '{urn:ietf:params:xml:ns:carddav}address-data' => "BEGIN:VCARD\r\nVERSION:4.0\r\n$prodId\r\nUID:12345\r\nEND:VCARD\r\n", + ) + ) + ), $result); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/PluginTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/PluginTest.php new file mode 100644 index 0000000..b8fc71d --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/PluginTest.php @@ -0,0 +1,103 @@ +assertEquals('{' . Plugin::NS_CARDDAV . '}addressbook', $this->server->resourceTypeMapping['Sabre\\CardDAV\\IAddressBook']); + + $this->assertTrue(in_array('addressbook', $this->plugin->getFeatures())); + $this->assertEquals('carddav', $this->plugin->getPluginInfo()['name']); + + } + + function testSupportedReportSet() { + + $this->assertEquals(array( + '{' . Plugin::NS_CARDDAV . '}addressbook-multiget', + '{' . Plugin::NS_CARDDAV . '}addressbook-query', + ), $this->plugin->getSupportedReportSet('addressbooks/user1/book1')); + + } + + function testSupportedReportSetEmpty() { + + $this->assertEquals(array( + ), $this->plugin->getSupportedReportSet('')); + + } + + function testAddressBookHomeSet() { + + $result = $this->server->getProperties('principals/user1', array('{' . Plugin::NS_CARDDAV . '}addressbook-home-set')); + + $this->assertEquals(1, count($result)); + $this->assertTrue(isset($result['{' . Plugin::NS_CARDDAV . '}addressbook-home-set'])); + $this->assertEquals('addressbooks/user1/', $result['{' . Plugin::NS_CARDDAV . '}addressbook-home-set']->getHref()); + + } + + function testDirectoryGateway() { + + $result = $this->server->getProperties('principals/user1', array('{' . Plugin::NS_CARDDAV . '}directory-gateway')); + + $this->assertEquals(1, count($result)); + $this->assertTrue(isset($result['{' . Plugin::NS_CARDDAV . '}directory-gateway'])); + $this->assertEquals(array('directory'), $result['{' . Plugin::NS_CARDDAV . '}directory-gateway']->getHrefs()); + + } + + function testReportPassThrough() { + + $this->assertNull($this->plugin->report('{DAV:}foo', new \DomDocument())); + + } + + function testHTMLActionsPanel() { + + $output = ''; + $r = $this->server->emit('onHTMLActionsPanel', [$this->server->tree->getNodeForPath('addressbooks/user1'), &$output]); + $this->assertFalse($r); + + $this->assertTrue(!!strpos($output,'Display name')); + + } + + function testAddressbookPluginProperties() { + + $ns = '{' . Plugin::NS_CARDDAV . '}'; + $propFind = new DAV\PropFind('addressbooks/user1/book1', [ + $ns . 'supported-address-data', + $ns . 'supported-collation-set', + ]); + $node = $this->server->tree->getNodeForPath('addressbooks/user1/book1'); + $this->plugin->propFindEarly($propFind, $node); + + $this->assertInstanceOf( + 'Sabre\\CardDAV\\Xml\\Property\\SupportedAddressData', + $propFind->get($ns . 'supported-address-data') + ); + $this->assertInstanceOf( + 'Sabre\\CardDAV\\Xml\\Property\\SupportedCollationSet', + $propFind->get($ns . 'supported-collation-set') + ); + + + } + + function testGetTransform() { + + $request = new \Sabre\HTTP\Request('GET', '/addressbooks/user1/book1/card1', ['Accept: application/vcard+json']); + $response = new \Sabre\HTTP\ResponseMock(); + $this->server->invokeMethod($request, $response); + + $this->assertEquals(200, $response->getStatus()); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/SogoStripContentTypeTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/SogoStripContentTypeTest.php new file mode 100644 index 0000000..f828cc2 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/SogoStripContentTypeTest.php @@ -0,0 +1,56 @@ + 1, + 'uri' => 'book1', + 'principaluri' => 'principals/user1', + ), + ); + protected $carddavCards = array( + 1 => array( + 'card1.vcf' => "BEGIN:VCARD\nVERSION:3.0\nUID:12345\nEND:VCARD", + ), + ); + + function testDontStrip() { + + $result = $this->server->getProperties('addressbooks/user1/book1/card1.vcf',array('{DAV:}getcontenttype')); + $this->assertEquals(array( + '{DAV:}getcontenttype' => 'text/vcard; charset=utf-8' + ), $result); + + } + function testStrip() { + + $this->server->httpRequest = HTTP\Sapi::createFromServerArray(array( + 'HTTP_USER_AGENT' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:10.0.2) Gecko/20120216 Thunderbird/10.0.2 Lightning/1.2.1', + )); + $result = $this->server->getProperties('addressbooks/user1/book1/card1.vcf',array('{DAV:}getcontenttype')); + $this->assertEquals(array( + '{DAV:}getcontenttype' => 'text/x-vcard' + ), $result); + + } + function testDontTouchOtherMimeTypes() { + + $this->server->httpRequest = new HTTP\Request('GET','/addressbooks/user1/book1/card1.vcf', [ + 'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:10.0.2) Gecko/20120216 Thunderbird/10.0.2 Lightning/1.2.1', + ]); + + $propFind = new PropFind('hello', ['{DAV:}getcontenttype']); + $propFind->set('{DAV:}getcontenttype', 'text/plain'); + $this->carddavPlugin->propFindLate($propFind, new \Sabre\DAV\SimpleCollection('foo')); + $this->assertEquals('text/plain', $propFind->get('{DAV:}getcontenttype')); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/TestUtil.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/TestUtil.php new file mode 100644 index 0000000..9f84566 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/TestUtil.php @@ -0,0 +1,68 @@ +setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION); + + // Yup this is definitely not 'fool proof', but good enough for now. + $queries = explode(';', file_get_contents(__DIR__ . '/../../../examples/sql/sqlite.addressbooks.sql')); + foreach($queries as $query) { + $pdo->exec($query); + } + // Inserting events through a backend class. + $backend = new Backend\PDO($pdo); + $addressbookId = $backend->createAddressBook( + 'principals/user1', + 'UUID-123467', + array( + '{DAV:}displayname' => 'user1 addressbook', + '{urn:ietf:params:xml:ns:carddav}addressbook-description' => 'AddressBook description', + ) + ); + $backend->createAddressBook( + 'principals/user1', + 'UUID-123468', + array( + '{DAV:}displayname' => 'user1 addressbook2', + '{urn:ietf:params:xml:ns:carddav}addressbook-description' => 'AddressBook description', + ) + ); + $backend->createCard($addressbookId, 'UUID-2345', self::getTestCardData()); + return $pdo; + + } + + static function getTestCardData($type = 1) { + + $addressbookData = 'BEGIN:VCARD +VERSION:3.0 +PRODID:-//Acme Inc.//RoadRunner 1.0//EN +FN:Wile E. Coyote +N:Coyote;Wile;Erroll;; +ORG:Acme Inc. +UID:39A6B5ED-DD51-4AFE-A683-C35EE3749627 +REV:2012-06-20T07:00:39+00:00 +END:VCARD'; + + return $addressbookData; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/VCFExportTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/VCFExportTest.php new file mode 100644 index 0000000..54c3a84 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/VCFExportTest.php @@ -0,0 +1,75 @@ + 'book1', + 'uri' => 'book1', + 'principaluri' => 'principals/user1', + ) + ); + protected $carddavCards = array( + 'book1' => array( + "card1" => "BEGIN:VCARD\r\nFN:Person1\r\nEND:VCARD\r\n", + "card2" => "BEGIN:VCARD\r\nFN:Person2\r\nEND:VCARD", + "card3" => "BEGIN:VCARD\r\nFN:Person3\r\nEND:VCARD\r\n", + "card4" => "BEGIN:VCARD\nFN:Person4\nEND:VCARD\n", + ) + ); + + function setUp() { + + parent::setUp(); + $this->server->addPlugin( + new VCFExportPlugin() + ); + + } + + function testSimple() { + + $this->assertInstanceOf('Sabre\\CardDAV\\VCFExportPlugin', $this->server->getPlugin('vcf-export')); + + } + + function testExport() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_URI' => '/addressbooks/user1/book1?export', + 'QUERY_STRING' => 'export', + 'REQUEST_METHOD' => 'GET', + )); + + $response = $this->request($request); + $this->assertEquals(200, $response->status, $response->body); + + $expected = "BEGIN:VCARD +FN:Person1 +END:VCARD +BEGIN:VCARD +FN:Person2 +END:VCARD +BEGIN:VCARD +FN:Person3 +END:VCARD +BEGIN:VCARD +FN:Person4 +END:VCARD +"; + // We actually expected windows line endings + $expected = str_replace("\n","\r\n", $expected); + + $this->assertEquals($expected, $response->body); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/ValidateFilterTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/ValidateFilterTest.php new file mode 100644 index 0000000..c87716c --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/ValidateFilterTest.php @@ -0,0 +1,204 @@ +assertTrue($this->plugin->validateFilters($input, $filters, $test), $message); + } else { + $this->assertFalse($this->plugin->validateFilters($input, $filters, $test), $message); + } + + } + + function data() { + + $body1 = << 'title', 'is-not-defined' => false, 'param-filters' => array(), 'text-matches' => array()); + + // Check if FOO is defined + $filter2 = + array('name' => 'foo', 'is-not-defined' => false, 'param-filters' => array(), 'text-matches' => array()); + + // Check if TITLE is not defined + $filter3 = + array('name' => 'title', 'is-not-defined' => true, 'param-filters' => array(), 'text-matches' => array()); + + // Check if FOO is not defined + $filter4 = + array('name' => 'foo', 'is-not-defined' => true, 'param-filters' => array(), 'text-matches' => array()); + + // Check if TEL[TYPE] is defined + $filter5 = + array( + 'name' => 'tel', + 'is-not-defined' => false, + 'test' => 'anyof', + 'param-filters' => array( + array( + 'name' => 'type', + 'is-not-defined' => false, + 'text-match' => null + ), + ), + 'text-matches' => array(), + ); + + // Check if TEL[FOO] is defined + $filter6 = $filter5; + $filter6['param-filters'][0]['name'] = 'FOO'; + + // Check if TEL[TYPE] is not defined + $filter7 = $filter5; + $filter7['param-filters'][0]['is-not-defined'] = true; + + // Check if TEL[FOO] is not defined + $filter8 = $filter5; + $filter8['param-filters'][0]['name'] = 'FOO'; + $filter8['param-filters'][0]['is-not-defined'] = true; + + // Combining property filters + $filter9 = $filter5; + $filter9['param-filters'][] = $filter6['param-filters'][0]; + + $filter10 = $filter5; + $filter10['param-filters'][] = $filter6['param-filters'][0]; + $filter10['test'] = 'allof'; + + // Check if URL contains 'google' + $filter11 = + array( + 'name' => 'url', + 'is-not-defined' => false, + 'test' => 'anyof', + 'param-filters' => array(), + 'text-matches' => array( + array( + 'match-type' => 'contains', + 'value' => 'google', + 'negate-condition' => false, + 'collation' => 'i;octet', + ), + ), + ); + + // Check if URL contains 'bing' + $filter12 = $filter11; + $filter12['text-matches'][0]['value'] = 'bing'; + + // Check if URL does not contain 'google' + $filter13 = $filter11; + $filter13['text-matches'][0]['negate-condition'] = true; + + // Check if URL does not contain 'bing' + $filter14 = $filter11; + $filter14['text-matches'][0]['value'] = 'bing'; + $filter14['text-matches'][0]['negate-condition'] = true; + + // Param filter with text + $filter15 = $filter5; + $filter15['param-filters'][0]['text-match'] = array( + 'match-type' => 'contains', + 'value' => 'WORK', + 'collation' => 'i;octet', + 'negate-condition' => false, + ); + $filter16 = $filter15; + $filter16['param-filters'][0]['text-match']['negate-condition'] = true; + + + // Param filter + text filter + $filter17 = $filter5; + $filter17['test'] = 'anyof'; + $filter17['text-matches'][] = array( + 'match-type' => 'contains', + 'value' => '444', + 'collation' => 'i;octet', + 'negate-condition' => false, + ); + + $filter18 = $filter17; + $filter18['text-matches'][0]['negate-condition'] = true; + + $filter18['test'] = 'allof'; + + return array( + + // Basic filters + array($body1, array($filter1), 'anyof',true), + array($body1, array($filter2), 'anyof',false), + array($body1, array($filter3), 'anyof',false), + array($body1, array($filter4), 'anyof',true), + + // Combinations + array($body1, array($filter1, $filter2), 'anyof',true), + array($body1, array($filter1, $filter2), 'allof',false), + array($body1, array($filter1, $filter4), 'anyof',true), + array($body1, array($filter1, $filter4), 'allof',true), + array($body1, array($filter2, $filter3), 'anyof',false), + array($body1, array($filter2, $filter3), 'allof',false), + + // Basic parameters + array($body1, array($filter5), 'anyof', true, 'TEL;TYPE is defined, so this should return true'), + array($body1, array($filter6), 'anyof', false, 'TEL;FOO is not defined, so this should return false'), + + array($body1, array($filter7), 'anyof', false, 'TEL;TYPE is defined, so this should return false'), + array($body1, array($filter8), 'anyof', true, 'TEL;TYPE is not defined, so this should return true'), + + // Combined parameters + array($body1, array($filter9), 'anyof', true), + array($body1, array($filter10), 'anyof', false), + + // Text-filters + array($body1, array($filter11), 'anyof', true), + array($body1, array($filter12), 'anyof', false), + array($body1, array($filter13), 'anyof', false), + array($body1, array($filter14), 'anyof', true), + + // Param filter with text-match + array($body1, array($filter15), 'anyof', true), + array($body1, array($filter16), 'anyof', false), + + // Param filter + text filter + array($body1, array($filter17), 'anyof', true), + array($body1, array($filter18), 'anyof', false), + array($body1, array($filter18), 'anyof', false), + ); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/ValidateVCardTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/ValidateVCardTest.php new file mode 100644 index 0000000..ad8495c --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/ValidateVCardTest.php @@ -0,0 +1,172 @@ + 'addressbook1', + 'principaluri' => 'principals/admin', + 'uri' => 'addressbook1', + ) + ); + + $this->cardBackend = new Backend\Mock($addressbooks,array()); + $principalBackend = new DAVACL\PrincipalBackend\Mock(); + + $tree = array( + new AddressBookRoot($principalBackend, $this->cardBackend), + ); + + $this->server = new DAV\Server($tree); + $this->server->sapi = new HTTP\SapiMock(); + $this->server->debugExceptions = true; + + $plugin = new Plugin(); + $this->server->addPlugin($plugin); + + $response = new HTTP\ResponseMock(); + $this->server->httpResponse = $response; + + } + + function request(HTTP\Request $request) { + + $this->server->httpRequest = $request; + $this->server->exec(); + + return $this->server->httpResponse; + + } + + function testCreateFile() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/addressbooks/admin/addressbook1/blabla.vcf', + )); + + $response = $this->request($request); + + $this->assertEquals(415, $response->status); + + } + + function testCreateFileValid() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/addressbooks/admin/addressbook1/blabla.vcf', + )); + $request->setBody("BEGIN:VCARD\r\nUID:foo\r\nEND:VCARD\r\n"); + + $response = $this->request($request); + + $this->assertEquals(201, $response->status, 'Incorrect status returned! Full response body: ' . $response->body); + $expected = array( + 'uri' => 'blabla.vcf', + 'carddata' => "BEGIN:VCARD\r\nUID:foo\r\nEND:VCARD\r\n", + ); + + $this->assertEquals($expected, $this->cardBackend->getCard('addressbook1','blabla.vcf')); + + } + + function testCreateFileNoUID() { + + $request = new HTTP\Request( + 'PUT', + '/addressbooks/admin/addressbook1/blabla.vcf' + ); + $request->setBody("BEGIN:VCARD\r\nEND:VCARD\r\n"); + + $response = $this->request($request); + + $this->assertEquals(201, $response->status, 'Incorrect status returned! Full response body: ' . $response->body); + + $foo = $this->cardBackend->getCard('addressbook1','blabla.vcf'); + $this->assertTrue(strpos($foo['carddata'],'UID')!==false); + } + + function testCreateFileJson() { + + $request = new HTTP\Request( + 'PUT', + '/addressbooks/admin/addressbook1/blabla.vcf' + ); + $request->setBody('[ "vcard" , [ [ "UID" , {}, "text", "foo" ] ] ]'); + + $response = $this->request($request); + + $this->assertEquals(201, $response->status, 'Incorrect status returned! Full response body: ' . $response->body); + + $foo = $this->cardBackend->getCard('addressbook1','blabla.vcf'); + $this->assertEquals("BEGIN:VCARD\r\nUID:foo\r\nEND:VCARD\r\n", $foo['carddata']); + + } + + function testCreateFileVCalendar() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/addressbooks/admin/addressbook1/blabla.vcf', + )); + $request->setBody("BEGIN:VCALENDAR\r\nEND:VCALENDAR\r\n"); + + $response = $this->request($request); + + $this->assertEquals(415, $response->status, 'Incorrect status returned! Full response body: ' . $response->body); + + } + + function testUpdateFile() { + + $this->cardBackend->createCard('addressbook1','blabla.vcf','foo'); + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/addressbooks/admin/addressbook1/blabla.vcf', + )); + + $response = $this->request($request); + + $this->assertEquals(415, $response->status); + + } + + function testUpdateFileParsableBody() { + + $this->cardBackend->createCard('addressbook1','blabla.vcf','foo'); + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/addressbooks/admin/addressbook1/blabla.vcf', + )); + $body = "BEGIN:VCARD\r\nUID:foo\r\nEND:VCARD\r\n"; + $request->setBody($body); + + $response = $this->request($request); + + $this->assertEquals(204, $response->status); + + $expected = array( + 'uri' => 'blabla.vcf', + 'carddata' => $body, + ); + + $this->assertEquals($expected, $this->cardBackend->getCard('addressbook1','blabla.vcf')); + + } +} + +?> diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/Xml/Property/SupportedAddressDataTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/Xml/Property/SupportedAddressDataTest.php new file mode 100644 index 0000000..02fbf4c --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/Xml/Property/SupportedAddressDataTest.php @@ -0,0 +1,38 @@ +assertInstanceOf('Sabre\CardDAV\Xml\Property\SupportedAddressData', $property); + + } + + /** + * @depends testSimple + */ + function testSerialize() { + + $property = new SupportedAddressData(); + + $this->namespaceMap[CardDAV\Plugin::NS_CARDDAV] = 'card'; + $xml = $this->write(['{DAV:}root' => $property]); + + $this->assertXmlStringEqualsXmlString( +' +' . +'' . +'' . +'' . +' +', $xml); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/Xml/Property/SupportedCollationSetTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/Xml/Property/SupportedCollationSetTest.php new file mode 100644 index 0000000..e06aff1 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/Xml/Property/SupportedCollationSetTest.php @@ -0,0 +1,38 @@ +assertInstanceOf('Sabre\CardDAV\Xml\Property\SupportedCollationSet', $property); + + } + + /** + * @depends testSimple + */ + function testSerialize() { + + $property = new SupportedCollationSet(); + + $this->namespaceMap[CardDAV\Plugin::NS_CARDDAV] = 'card'; + $xml = $this->write(['{DAV:}root' => $property]); + + $this->assertXmlStringEqualsXmlString( +' +' . +'i;ascii-casemap' . +'i;octet' . +'i;unicode-casemap' . +' +', $xml); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/Xml/Request/AddressBookQueryReportTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/Xml/Request/AddressBookQueryReportTest.php new file mode 100644 index 0000000..b110e20 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/CardDAV/Xml/Request/AddressBookQueryReportTest.php @@ -0,0 +1,302 @@ + 'Sabre\\CardDAV\\Xml\\Request\AddressBookQueryReport', + ]; + + function testDeserialize() { + + $xml = << + + + + + + + + +XML; + + $result = $this->parse($xml); + $addressBookQueryReport = new AddressBookQueryReport(); + $addressBookQueryReport->properties = [ + '{DAV:}getetag', + ]; + $addressBookQueryReport->test = 'anyof'; + $addressBookQueryReport->filters = [ + [ + 'name' => 'uid', + 'test' => 'anyof', + 'is-not-defined' => false, + 'param-filters' => [], + 'text-matches' => [], + ] + ]; + + $this->assertEquals( + $addressBookQueryReport, + $result['value'] + ); + + } + + function testDeserializeAllOf() { + + $xml = << + + + + + + + + +XML; + + $result = $this->parse($xml); + $addressBookQueryReport = new AddressBookQueryReport(); + $addressBookQueryReport->properties = [ + '{DAV:}getetag', + ]; + $addressBookQueryReport->test = 'allof'; + $addressBookQueryReport->filters = [ + [ + 'name' => 'uid', + 'test' => 'anyof', + 'is-not-defined' => false, + 'param-filters' => [], + 'text-matches' => [], + ] + ]; + + $this->assertEquals( + $addressBookQueryReport, + $result['value'] + ); + + } + + /** + * @expectedException \Sabre\DAV\Exception\BadRequest + */ + function testDeserializeBadTest() { + + $xml = << + + + + + + + + +XML; + + $this->parse($xml); + + } + + /** + * We should error on this, but KDE does this, so we chose to support it. + */ + function testDeserializeNoFilter() { + + $xml = << + + + + + +XML; + + $result = $this->parse($xml); + $addressBookQueryReport = new AddressBookQueryReport(); + $addressBookQueryReport->properties = [ + '{DAV:}getetag', + ]; + $addressBookQueryReport->test = 'anyof'; + $addressBookQueryReport->filters = []; + + $this->assertEquals( + $addressBookQueryReport, + $result['value'] + ); + + } + + function testDeserializeComplex() { + + $xml = << + + + + + + + + + + + + + + + + Hello! + + + + No + + + 10 + +XML; + + $result = $this->parse($xml); + $addressBookQueryReport = new AddressBookQueryReport(); + $addressBookQueryReport->properties = [ + '{DAV:}getetag', + '{urn:ietf:params:xml:ns:carddav}address-data', + ]; + $addressBookQueryReport->test = 'anyof'; + $addressBookQueryReport->filters = [ + [ + 'name' => 'uid', + 'test' => 'anyof', + 'is-not-defined' => true, + 'param-filters' => [], + 'text-matches' => [], + ], + [ + 'name' => 'x-foo', + 'test' => 'allof', + 'is-not-defined' => false, + 'param-filters' => [ + [ + 'name' => 'x-param1', + 'is-not-defined' => false, + 'text-match' => null, + ], + [ + 'name' => 'x-param2', + 'is-not-defined' => true, + 'text-match' => null, + ], + [ + 'name' => 'x-param3', + 'is-not-defined' => false, + 'text-match' => [ + 'negate-condition' => false, + 'value' => 'Hello!', + 'match-type' => 'contains', + 'collation' => 'i;unicode-casemap', + ], + ], + ], + 'text-matches' => [], + ], + [ + 'name' => 'x-prop2', + 'test' => 'anyof', + 'is-not-defined' => false, + 'param-filters' => [], + 'text-matches' => [ + [ + 'negate-condition' => true, + 'value' => 'No', + 'match-type' => 'starts-with', + 'collation' => 'i;unicode-casemap', + ], + ], + ] + ]; + + $addressBookQueryReport->version = '4.0'; + $addressBookQueryReport->contentType = 'application/vcard+json'; + $addressBookQueryReport->limit = 10; + + $this->assertEquals( + $addressBookQueryReport, + $result['value'] + ); + + } + + /** + * @expectedException \Sabre\DAV\Exception\BadRequest + */ + function testDeserializeBadMatchType() { + + $xml = << + + + + + + + + Hello! + + + + +XML; + $this->parse($xml); + + } + + /** + * @expectedException \Sabre\DAV\Exception\BadRequest + */ + function testDeserializeBadMatchType2() { + + $xml = << + + + + + + + No + + + +XML; + $this->parse($xml); + + } + + /** + * @expectedException \Sabre\DAV\Exception\BadRequest + */ + function testDeserializeDoubleFilter() { + + $xml = << + + + + + + + + + +XML; + $this->parse($xml); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/AbstractServer.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/AbstractServer.php new file mode 100644 index 0000000..b5b8d64 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/AbstractServer.php @@ -0,0 +1,64 @@ +response = new HTTP\ResponseMock(); + $this->server = new Server($this->getRootNode()); + $this->server->sapi = new HTTP\SapiMock(); + $this->server->httpResponse = $this->response; + $this->server->debugExceptions = true; + $this->deleteTree(SABRE_TEMPDIR,false); + file_put_contents(SABRE_TEMPDIR . '/test.txt', 'Test contents'); + mkdir(SABRE_TEMPDIR . '/dir'); + file_put_contents(SABRE_TEMPDIR . '/dir/child.txt', 'Child contents'); + + + } + + function tearDown() { + + $this->deleteTree(SABRE_TEMPDIR,false); + + } + + protected function getRootNode() { + + return new FS\Directory(SABRE_TEMPDIR); + + } + + private function deleteTree($path,$deleteRoot = true) { + + foreach(scandir($path) as $node) { + + if ($node=='.' || $node=='.svn' || $node=='..') continue; + $myPath = $path.'/'. $node; + if (is_file($myPath)) { + unlink($myPath); + } else { + $this->deleteTree($myPath); + } + + } + if ($deleteRoot) rmdir($path); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/AbstractBasicTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/AbstractBasicTest.php new file mode 100644 index 0000000..e92aa4b --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/AbstractBasicTest.php @@ -0,0 +1,94 @@ +assertFalse( + $backend->check($request, $response)[0] + ); + + } + + function testCheckUnknownUser() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'PHP_AUTH_USER' => 'username', + 'PHP_AUTH_PW' => 'wrongpassword', + )); + $response = new HTTP\Response(); + + $backend = new AbstractBasicMock(); + + $this->assertFalse( + $backend->check($request, $response)[0] + ); + + } + + function testCheckSuccess() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'PHP_AUTH_USER' => 'username', + 'PHP_AUTH_PW' => 'password', + )); + $response = new HTTP\Response(); + + $backend = new AbstractBasicMock(); + $this->assertEquals( + [true, 'principals/username'], + $backend->check($request, $response) + ); + + } + + function testRequireAuth() { + + $request = new HTTP\Request(); + $response = new HTTP\Response(); + + $backend = new AbstractBasicMock(); + $backend->setRealm('writing unittests on a saturday night'); + $backend->challenge($request, $response); + + $this->assertEquals( + 'Basic realm="writing unittests on a saturday night"', + $response->getHeader('WWW-Authenticate') + ); + + } + +} + + +class AbstractBasicMock extends AbstractBasic { + + /** + * 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 + */ + function validateUserPass($username, $password) { + + return ($username == 'username' && $password == 'password'); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/AbstractDigestTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/AbstractDigestTest.php new file mode 100644 index 0000000..247befa --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/AbstractDigestTest.php @@ -0,0 +1,144 @@ +assertFalse( + $backend->check($request, $response)[0] + ); + + } + + function testCheckBadGetUserInfoResponse() { + + $header = 'username=null, realm=myRealm, nonce=12345, uri=/, response=HASH, opaque=1, qop=auth, nc=1, cnonce=1'; + $request = HTTP\Sapi::createFromServerArray([ + 'PHP_AUTH_DIGEST' => $header, + ]); + $response = new HTTP\Response(); + + $backend = new AbstractDigestMock(); + $this->assertFalse( + $backend->check($request, $response)[0] + ); + + } + + /** + * @expectedException Sabre\DAV\Exception + */ + function testCheckBadGetUserInfoResponse2() { + + $header = 'username=array, realm=myRealm, nonce=12345, uri=/, response=HASH, opaque=1, qop=auth, nc=1, cnonce=1'; + $request = HTTP\Sapi::createFromServerArray([ + 'PHP_AUTH_DIGEST' => $header, + ]); + + $response = new HTTP\Response(); + + $backend = new AbstractDigestMock(); + $this->assertNull( + $backend->check($request, $response) + ); + + $backend = new AbstractDigestMock(); + $backend->check($request, $response); + + } + + function testCheckUnknownUser() { + + $header = 'username=false, realm=myRealm, nonce=12345, uri=/, response=HASH, opaque=1, qop=auth, nc=1, cnonce=1'; + $request = HTTP\Sapi::createFromServerArray([ + 'PHP_AUTH_DIGEST' => $header, + ]); + + $response = new HTTP\Response(); + + $backend = new AbstractDigestMock(); + $this->assertFalse( + $backend->check($request, $response)[0] + ); + + } + + function testCheckBadPassword() { + + $header = 'username=user, realm=myRealm, nonce=12345, uri=/, response=HASH, opaque=1, qop=auth, nc=1, cnonce=1'; + $request = HTTP\Sapi::createFromServerArray([ + 'PHP_AUTH_DIGEST' => $header, + 'REQUEST_METHOD' => 'PUT', + ]); + + $response = new HTTP\Response(); + + $backend = new AbstractDigestMock(); + $this->assertFalse( + $backend->check($request, $response)[0] + ); + + } + + function testCheck() { + + $digestHash = md5('HELLO:12345:1:1:auth:' . md5('GET:/')); + $header = 'username=user, realm=myRealm, nonce=12345, uri=/, response='.$digestHash.', opaque=1, qop=auth, nc=1, cnonce=1'; + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'GET', + 'PHP_AUTH_DIGEST' => $header, + 'REQUEST_URI' => '/', + )); + + $response = new HTTP\Response(); + + $backend = new AbstractDigestMock(); + $this->assertEquals( + [true, 'principals/user'], + $backend->check($request, $response) + ); + + } + + function testRequireAuth() { + + $request = new HTTP\Request(); + $response = new HTTP\Response(); + + $backend = new AbstractDigestMock(); + $backend->setRealm('writing unittests on a saturday night'); + $backend->challenge($request, $response); + + $this->assertStringStartsWith( + 'Digest realm="writing unittests on a saturday night"', + $response->getHeader('WWW-Authenticate') + ); + + } + +} + + +class AbstractDigestMock extends AbstractDigest { + + function getDigestHash($realm, $userName) { + + switch($userName) { + case 'null' : return null; + case 'false' : return false; + case 'array' : return array(); + case 'user' : return 'HELLO'; + } + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/AbstractPDOTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/AbstractPDOTest.php new file mode 100644 index 0000000..d22923d --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/AbstractPDOTest.php @@ -0,0 +1,35 @@ +getPDO(); + $backend = new PDO($pdo); + $this->assertTrue($backend instanceof PDO); + + } + + /** + * @depends testConstruct + */ + function testUserInfo() { + + $pdo = $this->getPDO(); + $backend = new PDO($pdo); + + $this->assertNull($backend->getDigestHash('realm','blabla')); + + $expected = 'hash'; + + $this->assertEquals($expected, $backend->getDigestHash('realm','user')); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/ApacheTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/ApacheTest.php new file mode 100644 index 0000000..697b593 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/ApacheTest.php @@ -0,0 +1,72 @@ +assertInstanceOf('Sabre\DAV\Auth\Backend\Apache', $backend); + + } + + function testNoHeader() { + + $request = new HTTP\Request(); + $response = new HTTP\Response(); + $backend = new Apache(); + + $this->assertFalse( + $backend->check($request, $response)[0] + ); + + } + + function testRemoteUser() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REMOTE_USER' => 'username', + ]); + $response = new HTTP\Response(); + $backend = new Apache(); + + $this->assertEquals( + [true, 'principals/username'], + $backend->check($request, $response) + ); + + } + + function testRedirectRemoteUser() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REDIRECT_REMOTE_USER' => 'username', + ]); + $response = new HTTP\Response(); + $backend = new Apache(); + + $this->assertEquals( + [true, 'principals/username'], + $backend->check($request, $response) + ); + + } + + function testRequireAuth() { + + $request = new HTTP\Request(); + $response = new HTTP\Response(); + + $backend = new Apache(); + $backend->challenge($request, $response); + + $this->assertNull( + $response->getHeader('WWW-Authenticate') + ); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/BasicCallBackTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/BasicCallBackTest.php new file mode 100644 index 0000000..5275a52 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/BasicCallBackTest.php @@ -0,0 +1,38 @@ + 'Basic ' . base64_encode('foo:bar'), + ]); + $response = new Response(); + + $this->assertEquals( + [true, 'principals/foo'], + $backend->check($request, $response) + ); + + $this->assertEquals(['foo','bar'], $args); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/FileTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/FileTest.php new file mode 100644 index 0000000..72f150a --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/FileTest.php @@ -0,0 +1,42 @@ +assertTrue($file instanceof File); + + } + + /** + * @expectedException Sabre\DAV\Exception + */ + function testLoadFileBroken() { + + file_put_contents(SABRE_TEMPDIR . '/backend','user:realm:hash'); + $file = new File(); + $file->loadFile(SABRE_TEMPDIR .'/backend'); + + } + + function testLoadFile() { + + file_put_contents(SABRE_TEMPDIR . '/backend','user:realm:' . md5('user:realm:password')); + $file = new File(); + $file->loadFile(SABRE_TEMPDIR . '/backend'); + + $this->assertFalse($file->getDigestHash('realm','blabla')); + $this->assertEquals(md5('user:realm:password'), $file->getDigesthash('realm','user')); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/Mock.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/Mock.php new file mode 100644 index 0000000..a782cb7 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/Mock.php @@ -0,0 +1,87 @@ +principal = $principal; + + } + + /** + * 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) { + + if ($this->invalidCheckResponse) { + return 'incorrect!'; + } + if ($this->fail) { + return [false, "fail!"]; + } + return [true, $this->principal]; + + } + + /** + * This method is called when a user could not be authenticated, and + * authentication was required for the current request. + * + * This gives you the oppurtunity 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. + * + * @return void + */ + function challenge(RequestInterface $request, ResponseInterface $response) { + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/PDOMySQLTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/PDOMySQLTest.php new file mode 100644 index 0000000..ede432d --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/PDOMySQLTest.php @@ -0,0 +1,31 @@ +markTestSkipped('MySQL driver is not available, or not properly configured'); + $pdo = \Sabre\TestUtil::getMySQLDB(); + if (!$pdo) $this->markTestSkipped('Could not connect to MySQL database'); + $pdo->query("DROP TABLE IF EXISTS users"); + $pdo->query(" +create table users ( + id integer unsigned not null primary key auto_increment, + username varchar(50), + digesta1 varchar(32), + email varchar(80), + displayname varchar(80), + unique(username) +);"); + + $pdo->query("INSERT INTO users (username,digesta1,email,displayname) VALUES ('user','hash','user@example.org','User')"); + + return $pdo; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/PDOSqliteTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/PDOSqliteTest.php new file mode 100644 index 0000000..abfb031 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/Backend/PDOSqliteTest.php @@ -0,0 +1,28 @@ +markTestSkipped('SQLite driver is not available'); + $pdo = new \PDO('sqlite:'.SABRE_TEMPDIR.'/pdobackend'); + $pdo->setAttribute(\PDO::ATTR_ERRMODE,\PDO::ERRMODE_EXCEPTION); + $pdo->query('CREATE TABLE users (username TEXT, digesta1 TEXT, email VARCHAR(80), displayname VARCHAR(80))'); + $pdo->query('INSERT INTO users VALUES ("user","hash","user@example.org","User")'); + + return $pdo; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/PluginTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/PluginTest.php new file mode 100644 index 0000000..b45b552 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Auth/PluginTest.php @@ -0,0 +1,130 @@ +assertTrue($plugin instanceof Plugin); + $fakeServer->addPlugin($plugin); + $this->assertEquals($plugin, $fakeServer->getPlugin('auth')); + $this->assertInternalType('array', $plugin->getPluginInfo()); + + } + + /** + * @depends testInit + */ + function testAuthenticate() { + + $fakeServer = new DAV\Server( new DAV\SimpleCollection('bla')); + $plugin = new Plugin(new Backend\Mock()); + $fakeServer->addPlugin($plugin); + $this->assertTrue( + $fakeServer->emit('beforeMethod', [new HTTP\Request(), new HTTP\Response()]) + ); + + } + + /** + * @depends testInit + * @expectedException Sabre\DAV\Exception\NotAuthenticated + */ + function testAuthenticateFail() { + + $fakeServer = new DAV\Server( new DAV\SimpleCollection('bla')); + $backend = new Backend\Mock(); + $backend->fail = true; + + $plugin = new Plugin($backend); + $fakeServer->addPlugin($plugin); + $fakeServer->emit('beforeMethod', [new HTTP\Request(), new HTTP\Response()]); + + } + + /** + * @depends testAuthenticate + */ + function testMultipleBackend() { + + $fakeServer = new DAV\Server( new DAV\SimpleCollection('bla')); + $backend1 = new Backend\Mock(); + $backend2 = new Backend\Mock(); + $backend2->fail = true; + + $plugin = new Plugin(); + $plugin->addBackend($backend1); + $plugin->addBackend($backend2); + + $fakeServer->addPlugin($plugin); + $fakeServer->emit('beforeMethod', [new HTTP\Request(), new HTTP\Response()]); + + $this->assertEquals('principals/admin', $plugin->getCurrentPrincipal()); + + } + + /** + * @depends testInit + * @expectedException Sabre\DAV\Exception + */ + function testNoAuthBackend() { + + $fakeServer = new DAV\Server( new DAV\SimpleCollection('bla')); + + $plugin = new Plugin(); + $fakeServer->addPlugin($plugin); + $fakeServer->emit('beforeMethod', [new HTTP\Request(), new HTTP\Response()]); + + } + /** + * @depends testInit + * @expectedException Sabre\DAV\Exception + */ + function testInvalidCheckResponse() { + + $fakeServer = new DAV\Server( new DAV\SimpleCollection('bla')); + $backend = new Backend\Mock(); + $backend->invalidCheckResponse = true; + + $plugin = new Plugin($backend); + $fakeServer->addPlugin($plugin); + $fakeServer->emit('beforeMethod', [new HTTP\Request(), new HTTP\Response()]); + + } + + /** + * @depends testAuthenticate + */ + function testGetCurrentPrincipal() { + + $fakeServer = new DAV\Server( new DAV\SimpleCollection('bla')); + $plugin = new Plugin(new Backend\Mock()); + $fakeServer->addPlugin($plugin); + $fakeServer->emit('beforeMethod', [new HTTP\Request(), new HTTP\Response()]); + $this->assertEquals('principals/admin', $plugin->getCurrentPrincipal()); + + } + + /** + * @depends testAuthenticate + */ + function testGetCurrentUser() { + + $fakeServer = new DAV\Server( new DAV\SimpleCollection('bla')); + $plugin = new Plugin(new Backend\Mock()); + $fakeServer->addPlugin($plugin); + $fakeServer->emit('beforeMethod', [new HTTP\Request(), new HTTP\Response()]); + $this->assertEquals('admin', $plugin->getCurrentUser()); + + } + +} + diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/BasicNodeTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/BasicNodeTest.php new file mode 100644 index 0000000..155c785 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/BasicNodeTest.php @@ -0,0 +1,235 @@ +put('hi'); + + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + */ + public function testGet() { + + $file = new FileMock(); + $file->get(); + + } + + public function testGetSize() { + + $file = new FileMock(); + $this->assertEquals(0,$file->getSize()); + + } + + + public function testGetETag() { + + $file = new FileMock(); + $this->assertNull($file->getETag()); + + } + + public function testGetContentType() { + + $file = new FileMock(); + $this->assertNull($file->getContentType()); + + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + */ + public function testDelete() { + + $file = new FileMock(); + $file->delete(); + + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + */ + public function testSetName() { + + $file = new FileMock(); + $file->setName('hi'); + + } + + public function testGetLastModified() { + + $file = new FileMock(); + // checking if lastmod is within the range of a few seconds + $lastMod = $file->getLastModified(); + $compareTime = ($lastMod + 1)-time(); + $this->assertTrue($compareTime < 3); + + } + + public function testGetChild() { + + $dir = new DirectoryMock(); + $file = $dir->getChild('mockfile'); + $this->assertTrue($file instanceof FileMock); + + } + + public function testChildExists() { + + $dir = new DirectoryMock(); + $this->assertTrue($dir->childExists('mockfile')); + + } + + public function testChildExistsFalse() { + + $dir = new DirectoryMock(); + $this->assertFalse($dir->childExists('mockfile2')); + + } + + /** + * @expectedException Sabre\DAV\Exception\NotFound + */ + public function testGetChild404() { + + $dir = new DirectoryMock(); + $file = $dir->getChild('blabla'); + + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + */ + public function testCreateFile() { + + $dir = new DirectoryMock(); + $dir->createFile('hello','data'); + + } + + /** + * @expectedException Sabre\DAV\Exception\Forbidden + */ + public function testCreateDirectory() { + + $dir = new DirectoryMock(); + $dir->createDirectory('hello'); + + } + + public function testSimpleDirectoryConstruct() { + + $dir = new SimpleCollection('simpledir',array()); + $this->assertInstanceOf('Sabre\DAV\SimpleCollection', $dir); + + } + + /** + * @depends testSimpleDirectoryConstruct + */ + public function testSimpleDirectoryConstructChild() { + + $file = new FileMock(); + $dir = new SimpleCollection('simpledir',array($file)); + $file2 = $dir->getChild('mockfile'); + + $this->assertEquals($file,$file2); + + } + + /** + * @expectedException Sabre\DAV\Exception + * @depends testSimpleDirectoryConstruct + */ + public function testSimpleDirectoryBadParam() { + + $dir = new SimpleCollection('simpledir',array('string shouldn\'t be here')); + + } + + /** + * @depends testSimpleDirectoryConstruct + */ + public function testSimpleDirectoryAddChild() { + + $file = new FileMock(); + $dir = new SimpleCollection('simpledir'); + $dir->addChild($file); + $file2 = $dir->getChild('mockfile'); + + $this->assertEquals($file,$file2); + + } + + /** + * @depends testSimpleDirectoryConstruct + * @depends testSimpleDirectoryAddChild + */ + public function testSimpleDirectoryGetChildren() { + + $file = new FileMock(); + $dir = new SimpleCollection('simpledir'); + $dir->addChild($file); + + $this->assertEquals(array($file),$dir->getChildren()); + + } + + /* + * @depends testSimpleDirectoryConstruct + */ + public function testSimpleDirectoryGetName() { + + $dir = new SimpleCollection('simpledir'); + $this->assertEquals('simpledir',$dir->getName()); + + } + + /** + * @depends testSimpleDirectoryConstruct + * @expectedException Sabre\DAV\Exception\NotFound + */ + public function testSimpleDirectoryGetChild404() { + + $dir = new SimpleCollection('simpledir'); + $dir->getChild('blabla'); + + } +} + +class DirectoryMock extends Collection { + + function getName() { + + return 'mockdir'; + + } + + function getChildren() { + + return array(new FileMock()); + + } + +} + +class FileMock extends File { + + function getName() { + + return 'mockfile'; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Browser/GuessContentTypeTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Browser/GuessContentTypeTest.php new file mode 100644 index 0000000..157c217 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Browser/GuessContentTypeTest.php @@ -0,0 +1,70 @@ +server->getPropertiesForPath('/somefile.jpg',$properties); + $this->assertArrayHasKey(0,$result); + $this->assertArrayHasKey(404,$result[0]); + $this->assertArrayHasKey('{DAV:}getcontenttype',$result[0][404]); + + } + + /** + * @depends testGetProperties + */ + function testGetPropertiesPluginEnabled() { + + $this->server->addPlugin(new GuessContentType()); + $properties = array( + '{DAV:}getcontenttype', + ); + $result = $this->server->getPropertiesForPath('/somefile.jpg',$properties); + $this->assertArrayHasKey(0,$result); + $this->assertArrayHasKey(200,$result[0], 'We received: ' . print_r($result,true)); + $this->assertArrayHasKey('{DAV:}getcontenttype',$result[0][200]); + $this->assertEquals('image/jpeg',$result[0][200]['{DAV:}getcontenttype']); + + } + + /** + * @depends testGetPropertiesPluginEnabled + */ + function testGetPropertiesUnknown() { + + $this->server->addPlugin(new GuessContentType()); + $properties = array( + '{DAV:}getcontenttype', + ); + $result = $this->server->getPropertiesForPath('/somefile.hoi',$properties); + $this->assertArrayHasKey(0,$result); + $this->assertArrayHasKey(200,$result[0]); + $this->assertArrayHasKey('{DAV:}getcontenttype',$result[0][200]); + $this->assertEquals('application/octet-stream',$result[0][200]['{DAV:}getcontenttype']); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Browser/MapGetToPropFindTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Browser/MapGetToPropFindTest.php new file mode 100644 index 0000000..9d9fbb3 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Browser/MapGetToPropFindTest.php @@ -0,0 +1,44 @@ +server->addPlugin(new MapGetToPropFind()); + + } + + function testCollectionGet() { + + $serverVars = array( + 'REQUEST_URI' => '/', + 'REQUEST_METHOD' => 'GET', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody(''); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(207, $this->response->status,'Incorrect status response received. Full response body: ' . $this->response->body); + $this->assertEquals(array( + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + 'DAV' => ['1, 3, extended-mkcol'], + 'Vary' => ['Brief,Prefer'], + ), + $this->response->getHeaders() + ); + + } + + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Browser/PluginTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Browser/PluginTest.php new file mode 100644 index 0000000..00beea9 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Browser/PluginTest.php @@ -0,0 +1,186 @@ +server->addPlugin($this->plugin = new Plugin()); + $this->server->tree->getNodeForPath('')->createDirectory('dir2'); + + } + + function testCollectionGet() { + + $request = new HTTP\Request('GET', '/dir'); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(200, $this->response->getStatus(), "Incorrect status received. Full response body: " . $this->response->getBodyAsString()); + $this->assertEquals( + [ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['text/html; charset=utf-8'], + 'Content-Security-Policy' => ["img-src 'self'; style-src 'self';"] + ], + $this->response->getHeaders() + ); + + $body = $this->response->getBodyAsString(); + $this->assertTrue(strpos($body, 'dir') !== false, $body); + $this->assertTrue(strpos($body, '<a href="/dir/child.txt">')!==false); + + } + + /** + * Adding the If-None-Match should have 0 effect, but it threw an error. + */ + function testCollectionGetIfNoneMatch() { + + $request = new HTTP\Request('GET', '/dir'); + $request->setHeader('If-None-Match', '"foo-bar"'); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(200, $this->response->getStatus(), "Incorrect status received. Full response body: " . $this->response->getBodyAsString()); + $this->assertEquals( + [ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['text/html; charset=utf-8'], + 'Content-Security-Policy' => ["img-src 'self'; style-src 'self';"] + ], + $this->response->getHeaders() + ); + + $body = $this->response->getBodyAsString(); + $this->assertTrue(strpos($body, '<title>dir') !== false, $body); + $this->assertTrue(strpos($body, '<a href="/dir/child.txt">')!==false); + + } + function testCollectionGetRoot() { + + $request = new HTTP\Request('GET', '/'); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(200, $this->response->status, "Incorrect status received. Full response body: " . $this->response->getBodyAsString()); + $this->assertEquals( + [ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['text/html; charset=utf-8'], + 'Content-Security-Policy' => ["img-src 'self'; style-src 'self';"] + ], + $this->response->getHeaders() + ); + + $body = $this->response->getBodyAsString(); + $this->assertTrue(strpos($body, '<title>/') !== false, $body); + $this->assertTrue(strpos($body, '<a href="/dir/">')!==false); + $this->assertTrue(strpos($body, '<span class="btn disabled">')!==false); + + } + + function testGETPassthru() { + + $request = new HTTP\Request('GET', '/random'); + $response = new HTTP\Response(); + $this->assertNull( + $this->plugin->httpGet($request, $response) + ); + + } + + function testPostOtherContentType() { + + $request = new HTTP\Request('POST', '/', ['Content-Type' => 'text/xml']); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(501, $this->response->status); + + } + + function testPostNoSabreAction() { + + $request = new HTTP\Request('POST', '/', ['Content-Type' => 'application/x-www-form-urlencoded']); + $request->setPostData([]); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(501, $this->response->status); + + } + + function testPostMkCol() { + + $serverVars = array( + 'REQUEST_URI' => '/', + 'REQUEST_METHOD' => 'POST', + 'CONTENT_TYPE' => 'application/x-www-form-urlencoded', + ); + $postVars = array( + 'sabreAction' => 'mkcol', + 'name' => 'new_collection', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setPostData($postVars); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(302, $this->response->status); + $this->assertEquals(array( + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Location' => ['/'], + ), $this->response->getHeaders()); + + $this->assertTrue(is_dir(SABRE_TEMPDIR . '/new_collection')); + + } + + function testGetAsset() { + + $request = new HTTP\Request('GET', '/?sabreAction=asset&assetName=favicon.ico'); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(200, $this->response->getStatus(), 'Error: ' . $this->response->body); + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['image/vnd.microsoft.icon'], + 'Content-Length' => ['4286'], + 'Cache-Control' => ['public, max-age=1209600'], + 'Content-Security-Policy' => ["img-src 'self'; style-src 'self';"] + ], $this->response->getHeaders()); + + } + + function testGetAsset404() { + + $request = new HTTP\Request('GET', '/?sabreAction=asset&assetName=flavicon.ico'); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(404, $this->response->getStatus(), 'Error: ' . $this->response->body); + + } + + function testGetAssetEscapeBasePath() { + + $request = new HTTP\Request('GET', '/?sabreAction=asset&assetName=./../assets/favicon.ico'); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(404, $this->response->getStatus(), 'Error: ' . $this->response->body); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Browser/PropFindAllTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Browser/PropFindAllTest.php new file mode 100644 index 0000000..08c2080 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Browser/PropFindAllTest.php @@ -0,0 +1,70 @@ +<?php + +namespace Sabre\DAV\Browser; + +class PropFindAllTest extends \PHPUnit_Framework_TestCase { + + function testHandleSimple() { + + $pf = new PropFindAll('foo'); + $pf->handle('{DAV:}displayname', 'foo'); + + $this->assertEquals(200, $pf->getStatus('{DAV:}displayname')); + $this->assertEquals('foo', $pf->get('{DAV:}displayname')); + + + } + + function testHandleCallBack() { + + $pf = new PropFindAll('foo'); + $pf->handle('{DAV:}displayname', function() { return 'foo'; }); + + $this->assertEquals(200, $pf->getStatus('{DAV:}displayname')); + $this->assertEquals('foo', $pf->get('{DAV:}displayname')); + + } + + function testSet() { + + $pf = new PropFindAll('foo'); + $pf->set('{DAV:}displayname', 'foo'); + + $this->assertEquals(200, $pf->getStatus('{DAV:}displayname')); + $this->assertEquals('foo', $pf->get('{DAV:}displayname')); + + } + + function testSetNull() { + + $pf = new PropFindAll('foo'); + $pf->set('{DAV:}displayname', null); + + $this->assertEquals(404, $pf->getStatus('{DAV:}displayname')); + $this->assertEquals(null, $pf->get('{DAV:}displayname')); + + } + + function testGet404Properties() { + + $pf = new PropFindAll('foo'); + $pf->set('{DAV:}displayname', null); + $this->assertEquals( + ['{DAV:}displayname'], + $pf->get404Properties() + ); + + } + + function testGet404PropertiesNothing() { + + $pf = new PropFindAll('foo'); + $pf->set('{DAV:}displayname', 'foo'); + $this->assertEquals( + ['{http://sabredav.org/ns}idk'], + $pf->get404Properties() + ); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ClientMock.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ClientMock.php new file mode 100644 index 0000000..d8b53a5 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ClientMock.php @@ -0,0 +1,34 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\HTTP\RequestInterface; + +class ClientMock extends Client { + + public $request; + public $response; + + public $url; + public $curlSettings; + + /** + * Just making this method public + * + * @param string $url + * @return string + */ + public function getAbsoluteUrl($url) { + + return parent::getAbsoluteUrl($url); + + } + + public function doRequest(RequestInterface $request) { + + $this->request = $request; + return $this->response; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ClientTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ClientTest.php new file mode 100644 index 0000000..1184057 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ClientTest.php @@ -0,0 +1,260 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\HTTP\Request; +use Sabre\HTTP\Response; + +require_once 'Sabre/DAV/ClientMock.php'; + +class ClientTest extends \PHPUnit_Framework_TestCase { + + function setUp() { + + if (!function_exists('curl_init')) { + $this->markTestSkipped('CURL must be installed to test the client'); + } + + } + + function testConstruct() { + + $client = new ClientMock(array( + 'baseUri' => '/', + )); + $this->assertInstanceOf('Sabre\DAV\ClientMock', $client); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testConstructNoBaseUri() { + + $client = new ClientMock(array()); + + } + + function testAuth() { + + $client = new ClientMock([ + 'baseUri' => '/', + 'userName' => 'foo', + 'password' => 'bar', + ]); + + $this->assertEquals("foo:bar", $client->curlSettings[CURLOPT_USERPWD]); + $this->assertEquals(CURLAUTH_BASIC | CURLAUTH_DIGEST, $client->curlSettings[CURLOPT_HTTPAUTH]); + + } + + function testBasicAuth() { + + $client = new ClientMock([ + 'baseUri' => '/', + 'userName' => 'foo', + 'password' => 'bar', + 'authType' => Client::AUTH_BASIC + ]); + + $this->assertEquals("foo:bar", $client->curlSettings[CURLOPT_USERPWD]); + $this->assertEquals(CURLAUTH_BASIC, $client->curlSettings[CURLOPT_HTTPAUTH]); + + } + + function testDigestAuth() { + + $client = new ClientMock([ + 'baseUri' => '/', + 'userName' => 'foo', + 'password' => 'bar', + 'authType' => Client::AUTH_DIGEST + ]); + + $this->assertEquals("foo:bar", $client->curlSettings[CURLOPT_USERPWD]); + $this->assertEquals(CURLAUTH_DIGEST, $client->curlSettings[CURLOPT_HTTPAUTH]); + + } + + function testNTLMAuth() { + + $client = new ClientMock([ + 'baseUri' => '/', + 'userName' => 'foo', + 'password' => 'bar', + 'authType' => Client::AUTH_NTLM + ]); + + $this->assertEquals("foo:bar", $client->curlSettings[CURLOPT_USERPWD]); + $this->assertEquals(CURLAUTH_NTLM, $client->curlSettings[CURLOPT_HTTPAUTH]); + + } + + function testProxy() { + + $client = new ClientMock([ + 'baseUri' => '/', + 'proxy' => 'localhost:8888', + ]); + + $this->assertEquals("localhost:8888", $client->curlSettings[CURLOPT_PROXY]); + + } + + function testEncoding() { + + $client = new ClientMock([ + 'baseUri' => '/', + 'encoding' => Client::ENCODING_IDENTITY | Client::ENCODING_GZIP | Client::ENCODING_DEFLATE, + ]); + + $this->assertEquals("identity,deflate,gzip", $client->curlSettings[CURLOPT_ENCODING]); + + } + + function testPropFind() { + + $client = new ClientMock([ + 'baseUri' => '/', + ]); + + $responseBody = <<<XML +<?xml version="1.0"?> +<multistatus xmlns="DAV:"> +<response> + <href>/foo</href> + <propstat> + <prop> + <displayname>bar</displayname> + </prop> + <status>HTTP/1.1 200 OK</status> + </propstat> +</response> +</multistatus> +XML; + + $client->response = new Response(207, [], $responseBody); + $result = $client->propfind('foo', ['{DAV:}displayname', '{urn:zim}gir']); + + $this->assertEquals(['{DAV:}displayname' => 'bar'], $result); + + $request = $client->request; + $this->assertEquals('PROPFIND', $request->getMethod()); + $this->assertEquals('/foo', $request->getUrl()); + $this->assertEquals([ + 'Depth' => ['0'], + 'Content-Type' => ['application/xml'], + ], $request->getHeaders()); + + } + + /** + * @expectedException \Sabre\DAV\Exception + */ + function testPropFindError() { + + $client = new ClientMock([ + 'baseUri' => '/', + ]); + + $client->response = new Response(405, []); + $client->propfind('foo', ['{DAV:}displayname', '{urn:zim}gir']); + + } + + function testPropFindDepth1() { + + $client = new ClientMock([ + 'baseUri' => '/', + ]); + + $responseBody = <<<XML +<?xml version="1.0"?> +<multistatus xmlns="DAV:"> +<response> + <href>/foo</href> + <propstat> + <prop> + <displayname>bar</displayname> + </prop> + <status>HTTP/1.1 200 OK</status> + </propstat> +</response> +</multistatus> +XML; + + $client->response = new Response(207, [], $responseBody); + $result = $client->propfind('foo', ['{DAV:}displayname', '{urn:zim}gir'], 1); + + $this->assertEquals([ + '/foo' => [ + '{DAV:}displayname' => 'bar' + ], + ], $result); + + $request = $client->request; + $this->assertEquals('PROPFIND', $request->getMethod()); + $this->assertEquals('/foo', $request->getUrl()); + $this->assertEquals([ + 'Depth' => ['1'], + 'Content-Type' => ['application/xml'], + ], $request->getHeaders()); + + } + + function testPropPatch() { + + $client = new ClientMock([ + 'baseUri' => '/', + ]); + + $responseBody = <<<XML +<?xml version="1.0"?> +<multistatus xmlns="DAV:"> +<response> + <href>/foo</href> + <propstat> + <prop> + <displayname>bar</displayname> + </prop> + <status>HTTP/1.1 200 OK</status> + </propstat> +</response> +</multistatus> +XML; + + $client->response = new Response(207, [], $responseBody); + $result = $client->propPatch('foo', ['{DAV:}displayname' => 'hi', '{urn:zim}gir' => null], 1); + $request = $client->request; + $this->assertEquals('PROPPATCH', $request->getMethod()); + $this->assertEquals('/foo', $request->getUrl()); + $this->assertEquals([ + 'Content-Type' => ['application/xml'], + ], $request->getHeaders()); + + } + + function testOPTIONS() { + + $client = new ClientMock([ + 'baseUri' => '/', + ]); + + $client->response = new Response(207, [ + 'DAV' => 'calendar-access, extended-mkcol', + ]); + $result = $client->options(); + + $this->assertEquals( + ['calendar-access', 'extended-mkcol'], + $result + ); + + $request = $client->request; + $this->assertEquals('OPTIONS', $request->getMethod()); + $this->assertEquals('/', $request->getUrl()); + $this->assertEquals([ + ], $request->getHeaders()); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/CopyTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/CopyTest.php new file mode 100644 index 0000000..56d2529 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/CopyTest.php @@ -0,0 +1,37 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\HTTP; + +class CopyTest extends \PHPUnit_Framework_TestCase { + + public function setUp() { + + \Sabre\TestUtil::clearTempDir(); + + } + + /** + * This test makes sure that a path like /foo cannot be copied into a path + * like /foo/bar/ + * + * @expectedException \Sabre\DAV\Exception\Conflict + */ + public function testCopyIntoSubPath() { + + $dir = new FS\Directory(SABRE_TEMPDIR); + $server = new Server($dir); + + $dir->createDirectory('foo'); + + $request = new HTTP\Request('COPY','/foo', [ + 'Destination' => '/foo/bar', + ]); + $response = new HTTP\ResponseMock(); + + $server->invokeMethod($request, $response); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Exception/LockedTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Exception/LockedTest.php new file mode 100644 index 0000000..c06d6aa --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Exception/LockedTest.php @@ -0,0 +1,68 @@ +<?php + +namespace Sabre\DAV\Exception; + +use + Sabre\DAV, + DOMDocument; + +class LockedTest extends \PHPUnit_Framework_TestCase { + + function testSerialize() { + + $dom = new DOMDocument('1.0'); + $dom->formatOutput = true; + $root = $dom->createElement('d:root'); + + $dom->appendChild($root); + $root->setAttribute('xmlns:d','DAV:'); + + $lockInfo = new DAV\Locks\LockInfo(); + $lockInfo->uri = '/foo'; + $locked = new Locked($lockInfo); + + $locked->serialize(new DAV\Server(), $root); + + $output = $dom->saveXML(); + + $expected = '<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:lock-token-submitted xmlns:d="DAV:"> + <d:href>/foo</d:href> + </d:lock-token-submitted> +</d:root> +'; + + $this->assertEquals($expected, $output); + + } + + function testSerializeAmpersand() { + + $dom = new DOMDocument('1.0'); + $dom->formatOutput = true; + $root = $dom->createElement('d:root'); + + $dom->appendChild($root); + $root->setAttribute('xmlns:d','DAV:'); + + $lockInfo = new DAV\Locks\LockInfo(); + $lockInfo->uri = '/foo&bar'; + $locked = new Locked($lockInfo); + + $locked->serialize(new DAV\Server(), $root); + + $output = $dom->saveXML(); + + $expected = '<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:lock-token-submitted xmlns:d="DAV:"> + <d:href>/foo&bar</d:href> + </d:lock-token-submitted> +</d:root> +'; + + $this->assertEquals($expected, $output); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Exception/PaymentRequiredTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Exception/PaymentRequiredTest.php new file mode 100644 index 0000000..7142937 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Exception/PaymentRequiredTest.php @@ -0,0 +1,14 @@ +<?php + +namespace Sabre\DAV\Exception; + +class PaymentRequiredTest extends \PHPUnit_Framework_TestCase { + + function testGetHTTPCode() { + + $ex = new PaymentRequired(); + $this->assertEquals(402, $ex->getHTTPCode()); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Exception/ServiceUnavailableTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Exception/ServiceUnavailableTest.php new file mode 100644 index 0000000..1cc691e --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Exception/ServiceUnavailableTest.php @@ -0,0 +1,14 @@ +<?php + +namespace Sabre\DAV\Exception; + +class ServiceUnavailableTest extends \PHPUnit_Framework_TestCase { + + function testGetHTTPCode() { + + $ex = new ServiceUnavailable(); + $this->assertEquals(503, $ex->getHTTPCode()); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Exception/TooManyMatchesTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Exception/TooManyMatchesTest.php new file mode 100644 index 0000000..a8b28c6 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Exception/TooManyMatchesTest.php @@ -0,0 +1,36 @@ +<?php + +namespace Sabre\DAV\Exception; + +use + Sabre\DAV, + DOMDocument; + +class TooManyMatchesTest extends \PHPUnit_Framework_TestCase { + + function testSerialize() { + + $dom = new DOMDocument('1.0'); + $dom->formatOutput = true; + $root = $dom->createElement('d:root'); + + $dom->appendChild($root); + $root->setAttribute('xmlns:d','DAV:'); + + $locked = new TooManyMatches(); + + $locked->serialize(new DAV\Server(), $root); + + $output = $dom->saveXML(); + + $expected = '<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:number-of-matches-within-limits xmlns:d="DAV:"/> +</d:root> +'; + + $this->assertEquals($expected, $output); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ExceptionTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ExceptionTest.php new file mode 100644 index 0000000..6d6bf56 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ExceptionTest.php @@ -0,0 +1,30 @@ +<?php + +namespace Sabre\DAV; + +class ExceptionTest extends \PHPUnit_Framework_TestCase { + + function testStatus() { + + $e = new Exception(); + $this->assertEquals(500,$e->getHTTPCode()); + + } + + function testExceptionStatuses() { + + $c = array( + 'Sabre\\DAV\\Exception\\NotAuthenticated' => 401, + 'Sabre\\DAV\\Exception\\InsufficientStorage' => 507, + ); + + foreach($c as $class=>$status) { + + $obj = new $class(); + $this->assertEquals($status, $obj->getHTTPCode()); + + } + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/FSExt/FileTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/FSExt/FileTest.php new file mode 100644 index 0000000..3708594 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/FSExt/FileTest.php @@ -0,0 +1,112 @@ +<?php + +namespace Sabre\DAV\FSExt; + +use Sabre\DAV; + +require_once 'Sabre/TestUtil.php'; + +class FileTest extends \PHPUnit_Framework_TestCase { + + function setUp() { + + file_put_contents(SABRE_TEMPDIR . '/file.txt', 'Contents'); + + } + + function tearDown() { + + \Sabre\TestUtil::clearTempDir(); + + } + + function testPut() { + + $filename = SABRE_TEMPDIR . '/file.txt'; + $file = new File($filename); + $result = $file->put('New contents'); + + $this->assertEquals('New contents',file_get_contents(SABRE_TEMPDIR . '/file.txt')); + $this->assertEquals( + '"' . + sha1( + fileinode($filename) . + filesize($filename ) . + filemtime($filename) + ) . '"', + $result + ); + + } + + function testRange() { + + $file = new File(SABRE_TEMPDIR . '/file.txt'); + $file->put('0000000'); + $file->patch('111', 2, 3); + + $this->assertEquals('0001110',file_get_contents(SABRE_TEMPDIR . '/file.txt')); + + } + + function testRangeStream() { + + $stream = fopen('php://memory','r+'); + fwrite($stream, "222"); + rewind($stream); + + $file = new File(SABRE_TEMPDIR . '/file.txt'); + $file->put('0000000'); + $file->patch($stream, 2, 3); + + $this->assertEquals('0002220',file_get_contents(SABRE_TEMPDIR . '/file.txt')); + + } + + + function testGet() { + + $file = new File(SABRE_TEMPDIR . '/file.txt'); + $this->assertEquals('Contents',stream_get_contents($file->get())); + + } + + function testDelete() { + + $file = new File(SABRE_TEMPDIR . '/file.txt'); + $file->delete(); + + $this->assertFalse(file_exists(SABRE_TEMPDIR . '/file.txt')); + + } + + function testGetETag() { + + $filename = SABRE_TEMPDIR . '/file.txt'; + $file = new File($filename); + $this->assertEquals( + '"' . + sha1( + fileinode($filename) . + filesize($filename ) . + filemtime($filename) + ) . '"', + $file->getETag() + ); + } + + function testGetContentType() { + + $file = new File(SABRE_TEMPDIR . '/file.txt'); + $this->assertNull($file->getContentType()); + + } + + function testGetSize() { + + $file = new File(SABRE_TEMPDIR . '/file.txt'); + $this->assertEquals(8,$file->getSize()); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/FSExt/ServerTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/FSExt/ServerTest.php new file mode 100644 index 0000000..2daf63f --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/FSExt/ServerTest.php @@ -0,0 +1,246 @@ +<?php + +namespace Sabre\DAV\FSExt; + +use Sabre\DAV; +use Sabre\HTTP; + +require_once 'Sabre/DAV/AbstractServer.php'; + +class ServerTest extends DAV\AbstractServer{ + + protected function getRootNode() { + + return new Directory($this->tempDir); + + } + + function testGet() { + + $request = new HTTP\Request('GET', '/test.txt'); + $filename = $this->tempDir . '/test.txt'; + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(200, $this->response->getStatus(), 'Invalid status code received.'); + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/octet-stream'], + 'Content-Length' => [13], + 'Last-Modified' => [HTTP\Util::toHTTPDate(new \DateTime('@' . filemtime($filename)))], + 'ETag' => ['"' . sha1(fileinode($filename ) . filesize($filename) . filemtime($filename)) . '"'], + ], + $this->response->getHeaders() + ); + + + $this->assertEquals('Test contents', stream_get_contents($this->response->body)); + + } + + function testHEAD() { + + $request = new HTTP\Request('HEAD', '/test.txt'); + $filename = $this->tempDir . '/test.txt'; + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/octet-stream'], + 'Content-Length' => [13], + 'Last-Modified' => [HTTP\Util::toHTTPDate(new \DateTime('@' . filemtime($this->tempDir . '/test.txt')))], + 'ETag' => ['"' . sha1(fileinode($filename ) . filesize($filename) . filemtime($filename)) . '"'], + ], + $this->response->getHeaders() + ); + + $this->assertEquals(200,$this->response->status); + $this->assertEquals('', $this->response->body); + + } + + function testPut() { + + $request = new HTTP\Request('PUT', '/testput.txt'); + $filename = $this->tempDir . '/testput.txt'; + $request->setBody('Testing new file'); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Length' => [0], + 'ETag' => ['"' . sha1(fileinode($filename ) . filesize($filename) . filemtime($filename)) . '"'], + ], $this->response->getHeaders()); + + $this->assertEquals(201, $this->response->status); + $this->assertEquals('', $this->response->body); + $this->assertEquals('Testing new file',file_get_contents($filename)); + + } + + function testPutAlreadyExists() { + + $request = new HTTP\Request('PUT', '/test.txt', ['If-None-Match' => '*']); + $request->setBody('Testing new file'); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ],$this->response->getHeaders()); + + $this->assertEquals(412, $this->response->status); + $this->assertNotEquals('Testing new file',file_get_contents($this->tempDir . '/test.txt')); + + } + + function testMkcol() { + + $request = new HTTP\Request('MKCOL', '/testcol'); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Length' => ['0'], + ],$this->response->getHeaders()); + + $this->assertEquals(201, $this->response->status); + $this->assertEquals('', $this->response->body); + $this->assertTrue(is_dir($this->tempDir . '/testcol')); + + } + + function testPutUpdate() { + + $request = new HTTP\Request('PUT', '/test.txt'); + $request->setBody('Testing updated file'); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals('0', $this->response->getHeader('Content-Length')); + + $this->assertEquals(204, $this->response->status); + $this->assertEquals('', $this->response->body); + $this->assertEquals('Testing updated file',file_get_contents($this->tempDir . '/test.txt')); + + } + + function testDelete() { + + $request = new HTTP\Request('DELETE', '/test.txt'); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Length' => ['0'], + ],$this->response->getHeaders()); + + $this->assertEquals(204, $this->response->status); + $this->assertEquals('', $this->response->body); + $this->assertFalse(file_exists($this->tempDir . '/test.txt')); + + } + + function testDeleteDirectory() { + + mkdir($this->tempDir.'/testcol'); + file_put_contents($this->tempDir.'/testcol/test.txt','Hi! I\'m a file with a short lifespan'); + + $request = new HTTP\Request('DELETE', '/testcol'); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Length' => ['0'], + ],$this->response->getHeaders()); + $this->assertEquals(204, $this->response->status); + $this->assertEquals('', $this->response->body); + $this->assertFalse(file_exists($this->tempDir . '/col')); + + } + + function testOptions() { + + $request = new HTTP\Request('OPTIONS', '/'); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals([ + 'DAV' => ['1, 3, extended-mkcol'], + 'MS-Author-Via' => ['DAV'], + 'Allow' => ['OPTIONS, GET, HEAD, DELETE, PROPFIND, PUT, PROPPATCH, COPY, MOVE, REPORT'], + 'Accept-Ranges' => ['bytes'], + 'Content-Length' => ['0'], + 'X-Sabre-Version'=> [DAV\Version::VERSION], + ], $this->response->getHeaders()); + + $this->assertEquals(200, $this->response->status); + $this->assertEquals('', $this->response->body); + + } + + function testMove() { + + mkdir($this->tempDir.'/testcol'); + + $request = new HTTP\Request('MOVE', '/test.txt', ['Destination' => '/testcol/test2.txt']); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(201, $this->response->status); + $this->assertEquals('', $this->response->body); + + $this->assertEquals([ + 'Content-Length' => ['0'], + 'X-Sabre-Version'=> [DAV\Version::VERSION], + ],$this->response->getHeaders()); + + $this->assertTrue( + is_file($this->tempDir . '/testcol/test2.txt') + ); + + + } + + /** + * This test checks if it's possible to move a non-FSExt collection into a + * FSExt collection. + * + * The moveInto function *should* ignore the object and let sabredav itself + * execute the slow move. + */ + function testMoveOtherObject() { + + mkdir($this->tempDir.'/tree1'); + mkdir($this->tempDir.'/tree2'); + + $tree = new DAV\Tree(new DAV\SimpleCollection('root', [ + new DAV\FS\Directory($this->tempDir . '/tree1'), + new DAV\FSExt\Directory($this->tempDir . '/tree2'), + ])); + $this->server->tree = $tree; + + $request = new HTTP\Request('MOVE', '/tree1', ['Destination' => '/tree2/tree1']); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(201, $this->response->status); + $this->assertEquals('', $this->response->body); + + $this->assertEquals([ + 'Content-Length' => ['0'], + 'X-Sabre-Version'=> [DAV\Version::VERSION], + ],$this->response->getHeaders()); + + $this->assertTrue( + is_dir($this->tempDir . '/tree2/tree1') + ); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/GetIfConditionsTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/GetIfConditionsTest.php new file mode 100644 index 0000000..4e9ebaf --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/GetIfConditionsTest.php @@ -0,0 +1,337 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\HTTP; + +require_once 'Sabre/HTTP/ResponseMock.php'; +require_once 'Sabre/DAV/AbstractServer.php'; + +class GetIfConditionsTest extends AbstractServer { + + function testNoConditions() { + + $request = new HTTP\Request(); + + $conditions = $this->server->getIfConditions($request); + $this->assertEquals(array(),$conditions); + + } + + function testLockToken() { + + $request = new HTTP\Request('GET', '/path/', ['If' => '(<opaquelocktoken:token1>)']); + $conditions = $this->server->getIfConditions($request); + + $compare = array( + + array( + 'uri' => 'path', + 'tokens' => array( + array( + 'negate' => false, + 'token' => 'opaquelocktoken:token1', + 'etag' => '', + ), + ), + + ), + + ); + + $this->assertEquals($compare,$conditions); + + } + + function testNotLockToken() { + + $serverVars = array( + 'HTTP_IF' => '(Not <opaquelocktoken:token1>)', + 'REQUEST_URI' => '/bla' + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $conditions = $this->server->getIfConditions($request); + + $compare = array( + + array( + 'uri' => 'bla', + 'tokens' => array( + array( + 'negate' => true, + 'token' => 'opaquelocktoken:token1', + 'etag' => '', + ), + ), + + ), + + ); + $this->assertEquals($compare,$conditions); + + } + + function testLockTokenUrl() { + + $serverVars = array( + 'HTTP_IF' => '<http://www.example.com/> (<opaquelocktoken:token1>)', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $conditions = $this->server->getIfConditions($request); + + $compare = array( + + array( + 'uri' => '', + 'tokens' => array( + array( + 'negate' => false, + 'token' => 'opaquelocktoken:token1', + 'etag' => '', + ), + ), + + ), + + ); + $this->assertEquals($compare,$conditions); + + } + + function test2LockTokens() { + + $serverVars = array( + 'HTTP_IF' => '(<opaquelocktoken:token1>) (Not <opaquelocktoken:token2>)', + 'REQUEST_URI' => '/bla', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $conditions = $this->server->getIfConditions($request); + + $compare = array( + + array( + 'uri' => 'bla', + 'tokens' => array( + array( + 'negate' => false, + 'token' => 'opaquelocktoken:token1', + 'etag' => '', + ), + array( + 'negate' => true, + 'token' => 'opaquelocktoken:token2', + 'etag' => '', + ), + ), + + ), + + ); + $this->assertEquals($compare,$conditions); + + } + + function test2UriLockTokens() { + + $serverVars = array( + 'HTTP_IF' => '<http://www.example.org/node1> (<opaquelocktoken:token1>) <http://www.example.org/node2> (Not <opaquelocktoken:token2>)', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $conditions = $this->server->getIfConditions($request); + + $compare = array( + + array( + 'uri' => 'node1', + 'tokens' => array( + array( + 'negate' => false, + 'token' => 'opaquelocktoken:token1', + 'etag' => '', + ), + ), + ), + array( + 'uri' => 'node2', + 'tokens' => array( + array( + 'negate' => true, + 'token' => 'opaquelocktoken:token2', + 'etag' => '', + ), + ), + + ), + + ); + $this->assertEquals($compare,$conditions); + + } + + function test2UriMultiLockTokens() { + + $serverVars = array( + 'HTTP_IF' => '<http://www.example.org/node1> (<opaquelocktoken:token1>) (<opaquelocktoken:token2>) <http://www.example.org/node2> (Not <opaquelocktoken:token3>)', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $conditions = $this->server->getIfConditions($request); + + $compare = array( + + array( + 'uri' => 'node1', + 'tokens' => array( + array( + 'negate' => false, + 'token' => 'opaquelocktoken:token1', + 'etag' => '', + ), + array( + 'negate' => false, + 'token' => 'opaquelocktoken:token2', + 'etag' => '', + ), + ), + ), + array( + 'uri' => 'node2', + 'tokens' => array( + array( + 'negate' => true, + 'token' => 'opaquelocktoken:token3', + 'etag' => '', + ), + ), + + ), + + ); + $this->assertEquals($compare,$conditions); + + } + + function testEtag() { + + $serverVars = array( + 'HTTP_IF' => '(["etag1"])', + 'REQUEST_URI' => '/foo', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $conditions = $this->server->getIfConditions($request); + + $compare = array( + + array( + 'uri' => 'foo', + 'tokens' => array( + array( + 'negate' => false, + 'token' => '', + 'etag' => '"etag1"', + ), + ), + ), + + ); + $this->assertEquals($compare,$conditions); + + } + + function test2Etags() { + + $serverVars = array( + 'HTTP_IF' => '<http://www.example.org/> (["etag1"]) (["etag2"])', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $conditions = $this->server->getIfConditions($request); + + $compare = array( + + array( + 'uri' => '', + 'tokens' => array( + array( + 'negate' => false, + 'token' => '', + 'etag' => '"etag1"', + ), + array( + 'negate' => false, + 'token' => '', + 'etag' => '"etag2"', + ), + ), + ), + + ); + $this->assertEquals($compare,$conditions); + + } + + function testComplexIf() { + + $serverVars = array( + 'HTTP_IF' => '<http://www.example.org/node1> (<opaquelocktoken:token1> ["etag1"]) ' . + '(Not <opaquelocktoken:token2>) (["etag2"]) <http://www.example.org/node2> ' . + '(<opaquelocktoken:token3>) (Not <opaquelocktoken:token4>) (["etag3"])', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $conditions = $this->server->getIfConditions($request); + + $compare = array( + + array( + 'uri' => 'node1', + 'tokens' => array( + array( + 'negate' => false, + 'token' => 'opaquelocktoken:token1', + 'etag' => '"etag1"', + ), + array( + 'negate' => true, + 'token' => 'opaquelocktoken:token2', + 'etag' => '', + ), + array( + 'negate' => false, + 'token' => '', + 'etag' => '"etag2"', + ), + ), + ), + array( + 'uri' => 'node2', + 'tokens' => array( + array( + 'negate' => false, + 'token' => 'opaquelocktoken:token3', + 'etag' => '', + ), + array( + 'negate' => true, + 'token' => 'opaquelocktoken:token4', + 'etag' => '', + ), + array( + 'negate' => false, + 'token' => '', + 'etag' => '"etag3"', + ), + ), + ), + + ); + $this->assertEquals($compare,$conditions); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/HTTPPreferParsingTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/HTTPPreferParsingTest.php new file mode 100644 index 0000000..cd8bee9 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/HTTPPreferParsingTest.php @@ -0,0 +1,188 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\HTTP; + +class HTTPPreferParsingTest extends \Sabre\DAVServerTest { + + function testParseSimple() { + + $httpRequest = HTTP\Sapi::createFromServerArray([ + 'HTTP_PREFER' => 'return-asynch', + ]); + + $server = new Server(); + $server->httpRequest = $httpRequest; + + $this->assertEquals([ + 'respond-async' => true, + 'return' => null, + 'handling' => null, + 'wait' => null, + ], $server->getHTTPPrefer()); + + } + + function testParseValue() { + + $httpRequest = HTTP\Sapi::createFromServerArray([ + 'HTTP_PREFER' => 'wait=10', + ]); + + $server = new Server(); + $server->httpRequest = $httpRequest; + + $this->assertEquals([ + 'respond-async' => false, + 'return' => null, + 'handling' => null, + 'wait' => '10', + ], $server->getHTTPPrefer()); + + } + + function testParseMultiple() { + + $httpRequest = HTTP\Sapi::createFromServerArray([ + 'HTTP_PREFER' => 'return-minimal, strict,lenient', + ]); + + $server = new Server(); + $server->httpRequest = $httpRequest; + + $this->assertEquals([ + 'respond-async' => false, + 'return' => 'minimal', + 'handling' => 'lenient', + 'wait' => null, + ], $server->getHTTPPrefer()); + + } + + function testParseWeirdValue() { + + $httpRequest = HTTP\Sapi::createFromServerArray([ + 'HTTP_PREFER' => 'BOOOH', + ]); + + $server = new Server(); + $server->httpRequest = $httpRequest; + + $this->assertEquals([ + 'respond-async' => false, + 'return' => null, + 'handling' => null, + 'wait' => null, + 'boooh' => true, + ], $server->getHTTPPrefer()); + + } + + function testBrief() { + + $httpRequest = HTTP\Sapi::createFromServerArray([ + 'HTTP_BRIEF' => 't', + ]); + + $server = new Server(); + $server->httpRequest = $httpRequest; + + $this->assertEquals([ + 'respond-async' => false, + 'return' => 'minimal', + 'handling' => null, + 'wait' => null, + ], $server->getHTTPPrefer()); + + } + + /** + * propfindMinimal + * + * @return void + */ + function testpropfindMinimal() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'PROPFIND', + 'REQUEST_URI' => '/', + 'HTTP_PREFER' => 'return-minimal', + ]); + $request->setBody(<<<BLA +<?xml version="1.0"?> +<d:propfind xmlns:d="DAV:"> + <d:prop> + <d:something /> + <d:resourcetype /> + </d:prop> +</d:propfind> +BLA + ); + + $response = $this->request($request); + + $body = $response->getBodyAsString(); + + $this->assertEquals(207, $response->getStatus(), $body); + + $this->assertTrue(strpos($body, 'resourcetype') !== false, $body); + $this->assertTrue(strpos($body, 'something') === false, $body); + + } + + function testproppatchMinimal() { + + $request = new HTTP\Request('PROPPATCH', '/', ['Prefer' => 'return-minimal']); + $request->setBody(<<<BLA +<?xml version="1.0"?> +<d:propertyupdate xmlns:d="DAV:"> + <d:set> + <d:prop> + <d:something>nope!</d:something> + </d:prop> + </d:set> +</d:propertyupdate> +BLA + ); + + $this->server->on('propPatch', function($path, PropPatch $propPatch) { + + $propPatch->handle('{DAV:}something', function($props) { + return true; + }); + + }); + + $response = $this->request($request); + + $this->assertEquals(0, strlen($response->body), 'Expected empty body: ' . $response->body); + $this->assertEquals(204, $response->status); + + } + + function testproppatchMinimalError() { + + $request = new HTTP\Request('PROPPATCH', '/', ['Prefer' => 'return-minimal']); + $request->setBody(<<<BLA +<?xml version="1.0"?> +<d:propertyupdate xmlns:d="DAV:"> + <d:set> + <d:prop> + <d:something>nope!</d:something> + </d:prop> + </d:set> +</d:propertyupdate> +BLA + ); + + $response = $this->request($request); + + $body = $response->getBodyAsString(); + + $this->assertEquals(207, $response->status); + $this->assertTrue(strpos($body, 'something') !== false); + $this->assertTrue(strpos($body, '403 Forbidden') !== false, $body); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/HttpDeleteTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/HttpDeleteTest.php new file mode 100644 index 0000000..6c10afa --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/HttpDeleteTest.php @@ -0,0 +1,137 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\DAVServerTest; +use Sabre\HTTP; + +/** + * Tests related to the PUT request. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class HttpDeleteTest extends DAVServerTest { + + /** + * Sets up the DAV tree. + * + * @return void + */ + public function setUpTree() { + + $this->tree = new Mock\Collection('root', [ + 'file1' => 'foo', + 'dir' => [ + 'subfile' => 'bar', + 'subfile2' => 'baz', + ], + ]); + + } + + /** + * A successful DELETE + */ + public function testDelete() { + + $request = new HTTP\Request('DELETE', '/file1'); + + $response = $this->request($request); + + $this->assertEquals( + 204, + $response->getStatus(), + "Incorrect status code. Response body: " . $response->getBodyAsString() + ); + + $this->assertEquals( + [ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Length' => ['0'], + ], + $response->getHeaders() + ); + + } + + /** + * Deleting a Directory + */ + public function testDeleteDirectory() { + + $request = new HTTP\Request('DELETE', '/dir'); + + $response = $this->request($request); + + $this->assertEquals( + 204, + $response->getStatus(), + "Incorrect status code. Response body: " . $response->getBodyAsString() + ); + + $this->assertEquals( + [ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Length' => ['0'], + ], + $response->getHeaders() + ); + + } + + /** + * DELETE on a node that does not exist + */ + public function testDeleteNotFound() { + + $request = new HTTP\Request('DELETE', '/file2'); + $response = $this->request($request); + + $this->assertEquals( + 404, + $response->getStatus(), + "Incorrect status code. Response body: " . $response->getBodyAsString() + ); + + } + + /** + * DELETE with preconditions + */ + public function testDeletePreconditions() { + + $request = new HTTP\Request('DELETE', '/file1', [ + 'If-Match' => '"' . md5('foo') . '"', + ]); + + $response = $this->request($request); + + $this->assertEquals( + 204, + $response->getStatus(), + "Incorrect status code. Response body: " . $response->getBodyAsString() + ); + + } + + /** + * DELETE with incorrect preconditions + */ + public function testDeletePreconditionsFailed() { + + $request = new HTTP\Request('DELETE', '/file1', [ + 'If-Match' => '"' . md5('bar') . '"', + ]); + + $response = $this->request($request); + + $this->assertEquals( + 412, + $response->getStatus(), + "Incorrect status code. Response body: " . $response->getBodyAsString() + ); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/HttpGetTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/HttpGetTest.php new file mode 100644 index 0000000..88ce88a --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/HttpGetTest.php @@ -0,0 +1,158 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\DAVServerTest; +use Sabre\HTTP; + +/** + * Tests related to the GET request. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class HttpGetTest extends DAVServerTest { + + /** + * Sets up the DAV tree. + * + * @return void + */ + function setUpTree() { + + $this->tree = new Mock\Collection('root', [ + 'file1' => 'foo', + new Mock\Collection('dir', []), + new Mock\StreamingFile('streaming', 'stream') + ]); + + } + + function testGet() { + + $request = new HTTP\Request('GET', '/file1'); + $response = $this->request($request); + + $this->assertEquals(200, $response->getStatus()); + + // Removing Last-Modified because it keeps changing. + $response->removeHeader('Last-Modified'); + + $this->assertEquals( + [ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/octet-stream'], + 'Content-Length' => [3], + 'ETag' => ['"' . md5('foo') . '"'], + ], + $response->getHeaders() + ); + + $this->assertEquals('foo', $response->getBodyAsString()); + + } + + function testGetHttp10() { + + $request = new HTTP\Request('GET', '/file1'); + $request->setHttpVersion('1.0'); + $response = $this->request($request); + + $this->assertEquals(200, $response->getStatus()); + + // Removing Last-Modified because it keeps changing. + $response->removeHeader('Last-Modified'); + + $this->assertEquals( + [ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/octet-stream'], + 'Content-Length' => [3], + 'ETag' => ['"' . md5('foo') . '"'], + ], + $response->getHeaders() + ); + + $this->assertEquals('1.0', $response->getHttpVersion()); + + $this->assertEquals('foo', $response->getBodyAsString()); + + } + + function testGet404() { + + $request = new HTTP\Request('GET', '/notfound'); + $response = $this->request($request); + + $this->assertEquals(404, $response->getStatus()); + + } + + function testGet404_aswell() { + + $request = new HTTP\Request('GET', '/file1/subfile'); + $response = $this->request($request); + + $this->assertEquals(404, $response->getStatus()); + + } + + /** + * We automatically normalize double slashes. + */ + function testGetDoubleSlash() { + + $request = new HTTP\Request('GET', '//file1'); + $response = $this->request($request); + + $this->assertEquals(200, $response->getStatus()); + + // Removing Last-Modified because it keeps changing. + $response->removeHeader('Last-Modified'); + + $this->assertEquals( + [ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/octet-stream'], + 'Content-Length' => [3], + 'ETag' => ['"' . md5('foo') . '"'], + ], + $response->getHeaders() + ); + + $this->assertEquals('foo', $response->getBodyAsString()); + + } + + function testGetCollection() { + + $request = new HTTP\Request('GET', '/dir'); + $response = $this->request($request); + + $this->assertEquals(501, $response->getStatus()); + + } + + function testGetStreaming() { + + $request = new HTTP\Request('GET', '/streaming'); + $response = $this->request($request); + + $this->assertEquals(200, $response->getStatus()); + + // Removing Last-Modified because it keeps changing. + $response->removeHeader('Last-Modified'); + + $this->assertEquals( + [ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/octet-stream'], + ], + $response->getHeaders() + ); + + $this->assertEquals('stream', $response->getBodyAsString()); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/HttpHeadTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/HttpHeadTest.php new file mode 100644 index 0000000..443fde6 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/HttpHeadTest.php @@ -0,0 +1,97 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\DAVServerTest; +use Sabre\HTTP; + +/** + * Tests related to the HEAD request. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class HttpHeadTest extends DAVServerTest { + + /** + * Sets up the DAV tree. + * + * @return void + */ + function setUpTree() { + + $this->tree = new Mock\Collection('root', [ + 'file1' => 'foo', + new Mock\Collection('dir', []), + new Mock\StreamingFile('streaming', 'stream') + ]); + + } + + function testHEAD() { + + $request = new HTTP\Request('HEAD', '//file1'); + $response = $this->request($request); + + $this->assertEquals(200, $response->getStatus()); + + // Removing Last-Modified because it keeps changing. + $response->removeHeader('Last-Modified'); + + $this->assertEquals( + [ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/octet-stream'], + 'Content-Length' => [3], + 'ETag' => ['"' . md5('foo') . '"'], + ], + $response->getHeaders() + ); + + $this->assertEquals('', $response->getBodyAsString()); + + } + + /** + * According to the specs, HEAD should behave identical to GET. But, broken + * clients needs HEAD requests on collections to respond with a 200, so + * that's what we do. + */ + function testHEADCollection() { + + $request = new HTTP\Request('HEAD', '/dir'); + $response = $this->request($request); + + $this->assertEquals(200, $response->getStatus()); + + } + + /** + * HEAD automatically internally maps to GET via a sub-request. + * The Auth plugin must not be triggered twice for these, so we'll + * test for that. + */ + function testDoubleAuth() { + + $count = 0; + + $authBackend = new Auth\Backend\BasicCallBack(function($userName,$password) use (&$count) { + $count++; + return true; + }); + $this->server->addPlugin( + new Auth\Plugin( + $authBackend + ) + ); + $request = new HTTP\Request('HEAD', '/file1', ['Authorization' => 'Basic ' . base64_encode('user:pass')]); + $response = $this->request($request); + + $this->assertEquals(200, $response->getStatus()); + + $this->assertEquals(1, $count, 'Auth was triggered twice :('); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/HttpMoveTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/HttpMoveTest.php new file mode 100644 index 0000000..fd78b87 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/HttpMoveTest.php @@ -0,0 +1,119 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\DAVServerTest; +use Sabre\HTTP; + +/** + * Tests related to the MOVE request. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class HttpMoveTest extends DAVServerTest { + + /** + * Sets up the DAV tree. + * + * @return void + */ + function setUpTree() { + + $this->tree = new Mock\Collection('root', [ + 'file1' => 'content1', + 'file2' => 'content2', + ]); + + } + + function testMoveToSelf() { + + $request = new HTTP\Request('MOVE', '/file1', [ + 'Destination' => '/file1' + ]); + $response = $this->request($request); + $this->assertEquals(403, $response->getStatus()); + $this->assertEquals('content1', $this->tree->getChild('file1')->get()); + + } + + function testMove() { + + $request = new HTTP\Request('MOVE', '/file1', [ + 'Destination' => '/file3' + ]); + $response = $this->request($request); + $this->assertEquals(201, $response->getStatus(), print_r($response,true)); + $this->assertEquals('content1', $this->tree->getChild('file3')->get()); + $this->assertFalse($this->tree->childExists('file1')); + + } + + function testMoveToExisting() { + + $request = new HTTP\Request('MOVE', '/file1', [ + 'Destination' => '/file2' + ]); + $response = $this->request($request); + $this->assertEquals(204, $response->getStatus(), print_r($response,true)); + $this->assertEquals('content1', $this->tree->getChild('file2')->get()); + $this->assertFalse($this->tree->childExists('file1')); + + } + + function testMoveToExistingOverwriteT() { + + $request = new HTTP\Request('MOVE', '/file1', [ + 'Destination' => '/file2', + 'Overwrite' => 'T', + ]); + $response = $this->request($request); + $this->assertEquals(204, $response->getStatus(), print_r($response,true)); + $this->assertEquals('content1', $this->tree->getChild('file2')->get()); + $this->assertFalse($this->tree->childExists('file1')); + + } + + function testMoveToExistingOverwriteF() { + + $request = new HTTP\Request('MOVE', '/file1', [ + 'Destination' => '/file2', + 'Overwrite' => 'F', + ]); + $response = $this->request($request); + $this->assertEquals(412, $response->getStatus(), print_r($response,true)); + $this->assertEquals('content1', $this->tree->getChild('file1')->get()); + $this->assertEquals('content2', $this->tree->getChild('file2')->get()); + $this->assertTrue($this->tree->childExists('file1')); + $this->assertTrue($this->tree->childExists('file2')); + + } + + /** + * If we MOVE to an existing file, but a plugin prevents the original from + * being deleted, we need to make sure that the server does not delete + * the destination. + */ + function testMoveToExistingBlockedDeleteSource() { + + $this->server->on('beforeUnbind', function($path) { + + if ($path==='file1') { + throw new \Sabre\DAV\Exception\Forbidden('uh oh'); + } + + }); + $request = new HTTP\Request('MOVE', '/file1', [ + 'Destination' => '/file2' + ]); + $response = $this->request($request); + $this->assertEquals(403, $response->getStatus(), print_r($response,true)); + $this->assertEquals('content1', $this->tree->getChild('file1')->get()); + $this->assertEquals('content2', $this->tree->getChild('file2')->get()); + $this->assertTrue($this->tree->childExists('file1')); + $this->assertTrue($this->tree->childExists('file2')); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/HttpPutTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/HttpPutTest.php new file mode 100644 index 0000000..eddaf3f --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/HttpPutTest.php @@ -0,0 +1,349 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\DAVServerTest; +use Sabre\HTTP; + +/** + * Tests related to the PUT request. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class HttpPutTest extends DAVServerTest { + + /** + * Sets up the DAV tree. + * + * @return void + */ + function setUpTree() { + + $this->tree = new Mock\Collection('root', [ + 'file1' => 'foo', + ]); + + } + + /** + * A successful PUT of a new file. + */ + function testPut() { + + $request = new HTTP\Request('PUT', '/file2', [], 'hello'); + + $response = $this->request($request); + + $this->assertEquals(201, $response->getStatus(), 'Incorrect status code received. Full response body:' . $response->getBodyAsString()); + + $this->assertEquals( + 'hello', + $this->server->tree->getNodeForPath('file2')->get() + ); + + $this->assertEquals( + [ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Length' => ['0'], + 'ETag' => ['"' . md5('hello') . '"'] + ], + $response->getHeaders() + ); + + } + + /** + * A successful PUT on an existing file. + * + * @depends testPut + */ + function testPutExisting() { + + $request = new HTTP\Request('PUT', '/file1', [], 'bar'); + + $response = $this->request($request); + + $this->assertEquals(204, $response->getStatus()); + + $this->assertEquals( + 'bar', + $this->server->tree->getNodeForPath('file1')->get() + ); + + $this->assertEquals( + [ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Length' => ['0'], + 'ETag' => ['"' . md5('bar') . '"'] + ], + $response->getHeaders() + ); + + } + + /** + * PUT on existing file with If-Match: * + * + * @depends testPutExisting + */ + function testPutExistingIfMatchStar() { + + $request = new HTTP\Request( + 'PUT', + '/file1', + ['If-Match' => '*'], + 'hello' + ); + + $response = $this->request($request); + + $this->assertEquals(204, $response->getStatus()); + + $this->assertEquals( + 'hello', + $this->server->tree->getNodeForPath('file1')->get() + ); + + $this->assertEquals( + [ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Length' => ['0'], + 'ETag' => ['"' . md5('hello') . '"'] + ], + $response->getHeaders() + ); + + } + + /** + * PUT on existing file with If-Match: with a correct etag + * + * @depends testPutExisting + */ + function testPutExistingIfMatchCorrect() { + + $request = new HTTP\Request( + 'PUT', + '/file1', + ['If-Match' => '"' . md5('foo') . '"'], + 'hello' + ); + + $response = $this->request($request); + + $this->assertEquals(204, $response->status); + + $this->assertEquals( + 'hello', + $this->server->tree->getNodeForPath('file1')->get() + ); + + $this->assertEquals( + [ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Length' => ['0'], + 'ETag' => ['"' . md5('hello') . '"'], + ], + $response->getHeaders() + ); + + } + + /** + * PUT with Content-Range should be rejected. + * + * @depends testPut + */ + function testPutContentRange() { + + $request = new HTTP\Request( + 'PUT', + '/file2', + ['Content-Range' => 'bytes/100-200'], + 'hello' + ); + + $response = $this->request($request); + $this->assertEquals(400, $response->getStatus()); + + } + + /** + * PUT on non-existing file with If-None-Match: * should work. + * + * @depends testPut + */ + function testPutIfNoneMatchStar() { + + $request = new HTTP\Request( + 'PUT', + '/file2', + ['If-None-Match' => '*'], + 'hello' + ); + + $response = $this->request($request); + + $this->assertEquals(201, $response->getStatus()); + + $this->assertEquals( + 'hello', + $this->server->tree->getNodeForPath('file2')->get() + ); + + $this->assertEquals( + [ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Length' => ['0'], + 'ETag' => ['"' . md5('hello') . '"'] + ], + $response->getHeaders() + ); + + } + + /** + * PUT on non-existing file with If-Match: * should fail. + * + * @depends testPut + */ + function testPutIfMatchStar() { + + $request = new HTTP\Request( + 'PUT', + '/file2', + ['If-Match' => '*'], + 'hello' + ); + + $response = $this->request($request); + + $this->assertEquals(412, $response->getStatus()); + + } + + /** + * PUT on existing file with If-None-Match: * should fail. + * + * @depends testPut + */ + function testPutExistingIfNoneMatchStar() { + + $request = new HTTP\Request( + 'PUT', + '/file1', + ['If-None-Match' => '*'], + 'hello' + ); + $request->setBody('hello'); + + $response = $this->request($request); + + $this->assertEquals(412, $response->getStatus()); + + } + + /** + * PUT thats created in a non-collection should be rejected. + * + * @depends testPut + */ + function testPutNoParent() { + + $request = new HTTP\Request( + 'PUT', + '/file1/file2', + [], + 'hello' + ); + + $response = $this->request($request); + $this->assertEquals(409, $response->getStatus()); + + } + + /** + * Finder may sometimes make a request, which gets its content-body + * stripped. We can't always prevent this from happening, but in some cases + * we can detected this and return an error instead. + * + * @depends testPut + */ + function testFinderPutSuccess() { + + $request = new HTTP\Request( + 'PUT', + '/file2', + ['X-Expected-Entity-Length' => '5'], + 'hello' + ); + $response = $this->request($request); + + $this->assertEquals(201, $response->getStatus()); + + $this->assertEquals( + 'hello', + $this->server->tree->getNodeForPath('file2')->get() + ); + + $this->assertEquals( + [ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Length' => ['0'], + 'ETag' => ['"' . md5('hello') . '"'], + ], + $response->getHeaders() + ); + + } + + /** + * Same as the last one, but in this case we're mimicing a failed request. + * + * @depends testFinderPutSuccess + */ + function testFinderPutFail() { + + $request = new HTTP\Request( + 'PUT', + '/file2', + ['X-Expected-Entity-Length' => '5'], + '' + ); + + $response = $this->request($request); + + $this->assertEquals(403, $response->getStatus()); + + } + + /** + * Plugins can intercept PUT. We need to make sure that works. + * + * @depends testPut + */ + function testPutIntercept() { + + $this->server->on('beforeBind', function($uri) { + $this->server->httpResponse->setStatus(418); + return false; + }); + + $request = new HTTP\Request('PUT', '/file2', [], 'hello'); + $response = $this->request($request); + + $this->assertEquals(418, $response->getStatus(), 'Incorrect status code received. Full response body: ' .$response->getBodyAsString()); + + $this->assertFalse( + $this->server->tree->nodeExists('file2') + ); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + ], $response->getHeaders()); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Issue33Test.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Issue33Test.php new file mode 100644 index 0000000..4ccb42f --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Issue33Test.php @@ -0,0 +1,106 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\HTTP; + +require_once 'Sabre/TestUtil.php'; + +class Issue33Test extends \PHPUnit_Framework_TestCase { + + function setUp() { + + \Sabre\TestUtil::clearTempDir(); + + } + + function testCopyMoveInfo() { + + $bar = new SimpleCollection('bar'); + $root = new SimpleCollection('webdav',array($bar)); + + $server = new Server($root); + $server->setBaseUri('/webdav/'); + + $serverVars = array( + 'REQUEST_URI' => '/webdav/bar', + 'HTTP_DESTINATION' => 'http://dev2.tribalos.com/webdav/%C3%A0fo%C3%B3', + 'HTTP_OVERWRITE' => 'F', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + + $server->httpRequest = $request; + + $info = $server->getCopyAndMoveInfo($request); + + $this->assertEquals('%C3%A0fo%C3%B3', urlencode($info['destination'])); + $this->assertFalse($info['destinationExists']); + $this->assertFalse($info['destinationNode']); + + } + + function testTreeMove() { + + mkdir(SABRE_TEMPDIR . '/issue33'); + $dir = new FS\Directory(SABRE_TEMPDIR . '/issue33'); + + $dir->createDirectory('bar'); + + $tree = new Tree($dir); + $tree->move('bar',urldecode('%C3%A0fo%C3%B3')); + + $node = $tree->getNodeForPath(urldecode('%C3%A0fo%C3%B3')); + $this->assertEquals(urldecode('%C3%A0fo%C3%B3'),$node->getName()); + + } + + function testDirName() { + + $dirname1 = 'bar'; + $dirname2 = urlencode('%C3%A0fo%C3%B3');; + + $this->assertTrue(dirname($dirname1)==dirname($dirname2)); + + } + + /** + * @depends testTreeMove + * @depends testCopyMoveInfo + */ + function testEverything() { + + // Request object + $serverVars = array( + 'REQUEST_METHOD' => 'MOVE', + 'REQUEST_URI' => '/webdav/bar', + 'HTTP_DESTINATION' => 'http://dev2.tribalos.com/webdav/%C3%A0fo%C3%B3', + 'HTTP_OVERWRITE' => 'F', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody(''); + + $response = new HTTP\ResponseMock(); + + // Server setup + mkdir(SABRE_TEMPDIR . '/issue33'); + $dir = new FS\Directory(SABRE_TEMPDIR . '/issue33'); + + $dir->createDirectory('bar'); + + $tree = new Tree($dir); + + $server = new Server($tree); + $server->setBaseUri('/webdav/'); + + $server->httpRequest = $request; + $server->httpResponse = $response; + $server->sapi = new HTTP\SapiMock(); + $server->exec(); + + $this->assertTrue(file_exists(SABRE_TEMPDIR . '/issue33/' . urldecode('%C3%A0fo%C3%B3'))); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/AbstractTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/AbstractTest.php new file mode 100644 index 0000000..f39e9a0 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/AbstractTest.php @@ -0,0 +1,196 @@ +<?php + +namespace Sabre\DAV\Locks\Backend; + +use Sabre\DAV; + +abstract class AbstractTest extends \PHPUnit_Framework_TestCase { + + /** + * @abstract + * @return AbstractBackend + */ + abstract function getBackend(); + + function testSetup() { + + $backend = $this->getBackend(); + $this->assertInstanceOf('Sabre\\DAV\\Locks\\Backend\\AbstractBackend', $backend); + + } + + /** + * @depends testSetup + */ + function testGetLocks() { + + $backend = $this->getBackend(); + + $lock = new DAV\Locks\LockInfo(); + $lock->owner = 'Sinterklaas'; + $lock->timeout = 60; + $lock->created = time(); + $lock->token = 'MY-UNIQUE-TOKEN'; + $lock->uri ='someuri'; + + $this->assertTrue($backend->lock('someuri', $lock)); + + $locks = $backend->getLocks('someuri', false); + + $this->assertEquals(1,count($locks)); + $this->assertEquals('Sinterklaas',$locks[0]->owner); + $this->assertEquals('someuri',$locks[0]->uri); + + } + + /** + * @depends testGetLocks + */ + function testGetLocksParent() { + + $backend = $this->getBackend(); + + $lock = new DAV\Locks\LockInfo(); + $lock->owner = 'Sinterklaas'; + $lock->timeout = 60; + $lock->created = time(); + $lock->depth = DAV\Server::DEPTH_INFINITY; + $lock->token = 'MY-UNIQUE-TOKEN'; + + $this->assertTrue($backend->lock('someuri', $lock)); + + $locks = $backend->getLocks('someuri/child', false); + + $this->assertEquals(1,count($locks)); + $this->assertEquals('Sinterklaas',$locks[0]->owner); + $this->assertEquals('someuri',$locks[0]->uri); + + } + + + /** + * @depends testGetLocks + */ + function testGetLocksParentDepth0() { + + $backend = $this->getBackend(); + + $lock = new DAV\Locks\LockInfo(); + $lock->owner = 'Sinterklaas'; + $lock->timeout = 60; + $lock->created = time(); + $lock->depth = 0; + $lock->token = 'MY-UNIQUE-TOKEN'; + + $this->assertTrue($backend->lock('someuri', $lock)); + + $locks = $backend->getLocks('someuri/child', false); + + $this->assertEquals(0,count($locks)); + + } + + function testGetLocksChildren() { + + $backend = $this->getBackend(); + + $lock = new DAV\Locks\LockInfo(); + $lock->owner = 'Sinterklaas'; + $lock->timeout = 60; + $lock->created = time(); + $lock->depth = 0; + $lock->token = 'MY-UNIQUE-TOKEN'; + + $this->assertTrue($backend->lock('someuri/child', $lock)); + + $locks = $backend->getLocks('someuri/child', false); + $this->assertEquals(1,count($locks)); + + $locks = $backend->getLocks('someuri', false); + $this->assertEquals(0,count($locks)); + + $locks = $backend->getLocks('someuri', true); + $this->assertEquals(1,count($locks)); + + } + + /** + * @depends testGetLocks + */ + function testLockRefresh() { + + $backend = $this->getBackend(); + + $lock = new DAV\Locks\LockInfo(); + $lock->owner = 'Sinterklaas'; + $lock->timeout = 60; + $lock->created = time(); + $lock->token = 'MY-UNIQUE-TOKEN'; + + $this->assertTrue($backend->lock('someuri', $lock)); + /* Second time */ + + $lock->owner = 'Santa Clause'; + $this->assertTrue($backend->lock('someuri', $lock)); + + $locks = $backend->getLocks('someuri', false); + + $this->assertEquals(1,count($locks)); + + $this->assertEquals('Santa Clause',$locks[0]->owner); + $this->assertEquals('someuri',$locks[0]->uri); + + } + + /** + * @depends testGetLocks + */ + function testUnlock() { + + $backend = $this->getBackend(); + + $lock = new DAV\Locks\LockInfo(); + $lock->owner = 'Sinterklaas'; + $lock->timeout = 60; + $lock->created = time(); + $lock->token = 'MY-UNIQUE-TOKEN'; + + $this->assertTrue($backend->lock('someuri', $lock)); + + $locks = $backend->getLocks('someuri', false); + $this->assertEquals(1,count($locks)); + + $this->assertTrue($backend->unlock('someuri',$lock)); + + $locks = $backend->getLocks('someuri', false); + $this->assertEquals(0,count($locks)); + + } + + /** + * @depends testUnlock + */ + function testUnlockUnknownToken() { + + $backend = $this->getBackend(); + + $lock = new DAV\Locks\LockInfo(); + $lock->owner = 'Sinterklaas'; + $lock->timeout = 60; + $lock->created = time(); + $lock->token = 'MY-UNIQUE-TOKEN'; + + $this->assertTrue($backend->lock('someuri', $lock)); + + $locks = $backend->getLocks('someuri', false); + $this->assertEquals(1,count($locks)); + + $lock->token = 'SOME-OTHER-TOKEN'; + $this->assertFalse($backend->unlock('someuri',$lock)); + + $locks = $backend->getLocks('someuri', false); + $this->assertEquals(1,count($locks)); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/FileTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/FileTest.php new file mode 100644 index 0000000..537996f --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/FileTest.php @@ -0,0 +1,24 @@ +<?php + +namespace Sabre\DAV\Locks\Backend; + +require_once 'Sabre/TestUtil.php'; + +class FileTest extends AbstractTest { + + function getBackend() { + + \Sabre\TestUtil::clearTempDir(); + $backend = new File(SABRE_TEMPDIR . '/lockdb'); + return $backend; + + } + + + function tearDown() { + + \Sabre\TestUtil::clearTempDir(); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/Mock.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/Mock.php new file mode 100644 index 0000000..ac57144 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/Mock.php @@ -0,0 +1,139 @@ +<?php + +namespace Sabre\DAV\Locks\Backend; + +use Sabre\DAV\Locks\LockInfo; + +/** + * Locks Mock backend. + * + * This backend stores lock information in memory. Mainly useful for testing. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Mock extends AbstractBackend { + + /** + * 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; + + } + + protected $data = []; + + /** + * Loads the lockdata from the filesystem. + * + * @return array + */ + protected function getData() { + + return $this->data; + + } + + /** + * Saves the lockdata + * + * @param array $newData + * @return void + */ + protected function putData(array $newData) { + + $this->data = $newData; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/PDOMySQLTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/PDOMySQLTest.php new file mode 100644 index 0000000..b6f0622 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/PDOMySQLTest.php @@ -0,0 +1,32 @@ +<?php + +namespace Sabre\DAV\Locks\Backend; + +require_once 'Sabre/TestUtil.php'; + +class PDOMySQLTest extends AbstractTest { + + function getBackend() { + + if (!SABRE_HASMYSQL) $this->markTestSkipped('MySQL driver is not available, or it was not properly configured'); + $pdo = \Sabre\TestUtil::getMySQLDB(); + if (!$pdo) $this->markTestSkipped('Could not connect to MySQL database'); + $pdo->query('DROP TABLE IF EXISTS locks;'); + $pdo->query(" +CREATE TABLE locks ( + id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + owner VARCHAR(100), + timeout INTEGER UNSIGNED, + created INTEGER, + token VARCHAR(100), + scope TINYINT, + depth TINYINT, + uri text +);"); + + $backend = new PDO($pdo); + return $backend; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/PDOTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/PDOTest.php new file mode 100644 index 0000000..d6336c7 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Locks/Backend/PDOTest.php @@ -0,0 +1,29 @@ +<?php + +namespace Sabre\DAV\Locks\Backend; + +require_once 'Sabre/TestUtil.php'; +require_once 'Sabre/DAV/Locks/Backend/AbstractTest.php'; + +class PDOTest extends AbstractTest { + + function getBackend() { + + if (!SABRE_HASSQLITE) $this->markTestSkipped('SQLite driver is not available'); + \Sabre\TestUtil::clearTempDir(); + mkdir(SABRE_TEMPDIR . '/pdolocks'); + $pdo = new \PDO('sqlite:' . SABRE_TEMPDIR . '/pdolocks/db.sqlite'); + $pdo->setAttribute(\PDO::ATTR_ERRMODE,\PDO::ERRMODE_EXCEPTION); + $pdo->query('CREATE TABLE locks ( id integer primary key asc, owner text, timeout text, created integer, token text, scope integer, depth integer, uri text)'); + $backend = new PDO($pdo); + return $backend; + + } + + function tearDown() { + + \Sabre\TestUtil::clearTempDir(); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Locks/MSWordTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Locks/MSWordTest.php new file mode 100644 index 0000000..e9eeacb --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Locks/MSWordTest.php @@ -0,0 +1,124 @@ +<?php + +namespace Sabre\DAV\Locks; + +use Sabre\HTTP; +use Sabre\DAV; + +require_once 'Sabre/HTTP/ResponseMock.php'; +require_once 'Sabre/TestUtil.php'; + +class MSWordTest extends \PHPUnit_Framework_TestCase { + + function testLockEtc() { + + mkdir(SABRE_TEMPDIR . '/mstest'); + $tree = new DAV\FS\Directory(SABRE_TEMPDIR . '/mstest'); + + $server = new DAV\Server($tree); + $server->debugExceptions = true; + $locksBackend = new Backend\File(SABRE_TEMPDIR . '/locksdb'); + $locksPlugin = new Plugin($locksBackend); + $server->addPlugin($locksPlugin); + + $response1 = new HTTP\ResponseMock(); + + $server->httpRequest = $this->getLockRequest(); + $server->httpResponse = $response1; + $server->sapi = new HTTP\SapiMock(); + $server->exec(); + + $this->assertEquals(201, $server->httpResponse->getStatus(), 'Full response body:' . $response1->getBodyAsString()); + $this->assertTrue(!!$server->httpResponse->getHeaders('Lock-Token')); + $lockToken = $server->httpResponse->getHeader('Lock-Token'); + + //sleep(10); + + $response2 = new HTTP\ResponseMock(); + + $server->httpRequest = $this->getLockRequest2(); + $server->httpResponse = $response2; + $server->exec(); + + $this->assertEquals(201, $server->httpResponse->status); + $this->assertTrue(!!$server->httpResponse->getHeaders('Lock-Token')); + + //sleep(10); + + $response3 = new HTTP\ResponseMock(); + $server->httpRequest = $this->getPutRequest($lockToken); + $server->httpResponse = $response3; + $server->exec(); + + $this->assertEquals(204, $server->httpResponse->status); + + } + + function tearDown() { + + \Sabre\TestUtil::clearTempDir(); + + } + + function getLockRequest() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'LOCK', + 'HTTP_CONTENT_TYPE' => 'application/xml', + 'HTTP_TIMEOUT' => 'Second-3600', + 'REQUEST_URI' => '/Nouveau%20Microsoft%20Office%20Excel%20Worksheet.xlsx', + )); + + $request->setBody('<D:lockinfo xmlns:D="DAV:"> + <D:lockscope> + <D:exclusive /> + </D:lockscope> + <D:locktype> + <D:write /> + </D:locktype> + <D:owner> + <D:href>PC-Vista\User</D:href> + </D:owner> +</D:lockinfo>'); + + return $request; + + } + function getLockRequest2() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'LOCK', + 'HTTP_CONTENT_TYPE' => 'application/xml', + 'HTTP_TIMEOUT' => 'Second-3600', + 'REQUEST_URI' => '/~$Nouveau%20Microsoft%20Office%20Excel%20Worksheet.xlsx', + )); + + $request->setBody('<D:lockinfo xmlns:D="DAV:"> + <D:lockscope> + <D:exclusive /> + </D:lockscope> + <D:locktype> + <D:write /> + </D:locktype> + <D:owner> + <D:href>PC-Vista\User</D:href> + </D:owner> +</D:lockinfo>'); + + return $request; + + } + + function getPutRequest($lockToken) { + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/Nouveau%20Microsoft%20Office%20Excel%20Worksheet.xlsx', + 'HTTP_IF' => 'If: ('.$lockToken.')', + )); + $request->setBody('FAKE BODY'); + return $request; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Locks/Plugin2Test.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Locks/Plugin2Test.php new file mode 100644 index 0000000..7af4907 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Locks/Plugin2Test.php @@ -0,0 +1,69 @@ +<?php + +namespace Sabre\DAV\Locks; + +use Sabre\HTTP\Request; + +class Plugin2Test extends \Sabre\DAVServerTest { + + public $setupLocks = true; + + function setUpTree() { + + $this->tree = new \Sabre\DAV\FS\Directory(SABRE_TEMPDIR); + + } + + function tearDown() { + + \Sabre\TestUtil::clearTempDir(); + + } + + /** + * This test first creates a file with LOCK and then deletes it. + * + * After deleting the file, the lock should no longer be in the lock + * backend. + * + * Reported in ticket #487 + */ + function testUnlockAfterDelete() { + + $body = '<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> +</D:lockinfo>'; + + $request = new Request( + 'LOCK', + '/file.txt', + [], + $body + ); + $response = $this->request($request); + $this->assertEquals(201, $response->getStatus(), $response->getBodyAsString()); + + $this->assertEquals( + 1, + count($this->locksBackend->getLocks('file.txt', true)) + ); + + $request = new Request( + 'DELETE', + '/file.txt', + [ + 'If' => '(' . $response->getHeader('Lock-Token') . ')', + ] + ); + $response = $this->request($request); + $this->assertEquals(204, $response->getStatus(), $response->getBodyAsString()); + + $this->assertEquals( + 0, + count($this->locksBackend->getLocks('file.txt', true)) + ); + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Locks/PluginTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Locks/PluginTest.php new file mode 100644 index 0000000..89ff25f --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Locks/PluginTest.php @@ -0,0 +1,994 @@ +<?php + +namespace Sabre\DAV\Locks; + +use Sabre\HTTP; +use Sabre\DAV; + +require_once 'Sabre/DAV/AbstractServer.php'; + +class PluginTest extends DAV\AbstractServer { + + /** + * @var Sabre\DAV\Locks\Plugin + */ + protected $locksPlugin; + + function setUp() { + + parent::setUp(); + $locksBackend = new Backend\File(SABRE_TEMPDIR . '/locksdb'); + $locksPlugin = new Plugin($locksBackend); + $this->server->addPlugin($locksPlugin); + $this->locksPlugin = $locksPlugin; + + } + + function testGetFeatures() { + + $this->assertEquals(array(2),$this->locksPlugin->getFeatures()); + + } + + function testGetHTTPMethods() { + + $this->assertEquals(array('LOCK','UNLOCK'),$this->locksPlugin->getHTTPMethods('')); + + } + + function testLockNoBody() { + + $request = new HTTP\Request('LOCK', '/test.txt'); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(array( + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ), + $this->response->getHeaders() + ); + + $this->assertEquals(400, $this->response->status); + + } + + function testLock() { + + $request = new HTTP\Request('LOCK', '/test.txt'); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8',$this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/',$this->response->getHeader('Lock-Token'))===1,'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + $this->assertEquals(200, $this->response->status,'Got an incorrect status back. Response body: ' . $this->response->body); + + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"urn:DAV\"",$this->response->body); + $xml = simplexml_load_string($body); + $xml->registerXPathNamespace('d','urn:DAV'); + + $elements = array( + '/d:prop', + '/d:prop/d:lockdiscovery', + '/d:prop/d:lockdiscovery/d:activelock', + '/d:prop/d:lockdiscovery/d:activelock/d:locktype', + '/d:prop/d:lockdiscovery/d:activelock/d:lockroot', + '/d:prop/d:lockdiscovery/d:activelock/d:lockroot/d:href', + '/d:prop/d:lockdiscovery/d:activelock/d:locktype/d:write', + '/d:prop/d:lockdiscovery/d:activelock/d:lockscope', + '/d:prop/d:lockdiscovery/d:activelock/d:lockscope/d:exclusive', + '/d:prop/d:lockdiscovery/d:activelock/d:depth', + '/d:prop/d:lockdiscovery/d:activelock/d:owner', + '/d:prop/d:lockdiscovery/d:activelock/d:timeout', + '/d:prop/d:lockdiscovery/d:activelock/d:locktoken', + '/d:prop/d:lockdiscovery/d:activelock/d:locktoken/d:href', + ); + + foreach($elements as $elem) { + $data = $xml->xpath($elem); + $this->assertEquals(1,count($data),'We expected 1 match for the xpath expression "' . $elem . '". ' . count($data) . ' were found. Full response body: ' . $this->response->body); + } + + $depth = $xml->xpath('/d:prop/d:lockdiscovery/d:activelock/d:depth'); + $this->assertEquals('infinity',(string)$depth[0]); + + $token = $xml->xpath('/d:prop/d:lockdiscovery/d:activelock/d:locktoken/d:href'); + $this->assertEquals($this->response->getHeader('Lock-Token'),'<' . (string)$token[0] . '>','Token in response body didn\'t match token in response header.'); + + } + + /** + * @depends testLock + */ + function testDoubleLock() { + + $request = new HTTP\Request('LOCK', '/test.txt'); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->response = new HTTP\ResponseMock(); + $this->server->httpResponse = $this->response; + + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8',$this->response->getHeader('Content-Type')); + + $this->assertEquals(423, $this->response->status, 'Full response: ' . $this->response->body); + + } + + /** + * @depends testLock + */ + function testLockRefresh() { + + $request = new HTTP\Request('LOCK', '/test.txt'); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $lockToken = $this->response->getHeader('Lock-Token'); + + $this->response = new HTTP\ResponseMock(); + $this->server->httpResponse = $this->response; + + $request = new HTTP\Request('LOCK', '/test.txt', ['If' => '(' . $lockToken . ')' ]); + $request->setBody(''); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8',$this->response->getHeader('Content-Type')); + + $this->assertEquals(200, $this->response->status,'We received an incorrect status code. Full response body: ' . $this->response->getBody()); + + } + + /** + * @depends testLock + */ + function testLockRefreshBadToken() { + + $request = new HTTP\Request('LOCK', '/test.txt'); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $lockToken = $this->response->getHeader('Lock-Token'); + + $this->response = new HTTP\ResponseMock(); + $this->server->httpResponse = $this->response; + + $request = new HTTP\Request('LOCK', '/test.txt', ['If' => '(' . $lockToken . 'foobar) (<opaquelocktoken:anotherbadtoken>)' ]); + $request->setBody(''); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8',$this->response->getHeader('Content-Type')); + + $this->assertEquals(423, $this->response->getStatus(),'We received an incorrect status code. Full response body: ' . $this->response->getBody()); + + } + + /** + * @depends testLock + */ + function testLockNoFile() { + + $request = new HTTP\Request('LOCK', '/notfound.txt'); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8',$this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/',$this->response->getHeader('Lock-Token'))===1,'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + $this->assertEquals(201, $this->response->status); + + } + + /** + * @depends testLock + */ + function testUnlockNoToken() { + + $request = new HTTP\Request('UNLOCK', '/test.txt'); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ], + $this->response->getHeaders() + ); + + $this->assertEquals(400, $this->response->status); + + } + + /** + * @depends testLock + */ + function testUnlockBadToken() { + + $request = new HTTP\Request('UNLOCK', '/test.txt', ['Lock-Token' => '<opaquelocktoken:blablabla>']); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ], + $this->response->getHeaders() + ); + + $this->assertEquals(409, $this->response->status, 'Got an incorrect status code. Full response body: ' . $this->response->body); + + } + + /** + * @depends testLock + */ + function testLockPutNoToken() { + + $request = new HTTP\Request('LOCK', '/test.txt'); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8',$this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/',$this->response->getHeader('Lock-Token'))===1,'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + $this->assertEquals(200, $this->response->status); + + $request = new HTTP\Request('PUT', '/test.txt'); + $request->setBody('newbody'); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8',$this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/',$this->response->getHeader('Lock-Token'))===1,'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + $this->assertEquals(423, $this->response->status); + + } + + /** + * @depends testLock + */ + function testUnlock() { + + $request = new HTTP\Request('LOCK', '/test.txt'); + $this->server->httpRequest = $request; + + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->invokeMethod($request, $this->server->httpResponse); + $lockToken = $this->server->httpResponse->getHeader('Lock-Token'); + + $request = new HTTP\Request('UNLOCK', '/test.txt', ['Lock-Token' => $lockToken]); + $this->server->httpRequest = $request; + $this->server->httpResponse = new HTTP\ResponseMock(); + $this->server->invokeMethod($request, $this->server->httpResponse); + + $this->assertEquals(204,$this->server->httpResponse->status,'Got an incorrect status code. Full response body: ' . $this->response->body); + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Length' => ['0'], + ], + $this->server->httpResponse->getHeaders() + ); + + + } + + /** + * @depends testLock + */ + function testUnlockWindowsBug() { + + $request = new HTTP\Request('LOCK', '/test.txt'); + $this->server->httpRequest = $request; + + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->invokeMethod($request, $this->server->httpResponse); + $lockToken = $this->server->httpResponse->getHeader('Lock-Token'); + + // See Issue 123 + $lockToken = trim($lockToken,'<>'); + + $request = new HTTP\Request('UNLOCK', '/test.txt', ['Lock-Token' => $lockToken]); + $this->server->httpRequest = $request; + $this->server->httpResponse = new HTTP\ResponseMock(); + $this->server->invokeMethod($request, $this->server->httpResponse); + + $this->assertEquals(204, $this->server->httpResponse->status,'Got an incorrect status code. Full response body: ' . $this->response->body); + $this->assertEquals([ + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Length' => ['0'], + ], + $this->server->httpResponse->getHeaders() + ); + + + } + + /** + * @depends testLock + */ + function testLockRetainOwner() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_URI' => '/test.txt', + 'REQUEST_METHOD' => 'LOCK', + ]); + $this->server->httpRequest = $request; + + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner>Evert</D:owner> +</D:lockinfo>'); + + $this->server->invokeMethod($request, $this->server->httpResponse); + $lockToken = $this->server->httpResponse->getHeader('Lock-Token'); + + $locks = $this->locksPlugin->getLocks('test.txt'); + $this->assertEquals(1,count($locks)); + $this->assertEquals('Evert',$locks[0]->owner); + + + } + + /** + * @depends testLock + */ + function testLockPutBadToken() { + + $serverVars = array( + 'REQUEST_URI' => '/test.txt', + 'REQUEST_METHOD' => 'LOCK', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8',$this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/',$this->response->getHeader('Lock-Token'))===1,'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + $this->assertEquals(200, $this->response->status); + + $serverVars = array( + 'REQUEST_URI' => '/test.txt', + 'REQUEST_METHOD' => 'PUT', + 'HTTP_IF' => '(<opaquelocktoken:token1>)', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('newbody'); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8',$this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/',$this->response->getHeader('Lock-Token'))===1,'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + // $this->assertEquals('412 Precondition failed',$this->response->status); + $this->assertEquals(423, $this->response->status); + + } + + /** + * @depends testLock + */ + function testLockDeleteParent() { + + $serverVars = array( + 'REQUEST_URI' => '/dir/child.txt', + 'REQUEST_METHOD' => 'LOCK', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8',$this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/',$this->response->getHeader('Lock-Token'))===1,'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + $this->assertEquals(200, $this->response->status); + + $serverVars = array( + 'REQUEST_URI' => '/dir', + 'REQUEST_METHOD' => 'DELETE', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(423, $this->response->status); + $this->assertEquals('application/xml; charset=utf-8',$this->response->getHeader('Content-Type')); + + } + /** + * @depends testLock + */ + function testLockDeleteSucceed() { + + $serverVars = array( + 'REQUEST_URI' => '/dir/child.txt', + 'REQUEST_METHOD' => 'LOCK', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8',$this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/',$this->response->getHeader('Lock-Token'))===1,'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + $this->assertEquals(200, $this->response->status); + + $serverVars = array( + 'REQUEST_URI' => '/dir/child.txt', + 'REQUEST_METHOD' => 'DELETE', + 'HTTP_IF' => '(' . $this->response->getHeader('Lock-Token') . ')', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(204, $this->response->status); + $this->assertEquals('application/xml; charset=utf-8',$this->response->getHeader('Content-Type')); + + } + + /** + * @depends testLock + */ + function testLockCopyLockSource() { + + $serverVars = array( + 'REQUEST_URI' => '/dir/child.txt', + 'REQUEST_METHOD' => 'LOCK', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8',$this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/',$this->response->getHeader('Lock-Token'))===1,'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + $this->assertEquals(200, $this->response->status); + + $serverVars = array( + 'REQUEST_URI' => '/dir/child.txt', + 'REQUEST_METHOD' => 'COPY', + 'HTTP_DESTINATION' => '/dir/child2.txt', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(201, $this->response->status,'Copy must succeed if only the source is locked, but not the destination'); + $this->assertEquals('application/xml; charset=utf-8',$this->response->getHeader('Content-Type')); + + } + /** + * @depends testLock + */ + function testLockCopyLockDestination() { + + $serverVars = array( + 'REQUEST_URI' => '/dir/child2.txt', + 'REQUEST_METHOD' => 'LOCK', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8',$this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/',$this->response->getHeader('Lock-Token'))===1,'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + $this->assertEquals(201, $this->response->status); + + $serverVars = array( + 'REQUEST_URI' => '/dir/child.txt', + 'REQUEST_METHOD' => 'COPY', + 'HTTP_DESTINATION' => '/dir/child2.txt', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(423, $this->response->status,'Copy must succeed if only the source is locked, but not the destination'); + $this->assertEquals('application/xml; charset=utf-8',$this->response->getHeader('Content-Type')); + + } + + /** + * @depends testLock + */ + function testLockMoveLockSourceLocked() { + + $serverVars = array( + 'REQUEST_URI' => '/dir/child.txt', + 'REQUEST_METHOD' => 'LOCK', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8',$this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/',$this->response->getHeader('Lock-Token'))===1,'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + $this->assertEquals(200, $this->response->status); + + $serverVars = array( + 'REQUEST_URI' => '/dir/child.txt', + 'REQUEST_METHOD' => 'MOVE', + 'HTTP_DESTINATION' => '/dir/child2.txt', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(423, $this->response->status,'Copy must succeed if only the source is locked, but not the destination'); + $this->assertEquals('application/xml; charset=utf-8',$this->response->getHeader('Content-Type')); + + } + + /** + * @depends testLock + */ + function testLockMoveLockSourceSucceed() { + + $serverVars = array( + 'REQUEST_URI' => '/dir/child.txt', + 'REQUEST_METHOD' => 'LOCK', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8',$this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/',$this->response->getHeader('Lock-Token'))===1,'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + $this->assertEquals(200, $this->response->status); + + $serverVars = array( + 'REQUEST_URI' => '/dir/child.txt', + 'REQUEST_METHOD' => 'MOVE', + 'HTTP_DESTINATION' => '/dir/child2.txt', + 'HTTP_IF' => '(' . $this->response->getHeader('Lock-Token') . ')', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(201, $this->response->status,'A valid lock-token was provided for the source, so this MOVE operation must succeed. Full response body: ' . $this->response->body); + + } + + /** + * @depends testLock + */ + function testLockMoveLockDestination() { + + $serverVars = array( + 'REQUEST_URI' => '/dir/child2.txt', + 'REQUEST_METHOD' => 'LOCK', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8',$this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/',$this->response->getHeader('Lock-Token'))===1,'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + $this->assertEquals(201, $this->response->status); + + $serverVars = array( + 'REQUEST_URI' => '/dir/child.txt', + 'REQUEST_METHOD' => 'MOVE', + 'HTTP_DESTINATION' => '/dir/child2.txt', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(423, $this->response->status,'Copy must succeed if only the source is locked, but not the destination'); + $this->assertEquals('application/xml; charset=utf-8',$this->response->getHeader('Content-Type')); + + } + /** + * @depends testLock + */ + function testLockMoveLockParent() { + + $serverVars = array( + 'REQUEST_URI' => '/dir', + 'REQUEST_METHOD' => 'LOCK', + 'HTTP_DEPTH' => 'infinite', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8',$this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/',$this->response->getHeader('Lock-Token'))===1,'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + $this->assertEquals(200,$this->response->status); + + $serverVars = array( + 'REQUEST_URI' => '/dir/child.txt', + 'REQUEST_METHOD' => 'MOVE', + 'HTTP_DESTINATION' => '/dir/child2.txt', + 'HTTP_IF' => '</dir> (' . $this->response->getHeader('Lock-Token') . ')', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(201, $this->response->status,'We locked the parent of both the source and destination, but the move didn\'t succeed.'); + $this->assertEquals('application/xml; charset=utf-8',$this->response->getHeader('Content-Type')); + + } + + /** + * @depends testLock + */ + function testLockPutGoodToken() { + + $serverVars = array( + 'REQUEST_URI' => '/test.txt', + 'REQUEST_METHOD' => 'LOCK', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8',$this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/',$this->response->getHeader('Lock-Token'))===1,'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + $this->assertEquals(200, $this->response->status); + + $serverVars = array( + 'REQUEST_URI' => '/test.txt', + 'REQUEST_METHOD' => 'PUT', + 'HTTP_IF' => '('.$this->response->getHeader('Lock-Token').')', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('newbody'); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8',$this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/',$this->response->getHeader('Lock-Token'))===1,'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + $this->assertEquals(204, $this->response->status); + + } + + /** + * @depends testLock + */ + function testLockPutUnrelatedToken() { + + $request = new HTTP\Request('LOCK', '/unrelated.txt'); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8',$this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/',$this->response->getHeader('Lock-Token'))===1,'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + $this->assertEquals(201, $this->response->getStatus()); + + $request = new HTTP\Request( + 'PUT', + '/test.txt', + ['If' => '</unrelated.txt> ('.$this->response->getHeader('Lock-Token').')'] + ); + $request->setBody('newbody'); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('application/xml; charset=utf-8',$this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/',$this->response->getHeader('Lock-Token'))===1,'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + + $this->assertEquals(204, $this->response->status); + + } + + function testPutWithIncorrectETag() { + + $serverVars = array( + 'REQUEST_URI' => '/test.txt', + 'REQUEST_METHOD' => 'PUT', + 'HTTP_IF' => '(["etag1"])', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('newbody'); + $this->server->httpRequest = $request; + $this->server->exec(); + $this->assertEquals(412, $this->response->status); + + } + + /** + * @depends testPutWithIncorrectETag + */ + function testPutWithCorrectETag() { + + // We need an ETag-enabled file node. + $tree = new DAV\Tree(new DAV\FSExt\Directory(SABRE_TEMPDIR)); + $this->server->tree = $tree; + + $filename = SABRE_TEMPDIR . '/test.txt'; + $etag = sha1( + fileinode($filename) . + filesize($filename ) . + filemtime($filename) + ); + $serverVars = array( + 'REQUEST_URI' => '/test.txt', + 'REQUEST_METHOD' => 'PUT', + 'HTTP_IF' => '(["'.$etag.'"])', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('newbody'); + $this->server->httpRequest = $request; + $this->server->exec(); + $this->assertEquals(204, $this->response->status, 'Incorrect status received. Full response body:' . $this->response->body); + + } + + function testDeleteWithETagOnCollection() { + + $serverVars = array( + 'REQUEST_URI' => '/dir', + 'REQUEST_METHOD' => 'DELETE', + 'HTTP_IF' => '(["etag1"])', + ); + $request = HTTP\Sapi::createFromServerArray($serverVars); + + $this->server->httpRequest = $request; + $this->server->exec(); + $this->assertEquals(412, $this->response->status); + + } + + function testGetTimeoutHeader() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'HTTP_TIMEOUT' => 'second-100', + )); + + $this->server->httpRequest = $request; + $this->assertEquals(100, $this->locksPlugin->getTimeoutHeader()); + + } + + function testGetTimeoutHeaderTwoItems() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'HTTP_TIMEOUT' => 'second-5, infinite', + )); + + $this->server->httpRequest = $request; + $this->assertEquals(5, $this->locksPlugin->getTimeoutHeader()); + + } + + function testGetTimeoutHeaderInfinite() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'HTTP_TIMEOUT' => 'infinite, second-5', + )); + + $this->server->httpRequest = $request; + $this->assertEquals(LockInfo::TIMEOUT_INFINITE, $this->locksPlugin->getTimeoutHeader()); + + } + + /** + * @expectedException Sabre\DAV\Exception\BadRequest + */ + function testGetTimeoutHeaderInvalid() { + + $request = HTTP\Sapi::createFromServerArray(array( + 'HTTP_TIMEOUT' => 'yourmom', + )); + + $this->server->httpRequest = $request; + $this->locksPlugin->getTimeoutHeader(); + + } + + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Mock/Collection.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Mock/Collection.php new file mode 100644 index 0000000..09a5e0e --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Mock/Collection.php @@ -0,0 +1,156 @@ +<?php + +namespace Sabre\DAV\Mock; + +use Sabre\DAV; + +/** + * Mock Collection. + * + * This collection quickly allows you to create trees of nodes. + * Children are specified as an array. + * + * Every key a filename, every array value is either: + * * an array, for a sub-collection + * * a string, for a file + * * An instance of \Sabre\DAV\INode. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Collection extends DAV\Collection { + + protected $name; + protected $children; + protected $parent; + + /** + * Creates the object + * + * @param string $name + * @param array $children + * @return void + */ + function __construct($name, array $children = [], Collection $parent = null) { + + $this->name = $name; + foreach ($children as $key => $value) { + if (is_string($value)) { + $this->children[] = new File($key, $value, $this); + } elseif (is_array($value)) { + $this->children[] = new self($key, $value, $this); + } elseif ($value instanceof \Sabre\DAV\INode) { + $this->children[] = $value; + } else { + throw new \InvalidArgumentException('Unknown value passed in $children'); + } + } + $this->parent = $parent; + + } + + /** + * Returns the name of the node. + * + * This is used to generate the url. + * + * @return string + */ + function getName() { + + return $this->name; + + } + + /** + * 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 successful 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) { + + if (is_resource($data)) { + $data = stream_get_contents($data); + } + $this->children[] = new File($name, $data, $this); + return '"' . md5($data) . '"'; + + } + + /** + * Creates a new subdirectory + * + * @param string $name + * @return void + */ + function createDirectory($name) { + + $this->children[] = new self($name); + + } + + /** + * Returns an array with all the child nodes + * + * @return \Sabre\DAV\INode[] + */ + function getChildren() { + + return $this->children; + + } + + /** + * Removes a childnode from this node. + * + * @param string $name + * @return void + */ + function deleteChild($name) { + + foreach ($this->children as $key => $value) { + + if ($value->getName() == $name) { + unset($this->children[$key]); + return; + } + + } + + } + + /** + * Deletes this collection and all its children,. + * + * @return void + */ + function delete() { + + foreach ($this->getChildren() as $child) { + $this->deleteChild($child->getName()); + } + $this->parent->deleteChild($this->getName()); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Mock/File.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Mock/File.php new file mode 100644 index 0000000..bde9560 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Mock/File.php @@ -0,0 +1,141 @@ +<?php + +namespace Sabre\DAV\Mock; + +use Sabre\DAV; + +/** + * Mock File + * + * See the Collection in this directory for more details. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class File extends DAV\File { + + protected $name; + protected $contents; + protected $parent; + + /** + * Creates the object + * + * @param string $name + * @param array $children + * @return void + */ + function __construct($name, $contents, Collection $parent = null) { + + $this->name = $name; + $this->put($contents); + $this->parent = $parent; + + } + + /** + * Returns the name of the node. + * + * This is used to generate the url. + * + * @return string + */ + function getName() { + + return $this->name; + + } + + /** + * Changes the name of the node. + * + * @return void + */ + function setName($name) { + + $this->name = $name; + + } + + /** + * Updates the data + * + * The data argument is a readable stream resource. + * + * After a succesful 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. + * + * If you don't plan to store the file byte-by-byte, and you return a + * different object on a subsequent GET you are strongly recommended to not + * return an ETag, and just return null. + * + * @param resource $data + * @return string|null + */ + function put($data) { + + if (is_resource($data)) { + $data = stream_get_contents($data); + } + $this->contents = $data; + return '"' . md5($data) . '"'; + + } + + /** + * Returns the data + * + * This method may either return a string or a readable stream resource + * + * @return mixed + */ + function get() { + + return $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. + * + * Return null if the ETag can not effectively be determined + * + * @return void + */ + function getETag() { + + return '"' . md5($this->contents) . '"'; + + } + + /** + * Returns the size of the node, in bytes + * + * @return int + */ + function getSize() { + + return strlen($this->contents); + + } + + /** + * Delete the node + * + * @return void + */ + function delete() { + + $this->parent->deleteChild($this->name); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Mock/PropertiesCollection.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Mock/PropertiesCollection.php new file mode 100644 index 0000000..d5a192b --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Mock/PropertiesCollection.php @@ -0,0 +1,94 @@ +<?php + +namespace Sabre\DAV\Mock; + +use Sabre\DAV\IProperties; +use Sabre\DAV\PropPatch; + +/** + * A node specifically for testing property-related operations + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class PropertiesCollection extends Collection implements IProperties { + + public $failMode = false; + + public $properties; + + /** + * Creates the object + * + * @param string $name + * @param array $children + * @param array $properties + * @return void + */ + function __construct($name, array $children, array $properties = []) { + + parent::__construct($name, $children, null); + $this->properties = $properties; + + } + + /** + * 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 array $mutations + * @return bool|array + */ + function propPatch(PropPatch $proppatch) { + + $proppatch->handleRemaining(function($updateProperties) { + + switch ($this->failMode) { + case 'updatepropsfalse' : return false; + case 'updatepropsarray' : + $r = []; + foreach ($updateProperties as $k => $v) $r[$k] = 402; + return $r; + case 'updatepropsobj' : + return new \STDClass(); + } + + }); + + } + + /** + * 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 array + */ + function getProperties($requestedProperties) { + + $returnedProperties = []; + foreach ($requestedProperties as $requestedProperty) { + if (isset($this->properties[$requestedProperty])) { + $returnedProperties[$requestedProperty] = + $this->properties[$requestedProperty]; + } + } + return $returnedProperties; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Mock/StreamingFile.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Mock/StreamingFile.php new file mode 100644 index 0000000..ef2016d --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Mock/StreamingFile.php @@ -0,0 +1,88 @@ +<?php + +namespace Sabre\DAV\Mock; + +/** + * Mock Streaming File File + * + * Works similar to the mock file, but this one works with streams and has no + * content-length or etags. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class StreamingFile extends File { + + /** + * Updates the data + * + * The data argument is a readable stream resource. + * + * After a succesful 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. + * + * If you don't plan to store the file byte-by-byte, and you return a + * different object on a subsequent GET you are strongly recommended to not + * return an ETag, and just return null. + * + * @param resource $data + * @return string|null + */ + function put($data) { + + if (is_string($data)) { + $stream = fopen('php://memory', 'r+'); + fwrite($stream, $data); + rewind($stream); + $data = $stream; + } + $this->contents = $data; + + } + + /** + * Returns the data + * + * This method may either return a string or a readable stream resource + * + * @return mixed + */ + function get() { + + return $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. + * + * Return null if the ETag can not effectively be determined + * + * @return void + */ + function getETag() { + + return null; + + } + + /** + * Returns the size of the node, in bytes + * + * @return int + */ + function getSize() { + + return null; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Mount/PluginTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Mount/PluginTest.php new file mode 100644 index 0000000..e641579 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Mount/PluginTest.php @@ -0,0 +1,58 @@ +<?php + +namespace Sabre\DAV\Mount; + +use Sabre\DAV; +use Sabre\HTTP; + +require_once 'Sabre/DAV/AbstractServer.php'; + +class PluginTest extends DAV\AbstractServer { + + function setUp() { + + parent::setUp(); + $this->server->addPlugin(new Plugin()); + + } + + function testPassThrough() { + + $serverVars = array( + 'REQUEST_URI' => '/', + 'REQUEST_METHOD' => 'GET', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(501, $this->response->status,'We expected GET to not be implemented for Directories. Response body: ' . $this->response->body); + + } + + function testMountResponse() { + + $serverVars = array( + 'REQUEST_URI' => '/?mount', + 'REQUEST_METHOD' => 'GET', + 'QUERY_STRING' => 'mount', + 'HTTP_HOST' => 'example.org', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(200, $this->response->status); + + $xml = simplexml_load_string($this->response->body); + $this->assertInstanceOf('SimpleXMLElement',$xml, 'Response was not a valid xml document. The list of errors:' . print_r(libxml_get_errors(),true) . '. xml body: ' . $this->response->body . '. What type we got: ' . gettype($xml) . ' class, if object: ' . get_class($xml)); + + $xml->registerXPathNamespace('dm','http://purl.org/NET/webdav/mount'); + $url = $xml->xpath('//dm:url'); + $this->assertEquals('http://example.org/',(string)$url[0]); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ObjectTreeTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ObjectTreeTest.php new file mode 100644 index 0000000..9b7eeb9 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ObjectTreeTest.php @@ -0,0 +1,100 @@ +<?php + +namespace Sabre\DAV; + +require_once 'Sabre/TestUtil.php'; + +class ObjectTreeTest extends \PHPUnit_Framework_TestCase { + + protected $tree; + + function setup() { + + \Sabre\TestUtil::clearTempDir(); + mkdir(SABRE_TEMPDIR . '/root'); + mkdir(SABRE_TEMPDIR . '/root/subdir'); + file_put_contents(SABRE_TEMPDIR . '/root/file.txt','contents'); + file_put_contents(SABRE_TEMPDIR . '/root/subdir/subfile.txt','subcontents'); + $rootNode = new FSExt\Directory(SABRE_TEMPDIR . '/root'); + $this->tree = new Tree($rootNode); + + } + + function teardown() { + + \Sabre\TestUtil::clearTempDir(); + + } + + function testGetRootNode() { + + $root = $this->tree->getNodeForPath(''); + $this->assertInstanceOf('Sabre\\DAV\\FSExt\\Directory',$root); + + } + + function testGetSubDir() { + + $root = $this->tree->getNodeForPath('subdir'); + $this->assertInstanceOf('Sabre\\DAV\\FSExt\\Directory',$root); + + } + + function testCopyFile() { + + $this->tree->copy('file.txt','file2.txt'); + $this->assertTrue(file_exists(SABRE_TEMPDIR.'/root/file2.txt')); + $this->assertEquals('contents',file_get_contents(SABRE_TEMPDIR.'/root/file2.txt')); + + } + + /** + * @depends testCopyFile + */ + function testCopyDirectory() { + + $this->tree->copy('subdir','subdir2'); + $this->assertTrue(file_exists(SABRE_TEMPDIR.'/root/subdir2')); + $this->assertTrue(file_exists(SABRE_TEMPDIR.'/root/subdir2/subfile.txt')); + $this->assertEquals('subcontents',file_get_contents(SABRE_TEMPDIR.'/root/subdir2/subfile.txt')); + + } + + /** + * @depends testCopyFile + */ + function testMoveFile() { + + $this->tree->move('file.txt','file2.txt'); + $this->assertTrue(file_exists(SABRE_TEMPDIR.'/root/file2.txt')); + $this->assertFalse(file_exists(SABRE_TEMPDIR.'/root/file.txt')); + $this->assertEquals('contents',file_get_contents(SABRE_TEMPDIR.'/root/file2.txt')); + + } + + /** + * @depends testMoveFile + */ + function testMoveFileNewParent() { + + $this->tree->move('file.txt','subdir/file2.txt'); + $this->assertTrue(file_exists(SABRE_TEMPDIR.'/root/subdir/file2.txt')); + $this->assertFalse(file_exists(SABRE_TEMPDIR.'/root/file.txt')); + $this->assertEquals('contents',file_get_contents(SABRE_TEMPDIR.'/root/subdir/file2.txt')); + + } + + /** + * @depends testCopyDirectory + */ + function testMoveDirectory() { + + $this->tree->move('subdir','subdir2'); + $this->assertTrue(file_exists(SABRE_TEMPDIR.'/root/subdir2')); + $this->assertTrue(file_exists(SABRE_TEMPDIR.'/root/subdir2/subfile.txt')); + $this->assertFalse(file_exists(SABRE_TEMPDIR.'/root/subdir')); + $this->assertEquals('subcontents',file_get_contents(SABRE_TEMPDIR.'/root/subdir2/subfile.txt')); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PartialUpdate/FileMock.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PartialUpdate/FileMock.php new file mode 100644 index 0000000..d6cc406 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PartialUpdate/FileMock.php @@ -0,0 +1,121 @@ +<?php + +namespace Sabre\DAV\PartialUpdate; +use Sabre\DAV; + +class FileMock implements IPatchSupport { + + protected $data = ''; + + function put($str) { + + if (is_resource($str)) { + $str = stream_get_contents($str); + } + $this->data = $str; + + } + + /** + * 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) { + + if (is_resource($data)) { + $data = stream_get_contents($data); + } + + switch($rangeType) { + + case 1 : + $this->data.=$data; + break; + case 3 : + // Turn the offset into an offset-offset. + $offset = strlen($this->data) - $offset; + // No break is intentional + case 2 : + $this->data = + substr($this->data, 0, $offset) . + $data . + substr($this->data, $offset + strlen($data)); + break; + + } + + } + + function get() { + + return $this->data; + + } + + function getContentType() { + + return 'text/plain'; + + } + + function getSize() { + + return strlen($this->data); + + } + + function getETag() { + + return '"' . $this->data . '"'; + + } + + function delete() { + + throw new DAV\Exception\MethodNotAllowed(); + + } + + function setName($name) { + + throw new DAV\Exception\MethodNotAllowed(); + + } + + function getName() { + + return 'partial'; + + } + + function getLastModified() { + + return null; + + } + + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PartialUpdate/PluginTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PartialUpdate/PluginTest.php new file mode 100644 index 0000000..6068c73 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PartialUpdate/PluginTest.php @@ -0,0 +1,135 @@ +<?php + +namespace Sabre\DAV\PartialUpdate; + +use Sabre\DAV; +use Sabre\HTTP; + +require_once 'Sabre/DAV/PartialUpdate/FileMock.php'; + +class PluginTest extends \Sabre\DAVServerTest { + + protected $node; + protected $plugin; + + function setUp() { + + $this->node = new FileMock(); + $this->tree[] = $this->node; + + parent::setUp(); + + $this->plugin = new Plugin(); + $this->server->addPlugin($this->plugin); + + + + } + + function testInit() { + + $this->assertEquals('partialupdate', $this->plugin->getPluginName()); + $this->assertEquals(['sabredav-partialupdate'], $this->plugin->getFeatures()); + $this->assertEquals([ + 'PATCH' + ], $this->plugin->getHTTPMethods('partial')); + $this->assertEquals([ + ], $this->plugin->getHTTPMethods('')); + + } + + function testPatchNoRange() { + + $this->node->put('00000000'); + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'PATCH', + 'REQUEST_URI' => '/partial', + ]); + $response = $this->request($request); + + $this->assertEquals(400, $response->status, 'Full response body:' . $response->body); + + } + + function testPatchNotSupported() { + + $this->node->put('00000000'); + $request = new HTTP\Request('PATCH', '/', ['X-Update-Range' => '3-4']); + $request->setBody( + '111' + ); + $response = $this->request($request); + + $this->assertEquals(405, $response->status, 'Full response body:' . $response->body); + + } + + function testPatchNoContentType() { + + $this->node->put('00000000'); + $request = new HTTP\Request('PATCH', '/partial', ['X-Update-Range' => 'bytes=3-4']); + $request->setBody( + '111' + ); + $response = $this->request($request); + + $this->assertEquals(415, $response->status, 'Full response body:' . $response->body); + + } + + function testPatchBadRange() { + + $this->node->put('00000000'); + $request = new HTTP\Request('PATCH', '/partial', ['X-Update-Range' => 'bytes=3-4', 'Content-Type' => 'application/x-sabredav-partialupdate', 'Content-Length' => '3']); + $request->setBody( + '111' + ); + $response = $this->request($request); + + $this->assertEquals(416, $response->status, 'Full response body:' . $response->body); + + } + + function testPatchNoLength() { + + $this->node->put('00000000'); + $request = new HTTP\Request('PATCH', '/partial', ['X-Update-Range' => 'bytes=3-5', 'Content-Type' => 'application/x-sabredav-partialupdate']); + $request->setBody( + '111' + ); + $response = $this->request($request); + + $this->assertEquals(411, $response->status, 'Full response body:' . $response->body); + + } + + function testPatchSuccess() { + + $this->node->put('00000000'); + $request = new HTTP\Request('PATCH', '/partial', ['X-Update-Range' => 'bytes=3-5', 'Content-Type' => 'application/x-sabredav-partialupdate', 'Content-Length' => 3]); + $request->setBody( + '111' + ); + $response = $this->request($request); + + $this->assertEquals(204, $response->status, 'Full response body:' . $response->body); + $this->assertEquals('00011100', $this->node->get()); + + } + + function testPatchNoEndRange() { + + $this->node->put('00000'); + $request = new HTTP\Request('PATCH', '/partial', ['X-Update-Range' => 'bytes=3-', 'Content-Type' => 'application/x-sabredav-partialupdate', 'Content-Length' => '3']); + $request->setBody( + '111' + ); + + $response = $this->request($request); + + $this->assertEquals(204, $response->getStatus(), 'Full response body:' . $response->getBodyAsString()); + $this->assertEquals('00111', $this->node->get()); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PartialUpdate/SpecificationTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PartialUpdate/SpecificationTest.php new file mode 100644 index 0000000..31be2a1 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PartialUpdate/SpecificationTest.php @@ -0,0 +1,89 @@ +<?php + +namespace Sabre\DAV\PartialUpdate; + +use Sabre\DAV\FSExt\File; +use Sabre\DAV\Server; +use Sabre\HTTP; + +/** + * This test is an end-to-end sabredav test that goes through all + * the cases in the specification. + * + * See: http://sabre.io/dav/http-patch/ + */ +class SpecificationTest extends \PHPUnit_Framework_TestCase { + + protected $server; + + public function setUp() { + + $tree = array( + new File(SABRE_TEMPDIR . '/foobar.txt') + ); + $server = new Server($tree); + $server->debugExceptions = true; + $server->addPlugin(new Plugin()); + + $tree[0]->put('1234567890'); + + $this->server = $server; + + } + + public function tearDown() { + + \Sabre\TestUtil::clearTempDir(); + + } + + /** + * @dataProvider data + */ + public function testUpdateRange($headerValue, $httpStatus, $endResult, $contentLength = 4) { + + $headers = [ + 'Content-Type' => 'application/x-sabredav-partialupdate', + 'X-Update-Range' => $headerValue, + ]; + + if ($contentLength) { + $headers['Content-Length'] = (string)$contentLength; + } + + $request = new HTTP\Request('PATCH', '/foobar.txt', $headers, '----'); + + $request->setBody('----'); + $this->server->httpRequest = $request; + $this->server->httpResponse = new HTTP\ResponseMock(); + $this->server->sapi = new HTTP\SapiMock(); + $this->server->exec(); + + $this->assertEquals($httpStatus, $this->server->httpResponse->status, 'Incorrect http status received: ' . $this->server->httpResponse->body); + if (!is_null($endResult)) { + $this->assertEquals($endResult, file_get_contents(SABRE_TEMPDIR . '/foobar.txt')); + } + + } + + public function data() { + + return array( + // Problems + array('foo', 400, null), + array('bytes=0-3', 411, null, 0), + array('bytes=4-1', 416, null), + + array('bytes=0-3', 204, '----567890'), + array('bytes=1-4', 204, '1----67890'), + array('bytes=0-', 204, '----567890'), + array('bytes=-4', 204, '123456----'), + array('bytes=-2', 204, '12345678----'), + array('bytes=2-', 204, '12----7890'), + array('append', 204, '1234567890----'), + + ); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PropFindTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PropFindTest.php new file mode 100644 index 0000000..ec1d616 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PropFindTest.php @@ -0,0 +1,76 @@ +<?php + +namespace Sabre\DAV; + +class PropFindTest extends \PHPUnit_Framework_TestCase { + + function testHandle() { + + $propFind = new PropFind('foo', ['{DAV:}displayname']); + $propFind->handle('{DAV:}displayname', 'foobar'); + + $this->assertEquals([ + 200 => ['{DAV:}displayname' => 'foobar'], + 404 => [], + ], $propFind->getResultForMultiStatus()); + + } + + function testHandleCallBack() { + + $propFind = new PropFind('foo', ['{DAV:}displayname']); + $propFind->handle('{DAV:}displayname', function() { return 'foobar'; }); + + $this->assertEquals([ + 200 => ['{DAV:}displayname' => 'foobar'], + 404 => [], + ], $propFind->getResultForMultiStatus()); + + } + + function testAllPropDefaults() { + + $propFind = new PropFind('foo', ['{DAV:}displayname'], 0, PropFind::ALLPROPS); + + $this->assertEquals([ + 200 => [], + ], $propFind->getResultForMultiStatus()); + + } + + function testSet() { + + $propFind = new PropFind('foo', ['{DAV:}displayname']); + $propFind->set('{DAV:}displayname', 'bar'); + + $this->assertEquals([ + 200 => ['{DAV:}displayname' => 'bar'], + 404 => [], + ], $propFind->getResultForMultiStatus()); + + } + + function testSetAllpropCustom() { + + $propFind = new PropFind('foo', ['{DAV:}displayname'], 0, PropFind::ALLPROPS); + $propFind->set('{DAV:}customproperty', 'bar'); + + $this->assertEquals([ + 200 => ['{DAV:}customproperty' => 'bar'], + ], $propFind->getResultForMultiStatus()); + + } + + function testSetUnset() { + + $propFind = new PropFind('foo', ['{DAV:}displayname']); + $propFind->set('{DAV:}displayname', 'bar'); + $propFind->set('{DAV:}displayname', null); + + $this->assertEquals([ + 200 => [], + 404 => ['{DAV:}displayname' => null], + ], $propFind->getResultForMultiStatus()); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PropPatchTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PropPatchTest.php new file mode 100644 index 0000000..21b45e7 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PropPatchTest.php @@ -0,0 +1,357 @@ +<?php + +namespace Sabre\DAV; + +class PropPatchTest extends \PHPUnit_Framework_TestCase { + + protected $propPatch; + + public function setUp() { + + $this->propPatch = new PropPatch([ + '{DAV:}displayname' => 'foo', + ]); + $this->assertEquals(['{DAV:}displayname' => 'foo'], $this->propPatch->getMutations()); + + } + + public function testHandleSingleSuccess() { + + $hasRan = false; + + $this->propPatch->handle('{DAV:}displayname', function($value) use (&$hasRan) { + $hasRan = true; + $this->assertEquals('foo', $value); + return true; + }); + + $this->assertTrue($this->propPatch->commit()); + $result = $this->propPatch->getResult(); + $this->assertEquals(['{DAV:}displayname' => 200], $result); + + $this->assertTrue($hasRan); + + } + + public function testHandleSingleFail() { + + $hasRan = false; + + $this->propPatch->handle('{DAV:}displayname', function($value) use (&$hasRan) { + $hasRan = true; + $this->assertEquals('foo', $value); + return false; + }); + + $this->assertFalse($this->propPatch->commit()); + $result = $this->propPatch->getResult(); + $this->assertEquals(['{DAV:}displayname' => 403], $result); + + $this->assertTrue($hasRan); + + } + + public function testHandleSingleCustomResult() { + + $hasRan = false; + + $this->propPatch->handle('{DAV:}displayname', function($value) use (&$hasRan) { + $hasRan = true; + $this->assertEquals('foo', $value); + return 201; + }); + + $this->assertTrue($this->propPatch->commit()); + $result = $this->propPatch->getResult(); + $this->assertEquals(['{DAV:}displayname' => 201], $result); + + $this->assertTrue($hasRan); + + } + + public function testHandleSingleDeleteSuccess() { + + $hasRan = false; + + $this->propPatch = new PropPatch(['{DAV:}displayname' => null]); + $this->propPatch->handle('{DAV:}displayname', function($value) use (&$hasRan) { + $hasRan = true; + $this->assertNull($value); + return true; + }); + + $this->assertTrue($this->propPatch->commit()); + $result = $this->propPatch->getResult(); + $this->assertEquals(['{DAV:}displayname' => 204], $result); + + $this->assertTrue($hasRan); + + } + + + public function testHandleNothing() { + + $hasRan = false; + + $this->propPatch->handle('{DAV:}foobar', function($value) use (&$hasRan) { + $hasRan = true; + }); + + $this->assertFalse($hasRan); + + } + + /** + * @depends testHandleSingleSuccess + */ + public function testHandleRemaining() { + + $hasRan = false; + + $this->propPatch->handleRemaining(function($mutations) use (&$hasRan) { + $hasRan = true; + $this->assertEquals(['{DAV:}displayname' => 'foo'], $mutations); + return true; + }); + + $this->assertTrue($this->propPatch->commit()); + $result = $this->propPatch->getResult(); + $this->assertEquals(['{DAV:}displayname' => 200], $result); + + $this->assertTrue($hasRan); + + } + public function testHandleRemainingNothingToDo() { + + $hasRan = false; + + $this->propPatch->handle('{DAV:}displayname', function() {} ); + $this->propPatch->handleRemaining(function($mutations) use (&$hasRan) { + $hasRan = true; + }); + + $this->assertFalse($hasRan); + + } + + public function testSetResultCode() { + + $this->propPatch->setResultCode('{DAV:}displayname', 201); + $this->assertTrue($this->propPatch->commit()); + $result = $this->propPatch->getResult(); + $this->assertEquals(['{DAV:}displayname' => 201], $result); + + } + + public function testSetResultCodeFail() { + + $this->propPatch->setResultCode('{DAV:}displayname', 402); + $this->assertFalse($this->propPatch->commit()); + $result = $this->propPatch->getResult(); + $this->assertEquals(['{DAV:}displayname' => 402], $result); + + } + + public function testSetRemainingResultCode() { + + $this->propPatch->setRemainingResultCode(204); + $this->assertTrue($this->propPatch->commit()); + $result = $this->propPatch->getResult(); + $this->assertEquals(['{DAV:}displayname' => 204], $result); + + } + + public function testCommitNoHandler() { + + $this->assertFalse($this->propPatch->commit()); + $result = $this->propPatch->getResult(); + $this->assertEquals(['{DAV:}displayname' => 403], $result); + + } + + public function testHandlerNotCalled() { + + $hasRan = false; + + $this->propPatch->setResultCode('{DAV:}displayname', 402); + $this->propPatch->handle('{DAV:}displayname', function($value) use (&$hasRan) { + $hasRan = true; + }); + + $this->propPatch->commit(); + + // The handler is not supposed to have ran + $this->assertFalse($hasRan); + + } + + public function testDependencyFail() { + + $propPatch = new PropPatch([ + '{DAV:}a' => 'foo', + '{DAV:}b' => 'bar', + ]); + + $calledA = false; + $calledB = false; + + $propPatch->handle('{DAV:}a', function() use (&$calledA) { + $calledA = true; + return false; + }); + $propPatch->handle('{DAV:}b', function() use (&$calledB) { + $calledB = true; + return false; + }); + + $result = $propPatch->commit(); + $this->assertTrue($calledA); + $this->assertFalse($calledB); + + $this->assertFalse($result); + + $this->assertEquals([ + '{DAV:}a' => 403, + '{DAV:}b' => 424, + ], $propPatch->getResult()); + + } + + /** + * @expectedException \UnexpectedValueException + */ + public function testHandleSingleBrokenResult() { + + $propPatch = new PropPatch([ + '{DAV:}a' => 'foo', + ]); + + $calledA = false; + $calledB = false; + + $propPatch->handle('{DAV:}a', function() use (&$calledA) { + return []; + }); + $propPatch->commit(); + + } + + public function testHandleMultiValueSuccess() { + + $propPatch = new PropPatch([ + '{DAV:}a' => 'foo', + '{DAV:}b' => 'bar', + '{DAV:}c' => null, + ]); + + $calledA = false; + + $propPatch->handle(['{DAV:}a', '{DAV:}b', '{DAV:}c'], function($properties) use (&$calledA) { + $calledA = true; + $this->assertEquals([ + '{DAV:}a' => 'foo', + '{DAV:}b' => 'bar', + '{DAV:}c' => null, + ], $properties); + return true; + }); + $result = $propPatch->commit(); + $this->assertTrue($calledA); + $this->assertTrue($result); + + $this->assertEquals([ + '{DAV:}a' => 200, + '{DAV:}b' => 200, + '{DAV:}c' => 204, + ], $propPatch->getResult()); + + } + + + public function testHandleMultiValueFail() { + + $propPatch = new PropPatch([ + '{DAV:}a' => 'foo', + '{DAV:}b' => 'bar', + '{DAV:}c' => null, + ]); + + $calledA = false; + + $propPatch->handle(['{DAV:}a', '{DAV:}b', '{DAV:}c'], function($properties) use (&$calledA) { + $calledA = true; + $this->assertEquals([ + '{DAV:}a' => 'foo', + '{DAV:}b' => 'bar', + '{DAV:}c' => null, + ], $properties); + return false; + }); + $result = $propPatch->commit(); + $this->assertTrue($calledA); + $this->assertFalse($result); + + $this->assertEquals([ + '{DAV:}a' => 403, + '{DAV:}b' => 403, + '{DAV:}c' => 403, + ], $propPatch->getResult()); + + } + + public function testHandleMultiValueCustomResult() { + + $propPatch = new PropPatch([ + '{DAV:}a' => 'foo', + '{DAV:}b' => 'bar', + '{DAV:}c' => null, + ]); + + $calledA = false; + + $propPatch->handle(['{DAV:}a', '{DAV:}b', '{DAV:}c'], function($properties) use (&$calledA) { + $calledA = true; + $this->assertEquals([ + '{DAV:}a' => 'foo', + '{DAV:}b' => 'bar', + '{DAV:}c' => null, + ], $properties); + + return [ + '{DAV:}a' => 201, + '{DAV:}b' => 204, + ]; + return false; + }); + $result = $propPatch->commit(); + $this->assertTrue($calledA); + $this->assertFalse($result); + + $this->assertEquals([ + '{DAV:}a' => 201, + '{DAV:}b' => 204, + '{DAV:}c' => 500, + ], $propPatch->getResult()); + + } + + /** + * @expectedException \UnexpectedValueException + */ + public function testHandleMultiValueBroken() { + + $propPatch = new PropPatch([ + '{DAV:}a' => 'foo', + '{DAV:}b' => 'bar', + '{DAV:}c' => null, + ]); + + $calledA = false; + + $propPatch->handle(['{DAV:}a', '{DAV:}b', '{DAV:}c'], function($properties) use (&$calledA) { + return 'hi'; + }); + $propPatch->commit(); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/AbstractPDOTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/AbstractPDOTest.php new file mode 100644 index 0000000..0edf8dd --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/AbstractPDOTest.php @@ -0,0 +1,190 @@ +<?php + +namespace Sabre\DAV\PropertyStorage\Backend; + +use Sabre\DAV\PropFind; +use Sabre\DAV\PropPatch; +use Sabre\DAV\Xml\Property\Complex; +use Sabre\DAV\Xml\Property\Href; + +abstract class AbstractPDOTest extends \PHPUnit_Framework_TestCase { + + /** + * Should return an instance of \PDO with the current tables initialized, + * and some test records. + */ + abstract function getPDO(); + + function getBackend() { + + return new PDO($this->getPDO()); + + } + + function testPropFind() { + + $backend = $this->getBackend(); + + $propFind = new PropFind('dir', ['{DAV:}displayname']); + $backend->propFind('dir', $propFind); + + $this->assertEquals('Directory', $propFind->get('{DAV:}displayname')); + + } + + function testPropFindNothingToDo() { + + $backend = $this->getBackend(); + + $propFind = new PropFind('dir', ['{DAV:}displayname']); + $propFind->set('{DAV:}displayname', 'foo'); + $backend->propFind('dir', $propFind); + + $this->assertEquals('foo', $propFind->get('{DAV:}displayname')); + + } + + /** + * @depends testPropFind + */ + function testPropPatchUpdate() { + + $backend = $this->getBackend(); + + $propPatch = new PropPatch(['{DAV:}displayname' => 'bar']); + $backend->propPatch('dir', $propPatch); + $propPatch->commit(); + + $propFind = new PropFind('dir', ['{DAV:}displayname']); + $backend->propFind('dir', $propFind); + + $this->assertEquals('bar', $propFind->get('{DAV:}displayname')); + + } + + /** + * @depends testPropPatchUpdate + */ + function testPropPatchComplex() { + + $backend = $this->getBackend(); + + $complex = new Complex('<foo xmlns="DAV:">somevalue</foo>'); + + $propPatch = new PropPatch(['{DAV:}complex' => $complex]); + $backend->propPatch('dir', $propPatch); + $propPatch->commit(); + + $propFind = new PropFind('dir', ['{DAV:}complex']); + $backend->propFind('dir', $propFind); + + $this->assertEquals($complex, $propFind->get('{DAV:}complex')); + + } + + + /** + * @depends testPropPatchComplex + */ + function testPropPatchCustom() { + + $backend = $this->getBackend(); + + $custom = new Href('/foo/bar/'); + + $propPatch = new PropPatch(['{DAV:}custom' => $custom]); + $backend->propPatch('dir', $propPatch); + $propPatch->commit(); + + $propFind = new PropFind('dir', ['{DAV:}custom']); + $backend->propFind('dir', $propFind); + + $this->assertEquals($custom, $propFind->get('{DAV:}custom')); + + } + + /** + * @depends testPropFind + */ + function testPropPatchRemove() { + + $backend = $this->getBackend(); + + $propPatch = new PropPatch(['{DAV:}displayname' => null]); + $backend->propPatch('dir', $propPatch); + $propPatch->commit(); + + $propFind = new PropFind('dir', ['{DAV:}displayname']); + $backend->propFind('dir', $propFind); + + $this->assertEquals(null, $propFind->get('{DAV:}displayname')); + + } + + /** + * @depends testPropFind + */ + function testDelete() { + + $backend = $this->getBackend(); + $backend->delete('dir'); + + $propFind = new PropFind('dir', ['{DAV:}displayname']); + $backend->propFind('dir', $propFind); + + $this->assertEquals(null, $propFind->get('{DAV:}displayname')); + + } + + /** + * @depends testPropFind + */ + function testMove() { + + $backend = $this->getBackend(); + // Creating a new child property. + $propPatch = new PropPatch(['{DAV:}displayname' => 'child']); + $backend->propPatch('dir/child', $propPatch); + $propPatch->commit(); + + $backend->move('dir','dir2'); + + // Old 'dir' + $propFind = new PropFind('dir', ['{DAV:}displayname']); + $backend->propFind('dir', $propFind); + $this->assertEquals(null, $propFind->get('{DAV:}displayname')); + + // Old 'dir/child' + $propFind = new PropFind('dir/child', ['{DAV:}displayname']); + $backend->propFind('dir/child', $propFind); + $this->assertEquals(null, $propFind->get('{DAV:}displayname')); + + // New 'dir2' + $propFind = new PropFind('dir2', ['{DAV:}displayname']); + $backend->propFind('dir2', $propFind); + $this->assertEquals('Directory', $propFind->get('{DAV:}displayname')); + + // New 'dir2/child' + $propFind = new PropFind('dir2/child', ['{DAV:}displayname']); + $backend->propFind('dir2/child', $propFind); + $this->assertEquals('child', $propFind->get('{DAV:}displayname')); + } + + /** + * @depends testPropFind + */ + function testDeepDelete() { + + $backend = $this->getBackend(); + $propPatch = new PropPatch(['{DAV:}displayname' => 'child']); + $backend->propPatch('dir/child', $propPatch); + $propPatch->commit(); + $backend->delete('dir'); + + $propFind = new PropFind('dir/child', ['{DAV:}displayname']); + $backend->propFind('dir/child', $propFind); + + $this->assertEquals(null, $propFind->get('{DAV:}displayname')); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/Mock.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/Mock.php new file mode 100644 index 0000000..87e7280 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/Mock.php @@ -0,0 +1,114 @@ +<?php + +namespace Sabre\DAV\PropertyStorage\Backend; + +use Sabre\DAV\PropFind; +use Sabre\DAV\PropPatch; + +class Mock implements BackendInterface { + + public $data = []; + + /** + * 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. + * + * @param string $path + * @param PropFind $propFind + * @return void + */ + public function propFind($path, PropFind $propFind) { + + if (!isset($this->data[$path])) { + return; + } + + foreach($this->data[$path] as $name=>$value) { + $propFind->set($name, $value); + } + + } + + /** + * 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 + */ + public function propPatch($path, PropPatch $propPatch) { + + if (!isset($this->data[$path])) { + $this->data[$path] = []; + } + $propPatch->handleRemaining(function($properties) use ($path) { + + foreach($properties as $propName=>$propValue) { + + if (is_null($propValue)) { + unset($this->data[$path][$propName]); + } else { + $this->data[$path][$propName] = $propValue; + } + return true; + + } + + }); + + } + + /** + * This method is called after a node is deleted. + * + * This allows a backend to clean up all associated properties. + */ + public function delete($path) { + + unset($this->data[$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 + */ + public function move($source, $destination) { + + foreach($this->data as $path => $props) { + + if ($path === $source) { + $this->data[$destination] = $props; + unset($this->data[$path]); + continue; + } + + if (strpos($path, $source . '/')===0) { + $this->data[$destination . substr($path, strlen($source)+1)] = $props; + unset($this->data[$path]); + } + + } + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/PDOMysqlTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/PDOMysqlTest.php new file mode 100644 index 0000000..ff6db27 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/PDOMysqlTest.php @@ -0,0 +1,31 @@ +<?php + +namespace Sabre\DAV\PropertyStorage\Backend; + +class PDOMySqlTest extends AbstractPDOTest { + + function getPDO() { + + $pdo = \Sabre\TestUtil::getMySQLDB(); + if (!$pdo) $this->markTestSkipped('MySQL is not enabled'); + + $setupSql = file_get_contents(__DIR__ . '/../../../../../examples/sql/mysql.propertystorage.sql'); + // Sloppy multi-query, but it works + $setupSql = explode(';', $setupSql); + + $pdo->exec('DROP TABLE IF EXISTS propertystorage'); + + foreach($setupSql as $sql) { + + if (!trim($sql)) continue; + $pdo->exec($sql); + + } + $pdo->exec('INSERT INTO propertystorage (path, name, value) VALUES ("dir", "{DAV:}displayname", "Directory")'); + + return $pdo; + + } + +} + diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/PDOSqliteTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/PDOSqliteTest.php new file mode 100644 index 0000000..a48cfeb --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/Backend/PDOSqliteTest.php @@ -0,0 +1,37 @@ +<?php + +namespace Sabre\DAV\PropertyStorage\Backend; + +class PDOSqliteTest extends AbstractPDOTest { + + function getPDO() { + + $pdo = \Sabre\TestUtil::getSqliteDB(); + if (!$pdo) $this->markTestSkipped('Sqlite is not enabled'); + + $setupSql = file_get_contents(__DIR__ . '/../../../../../examples/sql/sqlite.propertystorage.sql'); + // Sloppy multi-query, but it works + $setupSql = explode(';', $setupSql); + + $pdo->exec('DROP TABLE IF EXISTS propertystorage'); + + foreach($setupSql as $sql) { + + if (!trim($sql)) continue; + $pdo->exec($sql); + + } + $pdo->exec('INSERT INTO propertystorage (path, name, value) VALUES ("dir", "{DAV:}displayname", "Directory")'); + + return $pdo; + + } + + function tearDown() { + + \Sabre\TestUtil::clearTempDir(); + + } + +} + diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/PluginTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/PluginTest.php new file mode 100644 index 0000000..3f93a95 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/PropertyStorage/PluginTest.php @@ -0,0 +1,117 @@ +<?php + +namespace Sabre\DAV\PropertyStorage; + +class PluginTest extends \Sabre\DAVServerTest { + + protected $backend; + protected $plugin; + + protected $setupFiles = true; + + function setUp() { + + parent::setUp(); + $this->backend = new Backend\Mock(); + $this->plugin = new Plugin( + $this->backend + ); + + $this->server->addPlugin($this->plugin); + + } + + function testGetInfo() { + + $this->assertArrayHasKey( + 'name', + $this->plugin->getPluginInfo() + ); + + } + + function testSetProperty() { + + $this->server->updateProperties('', ['{DAV:}displayname' => 'hi']); + $this->assertEquals([ + '' => [ + '{DAV:}displayname' => 'hi', + ] + ], $this->backend->data); + + } + + /** + * @depends testSetProperty + */ + function testGetProperty() { + + $this->testSetProperty(); + $result = $this->server->getProperties('', ['{DAV:}displayname']); + + $this->assertEquals([ + '{DAV:}displayname' => 'hi', + ], $result); + + } + + /** + * @depends testSetProperty + */ + function testDeleteProperty() { + + $this->testSetProperty(); + $this->server->emit('afterUnbind', ['']); + $this->assertEquals([],$this->backend->data); + + } + + function testMove() { + + $this->server->tree->getNodeForPath('files')->createFile('source'); + $this->server->updateProperties('files/source', ['{DAV:}displayname' => 'hi']); + + $request = new \Sabre\HTTP\Request('MOVE', '/files/source', ['Destination' => '/files/dest']); + $this->assertHTTPStatus(201, $request); + + $result = $this->server->getProperties('/files/dest', ['{DAV:}displayname']); + + $this->assertEquals([ + '{DAV:}displayname' => 'hi', + ], $result); + + $this->server->tree->getNodeForPath('files')->createFile('source'); + $result = $this->server->getProperties('/files/source', ['{DAV:}displayname']); + + $this->assertEquals([], $result); + + } + + /** + * @depends testDeleteProperty + */ + function testSetPropertyInFilteredPath() { + + $this->plugin->pathFilter = function($path) { + + return false; + + }; + + $this->server->updateProperties('', ['{DAV:}displayname' => 'hi']); + $this->assertEquals([], $this->backend->data); + + } + + /** + * @depends testSetPropertyInFilteredPath + */ + function testGetPropertyInFilteredPath() { + + $this->testSetPropertyInFilteredPath(); + $result = $this->server->getProperties('', ['{DAV:}displayname']); + + $this->assertEquals([], $result); + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerCopyMoveTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerCopyMoveTest.php new file mode 100644 index 0000000..3dfbc9e --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerCopyMoveTest.php @@ -0,0 +1,209 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\HTTP; + +require_once 'Sabre/HTTP/ResponseMock.php'; + +class ServerCopyMoveTest extends \PHPUnit_Framework_TestCase { + + private $response; + /** + * @var Server + */ + private $server; + + function setUp() { + + $this->response = new HTTP\ResponseMock(); + $dir = new FS\Directory(SABRE_TEMPDIR); + $tree = new Tree($dir); + $this->server = new Server($tree); + $this->server->sapi = new HTTP\SapiMock(); + $this->server->debugExceptions = true; + $this->server->httpResponse = $this->response; + file_put_contents(SABRE_TEMPDIR . '/test.txt', 'Test contents'); + file_put_contents(SABRE_TEMPDIR . '/test2.txt', 'Test contents2'); + mkdir(SABRE_TEMPDIR . '/col'); + file_put_contents(SABRE_TEMPDIR . 'col/test.txt', 'Test contents'); + + } + + function tearDown() { + + $cleanUp = array('test.txt','testput.txt','testcol','test2.txt','test3.txt','col/test.txt','col','col2/test.txt','col2'); + foreach($cleanUp as $file) { + $tmpFile = SABRE_TEMPDIR . '/' . $file; + if (file_exists($tmpFile)) { + + if (is_dir($tmpFile)) { + rmdir($tmpFile); + } else { + unlink($tmpFile); + } + + } + } + + } + + + function testCopyOverWrite() { + + $serverVars = array( + 'REQUEST_URI' => '/test.txt', + 'REQUEST_METHOD' => 'COPY', + 'HTTP_DESTINATION' => '/test2.txt', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(204, $this->response->status, 'Received an incorrect HTTP status. Full body inspection: ' . $this->response->body); + $this->assertEquals(array( + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Length' => ['0'], + ), + $this->response->getHeaders() + ); + + $this->assertEquals('Test contents', file_get_contents(SABRE_TEMPDIR. '/test2.txt')); + + } + + function testCopyToSelf() { + + $serverVars = array( + 'REQUEST_URI' => '/test.txt', + 'REQUEST_METHOD' => 'COPY', + 'HTTP_DESTINATION' => '/test.txt', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(403, $this->response->status, 'Received an incorrect HTTP status. Full body inspection: ' . $this->response->body); + $this->assertEquals('Test contents', file_get_contents(SABRE_TEMPDIR. '/test.txt')); + + } + + function testNonExistantParent() { + + $serverVars = array( + 'REQUEST_URI' => '/test.txt', + 'REQUEST_METHOD' => 'COPY', + 'HTTP_DESTINATION' => '/testcol2/test2.txt', + 'HTTP_OVERWRITE' => 'F', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(array( + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ), + $this->response->getHeaders() + ); + + $this->assertEquals(409, $this->response->status); + + } + + function testRandomOverwriteHeader() { + + $serverVars = array( + 'REQUEST_URI' => '/test.txt', + 'REQUEST_METHOD' => 'COPY', + 'HTTP_DESTINATION' => '/testcol2/test2.txt', + 'HTTP_OVERWRITE' => 'SURE!', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(400, $this->response->status); + + } + + function testCopyDirectory() { + + $serverVars = array( + 'REQUEST_URI' => '/col', + 'REQUEST_METHOD' => 'COPY', + 'HTTP_DESTINATION' => '/col2', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(201, $this->response->status, 'Full response: ' . $this->response->getBody(true)); + + $this->assertEquals(array( + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Length' => ['0'], + ), + $this->response->getHeaders() + ); + + $this->assertEquals('Test contents',file_get_contents(SABRE_TEMPDIR . '/col2/test.txt')); + + } + + function testSimpleCopyFile() { + + $serverVars = array( + 'REQUEST_URI' => '/test.txt', + 'REQUEST_METHOD' => 'COPY', + 'HTTP_DESTINATION' => '/test3.txt', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(array( + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Length' => ['0'], + ), + $this->response->getHeaders() + ); + + $this->assertEquals(201, $this->response->status); + $this->assertEquals('Test contents', file_get_contents(SABRE_TEMPDIR . '/test3.txt')); + + } + + function testSimpleCopyCollection() { + + $serverVars = array( + 'REQUEST_URI' => '/col', + 'REQUEST_METHOD' => 'COPY', + 'HTTP_DESTINATION' => '/col2', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(201, $this->response->status, 'Incorrect status received. Full response body: ' . $this->response->body); + + $this->assertEquals(array( + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Length' => ['0'], + ), + $this->response->getHeaders() + ); + + + $this->assertEquals('Test contents',file_get_contents(SABRE_TEMPDIR . '/col2/test.txt')); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerEventsTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerEventsTest.php new file mode 100644 index 0000000..7f995c6 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerEventsTest.php @@ -0,0 +1,121 @@ +<?php + +namespace Sabre\DAV; +use Sabre\HTTP; + +require_once 'Sabre/DAV/AbstractServer.php'; + +class ServerEventsTest extends AbstractServer { + + private $tempPath; + + private $exception; + + function testAfterBind() { + + $this->server->on('afterBind', [$this,'afterBindHandler']); + $newPath = 'afterBind'; + + $this->tempPath = ''; + $this->server->createFile($newPath,'body'); + $this->assertEquals($newPath, $this->tempPath); + + } + + function afterBindHandler($path) { + + $this->tempPath = $path; + + } + + function testAfterResponse() { + + $mock = $this->getMock('stdClass', array('afterResponseCallback')); + $mock->expects($this->once())->method('afterResponseCallback'); + + $this->server->on('afterResponse', [$mock, 'afterResponseCallback']); + + $this->server->httpRequest = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'GET', + 'REQUEST_URI' => '/test.txt', + )); + + $this->server->exec(); + + } + + function testBeforeBindCancel() { + + $this->server->on('beforeBind', [$this,'beforeBindCancelHandler']); + $this->assertFalse($this->server->createFile('bla','body')); + + // Also testing put() + $req = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'PUT', + 'REQUEST_URI' => '/barbar', + )); + + $this->server->httpRequest = $req; + $this->server->exec(); + + $this->assertEquals('',$this->server->httpResponse->status); + + } + + function beforeBindCancelHandler() { + + return false; + + } + + function testException() { + + $this->server->on('exception', [$this, 'exceptionHandler']); + + $req = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'GET', + 'REQUEST_URI' => '/not/exisitng', + )); + $this->server->httpRequest = $req; + $this->server->exec(); + + $this->assertInstanceOf('Sabre\\DAV\\Exception\\NotFound', $this->exception); + + } + + function exceptionHandler(Exception $exception) { + + $this->exception = $exception; + + } + + function testMethod() { + + $k = 1; + $this->server->on('method', function() use (&$k) { + + $k+=1; + + return false; + + }); + $this->server->on('method', function() use (&$k) { + + $k+=2; + + return false; + + }); + + $this->server->invokeMethod( + new HTTP\Request('BLABLA', '/'), + new HTTP\Response(), + false + ); + + $this->assertEquals(2, $k); + + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerMKCOLTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerMKCOLTest.php new file mode 100644 index 0000000..e35189e --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerMKCOLTest.php @@ -0,0 +1,346 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\HTTP; + +class ServerMKCOLTest extends AbstractServer { + + function testMkcol() { + + $serverVars = array( + 'REQUEST_URI' => '/testcol', + 'REQUEST_METHOD' => 'MKCOL', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody(""); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(array( + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Length' => ['0'], + ),$this->response->getHeaders()); + + $this->assertEquals(201, $this->response->status); + $this->assertEquals('', $this->response->body); + $this->assertTrue(is_dir($this->tempDir . '/testcol')); + + } + + /** + * @depends testMkcol + */ + function testMKCOLUnknownBody() { + + $serverVars = array( + 'REQUEST_URI' => '/testcol', + 'REQUEST_METHOD' => 'MKCOL', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody("Hello"); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(array( + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ),$this->response->getHeaders()); + + $this->assertEquals(415, $this->response->status); + + } + + /** + * @depends testMkcol + */ + function testMKCOLBrokenXML() { + + $serverVars = array( + 'REQUEST_URI' => '/testcol', + 'REQUEST_METHOD' => 'MKCOL', + 'HTTP_CONTENT_TYPE' => 'application/xml', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody("Hello"); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(array( + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ),$this->response->getHeaders()); + + $this->assertEquals(400, $this->response->getStatus(), $this->response->getBodyAsString() ); + + } + + /** + * @depends testMkcol + */ + function testMKCOLUnknownXML() { + + $serverVars = array( + 'REQUEST_URI' => '/testcol', + 'REQUEST_METHOD' => 'MKCOL', + 'HTTP_CONTENT_TYPE' => 'application/xml', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('<?xml version="1.0"?><html></html>'); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(array( + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ),$this->response->getHeaders()); + + $this->assertEquals(400, $this->response->getStatus()); + + } + + /** + * @depends testMkcol + */ + function testMKCOLNoResourceType() { + + $serverVars = array( + 'REQUEST_URI' => '/testcol', + 'REQUEST_METHOD' => 'MKCOL', + 'HTTP_CONTENT_TYPE' => 'application/xml', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('<?xml version="1.0"?> +<mkcol xmlns="DAV:"> + <set> + <prop> + <displayname>Evert</displayname> + </prop> + </set> +</mkcol>'); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(array( + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ),$this->response->getHeaders()); + + $this->assertEquals(400, $this->response->status, 'Wrong statuscode received. Full response body: ' .$this->response->body); + + } + + /** + * @depends testMkcol + */ + function testMKCOLIncorrectResourceType() { + + $serverVars = array( + 'REQUEST_URI' => '/testcol', + 'REQUEST_METHOD' => 'MKCOL', + 'HTTP_CONTENT_TYPE' => 'application/xml', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('<?xml version="1.0"?> +<mkcol xmlns="DAV:"> + <set> + <prop> + <resourcetype><collection /><blabla /></resourcetype> + </prop> + </set> +</mkcol>'); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(array( + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ),$this->response->getHeaders()); + + $this->assertEquals(403, $this->response->status, 'Wrong statuscode received. Full response body: ' .$this->response->body); + + } + + /** + * @depends testMKCOLIncorrectResourceType + */ + function testMKCOLSuccess() { + + $serverVars = array( + 'REQUEST_URI' => '/testcol', + 'REQUEST_METHOD' => 'MKCOL', + 'HTTP_CONTENT_TYPE' => 'application/xml', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('<?xml version="1.0"?> +<mkcol xmlns="DAV:"> + <set> + <prop> + <resourcetype><collection /></resourcetype> + </prop> + </set> +</mkcol>'); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(array( + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Length' => ['0'], + ),$this->response->getHeaders()); + + $this->assertEquals(201, $this->response->status, 'Wrong statuscode received. Full response body: ' .$this->response->body); + + } + + /** + * @depends testMKCOLIncorrectResourceType + */ + function testMKCOLWhiteSpaceResourceType() { + + $serverVars = array( + 'REQUEST_URI' => '/testcol', + 'REQUEST_METHOD' => 'MKCOL', + 'HTTP_CONTENT_TYPE' => 'application/xml', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('<?xml version="1.0"?> +<mkcol xmlns="DAV:"> + <set> + <prop> + <resourcetype> + <collection /> + </resourcetype> + </prop> + </set> +</mkcol>'); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(array( + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Length' => ['0'], + ),$this->response->getHeaders()); + + $this->assertEquals(201, $this->response->status, 'Wrong statuscode received. Full response body: ' .$this->response->body); + + } + + /** + * @depends testMKCOLIncorrectResourceType + */ + function testMKCOLNoParent() { + + $serverVars = array( + 'REQUEST_URI' => '/testnoparent/409me', + 'REQUEST_METHOD' => 'MKCOL', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody(''); + + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(array( + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ),$this->response->getHeaders()); + + $this->assertEquals(409, $this->response->status, 'Wrong statuscode received. Full response body: ' .$this->response->body); + + } + + /** + * @depends testMKCOLIncorrectResourceType + */ + function testMKCOLParentIsNoCollection() { + + $serverVars = array( + 'REQUEST_URI' => '/test.txt/409me', + 'REQUEST_METHOD' => 'MKCOL', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody(''); + + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(array( + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ),$this->response->getHeaders()); + + $this->assertEquals(409, $this->response->status, 'Wrong statuscode received. Full response body: ' .$this->response->body); + + } + + /** + * @depends testMKCOLIncorrectResourceType + */ + function testMKCOLAlreadyExists() { + + $serverVars = array( + 'REQUEST_URI' => '/test.txt', + 'REQUEST_METHOD' => 'MKCOL', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody(''); + + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(array( + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + 'Allow' => ['OPTIONS, GET, HEAD, DELETE, PROPFIND, PUT, PROPPATCH, COPY, MOVE, REPORT'], + ),$this->response->getHeaders()); + + $this->assertEquals(405, $this->response->status, 'Wrong statuscode received. Full response body: ' .$this->response->body); + + } + + /** + * @depends testMKCOLSuccess + * @depends testMKCOLAlreadyExists + */ + function testMKCOLAndProps() { + + $serverVars = array( + 'REQUEST_URI' => '/testcol', + 'REQUEST_METHOD' => 'MKCOL', + 'HTTP_CONTENT_TYPE' => 'application/xml', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody('<?xml version="1.0"?> +<mkcol xmlns="DAV:"> + <set> + <prop> + <resourcetype><collection /></resourcetype> + <displayname>my new collection</displayname> + </prop> + </set> +</mkcol>'); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(207, $this->response->status, 'Wrong statuscode received. Full response body: ' .$this->response->body); + + $this->assertEquals(array( + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ),$this->response->getHeaders()); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerPluginTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerPluginTest.php new file mode 100644 index 0000000..ab0ad29 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerPluginTest.php @@ -0,0 +1,107 @@ +<?php + +namespace Sabre\DAV; +use Sabre\HTTP; + +require_once 'Sabre/DAV/AbstractServer.php'; +require_once 'Sabre/DAV/TestPlugin.php'; + +class ServerPluginTest extends AbstractServer { + + /** + * @var Sabre\DAV\TestPlugin + */ + protected $testPlugin; + + function setUp() { + + parent::setUp(); + + $testPlugin = new TestPlugin(); + $this->server->addPlugin($testPlugin); + $this->testPlugin = $testPlugin; + + } + + /** + */ + function testBaseClass() { + + $p = new ServerPluginMock(); + $this->assertEquals([],$p->getFeatures()); + $this->assertEquals([],$p->getHTTPMethods('')); + $this->assertEquals( + [ + 'name' => 'Sabre\DAV\ServerPluginMock', + 'description' => null, + 'link' => null + ], $p->getPluginInfo() + ); + + } + + function testOptions() { + + $serverVars = array( + 'REQUEST_URI' => '/', + 'REQUEST_METHOD' => 'OPTIONS', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(array( + 'DAV' => ['1, 3, extended-mkcol, drinking'], + 'MS-Author-Via' => ['DAV'], + 'Allow' => ['OPTIONS, GET, HEAD, DELETE, PROPFIND, PUT, PROPPATCH, COPY, MOVE, REPORT, BEER, WINE'], + 'Accept-Ranges' => ['bytes'], + 'Content-Length' => ['0'], + 'X-Sabre-Version' => [Version::VERSION], + ),$this->response->getHeaders()); + + $this->assertEquals(200, $this->response->status); + $this->assertEquals('', $this->response->body); + $this->assertEquals('OPTIONS',$this->testPlugin->beforeMethod); + + + } + + function testGetPlugin() { + + $this->assertEquals($this->testPlugin,$this->server->getPlugin(get_class($this->testPlugin))); + + } + + function testUnknownPlugin() { + + $this->assertNull($this->server->getPlugin('SomeRandomClassName')); + + } + + function testGetSupportedReportSet() { + + $this->assertEquals(array(), $this->testPlugin->getSupportedReportSet('/')); + + } + + function testGetPlugins() { + + $this->assertEquals( + array( + get_class($this->testPlugin) => $this->testPlugin, + 'core' => $this->server->getPlugin('core'), + ), + $this->server->getPlugins() + ); + + } + + +} + +class ServerPluginMock extends ServerPlugin { + + function initialize(Server $s) { } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerPreconditionTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerPreconditionTest.php new file mode 100644 index 0000000..170deb7 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerPreconditionTest.php @@ -0,0 +1,346 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\HTTP; + +require_once 'Sabre/HTTP/ResponseMock.php'; + +class ServerPreconditionsTest extends \PHPUnit_Framework_TestCase { + + /** + * @expectedException Sabre\DAV\Exception\PreconditionFailed + */ + function testIfMatchNoNode() { + + $root = new SimpleCollection('root',array(new ServerPreconditionsNode())); + $server = new Server($root); + $httpRequest = new HTTP\Request('GET', '/bar', ['If-Match' => '*']); + $httpResponse = new HTTP\Response(); + $server->checkPreconditions($httpRequest, $httpResponse); + + } + + /** + */ + function testIfMatchHasNode() { + + $root = new SimpleCollection('root',array(new ServerPreconditionsNode())); + $server = new Server($root); + $httpRequest = new HTTP\Request('GET', '/foo', ['If-Match' => '*']); + $httpResponse = new HTTP\Response(); + $this->assertTrue($server->checkPreconditions($httpRequest, $httpResponse)); + + } + + /** + * @expectedException Sabre\DAV\Exception\PreconditionFailed + */ + function testIfMatchWrongEtag() { + + $root = new SimpleCollection('root',array(new ServerPreconditionsNode())); + $server = new Server($root); + $httpRequest = new HTTP\Request('GET', '/foo', ['If-Match' => '1234']); + $httpResponse = new HTTP\Response(); + $server->checkPreconditions($httpRequest, $httpResponse); + + } + + /** + */ + function testIfMatchCorrectEtag() { + + $root = new SimpleCollection('root',array(new ServerPreconditionsNode())); + $server = new Server($root); + $httpRequest = new HTTP\Request('GET', '/foo', ['If-Match' => '"abc123"']); + $httpResponse = new HTTP\Response(); + $this->assertTrue($server->checkPreconditions($httpRequest, $httpResponse)); + + } + + /** + * Evolution sometimes uses \" instead of " for If-Match headers. + * + * @depends testIfMatchCorrectEtag + */ + function testIfMatchEvolutionEtag() { + + $root = new SimpleCollection('root',array(new ServerPreconditionsNode())); + $server = new Server($root); + $httpRequest = new HTTP\Request('GET', '/foo', ['If-Match' => '\\"abc123\\"']); + $httpResponse = new HTTP\Response(); + $this->assertTrue($server->checkPreconditions($httpRequest, $httpResponse)); + + } + + /** + */ + function testIfMatchMultiple() { + + $root = new SimpleCollection('root',array(new ServerPreconditionsNode())); + $server = new Server($root); + $httpRequest = new HTTP\Request('GET', '/foo', ['If-Match' => '"hellothere", "abc123"']); + $httpResponse = new HTTP\Response(); + $this->assertTrue($server->checkPreconditions($httpRequest, $httpResponse)); + + } + + /** + */ + function testIfNoneMatchNoNode() { + + $root = new SimpleCollection('root',array(new ServerPreconditionsNode())); + $server = new Server($root); + $httpRequest = new HTTP\Request('GET', '/bar', ['If-None-Match' => '*']); + $httpResponse = new HTTP\Response(); + $this->assertTrue($server->checkPreconditions($httpRequest, $httpResponse)); + + } + + /** + * @expectedException Sabre\DAV\Exception\PreconditionFailed + */ + function testIfNoneMatchHasNode() { + + $root = new SimpleCollection('root',array(new ServerPreconditionsNode())); + $server = new Server($root); + $httpRequest = new HTTP\Request('POST', '/foo', ['If-None-Match' => '*']); + $httpResponse = new HTTP\Response(); + $server->checkPreconditions($httpRequest, $httpResponse); + + } + + /** + */ + function testIfNoneMatchWrongEtag() { + + $root = new SimpleCollection('root',array(new ServerPreconditionsNode())); + $server = new Server($root); + $httpRequest = new HTTP\Request('POST', '/foo', ['If-None-Match' => '"1234"']); + $httpResponse = new HTTP\Response(); + $this->assertTrue($server->checkPreconditions($httpRequest, $httpResponse)); + + } + + /** + */ + function testIfNoneMatchWrongEtagMultiple() { + + $root = new SimpleCollection('root',array(new ServerPreconditionsNode())); + $server = new Server($root); + $httpRequest = new HTTP\Request('POST', '/foo', ['If-None-Match' => '"1234", "5678"']); + $httpResponse = new HTTP\Response(); + $this->assertTrue($server->checkPreconditions($httpRequest, $httpResponse)); + + } + + /** + * @expectedException Sabre\DAV\Exception\PreconditionFailed + */ + public function testIfNoneMatchCorrectEtag() { + + $root = new SimpleCollection('root',array(new ServerPreconditionsNode())); + $server = new Server($root); + $httpRequest = new HTTP\Request('POST', '/foo', ['If-None-Match' => '"abc123"']); + $httpResponse = new HTTP\Response(); + $server->checkPreconditions($httpRequest, $httpResponse); + + } + + /** + * @expectedException Sabre\DAV\Exception\PreconditionFailed + */ + public function testIfNoneMatchCorrectEtagMultiple() { + + $root = new SimpleCollection('root',array(new ServerPreconditionsNode())); + $server = new Server($root); + $httpRequest = new HTTP\Request('POST', '/foo', ['If-None-Match' => '"1234, "abc123"']); + $httpResponse = new HTTP\Response(); + $server->checkPreconditions($httpRequest, $httpResponse); + + } + + /** + */ + public function testIfNoneMatchCorrectEtagAsGet() { + + $root = new SimpleCollection('root',array(new ServerPreconditionsNode())); + $server = new Server($root); + $httpRequest = new HTTP\Request('GET', '/foo', ['If-None-Match' => '"abc123"']); + $server->httpResponse = new HTTP\ResponseMock(); + + $this->assertFalse($server->checkPreconditions($httpRequest, $server->httpResponse)); + $this->assertEquals(304, $server->httpResponse->getStatus()); + $this->assertEquals(['ETag' => ['"abc123"']], $server->httpResponse->getHeaders()); + + } + + /** + * This was a test written for issue #515. + */ + public function testNoneMatchCorrectEtagEnsureSapiSent() { + + $root = new SimpleCollection('root',array(new ServerPreconditionsNode())); + $server = new Server($root); + $server->sapi = new HTTP\SapiMock(); + HTTP\SapiMock::$sent = 0; + $httpRequest = new HTTP\Request('GET', '/foo', ['If-None-Match' => '"abc123"']); + $server->httpRequest = $httpRequest; + $server->httpResponse = new HTTP\ResponseMock(); + + $server->exec(); + + $this->assertFalse($server->checkPreconditions($httpRequest, $server->httpResponse)); + $this->assertEquals(304, $server->httpResponse->getStatus()); + $this->assertEquals([ + 'ETag' => ['"abc123"'], + 'X-Sabre-Version' => [Version::VERSION], + ], $server->httpResponse->getHeaders()); + $this->assertEquals(1, HTTP\SapiMock::$sent); + + } + + /** + */ + public function testIfModifiedSinceUnModified() { + + $root = new SimpleCollection('root',array(new ServerPreconditionsNode())); + $server = new Server($root); + $httpRequest = HTTP\Sapi::createFromServerArray(array( + 'HTTP_IF_MODIFIED_SINCE' => 'Sun, 06 Nov 1994 08:49:37 GMT', + 'REQUEST_URI' => '/foo' + )); + $server->httpResponse = new HTTP\ResponseMock(); + $this->assertFalse($server->checkPreconditions($httpRequest, $server->httpResponse)); + + $this->assertEquals(304, $server->httpResponse->status); + $this->assertEquals(array( + 'Last-Modified' => ['Sat, 06 Apr 1985 23:30:00 GMT'], + ), $server->httpResponse->getHeaders()); + + } + + + /** + */ + public function testIfModifiedSinceModified() { + + $root = new SimpleCollection('root',array(new ServerPreconditionsNode())); + $server = new Server($root); + $httpRequest = HTTP\Sapi::createFromServerArray(array( + 'HTTP_IF_MODIFIED_SINCE' => 'Tue, 06 Nov 1984 08:49:37 GMT', + 'REQUEST_URI' => '/foo' + )); + + $httpRequest = $httpRequest; + $httpResponse = new HTTP\ResponseMock(); + $this->assertTrue($server->checkPreconditions($httpRequest, $httpResponse)); + + } + + /** + */ + public function testIfModifiedSinceInvalidDate() { + + $root = new SimpleCollection('root',array(new ServerPreconditionsNode())); + $server = new Server($root); + $httpRequest = HTTP\Sapi::createFromServerArray(array( + 'HTTP_IF_MODIFIED_SINCE' => 'Your mother', + 'REQUEST_URI' => '/foo' + )); + $httpRequest = $httpRequest; + $httpResponse = new HTTP\ResponseMock(); + + // Invalid dates must be ignored, so this should return true + $this->assertTrue($server->checkPreconditions($httpRequest, $httpResponse)); + + } + + /** + */ + public function testIfModifiedSinceInvalidDate2() { + + $root = new SimpleCollection('root',array(new ServerPreconditionsNode())); + $server = new Server($root); + $httpRequest = HTTP\Sapi::createFromServerArray(array( + 'HTTP_IF_MODIFIED_SINCE' => 'Sun, 06 Nov 1994 08:49:37 EST', + 'REQUEST_URI' => '/foo' + )); + $httpResponse = new HTTP\ResponseMock(); + $this->assertTrue($server->checkPreconditions($httpRequest, $httpResponse)); + + } + + + /** + */ + public function testIfUnmodifiedSinceUnModified() { + + $root = new SimpleCollection('root',array(new ServerPreconditionsNode())); + $server = new Server($root); + $httpRequest = HTTP\Sapi::createFromServerArray(array( + 'HTTP_IF_UNMODIFIED_SINCE' => 'Sun, 06 Nov 1994 08:49:37 GMT', + 'REQUEST_URI' => '/foo' + )); + $httpResponse = new HTTP\Response(); + $this->assertTrue($server->checkPreconditions($httpRequest, $httpResponse)); + + } + + + /** + * @expectedException Sabre\DAV\Exception\PreconditionFailed + */ + public function testIfUnmodifiedSinceModified() { + + $root = new SimpleCollection('root',array(new ServerPreconditionsNode())); + $server = new Server($root); + $httpRequest = HTTP\Sapi::createFromServerArray(array( + 'HTTP_IF_UNMODIFIED_SINCE' => 'Tue, 06 Nov 1984 08:49:37 GMT', + 'REQUEST_URI' => '/foo' + )); + $httpResponse = new HTTP\ResponseMock(); + $server->checkPreconditions($httpRequest, $httpResponse); + + } + + /** + */ + public function testIfUnmodifiedSinceInvalidDate() { + + $root = new SimpleCollection('root',array(new ServerPreconditionsNode())); + $server = new Server($root); + $httpRequest = HTTP\Sapi::createFromServerArray(array( + 'HTTP_IF_UNMODIFIED_SINCE' => 'Sun, 06 Nov 1984 08:49:37 CET', + 'REQUEST_URI' => '/foo' + )); + $httpResponse = new HTTP\ResponseMock(); + $this->assertTrue($server->checkPreconditions($httpRequest, $httpResponse)); + + } + + +} + +class ServerPreconditionsNode extends File { + + function getETag() { + + return '"abc123"'; + + } + + function getLastModified() { + + /* my birthday & time, I believe */ + return strtotime('1985-04-07 01:30 +02:00'); + + } + + function getName() { + + return 'foo'; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerPropsInfiniteDepthTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerPropsInfiniteDepthTest.php new file mode 100644 index 0000000..f00b21c --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerPropsInfiniteDepthTest.php @@ -0,0 +1,166 @@ +<?php + +namespace Sabre\DAV; +use Sabre\HTTP; + +require_once 'Sabre/DAV/AbstractServer.php'; + +class ServerPropsInfiniteDepthTest extends AbstractServer { + + protected function getRootNode() { + + return new FSExt\Directory(SABRE_TEMPDIR); + + } + + function setUp() { + + if (file_exists(SABRE_TEMPDIR.'../.sabredav')) unlink(SABRE_TEMPDIR.'../.sabredav'); + parent::setUp(); + file_put_contents(SABRE_TEMPDIR . '/test2.txt', 'Test contents2'); + mkdir(SABRE_TEMPDIR . '/col'); + mkdir(SABRE_TEMPDIR . '/col/col'); + file_put_contents(SABRE_TEMPDIR . 'col/col/test.txt', 'Test contents'); + $this->server->addPlugin(new Locks\Plugin(new Locks\Backend\File(SABRE_TEMPDIR . '/.locksdb'))); + $this->server->enablePropfindDepthInfinity = true; + + } + + function tearDown() { + + parent::tearDown(); + if (file_exists(SABRE_TEMPDIR.'../.locksdb')) unlink(SABRE_TEMPDIR.'../.locksdb'); + + } + + private function sendRequest($body) { + + $request = new HTTP\Request('PROPFIND', '/', ['Depth' => 'infinity']); + $request->setBody($body); + + $this->server->httpRequest = $request; + $this->server->exec(); + + } + + public function testPropFindEmptyBody() { + + $hasFired = false; + + $self = $this; + + $this->sendRequest(""); + + $this->assertEquals(207, $this->response->status, 'Incorrect status received. Full response body: ' . $this->response->getBodyAsString()); + + $this->assertEquals(array( + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + 'DAV' => ['1, 3, extended-mkcol, 2'], + 'Vary' => ['Brief,Prefer'], + ), + $this->response->getHeaders() + ); + + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"urn:DAV\"",$this->response->body); + $xml = simplexml_load_string($body); + $xml->registerXPathNamespace('d','urn:DAV'); + + list($data) = $xml->xpath('/d:multistatus/d:response/d:href'); + $this->assertEquals('/',(string)$data,'href element should have been /'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:resourcetype'); + // 8 resources are to be returned: /, col, col/col, col/col/test.txt, dir, dir/child.txt, test.txt and test2.txt + $this->assertEquals(8,count($data)); + + } + + function testSupportedLocks() { + + $xml = '<?xml version="1.0"?> +<d:propfind xmlns:d="DAV:"> + <d:prop> + <d:supportedlock /> + </d:prop> +</d:propfind>'; + + $this->sendRequest($xml); + + $body = $this->response->getBodyAsString(); + $this->assertEquals(207, $this->response->getStatus(), $body); + + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"urn:DAV\"",$body); + $xml = simplexml_load_string($body); + $xml->registerXPathNamespace('d','urn:DAV'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supportedlock/d:lockentry'); + $this->assertEquals(16,count($data),'We expected sixteen \'d:lockentry\' tags'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supportedlock/d:lockentry/d:lockscope'); + $this->assertEquals(16,count($data),'We expected sixteen \'d:lockscope\' tags'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supportedlock/d:lockentry/d:locktype'); + $this->assertEquals(16,count($data),'We expected sixteen \'d:locktype\' tags'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supportedlock/d:lockentry/d:lockscope/d:shared'); + $this->assertEquals(8,count($data),'We expected eight \'d:shared\' tags'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supportedlock/d:lockentry/d:lockscope/d:exclusive'); + $this->assertEquals(8,count($data),'We expected eight \'d:exclusive\' tags'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supportedlock/d:lockentry/d:locktype/d:write'); + $this->assertEquals(16,count($data),'We expected sixteen \'d:write\' tags'); + } + + function testLockDiscovery() { + + $xml = '<?xml version="1.0"?> +<d:propfind xmlns:d="DAV:"> + <d:prop> + <d:lockdiscovery /> + </d:prop> +</d:propfind>'; + + $this->sendRequest($xml); + + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"urn:DAV\"",$this->response->body); + $xml = simplexml_load_string($body); + $xml->registerXPathNamespace('d','urn:DAV'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:lockdiscovery'); + $this->assertEquals(8,count($data),'We expected eight \'d:lockdiscovery\' tags'); + + } + + function testUnknownProperty() { + + $xml = '<?xml version="1.0"?> +<d:propfind xmlns:d="DAV:"> + <d:prop> + <d:macaroni /> + </d:prop> +</d:propfind>'; + + $this->sendRequest($xml); + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"urn:DAV\"",$this->response->body); + $xml = simplexml_load_string($body); + $xml->registerXPathNamespace('d','urn:DAV'); + $pathTests = array( + '/d:multistatus', + '/d:multistatus/d:response', + '/d:multistatus/d:response/d:propstat', + '/d:multistatus/d:response/d:propstat/d:status', + '/d:multistatus/d:response/d:propstat/d:prop', + '/d:multistatus/d:response/d:propstat/d:prop/d:macaroni', + ); + foreach($pathTests as $test) { + $this->assertTrue(count($xml->xpath($test))==true,'We expected the ' . $test . ' element to appear in the response, we got: ' . $body); + } + + $val = $xml->xpath('/d:multistatus/d:response/d:propstat/d:status'); + $this->assertEquals(8,count($val),$body); + $this->assertEquals('HTTP/1.1 404 Not Found',(string)$val[0]); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerPropsTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerPropsTest.php new file mode 100644 index 0000000..ac9137a --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerPropsTest.php @@ -0,0 +1,200 @@ +<?php + +namespace Sabre\DAV; +use Sabre\HTTP; + +require_once 'Sabre/HTTP/ResponseMock.php'; +require_once 'Sabre/DAV/AbstractServer.php'; + +class ServerPropsTest extends AbstractServer { + + protected function getRootNode() { + + return new FSExt\Directory(SABRE_TEMPDIR); + + } + + function setUp() { + + if (file_exists(SABRE_TEMPDIR.'../.sabredav')) unlink(SABRE_TEMPDIR.'../.sabredav'); + parent::setUp(); + file_put_contents(SABRE_TEMPDIR . '/test2.txt', 'Test contents2'); + mkdir(SABRE_TEMPDIR . '/col'); + file_put_contents(SABRE_TEMPDIR . 'col/test.txt', 'Test contents'); + $this->server->addPlugin(new Locks\Plugin(new Locks\Backend\File(SABRE_TEMPDIR . '/.locksdb'))); + + } + + function tearDown() { + + parent::tearDown(); + if (file_exists(SABRE_TEMPDIR.'../.locksdb')) unlink(SABRE_TEMPDIR.'../.locksdb'); + + } + + private function sendRequest($body, $path = '/', $headers = ['Depth' => '0']) { + + $request = new HTTP\Request('PROPFIND', $path, $headers, $body); + + $this->server->httpRequest = $request; + $this->server->exec(); + + } + + function testPropFindEmptyBody() { + + $this->sendRequest(""); + $this->assertEquals(207, $this->response->status); + + $this->assertEquals(array( + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + 'DAV' => ['1, 3, extended-mkcol, 2'], + 'Vary' => ['Brief,Prefer'], + ), + $this->response->getHeaders() + ); + + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"urn:DAV\"",$this->response->body); + $xml = simplexml_load_string($body); + $xml->registerXPathNamespace('d','urn:DAV'); + + list($data) = $xml->xpath('/d:multistatus/d:response/d:href'); + $this->assertEquals('/',(string)$data,'href element should have been /'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:resourcetype'); + $this->assertEquals(1,count($data)); + + } + + function testPropFindEmptyBodyFile() { + + $this->sendRequest("", '/test2.txt', []); + $this->assertEquals(207, $this->response->status); + + $this->assertEquals(array( + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + 'DAV' => ['1, 3, extended-mkcol, 2'], + 'Vary' => ['Brief,Prefer'], + ), + $this->response->getHeaders() + ); + + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"urn:DAV\"",$this->response->body); + $xml = simplexml_load_string($body); + $xml->registerXPathNamespace('d','urn:DAV'); + + list($data) = $xml->xpath('/d:multistatus/d:response/d:href'); + $this->assertEquals('/test2.txt',(string)$data,'href element should have been /'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:getcontentlength'); + $this->assertEquals(1,count($data)); + + } + + function testSupportedLocks() { + + $xml = '<?xml version="1.0"?> +<d:propfind xmlns:d="DAV:"> + <d:prop> + <d:supportedlock /> + </d:prop> +</d:propfind>'; + + $this->sendRequest($xml); + + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"urn:DAV\"",$this->response->body); + $xml = simplexml_load_string($body); + $xml->registerXPathNamespace('d','urn:DAV'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supportedlock/d:lockentry'); + $this->assertEquals(2,count($data),'We expected two \'d:lockentry\' tags'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supportedlock/d:lockentry/d:lockscope'); + $this->assertEquals(2,count($data),'We expected two \'d:lockscope\' tags'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supportedlock/d:lockentry/d:locktype'); + $this->assertEquals(2,count($data),'We expected two \'d:locktype\' tags'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supportedlock/d:lockentry/d:lockscope/d:shared'); + $this->assertEquals(1,count($data),'We expected a \'d:shared\' tag'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supportedlock/d:lockentry/d:lockscope/d:exclusive'); + $this->assertEquals(1,count($data),'We expected a \'d:exclusive\' tag'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supportedlock/d:lockentry/d:locktype/d:write'); + $this->assertEquals(2,count($data),'We expected two \'d:write\' tags'); + } + + function testLockDiscovery() { + + $xml = '<?xml version="1.0"?> +<d:propfind xmlns:d="DAV:"> + <d:prop> + <d:lockdiscovery /> + </d:prop> +</d:propfind>'; + + $this->sendRequest($xml); + + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"urn:DAV\"",$this->response->body); + $xml = simplexml_load_string($body); + $xml->registerXPathNamespace('d','urn:DAV'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:lockdiscovery'); + $this->assertEquals(1,count($data),'We expected a \'d:lockdiscovery\' tag'); + + } + + function testUnknownProperty() { + + $xml = '<?xml version="1.0"?> +<d:propfind xmlns:d="DAV:"> + <d:prop> + <d:macaroni /> + </d:prop> +</d:propfind>'; + + $this->sendRequest($xml); + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"urn:DAV\"",$this->response->body); + $xml = simplexml_load_string($body); + $xml->registerXPathNamespace('d','urn:DAV'); + $pathTests = array( + '/d:multistatus', + '/d:multistatus/d:response', + '/d:multistatus/d:response/d:propstat', + '/d:multistatus/d:response/d:propstat/d:status', + '/d:multistatus/d:response/d:propstat/d:prop', + '/d:multistatus/d:response/d:propstat/d:prop/d:macaroni', + ); + foreach($pathTests as $test) { + $this->assertTrue(count($xml->xpath($test))==true,'We expected the ' . $test . ' element to appear in the response, we got: ' . $body); + } + + $val = $xml->xpath('/d:multistatus/d:response/d:propstat/d:status'); + $this->assertEquals(1,count($val),$body); + $this->assertEquals('HTTP/1.1 404 Not Found',(string)$val[0]); + + } + + function testParsePropPatchRequest() { + + $body = '<?xml version="1.0"?> +<d:propertyupdate xmlns:d="DAV:" xmlns:s="http://sabredav.org/NS/test"> + <d:set><d:prop><s:someprop>somevalue</s:someprop></d:prop></d:set> + <d:remove><d:prop><s:someprop2 /></d:prop></d:remove> + <d:set><d:prop><s:someprop3>removeme</s:someprop3></d:prop></d:set> + <d:remove><d:prop><s:someprop3 /></d:prop></d:remove> +</d:propertyupdate>'; + + $result = $this->server->xml->parse($body); + $this->assertEquals([ + '{http://sabredav.org/NS/test}someprop' => 'somevalue', + '{http://sabredav.org/NS/test}someprop2' => null, + '{http://sabredav.org/NS/test}someprop3' => null, + ], $result->properties); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerRangeTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerRangeTest.php new file mode 100644 index 0000000..c8c236b --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerRangeTest.php @@ -0,0 +1,281 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\HTTP; + +require_once 'Sabre/DAV/AbstractServer.php'; + +class ServerRangeTest extends AbstractServer{ + + protected function getRootNode() { + + return new FSExt\Directory(SABRE_TEMPDIR); + + } + + function testRange() { + + $request = new HTTP\Request( + 'GET', + '/test.txt', + ['Range' => 'Bytes=2-5'] + ); + $filename = SABRE_TEMPDIR . '/test.txt'; + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/octet-stream'], + 'Content-Length' => [4], + 'Content-Range' => ['bytes 2-5/13'], + 'Last-Modified' => [HTTP\Util::toHTTPDate(new \DateTime('@' . filemtime($this->tempDir . '/test.txt')))], + 'ETag' => ['"' . sha1(fileinode($filename) . filesize($filename) . filemtime($filename)) . '"'], + ], + $this->response->getHeaders() + ); + + $this->assertEquals(206, $this->response->status); + $this->assertEquals('st c', stream_get_contents($this->response->body, 4)); + + } + + /** + * @depends testRange + */ + function testStartRange() { + + $request = new HTTP\Request( + 'GET', + '/test.txt', + ['Range' => 'bytes=2-'] + ); + $filename = SABRE_TEMPDIR . '/test.txt'; + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/octet-stream'], + 'Content-Length' => [11], + 'Content-Range' => ['bytes 2-12/13'], + 'Last-Modified' => [HTTP\Util::toHTTPDate(new \DateTime('@' . filemtime($this->tempDir . '/test.txt')))], + 'ETag' => ['"' . sha1(fileinode($filename) . filesize($filename) . filemtime($filename)) . '"'], + ], + $this->response->getHeaders() + ); + + $this->assertEquals(206, $this->response->status); + $this->assertEquals('st contents', stream_get_contents($this->response->body, 11)); + + } + + /** + * @depends testRange + */ + function testEndRange() { + + $request = new HTTP\Request( + 'GET', + '/test.txt', + ['Range' => 'bytes=-8'] + ); + $filename = SABRE_TEMPDIR . '/test.txt'; + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/octet-stream'], + 'Content-Length' => [8], + 'Content-Range' => ['bytes 5-12/13'], + 'Last-Modified' => [HTTP\Util::toHTTPDate(new \DateTime('@' . filemtime($this->tempDir . '/test.txt')))], + 'ETag' => ['"' . sha1(fileinode($filename) . filesize($filename) . filemtime($filename)) . '"'], + ], + $this->response->getHeaders() + ); + + $this->assertEquals(206, $this->response->status); + $this->assertEquals('contents', stream_get_contents($this->response->body, 8)); + + } + + /** + * @depends testRange + */ + function testTooHighRange() { + + $request = new HTTP\Request( + 'GET', + '/test.txt', + ['Range' => 'bytes=100-200'] + ); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(416, $this->response->status); + + } + + /** + * @depends testRange + */ + function testCrazyRange() { + + $request = new HTTP\Request( + 'GET', + '/test.txt', + ['Range' => 'bytes=8-4'] + ); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(416, $this->response->status); + + } + + /** + * @depends testRange + */ + function testIfRangeEtag() { + + $node = $this->server->tree->getNodeForPath('test.txt'); + + $request = new HTTP\Request( + 'GET', + '/test.txt', + ['Range' => 'bytes=2-5'] + ); + $filename = SABRE_TEMPDIR . '/test.txt'; + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/octet-stream'], + 'Content-Length' => [4], + 'Content-Range' => ['bytes 2-5/13'], + 'Last-Modified' => [HTTP\Util::toHTTPDate(new \DateTime('@' . filemtime($this->tempDir . '/test.txt')))], + 'ETag' => ['"' . sha1(fileinode($filename) . filesize($filename) . filemtime($filename)) . '"'], + ], + $this->response->getHeaders() + ); + + $this->assertEquals(206, $this->response->status); + $this->assertEquals('st c', stream_get_contents($this->response->body, 4)); + + } + + /** + * @depends testRange + */ + function testIfRangeEtagIncorrect() { + + $node = $this->server->tree->getNodeForPath('test.txt'); + + $request = new HTTP\Request( + 'GET', + '/test.txt', + [ + 'Range' => 'bytes=2-5', + 'If-Range' => $node->getEtag() . 'blabla' + ] + ); + $filename = SABRE_TEMPDIR . '/test.txt'; + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/octet-stream'], + 'Content-Length' => [13], + 'Last-Modified' => [HTTP\Util::toHTTPDate(new \DateTime('@' . filemtime($this->tempDir . '/test.txt')))], + 'ETag' => ['"' . sha1(fileinode($filename) . filesize($filename) . filemtime($filename)) . '"'], + ], + $this->response->getHeaders() + ); + + $this->assertEquals(200, $this->response->status); + $this->assertEquals('Test contents', stream_get_contents($this->response->body)); + + } + + /** + * @depends testRange + */ + function testIfRangeModificationDate() { + + $node = $this->server->tree->getNodeForPath('test.txt'); + + $request = new HTTP\Request( + 'GET', + '/test.txt', + [ + 'Range' => 'bytes=2-5', + 'If-Range' => 'tomorrow', + ] + ); + $filename = SABRE_TEMPDIR . '/test.txt'; + + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/octet-stream'], + 'Content-Length' => [4], + 'Content-Range' => ['bytes 2-5/13'], + 'Last-Modified' => [HTTP\Util::toHTTPDate(new \DateTime('@' . filemtime($this->tempDir . '/test.txt')))], + 'ETag' => ['"' . sha1(fileinode($filename) . filesize($filename) . filemtime($filename)) . '"'], + ], + $this->response->getHeaders() + ); + + $this->assertEquals(206, $this->response->status); + $this->assertEquals('st c', stream_get_contents($this->response->body, 4)); + + } + + /** + * @depends testRange + */ + function testIfRangeModificationDateModified() { + + $node = $this->server->tree->getNodeForPath('test.txt'); + + $request = new HTTP\Request( + 'GET', + '/test.txt', + [ + 'Range' => 'bytes=2-5', + 'If-Range' => '-2 years', + ] + ); + + $filename = SABRE_TEMPDIR . '/test.txt'; + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/octet-stream'], + 'Content-Length' => [13], + 'Last-Modified' => [HTTP\Util::toHTTPDate(new \DateTime('@' . filemtime($this->tempDir . '/test.txt')))], + 'ETag' => ['"' . sha1(fileinode($filename) . filesize($filename) . filemtime($filename)) . '"'], + ], + $this->response->getHeaders() + ); + + $this->assertEquals(200, $this->response->status); + $this->assertEquals('Test contents', stream_get_contents($this->response->body)); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerSimpleTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerSimpleTest.php new file mode 100644 index 0000000..99742bd --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerSimpleTest.php @@ -0,0 +1,462 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\HTTP; + +class ServerSimpleTest extends AbstractServer{ + + function testConstructArray() { + + $nodes = [ + new SimpleCollection('hello') + ]; + + $server = new Server($nodes); + $this->assertEquals($nodes[0], $server->tree->getNodeForPath('hello')); + + } + + /** + * @expectedException Sabre\DAV\Exception + */ + function testConstructIncorrectObj() { + + $nodes = [ + new SimpleCollection('hello'), + new \STDClass(), + ]; + + $server = new Server($nodes); + + } + + /** + * @expectedException Sabre\DAV\Exception + */ + function testConstructInvalidArg() { + + $server = new Server(1); + + } + + function testOptions() { + + $request = new HTTP\Request('OPTIONS', '/'); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals([ + 'DAV' => ['1, 3, extended-mkcol'], + 'MS-Author-Via' => ['DAV'], + 'Allow' => ['OPTIONS, GET, HEAD, DELETE, PROPFIND, PUT, PROPPATCH, COPY, MOVE, REPORT'], + 'Accept-Ranges' => ['bytes'], + 'Content-Length' => ['0'], + 'X-Sabre-Version' => [Version::VERSION], + ],$this->response->getHeaders()); + + $this->assertEquals(200, $this->response->status); + $this->assertEquals('', $this->response->body); + + } + + function testOptionsUnmapped() { + + $request = new HTTP\Request('OPTIONS', '/unmapped'); + $this->server->httpRequest = $request; + + $this->server->exec(); + + $this->assertEquals([ + 'DAV' => ['1, 3, extended-mkcol'], + 'MS-Author-Via' => ['DAV'], + 'Allow' => ['OPTIONS, GET, HEAD, DELETE, PROPFIND, PUT, PROPPATCH, COPY, MOVE, REPORT, MKCOL'], + 'Accept-Ranges' => ['bytes'], + 'Content-Length' => ['0'], + 'X-Sabre-Version' => [Version::VERSION], + ],$this->response->getHeaders()); + + $this->assertEquals(200, $this->response->status); + $this->assertEquals('', $this->response->body); + + } + + function testNonExistantMethod() { + + $serverVars = [ + 'REQUEST_URI' => '/', + 'REQUEST_METHOD' => 'BLABLA', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ],$this->response->getHeaders()); + + $this->assertEquals(501, $this->response->status); + + + } + + function testBaseUri() { + + $serverVars = [ + 'REQUEST_URI' => '/blabla/test.txt', + 'REQUEST_METHOD' => 'GET', + ]; + $filename = $this->tempDir . '/test.txt'; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->setBaseUri('/blabla/'); + $this->assertEquals('/blabla/',$this->server->getBaseUri()); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/octet-stream'], + 'Content-Length' => [13], + 'Last-Modified' => [HTTP\Util::toHTTPDate(new \DateTime('@' . filemtime($this->tempDir . '/test.txt')))], + 'ETag' => ['"' . sha1(fileinode($filename) . filesize($filename) . filemtime($filename)) . '"'], + ], + $this->response->getHeaders() + ); + + $this->assertEquals(200, $this->response->status); + $this->assertEquals('Test contents', stream_get_contents($this->response->body)); + + } + + function testBaseUriAddSlash() { + + $tests = [ + '/' => '/', + '/foo' => '/foo/', + '/foo/' => '/foo/', + '/foo/bar' => '/foo/bar/', + '/foo/bar/' => '/foo/bar/', + ]; + + foreach($tests as $test=>$result) { + $this->server->setBaseUri($test); + + $this->assertEquals($result, $this->server->getBaseUri()); + + } + + } + + function testCalculateUri() { + + $uris = [ + 'http://www.example.org/root/somepath', + '/root/somepath', + '/root/somepath/', + ]; + + $this->server->setBaseUri('/root/'); + + foreach($uris as $uri) { + + $this->assertEquals('somepath',$this->server->calculateUri($uri)); + + } + + $this->server->setBaseUri('/root'); + + foreach($uris as $uri) { + + $this->assertEquals('somepath',$this->server->calculateUri($uri)); + + } + + $this->assertEquals('', $this->server->calculateUri('/root')); + + } + + function testCalculateUriSpecialChars() { + + $uris = [ + 'http://www.example.org/root/%C3%A0fo%C3%B3', + '/root/%C3%A0fo%C3%B3', + '/root/%C3%A0fo%C3%B3/' + ]; + + $this->server->setBaseUri('/root/'); + + foreach($uris as $uri) { + + $this->assertEquals("\xc3\xa0fo\xc3\xb3",$this->server->calculateUri($uri)); + + } + + $this->server->setBaseUri('/root'); + + foreach($uris as $uri) { + + $this->assertEquals("\xc3\xa0fo\xc3\xb3",$this->server->calculateUri($uri)); + + } + + $this->server->setBaseUri('/'); + + foreach($uris as $uri) { + + $this->assertEquals("root/\xc3\xa0fo\xc3\xb3",$this->server->calculateUri($uri)); + + } + + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + */ + function testCalculateUriBreakout() { + + $uri = '/path1/'; + + $this->server->setBaseUri('/path2/'); + $this->server->calculateUri($uri); + + } + + /** + */ + function testGuessBaseUri() { + + $serverVars = [ + 'REQUEST_URI' => '/index.php/root', + 'PATH_INFO' => '/root', + ]; + + $httpRequest = HTTP\Sapi::createFromServerArray($serverVars); + $server = new Server(); + $server->httpRequest = $httpRequest; + + $this->assertEquals('/index.php/', $server->guessBaseUri()); + + } + + /** + * @depends testGuessBaseUri + */ + function testGuessBaseUriPercentEncoding() { + + $serverVars = [ + 'REQUEST_URI' => '/index.php/dir/path2/path%20with%20spaces', + 'PATH_INFO' => '/dir/path2/path with spaces', + ]; + + $httpRequest = HTTP\Sapi::createFromServerArray($serverVars); + $server = new Server(); + $server->httpRequest = $httpRequest; + + $this->assertEquals('/index.php/', $server->guessBaseUri()); + + } + + /** + * @depends testGuessBaseUri + */ + /* + function testGuessBaseUriPercentEncoding2() { + + $this->markTestIncomplete('This behaviour is not yet implemented'); + $serverVars = [ + 'REQUEST_URI' => '/some%20directory+mixed/index.php/dir/path2/path%20with%20spaces', + 'PATH_INFO' => '/dir/path2/path with spaces', + ]; + + $httpRequest = HTTP\Sapi::createFromServerArray($serverVars); + $server = new Server(); + $server->httpRequest = $httpRequest; + + $this->assertEquals('/some%20directory+mixed/index.php/', $server->guessBaseUri()); + + }*/ + + function testGuessBaseUri2() { + + $serverVars = [ + 'REQUEST_URI' => '/index.php/root/', + 'PATH_INFO' => '/root/', + ]; + + $httpRequest = HTTP\Sapi::createFromServerArray($serverVars); + $server = new Server(); + $server->httpRequest = $httpRequest; + + $this->assertEquals('/index.php/', $server->guessBaseUri()); + + } + + function testGuessBaseUriNoPathInfo() { + + $serverVars = [ + 'REQUEST_URI' => '/index.php/root', + ]; + + $httpRequest = HTTP\Sapi::createFromServerArray($serverVars); + $server = new Server(); + $server->httpRequest = $httpRequest; + + $this->assertEquals('/', $server->guessBaseUri()); + + } + + function testGuessBaseUriNoPathInfo2() { + + $serverVars = [ + 'REQUEST_URI' => '/a/b/c/test.php', + ]; + + $httpRequest = HTTP\Sapi::createFromServerArray($serverVars); + $server = new Server(); + $server->httpRequest = $httpRequest; + + $this->assertEquals('/', $server->guessBaseUri()); + + } + + + /** + * @depends testGuessBaseUri + */ + function testGuessBaseUriQueryString() { + + $serverVars = [ + 'REQUEST_URI' => '/index.php/root?query_string=blabla', + 'PATH_INFO' => '/root', + ]; + + $httpRequest = HTTP\Sapi::createFromServerArray($serverVars); + $server = new Server(); + $server->httpRequest = $httpRequest; + + $this->assertEquals('/index.php/', $server->guessBaseUri()); + + } + + /** + * @depends testGuessBaseUri + * @expectedException \Sabre\DAV\Exception + */ + function testGuessBaseUriBadConfig() { + + $serverVars = [ + 'REQUEST_URI' => '/index.php/root/heyyy', + 'PATH_INFO' => '/root', + ]; + + $httpRequest = HTTP\Sapi::createFromServerArray($serverVars); + $server = new Server(); + $server->httpRequest = $httpRequest; + + $server->guessBaseUri(); + + } + + function testTriggerException() { + + $serverVars = [ + 'REQUEST_URI' => '/', + 'REQUEST_METHOD' => 'FOO', + ]; + + $httpRequest = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = $httpRequest; + $this->server->on('beforeMethod', [$this,'exceptionTrigger']); + $this->server->exec(); + + $this->assertEquals([ + 'Content-Type' => ['application/xml; charset=utf-8'], + ],$this->response->getHeaders()); + + $this->assertEquals(500, $this->response->status); + + } + + function exceptionTrigger() { + + throw new Exception('Hola'); + + } + + function testReportNotFound() { + + $serverVars = [ + 'REQUEST_URI' => '/', + 'REQUEST_METHOD' => 'REPORT', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = ($request); + $this->server->httpRequest->setBody('<?xml version="1.0"?><bla:myreport xmlns:bla="http://www.rooftopsolutions.nl/NS"></bla:myreport>'); + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ], + $this->response->getHeaders() + ); + + $this->assertEquals(415, $this->response->status, 'We got an incorrect status back. Full response body follows: ' . $this->response->body); + + } + + function testReportIntercepted() { + + $serverVars = [ + 'REQUEST_URI' => '/', + 'REQUEST_METHOD' => 'REPORT', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $this->server->httpRequest = ($request); + $this->server->httpRequest->setBody('<?xml version="1.0"?><bla:myreport xmlns:bla="http://www.rooftopsolutions.nl/NS"></bla:myreport>'); + $this->server->on('report', [$this,'reportHandler']); + $this->server->exec(); + + $this->assertEquals([ + 'X-Sabre-Version' => [Version::VERSION], + 'testheader' => ['testvalue'], + ], + $this->response->getHeaders() + ); + + $this->assertEquals(418, $this->response->status,'We got an incorrect status back. Full response body follows: ' . $this->response->body); + + } + + function reportHandler($reportName) { + + if ($reportName=='{http://www.rooftopsolutions.nl/NS}myreport') { + $this->server->httpResponse->setStatus(418); + $this->server->httpResponse->setHeader('testheader','testvalue'); + return false; + } + else return; + + } + + function testGetPropertiesForChildren() { + + $result = $this->server->getPropertiesForChildren('',[ + '{DAV:}getcontentlength', + ]); + + $expected = [ + 'test.txt' => ['{DAV:}getcontentlength' => 13], + 'dir/' => [], + ]; + + $this->assertEquals($expected,$result); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerUpdatePropertiesTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerUpdatePropertiesTest.php new file mode 100644 index 0000000..dd7b134 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/ServerUpdatePropertiesTest.php @@ -0,0 +1,127 @@ +<?php + +namespace Sabre\DAV; +use Sabre\HTTP; + +class ServerUpdatePropertiesTest extends \PHPUnit_Framework_TestCase { + + function testUpdatePropertiesFail() { + + $tree = array( + new SimpleCollection('foo'), + ); + $server = new Server($tree); + + $result = $server->updateProperties('foo', array( + '{DAV:}foo' => 'bar' + )); + + $expected = array( + '{DAV:}foo' => 403, + ); + $this->assertEquals($expected, $result); + + } + + function testUpdatePropertiesProtected() { + + $tree = array( + new SimpleCollection('foo'), + ); + $server = new Server($tree); + + $server->on('propPatch', function($path, PropPatch $propPatch) { + $propPatch->handleRemaining(function() { return true; }); + }); + $result = $server->updateProperties('foo', array( + '{DAV:}getetag' => 'bla', + '{DAV:}foo' => 'bar' + )); + + $expected = array( + '{DAV:}getetag' => 403, + '{DAV:}foo' => 424, + ); + $this->assertEquals($expected, $result); + + } + + function testUpdatePropertiesEventFail() { + + $tree = array( + new SimpleCollection('foo'), + ); + $server = new Server($tree); + $server->on('propPatch', function($path, PropPatch $propPatch) { + $propPatch->setResultCode('{DAV:}foo', 404); + $propPatch->handleRemaining(function() { return true; }); + }); + + $result = $server->updateProperties('foo', array( + '{DAV:}foo' => 'bar', + '{DAV:}foo2' => 'bla', + )); + + $expected = array( + '{DAV:}foo' => 404, + '{DAV:}foo2' => 424, + ); + $this->assertEquals($expected, $result); + + } + + function updatePropFail(&$propertyDelta, &$result, $node) { + + $result[404] = array( + '{DAV:}foo' => null, + ); + unset($propertyDelta['{DAV:}foo']); + return false; + + } + + + function testUpdatePropertiesEventSuccess() { + + $tree = array( + new SimpleCollection('foo'), + ); + $server = new Server($tree); + $server->on('propPatch', function($path, PropPatch $propPatch) { + + $propPatch->handle(['{DAV:}foo', '{DAV:}foo2'], function() { + return [ + '{DAV:}foo' => 200, + '{DAV:}foo2' => 201, + ]; + }); + + }); + + $result = $server->updateProperties('foo', array( + '{DAV:}foo' => 'bar', + '{DAV:}foo2' => 'bla', + )); + + $expected = array( + '{DAV:}foo' => 200, + '{DAV:}foo2' => 201, + ); + $this->assertEquals($expected, $result); + + } + + function updatePropSuccess(&$propertyDelta, &$result, $node) { + + $result[200] = array( + '{DAV:}foo' => null, + ); + $result[201] = array( + '{DAV:}foo2' => null, + ); + unset($propertyDelta['{DAV:}foo']); + unset($propertyDelta['{DAV:}foo2']); + return; + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/SimpleFileTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/SimpleFileTest.php new file mode 100644 index 0000000..406c193 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/SimpleFileTest.php @@ -0,0 +1,19 @@ +<?php + +namespace Sabre\DAV; + +class SimpleFileTest extends \PHPUnit_Framework_TestCase { + + function testAll() { + + $file = new SimpleFile('filename.txt','contents','text/plain'); + + $this->assertEquals('filename.txt', $file->getName()); + $this->assertEquals('contents', $file->get()); + $this->assertEquals('8', $file->getSize()); + $this->assertEquals('"' . sha1('contents') . '"', $file->getETag()); + $this->assertEquals('text/plain', $file->getContentType()); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/StringUtilTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/StringUtilTest.php new file mode 100644 index 0000000..941d1f9 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/StringUtilTest.php @@ -0,0 +1,122 @@ +<?php + +namespace Sabre\DAV; + +class StringUtilTest extends \PHPUnit_Framework_TestCase { + + /** + * @dataProvider dataset + */ + function testTextMatch($haystack, $needle, $collation, $matchType, $result) { + + $this->assertEquals($result, StringUtil::textMatch($haystack, $needle, $collation, $matchType)); + + } + + function dataset() { + + return array( + array('FOOBAR', 'FOO', 'i;octet', 'contains', true), + array('FOOBAR', 'foo', 'i;octet', 'contains', false), + array('FÖÖBAR', 'FÖÖ', 'i;octet', 'contains', true), + array('FÖÖBAR', 'föö', 'i;octet', 'contains', false), + array('FOOBAR', 'FOOBAR', 'i;octet', 'equals', true), + array('FOOBAR', 'fooBAR', 'i;octet', 'equals', false), + array('FOOBAR', 'FOO', 'i;octet', 'starts-with', true), + array('FOOBAR', 'foo', 'i;octet', 'starts-with', false), + array('FOOBAR', 'BAR', 'i;octet', 'starts-with', false), + array('FOOBAR', 'bar', 'i;octet', 'starts-with', false), + array('FOOBAR', 'FOO', 'i;octet', 'ends-with', false), + array('FOOBAR', 'foo', 'i;octet', 'ends-with', false), + array('FOOBAR', 'BAR', 'i;octet', 'ends-with', true), + array('FOOBAR', 'bar', 'i;octet', 'ends-with', false), + + array('FOOBAR', 'FOO', 'i;ascii-casemap', 'contains', true), + array('FOOBAR', 'foo', 'i;ascii-casemap', 'contains', true), + array('FÖÖBAR', 'FÖÖ', 'i;ascii-casemap', 'contains', true), + array('FÖÖBAR', 'föö', 'i;ascii-casemap', 'contains', false), + array('FOOBAR', 'FOOBAR', 'i;ascii-casemap', 'equals', true), + array('FOOBAR', 'fooBAR', 'i;ascii-casemap', 'equals', true), + array('FOOBAR', 'FOO', 'i;ascii-casemap', 'starts-with', true), + array('FOOBAR', 'foo', 'i;ascii-casemap', 'starts-with', true), + array('FOOBAR', 'BAR', 'i;ascii-casemap', 'starts-with', false), + array('FOOBAR', 'bar', 'i;ascii-casemap', 'starts-with', false), + array('FOOBAR', 'FOO', 'i;ascii-casemap', 'ends-with', false), + array('FOOBAR', 'foo', 'i;ascii-casemap', 'ends-with', false), + array('FOOBAR', 'BAR', 'i;ascii-casemap', 'ends-with', true), + array('FOOBAR', 'bar', 'i;ascii-casemap', 'ends-with', true), + + array('FOOBAR', 'FOO', 'i;unicode-casemap', 'contains', true), + array('FOOBAR', 'foo', 'i;unicode-casemap', 'contains', true), + array('FÖÖBAR', 'FÖÖ', 'i;unicode-casemap', 'contains', true), + array('FÖÖBAR', 'föö', 'i;unicode-casemap', 'contains', true), + array('FOOBAR', 'FOOBAR', 'i;unicode-casemap', 'equals', true), + array('FOOBAR', 'fooBAR', 'i;unicode-casemap', 'equals', true), + array('FOOBAR', 'FOO', 'i;unicode-casemap', 'starts-with', true), + array('FOOBAR', 'foo', 'i;unicode-casemap', 'starts-with', true), + array('FOOBAR', 'BAR', 'i;unicode-casemap', 'starts-with', false), + array('FOOBAR', 'bar', 'i;unicode-casemap', 'starts-with', false), + array('FOOBAR', 'FOO', 'i;unicode-casemap', 'ends-with', false), + array('FOOBAR', 'foo', 'i;unicode-casemap', 'ends-with', false), + array('FOOBAR', 'BAR', 'i;unicode-casemap', 'ends-with', true), + array('FOOBAR', 'bar', 'i;unicode-casemap', 'ends-with', true), + ); + + } + + /** + * @expectedException Sabre\DAV\Exception\BadRequest + */ + public function testBadCollation() { + + StringUtil::textMatch('foobar','foo','blabla','contains'); + + } + + + /** + * @expectedException Sabre\DAV\Exception\BadRequest + */ + public function testBadMatchType() { + + StringUtil::textMatch('foobar','foo','i;octet','booh'); + + } + + public function testEnsureUTF8_ascii() { + + $inputString = "harkema"; + $outputString = "harkema"; + + $this->assertEquals( + $outputString, + StringUtil::ensureUTF8($inputString) + ); + + } + + public function testEnsureUTF8_latin1() { + + $inputString = "m\xfcnster"; + $outputString = "münster"; + + $this->assertEquals( + $outputString, + StringUtil::ensureUTF8($inputString) + ); + + } + + public function testEnsureUTF8_utf8() { + + $inputString = "m\xc3\xbcnster"; + $outputString = "münster"; + + $this->assertEquals( + $outputString, + StringUtil::ensureUTF8($inputString) + ); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Sync/MockSyncCollection.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Sync/MockSyncCollection.php new file mode 100644 index 0000000..81a4ac6 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Sync/MockSyncCollection.php @@ -0,0 +1,169 @@ +<?php + +namespace Sabre\DAV\Sync; + +use Sabre\DAV; + +/** + * This mocks a ISyncCollection, for unittesting. + * + * This object behaves the same as SimpleCollection. Call addChange to update + * the 'changelog' that this class uses for the collection. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class MockSyncCollection extends DAV\SimpleCollection implements ISyncCollection { + + public $changeLog = []; + + public $token = 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 + */ + public function getSyncToken() { + + // Will be 'null' in the first round, and will increment ever after. + return $this->token; + + } + + public function addChange(array $added, array $modified, array $deleted) { + + $this->token++; + $this->changeLog[$this->token] = [ + 'added' => $added, + 'modified' => $modified, + 'deleted' => $deleted, + ]; + + } + + /** + * 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: + * + * array( + * 'syncToken' => 'The current synctoken', + * 'modified' => array( + * 'new.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 + */ + public function getChanges($syncToken, $syncLevel, $limit = null) { + + // This is an initial sync + if (is_null($syncToken)) { + return [ + 'added' => array_map( + function($item) { + return $item->getName(); + }, $this->getChildren() + ), + 'modified' => [], + 'deleted' => [], + 'syncToken' => $this->getSyncToken(), + ]; + } + + if (!is_int($syncToken) && !ctype_digit($syncToken)) { + + return null; + + } + if (is_null($this->token)) return null; + + $added = []; + $modified = []; + $deleted = []; + + foreach($this->changeLog as $token=>$change) { + + if ($token > $syncToken) { + + $added = array_merge($added, $change['added']); + $modified = array_merge($modified, $change['modified']); + $deleted = array_merge($deleted, $change['deleted']); + + if ($limit) { + // If there's a limit, we may need to cut things off. + // This alghorithm is weird and stupid, but it works. + $left = $limit - (count($modified) + count($deleted)); + if ($left>0) continue; + if ($left===0) break; + if ($left<0) { + $modified = array_slice($modified, 0, $left); + } + $left = $limit - (count($modified) + count($deleted)); + if ($left===0) break; + if ($left<0) { + $deleted = array_slice($deleted, 0, $left); + } + break; + + } + + } + + } + + return array( + 'syncToken' => $this->token, + 'added' => $added, + 'modified' => $modified, + 'deleted' => $deleted, + ); + + } + + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Sync/PluginTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Sync/PluginTest.php new file mode 100644 index 0000000..49e8741 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Sync/PluginTest.php @@ -0,0 +1,524 @@ +<?php + +namespace Sabre\DAV\Sync; + +use + Sabre\DAV, + Sabre\HTTP; + +require_once __DIR__ . '/MockSyncCollection.php'; + +class PluginTest extends \Sabre\DAVServerTest { + + protected $collection; + + function setUp() { + + parent::setUp(); + $this->server->addPlugin(new Plugin()); + + } + + function testGetInfo() { + + $this->assertArrayHasKey( + 'name', + (new Plugin())->getPluginInfo() + ); + + } + + function setUpTree() { + + $this->collection = + new MockSyncCollection('coll', [ + new DAV\SimpleFile('file1.txt','foo'), + new DAV\SimpleFile('file2.txt','bar'), + ]); + $this->tree = [ + $this->collection, + new DAV\SimpleCollection('normalcoll', []) + ]; + + } + + function testSupportedReportSet() { + + $result = $this->server->getProperties('/coll', ['{DAV:}supported-report-set']); + $this->assertFalse($result['{DAV:}supported-report-set']->has('{DAV:}sync-collection')); + + // Making a change + $this->collection->addChange(['file1.txt'], [], []); + + $result = $this->server->getProperties('/coll', ['{DAV:}supported-report-set']); + $this->assertTrue($result['{DAV:}supported-report-set']->has('{DAV:}sync-collection')); + + } + + function testGetSyncToken() { + + $result = $this->server->getProperties('/coll', ['{DAV:}sync-token']); + $this->assertFalse(isset($result['{DAV:}sync-token'])); + + // Making a change + $this->collection->addChange(['file1.txt'], [], []); + + $result = $this->server->getProperties('/coll', ['{DAV:}sync-token']); + $this->assertTrue(isset($result['{DAV:}sync-token'])); + + // non-sync-enabled collection + $this->collection->addChange(['file1.txt'], [], []); + + $result = $this->server->getProperties('/normalcoll', ['{DAV:}sync-token']); + $this->assertFalse(isset($result['{DAV:}sync-token'])); + } + + function testSyncInitialSyncCollection() { + + // Making a change + $this->collection->addChange(['file1.txt'], [], []); + + $request = new HTTP\Request('REPORT', '/coll/', ['Content-Type' => 'application/xml']); + + $body = <<<BLA +<?xml version="1.0" encoding="utf-8" ?> +<D:sync-collection xmlns:D="DAV:"> + <D:sync-token/> + <D:sync-level>1</D:sync-level> + <D:prop> + <D:getcontentlength/> + </D:prop> +</D:sync-collection> +BLA; + + $request->setBody($body); + + $response = $this->request($request); + + $this->assertEquals(207, $response->status, 'Full response body:' . $response->body); + + $multiStatus = $this->server->xml->parse($response->getBodyAsString()); + + // Checking the sync-token + $this->assertEquals( + 'http://sabre.io/ns/sync/1', + $multiStatus->getSyncToken() + ); + + $responses = $multiStatus->getResponses(); + $this->assertEquals(2, count($responses), 'We expected exactly 2 {DAV:}response'); + + $response = $responses[0]; + + $this->assertNull($response->getHttpStatus()); + $this->assertEquals('/coll/file1.txt', $response->getHref()); + $this->assertEquals([ + 200 => [ + '{DAV:}getcontentlength' => 3, + ] + ], $response->getResponseProperties()); + + $response = $responses[1]; + + $this->assertNull($response->getHttpStatus()); + $this->assertEquals('/coll/file2.txt', $response->getHref()); + $this->assertEquals([ + 200 => [ + '{DAV:}getcontentlength' => 3, + ] + ], $response->getResponseProperties()); + + } + + function testSubsequentSyncSyncCollection() { + + // Making a change + $this->collection->addChange(['file1.txt'], [], []); + // Making another change + $this->collection->addChange([], ['file2.txt'], ['file3.txt']); + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/coll/', + 'CONTENT_TYPE' => 'application/xml', + ]); + + $body = <<<BLA +<?xml version="1.0" encoding="utf-8" ?> +<D:sync-collection xmlns:D="DAV:"> + <D:sync-token>http://sabre.io/ns/sync/1</D:sync-token> + <D:sync-level>infinite</D:sync-level> + <D:prop> + <D:getcontentlength/> + </D:prop> +</D:sync-collection> +BLA; + + $request->setBody($body); + + $response = $this->request($request); + + $this->assertEquals(207, $response->status, 'Full response body:' . $response->body); + + $multiStatus = $this->server->xml->parse($response->getBodyAsString()); + + // Checking the sync-token + $this->assertEquals( + 'http://sabre.io/ns/sync/2', + $multiStatus->getSyncToken() + ); + + $responses = $multiStatus->getResponses(); + $this->assertEquals(2, count($responses), 'We expected exactly 2 {DAV:}response'); + + $response = $responses[0]; + + $this->assertNull($response->getHttpStatus()); + $this->assertEquals('/coll/file2.txt', $response->getHref()); + $this->assertEquals([ + 200 => [ + '{DAV:}getcontentlength' => 3, + ] + ], $response->getResponseProperties()); + + $response = $responses[1]; + + $this->assertEquals('404', $response->getHttpStatus()); + $this->assertEquals('/coll/file3.txt', $response->getHref()); + $this->assertEquals([], $response->getResponseProperties()); + + } + + function testSubsequentSyncSyncCollectionLimit() { + + // Making a change + $this->collection->addChange(['file1.txt'], [], []); + // Making another change + $this->collection->addChange([], ['file2.txt'], ['file3.txt']); + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/coll/', + 'CONTENT_TYPE' => 'application/xml', + ]); + + $body = <<<BLA +<?xml version="1.0" encoding="utf-8" ?> +<D:sync-collection xmlns:D="DAV:"> + <D:sync-token>http://sabre.io/ns/sync/1</D:sync-token> + <D:sync-level>infinite</D:sync-level> + <D:prop> + <D:getcontentlength/> + </D:prop> + <D:limit><D:nresults>1</D:nresults></D:limit> +</D:sync-collection> +BLA; + + $request->setBody($body); + + $response = $this->request($request); + + $this->assertEquals(207, $response->status, 'Full response body:' . $response->body); + + $multiStatus = $this->server->xml->parse( + $response->getBodyAsString() + ); + + // Checking the sync-token + $this->assertEquals( + 'http://sabre.io/ns/sync/2', + $multiStatus->getSyncToken() + ); + + $responses = $multiStatus->getResponses(); + $this->assertEquals(1, count($responses), 'We expected exactly 1 {DAV:}response'); + + $response = $responses[0]; + + $this->assertEquals('404', $response->getHttpStatus()); + $this->assertEquals('/coll/file3.txt', $response->getHref()); + $this->assertEquals([], $response->getResponseProperties()); + + } + + function testSubsequentSyncSyncCollectionDepthFallBack() { + + // Making a change + $this->collection->addChange(['file1.txt'], [], []); + // Making another change + $this->collection->addChange([], ['file2.txt'], ['file3.txt']); + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/coll/', + 'CONTENT_TYPE' => 'application/xml', + 'HTTP_DEPTH' => "1", + ]); + + $body = <<<BLA +<?xml version="1.0" encoding="utf-8" ?> +<D:sync-collection xmlns:D="DAV:"> + <D:sync-token>http://sabre.io/ns/sync/1</D:sync-token> + <D:prop> + <D:getcontentlength/> + </D:prop> +</D:sync-collection> +BLA; + + $request->setBody($body); + + $response = $this->request($request); + + $this->assertEquals(207, $response->status, 'Full response body:' . $response->body); + + $multiStatus = $this->server->xml->parse( + $response->getBodyAsString() + ); + + // Checking the sync-token + $this->assertEquals( + 'http://sabre.io/ns/sync/2', + $multiStatus->getSyncToken() + ); + + $responses = $multiStatus->getResponses(); + $this->assertEquals(2, count($responses), 'We expected exactly 2 {DAV:}response'); + + $response = $responses[0]; + + $this->assertNull($response->getHttpStatus()); + $this->assertEquals('/coll/file2.txt', $response->getHref()); + $this->assertEquals([ + 200 => [ + '{DAV:}getcontentlength' => 3, + ] + ], $response->getResponseProperties()); + + $response = $responses[1]; + + $this->assertEquals('404', $response->getHttpStatus()); + $this->assertEquals('/coll/file3.txt', $response->getHref()); + $this->assertEquals([], $response->getResponseProperties()); + + } + + function testSyncNoSyncInfo() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/coll/', + 'CONTENT_TYPE' => 'application/xml', + ]); + + $body = <<<BLA +<?xml version="1.0" encoding="utf-8" ?> +<D:sync-collection xmlns:D="DAV:"> + <D:sync-token/> + <D:sync-level>1</D:sync-level> + <D:prop> + <D:getcontentlength/> + </D:prop> +</D:sync-collection> +BLA; + + $request->setBody($body); + + $response = $this->request($request); + + // The default state has no sync-token, so this report should not yet + // be supported. + $this->assertEquals(415, $response->status, 'Full response body:' . $response->body); + + } + + function testSyncNoSyncCollection() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/normalcoll/', + 'CONTENT_TYPE' => 'application/xml', + ]); + + $body = <<<BLA +<?xml version="1.0" encoding="utf-8" ?> +<D:sync-collection xmlns:D="DAV:"> + <D:sync-token/> + <D:sync-level>1</D:sync-level> + <D:prop> + <D:getcontentlength/> + </D:prop> +</D:sync-collection> +BLA; + + $request->setBody($body); + + $response = $this->request($request); + + // The default state has no sync-token, so this report should not yet + // be supported. + $this->assertEquals(415, $response->status, 'Full response body:' . $response->body); + + } + + function testSyncInvalidToken() { + + $this->collection->addChange(['file1.txt'], [], []); + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/coll/', + 'CONTENT_TYPE' => 'application/xml', + ]); + + $body = <<<BLA +<?xml version="1.0" encoding="utf-8" ?> +<D:sync-collection xmlns:D="DAV:"> + <D:sync-token>http://sabre.io/ns/sync/invalid</D:sync-token> + <D:sync-level>1</D:sync-level> + <D:prop> + <D:getcontentlength/> + </D:prop> +</D:sync-collection> +BLA; + + $request->setBody($body); + + $response = $this->request($request); + + // The default state has no sync-token, so this report should not yet + // be supported. + $this->assertEquals(403, $response->status, 'Full response body:' . $response->body); + + } + function testSyncInvalidTokenNoPrefix() { + + $this->collection->addChange(['file1.txt'], [], []); + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/coll/', + 'CONTENT_TYPE' => 'application/xml', + ]); + + $body = <<<BLA +<?xml version="1.0" encoding="utf-8" ?> +<D:sync-collection xmlns:D="DAV:"> + <D:sync-token>invalid</D:sync-token> + <D:sync-level>1</D:sync-level> + <D:prop> + <D:getcontentlength/> + </D:prop> +</D:sync-collection> +BLA; + + $request->setBody($body); + + $response = $this->request($request); + + // The default state has no sync-token, so this report should not yet + // be supported. + $this->assertEquals(403, $response->status, 'Full response body:' . $response->body); + + } + + function testSyncNoSyncToken() { + + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/coll/', + 'CONTENT_TYPE' => 'application/xml', + ]); + + $body = <<<BLA +<?xml version="1.0" encoding="utf-8" ?> +<D:sync-collection xmlns:D="DAV:"> + <D:sync-level>1</D:sync-level> + <D:prop> + <D:getcontentlength/> + </D:prop> +</D:sync-collection> +BLA; + + $request->setBody($body); + + $response = $this->request($request); + + // The default state has no sync-token, so this report should not yet + // be supported. + $this->assertEquals(400, $response->status, 'Full response body:' . $response->body); + + } + + function testSyncNoProp() { + + $this->collection->addChange(['file1.txt'], [], []); + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'REPORT', + 'REQUEST_URI' => '/coll/', + 'CONTENT_TYPE' => 'application/xml', + ]); + + $body = <<<BLA +<?xml version="1.0" encoding="utf-8" ?> +<D:sync-collection xmlns:D="DAV:"> + <D:sync-token /> + <D:sync-level>1</D:sync-level> +</D:sync-collection> +BLA; + + $request->setBody($body); + + $response = $this->request($request); + + // The default state has no sync-token, so this report should not yet + // be supported. + $this->assertEquals(400, $response->status, 'Full response body:' . $response->body); + + } + + function testIfConditions() { + + $this->collection->addChange(['file1.txt'], [], []); + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'DELETE', + 'REQUEST_URI' => '/coll/file1.txt', + 'HTTP_IF' => '</coll> (<http://sabre.io/ns/sync/1>)', + ]); + $response = $this->request($request); + + // If a 403 is thrown this works correctly. The file in questions + // doesn't allow itself to be deleted. + // If the If conditions failed, it would have been a 412 instead. + $this->assertEquals(403, $response->status); + + } + + function testIfConditionsNot() { + + $this->collection->addChange(['file1.txt'], [], []); + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'DELETE', + 'REQUEST_URI' => '/coll/file1.txt', + 'HTTP_IF' => '</coll> (Not <http://sabre.io/ns/sync/2>)', + ]); + $response = $this->request($request); + + // If a 403 is thrown this works correctly. The file in questions + // doesn't allow itself to be deleted. + // If the If conditions failed, it would have been a 412 instead. + $this->assertEquals(403, $response->status); + + } + + function testIfConditionsNoSyncToken() { + + $this->collection->addChange(['file1.txt'], [], []); + $request = HTTP\Sapi::createFromServerArray([ + 'REQUEST_METHOD' => 'DELETE', + 'REQUEST_URI' => '/coll/file1.txt', + 'HTTP_IF' => '</coll> (<opaquelocktoken:foo>)', + ]); + $response = $this->request($request); + + $this->assertEquals(412, $response->status); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/SyncTokenPropertyTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/SyncTokenPropertyTest.php new file mode 100644 index 0000000..b509508 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/SyncTokenPropertyTest.php @@ -0,0 +1,100 @@ +<?php + +namespace Sabre\DAV; + +class SyncTokenPropertyTest extends \Sabre\DAVServerTest { + + /** + * The assumption in these tests is that a PROPFIND is going on, and to + * fetch the sync-token, the event handler is just able to use the existing + * result. + * + * @dataProvider data + */ + function testAlreadyThere1($name, $value) { + + $propFind = new PropFind('foo', [ + '{http://calendarserver.org/ns/}getctag', + $name, + ]); + + $propFind->set($name, $value); + $corePlugin = new CorePlugin(); + $corePlugin->propFindLate($propFind, new SimpleCollection('hi')); + + $this->assertEquals("hello", $propFind->get('{http://calendarserver.org/ns/}getctag')); + + } + + /** + * In these test-cases, the plugin is forced to do a local propfind to + * fetch the items. + * + * @dataProvider data + */ + function testRefetch($name, $value) { + + $this->server->tree = new Tree( + new SimpleCollection('root', [ + new Mock\PropertiesCollection( + 'foo', + [], + [$name => $value] + ) + ]) + ); + $propFind = new PropFind('foo', [ + '{http://calendarserver.org/ns/}getctag', + $name, + ]); + + $corePlugin = $this->server->getPlugin('core'); + $corePlugin->propFindLate($propFind, new SimpleCollection('hi')); + + $this->assertEquals("hello", $propFind->get('{http://calendarserver.org/ns/}getctag')); + + } + + function testNoData() { + + $this->server->tree = new Tree( + new SimpleCollection('root', [ + new Mock\PropertiesCollection( + 'foo', + [], + [] + ) + ]) + ); + + $propFind = new PropFind('foo', [ + '{http://calendarserver.org/ns/}getctag', + ]); + + $corePlugin = $this->server->getPlugin('core'); + $corePlugin->propFindLate($propFind, new SimpleCollection('hi')); + + $this->assertNull($propFind->get('{http://calendarserver.org/ns/}getctag')); + + } + + function data() { + + return [ + [ + '{http://sabredav.org/ns}sync-token', + "hello" + ], + [ + '{DAV:}sync-token', + "hello" + ], + [ + '{DAV:}sync-token', + new Xml\Property\Href(Sync\Plugin::SYNCTOKEN_PREFIX . "hello", false) + ] + ]; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/TemporaryFileFilterTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/TemporaryFileFilterTest.php new file mode 100644 index 0000000..7122f4a --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/TemporaryFileFilterTest.php @@ -0,0 +1,199 @@ +<?php + +namespace Sabre\DAV; + +use Sabre\HTTP; + +class TemporaryFileFilterTest extends AbstractServer { + + function setUp() { + + parent::setUp(); + $plugin = new TemporaryFileFilterPlugin(SABRE_TEMPDIR . '/tff'); + $this->server->addPlugin($plugin); + + } + + function testPutNormal() { + + $request = new HTTP\Request('PUT', '/testput.txt', [], 'Testing new file'); + + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals('', $this->response->body); + $this->assertEquals(201, $this->response->status); + $this->assertEquals('0', $this->response->getHeader('Content-Length')); + + $this->assertEquals('Testing new file',file_get_contents(SABRE_TEMPDIR . '/testput.txt')); + + } + + function testPutTemp() { + + // mimicking an OS/X resource fork + $request = new HTTP\Request('PUT', '/._testput.txt', [], 'Testing new file'); + + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals('', $this->response->body); + $this->assertEquals(201, $this->response->status); + $this->assertEquals(array( + 'X-Sabre-Temp' => ['true'], + ),$this->response->getHeaders()); + + $this->assertFalse(file_exists(SABRE_TEMPDIR . '/._testput.txt'),'._testput.txt should not exist in the regular file structure.'); + + } + + function testPutTempIfNoneMatch() { + + // mimicking an OS/X resource fork + $request = new HTTP\Request('PUT', '/._testput.txt', ['If-None-Match' => '*'], 'Testing new file'); + + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals('', $this->response->body); + $this->assertEquals(201, $this->response->status); + $this->assertEquals(array( + 'X-Sabre-Temp' => ['true'], + ),$this->response->getHeaders()); + + $this->assertFalse(file_exists(SABRE_TEMPDIR . '/._testput.txt'),'._testput.txt should not exist in the regular file structure.'); + + + $this->server->exec(); + + $this->assertEquals(412, $this->response->status); + $this->assertEquals(array( + 'X-Sabre-Temp' => ['true'], + 'Content-Type' => ['application/xml; charset=utf-8'], + ),$this->response->getHeaders()); + + } + + function testPutGet() { + + // mimicking an OS/X resource fork + $request = new HTTP\Request('PUT', '/._testput.txt', [], 'Testing new file'); + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals('', $this->response->body); + $this->assertEquals(201, $this->response->status); + $this->assertEquals(array( + 'X-Sabre-Temp' => ['true'], + ),$this->response->getHeaders()); + + $request = new HTTP\Request('GET', '/._testput.txt'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(200, $this->response->status); + $this->assertEquals(array( + 'X-Sabre-Temp' => ['true'], + 'Content-Length' => [16], + 'Content-Type' => ['application/octet-stream'], + ),$this->response->getHeaders()); + + $this->assertEquals('Testing new file',stream_get_contents($this->response->body)); + + } + + function testLockNonExistant() { + + mkdir(SABRE_TEMPDIR . '/locksdir'); + $locksBackend = new Locks\Backend\File(SABRE_TEMPDIR . '/locks'); + $locksPlugin = new Locks\Plugin($locksBackend); + $this->server->addPlugin($locksPlugin); + + // mimicking an OS/X resource fork + $request = new HTTP\Request('LOCK', '/._testput.txt'); + $request->setBody('<?xml version="1.0"?> +<D:lockinfo xmlns:D="DAV:"> + <D:lockscope><D:exclusive/></D:lockscope> + <D:locktype><D:write/></D:locktype> + <D:owner> + <D:href>http://example.org/~ejw/contact.html</D:href> + </D:owner> +</D:lockinfo>'); + + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(201, $this->response->status); + $this->assertEquals('application/xml; charset=utf-8',$this->response->getHeader('Content-Type')); + $this->assertTrue(preg_match('/^<opaquelocktoken:(.*)>$/',$this->response->getHeader('Lock-Token'))===1,'We did not get a valid Locktoken back (' . $this->response->getHeader('Lock-Token') . ')'); + $this->assertEquals('true',$this->response->getHeader('X-Sabre-Temp')); + + $this->assertFalse(file_exists(SABRE_TEMPDIR . '/._testlock.txt'),'._testlock.txt should not exist in the regular file structure.'); + + } + + function testPutDelete() { + + // mimicking an OS/X resource fork + $request = new HTTP\Request('PUT', '/._testput.txt', [], 'Testing new file'); + + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('', $this->response->body); + $this->assertEquals(201, $this->response->status); + $this->assertEquals(array( + 'X-Sabre-Temp' => ['true'], + ),$this->response->getHeaders()); + + $request = new HTTP\Request('DELETE', '/._testput.txt'); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals(204, $this->response->status, "Incorrect status code received. Full body:\n". $this->response->body); + $this->assertEquals(array( + 'X-Sabre-Temp' => ['true'], + ),$this->response->getHeaders()); + + $this->assertEquals('',$this->response->body); + + } + + function testPutPropfind() { + + // mimicking an OS/X resource fork + $request = new HTTP\Request('PUT', '/._testput.txt', [], 'Testing new file'); + $this->server->httpRequest = $request; + $this->server->exec(); + + $this->assertEquals('', $this->response->body); + $this->assertEquals(201, $this->response->status); + $this->assertEquals(array( + 'X-Sabre-Temp' => ['true'], + ),$this->response->getHeaders()); + + $request = new HTTP\Request('PROPFIND', '/._testput.txt'); + + $this->server->httpRequest = ($request); + $this->server->exec(); + + $this->assertEquals(207, $this->response->status,'Incorrect status code returned. Body: ' . $this->response->body); + $this->assertEquals(array( + 'X-Sabre-Temp' => ['true'], + 'Content-Type' => ['application/xml; charset=utf-8'], + ),$this->response->getHeaders()); + + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/","xmlns\\1=\"urn:DAV\"",$this->response->body); + $xml = simplexml_load_string($body); + $xml->registerXPathNamespace('d','urn:DAV'); + + list($data) = $xml->xpath('/d:multistatus/d:response/d:href'); + $this->assertEquals('/._testput.txt',(string)$data,'href element should have been /._testput.txt'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:resourcetype'); + $this->assertEquals(1,count($data)); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/TestPlugin.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/TestPlugin.php new file mode 100644 index 0000000..bb5ea6a --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/TestPlugin.php @@ -0,0 +1,38 @@ +<?php + +namespace Sabre\DAV; + +use + Sabre\HTTP\RequestInterface, + Sabre\HTTP\ResponseInterface; + +class TestPlugin extends ServerPlugin { + + public $beforeMethod; + + function getFeatures() { + + return ['drinking']; + + } + + function getHTTPMethods($uri) { + + return ['BEER','WINE']; + + } + + function initialize(Server $server) { + + $server->on('beforeMethod', [$this,'beforeMethod']); + + } + + function beforeMethod(RequestInterface $request, ResponseInterface $response) { + + $this->beforeMethod = $request->getMethod(); + return true; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/TreeTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/TreeTest.php new file mode 100644 index 0000000..9516c23 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/TreeTest.php @@ -0,0 +1,241 @@ +<?php + +namespace Sabre\DAV; + +class TreeTest extends \PHPUnit_Framework_TestCase { + + function testNodeExists() { + + $tree = new TreeMock(); + + $this->assertTrue($tree->nodeExists('hi')); + $this->assertFalse($tree->nodeExists('hello')); + + } + + function testCopy() { + + $tree = new TreeMock(); + $tree->copy('hi','hi2'); + + $this->assertArrayHasKey('hi2', $tree->getNodeForPath('')->newDirectories); + $this->assertEquals('foobar', $tree->getNodeForPath('hi/file')->get()); + $this->assertEquals(array('test1'=>'value'), $tree->getNodeForPath('hi/file')->getProperties(array())); + + } + + function testMove() { + + $tree = new TreeMock(); + $tree->move('hi','hi2'); + + $this->assertEquals('hi2', $tree->getNodeForPath('hi')->getName()); + $this->assertTrue($tree->getNodeForPath('hi')->isRenamed); + + } + + function testDeepMove() { + + $tree = new TreeMock(); + $tree->move('hi/sub','hi2'); + + $this->assertArrayHasKey('hi2', $tree->getNodeForPath('')->newDirectories); + $this->assertTrue($tree->getNodeForPath('hi/sub')->isDeleted); + + } + + function testDelete() { + + $tree = new TreeMock(); + $tree->delete('hi'); + $this->assertTrue($tree->getNodeForPath('hi')->isDeleted); + + } + + function testGetChildren() { + + $tree = new TreeMock(); + $children = $tree->getChildren(''); + $this->assertEquals(2,count($children)); + $this->assertEquals('hi', $children[0]->getName()); + + } + + function testGetMultipleNodes() { + + $tree = new TreeMock(); + $result = $tree->getMultipleNodes(['hi/sub', 'hi/file']); + $this->assertArrayHasKey('hi/sub', $result); + $this->assertArrayHasKey('hi/file', $result); + + $this->assertEquals('sub', $result['hi/sub']->getName()); + $this->assertEquals('file', $result['hi/file']->getName()); + + } + function testGetMultipleNodes2() { + + $tree = new TreeMock(); + $result = $tree->getMultipleNodes(['multi/1', 'multi/2']); + $this->assertArrayHasKey('multi/1', $result); + $this->assertArrayHasKey('multi/2', $result); + + } + +} + +class TreeMock extends Tree { + + private $nodes = array(); + + function __construct() { + + $file = new TreeFileTester('file'); + $file->properties = ['test1'=>'value']; + $file->data = 'foobar'; + + parent::__construct( + new TreeDirectoryTester('root', [ + new TreeDirectoryTester('hi', [ + new TreeDirectoryTester('sub'), + $file, + ]), + new TreeMultiGetTester('multi', [ + new TreeFileTester('1'), + new TreeFileTester('2'), + new TreeFileTester('3'), + ]) + ]) + ); + + } + +} + +class TreeDirectoryTester extends SimpleCollection { + + public $newDirectories = array(); + public $newFiles = array(); + public $isDeleted = false; + public $isRenamed = false; + + function createDirectory($name) { + + $this->newDirectories[$name] = true; + + } + + function createFile($name,$data = null) { + + $this->newFiles[$name] = $data; + + } + + function getChild($name) { + + if (isset($this->newDirectories[$name])) return new TreeDirectoryTester($name); + if (isset($this->newFiles[$name])) return new TreeFileTester($name, $this->newFiles[$name]); + return parent::getChild($name); + + } + + function childExists($name) { + + return !!$this->getChild($name); + + } + + function delete() { + + $this->isDeleted = true; + + } + + function setName($name) { + + $this->isRenamed = true; + $this->name = $name; + + } + +} + +class TreeFileTester extends File implements IProperties { + + public $name; + public $data; + public $properties; + + function __construct($name, $data = null) { + + $this->name = $name; + if (is_null($data)) $data = 'bla'; + $this->data = $data; + + } + + function getName() { + + return $this->name; + + } + + function get() { + + return $this->data; + + } + + function getProperties($properties) { + + return $this->properties; + + } + + /** + * 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 array $mutations + * @return bool|array + */ + function propPatch(PropPatch $propPatch) { + + $this->properties = $propPatch->getMutations(); + $propPatch->setRemainingResultCode(200); + + } + +} + +class TreeMultiGetTester extends TreeDirectoryTester implements IMultiGet { + + /** + * 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. + * + * @return array + */ + function getMultipleChildren(array $paths) { + + $result = []; + foreach($paths as $path) { + try { + $child = $this->getChild($path); + $result[] = $child; + } catch (Exception\NotFound $e) { + // Do nothing + } + } + + return $result; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/UUIDUtilTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/UUIDUtilTest.php new file mode 100644 index 0000000..f005ecc --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/UUIDUtilTest.php @@ -0,0 +1,25 @@ +<?php + +namespace Sabre\DAV; + +class UUIDUtilTest extends \PHPUnit_Framework_TestCase { + + function testValidateUUID() { + + $this->assertTrue( + UUIDUtil::validateUUID('11111111-2222-3333-4444-555555555555') + ); + $this->assertFalse( + UUIDUtil::validateUUID(' 11111111-2222-3333-4444-555555555555') + ); + $this->assertTrue( + UUIDUtil::validateUUID('ffffffff-2222-3333-4444-555555555555') + ); + $this->assertFalse( + UUIDUtil::validateUUID('fffffffg-2222-3333-4444-555555555555') + ); + + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Element/PropTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Element/PropTest.php new file mode 100644 index 0000000..f18b82d --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Element/PropTest.php @@ -0,0 +1,154 @@ +<?php + +namespace Sabre\DAV\Xml\Element; + +use Sabre\DAV\Xml\XmlTest; +use Sabre\DAV\Xml\Property\Complex; +use Sabre\DAV\Xml\Property\Href; + +class PropTest extends XmlTest { + + function testDeserializeSimple() { + + $input = <<<XML +<?xml version="1.0"?> +<root xmlns="DAV:"> + <foo>bar</foo> +</root> +XML; + + $expected = [ + '{DAV:}foo' => 'bar', + ]; + + $this->assertDecodeProp($input, $expected); + + } + function testDeserializeEmpty() { + + $input = <<<XML +<?xml version="1.0"?> +<root xmlns="DAV:" /> +XML; + + $expected = [ + ]; + + $this->assertDecodeProp($input, $expected); + + } + function testDeserializeComplex() { + + $input = <<<XML +<?xml version="1.0"?> +<root xmlns="DAV:"> + <foo><no>yes</no></foo> +</root> +XML; + + $expected = [ + '{DAV:}foo' => new Complex('<no xmlns="DAV:">yes</no>') + ]; + + $this->assertDecodeProp($input, $expected); + + } + function testDeserializeCustom() { + + $input = <<<XML +<?xml version="1.0"?> +<root xmlns="DAV:"> + <foo><href>/hello</href></foo> +</root> +XML; + + $expected = [ + '{DAV:}foo' => new Href('/hello', false) + ]; + + $elementMap = [ + '{DAV:}foo' => 'Sabre\DAV\Xml\Property\Href' + ]; + + $this->assertDecodeProp($input, $expected, $elementMap); + + } + function testDeserializeCustomCallback() { + + $input = <<<XML +<?xml version="1.0"?> +<root xmlns="DAV:"> + <foo>blabla</foo> +</root> +XML; + + $expected = [ + '{DAV:}foo' => 'zim', + ]; + + $elementMap = [ + '{DAV:}foo' => function($reader) { + $reader->next(); + return 'zim'; + } + ]; + + $this->assertDecodeProp($input, $expected, $elementMap); + + } + + /** + * @expectedException \LogicException + */ + function testDeserializeCustomBad() { + + $input = <<<XML +<?xml version="1.0"?> +<root xmlns="DAV:"> + <foo>blabla</foo> +</root> +XML; + + $expected = []; + + $elementMap = [ + '{DAV:}foo' => 'idk?', + ]; + + $this->assertDecodeProp($input, $expected, $elementMap); + + } + + /** + * @expectedException \LogicException + */ + function testDeserializeCustomBadObj() { + + $input = <<<XML +<?xml version="1.0"?> +<root xmlns="DAV:"> + <foo>blabla</foo> +</root> +XML; + + $expected = []; + + $elementMap = [ + '{DAV:}foo' => new \StdClass(), + ]; + + $this->assertDecodeProp($input, $expected, $elementMap); + + } + + function assertDecodeProp($input, array $expected, array $elementMap = []) { + + $elementMap['{DAV:}root'] = 'Sabre\DAV\Xml\Element\Prop'; + + $result = $this->parse($input, $elementMap); + $this->assertInternalType('array', $result); + $this->assertEquals($expected, $result['value']); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Element/ResponseTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Element/ResponseTest.php new file mode 100644 index 0000000..d90478d --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Element/ResponseTest.php @@ -0,0 +1,313 @@ +<?php + +namespace Sabre\DAV\Xml\Element; + +use Sabre\DAV; + +class ResponseTest extends DAV\Xml\XmlTest { + + function testSimple() { + + $innerProps = [ + 200 => [ + '{DAV:}displayname' => 'my file', + ], + 404 => [ + '{DAV:}owner' => null, + ] + ]; + + $property = new Response('uri', $innerProps); + + $this->assertEquals('uri', $property->getHref()); + $this->assertEquals($innerProps, $property->getResponseProperties()); + + + } + + /** + * @depends testSimple + */ + function testSerialize() { + + $innerProps = [ + 200 => [ + '{DAV:}displayname' => 'my file', + ], + 404 => [ + '{DAV:}owner' => null, + ] + ]; + + $property = new Response('uri', $innerProps); + + $xml = $this->write(['{DAV:}root' => ['{DAV:}response' => $property]]); + + $this->assertXmlStringEqualsXmlString( +'<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:response> + <d:href>/uri</d:href> + <d:propstat> + <d:prop> + <d:displayname>my file</d:displayname> + </d:prop> + <d:status>HTTP/1.1 200 OK</d:status> + </d:propstat> + <d:propstat> + <d:prop> + <d:owner/> + </d:prop> + <d:status>HTTP/1.1 404 Not Found</d:status> + </d:propstat> + </d:response> +</d:root> +', $xml); + + } + + /** + * This one is specifically for testing properties with no namespaces, which is legal xml + * + * @depends testSerialize + */ + function testSerializeEmptyNamespace() { + + $innerProps = [ + 200 => [ + '{}propertyname' => 'value', + ], + ]; + + $property = new Response('uri', $innerProps); + + $xml = $this->write(['{DAV:}root' => ['{DAV:}response' => $property]]); + + $this->assertEquals( +'<d:root xmlns:d="DAV:"> + <d:response> + <d:href>/uri</d:href> + <d:propstat> + <d:prop> + <propertyname xmlns="">value</propertyname> + </d:prop> + <d:status>HTTP/1.1 200 OK</d:status> + </d:propstat> + </d:response> +</d:root> +', $xml); + + } + + /** + * This one is specifically for testing properties with no namespaces, which is legal xml + * + * @depends testSerialize + */ + function testSerializeCustomNamespace() { + + $innerProps = [ + 200 => [ + '{http://sabredav.org/NS/example}propertyname' => 'value', + ], + ]; + + $property = new Response('uri', $innerProps); + $xml = $this->write(['{DAV:}root' => ['{DAV:}response' => $property]]); + + $this->assertXmlStringEqualsXmlString( +'<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:response> + <d:href>/uri</d:href> + <d:propstat> + <d:prop> + <x1:propertyname xmlns:x1="http://sabredav.org/NS/example">value</x1:propertyname> + </d:prop> + <d:status>HTTP/1.1 200 OK</d:status> + </d:propstat> + </d:response> +</d:root>', $xml); + + } + + /** + * @depends testSerialize + */ + function testSerializeComplexProperty() { + + $innerProps = [ + 200 => [ + '{DAV:}link' => new DAV\Xml\Property\Href('http://sabredav.org/', false) + ], + ]; + + $property = new Response('uri', $innerProps); + $xml = $this->write(['{DAV:}root' => ['{DAV:}response' => $property]]); + + $this->assertXmlStringEqualsXmlString( +'<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:response> + <d:href>/uri</d:href> + <d:propstat> + <d:prop> + <d:link><d:href>http://sabredav.org/</d:href></d:link> + </d:prop> + <d:status>HTTP/1.1 200 OK</d:status> + </d:propstat> + </d:response> +</d:root> +', $xml); + + } + + /** + * @depends testSerialize + * @expectedException \InvalidArgumentException + */ + function testSerializeBreak() { + + $innerProps = [ + 200 => [ + '{DAV:}link' => new \STDClass() + ], + ]; + + $property = new Response('uri', $innerProps); + $this->write(['{DAV:}root' => ['{DAV:}response' => $property]]); + + } + + function testDeserializeComplexProperty() { + + $xml = '<?xml version="1.0"?> +<d:response xmlns:d="DAV:"> + <d:href>/uri</d:href> + <d:propstat> + <d:prop> + <d:foo>hello</d:foo> + </d:prop> + <d:status>HTTP/1.1 200 OK</d:status> + </d:propstat> +</d:response> +'; + + $result = $this->parse($xml, [ + '{DAV:}response' => 'Sabre\DAV\Xml\Element\Response', + '{DAV:}foo' => function($reader) { + + $reader->next(); + return 'world'; + }, + ]); + $this->assertEquals( + new Response('/uri', [ + '200' => [ + '{DAV:}foo' => 'world', + ] + ]), + $result['value'] + ); + + } + + /** + * @depends testSimple + */ + function testSerializeUrlencoding() { + + $innerProps = [ + 200 => [ + '{DAV:}displayname' => 'my file', + ], + ]; + + $property = new Response('space here', $innerProps); + + $xml = $this->write(['{DAV:}root' => ['{DAV:}response' => $property]]); + + $this->assertXmlStringEqualsXmlString( +'<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:response> + <d:href>/space%20here</d:href> + <d:propstat> + <d:prop> + <d:displayname>my file</d:displayname> + </d:prop> + <d:status>HTTP/1.1 200 OK</d:status> + </d:propstat> + </d:response> +</d:root> +', $xml); + + } + + /** + * @depends testSerialize + * + * The WebDAV spec _requires_ at least one DAV:propstat to appear for + * every DAV:response. There are circumstances however, there are no + * properties to encode. + * + * In those cases we MUST specify at least one DAV:propstat anyway, with + * no properties. + */ + function testSerializeNoProperties() { + + $innerProps = []; + + $property = new Response('uri', $innerProps); + $xml = $this->write(['{DAV:}root' => ['{DAV:}response' => $property]]); + + $this->assertXmlStringEqualsXmlString( +'<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:response> + <d:href>/uri</d:href> + <d:propstat> + <d:prop /> + <d:status>HTTP/1.1 418 I\'m a teapot</d:status> + </d:propstat> + </d:response> +</d:root> +', $xml); + + } + + /** + * In the case of {DAV:}prop, a deserializer should never get called, if + * the property element is empty. + */ + function testDeserializeComplexPropertyEmpty() { + + $xml = '<?xml version="1.0"?> +<d:response xmlns:d="DAV:"> + <d:href>/uri</d:href> + <d:propstat> + <d:prop> + <d:foo /> + </d:prop> + <d:status>HTTP/1.1 404 Not Found</d:status> + </d:propstat> +</d:response> +'; + + $result = $this->parse($xml, [ + '{DAV:}response' => 'Sabre\DAV\Xml\Element\Response', + '{DAV:}foo' => function($reader) { + throw new \LogicException('This should never happen'); + }, + ]); + $this->assertEquals( + new Response('/uri', [ + '404' => [ + '{DAV:}foo' => null + ] + ]), + $result['value'] + ); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/HrefTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/HrefTest.php new file mode 100644 index 0000000..13db316 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/HrefTest.php @@ -0,0 +1,138 @@ +<?php + +namespace Sabre\DAV\Xml\Property; + +use Sabre\DAV; +use Sabre\DAV\Browser\HtmlOutputHelper; +use Sabre\DAV\Xml\XmlTest; + +class HrefTest extends XmlTest { + + function testConstruct() { + + $href = new Href('path'); + $this->assertEquals('path', $href->getHref()); + + } + + function testSerialize() { + + $href = new Href('path'); + $this->assertEquals('path', $href->getHref()); + + $this->contextUri = '/bla/'; + + $xml = $this->write(['{DAV:}anything' => $href]); + + $this->assertXmlStringEqualsXmlString( +'<?xml version="1.0"?> +<d:anything xmlns:d="DAV:"><d:href>/bla/path</d:href></d:anything> +', $xml); + + } + function testSerializeSpace() { + + $href = new Href('path alsopath'); + $this->assertEquals('path alsopath', $href->getHref()); + + $this->contextUri = '/bla/'; + + $xml = $this->write(['{DAV:}anything' => $href]); + + $this->assertXmlStringEqualsXmlString( +'<?xml version="1.0"?> +<d:anything xmlns:d="DAV:"><d:href>/bla/path%20alsopath</d:href></d:anything> +', $xml); + + } + + function testSerializeNoPrefix() { + + $href = new Href('path', false); + $this->assertEquals('path', $href->getHref()); + + $xml = $this->write(['{DAV:}anything' => $href]); + + $this->assertXmlStringEqualsXmlString( +'<?xml version="1.0"?> +<d:anything xmlns:d="DAV:"><d:href>path</d:href></d:anything> +', $xml); + + } + + function testUnserialize() { + + $xml = '<?xml version="1.0"?> +<d:anything xmlns:d="DAV:"><d:href>/bla/path</d:href></d:anything> +'; + + $result = $this->parse($xml, ['{DAV:}anything' => 'Sabre\\DAV\\Xml\\Property\\Href']); + + $href = $result['value']; + + $this->assertInstanceOf('Sabre\\DAV\\Xml\\Property\\Href', $href); + + $this->assertEquals('/bla/path', $href->getHref()); + + } + + function testUnserializeIncompatible() { + + $xml = '<?xml version="1.0"?> +<d:anything xmlns:d="DAV:"><d:href2>/bla/path</d:href2></d:anything> +'; + $result = $this->parse($xml, ['{DAV:}anything' => 'Sabre\\DAV\\Xml\\Property\\Href']); + $href = $result['value']; + $this->assertNull($href); + + } + function testUnserializeEmpty() { + + $xml = '<?xml version="1.0"?> +<d:anything xmlns:d="DAV:"></d:anything> +'; + $result = $this->parse($xml, ['{DAV:}anything' => 'Sabre\\DAV\\Xml\\Property\\Href']); + $href = $result['value']; + $this->assertNull($href); + + } + + /** + * This method tests if hrefs containing & are correctly encoded. + */ + function testSerializeEntity() { + + $href = new Href('http://example.org/?a&b', false); + $this->assertEquals('http://example.org/?a&b', $href->getHref()); + + $xml = $this->write(['{DAV:}anything' => $href]); + + $this->assertXmlStringEqualsXmlString( +'<?xml version="1.0"?> +<d:anything xmlns:d="DAV:"><d:href>http://example.org/?a&b</d:href></d:anything> +', $xml); + + } + + function testToHtml() { + + $href = new Href([ + '/foo/bar', + 'foo/bar', + 'http://example.org/bar' + ]); + + $html = new HtmlOutputHelper( + '/base/', + [] + ); + + $expected = + '<a href="/foo/bar">/foo/bar</a><br />' . + '<a href="/base/foo/bar">/base/foo/bar</a><br />' . + '<a href="http://example.org/bar">http://example.org/bar</a>'; + $this->assertEquals($expected, $href->toHtml($html)); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/LastModifiedTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/LastModifiedTest.php new file mode 100644 index 0000000..459fb28 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/LastModifiedTest.php @@ -0,0 +1,59 @@ +<?php + +namespace Sabre\DAV\Xml\Property; + +use Sabre\DAV\Xml\XmlTest; +use DateTime; +use DateTimeZone; + +class LastModifiedTest extends XmlTest { + + function testSerializeDateTime() { + + $dt = new DateTime('2015-03-24 11:47:00', new DateTimeZone('America/Vancouver')); + $val = ['{DAV:}getlastmodified' => new GetLastModified($dt)]; + + $result = $this->write($val); + $expected = <<<XML +<?xml version="1.0"?> +<d:getlastmodified xmlns:d="DAV:">Tue, 24 Mar 2015 18:47:00 GMT</d:getlastmodified> +XML; + + $this->assertXmlStringEqualsXmlString($expected, $result); + + } + + function testSerializeTimeStamp() { + + $dt = new DateTime('2015-03-24 11:47:00', new DateTimeZone('America/Vancouver')); + $dt = $dt->getTimeStamp(); + $val = ['{DAV:}getlastmodified' => new GetLastModified($dt)]; + + $result = $this->write($val); + $expected = <<<XML +<?xml version="1.0"?> +<d:getlastmodified xmlns:d="DAV:">Tue, 24 Mar 2015 18:47:00 GMT</d:getlastmodified> +XML; + + $this->assertXmlStringEqualsXmlString($expected, $result); + + } + + function testDeserialize() { + + $input = <<<XML +<?xml version="1.0"?> +<d:getlastmodified xmlns:d="DAV:">Tue, 24 Mar 2015 18:47:00 GMT</d:getlastmodified> +XML; + + $elementMap = ['{DAV:}getlastmodified' => 'Sabre\DAV\Xml\Property\GetLastModified']; + $result = $this->parse($input, $elementMap); + + $this->assertEquals( + new DateTime('2015-03-24 18:47:00', new DateTimeZone('UTC')), + $result['value']->getTime() + ); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/LockDiscoveryTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/LockDiscoveryTest.php new file mode 100644 index 0000000..6567c72 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/LockDiscoveryTest.php @@ -0,0 +1,86 @@ +<?php + +namespace Sabre\DAV\Xml\Property; + +use Sabre\DAV\Xml\XmlTest; +use Sabre\DAV\Locks\LockInfo; + +class LockDiscoveryTest extends XmlTest { + + function testSerialize() { + + $lock = new LockInfo(); + $lock->owner = 'hello'; + $lock->token = 'blabla'; + $lock->timeout = 600; + $lock->created = strtotime('2015-03-25 19:21:00'); + $lock->scope = LockInfo::EXCLUSIVE; + $lock->depth = 0; + $lock->uri = 'hi'; + + $prop = new LockDiscovery([$lock]); + + $xml = $this->write(['{DAV:}root' => $prop]); + + $this->assertXmlStringEqualsXmlString( +'<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:activelock> + <d:lockscope><d:exclusive /></d:lockscope> + <d:locktype><d:write /></d:locktype> + <d:lockroot> + <d:href>/hi</d:href> + </d:lockroot> + <d:depth>0</d:depth> + <d:timeout>Second-600</d:timeout> + <d:locktoken> + <d:href>opaquelocktoken:blabla</d:href> + </d:locktoken> + <d:owner>hello</d:owner> + + +</d:activelock> +</d:root> +', $xml); + + } + + function testSerializeShared() { + + $lock = new LockInfo(); + $lock->owner = 'hello'; + $lock->token = 'blabla'; + $lock->timeout = 600; + $lock->created = strtotime('2015-03-25 19:21:00'); + $lock->scope = LockInfo::SHARED; + $lock->depth = 0; + $lock->uri = 'hi'; + + $prop = new LockDiscovery([$lock]); + + $xml = $this->write(['{DAV:}root' => $prop]); + + $this->assertXmlStringEqualsXmlString( +'<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:activelock> + <d:lockscope><d:shared /></d:lockscope> + <d:locktype><d:write /></d:locktype> + <d:lockroot> + <d:href>/hi</d:href> + </d:lockroot> + <d:depth>0</d:depth> + <d:timeout>Second-600</d:timeout> + <d:locktoken> + <d:href>opaquelocktoken:blabla</d:href> + </d:locktoken> + <d:owner>hello</d:owner> + + +</d:activelock> +</d:root> +', $xml); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/SupportedMethodSetTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/SupportedMethodSetTest.php new file mode 100644 index 0000000..f5fa330 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/SupportedMethodSetTest.php @@ -0,0 +1,62 @@ +<?php + +namespace Sabre\DAV\Xml\Property; + +use Sabre\DAV; +use Sabre\HTTP; + +require_once 'Sabre/HTTP/ResponseMock.php'; +require_once 'Sabre/DAV/AbstractServer.php'; + +class SupportedMethodSetTest extends DAV\AbstractServer { + + function sendPROPFIND($body) { + + $request = new HTTP\Request('PROPFIND', '/', ['Depth' => '0' ]); + $request->setBody($body); + + $this->server->httpRequest = $request; + $this->server->exec(); + + } + + /** + */ + function testMethods() { + + $xml = '<?xml version="1.0"?> +<d:propfind xmlns:d="DAV:"> + <d:prop> + <d:supported-method-set /> + </d:prop> +</d:propfind>'; + + $this->sendPROPFIND($xml); + + $this->assertEquals(207, $this->response->status, 'We expected a multi-status response. Full response body: ' . $this->response->body); + + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/", "xmlns\\1=\"urn:DAV\"", $this->response->body); + $xml = simplexml_load_string($body); + $xml->registerXPathNamespace('d', 'urn:DAV'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop'); + $this->assertEquals(1, count($data), 'We expected 1 \'d:prop\' element'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supported-method-set'); + $this->assertEquals(1, count($data), 'We expected 1 \'d:supported-method-set\' element'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:status'); + $this->assertEquals(1, count($data), 'We expected 1 \'d:status\' element'); + + $this->assertEquals('HTTP/1.1 200 OK', (string)$data[0], 'The status for this property should have been 200'); + + } + + function testGetObj() { + + $result = $this->server->getProperties('/', ['{DAV:}supported-method-set']); + $this->assertTrue($result['{DAV:}supported-method-set']->has('PROPFIND')); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/SupportedReportSetTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/SupportedReportSetTest.php new file mode 100644 index 0000000..808b752 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Property/SupportedReportSetTest.php @@ -0,0 +1,115 @@ +<?php + +namespace Sabre\DAV\Property; + +use Sabre\DAV; +use Sabre\HTTP; + +require_once 'Sabre/HTTP/ResponseMock.php'; +require_once 'Sabre/DAV/AbstractServer.php'; + +class SupportedReportSetTest extends DAV\AbstractServer { + + function sendPROPFIND($body) { + + $serverVars = [ + 'REQUEST_URI' => '/', + 'REQUEST_METHOD' => 'PROPFIND', + 'HTTP_DEPTH' => '0', + ]; + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody($body); + + $this->server->httpRequest = ($request); + $this->server->exec(); + + } + + /** + */ + function testNoReports() { + + $xml = '<?xml version="1.0"?> +<d:propfind xmlns:d="DAV:"> + <d:prop> + <d:supported-report-set /> + </d:prop> +</d:propfind>'; + + $this->sendPROPFIND($xml); + + $this->assertEquals(207, $this->response->status, 'We expected a multi-status response. Full response body: ' . $this->response->body); + + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/", "xmlns\\1=\"urn:DAV\"", $this->response->body); + $xml = simplexml_load_string($body); + $xml->registerXPathNamespace('d', 'urn:DAV'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop'); + $this->assertEquals(1, count($data), 'We expected 1 \'d:prop\' element'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supported-report-set'); + $this->assertEquals(1, count($data), 'We expected 1 \'d:supported-report-set\' element'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:status'); + $this->assertEquals(1, count($data), 'We expected 1 \'d:status\' element'); + + $this->assertEquals('HTTP/1.1 200 OK', (string)$data[0], 'The status for this property should have been 200'); + + } + + /** + * @depends testNoReports + */ + function testCustomReport() { + + // Intercepting the report property + $this->server->on('propFind', function(DAV\PropFind $propFind, DAV\INode $node) { + if ($prop = $propFind->get('{DAV:}supported-report-set')) { + $prop->addReport('{http://www.rooftopsolutions.nl/testnamespace}myreport'); + $prop->addReport('{DAV:}anotherreport'); + } + }, 200); + + $xml = '<?xml version="1.0"?> +<d:propfind xmlns:d="DAV:"> + <d:prop> + <d:supported-report-set /> + </d:prop> +</d:propfind>'; + + $this->sendPROPFIND($xml); + + $this->assertEquals(207, $this->response->status, 'We expected a multi-status response. Full response body: ' . $this->response->body); + + $body = preg_replace("/xmlns(:[A-Za-z0-9_])?=(\"|\')DAV:(\"|\')/", "xmlns\\1=\"urn:DAV\"", $this->response->body); + $xml = simplexml_load_string($body); + $xml->registerXPathNamespace('d', 'urn:DAV'); + $xml->registerXPathNamespace('x', 'http://www.rooftopsolutions.nl/testnamespace'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop'); + $this->assertEquals(1, count($data), 'We expected 1 \'d:prop\' element'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supported-report-set'); + $this->assertEquals(1, count($data), 'We expected 1 \'d:supported-report-set\' element'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supported-report-set/d:supported-report'); + $this->assertEquals(2, count($data), 'We expected 2 \'d:supported-report\' elements'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supported-report-set/d:supported-report/d:report'); + $this->assertEquals(2, count($data), 'We expected 2 \'d:report\' elements'); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supported-report-set/d:supported-report/d:report/x:myreport'); + $this->assertEquals(1, count($data), 'We expected 1 \'x:myreport\' element. Full body: ' . $this->response->body); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:prop/d:supported-report-set/d:supported-report/d:report/d:anotherreport'); + $this->assertEquals(1, count($data), 'We expected 1 \'d:anotherreport\' element. Full body: ' . $this->response->body); + + $data = $xml->xpath('/d:multistatus/d:response/d:propstat/d:status'); + $this->assertEquals(1, count($data), 'We expected 1 \'d:status\' element'); + + $this->assertEquals('HTTP/1.1 200 OK', (string)$data[0], 'The status for this property should have been 200'); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Request/PropFindTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Request/PropFindTest.php new file mode 100644 index 0000000..c11668b --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Request/PropFindTest.php @@ -0,0 +1,48 @@ +<?php + +namespace Sabre\DAV\Xml\Request; + +use Sabre\DAV\Xml\XmlTest; + +class PropFindTest extends XmlTest { + + function testDeserializeProp() { + + $xml = '<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:prop> + <d:hello /> + </d:prop> +</d:root> +'; + + $result = $this->parse($xml, ['{DAV:}root' => 'Sabre\\DAV\\Xml\\Request\PropFind']); + + $propFind = new PropFind(); + $propFind->properties = ['{DAV:}hello']; + + $this->assertEquals($propFind, $result['value']); + + + } + + function testDeserializeAllProp() { + + $xml = '<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:allprop /> +</d:root> +'; + + $result = $this->parse($xml, ['{DAV:}root' => 'Sabre\\DAV\\Xml\\Request\PropFind']); + + $propFind = new PropFind(); + $propFind->allProp = true; + + $this->assertEquals($propFind, $result['value']); + + + } + + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Request/PropPatchTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Request/PropPatchTest.php new file mode 100644 index 0000000..02ba305 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Request/PropPatchTest.php @@ -0,0 +1,53 @@ +<?php + +namespace Sabre\DAV\Xml\Request; + +use Sabre\DAV\Xml\XmlTest; +use Sabre\DAV\Xml\Property\Href; + +class PropPatchTest extends XmlTest { + + function testSerialize() { + + $propPatch = new PropPatch(); + $propPatch->properties = [ + '{DAV:}displayname' => 'Hello!', + '{DAV:}delete-me' => null, + '{DAV:}some-url' => new Href('foo/bar') + ]; + + $result = $this->write( + ['{DAV:}propertyupdate' => $propPatch] + ); + + $expected = <<<XML +<?xml version="1.0"?> +<d:propertyupdate xmlns:d="DAV:"> + <d:set> + <d:prop> + <d:displayname>Hello!</d:displayname> + </d:prop> + </d:set> + <d:remove> + <d:prop> + <d:delete-me /> + </d:prop> + </d:remove> + <d:set> + <d:prop> + <d:some-url> + <d:href>/foo/bar</d:href> + </d:some-url> + </d:prop> + </d:set> +</d:propertyupdate> +XML; + + $this->assertXmlStringEqualsXmlString( + $expected, + $result + ); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Request/SyncCollectionTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Request/SyncCollectionTest.php new file mode 100644 index 0000000..6dfecc5 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/Request/SyncCollectionTest.php @@ -0,0 +1,94 @@ +<?php + +namespace Sabre\DAV\Xml\Request; + +use Sabre\DAV\Xml\XmlTest; + +class SyncCollectionTest extends XmlTest { + + function testDeserializeProp() { + + $xml = '<?xml version="1.0"?> +<d:sync-collection xmlns:d="DAV:"> + <d:sync-token /> + <d:sync-level>1</d:sync-level> + <d:prop> + <d:foo /> + </d:prop> +</d:sync-collection> +'; + + $result = $this->parse($xml, ['{DAV:}sync-collection' => 'Sabre\\DAV\\Xml\\Request\SyncCollectionReport']); + + $elem = new SyncCollectionReport(); + $elem->syncLevel = 1; + $elem->properties = ['{DAV:}foo']; + + $this->assertEquals($elem, $result['value']); + + } + + + function testDeserializeLimit() { + + $xml = '<?xml version="1.0"?> +<d:sync-collection xmlns:d="DAV:"> + <d:sync-token /> + <d:sync-level>1</d:sync-level> + <d:prop> + <d:foo /> + </d:prop> + <d:limit><d:nresults>5</d:nresults></d:limit> +</d:sync-collection> +'; + + $result = $this->parse($xml, ['{DAV:}sync-collection' => 'Sabre\\DAV\\Xml\\Request\SyncCollectionReport']); + + $elem = new SyncCollectionReport(); + $elem->syncLevel = 1; + $elem->properties = ['{DAV:}foo']; + $elem->limit = 5; + + $this->assertEquals($elem, $result['value']); + + } + + + function testDeserializeInfinity() { + + $xml = '<?xml version="1.0"?> +<d:sync-collection xmlns:d="DAV:"> + <d:sync-token /> + <d:sync-level>infinity</d:sync-level> + <d:prop> + <d:foo /> + </d:prop> +</d:sync-collection> +'; + + $result = $this->parse($xml, ['{DAV:}sync-collection' => 'Sabre\\DAV\\Xml\\Request\SyncCollectionReport']); + + $elem = new SyncCollectionReport(); + $elem->syncLevel = \Sabre\DAV\Server::DEPTH_INFINITY; + $elem->properties = ['{DAV:}foo']; + + $this->assertEquals($elem, $result['value']); + + } + + /** + * @expectedException \Sabre\DAV\Exception\BadRequest + */ + function testDeserializeMissingElem() { + + $xml = '<?xml version="1.0"?> +<d:sync-collection xmlns:d="DAV:"> + <d:sync-token /> +</d:sync-collection> +'; + + $result = $this->parse($xml, ['{DAV:}sync-collection' => 'Sabre\\DAV\\Xml\\Request\SyncCollectionReport']); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/XmlTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/XmlTest.php new file mode 100644 index 0000000..9867011 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAV/Xml/XmlTest.php @@ -0,0 +1,41 @@ +<?php + +namespace Sabre\DAV\Xml; + +use Sabre\Xml\Writer; +use Sabre\Xml\Reader; + +abstract class XmlTest extends \PHPUnit_Framework_TestCase { + + protected $elementMap = []; + protected $namespaceMap = ['DAV:' => 'd']; + protected $contextUri = '/'; + + function write($input) { + + $writer = new Writer(); + $writer->contextUri = $this->contextUri; + $writer->namespaceMap = $this->namespaceMap; + $writer->openMemory(); + $writer->setIndent(true); + $writer->write($input); + return $writer->outputMemory(); + + } + + function parse($xml, array $elementMap = []) { + + $reader = new Reader(); + $reader->elementMap = array_merge($this->elementMap, $elementMap); + $reader->xml($xml); + return $reader->parse(); + + } + + function cleanUp() { + + libxml_clear_errors(); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/ACLMethodTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/ACLMethodTest.php new file mode 100644 index 0000000..4ecd427 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/ACLMethodTest.php @@ -0,0 +1,324 @@ +<?php + +namespace Sabre\DAVACL; + +use Sabre\DAV; +use Sabre\HTTP; + +class ACLMethodTest extends \PHPUnit_Framework_TestCase { + + /** + * @expectedException Sabre\DAV\Exception\BadRequest + */ + function testCallback() { + + $acl = new Plugin(); + $server = new DAV\Server(); + $server->addPlugin($acl); + + $acl->httpAcl($server->httpRequest, $server->httpResponse); + + } + + /** + + /** + * @expectedException Sabre\DAV\Exception\MethodNotAllowed + */ + function testNotSupportedByNode() { + + $tree = array( + new DAV\SimpleCollection('test'), + ); + $acl = new Plugin(); + $server = new DAV\Server($tree); + $server->httpRequest = new HTTP\Request(); + $body = '<?xml version="1.0"?> +<d:acl xmlns:d="DAV:"> +</d:acl>'; + $server->httpRequest->setBody($body); + $server->addPlugin($acl); + + $acl->httpACL($server->httpRequest, $server->httpResponse); + + } + + function testSuccessSimple() { + + $tree = array( + new MockACLNode('test',array()), + ); + $acl = new Plugin(); + $server = new DAV\Server($tree); + $server->httpRequest = new HTTP\Request(); + $server->httpRequest->setUrl('/test'); + + $body = '<?xml version="1.0"?> +<d:acl xmlns:d="DAV:"> +</d:acl>'; + $server->httpRequest->setBody($body); + $server->addPlugin($acl); + + $this->assertFalse($acl->httpACL($server->httpRequest, $server->httpResponse)); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\NotRecognizedPrincipal + */ + function testUnrecognizedPrincipal() { + + $tree = array( + new MockACLNode('test',array()), + ); + $acl = new Plugin(); + $server = new DAV\Server($tree); + $server->httpRequest = new HTTP\Request('ACL','/test'); + $body = '<?xml version="1.0"?> +<d:acl xmlns:d="DAV:"> + <d:ace> + <d:grant><d:privilege><d:read /></d:privilege></d:grant> + <d:principal><d:href>/principals/notfound</d:href></d:principal> + </d:ace> +</d:acl>'; + $server->httpRequest->setBody($body); + $server->addPlugin($acl); + + $acl->httpACL($server->httpRequest, $server->httpResponse); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\NotRecognizedPrincipal + */ + function testUnrecognizedPrincipal2() { + + $tree = array( + new MockACLNode('test',array()), + new DAV\SimpleCollection('principals',array( + new DAV\SimpleCollection('notaprincipal'), + )), + ); + $acl = new Plugin(); + $server = new DAV\Server($tree); + $server->httpRequest = new HTTP\Request('ACL','/test'); + $body = '<?xml version="1.0"?> +<d:acl xmlns:d="DAV:"> + <d:ace> + <d:grant><d:privilege><d:read /></d:privilege></d:grant> + <d:principal><d:href>/principals/notaprincipal</d:href></d:principal> + </d:ace> +</d:acl>'; + $server->httpRequest->setBody($body); + $server->addPlugin($acl); + + $acl->httpACL($server->httpRequest, $server->httpResponse); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\NotSupportedPrivilege + */ + function testUnknownPrivilege() { + + $tree = array( + new MockACLNode('test',array()), + ); + $acl = new Plugin(); + $server = new DAV\Server($tree); + $server->httpRequest = new HTTP\Request('ACL','/test'); + $body = '<?xml version="1.0"?> +<d:acl xmlns:d="DAV:"> + <d:ace> + <d:grant><d:privilege><d:bananas /></d:privilege></d:grant> + <d:principal><d:href>/principals/notfound</d:href></d:principal> + </d:ace> +</d:acl>'; + $server->httpRequest->setBody($body); + $server->addPlugin($acl); + + $acl->httpACL($server->httpRequest, $server->httpResponse); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\NoAbstract + */ + function testAbstractPrivilege() { + + $tree = array( + new MockACLNode('test',array()), + ); + $acl = new Plugin(); + $server = new DAV\Server($tree); + $server->httpRequest = new HTTP\Request('ACL','/test'); + $body = '<?xml version="1.0"?> +<d:acl xmlns:d="DAV:"> + <d:ace> + <d:grant><d:privilege><d:all /></d:privilege></d:grant> + <d:principal><d:href>/principals/notfound</d:href></d:principal> + </d:ace> +</d:acl>'; + $server->httpRequest->setBody($body); + $server->addPlugin($acl); + + $acl->httpACL($server->httpRequest, $server->httpResponse); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\AceConflict + */ + function testUpdateProtectedPrivilege() { + + $oldACL = array( + array( + 'principal' => 'principals/notfound', + 'privilege' => '{DAV:}write', + 'protected' => true, + ), + ); + + $tree = array( + new MockACLNode('test',$oldACL), + ); + $acl = new Plugin(); + $server = new DAV\Server($tree); + $server->httpRequest = new HTTP\Request('ACL','/test'); + $body = '<?xml version="1.0"?> +<d:acl xmlns:d="DAV:"> + <d:ace> + <d:grant><d:privilege><d:read /></d:privilege></d:grant> + <d:principal><d:href>/principals/notfound</d:href></d:principal> + </d:ace> +</d:acl>'; + $server->httpRequest->setBody($body); + $server->addPlugin($acl); + + $acl->httpACL($server->httpRequest, $server->httpResponse); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\AceConflict + */ + function testUpdateProtectedPrivilege2() { + + $oldACL = array( + array( + 'principal' => 'principals/notfound', + 'privilege' => '{DAV:}write', + 'protected' => true, + ), + ); + + $tree = array( + new MockACLNode('test',$oldACL), + ); + $acl = new Plugin(); + $server = new DAV\Server($tree); + $server->httpRequest = new HTTP\Request('ACL','/test'); + $body = '<?xml version="1.0"?> +<d:acl xmlns:d="DAV:"> + <d:ace> + <d:grant><d:privilege><d:write /></d:privilege></d:grant> + <d:principal><d:href>/principals/foo</d:href></d:principal> + </d:ace> +</d:acl>'; + $server->httpRequest->setBody($body); + $server->addPlugin($acl); + + $acl->httpACL($server->httpRequest, $server->httpResponse); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\AceConflict + */ + function testUpdateProtectedPrivilege3() { + + $oldACL = array( + array( + 'principal' => 'principals/notfound', + 'privilege' => '{DAV:}write', + 'protected' => true, + ), + ); + + $tree = array( + new MockACLNode('test',$oldACL), + ); + $acl = new Plugin(); + $server = new DAV\Server($tree); + $server->httpRequest = new HTTP\Request('ACL','/test'); + $body = '<?xml version="1.0"?> +<d:acl xmlns:d="DAV:"> + <d:ace> + <d:grant><d:privilege><d:write /></d:privilege></d:grant> + <d:principal><d:href>/principals/notfound</d:href></d:principal> + </d:ace> +</d:acl>'; + $server->httpRequest->setBody($body); + $server->addPlugin($acl); + + $acl->httpACL($server->httpRequest, $server->httpResponse); + + } + + function testSuccessComplex() { + + $oldACL = array( + array( + 'principal' => 'principals/foo', + 'privilege' => '{DAV:}write', + 'protected' => true, + ), + array( + 'principal' => 'principals/bar', + 'privilege' => '{DAV:}read', + ), + ); + + $tree = array( + $node = new MockACLNode('test',$oldACL), + new DAV\SimpleCollection('principals', array( + new MockPrincipal('foo','principals/foo'), + new MockPrincipal('baz','principals/baz'), + )), + ); + $acl = new Plugin(); + $server = new DAV\Server($tree); + $server->httpRequest = new HTTP\Request('ACL','/test'); + $body = '<?xml version="1.0"?> +<d:acl xmlns:d="DAV:"> + <d:ace> + <d:grant><d:privilege><d:write /></d:privilege></d:grant> + <d:principal><d:href>/principals/foo</d:href></d:principal> + <d:protected /> + </d:ace> + <d:ace> + <d:grant><d:privilege><d:write /></d:privilege></d:grant> + <d:principal><d:href>/principals/baz</d:href></d:principal> + </d:ace> +</d:acl>'; + $server->httpRequest->setBody($body); + $server->addPlugin($acl); + + + $this->assertFalse($acl->httpAcl($server->httpRequest, $server->httpResponse)); + + $this->assertEquals(array( + array( + 'principal' => 'principals/foo', + 'privilege' => '{DAV:}write', + 'protected' => true, + ), + array( + 'principal' => 'principals/baz', + 'privilege' => '{DAV:}write', + 'protected' => false, + ), + ), $node->getACL()); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/AllowAccessTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/AllowAccessTest.php new file mode 100644 index 0000000..14a8000 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/AllowAccessTest.php @@ -0,0 +1,131 @@ +<?php + +namespace Sabre\DAVACL; + +use Sabre\DAV; +use Sabre\HTTP; + +class AllowAccessTest extends \PHPUnit_Framework_TestCase { + + /** + * @var DAV\Server + */ + protected $server; + + function setUp() { + + $nodes = array( + new DAV\SimpleCollection('testdir'), + ); + + $this->server = new DAV\Server($nodes); + $aclPlugin = new Plugin(); + $aclPlugin->allowAccessToNodesWithoutACL = true; + $this->server->addPlugin($aclPlugin); + + } + + function testGet() { + + $this->server->httpRequest->setMethod('GET'); + $this->server->httpRequest->setUrl('/testdir'); + + $this->assertTrue($this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse])); + + } + + function testGetDoesntExist() { + + $this->server->httpRequest->setMethod('GET'); + $this->server->httpRequest->setUrl('/foo'); + + $this->assertTrue($this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse])); + + } + + function testHEAD() { + + $this->server->httpRequest->setMethod('HEAD'); + $this->server->httpRequest->setUrl('/testdir'); + + $this->assertTrue($this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse])); + + } + + function testOPTIONS() { + + $this->server->httpRequest->setMethod('OPTIONS'); + $this->server->httpRequest->setUrl('/testdir'); + + $this->assertTrue($this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse])); + + } + + function testPUT() { + + $this->server->httpRequest->setMethod('PUT'); + $this->server->httpRequest->setUrl('/testdir'); + + $this->assertTrue($this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse])); + + } + + function testACL() { + + $this->server->httpRequest->setMethod('ACL'); + $this->server->httpRequest->setUrl('/testdir'); + + $this->assertTrue($this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse])); + + } + + function testPROPPATCH() { + + $this->server->httpRequest->setMethod('PROPPATCH'); + $this->server->httpRequest->setUrl('/testdir'); + + $this->assertTrue($this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse])); + + } + + function testCOPY() { + + $this->server->httpRequest->setMethod('COPY'); + $this->server->httpRequest->setUrl('/testdir'); + + $this->assertTrue($this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse])); + + } + + function testMOVE() { + + $this->server->httpRequest->setMethod('MOVE'); + $this->server->httpRequest->setUrl('/testdir'); + + $this->assertTrue($this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse])); + + } + + function testLOCK() { + + $this->server->httpRequest->setMethod('LOCK'); + $this->server->httpRequest->setUrl('/testdir'); + + $this->assertTrue($this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse])); + + } + + function testBeforeBind() { + + $this->assertTrue($this->server->emit('beforeBind', ['testdir/file'])); + + } + + + function testBeforeUnbind() { + + $this->assertTrue($this->server->emit('beforeUnbind', ['testdir'])); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/BlockAccessTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/BlockAccessTest.php new file mode 100644 index 0000000..be3e9da --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/BlockAccessTest.php @@ -0,0 +1,206 @@ +<?php + +namespace Sabre\DAVACL; + +use Sabre\DAV; +use Sabre\HTTP; + +class BlockAccessTest extends \PHPUnit_Framework_TestCase { + + /** + * @var DAV\Server + */ + protected $server; + protected $plugin; + + function setUp() { + + $nodes = [ + new DAV\SimpleCollection('testdir'), + ]; + + $this->server = new DAV\Server($nodes); + $this->plugin = new Plugin(); + $this->plugin->allowAccessToNodesWithoutACL = false; + $this->server->addPlugin($this->plugin); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\NeedPrivileges + */ + function testGet() { + + $this->server->httpRequest->setMethod('GET'); + $this->server->httpRequest->setUrl('/testdir'); + + $this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse]); + + } + + function testGetDoesntExist() { + + $this->server->httpRequest->setMethod('GET'); + $this->server->httpRequest->setUrl('/foo'); + + $r = $this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse]); + $this->assertTrue($r); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\NeedPrivileges + */ + function testHEAD() { + + $this->server->httpRequest->setMethod('HEAD'); + $this->server->httpRequest->setUrl('/testdir'); + + $this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse]); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\NeedPrivileges + */ + function testOPTIONS() { + + $this->server->httpRequest->setMethod('OPTIONS'); + $this->server->httpRequest->setUrl('/testdir'); + + $this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse]); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\NeedPrivileges + */ + function testPUT() { + + $this->server->httpRequest->setMethod('PUT'); + $this->server->httpRequest->setUrl('/testdir'); + + $this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse]); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\NeedPrivileges + */ + function testPROPPATCH() { + + $this->server->httpRequest->setMethod('PROPPATCH'); + $this->server->httpRequest->setUrl('/testdir'); + + $this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse]); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\NeedPrivileges + */ + function testCOPY() { + + $this->server->httpRequest->setMethod('COPY'); + $this->server->httpRequest->setUrl('/testdir'); + + $this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse]); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\NeedPrivileges + */ + function testMOVE() { + + $this->server->httpRequest->setMethod('MOVE'); + $this->server->httpRequest->setUrl('/testdir'); + + $this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse]); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\NeedPrivileges + */ + function testACL() { + + $this->server->httpRequest->setMethod('ACL'); + $this->server->httpRequest->setUrl('/testdir'); + + $this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse]); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\NeedPrivileges + */ + function testLOCK() { + + $this->server->httpRequest->setMethod('LOCK'); + $this->server->httpRequest->setUrl('/testdir'); + + $this->server->emit('beforeMethod', [$this->server->httpRequest, $this->server->httpResponse]); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\NeedPrivileges + */ + function testBeforeBind() { + + $this->server->emit('beforeBind', ['testdir/file']); + + } + + /** + * @expectedException Sabre\DAVACL\Exception\NeedPrivileges + */ + function testBeforeUnbind() { + + $this->server->emit('beforeUnbind', ['testdir']); + + } + + function testPropFind() { + + $propFind = new DAV\PropFind('testdir', [ + '{DAV:}displayname', + '{DAV:}getcontentlength', + '{DAV:}bar', + '{DAV:}owner', + ]); + + $r = $this->server->emit('propFind', [$propFind, new DAV\SimpleCollection('testdir')]); + $this->assertTrue($r); + + $expected = [ + 200 => [], + 404 => [], + 403 => [ + '{DAV:}displayname' => null, + '{DAV:}getcontentlength' => null, + '{DAV:}bar' => null, + '{DAV:}owner' => null, + ], + ]; + + $this->assertEquals($expected, $propFind->getResultForMultiStatus()); + + } + + function testBeforeGetPropertiesNoListing() { + + $this->plugin->hideNodesFromListings = true; + $propFind = new DAV\PropFind('testdir', [ + '{DAV:}displayname', + '{DAV:}getcontentlength', + '{DAV:}bar', + '{DAV:}owner', + ]); + + $r = $this->server->emit('propFind', [$propFind, new DAV\SimpleCollection('testdir')]); + $this->assertFalse($r); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/AceConflictTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/AceConflictTest.php new file mode 100644 index 0000000..fc48af6 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/AceConflictTest.php @@ -0,0 +1,39 @@ +<?php + +namespace Sabre\DAVACL\Exception; + +use Sabre\DAV; + +class AceConflictTest extends \PHPUnit_Framework_TestCase { + + function testSerialize() { + + $ex = new AceConflict('message'); + + $server = new DAV\Server(); + $dom = new \DOMDocument('1.0','utf-8'); + $root = $dom->createElementNS('DAV:','d:root'); + $dom->appendChild($root); + + $ex->serialize($server, $root); + + $xpaths = array( + '/d:root' => 1, + '/d:root/d:no-ace-conflict' => 1, + ); + + // Reloading because PHP DOM sucks + $dom2 = new \DOMDocument('1.0', 'utf-8'); + $dom2->loadXML($dom->saveXML()); + + $dxpath = new \DOMXPath($dom2); + $dxpath->registerNamespace('d','DAV:'); + foreach($xpaths as $xpath=>$count) { + + $this->assertEquals($count, $dxpath->query($xpath)->length, 'Looking for : ' . $xpath . ', we could only find ' . $dxpath->query($xpath)->length . ' elements, while we expected ' . $count); + + } + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/NeedPrivilegesExceptionTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/NeedPrivilegesExceptionTest.php new file mode 100644 index 0000000..7e66ada --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/NeedPrivilegesExceptionTest.php @@ -0,0 +1,49 @@ +<?php + +namespace Sabre\DAVACL\Exception; + +use Sabre\DAV; + +class NeedPrivilegesTest extends \PHPUnit_Framework_TestCase { + + function testSerialize() { + + $uri = 'foo'; + $privileges = array( + '{DAV:}read', + '{DAV:}write', + ); + $ex = new NeedPrivileges($uri, $privileges); + + $server = new DAV\Server(); + $dom = new \DOMDocument('1.0','utf-8'); + $root = $dom->createElementNS('DAV:','d:root'); + $dom->appendChild($root); + + $ex->serialize($server, $root); + + $xpaths = array( + '/d:root' => 1, + '/d:root/d:need-privileges' => 1, + '/d:root/d:need-privileges/d:resource' => 2, + '/d:root/d:need-privileges/d:resource/d:href' => 2, + '/d:root/d:need-privileges/d:resource/d:privilege' => 2, + '/d:root/d:need-privileges/d:resource/d:privilege/d:read' => 1, + '/d:root/d:need-privileges/d:resource/d:privilege/d:write' => 1, + ); + + // Reloading because PHP DOM sucks + $dom2 = new \DOMDocument('1.0', 'utf-8'); + $dom2->loadXML($dom->saveXML()); + + $dxpath = new \DOMXPath($dom2); + $dxpath->registerNamespace('d','DAV:'); + foreach($xpaths as $xpath=>$count) { + + $this->assertEquals($count, $dxpath->query($xpath)->length, 'Looking for : ' . $xpath . ', we could only find ' . $dxpath->query($xpath)->length . ' elements, while we expected ' . $count); + + } + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/NoAbstractTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/NoAbstractTest.php new file mode 100644 index 0000000..2406c1c --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/NoAbstractTest.php @@ -0,0 +1,39 @@ +<?php + +namespace Sabre\DAVACL\Exception; + +use Sabre\DAV; + +class NoAbstractTest extends \PHPUnit_Framework_TestCase { + + function testSerialize() { + + $ex = new NoAbstract('message'); + + $server = new DAV\Server(); + $dom = new \DOMDocument('1.0','utf-8'); + $root = $dom->createElementNS('DAV:','d:root'); + $dom->appendChild($root); + + $ex->serialize($server, $root); + + $xpaths = array( + '/d:root' => 1, + '/d:root/d:no-abstract' => 1, + ); + + // Reloading because PHP DOM sucks + $dom2 = new \DOMDocument('1.0', 'utf-8'); + $dom2->loadXML($dom->saveXML()); + + $dxpath = new \DOMXPath($dom2); + $dxpath->registerNamespace('d','DAV:'); + foreach($xpaths as $xpath=>$count) { + + $this->assertEquals($count, $dxpath->query($xpath)->length, 'Looking for : ' . $xpath . ', we could only find ' . $dxpath->query($xpath)->length . ' elements, while we expected ' . $count); + + } + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/NotRecognizedPrincipalTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/NotRecognizedPrincipalTest.php new file mode 100644 index 0000000..6077b0b --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/NotRecognizedPrincipalTest.php @@ -0,0 +1,39 @@ +<?php + +namespace Sabre\DAVACL\Exception; + +use Sabre\DAV; + +class NotRecognizedPrincipalTest extends \PHPUnit_Framework_TestCase { + + function testSerialize() { + + $ex = new NotRecognizedPrincipal('message'); + + $server = new DAV\Server(); + $dom = new \DOMDocument('1.0','utf-8'); + $root = $dom->createElementNS('DAV:','d:root'); + $dom->appendChild($root); + + $ex->serialize($server, $root); + + $xpaths = array( + '/d:root' => 1, + '/d:root/d:recognized-principal' => 1, + ); + + // Reloading because PHP DOM sucks + $dom2 = new \DOMDocument('1.0', 'utf-8'); + $dom2->loadXML($dom->saveXML()); + + $dxpath = new \DOMXPath($dom2); + $dxpath->registerNamespace('d','DAV:'); + foreach($xpaths as $xpath=>$count) { + + $this->assertEquals($count, $dxpath->query($xpath)->length, 'Looking for : ' . $xpath . ', we could only find ' . $dxpath->query($xpath)->length . ' elements, while we expected ' . $count); + + } + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/NotSupportedPrivilegeTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/NotSupportedPrivilegeTest.php new file mode 100644 index 0000000..8e7b368 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Exception/NotSupportedPrivilegeTest.php @@ -0,0 +1,39 @@ +<?php + +namespace Sabre\DAVACL\Exception; + +use Sabre\DAV; + +class NotSupportedPrivilegeTest extends \PHPUnit_Framework_TestCase { + + function testSerialize() { + + $ex = new NotSupportedPrivilege('message'); + + $server = new DAV\Server(); + $dom = new \DOMDocument('1.0','utf-8'); + $root = $dom->createElementNS('DAV:','d:root'); + $dom->appendChild($root); + + $ex->serialize($server, $root); + + $xpaths = array( + '/d:root' => 1, + '/d:root/d:not-supported-privilege' => 1, + ); + + // Reloading because PHP DOM sucks + $dom2 = new \DOMDocument('1.0', 'utf-8'); + $dom2->loadXML($dom->saveXML()); + + $dxpath = new \DOMXPath($dom2); + $dxpath->registerNamespace('d','DAV:'); + foreach($xpaths as $xpath=>$count) { + + $this->assertEquals($count, $dxpath->query($xpath)->length, 'Looking for : ' . $xpath . ', we could only find ' . $dxpath->query($xpath)->length . ' elements, while we expected ' . $count); + + } + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/ExpandPropertiesTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/ExpandPropertiesTest.php new file mode 100644 index 0000000..5e99f2e --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/ExpandPropertiesTest.php @@ -0,0 +1,310 @@ +<?php + +namespace Sabre\DAVACL; + +use Sabre\DAV; +use Sabre\HTTP; + +require_once 'Sabre/HTTP/ResponseMock.php'; + +class ExpandPropertiesTest extends \PHPUnit_Framework_TestCase { + + function getServer() { + + $tree = array( + new DAV\Mock\PropertiesCollection('node1', [], array( + '{http://sabredav.org/ns}simple' => 'foo', + '{http://sabredav.org/ns}href' => new DAV\Xml\Property\Href('node2'), + '{DAV:}displayname' => 'Node 1', + )), + new DAV\Mock\PropertiesCollection('node2', [], array( + '{http://sabredav.org/ns}simple' => 'simple', + '{http://sabredav.org/ns}hreflist' => new DAV\Xml\Property\Href(['node1','node3']), + '{DAV:}displayname' => 'Node 2', + )), + new DAV\Mock\PropertiesCollection('node3', [], array( + '{http://sabredav.org/ns}simple' => 'simple', + '{DAV:}displayname' => 'Node 3', + )), + ); + + $fakeServer = new DAV\Server($tree); + $fakeServer->sapi = new HTTP\SapiMock(); + $fakeServer->debugExceptions = true; + $fakeServer->httpResponse = new HTTP\ResponseMock(); + $plugin = new Plugin(); + $plugin->allowAccessToNodesWithoutACL = true; + + $this->assertTrue($plugin instanceof Plugin); + $fakeServer->addPlugin($plugin); + $this->assertEquals($plugin, $fakeServer->getPlugin('acl')); + + return $fakeServer; + + } + + function testSimple() { + + $xml = '<?xml version="1.0"?> +<d:expand-property xmlns:d="DAV:"> + <d:property name="displayname" /> + <d:property name="foo" namespace="http://www.sabredav.org/NS/2010/nonexistant" /> + <d:property name="simple" namespace="http://sabredav.org/ns" /> + <d:property name="href" namespace="http://sabredav.org/ns" /> +</d:expand-property>'; + + $serverVars = array( + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_DEPTH' => '0', + 'REQUEST_URI' => '/node1', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody($xml); + + $server = $this->getServer(); + $server->httpRequest = $request; + + $server->exec(); + + $this->assertEquals(207, $server->httpResponse->status,'Incorrect status code received. Full body: ' . $server->httpResponse->body); + $this->assertEquals(array( + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ), $server->httpResponse->getHeaders()); + + + $check = array( + '/d:multistatus', + '/d:multistatus/d:response' => 1, + '/d:multistatus/d:response/d:href' => 1, + '/d:multistatus/d:response/d:propstat' => 2, + '/d:multistatus/d:response/d:propstat/d:prop' => 2, + '/d:multistatus/d:response/d:propstat/d:prop/d:displayname' => 1, + '/d:multistatus/d:response/d:propstat/d:prop/s:simple' => 1, + '/d:multistatus/d:response/d:propstat/d:prop/s:href' => 1, + '/d:multistatus/d:response/d:propstat/d:prop/s:href/d:href' => 1, + ); + + $xml = simplexml_load_string($server->httpResponse->body); + $xml->registerXPathNamespace('d','DAV:'); + $xml->registerXPathNamespace('s','http://sabredav.org/ns'); + foreach($check as $v1=>$v2) { + + $xpath = is_int($v1)?$v2:$v1; + + $result = $xml->xpath($xpath); + + $count = 1; + if (!is_int($v1)) $count = $v2; + + $this->assertEquals($count,count($result), 'we expected ' . $count . ' appearances of ' . $xpath . ' . We found ' . count($result) . '. Full response: ' . $server->httpResponse->body); + + } + + } + + /** + * @depends testSimple + */ + function testExpand() { + + $xml = '<?xml version="1.0"?> +<d:expand-property xmlns:d="DAV:"> + <d:property name="href" namespace="http://sabredav.org/ns"> + <d:property name="displayname" /> + </d:property> +</d:expand-property>'; + + $serverVars = array( + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_DEPTH' => '0', + 'REQUEST_URI' => '/node1', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody($xml); + + $server = $this->getServer(); + $server->httpRequest = $request; + + $server->exec(); + + $this->assertEquals(207, $server->httpResponse->status, 'Incorrect response status received. Full response body: ' . $server->httpResponse->body); + $this->assertEquals(array( + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ), $server->httpResponse->getHeaders()); + + + $check = array( + '/d:multistatus', + '/d:multistatus/d:response' => 1, + '/d:multistatus/d:response/d:href' => 1, + '/d:multistatus/d:response/d:propstat' => 1, + '/d:multistatus/d:response/d:propstat/d:prop' => 1, + '/d:multistatus/d:response/d:propstat/d:prop/s:href' => 1, + '/d:multistatus/d:response/d:propstat/d:prop/s:href/d:response' => 1, + '/d:multistatus/d:response/d:propstat/d:prop/s:href/d:response/d:href' => 1, + '/d:multistatus/d:response/d:propstat/d:prop/s:href/d:response/d:propstat' => 1, + '/d:multistatus/d:response/d:propstat/d:prop/s:href/d:response/d:propstat/d:prop' => 1, + '/d:multistatus/d:response/d:propstat/d:prop/s:href/d:response/d:propstat/d:prop/d:displayname' => 1, + ); + + $xml = simplexml_load_string($server->httpResponse->body); + $xml->registerXPathNamespace('d','DAV:'); + $xml->registerXPathNamespace('s','http://sabredav.org/ns'); + foreach($check as $v1=>$v2) { + + $xpath = is_int($v1)?$v2:$v1; + + $result = $xml->xpath($xpath); + + $count = 1; + if (!is_int($v1)) $count = $v2; + + $this->assertEquals($count,count($result), 'we expected ' . $count . ' appearances of ' . $xpath . ' . We found ' . count($result) . ' Full response body: ' . $server->httpResponse->getBodyAsString()); + + } + + } + + /** + * @depends testSimple + */ + function testExpandHrefList() { + + $xml = '<?xml version="1.0"?> +<d:expand-property xmlns:d="DAV:"> + <d:property name="hreflist" namespace="http://sabredav.org/ns"> + <d:property name="displayname" /> + </d:property> +</d:expand-property>'; + + $serverVars = array( + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_DEPTH' => '0', + 'REQUEST_URI' => '/node2', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody($xml); + + $server = $this->getServer(); + $server->httpRequest = $request; + + $server->exec(); + + $this->assertEquals(207, $server->httpResponse->status); + $this->assertEquals(array( + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ), $server->httpResponse->getHeaders()); + + + $check = array( + '/d:multistatus', + '/d:multistatus/d:response' => 1, + '/d:multistatus/d:response/d:href' => 1, + '/d:multistatus/d:response/d:propstat' => 1, + '/d:multistatus/d:response/d:propstat/d:prop' => 1, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist' => 1, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist/d:response' => 2, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist/d:response/d:href' => 2, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist/d:response/d:propstat' => 2, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist/d:response/d:propstat/d:prop' => 2, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist/d:response/d:propstat/d:prop/d:displayname' => 2, + ); + + $xml = simplexml_load_string($server->httpResponse->body); + $xml->registerXPathNamespace('d','DAV:'); + $xml->registerXPathNamespace('s','http://sabredav.org/ns'); + foreach($check as $v1=>$v2) { + + $xpath = is_int($v1)?$v2:$v1; + + $result = $xml->xpath($xpath); + + $count = 1; + if (!is_int($v1)) $count = $v2; + + $this->assertEquals($count,count($result), 'we expected ' . $count . ' appearances of ' . $xpath . ' . We found ' . count($result)); + + } + + } + + /** + * @depends testExpand + */ + function testExpandDeep() { + + $xml = '<?xml version="1.0"?> +<d:expand-property xmlns:d="DAV:"> + <d:property name="hreflist" namespace="http://sabredav.org/ns"> + <d:property name="href" namespace="http://sabredav.org/ns"> + <d:property name="displayname" /> + </d:property> + <d:property name="displayname" /> + </d:property> +</d:expand-property>'; + + $serverVars = array( + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_DEPTH' => '0', + 'REQUEST_URI' => '/node2', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody($xml); + + $server = $this->getServer(); + $server->httpRequest = $request; + + $server->exec(); + + $this->assertEquals(207, $server->httpResponse->status); + $this->assertEquals(array( + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ), $server->httpResponse->getHeaders()); + + + $check = array( + '/d:multistatus', + '/d:multistatus/d:response' => 1, + '/d:multistatus/d:response/d:href' => 1, + '/d:multistatus/d:response/d:propstat' => 1, + '/d:multistatus/d:response/d:propstat/d:prop' => 1, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist' => 1, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist/d:response' => 2, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist/d:response/d:href' => 2, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist/d:response/d:propstat' => 3, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist/d:response/d:propstat/d:prop' => 3, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist/d:response/d:propstat/d:prop/d:displayname' => 2, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist/d:response/d:propstat/d:prop/s:href' => 2, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist/d:response/d:propstat/d:prop/s:href/d:response' => 1, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist/d:response/d:propstat/d:prop/s:href/d:response/d:href' => 1, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist/d:response/d:propstat/d:prop/s:href/d:response/d:propstat' => 1, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist/d:response/d:propstat/d:prop/s:href/d:response/d:propstat/d:prop' => 1, + '/d:multistatus/d:response/d:propstat/d:prop/s:hreflist/d:response/d:propstat/d:prop/s:href/d:response/d:propstat/d:prop/d:displayname' => 1, + ); + + $xml = simplexml_load_string($server->httpResponse->body); + $xml->registerXPathNamespace('d','DAV:'); + $xml->registerXPathNamespace('s','http://sabredav.org/ns'); + foreach($check as $v1=>$v2) { + + $xpath = is_int($v1)?$v2:$v1; + + $result = $xml->xpath($xpath); + + $count = 1; + if (!is_int($v1)) $count = $v2; + + $this->assertEquals($count,count($result), 'we expected ' . $count . ' appearances of ' . $xpath . ' . We found ' . count($result)); + + } + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/FS/CollectionTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/FS/CollectionTest.php new file mode 100644 index 0000000..af18e7c --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/FS/CollectionTest.php @@ -0,0 +1,44 @@ +<?php + +namespace Sabre\DAVACL\FS; + +class CollectionTest extends FileTest { + + function setUp() { + + $this->path = SABRE_TEMPDIR; + $this->sut = new Collection($this->path, $this->acl, $this->owner); + + } + + function tearDown() { + + \Sabre\TestUtil::clearTempDir(); + + } + + function testGetChildFile() { + + file_put_contents(SABRE_TEMPDIR . '/file.txt', 'hello'); + $child = $this->sut->getChild('file.txt'); + $this->assertInstanceOf('Sabre\\DAVACL\\FS\\File', $child); + + $this->assertEquals('file.txt', $child->getName()); + $this->assertEquals($this->acl, $child->getACL()); + $this->assertEquals($this->owner, $child->getOwner()); + + } + + function testGetChildDirectory() { + + mkdir(SABRE_TEMPDIR . '/dir'); + $child = $this->sut->getChild('dir'); + $this->assertInstanceOf('Sabre\\DAVACL\\FS\\Collection', $child); + + $this->assertEquals('dir', $child->getName()); + $this->assertEquals($this->acl, $child->getACL()); + $this->assertEquals($this->owner, $child->getOwner()); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/FS/FileTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/FS/FileTest.php new file mode 100644 index 0000000..f57b2fa --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/FS/FileTest.php @@ -0,0 +1,73 @@ +<?php + +namespace Sabre\DAVACL\FS; + +class FileTest extends \PHPUnit_Framework_TestCase { + + /** + * System under test + * + * @var File + */ + protected $sut; + + protected $path = 'foo'; + protected $acl = [ + [ + 'privilege' => '{DAV:}read', + 'principal' => '{DAV:}authenticated', + ] + ]; + + protected $owner = 'principals/evert'; + + function setUp() { + + $this->sut = new File($this->path, $this->acl, $this->owner); + + } + + function testGetOwner() { + + $this->assertEquals( + $this->owner, + $this->sut->getOwner() + ); + + } + + function testGetGroup() { + + $this->assertNull( + $this->sut->getGroup() + ); + + } + + function testGetACL() { + + $this->assertEquals( + $this->acl, + $this->sut->getACL() + ); + + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + */ + function testSetAcl() { + + $this->sut->setACL([]); + + } + + function testGetSupportedPrivilegeSet() { + + $this->assertNull( + $this->sut->getSupportedPrivilegeSet() + ); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/FS/HomeCollectionTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/FS/HomeCollectionTest.php new file mode 100644 index 0000000..0f9c65a --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/FS/HomeCollectionTest.php @@ -0,0 +1,121 @@ +<?php + +namespace Sabre\DAVACL\FS; + +use Sabre\DAVACL\PrincipalBackend\Mock as PrincipalBackend; + +class HomeCollectionTest extends \PHPUnit_Framework_TestCase { + + /** + * System under test + * + * @var HomeCollection + */ + protected $sut; + + protected $path; + protected $name = 'thuis'; + + function setUp() { + + $principalBackend = new PrincipalBackend(); + + $this->path = SABRE_TEMPDIR . '/home'; + + $this->sut = new HomeCollection($principalBackend, $this->path); + $this->sut->collectionName = $this->name; + + + } + + function tearDown() { + + \Sabre\TestUtil::clearTempDir(); + + } + + function testGetName() { + + $this->assertEquals( + $this->name, + $this->sut->getName() + ); + + } + + function testGetChild() { + + $child = $this->sut->getChild('user1'); + $this->assertInstanceOf('Sabre\\DAVACL\\FS\\Collection', $child); + $this->assertEquals('user1', $child->getName()); + + $owner = 'principals/user1'; + $acl = [ + [ + 'privilege' => '{DAV:}read', + 'principal' => $owner, + 'protected' => true, + ], + [ + 'privilege' => '{DAV:}write', + 'principal' => $owner, + 'protected' => true, + ], + ]; + + $this->assertEquals($acl, $child->getACL()); + $this->assertEquals($owner, $child->getOwner()); + + } + + function testGetOwner() { + + $this->assertNull( + $this->sut->getOwner() + ); + + } + + function testGetGroup() { + + $this->assertNull( + $this->sut->getGroup() + ); + + } + + function testGetACL() { + + $acl = [ + [ + 'principal' => '{DAV:}authenticated', + 'privilege' => '{DAV:}read', + 'protected' => true, + ] + ]; + + $this->assertEquals( + $acl, + $this->sut->getACL() + ); + + } + + /** + * @expectedException \Sabre\DAV\Exception\Forbidden + */ + function testSetAcl() { + + $this->sut->setACL([]); + + } + + function testGetSupportedPrivilegeSet() { + + $this->assertNull( + $this->sut->getSupportedPrivilegeSet() + ); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/MockACLNode.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/MockACLNode.php new file mode 100644 index 0000000..7b3e8fc --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/MockACLNode.php @@ -0,0 +1,56 @@ +<?php + +namespace Sabre\DAVACL; + +use Sabre\DAV; +use Sabre\HTTP; + +class MockACLNode extends DAV\Node implements IACL { + + public $name; + public $acl; + + function __construct($name, array $acl = array()) { + + $this->name = $name; + $this->acl = $acl; + + } + + function getName() { + + return $this->name; + + } + + function getOwner() { + + return null; + + } + + function getGroup() { + + return null; + + } + + function getACL() { + + return $this->acl; + + } + + function setACL(array $acl) { + + $this->acl = $acl; + + } + + function getSupportedPrivilegeSet() { + + return null; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/MockPrincipal.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/MockPrincipal.php new file mode 100644 index 0000000..dd8542b --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/MockPrincipal.php @@ -0,0 +1,66 @@ +<?php + +namespace Sabre\DAVACL; + +use Sabre\DAV; +use Sabre\HTTP; + +class MockPrincipal extends DAV\Node implements IPrincipal { + + public $name; + public $principalUrl; + public $groupMembership = array(); + public $groupMemberSet = array(); + + function __construct($name,$principalUrl,array $groupMembership = array(), array $groupMemberSet = array()) { + + $this->name = $name; + $this->principalUrl = $principalUrl; + $this->groupMembership = $groupMembership; + $this->groupMemberSet = $groupMemberSet; + + } + + function getName() { + + return $this->name; + + } + + function getDisplayName() { + + return $this->getName(); + + } + + function getAlternateUriSet() { + + return array(); + + } + + function getPrincipalUrl() { + + return $this->principalUrl; + + } + + function getGroupMemberSet() { + + return $this->groupMemberSet; + + } + + function getGroupMemberShip() { + + return $this->groupMembership; + + } + + function setGroupMemberSet(array $groupMemberSet) { + + $this->groupMemberSet = $groupMemberSet; + + } +} + diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PluginAdminTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PluginAdminTest.php new file mode 100644 index 0000000..f824948 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PluginAdminTest.php @@ -0,0 +1,85 @@ +<?php + +namespace Sabre\DAVACL; + +use Sabre\DAV; +use Sabre\HTTP; + + +require_once 'Sabre/DAVACL/MockACLNode.php'; +require_once 'Sabre/HTTP/ResponseMock.php'; + +class PluginAdminTest extends \PHPUnit_Framework_TestCase { + + function testNoAdminAccess() { + + $principalBackend = new PrincipalBackend\Mock(); + + $tree = array( + new MockACLNode('adminonly', array()), + new PrincipalCollection($principalBackend), + ); + + $fakeServer = new DAV\Server($tree); + $fakeServer->sapi = new HTTP\SapiMock(); + $plugin = new DAV\Auth\Plugin(new DAV\Auth\Backend\Mock(),'realm'); + $fakeServer->addPlugin($plugin); + $plugin = new Plugin(); + $fakeServer->addPlugin($plugin); + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'OPTIONS', + 'HTTP_DEPTH' => 1, + 'REQUEST_URI' => '/adminonly', + )); + + $response = new HTTP\ResponseMock(); + + $fakeServer->httpRequest = $request; + $fakeServer->httpResponse = $response; + + $fakeServer->exec(); + + $this->assertEquals(403, $response->status); + + } + + /** + * @depends testNoAdminAccess + */ + function testAdminAccess() { + + $principalBackend = new PrincipalBackend\Mock(); + + $tree = array( + new MockACLNode('adminonly', array()), + new PrincipalCollection($principalBackend), + ); + + $fakeServer = new DAV\Server($tree); + $fakeServer->sapi = new HTTP\SapiMock(); + $plugin = new DAV\Auth\Plugin(new DAV\Auth\Backend\Mock(),'realm'); + $fakeServer->addPlugin($plugin); + $plugin = new Plugin(); + $plugin->adminPrincipals = array( + 'principals/admin', + ); + $fakeServer->addPlugin($plugin); + + $request = HTTP\Sapi::createFromServerArray(array( + 'REQUEST_METHOD' => 'OPTIONS', + 'HTTP_DEPTH' => 1, + 'REQUEST_URI' => '/adminonly', + )); + + $response = new HTTP\ResponseMock(); + + $fakeServer->httpRequest = $request; + $fakeServer->httpResponse = $response; + + $fakeServer->exec(); + + $this->assertEquals(200, $response->status); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PluginPropertiesTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PluginPropertiesTest.php new file mode 100644 index 0000000..b3d6a17 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PluginPropertiesTest.php @@ -0,0 +1,352 @@ +<?php + +namespace Sabre\DAVACL; + +use Sabre\DAV; +use Sabre\HTTP; + +class PluginPropertiesTest extends \PHPUnit_Framework_TestCase { + + function testPrincipalCollectionSet() { + + $plugin = new Plugin(); + $plugin->principalCollectionSet = [ + 'principals1', + 'principals2', + ]; + + $requestedProperties = [ + '{DAV:}principal-collection-set', + ]; + + $server = new DAV\Server(new DAV\SimpleCollection('root')); + $server->addPlugin($plugin); + + $result = $server->getPropertiesForPath('', $requestedProperties); + $result = $result[0]; + + $this->assertEquals(1,count($result[200])); + $this->assertArrayHasKey('{DAV:}principal-collection-set',$result[200]); + $this->assertInstanceOf('Sabre\\DAV\\Xml\\Property\\Href', $result[200]['{DAV:}principal-collection-set']); + + $expected = [ + 'principals1/', + 'principals2/', + ]; + + + $this->assertEquals($expected, $result[200]['{DAV:}principal-collection-set']->getHrefs()); + + + } + + function testCurrentUserPrincipal() { + + $fakeServer = new DAV\Server(); + $plugin = new DAV\Auth\Plugin(new DAV\Auth\Backend\Mock(),'realm'); + $fakeServer->addPlugin($plugin); + $plugin = new Plugin(); + $fakeServer->addPlugin($plugin); + + + $requestedProperties = [ + '{DAV:}current-user-principal', + ]; + + $result = $fakeServer->getPropertiesForPath('', $requestedProperties); + $result = $result[0]; + + $this->assertEquals(1,count($result[200])); + $this->assertArrayHasKey('{DAV:}current-user-principal',$result[200]); + $this->assertInstanceOf('Sabre\DAVACL\Xml\Property\Principal', $result[200]['{DAV:}current-user-principal']); + $this->assertEquals(Xml\Property\Principal::UNAUTHENTICATED, $result[200]['{DAV:}current-user-principal']->getType()); + + // This will force the login + $fakeServer->emit('beforeMethod', [$fakeServer->httpRequest, $fakeServer->httpResponse]); + + $result = $fakeServer->getPropertiesForPath('', $requestedProperties); + $result = $result[0]; + + $this->assertEquals(1,count($result[200])); + $this->assertArrayHasKey('{DAV:}current-user-principal',$result[200]); + $this->assertInstanceOf('Sabre\DAVACL\Xml\Property\Principal', $result[200]['{DAV:}current-user-principal']); + $this->assertEquals(Xml\Property\Principal::HREF, $result[200]['{DAV:}current-user-principal']->getType()); + $this->assertEquals('principals/admin/', $result[200]['{DAV:}current-user-principal']->getHref()); + + } + + function testSupportedPrivilegeSet() { + + $plugin = new Plugin(); + $server = new DAV\Server(); + $server->addPlugin($plugin); + + $requestedProperties = [ + '{DAV:}supported-privilege-set', + ]; + + $result = $server->getPropertiesForPath('', $requestedProperties); + $result = $result[0]; + + $this->assertEquals(1,count($result[200])); + $this->assertArrayHasKey('{DAV:}supported-privilege-set',$result[200]); + $this->assertInstanceOf('Sabre\\DAVACL\\Xml\\Property\\SupportedPrivilegeSet', $result[200]['{DAV:}supported-privilege-set']); + + $server = new DAV\Server(); + + $prop = $result[200]['{DAV:}supported-privilege-set']; + $result = $server->xml->write('{DAV:}root', $prop); + + $xpaths = [ + '/d:root' => 1, + '/d:root/d:supported-privilege' => 1, + '/d:root/d:supported-privilege/d:privilege' => 1, + '/d:root/d:supported-privilege/d:privilege/d:all' => 1, + '/d:root/d:supported-privilege/d:abstract' => 1, + '/d:root/d:supported-privilege/d:supported-privilege' => 2, + '/d:root/d:supported-privilege/d:supported-privilege/d:privilege' => 2, + '/d:root/d:supported-privilege/d:supported-privilege/d:privilege/d:read' => 1, + '/d:root/d:supported-privilege/d:supported-privilege/d:privilege/d:write' => 1, + '/d:root/d:supported-privilege/d:supported-privilege/d:supported-privilege' => 8, + '/d:root/d:supported-privilege/d:supported-privilege/d:supported-privilege/d:privilege' => 8, + '/d:root/d:supported-privilege/d:supported-privilege/d:supported-privilege/d:privilege/d:read-acl' => 1, + '/d:root/d:supported-privilege/d:supported-privilege/d:supported-privilege/d:privilege/d:read-current-user-privilege-set' => 1, + '/d:root/d:supported-privilege/d:supported-privilege/d:supported-privilege/d:privilege/d:write-content' => 1, + '/d:root/d:supported-privilege/d:supported-privilege/d:supported-privilege/d:privilege/d:write-properties' => 1, + '/d:root/d:supported-privilege/d:supported-privilege/d:supported-privilege/d:privilege/d:write-acl' => 1, + '/d:root/d:supported-privilege/d:supported-privilege/d:supported-privilege/d:privilege/d:bind' => 1, + '/d:root/d:supported-privilege/d:supported-privilege/d:supported-privilege/d:privilege/d:unbind' => 1, + '/d:root/d:supported-privilege/d:supported-privilege/d:supported-privilege/d:privilege/d:unlock' => 1, + '/d:root/d:supported-privilege/d:supported-privilege/d:supported-privilege/d:abstract' => 0, + ]; + + + // reloading because php dom sucks + $dom2 = new \DOMDocument('1.0', 'utf-8'); + $dom2->loadXML($result); + + $dxpath = new \DOMXPath($dom2); + $dxpath->registerNamespace('d','DAV:'); + foreach($xpaths as $xpath=>$count) { + + $this->assertEquals($count, $dxpath->query($xpath)->length, 'Looking for : ' . $xpath . ', we could only find ' . $dxpath->query($xpath)->length . ' elements, while we expected ' . $count. ' Full XML: ' . $result); + + } + + } + + function testACL() { + + $plugin = new Plugin(); + + $nodes = [ + new MockACLNode('foo', [ + [ + 'principal' => 'principals/admin', + 'privilege' => '{DAV:}read', + ] + ]), + new DAV\SimpleCollection('principals', [ + $principal = new MockPrincipal('admin','principals/admin'), + ]), + + ]; + + $server = new DAV\Server($nodes); + $server->addPlugin($plugin); + $authPlugin = new DAV\Auth\Plugin(new DAV\Auth\Backend\Mock(),'realm'); + $server->addPlugin($authPlugin); + + // Force login + $authPlugin->beforeMethod(new HTTP\Request(), new HTTP\Response()); + + $requestedProperties = [ + '{DAV:}acl', + ]; + + $result = $server->getPropertiesForPath('foo', $requestedProperties); + $result = $result[0]; + + $this->assertEquals(1,count($result[200]),'The {DAV:}acl property did not return from the list. Full list: ' . print_r($result, true)); + $this->assertArrayHasKey('{DAV:}acl',$result[200]); + $this->assertInstanceOf('Sabre\\DAVACL\\Xml\Property\\Acl', $result[200]['{DAV:}acl']); + + } + + function testACLRestrictions() { + + $plugin = new Plugin(); + + $nodes = [ + new MockACLNode('foo', [ + [ + 'principal' => 'principals/admin', + 'privilege' => '{DAV:}read', + ] + ]), + new DAV\SimpleCollection('principals', [ + $principal = new MockPrincipal('admin','principals/admin'), + ]), + + ]; + + $server = new DAV\Server($nodes); + $server->addPlugin($plugin); + $authPlugin = new DAV\Auth\Plugin(new DAV\Auth\Backend\Mock(),'realm'); + $server->addPlugin($authPlugin); + + // Force login + $authPlugin->beforeMethod(new HTTP\Request(), new HTTP\Response()); + + $requestedProperties = [ + '{DAV:}acl-restrictions', + ]; + + $result = $server->getPropertiesForPath('foo', $requestedProperties); + $result = $result[0]; + + $this->assertEquals(1,count($result[200]),'The {DAV:}acl-restrictions property did not return from the list. Full list: ' . print_r($result, true)); + $this->assertArrayHasKey('{DAV:}acl-restrictions',$result[200]); + $this->assertInstanceOf('Sabre\\DAVACL\\Xml\\Property\\AclRestrictions', $result[200]['{DAV:}acl-restrictions']); + + } + + function testAlternateUriSet() { + + $tree = [ + new DAV\SimpleCollection('principals', [ + $principal = new MockPrincipal('user','principals/user'), + ]) + ]; + + $fakeServer = new DAV\Server($tree); + //$plugin = new DAV\Auth\Plugin(new DAV\Auth\MockBackend(),'realm'); + //$fakeServer->addPlugin($plugin); + $plugin = new Plugin(); + $fakeServer->addPlugin($plugin); + + $requestedProperties = [ + '{DAV:}alternate-URI-set', + ]; + $result = $fakeServer->getPropertiesForPath('principals/user', $requestedProperties); + $result = $result[0]; + + $this->assertTrue(isset($result[200])); + $this->assertTrue(isset($result[200]['{DAV:}alternate-URI-set'])); + $this->assertInstanceOf('Sabre\\DAV\\Xml\\Property\\Href', $result[200]['{DAV:}alternate-URI-set']); + + $this->assertEquals([], $result[200]['{DAV:}alternate-URI-set']->getHrefs()); + + } + + function testPrincipalURL() { + + $tree = [ + new DAV\SimpleCollection('principals', [ + $principal = new MockPrincipal('user','principals/user'), + ]), + ]; + + $fakeServer = new DAV\Server($tree); + //$plugin = new DAV\Auth\Plugin(new DAV\Auth\MockBackend(),'realm'); + //$fakeServer->addPlugin($plugin); + $plugin = new Plugin(); + $fakeServer->addPlugin($plugin); + + $requestedProperties = [ + '{DAV:}principal-URL', + ]; + + $result = $fakeServer->getPropertiesForPath('principals/user', $requestedProperties); + $result = $result[0]; + + $this->assertTrue(isset($result[200])); + $this->assertTrue(isset($result[200]['{DAV:}principal-URL'])); + $this->assertInstanceOf('Sabre\\DAV\\Xml\\Property\\Href', $result[200]['{DAV:}principal-URL']); + + $this->assertEquals('principals/user/', $result[200]['{DAV:}principal-URL']->getHref()); + + } + + function testGroupMemberSet() { + + $tree = [ + new DAV\SimpleCollection('principals', [ + $principal = new MockPrincipal('user','principals/user'), + ]), + ]; + + $fakeServer = new DAV\Server($tree); + //$plugin = new DAV\Auth\Plugin(new DAV\Auth\MockBackend(),'realm'); + //$fakeServer->addPlugin($plugin); + $plugin = new Plugin(); + $fakeServer->addPlugin($plugin); + + $requestedProperties = [ + '{DAV:}group-member-set', + ]; + + $result = $fakeServer->getPropertiesForPath('principals/user', $requestedProperties); + $result = $result[0]; + + $this->assertTrue(isset($result[200])); + $this->assertTrue(isset($result[200]['{DAV:}group-member-set'])); + $this->assertInstanceOf('Sabre\\DAV\\Xml\\Property\\Href', $result[200]['{DAV:}group-member-set']); + + $this->assertEquals([], $result[200]['{DAV:}group-member-set']->getHrefs()); + + } + + function testGroupMemberShip() { + + $tree = [ + new DAV\SimpleCollection('principals', [ + $principal = new MockPrincipal('user','principals/user'), + ]), + ]; + + $fakeServer = new DAV\Server($tree); + $plugin = new Plugin(); + $fakeServer->addPlugin($plugin); + + $requestedProperties = [ + '{DAV:}group-membership', + ]; + + $result = $fakeServer->getPropertiesForPath('principals/user', $requestedProperties); + $result = $result[0]; + + $this->assertTrue(isset($result[200])); + $this->assertTrue(isset($result[200]['{DAV:}group-membership'])); + $this->assertInstanceOf('Sabre\\DAV\\Xml\\Property\\Href', $result[200]['{DAV:}group-membership']); + + $this->assertEquals([], $result[200]['{DAV:}group-membership']->getHrefs()); + + } + + function testGetDisplayName() { + + $tree = [ + new DAV\SimpleCollection('principals', [ + $principal = new MockPrincipal('user','principals/user'), + ]), + ]; + + $fakeServer = new DAV\Server($tree); + $plugin = new Plugin(); + $fakeServer->addPlugin($plugin); + + $requestedProperties = [ + '{DAV:}displayname', + ]; + + $result = $fakeServer->getPropertiesForPath('principals/user', $requestedProperties); + $result = $result[0]; + + $this->assertTrue(isset($result[200])); + $this->assertTrue(isset($result[200]['{DAV:}displayname'])); + + $this->assertEquals('user', $result[200]['{DAV:}displayname']); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PluginUpdatePropertiesTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PluginUpdatePropertiesTest.php new file mode 100644 index 0000000..64cedd1 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PluginUpdatePropertiesTest.php @@ -0,0 +1,111 @@ +<?php + +namespace Sabre\DAVACL; + +use Sabre\DAV; +use Sabre\HTTP; + + +require_once 'Sabre/DAVACL/MockPrincipal.php'; + +class PluginUpdatePropertiesTest extends \PHPUnit_Framework_TestCase { + + function testUpdatePropertiesPassthrough() { + + $tree = array( + new DAV\SimpleCollection('foo'), + ); + $server = new DAV\Server($tree); + $server->addPlugin(new Plugin()); + + $result = $server->updateProperties('foo', array( + '{DAV:}foo' => 'bar', + )); + + $expected = array( + '{DAV:}foo' => 403, + ); + + $this->assertEquals($expected, $result); + + } + + function testRemoveGroupMembers() { + + $tree = array( + new MockPrincipal('foo','foo'), + ); + $server = new DAV\Server($tree); + $server->addPlugin(new Plugin()); + + $result = $server->updateProperties('foo', array( + '{DAV:}group-member-set' => null, + )); + + $expected = array( + '{DAV:}group-member-set' => 204 + ); + + $this->assertEquals($expected, $result); + $this->assertEquals(array(),$tree[0]->getGroupMemberSet()); + + } + + function testSetGroupMembers() { + + $tree = [ + new MockPrincipal('foo','foo'), + ]; + $server = new DAV\Server($tree); + $server->addPlugin(new Plugin()); + + $result = $server->updateProperties('foo', [ + '{DAV:}group-member-set' => new DAV\Xml\Property\Href(['/bar','/baz'], true), + ]); + + $expected = [ + '{DAV:}group-member-set' => 200 + ]; + + $this->assertEquals($expected, $result); + $this->assertEquals(['bar', 'baz'],$tree[0]->getGroupMemberSet()); + + } + + /** + * @expectedException Sabre\DAV\Exception + */ + function testSetBadValue() { + + $tree = array( + new MockPrincipal('foo','foo'), + ); + $server = new DAV\Server($tree); + $server->addPlugin(new Plugin()); + + $result = $server->updateProperties('foo', array( + '{DAV:}group-member-set' => new \StdClass(), + )); + + } + + function testSetBadNode() { + + $tree = [ + new DAV\SimpleCollection('foo'), + ]; + $server = new DAV\Server($tree); + $server->addPlugin(new Plugin()); + + $result = $server->updateProperties('foo', [ + '{DAV:}group-member-set' => new DAV\Xml\Property\Href(['/bar','/baz'],false), + ]); + + $expected = [ + '{DAV:}group-member-set' => 403, + ]; + + $this->assertEquals($expected, $result); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/AbstractPDOTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/AbstractPDOTest.php new file mode 100644 index 0000000..5aab1b7 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/AbstractPDOTest.php @@ -0,0 +1,178 @@ +<?php + +namespace Sabre\DAVACL\PrincipalBackend; + +use Sabre\DAV; +use Sabre\HTTP; + + +abstract class AbstractPDOTest extends \PHPUnit_Framework_TestCase { + + abstract function getPDO(); + + function testConstruct() { + + $pdo = $this->getPDO(); + $backend = new PDO($pdo); + $this->assertTrue($backend instanceof PDO); + + } + + /** + * @depends testConstruct + */ + function testGetPrincipalsByPrefix() { + + $pdo = $this->getPDO(); + $backend = new PDO($pdo); + + $expected = array( + array( + 'uri' => 'principals/user', + '{http://sabredav.org/ns}email-address' => 'user@example.org', + '{DAV:}displayname' => 'User', + ), + array( + 'uri' => 'principals/group', + '{http://sabredav.org/ns}email-address' => 'group@example.org', + '{DAV:}displayname' => 'Group', + ), + ); + + $this->assertEquals($expected, $backend->getPrincipalsByPrefix('principals')); + $this->assertEquals(array(), $backend->getPrincipalsByPrefix('foo')); + + } + + /** + * @depends testConstruct + */ + function testGetPrincipalByPath() { + + $pdo = $this->getPDO(); + $backend = new PDO($pdo); + + $expected = array( + 'id' => 1, + 'uri' => 'principals/user', + '{http://sabredav.org/ns}email-address' => 'user@example.org', + '{DAV:}displayname' => 'User', + ); + + $this->assertEquals($expected, $backend->getPrincipalByPath('principals/user')); + $this->assertEquals(null, $backend->getPrincipalByPath('foo')); + + } + + function testGetGroupMemberSet() { + + $pdo = $this->getPDO(); + $backend = new PDO($pdo); + $expected = array('principals/user'); + + $this->assertEquals($expected,$backend->getGroupMemberSet('principals/group')); + + } + + function testGetGroupMembership() { + + $pdo = $this->getPDO(); + $backend = new PDO($pdo); + $expected = array('principals/group'); + + $this->assertEquals($expected,$backend->getGroupMembership('principals/user')); + + } + + function testSetGroupMemberSet() { + + $pdo = $this->getPDO(); + + // Start situation + $backend = new PDO($pdo); + $this->assertEquals(array('principals/user'), $backend->getGroupMemberSet('principals/group')); + + // Removing all principals + $backend->setGroupMemberSet('principals/group', array()); + $this->assertEquals(array(), $backend->getGroupMemberSet('principals/group')); + + // Adding principals again + $backend->setGroupMemberSet('principals/group', array('principals/user')); + $this->assertEquals(array('principals/user'), $backend->getGroupMemberSet('principals/group')); + + + } + + function testSearchPrincipals() { + + $pdo = $this->getPDO(); + + $backend = new PDO($pdo); + + $result = $backend->searchPrincipals('principals', array('{DAV:}blabla' => 'foo')); + $this->assertEquals(array(), $result); + + $result = $backend->searchPrincipals('principals', array('{DAV:}displayname' => 'ou')); + $this->assertEquals(array('principals/group'), $result); + + $result = $backend->searchPrincipals('principals', array('{DAV:}displayname' => 'UsEr', '{http://sabredav.org/ns}email-address' => 'USER@EXAMPLE')); + $this->assertEquals(array('principals/user'), $result); + + $result = $backend->searchPrincipals('mom', array('{DAV:}displayname' => 'UsEr', '{http://sabredav.org/ns}email-address' => 'USER@EXAMPLE')); + $this->assertEquals(array(), $result); + + } + + function testUpdatePrincipal() { + + $pdo = $this->getPDO(); + $backend = new PDO($pdo); + + $propPatch = new DAV\PropPatch([ + '{DAV:}displayname' => 'pietje', + ]); + + $backend->updatePrincipal('principals/user', $propPatch); + $result = $propPatch->commit(); + + $this->assertTrue($result); + + $this->assertEquals(array( + 'id' => 1, + 'uri' => 'principals/user', + '{DAV:}displayname' => 'pietje', + '{http://sabredav.org/ns}email-address' => 'user@example.org', + ), $backend->getPrincipalByPath('principals/user')); + + } + + function testUpdatePrincipalUnknownField() { + + $pdo = $this->getPDO(); + $backend = new PDO($pdo); + + $propPatch = new DAV\PropPatch([ + '{DAV:}displayname' => 'pietje', + '{DAV:}unknown' => 'foo', + ]); + + $backend->updatePrincipal('principals/user', $propPatch); + $result = $propPatch->commit(); + + $this->assertFalse($result); + + $this->assertEquals(array( + '{DAV:}displayname' => 424, + '{DAV:}unknown' => 403 + ), $propPatch->getResult()); + + $this->assertEquals(array( + 'id' => '1', + 'uri' => 'principals/user', + '{DAV:}displayname' => 'User', + '{http://sabredav.org/ns}email-address' => 'user@example.org', + ), $backend->getPrincipalByPath('principals/user')); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/Mock.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/Mock.php new file mode 100644 index 0000000..afb094a --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/Mock.php @@ -0,0 +1,168 @@ +<?php + +namespace Sabre\DAVACL\PrincipalBackend; + +class Mock extends AbstractBackend { + + public $groupMembers = []; + public $principals; + + function __construct(array $principals = null) { + + $this->principals = $principals; + + if (is_null($principals)) { + + $this->principals = [ + [ + 'uri' => 'principals/user1', + '{DAV:}displayname' => 'User 1', + '{http://sabredav.org/ns}email-address' => 'user1.sabredav@sabredav.org', + '{http://sabredav.org/ns}vcard-url' => 'addressbooks/user1/book1/vcard1.vcf', + ], + [ + 'uri' => 'principals/admin', + '{DAV:}displayname' => 'Admin', + ], + [ + 'uri' => 'principals/user2', + '{DAV:}displayname' => 'User 2', + '{http://sabredav.org/ns}email-address' => 'user2.sabredav@sabredav.org', + ], + ]; + + } + + } + + function getPrincipalsByPrefix($prefix) { + + $prefix = trim($prefix, '/'); + if ($prefix) $prefix .= '/'; + $return = []; + + foreach ($this->principals as $principal) { + + if ($prefix && strpos($principal['uri'], $prefix) !== 0) continue; + + $return[] = $principal; + + } + + return $return; + + } + + function addPrincipal(array $principal) { + + $this->principals[] = $principal; + + } + + function getPrincipalByPath($path) { + + foreach ($this->getPrincipalsByPrefix('principals') as $principal) { + if ($principal['uri'] === $path) return $principal; + } + + } + + function searchPrincipals($prefixPath, array $searchProperties, $test = 'allof') { + + $matches = []; + foreach ($this->getPrincipalsByPrefix($prefixPath) as $principal) { + + foreach ($searchProperties as $key => $value) { + + if (!isset($principal[$key])) { + continue 2; + } + if (mb_stripos($principal[$key], $value, 0, 'UTF-8') === false) { + continue 2; + } + + // We have a match for this searchProperty! + if ($test === 'allof') { + continue; + } else { + break; + } + + } + $matches[] = $principal['uri']; + + } + return $matches; + + } + + function getGroupMemberSet($path) { + + return isset($this->groupMembers[$path]) ? $this->groupMembers[$path] : []; + + } + + function getGroupMembership($path) { + + $membership = []; + foreach ($this->groupMembers as $group => $members) { + if (in_array($path, $members)) $membership[] = $group; + } + return $membership; + + } + + function setGroupMemberSet($path, array $members) { + + $this->groupMembers[$path] = $members; + + } + + /** + * 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 \Sabre\DAV\PropPatch $propPatch + */ + function updatePrincipal($path, \Sabre\DAV\PropPatch $propPatch) { + + $value = null; + foreach ($this->principals as $principalIndex => $value) { + if ($value['uri'] === $path) { + $principal = $value; + break; + } + } + if (!$principal) return; + + $propPatch->handleRemaining(function($mutations) use ($principal, $principalIndex) { + + foreach ($mutations as $prop => $value) { + + if (is_null($value) && isset($principal[$prop])) { + unset($principal[$prop]); + } else { + $principal[$prop] = $value; + } + + } + + $this->principals[$principalIndex] = $principal; + + return true; + + }); + + } + + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/PDOMySQLTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/PDOMySQLTest.php new file mode 100644 index 0000000..84ba062 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/PDOMySQLTest.php @@ -0,0 +1,45 @@ +<?php + +namespace Sabre\DAVACL\PrincipalBackend; + +use Sabre\DAV; +use Sabre\HTTP; + + +require_once 'Sabre/TestUtil.php'; + +class PDOMySQLTest extends AbstractPDOTest { + + function getPDO() { + + if (!SABRE_HASMYSQL) $this->markTestSkipped('MySQL driver is not available, or not properly configured'); + $pdo = \Sabre\TestUtil::getMySQLDB(); + if (!$pdo) $this->markTestSkipped('Could not connect to MySQL database'); + $pdo->query("DROP TABLE IF EXISTS principals"); + $pdo->query(" +create table principals ( + id integer unsigned not null primary key auto_increment, + uri varchar(50), + email varchar(80), + displayname VARCHAR(80), + vcardurl VARCHAR(80), + unique(uri) +);"); + + $pdo->query("INSERT INTO principals (uri,email,displayname) VALUES ('principals/user','user@example.org','User')"); + $pdo->query("INSERT INTO principals (uri,email,displayname) VALUES ('principals/group','group@example.org','Group')"); + $pdo->query("DROP TABLE IF EXISTS groupmembers"); + $pdo->query("CREATE TABLE groupmembers ( + id INTEGER UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, + principal_id INTEGER UNSIGNED NOT NULL, + member_id INTEGER UNSIGNED NOT NULL, + UNIQUE(principal_id, member_id) + );"); + + $pdo->query("INSERT INTO groupmembers (principal_id,member_id) VALUES (2,1)"); + + return $pdo; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/PDOSqliteTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/PDOSqliteTest.php new file mode 100644 index 0000000..d3a3575 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalBackend/PDOSqliteTest.php @@ -0,0 +1,42 @@ +<?php + +namespace Sabre\DAVACL\PrincipalBackend; + +use Sabre\DAV; +use Sabre\HTTP; + + +require_once 'Sabre/DAV/Auth/Backend/AbstractPDOTest.php'; + +class PDOSQLiteTest extends AbstractPDOTest { + + function tearDown() { + + if (file_exists(SABRE_TEMPDIR . '/pdobackend')) unlink(SABRE_TEMPDIR . '/pdobackend'); + if (file_exists(SABRE_TEMPDIR . '/pdobackend2')) unlink(SABRE_TEMPDIR . '/pdobackend2'); + + } + + function getPDO() { + + if (!SABRE_HASSQLITE) $this->markTestSkipped('SQLite driver is not available'); + $pdo = new \PDO('sqlite:'.SABRE_TEMPDIR.'/pdobackend'); + $pdo->setAttribute(\PDO::ATTR_ERRMODE,\PDO::ERRMODE_EXCEPTION); + $pdo->query('CREATE TABLE principals (id INTEGER PRIMARY KEY ASC, uri TEXT, email VARCHAR(80), displayname VARCHAR(80))'); + $pdo->query('INSERT INTO principals VALUES (1, "principals/user","user@example.org","User")'); + $pdo->query('INSERT INTO principals VALUES (2, "principals/group","group@example.org","Group")'); + + $pdo->query("CREATE TABLE groupmembers ( + id INTEGER PRIMARY KEY ASC, + principal_id INT, + member_id INT, + UNIQUE(principal_id, member_id) + );"); + + $pdo->query("INSERT INTO groupmembers (principal_id,member_id) VALUES (2,1)"); + + return $pdo; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalCollectionTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalCollectionTest.php new file mode 100644 index 0000000..f51d2dc --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalCollectionTest.php @@ -0,0 +1,61 @@ +<?php + +namespace Sabre\DAVACL; + +use Sabre\DAV; +use Sabre\HTTP; + + +class PrincipalCollectionTest extends \PHPUnit_Framework_TestCase { + + public function testBasic() { + + $backend = new PrincipalBackend\Mock(); + $pc = new PrincipalCollection($backend); + $this->assertTrue($pc instanceof PrincipalCollection); + + $this->assertEquals('principals',$pc->getName()); + + } + + /** + * @depends testBasic + */ + public function testGetChildren() { + + $backend = new PrincipalBackend\Mock(); + $pc = new PrincipalCollection($backend); + + $children = $pc->getChildren(); + $this->assertTrue(is_array($children)); + + foreach($children as $child) { + $this->assertTrue($child instanceof IPrincipal); + } + + } + + /** + * @depends testBasic + * @expectedException Sabre\DAV\Exception\MethodNotAllowed + */ + public function testGetChildrenDisable() { + + $backend = new PrincipalBackend\Mock(); + $pc = new PrincipalCollection($backend); + $pc->disableListing = true; + + $children = $pc->getChildren(); + + } + + public function testFindByUri() { + + $backend = new PrincipalBackend\Mock(); + $pc = new PrincipalCollection($backend); + $this->assertEquals('principals/user1', $pc->findByUri('mailto:user1.sabredav@sabredav.org')); + $this->assertNull($pc->findByUri('mailto:fake.user.sabredav@sabredav.org')); + $this->assertNull($pc->findByUri('')); + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalPropertySearchTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalPropertySearchTest.php new file mode 100644 index 0000000..3227a4f --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalPropertySearchTest.php @@ -0,0 +1,396 @@ +<?php + +namespace Sabre\DAVACL; + +use Sabre\DAV; +use Sabre\HTTP; + +require_once 'Sabre/HTTP/ResponseMock.php'; + +class PrincipalPropertySearchTest extends \PHPUnit_Framework_TestCase { + + function getServer() { + + $backend = new PrincipalBackend\Mock(); + + $dir = new DAV\SimpleCollection('root'); + $principals = new PrincipalCollection($backend); + $dir->addChild($principals); + + $fakeServer = new DAV\Server($dir); + $fakeServer->sapi = new HTTP\SapiMock(); + $fakeServer->httpResponse = new HTTP\ResponseMock(); + $fakeServer->debugExceptions = true; + $plugin = new MockPlugin($backend,'realm'); + $plugin->allowAccessToNodesWithoutACL = true; + + $this->assertTrue($plugin instanceof Plugin); + $fakeServer->addPlugin($plugin); + $this->assertEquals($plugin, $fakeServer->getPlugin('acl')); + + return $fakeServer; + + } + + function testDepth1() { + + $xml = '<?xml version="1.0"?> +<d:principal-property-search xmlns:d="DAV:"> + <d:property-search> + <d:prop> + <d:displayname /> + </d:prop> + <d:match>user</d:match> + </d:property-search> + <d:prop> + <d:displayname /> + <d:getcontentlength /> + </d:prop> +</d:principal-property-search>'; + + $serverVars = array( + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_DEPTH' => '1', + 'REQUEST_URI' => '/principals', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody($xml); + + $server = $this->getServer(); + $server->httpRequest = $request; + + $server->exec(); + + $this->assertEquals(400, $server->httpResponse->getStatus(), $server->httpResponse->getBodyAsString()); + $this->assertEquals(array( + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ), $server->httpResponse->getHeaders()); + + } + + + function testUnknownSearchField() { + + $xml = '<?xml version="1.0"?> +<d:principal-property-search xmlns:d="DAV:"> + <d:property-search> + <d:prop> + <d:yourmom /> + </d:prop> + <d:match>user</d:match> + </d:property-search> + <d:prop> + <d:displayname /> + <d:getcontentlength /> + </d:prop> +</d:principal-property-search>'; + + $serverVars = array( + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_DEPTH' => '0', + 'REQUEST_URI' => '/principals', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody($xml); + + $server = $this->getServer(); + $server->httpRequest = $request; + + $server->exec(); + + $this->assertEquals(207, $server->httpResponse->getStatus(), "Full body: " . $server->httpResponse->getBodyAsString()); + $this->assertEquals(array( + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + 'Vary' => ['Brief,Prefer'], + ), $server->httpResponse->getHeaders()); + + } + + function testCorrect() { + + $xml = '<?xml version="1.0"?> +<d:principal-property-search xmlns:d="DAV:"> + <d:apply-to-principal-collection-set /> + <d:property-search> + <d:prop> + <d:displayname /> + </d:prop> + <d:match>user</d:match> + </d:property-search> + <d:prop> + <d:displayname /> + <d:getcontentlength /> + </d:prop> +</d:principal-property-search>'; + + $serverVars = array( + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_DEPTH' => '0', + 'REQUEST_URI' => '/', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody($xml); + + $server = $this->getServer(); + $server->httpRequest = $request; + + $server->exec(); + + $this->assertEquals(207, $server->httpResponse->status, $server->httpResponse->body); + $this->assertEquals(array( + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + 'Vary' => ['Brief,Prefer'], + ), $server->httpResponse->getHeaders()); + + + $check = array( + '/d:multistatus', + '/d:multistatus/d:response' => 2, + '/d:multistatus/d:response/d:href' => 2, + '/d:multistatus/d:response/d:propstat' => 4, + '/d:multistatus/d:response/d:propstat/d:prop' => 4, + '/d:multistatus/d:response/d:propstat/d:prop/d:displayname' => 2, + '/d:multistatus/d:response/d:propstat/d:prop/d:getcontentlength' => 2, + '/d:multistatus/d:response/d:propstat/d:status' => 4, + ); + + $xml = simplexml_load_string($server->httpResponse->body); + $xml->registerXPathNamespace('d','DAV:'); + foreach($check as $v1=>$v2) { + + $xpath = is_int($v1)?$v2:$v1; + + $result = $xml->xpath($xpath); + + $count = 1; + if (!is_int($v1)) $count = $v2; + + $this->assertEquals($count,count($result), 'we expected ' . $count . ' appearances of ' . $xpath . ' . We found ' . count($result) . '. Full response body: ' . $server->httpResponse->body); + + } + + } + + function testAND() { + + $xml = '<?xml version="1.0"?> +<d:principal-property-search xmlns:d="DAV:"> + <d:apply-to-principal-collection-set /> + <d:property-search> + <d:prop> + <d:displayname /> + </d:prop> + <d:match>user</d:match> + </d:property-search> + <d:property-search> + <d:prop> + <d:foo /> + </d:prop> + <d:match>bar</d:match> + </d:property-search> + <d:prop> + <d:displayname /> + <d:getcontentlength /> + </d:prop> +</d:principal-property-search>'; + + $serverVars = array( + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_DEPTH' => '0', + 'REQUEST_URI' => '/', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody($xml); + + $server = $this->getServer(); + $server->httpRequest = $request; + + $server->exec(); + + $this->assertEquals(207, $server->httpResponse->status, $server->httpResponse->body); + $this->assertEquals(array( + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + 'Vary' => ['Brief,Prefer'], + ), $server->httpResponse->getHeaders()); + + + $check = array( + '/d:multistatus', + '/d:multistatus/d:response' => 0, + '/d:multistatus/d:response/d:href' => 0, + '/d:multistatus/d:response/d:propstat' => 0, + '/d:multistatus/d:response/d:propstat/d:prop' => 0, + '/d:multistatus/d:response/d:propstat/d:prop/d:displayname' => 0, + '/d:multistatus/d:response/d:propstat/d:prop/d:getcontentlength' => 0, + '/d:multistatus/d:response/d:propstat/d:status' => 0, + ); + + $xml = simplexml_load_string($server->httpResponse->body); + $xml->registerXPathNamespace('d','DAV:'); + foreach($check as $v1=>$v2) { + + $xpath = is_int($v1)?$v2:$v1; + + $result = $xml->xpath($xpath); + + $count = 1; + if (!is_int($v1)) $count = $v2; + + $this->assertEquals($count,count($result), 'we expected ' . $count . ' appearances of ' . $xpath . ' . We found ' . count($result) . '. Full response body: ' . $server->httpResponse->body); + + } + + } + function testOR() { + + $xml = '<?xml version="1.0"?> +<d:principal-property-search xmlns:d="DAV:" test="anyof"> + <d:apply-to-principal-collection-set /> + <d:property-search> + <d:prop> + <d:displayname /> + </d:prop> + <d:match>user</d:match> + </d:property-search> + <d:property-search> + <d:prop> + <d:foo /> + </d:prop> + <d:match>bar</d:match> + </d:property-search> + <d:prop> + <d:displayname /> + <d:getcontentlength /> + </d:prop> +</d:principal-property-search>'; + + $serverVars = array( + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_DEPTH' => '0', + 'REQUEST_URI' => '/', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody($xml); + + $server = $this->getServer(); + $server->httpRequest = $request; + + $server->exec(); + + $this->assertEquals(207, $server->httpResponse->status, $server->httpResponse->body); + $this->assertEquals(array( + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + 'Vary' => ['Brief,Prefer'], + ), $server->httpResponse->getHeaders()); + + + $check = array( + '/d:multistatus', + '/d:multistatus/d:response' => 2, + '/d:multistatus/d:response/d:href' => 2, + '/d:multistatus/d:response/d:propstat' => 4, + '/d:multistatus/d:response/d:propstat/d:prop' => 4, + '/d:multistatus/d:response/d:propstat/d:prop/d:displayname' => 2, + '/d:multistatus/d:response/d:propstat/d:prop/d:getcontentlength' => 2, + '/d:multistatus/d:response/d:propstat/d:status' => 4, + ); + + $xml = simplexml_load_string($server->httpResponse->body); + $xml->registerXPathNamespace('d','DAV:'); + foreach($check as $v1=>$v2) { + + $xpath = is_int($v1)?$v2:$v1; + + $result = $xml->xpath($xpath); + + $count = 1; + if (!is_int($v1)) $count = $v2; + + $this->assertEquals($count,count($result), 'we expected ' . $count . ' appearances of ' . $xpath . ' . We found ' . count($result) . '. Full response body: ' . $server->httpResponse->body); + + } + + } + function testWrongUri() { + + $xml = '<?xml version="1.0"?> +<d:principal-property-search xmlns:d="DAV:"> + <d:property-search> + <d:prop> + <d:displayname /> + </d:prop> + <d:match>user</d:match> + </d:property-search> + <d:prop> + <d:displayname /> + <d:getcontentlength /> + </d:prop> +</d:principal-property-search>'; + + $serverVars = array( + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_DEPTH' => '0', + 'REQUEST_URI' => '/', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody($xml); + + $server = $this->getServer(); + $server->httpRequest = $request; + + $server->exec(); + + $this->assertEquals(207, $server->httpResponse->status, $server->httpResponse->body); + $this->assertEquals(array( + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + 'Vary' => ['Brief,Prefer'], + ), $server->httpResponse->getHeaders()); + + + $check = array( + '/d:multistatus', + '/d:multistatus/d:response' => 0, + ); + + $xml = simplexml_load_string($server->httpResponse->body); + $xml->registerXPathNamespace('d','DAV:'); + foreach($check as $v1=>$v2) { + + $xpath = is_int($v1)?$v2:$v1; + + $result = $xml->xpath($xpath); + + $count = 1; + if (!is_int($v1)) $count = $v2; + + $this->assertEquals($count,count($result), 'we expected ' . $count . ' appearances of ' . $xpath . ' . We found ' . count($result) . '. Full response body: ' . $server->httpResponse->body); + + } + + } +} + +class MockPlugin extends Plugin { + + function getCurrentUserPrivilegeSet($node) { + + return array( + '{DAV:}read', + '{DAV:}write', + ); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalSearchPropertySetTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalSearchPropertySetTest.php new file mode 100644 index 0000000..12962fc --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalSearchPropertySetTest.php @@ -0,0 +1,139 @@ +<?php + +namespace Sabre\DAVACL; + +use Sabre\DAV; +use Sabre\HTTP; + +require_once 'Sabre/HTTP/ResponseMock.php'; + +class PrincipalSearchPropertySetTest extends \PHPUnit_Framework_TestCase { + + function getServer() { + + $backend = new PrincipalBackend\Mock(); + + $dir = new DAV\SimpleCollection('root'); + $principals = new PrincipalCollection($backend); + $dir->addChild($principals); + + $fakeServer = new DAV\Server($dir); + $fakeServer->sapi = new HTTP\SapiMock(); + $fakeServer->httpResponse = new HTTP\ResponseMock(); + $plugin = new Plugin($backend,'realm'); + $this->assertTrue($plugin instanceof Plugin); + $fakeServer->addPlugin($plugin); + $this->assertEquals($plugin, $fakeServer->getPlugin('acl')); + + return $fakeServer; + + } + + function testDepth1() { + + $xml = '<?xml version="1.0"?> +<d:principal-search-property-set xmlns:d="DAV:" />'; + + $serverVars = array( + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_DEPTH' => '1', + 'REQUEST_URI' => '/principals', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody($xml); + + $server = $this->getServer(); + $server->httpRequest = $request; + + $server->exec(); + + $this->assertEquals(400, $server->httpResponse->status); + $this->assertEquals(array( + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ), $server->httpResponse->getHeaders()); + + } + + function testDepthIncorrectXML() { + + $xml = '<?xml version="1.0"?> +<d:principal-search-property-set xmlns:d="DAV:"><d:ohell /></d:principal-search-property-set>'; + + $serverVars = array( + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_DEPTH' => '0', + 'REQUEST_URI' => '/principals', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody($xml); + + $server = $this->getServer(); + $server->httpRequest = $request; + + $server->exec(); + + $this->assertEquals(400, $server->httpResponse->status, $server->httpResponse->body); + $this->assertEquals(array( + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ), $server->httpResponse->getHeaders()); + + } + + function testCorrect() { + + $xml = '<?xml version="1.0"?> +<d:principal-search-property-set xmlns:d="DAV:"/>'; + + $serverVars = array( + 'REQUEST_METHOD' => 'REPORT', + 'HTTP_DEPTH' => '0', + 'REQUEST_URI' => '/principals', + ); + + $request = HTTP\Sapi::createFromServerArray($serverVars); + $request->setBody($xml); + + $server = $this->getServer(); + $server->httpRequest = $request; + + $server->exec(); + + $this->assertEquals(200, $server->httpResponse->status, $server->httpResponse->body); + $this->assertEquals(array( + 'X-Sabre-Version' => [DAV\Version::VERSION], + 'Content-Type' => ['application/xml; charset=utf-8'], + ), $server->httpResponse->getHeaders()); + + + $check = array( + '/d:principal-search-property-set', + '/d:principal-search-property-set/d:principal-search-property' => 2, + '/d:principal-search-property-set/d:principal-search-property/d:prop' => 2, + '/d:principal-search-property-set/d:principal-search-property/d:prop/d:displayname' => 1, + '/d:principal-search-property-set/d:principal-search-property/d:prop/s:email-address' => 1, + '/d:principal-search-property-set/d:principal-search-property/d:description' => 2, + ); + + $xml = simplexml_load_string($server->httpResponse->body); + $xml->registerXPathNamespace('d','DAV:'); + $xml->registerXPathNamespace('s','http://sabredav.org/ns'); + foreach($check as $v1=>$v2) { + + $xpath = is_int($v1)?$v2:$v1; + + $result = $xml->xpath($xpath); + + $count = 1; + if (!is_int($v1)) $count = $v2; + + $this->assertEquals($count,count($result), 'we expected ' . $count . ' appearances of ' . $xpath . ' . We found ' . count($result) . '. Full response body: ' . $server->httpResponse->body); + + } + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalTest.php new file mode 100644 index 0000000..03fd9d6 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/PrincipalTest.php @@ -0,0 +1,208 @@ +<?php + +namespace Sabre\DAVACL; + +use Sabre\DAV; +use Sabre\HTTP; + +class PrincipalTest extends \PHPUnit_Framework_TestCase { + + public function testConstruct() { + + $principalBackend = new PrincipalBackend\Mock(); + $principal = new Principal($principalBackend, array('uri' => 'principals/admin')); + $this->assertTrue($principal instanceof Principal); + + } + + /** + * @expectedException Sabre\DAV\Exception + */ + public function testConstructNoUri() { + + $principalBackend = new PrincipalBackend\Mock(); + $principal = new Principal($principalBackend, array()); + + } + + public function testGetName() { + + $principalBackend = new PrincipalBackend\Mock(); + $principal = new Principal($principalBackend, array('uri' => 'principals/admin')); + $this->assertEquals('admin',$principal->getName()); + + } + + public function testGetDisplayName() { + + $principalBackend = new PrincipalBackend\Mock(); + $principal = new Principal($principalBackend, array('uri' => 'principals/admin')); + $this->assertEquals('admin',$principal->getDisplayname()); + + $principal = new Principal($principalBackend, array( + 'uri' => 'principals/admin', + '{DAV:}displayname' => 'Mr. Admin' + )); + $this->assertEquals('Mr. Admin',$principal->getDisplayname()); + + } + + public function testGetProperties() { + + $principalBackend = new PrincipalBackend\Mock(); + $principal = new Principal($principalBackend, array( + 'uri' => 'principals/admin', + '{DAV:}displayname' => 'Mr. Admin', + '{http://www.example.org/custom}custom' => 'Custom', + '{http://sabredav.org/ns}email-address' => 'admin@example.org', + )); + + $keys = array( + '{DAV:}displayname', + '{http://www.example.org/custom}custom', + '{http://sabredav.org/ns}email-address', + ); + $props = $principal->getProperties($keys); + + foreach($keys as $key) $this->assertArrayHasKey($key,$props); + + $this->assertEquals('Mr. Admin',$props['{DAV:}displayname']); + + $this->assertEquals('admin@example.org', $props['{http://sabredav.org/ns}email-address']); + } + + public function testUpdateProperties() { + + $principalBackend = new PrincipalBackend\Mock(); + $principal = new Principal($principalBackend, array('uri' => 'principals/admin')); + + $propPatch = new DAV\PropPatch(array('{DAV:}yourmom' => 'test')); + + $result = $principal->propPatch($propPatch); + $result = $propPatch->commit(); + $this->assertTrue($result); + + } + + public function testGetPrincipalUrl() { + + $principalBackend = new PrincipalBackend\Mock(); + $principal = new Principal($principalBackend, array('uri' => 'principals/admin')); + $this->assertEquals('principals/admin',$principal->getPrincipalUrl()); + + } + + public function testGetAlternateUriSet() { + + $principalBackend = new PrincipalBackend\Mock(); + $principal = new Principal($principalBackend, array( + 'uri' => 'principals/admin', + '{DAV:}displayname' => 'Mr. Admin', + '{http://www.example.org/custom}custom' => 'Custom', + '{http://sabredav.org/ns}email-address' => 'admin@example.org', + '{DAV:}alternate-URI-set' => array( + 'mailto:admin+1@example.org', + 'mailto:admin+2@example.org', + 'mailto:admin@example.org', + ), + )); + + $expected = array( + 'mailto:admin+1@example.org', + 'mailto:admin+2@example.org', + 'mailto:admin@example.org', + ); + + $this->assertEquals($expected,$principal->getAlternateUriSet()); + + } + public function testGetAlternateUriSetEmpty() { + + $principalBackend = new PrincipalBackend\Mock(); + $principal = new Principal($principalBackend, array( + 'uri' => 'principals/admin', + )); + + $expected = array(); + + $this->assertEquals($expected,$principal->getAlternateUriSet()); + + } + + public function testGetGroupMemberSet() { + + $principalBackend = new PrincipalBackend\Mock(); + $principal = new Principal($principalBackend, array('uri' => 'principals/admin')); + $this->assertEquals(array(),$principal->getGroupMemberSet()); + + } + public function testGetGroupMembership() { + + $principalBackend = new PrincipalBackend\Mock(); + $principal = new Principal($principalBackend, array('uri' => 'principals/admin')); + $this->assertEquals(array(),$principal->getGroupMembership()); + + } + + public function testSetGroupMemberSet() { + + $principalBackend = new PrincipalBackend\Mock(); + $principal = new Principal($principalBackend, array('uri' => 'principals/admin')); + $principal->setGroupMemberSet(array('principals/foo')); + + $this->assertEquals(array( + 'principals/admin' => array('principals/foo'), + ), $principalBackend->groupMembers); + + } + + public function testGetOwner() { + + $principalBackend = new PrincipalBackend\Mock(); + $principal = new Principal($principalBackend, array('uri' => 'principals/admin')); + $this->assertEquals('principals/admin',$principal->getOwner()); + + } + + public function testGetGroup() { + + $principalBackend = new PrincipalBackend\Mock(); + $principal = new Principal($principalBackend, array('uri' => 'principals/admin')); + $this->assertNull($principal->getGroup()); + + } + + public function testGetACl() { + + $principalBackend = new PrincipalBackend\Mock(); + $principal = new Principal($principalBackend, array('uri' => 'principals/admin')); + $this->assertEquals(array( + array( + 'privilege' => '{DAV:}read', + 'principal' => '{DAV:}authenticated', + 'protected' => true, + ) + ),$principal->getACL()); + + } + + /** + * @expectedException Sabre\DAV\Exception\MethodNotAllowed + */ + public function testSetACl() { + + $principalBackend = new PrincipalBackend\Mock(); + $principal = new Principal($principalBackend, array('uri' => 'principals/admin')); + $principal->setACL(array()); + + } + + public function testGetSupportedPrivilegeSet() { + + $principalBackend = new PrincipalBackend\Mock(); + $principal = new Principal($principalBackend, array('uri' => 'principals/admin')); + $this->assertNull($principal->getSupportedPrivilegeSet()); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/SimplePluginTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/SimplePluginTest.php new file mode 100644 index 0000000..67c9240 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/SimplePluginTest.php @@ -0,0 +1,327 @@ +<?php + +namespace Sabre\DAVACL; + +use Sabre\DAV; +use Sabre\HTTP; + + +require_once 'Sabre/DAVACL/MockPrincipal.php'; +require_once 'Sabre/DAVACL/MockACLNode.php'; + +class SimplePluginTest extends \PHPUnit_Framework_TestCase { + + function testValues() { + + $aclPlugin = new Plugin(); + $this->assertEquals('acl',$aclPlugin->getPluginName()); + $this->assertEquals( + array('access-control', 'calendarserver-principal-property-search'), + $aclPlugin->getFeatures() + ); + + $this->assertEquals( + array( + '{DAV:}expand-property', + '{DAV:}principal-property-search', + '{DAV:}principal-search-property-set' + ), + $aclPlugin->getSupportedReportSet('')); + + $this->assertEquals(array('ACL'), $aclPlugin->getMethods('')); + + + $this->assertEquals( + 'acl', + $aclPlugin->getPluginInfo()['name'] + ); + } + + function testGetFlatPrivilegeSet() { + + $expected = array( + '{DAV:}all' => array( + 'privilege' => '{DAV:}all', + 'abstract' => true, + 'aggregates' => array( + '{DAV:}read', + '{DAV:}write', + ), + 'concrete' => null, + ), + '{DAV:}read' => array( + 'privilege' => '{DAV:}read', + 'abstract' => false, + 'aggregates' => array( + '{DAV:}read-acl', + '{DAV:}read-current-user-privilege-set', + ), + 'concrete' => '{DAV:}read', + ), + '{DAV:}read-acl' => array( + 'privilege' => '{DAV:}read-acl', + 'abstract' => false, + 'aggregates' => array(), + 'concrete' => '{DAV:}read-acl', + ), + '{DAV:}read-current-user-privilege-set' => array( + 'privilege' => '{DAV:}read-current-user-privilege-set', + 'abstract' => false, + 'aggregates' => array(), + 'concrete' => '{DAV:}read-current-user-privilege-set', + ), + '{DAV:}write' => array( + 'privilege' => '{DAV:}write', + 'abstract' => false, + 'aggregates' => array( + '{DAV:}write-acl', + '{DAV:}write-properties', + '{DAV:}write-content', + '{DAV:}bind', + '{DAV:}unbind', + '{DAV:}unlock', + ), + 'concrete' => '{DAV:}write', + ), + '{DAV:}write-acl' => array( + 'privilege' => '{DAV:}write-acl', + 'abstract' => false, + 'aggregates' => array(), + 'concrete' => '{DAV:}write-acl', + ), + '{DAV:}write-properties' => array( + 'privilege' => '{DAV:}write-properties', + 'abstract' => false, + 'aggregates' => array(), + 'concrete' => '{DAV:}write-properties', + ), + '{DAV:}write-content' => array( + 'privilege' => '{DAV:}write-content', + 'abstract' => false, + 'aggregates' => array(), + 'concrete' => '{DAV:}write-content', + ), + '{DAV:}unlock' => array( + 'privilege' => '{DAV:}unlock', + 'abstract' => false, + 'aggregates' => array(), + 'concrete' => '{DAV:}unlock', + ), + '{DAV:}bind' => array( + 'privilege' => '{DAV:}bind', + 'abstract' => false, + 'aggregates' => array(), + 'concrete' => '{DAV:}bind', + ), + '{DAV:}unbind' => array( + 'privilege' => '{DAV:}unbind', + 'abstract' => false, + 'aggregates' => array(), + 'concrete' => '{DAV:}unbind', + ), + + ); + + $plugin = new Plugin(); + $server = new DAV\Server(); + $server->addPlugin($plugin); + $this->assertEquals($expected, $plugin->getFlatPrivilegeSet('')); + + } + + function testCurrentUserPrincipalsNotLoggedIn() { + + $acl = new Plugin(); + $server = new DAV\Server(); + $server->addPlugin($acl); + + $this->assertEquals(array(),$acl->getCurrentUserPrincipals()); + + } + + function testCurrentUserPrincipalsSimple() { + + $tree = array( + + new DAV\SimpleCollection('principals', array( + new MockPrincipal('admin','principals/admin'), + )) + + ); + + $acl = new Plugin(); + $server = new DAV\Server($tree); + $server->addPlugin($acl); + + $auth = new DAV\Auth\Plugin(new DAV\Auth\Backend\Mock(),'SabreDAV'); + $server->addPlugin($auth); + + //forcing login + $auth->beforeMethod(new HTTP\Request(), new HTTP\Response()); + + $this->assertEquals(array('principals/admin'),$acl->getCurrentUserPrincipals()); + + } + + function testCurrentUserPrincipalsGroups() { + + $tree = array( + + new DAV\SimpleCollection('principals', array( + new MockPrincipal('admin','principals/admin',array('principals/administrators', 'principals/everyone')), + new MockPrincipal('administrators','principals/administrators',array('principals/groups'), array('principals/admin')), + new MockPrincipal('everyone','principals/everyone',array(), array('principals/admin')), + new MockPrincipal('groups','principals/groups',array(), array('principals/administrators')), + )) + + ); + + $acl = new Plugin(); + $server = new DAV\Server($tree); + $server->addPlugin($acl); + + $auth = new DAV\Auth\Plugin(new DAV\Auth\Backend\Mock(),'SabreDAV'); + $server->addPlugin($auth); + + //forcing login + $auth->beforeMethod(new HTTP\Request(), new HTTP\Response()); + + $expected = array( + 'principals/admin', + 'principals/administrators', + 'principals/everyone', + 'principals/groups', + ); + + $this->assertEquals($expected,$acl->getCurrentUserPrincipals()); + + // The second one should trigger the cache and be identical + $this->assertEquals($expected,$acl->getCurrentUserPrincipals()); + + } + + function testGetACL() { + + $acl = array( + array( + 'principal' => 'principals/admin', + 'privilege' => '{DAV:}read', + ), + array( + 'principal' => 'principals/admin', + 'privilege' => '{DAV:}write', + ), + ); + + + $tree = array( + new MockACLNode('foo',$acl), + ); + + $server = new DAV\Server($tree); + $aclPlugin = new Plugin(); + $server->addPlugin($aclPlugin); + + $this->assertEquals($acl,$aclPlugin->getACL('foo')); + + } + + function testGetCurrentUserPrivilegeSet() { + + $acl = array( + array( + 'principal' => 'principals/admin', + 'privilege' => '{DAV:}read', + ), + array( + 'principal' => 'principals/user1', + 'privilege' => '{DAV:}read', + ), + array( + 'principal' => 'principals/admin', + 'privilege' => '{DAV:}write', + ), + ); + + + $tree = array( + new MockACLNode('foo',$acl), + + new DAV\SimpleCollection('principals', array( + new MockPrincipal('admin','principals/admin'), + )), + + ); + + $server = new DAV\Server($tree); + $aclPlugin = new Plugin(); + $server->addPlugin($aclPlugin); + + $auth = new DAV\Auth\Plugin(new DAV\Auth\Backend\Mock(),'SabreDAV'); + $server->addPlugin($auth); + + //forcing login + $auth->beforeMethod(new HTTP\Request(), new HTTP\Response()); + + $expected = array( + '{DAV:}write', + '{DAV:}write-acl', + '{DAV:}write-properties', + '{DAV:}write-content', + '{DAV:}bind', + '{DAV:}unbind', + '{DAV:}unlock', + '{DAV:}read', + '{DAV:}read-acl', + '{DAV:}read-current-user-privilege-set', + ); + + $this->assertEquals($expected,$aclPlugin->getCurrentUserPrivilegeSet('foo')); + + } + + function testCheckPrivileges() { + + $acl = array( + array( + 'principal' => 'principals/admin', + 'privilege' => '{DAV:}read', + ), + array( + 'principal' => 'principals/user1', + 'privilege' => '{DAV:}read', + ), + array( + 'principal' => 'principals/admin', + 'privilege' => '{DAV:}write', + ), + ); + + + $tree = array( + new MockACLNode('foo',$acl), + + new DAV\SimpleCollection('principals', array( + new MockPrincipal('admin','principals/admin'), + )), + + ); + + $server = new DAV\Server($tree); + $aclPlugin = new Plugin(); + $server->addPlugin($aclPlugin); + + $auth = new DAV\Auth\Plugin(new DAV\Auth\Backend\Mock(),'SabreDAV'); + $server->addPlugin($auth); + + //forcing login + //$auth->beforeMethod('GET','/'); + + $this->assertFalse($aclPlugin->checkPrivileges('foo', array('{DAV:}read'), Plugin::R_PARENT, false)); + + } +} + + + + diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/ACLTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/ACLTest.php new file mode 100644 index 0000000..0d15147 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/ACLTest.php @@ -0,0 +1,342 @@ +<?php + +namespace Sabre\DAVACL\Xml\Property; + +use Sabre\DAV; +use Sabre\HTTP; +use Sabre\DAV\Browser\HtmlOutputHelper; + +class ACLTest extends \PHPUnit_Framework_TestCase { + + function testConstruct() { + + $acl = new Acl([]); + $this->assertInstanceOf('Sabre\DAVACL\Xml\Property\ACL', $acl); + + } + + function testSerializeEmpty() { + + $acl = new Acl([]); + $xml = (new DAV\Server())->xml->write('{DAV:}root', $acl); + + $expected = '<?xml version="1.0"?> +<d:root xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" />'; + + $this->assertXmlStringEqualsXmlString($expected, $xml); + + } + + function testSerialize() { + + $privileges = [ + [ + 'principal' => 'principals/evert', + 'privilege' => '{DAV:}write', + ], + [ + 'principal' => 'principals/foo', + 'privilege' => '{DAV:}read', + 'protected' => true, + ], + ]; + + $acl = new Acl($privileges); + $xml = (new DAV\Server())->xml->write('{DAV:}root', $acl, '/'); + + $expected = '<?xml version="1.0"?> +<d:root xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns"> + <d:ace> + <d:principal> + <d:href>/principals/evert/</d:href> + </d:principal> + <d:grant> + <d:privilege> + <d:write/> + </d:privilege> + </d:grant> + </d:ace> + <d:ace> + <d:principal> + <d:href>/principals/foo/</d:href> + </d:principal> + <d:grant> + <d:privilege> + <d:read/> + </d:privilege> + </d:grant> + <d:protected/> + </d:ace> +</d:root> +'; + $this->assertXmlStringEqualsXmlString($expected, $xml); + + } + + function testSerializeSpecialPrincipals() { + + $privileges = [ + [ + 'principal' => '{DAV:}authenticated', + 'privilege' => '{DAV:}write', + ], + [ + 'principal' => '{DAV:}unauthenticated', + 'privilege' => '{DAV:}write', + ], + [ + 'principal' => '{DAV:}all', + 'privilege' => '{DAV:}write', + ], + + ]; + + $acl = new Acl($privileges); + $xml = (new DAV\Server())->xml->write('{DAV:}root', $acl, '/'); + + $expected = '<?xml version="1.0"?> +<d:root xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns"> + <d:ace> + <d:principal> + <d:authenticated/> + </d:principal> + <d:grant> + <d:privilege> + <d:write/> + </d:privilege> + </d:grant> + </d:ace> + <d:ace> + <d:principal> + <d:unauthenticated/> + </d:principal> + <d:grant> + <d:privilege> + <d:write/> + </d:privilege> + </d:grant> + </d:ace> + <d:ace> + <d:principal> + <d:all/> + </d:principal> + <d:grant> + <d:privilege> + <d:write/> + </d:privilege> + </d:grant> + </d:ace> +</d:root> +'; + $this->assertXmlStringEqualsXmlString($expected, $xml); + + } + + function testUnserialize() { + + $source = '<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:ace> + <d:principal> + <d:href>/principals/evert/</d:href> + </d:principal> + <d:grant> + <d:privilege> + <d:write/> + </d:privilege> + </d:grant> + </d:ace> + <d:ace> + <d:principal> + <d:href>/principals/foo/</d:href> + </d:principal> + <d:grant> + <d:privilege> + <d:read/> + </d:privilege> + </d:grant> + <d:protected/> + </d:ace> +</d:root> +'; + + $reader = new \Sabre\Xml\Reader(); + $reader->elementMap['{DAV:}root'] = 'Sabre\DAVACL\Xml\Property\Acl'; + $reader->xml($source); + + $result = $reader->parse(); + $result = $result['value']; + + $this->assertInstanceOf('Sabre\\DAVACL\\Xml\\Property\\Acl', $result); + + $expected = [ + [ + 'principal' => '/principals/evert/', + 'protected' => false, + 'privilege' => '{DAV:}write', + ], + [ + 'principal' => '/principals/foo/', + 'protected' => true, + 'privilege' => '{DAV:}read', + ], + ]; + + $this->assertEquals($expected, $result->getPrivileges()); + + + } + + /** + * @expectedException Sabre\DAV\Exception\BadRequest + */ + function testUnserializeNoPrincipal() { + + $source = '<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:ace> + <d:grant> + <d:privilege> + <d:write/> + </d:privilege> + </d:grant> + </d:ace> +</d:root> +'; + + + $reader = new \Sabre\Xml\Reader(); + $reader->elementMap['{DAV:}root'] = 'Sabre\DAVACL\Xml\Property\Acl'; + $reader->xml($source); + + $result = $reader->parse(); + + } + + function testUnserializeOtherPrincipal() { + + $source = '<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:ace> + <d:grant> + <d:privilege> + <d:write/> + </d:privilege> + </d:grant> + <d:principal><d:authenticated /></d:principal> + </d:ace> + <d:ace> + <d:grant> + <d:ignoreme /> + <d:privilege> + <d:write/> + </d:privilege> + </d:grant> + <d:principal><d:unauthenticated /></d:principal> + </d:ace> + <d:ace> + <d:grant> + <d:privilege> + <d:write/> + </d:privilege> + </d:grant> + <d:principal><d:all /></d:principal> + </d:ace> +</d:root> +'; + + $reader = new \Sabre\Xml\Reader(); + $reader->elementMap['{DAV:}root'] = 'Sabre\DAVACL\Xml\Property\Acl'; + $reader->xml($source); + + $result = $reader->parse(); + $result = $result['value']; + + $this->assertInstanceOf('Sabre\\DAVACL\\Xml\\Property\\Acl', $result); + + $expected = [ + [ + 'principal' => '{DAV:}authenticated', + 'protected' => false, + 'privilege' => '{DAV:}write', + ], + [ + 'principal' => '{DAV:}unauthenticated', + 'protected' => false, + 'privilege' => '{DAV:}write', + ], + [ + 'principal' => '{DAV:}all', + 'protected' => false, + 'privilege' => '{DAV:}write', + ], + ]; + + $this->assertEquals($expected, $result->getPrivileges()); + + } + + /** + * @expectedException Sabre\DAV\Exception\NotImplemented + */ + function testUnserializeDeny() { + + $source = '<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:ignore-me /> + <d:ace> + <d:deny> + <d:privilege> + <d:write/> + </d:privilege> + </d:deny> + <d:principal><d:href>/principals/evert</d:href></d:principal> + </d:ace> +</d:root> +'; + + $reader = new \Sabre\Xml\Reader(); + $reader->elementMap['{DAV:}root'] = 'Sabre\DAVACL\Xml\Property\Acl'; + $reader->xml($source); + + $result = $reader->parse(); + + } + + function testToHtml() { + + $privileges = [ + [ + 'principal' => 'principals/evert', + 'privilege' => '{DAV:}write', + ], + [ + 'principal' => 'principals/foo', + 'privilege' => '{http://example.org/ns}read', + 'protected' => true, + ], + [ + 'principal' => '{DAV:}authenticated', + 'privilege' => '{DAV:}write', + ], + ]; + + $acl = new Acl($privileges); + $html = new HtmlOutputHelper( + '/base/', + ['DAV:' => 'd'] + ); + + $expected = + '<table>' . + '<tr><th>Principal</th><th>Privilege</th><th></th></tr>' . + '<tr><td><a href="/base/principals/evert">/base/principals/evert</a></td><td><span title="{DAV:}write">d:write</span></td><td></td></tr>' . + '<tr><td><a href="/base/principals/foo">/base/principals/foo</a></td><td><span title="{http://example.org/ns}read">{http://example.org/ns}read</span></td><td>(protected)</td></tr>' . + '<tr><td><span title="{DAV:}authenticated">d:authenticated</span></td><td><span title="{DAV:}write">d:write</span></td><td></td></tr>' . + '</table>'; + + $this->assertEquals($expected, $acl->toHtml($html)); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/AclRestrictionsTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/AclRestrictionsTest.php new file mode 100644 index 0000000..6d8b83a --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/AclRestrictionsTest.php @@ -0,0 +1,30 @@ +<?php + +namespace Sabre\DAVACL\Xml\Property; + +use Sabre\DAV; +use Sabre\HTTP; + +class AclRestrictionsTest extends \PHPUnit_Framework_TestCase { + + function testConstruct() { + + $prop = new AclRestrictions(); + $this->assertInstanceOf('Sabre\DAVACL\Xml\Property\AclRestrictions', $prop); + + } + + function testSerialize() { + + $prop = new AclRestrictions(); + $xml = (new DAV\Server())->xml->write('{DAV:}root', $prop); + + $expected = '<?xml version="1.0"?> +<d:root xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns"><d:grant-only/><d:no-invert/></d:root>'; + + $this->assertXmlStringEqualsXmlString($expected, $xml); + + } + + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/CurrentUserPrivilegeSetTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/CurrentUserPrivilegeSetTest.php new file mode 100644 index 0000000..d6e6b2d --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/CurrentUserPrivilegeSetTest.php @@ -0,0 +1,86 @@ +<?php + +namespace Sabre\DAVACL\Xml\Property; + +use Sabre\DAV; +use Sabre\DAV\Browser\HtmlOutputHelper; +use Sabre\HTTP; +use Sabre\Xml\Reader; + +class CurrentUserPrivilegeSetTest extends \PHPUnit_Framework_TestCase { + + function testSerialize() { + + $privileges = [ + '{DAV:}read', + '{DAV:}write', + ]; + $prop = new CurrentUserPrivilegeSet($privileges); + $xml = (new DAV\Server())->xml->write('{DAV:}root', $prop); + + $expected = <<<XML +<d:root xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns"> + <d:privilege> + <d:read /> + </d:privilege> + <d:privilege> + <d:write /> + </d:privilege> +</d:root> +XML; + + + $this->assertXmlStringEqualsXmlString($expected, $xml); + + } + + function testUnserialize() { + + $source = '<?xml version="1.0"?> +<d:root xmlns:d="DAV:"> + <d:privilege> + <d:write-properties /> + </d:privilege> + <d:ignoreme /> + <d:privilege> + <d:read /> + </d:privilege> +</d:root> +'; + + $result = $this->parse($source); + $this->assertTrue($result->has('{DAV:}read')); + $this->assertTrue($result->has('{DAV:}write-properties')); + $this->assertFalse($result->has('{DAV:}bind')); + + } + + function parse($xml) { + + $reader = new Reader(); + $reader->elementMap['{DAV:}root'] = 'Sabre\\DAVACL\\Xml\\Property\\CurrentUserPrivilegeSet'; + $reader->xml($xml); + $result = $reader->parse(); + return $result['value']; + + } + + function testToHtml() { + + $privileges = ['{DAV:}read', '{DAV:}write']; + + $prop = new CurrentUserPrivilegeSet($privileges); + $html = new HtmlOutputHelper( + '/base/', + ['DAV:' => 'd'] + ); + + $expected = + '<span title="{DAV:}read">d:read</span>, ' . + '<span title="{DAV:}write">d:write</span>'; + + $this->assertEquals($expected, $prop->toHtml($html)); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/PrincipalTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/PrincipalTest.php new file mode 100644 index 0000000..876d107 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/PrincipalTest.php @@ -0,0 +1,191 @@ +<?php + +namespace Sabre\DAVACL\Xml\Property; + +use Sabre\DAV; +use Sabre\DAV\Browser\HtmlOutputHelper; +use Sabre\HTTP; +use Sabre\Xml\Reader; + +class PrincipalTest extends \PHPUnit_Framework_TestCase { + + function testSimple() { + + $principal = new Principal(Principal::UNAUTHENTICATED); + $this->assertEquals(Principal::UNAUTHENTICATED, $principal->getType()); + $this->assertNull($principal->getHref()); + + $principal = new Principal(Principal::AUTHENTICATED); + $this->assertEquals(Principal::AUTHENTICATED, $principal->getType()); + $this->assertNull($principal->getHref()); + + $principal = new Principal(Principal::HREF, 'admin'); + $this->assertEquals(Principal::HREF, $principal->getType()); + $this->assertEquals('admin/', $principal->getHref()); + + } + + /** + * @depends testSimple + * @expectedException Sabre\DAV\Exception + */ + function testNoHref() { + + $principal = new Principal(Principal::HREF); + + } + + /** + * @depends testSimple + */ + function testSerializeUnAuthenticated() { + + $prin = new Principal(Principal::UNAUTHENTICATED); + + $xml = (new DAV\Server())->xml->write('{DAV:}principal', $prin); + + $this->assertXmlStringEqualsXmlString(' +<d:principal xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns"> +<d:unauthenticated/> +</d:principal>', $xml); + + } + + + /** + * @depends testSerializeUnAuthenticated + */ + function testSerializeAuthenticated() { + + $prin = new Principal(Principal::AUTHENTICATED); + $xml = (new DAV\Server())->xml->write('{DAV:}principal', $prin); + + $this->assertXmlStringEqualsXmlString(' +<d:principal xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns"> +<d:authenticated/> +</d:principal>', $xml); + + } + + + /** + * @depends testSerializeUnAuthenticated + */ + function testSerializeHref() { + + $prin = new Principal(Principal::HREF, 'principals/admin'); + $xml = (new DAV\Server())->xml->write('{DAV:}principal', $prin, '/'); + + $this->assertXmlStringEqualsXmlString(' +<d:principal xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns"> +<d:href>/principals/admin/</d:href> +</d:principal>', $xml); + + } + + function testUnserializeHref() { + + $xml = '<?xml version="1.0"?> +<d:principal xmlns:d="DAV:">' . +'<d:href>/principals/admin</d:href>' . +'</d:principal>'; + + $principal = $this->parse($xml); + $this->assertEquals(Principal::HREF, $principal->getType()); + $this->assertEquals('/principals/admin/', $principal->getHref()); + + } + + function testUnserializeAuthenticated() { + + $xml = '<?xml version="1.0"?> +<d:principal xmlns:d="DAV:">' . +' <d:authenticated />' . +'</d:principal>'; + + $principal = $this->parse($xml); + $this->assertEquals(Principal::AUTHENTICATED, $principal->getType()); + + } + + function testUnserializeUnauthenticated() { + + $xml = '<?xml version="1.0"?> +<d:principal xmlns:d="DAV:">' . +' <d:unauthenticated />' . +'</d:principal>'; + + $principal = $this->parse($xml); + $this->assertEquals(Principal::UNAUTHENTICATED, $principal->getType()); + + } + + /** + * @expectedException Sabre\DAV\Exception\BadRequest + */ + function testUnserializeUnknown() { + + $xml = '<?xml version="1.0"?> +<d:principal xmlns:d="DAV:">' . +' <d:foo />' . +'</d:principal>'; + + $this->parse($xml); + + } + + function parse($xml) { + + $reader = new Reader(); + $reader->elementMap['{DAV:}principal'] = 'Sabre\\DAVACL\\Xml\\Property\\Principal'; + $reader->xml($xml); + $result = $reader->parse(); + return $result['value']; + + } + + /** + * @depends testSimple + * @dataProvider htmlProvider + */ + function testToHtml($principal, $output) { + + $html = $principal->toHtml(new HtmlOutputHelper('/', [])); + + $this->assertXmlStringEqualsXmlString( + $output, + $html + ); + + } + + /** + * Provides data for the html tests + * + * @return array + */ + function htmlProvider() { + + return [ + [ + new Principal(Principal::UNAUTHENTICATED), + '<em>unauthenticated</em>', + ], + [ + new Principal(Principal::AUTHENTICATED), + '<em>authenticated</em>', + ], + [ + new Principal(Principal::ALL), + '<em>all</em>', + ], + [ + new Principal(Principal::HREF, 'principals/admin'), + '<a href="/principals/admin/">/principals/admin/</a>', + ], + + ]; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/SupportedPrivilegeSetTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/SupportedPrivilegeSetTest.php new file mode 100644 index 0000000..07f407c --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVACL/Xml/Property/SupportedPrivilegeSetTest.php @@ -0,0 +1,120 @@ +<?php + +namespace Sabre\DAVACL\Xml\Property; + +use Sabre\DAV; +use Sabre\HTTP; +use Sabre\DAV\Browser\HtmlOutputHelper; + +class SupportedPrivilegeSetTest extends \PHPUnit_Framework_TestCase { + + function testSimple() { + + $prop = new SupportedPrivilegeSet([ + 'privilege' => '{DAV:}all', + ]); + $this->assertInstanceOf('Sabre\DAVACL\Xml\Property\SupportedPrivilegeSet', $prop); + + } + + + /** + * @depends testSimple + */ + function testSerializeSimple() { + + $prop = new SupportedPrivilegeSet([ + 'privilege' => '{DAV:}all', + ]); + + $xml = (new DAV\Server())->xml->write('{DAV:}supported-privilege-set', $prop); + + $this->assertXmlStringEqualsXmlString(' +<d:supported-privilege-set xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns"> + <d:supported-privilege> + <d:privilege> + <d:all/> + </d:privilege> + </d:supported-privilege> +</d:supported-privilege-set>', $xml); + + } + + /** + * @depends testSimple + */ + function testSerializeAggregate() { + + $prop = new SupportedPrivilegeSet([ + 'privilege' => '{DAV:}all', + 'abstract' => true, + 'aggregates' => [ + [ + 'privilege' => '{DAV:}read', + ], + [ + 'privilege' => '{DAV:}write', + 'description' => 'booh', + ], + ], + ]); + + $xml = (new DAV\Server())->xml->write('{DAV:}supported-privilege-set', $prop); + + $this->assertXmlStringEqualsXmlString(' +<d:supported-privilege-set xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns"> + <d:supported-privilege> + <d:privilege> + <d:all/> + </d:privilege> + <d:abstract/> + <d:supported-privilege> + <d:privilege> + <d:read/> + </d:privilege> + </d:supported-privilege> + <d:supported-privilege> + <d:privilege> + <d:write/> + </d:privilege> + <d:description>booh</d:description> + </d:supported-privilege> + </d:supported-privilege> +</d:supported-privilege-set>', $xml); + + } + + function testToHtml() { + + $prop = new SupportedPrivilegeSet([ + 'privilege' => '{DAV:}all', + 'abstract' => true, + 'aggregates' => [ + [ + 'privilege' => '{DAV:}read', + ], + [ + 'privilege' => '{DAV:}write', + 'description' => 'booh', + ], + ], + ]); + $html = new HtmlOutputHelper( + '/base/', + ['DAV:' => 'd'] + ); + + $expected = <<<HTML +<ul class="tree"><li><span title="{DAV:}all">d:all</span> <i>(abstract)</i> +<ul> +<li><span title="{DAV:}read">d:read</span></li> +<li><span title="{DAV:}write">d:write</span> booh</li> +</ul></li> +</ul> + +HTML; + + $this->assertEquals($expected, $prop->toHtml($html)); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVServerTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVServerTest.php new file mode 100644 index 0000000..fedbe2a --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/DAVServerTest.php @@ -0,0 +1,239 @@ +<?php + +namespace Sabre; + +use + Sabre\HTTP\Request, + Sabre\HTTP\Response, + Sabre\HTTP\Sapi; + +/** + * This class may be used as a basis for other webdav-related unittests. + * + * This class is supposed to provide a reasonably big framework to quickly get + * a testing environment running. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +abstract class DAVServerTest extends \PHPUnit_Framework_TestCase { + + protected $setupCalDAV = false; + protected $setupCardDAV = false; + protected $setupACL = false; + protected $setupCalDAVSharing = false; + protected $setupCalDAVScheduling = false; + protected $setupCalDAVSubscriptions = false; + protected $setupCalDAVICSExport = false; + protected $setupLocks = false; + protected $setupFiles = false; + + /** + * An array with calendars. Every calendar should have + * - principaluri + * - uri + */ + protected $caldavCalendars = array(); + protected $caldavCalendarObjects = array(); + + protected $carddavAddressBooks = array(); + protected $carddavCards = array(); + + /** + * @var Sabre\DAV\Server + */ + protected $server; + protected $tree = array(); + + protected $caldavBackend; + protected $carddavBackend; + protected $principalBackend; + protected $locksBackend; + + /** + * @var Sabre\CalDAV\Plugin + */ + protected $caldavPlugin; + + /** + * @var Sabre\CardDAV\Plugin + */ + protected $carddavPlugin; + + /** + * @var Sabre\DAVACL\Plugin + */ + protected $aclPlugin; + + /** + * @var Sabre\CalDAV\SharingPlugin + */ + protected $caldavSharingPlugin; + + /** + * CalDAV scheduling plugin + * + * @var CalDAV\Schedule\Plugin + */ + protected $caldavSchedulePlugin; + + /** + * @var Sabre\DAV\Auth\Plugin + */ + protected $authPlugin; + + /** + * @var Sabre\DAV\Locks\Plugin + */ + protected $locksPlugin; + + /** + * If this string is set, we will automatically log in the user with this + * name. + */ + protected $autoLogin = null; + + function setUp() { + + $this->setUpBackends(); + $this->setUpTree(); + + $this->server = new DAV\Server($this->tree); + $this->server->sapi = new HTTP\SapiMock(); + $this->server->debugExceptions = true; + + if ($this->setupCalDAV) { + $this->caldavPlugin = new CalDAV\Plugin(); + $this->server->addPlugin($this->caldavPlugin); + } + if ($this->setupCalDAVSharing) { + $this->caldavSharingPlugin = new CalDAV\SharingPlugin(); + $this->server->addPlugin($this->caldavSharingPlugin); + } + if ($this->setupCalDAVScheduling) { + $this->caldavSchedulePlugin = new CalDAV\Schedule\Plugin(); + $this->server->addPlugin($this->caldavSchedulePlugin); + } + if ($this->setupCalDAVSubscriptions) { + $this->server->addPlugin(new CalDAV\Subscriptions\Plugin()); + } + if ($this->setupCalDAVICSExport) { + $this->caldavICSExportPlugin = new CalDAV\ICSExportPlugin(); + $this->server->addPlugin($this->caldavICSExportPlugin); + } + if ($this->setupCardDAV) { + $this->carddavPlugin = new CardDAV\Plugin(); + $this->server->addPlugin($this->carddavPlugin); + } + if ($this->setupACL) { + $this->aclPlugin = new DAVACL\Plugin(); + $this->server->addPlugin($this->aclPlugin); + } + if ($this->setupLocks) { + $this->locksPlugin = new DAV\Locks\Plugin( + $this->locksBackend + ); + $this->server->addPlugin($this->locksPlugin); + } + if ($this->autoLogin) { + $authBackend = new DAV\Auth\Backend\Mock(); + $authBackend->setPrincipal('principals/' . $this->autoLogin); + $this->authPlugin = new DAV\Auth\Plugin($authBackend); + $this->server->addPlugin($this->authPlugin); + + // This will trigger the actual login procedure + $this->authPlugin->beforeMethod(new Request(), new Response()); + } + + } + + /** + * Makes a request, and returns a response object. + * + * You can either pass an instance of Sabre\HTTP\Request, or an array, + * which will then be used as the _SERVER array. + * + * @param array|\Sabre\HTTP\Request $request + * @return \Sabre\HTTP\Response + */ + function request($request) { + + if (is_array($request)) { + $request = HTTP\Request::createFromServerArray($request); + } + $this->server->httpRequest = $request; + $this->server->httpResponse = new HTTP\ResponseMock(); + $this->server->exec(); + + return $this->server->httpResponse; + + } + + /** + * Override this to provide your own Tree for your test-case. + */ + function setUpTree() { + + if ($this->setupCalDAV) { + $this->tree[] = new CalDAV\CalendarRoot( + $this->principalBackend, + $this->caldavBackend + ); + } + if ($this->setupCardDAV) { + $this->tree[] = new CardDAV\AddressBookRoot( + $this->principalBackend, + $this->carddavBackend + ); + } + + if ($this->setupCardDAV || $this->setupCalDAV) { + $this->tree[] = new DAVACL\PrincipalCollection( + $this->principalBackend + ); + } + if ($this->setupFiles) { + + $this->tree[] = new DAV\Mock\Collection('files'); + + } + + } + + function setUpBackends() { + + if ($this->setupCalDAVSharing && is_null($this->caldavBackend)) { + $this->caldavBackend = new CalDAV\Backend\MockSharing($this->caldavCalendars, $this->caldavCalendarObjects); + } + if ($this->setupCalDAVSubscriptions && is_null($this->caldavBackend)) { + $this->caldavBackend = new CalDAV\Backend\MockSubscriptionSupport($this->caldavCalendars, $this->caldavCalendarObjects); + } + if ($this->setupCalDAV && is_null($this->caldavBackend)) { + if ($this->setupCalDAVScheduling) { + $this->caldavBackend = new CalDAV\Backend\MockScheduling($this->caldavCalendars, $this->caldavCalendarObjects); + } else { + $this->caldavBackend = new CalDAV\Backend\Mock($this->caldavCalendars, $this->caldavCalendarObjects); + } + } + if ($this->setupCardDAV && is_null($this->carddavBackend)) { + $this->carddavBackend = new CardDAV\Backend\Mock($this->carddavAddressBooks, $this->carddavCards); + } + if ($this->setupCardDAV || $this->setupCalDAV) { + $this->principalBackend = new DAVACL\PrincipalBackend\Mock(); + } + if ($this->setupLocks) { + $this->locksBackend = new DAV\Locks\Backend\Mock(); + } + + } + + + function assertHTTPStatus($expectedStatus, HTTP\Request $req) { + + $resp = $this->request($req); + $this->assertEquals((int)$expectedStatus, (int)$resp->status,'Incorrect HTTP status received: ' . $resp->body); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/HTTP/ResponseMock.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/HTTP/ResponseMock.php new file mode 100644 index 0000000..eb486bf --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/HTTP/ResponseMock.php @@ -0,0 +1,22 @@ +<?php + +namespace Sabre\HTTP; + +/** + * HTTP Response Mock object + * + * This class exists to make the transition to sabre/http easier. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class ResponseMock extends Response { + + /** + * Making these public. + */ + public $body; + public $status; + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/HTTP/SapiMock.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/HTTP/SapiMock.php new file mode 100644 index 0000000..28aab13 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/HTTP/SapiMock.php @@ -0,0 +1,29 @@ +<?php + +namespace Sabre\HTTP; + +/** + * HTTP Response Mock object + * + * This class exists to make the transition to sabre/http easier. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class SapiMock extends Sapi { + + static $sent = 0; + + /** + * Overriding this so nothing is ever echo'd. + * + * @return void + */ + static function sendResponse(ResponseInterface $response) { + + self::$sent++; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/TestUtil.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/TestUtil.php new file mode 100644 index 0000000..20bce1e --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/Sabre/TestUtil.php @@ -0,0 +1,58 @@ +<?php + +namespace Sabre; + +class TestUtil { + + /** + * This function deletes all the contents of the temporary directory. + * + * @return void + */ + static function clearTempDir() { + + self::deleteTree(SABRE_TEMPDIR,false); + + } + + + static private function deleteTree($path,$deleteRoot = true) { + + foreach(scandir($path) as $node) { + + if ($node=='.' || $node=='..') continue; + $myPath = $path.'/'. $node; + if (is_file($myPath)) { + unlink($myPath); + } else { + self::deleteTree($myPath); + } + + } + if ($deleteRoot) { + rmdir($path); + } + + } + + static function getMySQLDB() { + + try { + $pdo = new \PDO(SABRE_MYSQLDSN,SABRE_MYSQLUSER,SABRE_MYSQLPASS); + $pdo->setAttribute(\PDO::ATTR_ERRMODE,\PDO::ERRMODE_EXCEPTION); + return $pdo; + } catch (\PDOException $e) { + return null; + } + + } + + static function getSQLiteDB() { + + $pdo = new \PDO('sqlite:'.SABRE_TEMPDIR.'/pdobackend'); + $pdo->setAttribute(\PDO::ATTR_ERRMODE,\PDO::ERRMODE_EXCEPTION); + return $pdo; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/bootstrap.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/bootstrap.php new file mode 100644 index 0000000..fcb49d3 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/bootstrap.php @@ -0,0 +1,29 @@ +<?php + +set_include_path(__DIR__ . '/../lib/' . PATH_SEPARATOR . __DIR__ . PATH_SEPARATOR . get_include_path()); + +$autoLoader = include __DIR__ . '/../vendor/autoload.php'; + +// SabreDAV tests auto loading +$autoLoader->add('Sabre\\', __DIR__); +// VObject tests auto loading +$autoLoader->addPsr4('Sabre\\VObject\\',__DIR__ . '/../vendor/sabre/vobject/tests/VObject'); +$autoLoader->addPsr4('Sabre\\Xml\\',__DIR__ . '/../vendor/sabre/xml/tests/Sabre/Xml'); + +date_default_timezone_set('UTC'); + +$config = [ + 'SABRE_TEMPDIR' => dirname(__FILE__) . '/temp/', + 'SABRE_HASSQLITE' => in_array('sqlite',PDO::getAvailableDrivers()), + 'SABRE_HASMYSQL' => in_array('mysql',PDO::getAvailableDrivers()), + 'SABRE_MYSQLDSN' => 'mysql:host=127.0.0.1;dbname=sabredav', + 'SABRE_MYSQLUSER' => 'root', + 'SABRE_MYSQLPASS' => '', +]; + +foreach($config as $key=>$value) { + if (!defined($key)) define($key, $value); +} + +if (!file_exists(SABRE_TEMPDIR)) mkdir(SABRE_TEMPDIR); +if (file_exists('.sabredav')) unlink('.sabredav'); diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/phpcs/ruleset.xml b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/phpcs/ruleset.xml new file mode 100644 index 0000000..ec2c4c8 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/phpcs/ruleset.xml @@ -0,0 +1,57 @@ +<?xml version="1.0"?> +<ruleset name="sabre.php"> + <description>sabre.io codesniffer ruleset</description> + + <!-- Include the whole PSR-1 standard --> + <rule ref="PSR1" /> + + <!-- All PHP files MUST use the Unix LF (linefeed) line ending. --> + <rule ref="Generic.Files.LineEndings"> + <properties> + <property name="eolChar" value="\n"/> + </properties> + </rule> + + <!-- The closing ?> tag MUST be omitted from files containing only PHP. --> + <rule ref="Zend.Files.ClosingTag"/> + + <!-- There MUST NOT be trailing whitespace at the end of non-blank lines. --> + <rule ref="Squiz.WhiteSpace.SuperfluousWhitespace"> + <properties> + <property name="ignoreBlankLines" value="true"/> + </properties> + </rule> + + <!-- There MUST NOT be more than one statement per line. --> + <rule ref="Generic.Formatting.DisallowMultipleStatements"/> + + <rule ref="Generic.WhiteSpace.ScopeIndent"> + <properties> + <property name="ignoreIndentationTokens" type="array" value="T_COMMENT,T_DOC_COMMENT"/> + </properties> + </rule> + <rule ref="Generic.WhiteSpace.DisallowTabIndent"/> + + <!-- PHP keywords MUST be in lower case. --> + <rule ref="Generic.PHP.LowerCaseKeyword"/> + + <!-- The PHP constants true, false, and null MUST be in lower case. --> + <rule ref="Generic.PHP.LowerCaseConstant"/> + + <!-- <rule ref="Squiz.Scope.MethodScope"/> --> + <rule ref="Squiz.WhiteSpace.ScopeKeywordSpacing"/> + + <!-- In the argument list, there MUST NOT be a space before each comma, and there MUST be one space after each comma. --> + <!-- + <rule ref="Squiz.Functions.FunctionDeclarationArgumentSpacing"> + <properties> + <property name="equalsSpacing" value="1"/> + </properties> + </rule> + <rule ref="Squiz.Functions.FunctionDeclarationArgumentSpacing.SpacingAfterHint"> + <severity>0</severity> + </rule> + --> + <rule ref="PEAR.WhiteSpace.ScopeClosingBrace"/> + +</ruleset> diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/phpunit.xml b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/phpunit.xml new file mode 100644 index 0000000..db475f1 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/dav/tests/phpunit.xml @@ -0,0 +1,47 @@ +<phpunit + colors="true" + bootstrap="bootstrap.php" + convertErrorsToExceptions="true" + convertNoticesToExceptions="true" + convertWarningsToExceptions="true" + strict="true" + > + <testsuite name="sabre-event"> + <directory>../vendor/sabre/event/tests/</directory> + </testsuite> + <testsuite name="sabre-uri"> + <directory>../vendor/sabre/uri/tests/</directory> + </testsuite> + <testsuite name="sabre-xml"> + <directory>../vendor/sabre/xml/tests/Sabre/Xml/</directory> + </testsuite> + <testsuite name="sabre-http"> + <directory>../vendor/sabre/http/tests/HTTP</directory> + </testsuite> + <testsuite name="sabre-vobject"> + <directory>../vendor/sabre/vobject/tests/VObject</directory> + </testsuite> + + <testsuite name="sabre-dav"> + <directory>Sabre/DAV</directory> + </testsuite> + <testsuite name="sabre-davacl"> + <directory>Sabre/DAVACL</directory> + </testsuite> + <testsuite name="sabre-caldav"> + <directory>Sabre/CalDAV</directory> + </testsuite> + <testsuite name="sabre-carddav"> + <directory>Sabre/CardDAV</directory> + </testsuite> + + <filter> + <whitelist addUncoveredFilesFromWhitelist="true"> + <directory suffix=".php">../lib/</directory> + <exclude> + <file>../lib/Sabre/autoload.php</file> + <file>../lib/Sabre/VObject/includes.php</file> + </exclude> + </whitelist> + </filter> +</phpunit> diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/.gitignore b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/.gitignore new file mode 100644 index 0000000..d06a781 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/.gitignore @@ -0,0 +1,14 @@ +#composer +vendor +composer.lock + +#binaries +bin/sabre-cs-fixer +bin/php-cs-fixer +bin/phpunit + +#vim lock files +.*.swp + +#development stuff +tests/cov diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/.travis.yml b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/.travis.yml new file mode 100644 index 0000000..a4fbd0b --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/.travis.yml @@ -0,0 +1,18 @@ +language: php +php: + - 5.4 + - 5.5 + - 5.6 + - 7 + - hhvm + +matrix: + fast_finish: true + allow_failures: + - php: 7 + +script: + - ./bin/phpunit + - ./bin/sabre-cs-fixer fix . --dry-run --diff + +before_script: composer install --dev diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/CHANGELOG.md b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/CHANGELOG.md new file mode 100644 index 0000000..7e75c15 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/CHANGELOG.md @@ -0,0 +1,51 @@ +ChangeLog +========= + +2.0.2 (2015-05-19) +------------------ + +* This release has no functional changes. It's just been brought up to date + with the latest coding standards. + + +2.0.1 (2014-10-06) +------------------ + +* Fixed: `$priority` was ignored in `EventEmitter::once` method. +* Fixed: Breaking the event chain was not possible in `EventEmitter::once`. + + +2.0.0 (2014-06-21) +------------------ + +* Added: When calling emit, it's now possible to specify a callback that will be + triggered after each method handled. This is dubbed the 'continueCallback' and + can be used to implement strategy patterns. +* Added: Promise object! +* Changed: EventEmitter::listeners now returns just the callbacks for an event, + and no longer returns the list by reference. The list is now automatically + sorted by priority. +* Update: Speed improvements. +* Updated: It's now possible to remove all listeners for every event. +* Changed: Now uses psr-4 autoloading. + + +1.0.1 (2014-06-12) +------------------ + +* hhvm compatible! +* Fixed: Issue #4. Compatiblitiy for PHP < 5.4.14. + + +1.0.0 (2013-07-19) +------------------ + +* Added: removeListener, removeAllListeners +* Added: once, to only listen to an event emitting once. +* Added README.md. + + +0.0.1-alpha (2013-06-29) +------------------------ + +* First version! diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/LICENSE b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/LICENSE new file mode 100644 index 0000000..9a495ce --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/LICENSE @@ -0,0 +1,27 @@ +Copyright (C) 2013-2015 fruux GmbH (https://fruux.com/) + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name Sabre nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/README.md b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/README.md new file mode 100644 index 0000000..12863b6 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/README.md @@ -0,0 +1,49 @@ +sabre/event +=========== + +A lightweight library for event-based development in PHP. + +This library provides two patterns: + +1. EventEmitter +2. Promises + +Full documentation can be found on [the website][1]. + +Installation +------------ + +Make sure you have [composer][3] installed, and then run: + + composer require sabre/event "~2.0.0" + +For legacy reasons, we also provide a unsupported [PHP 5.3 compatible version][6]. +We recommend that you update your servers and use the regular version instead, though. + +Build status +------------ + +| branch | status | +| ------ | ------ | +| master | [![Build Status](https://travis-ci.org/fruux/sabre-event.svg?branch=master)](https://travis-ci.org/fruux/sabre-event) | +| 2.0 | [![Build Status](https://travis-ci.org/fruux/sabre-event.svg?branch=2.0)](https://travis-ci.org/fruux/sabre-event) | +| 1.0 | [![Build Status](https://travis-ci.org/fruux/sabre-event.svg?branch=1.0)](https://travis-ci.org/fruux/sabre-event) | +| php53 | [![Build Status](https://travis-ci.org/fruux/sabre-event.svg?branch=php53)](https://travis-ci.org/fruux/sabre-event) | + + +Questions? +---------- + +Head over to the [sabre/dav mailinglist][4], or you can also just open a ticket +on [GitHub][5]. + +Made at fruux +------------- + +This library is being developed by [fruux](https://fruux.com/). Drop us a line for commercial services or enterprise support. + +[1]: http://sabre.io/event/ +[3]: http://getcomposer.org/ +[4]: http://groups.google.com/group/sabredav-discuss +[5]: https://github.com/fruux/sabre-event/issues/ +[6]: https://github.com/fruux/sabre-event/tree/php53 diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/bin/.empty b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/bin/.empty new file mode 100644 index 0000000..e69de29 diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/composer.json b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/composer.json new file mode 100644 index 0000000..b2b5533 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/composer.json @@ -0,0 +1,41 @@ +{ + "name": "sabre/event", + "description": "sabre/event is a library for lightweight event-based programming", + "keywords": [ + "Events", + "EventEmitter", + "Promise", + "Hooks", + "Plugin", + "Signal" + ], + "homepage": "http://sabre.io/event/", + "license": "BSD-3-Clause", + "require": { + "php": ">=5.4.1" + }, + "authors": [ + { + "name": "Evert Pot", + "email": "me@evertpot.com", + "homepage": "http://evertpot.com/", + "role": "Developer" + } + ], + "support": { + "forum": "https://groups.google.com/group/sabredav-discuss", + "source": "https://github.com/fruux/sabre-event" + }, + "autoload": { + "psr-4": { + "Sabre\\Event\\": "lib/" + } + }, + "require-dev": { + "sabre/cs": "~0.0.1", + "phpunit/phpunit" : "*" + }, + "config" : { + "bin-dir" : "bin/" + } +} diff --git a/src/serverlib/3rdparty/sabre/Event/EventEmitter.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/lib/EventEmitter.php similarity index 100% rename from src/serverlib/3rdparty/sabre/Event/EventEmitter.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/lib/EventEmitter.php diff --git a/src/serverlib/3rdparty/sabre/Event/EventEmitterInterface.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/lib/EventEmitterInterface.php similarity index 100% rename from src/serverlib/3rdparty/sabre/Event/EventEmitterInterface.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/lib/EventEmitterInterface.php diff --git a/src/serverlib/3rdparty/sabre/Event/EventEmitterTrait.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/lib/EventEmitterTrait.php similarity index 100% rename from src/serverlib/3rdparty/sabre/Event/EventEmitterTrait.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/lib/EventEmitterTrait.php diff --git a/src/serverlib/3rdparty/sabre/Event/Promise.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/lib/Promise.php similarity index 100% rename from src/serverlib/3rdparty/sabre/Event/Promise.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/lib/Promise.php diff --git a/src/serverlib/3rdparty/sabre/Event/PromiseAlreadyResolvedException.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/lib/PromiseAlreadyResolvedException.php similarity index 100% rename from src/serverlib/3rdparty/sabre/Event/PromiseAlreadyResolvedException.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/lib/PromiseAlreadyResolvedException.php diff --git a/src/serverlib/3rdparty/sabre/Event/Version.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/lib/Version.php similarity index 100% rename from src/serverlib/3rdparty/sabre/Event/Version.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/lib/Version.php diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/phpunit.xml.dist b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/phpunit.xml.dist new file mode 100644 index 0000000..ccd59be --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/phpunit.xml.dist @@ -0,0 +1,18 @@ +<phpunit + colors="true" + bootstrap="vendor/autoload.php" + convertErrorsToExceptions="true" + convertNoticesToExceptions="true" + convertWarningsToExceptions="true" + strict="true" + > + <testsuite name="sabre-event"> + <directory>tests/</directory> + </testsuite> + + <filter> + <whitelist addUncoveredFilesFromWhitelist="true"> + <directory suffix=".php">./lib/</directory> + </whitelist> + </filter> +</phpunit> diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/tests/ContinueCallbackTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/tests/ContinueCallbackTest.php new file mode 100644 index 0000000..c469913 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/tests/ContinueCallbackTest.php @@ -0,0 +1,76 @@ +<?php + +namespace Sabre\Event; + +class ContinueCallbackTest extends \PHPUnit_Framework_TestCase { + + function testContinueCallBack() { + + $ee = new EventEmitter(); + + $handlerCounter = 0; + $bla = function() use (&$handlerCounter) { + $handlerCounter++; + }; + $ee->on('foo', $bla); + $ee->on('foo', $bla); + $ee->on('foo', $bla); + + $continueCounter = 0; + $r = $ee->emit('foo', [], function() use (&$continueCounter) { + $continueCounter++; + return true; + }); + $this->assertTrue($r); + $this->assertEquals(3, $handlerCounter); + $this->assertEquals(2, $continueCounter); + + } + + function testContinueCallBackBreak() { + + $ee = new EventEmitter(); + + $handlerCounter = 0; + $bla = function() use (&$handlerCounter) { + $handlerCounter++; + }; + $ee->on('foo', $bla); + $ee->on('foo', $bla); + $ee->on('foo', $bla); + + $continueCounter = 0; + $r = $ee->emit('foo', [], function() use (&$continueCounter) { + $continueCounter++; + return false; + }); + $this->assertTrue($r); + $this->assertEquals(1, $handlerCounter); + $this->assertEquals(1, $continueCounter); + + } + + function testContinueCallBackBreakByHandler() { + + $ee = new EventEmitter(); + + $handlerCounter = 0; + $bla = function() use (&$handlerCounter) { + $handlerCounter++; + return false; + }; + $ee->on('foo', $bla); + $ee->on('foo', $bla); + $ee->on('foo', $bla); + + $continueCounter = 0; + $r = $ee->emit('foo', [], function() use (&$continueCounter) { + $continueCounter++; + return false; + }); + $this->assertFalse($r); + $this->assertEquals(1, $handlerCounter); + $this->assertEquals(0, $continueCounter); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/tests/EventEmitterTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/tests/EventEmitterTest.php new file mode 100644 index 0000000..df08e9c --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/tests/EventEmitterTest.php @@ -0,0 +1,318 @@ +<?php + +namespace Sabre\Event; + +class EventEmitterTest extends \PHPUnit_Framework_TestCase { + + function testInit() { + + $ee = new EventEmitter(); + $this->assertInstanceOf('Sabre\\Event\\EventEmitter', $ee); + + } + + function testListeners() { + + $ee = new EventEmitter(); + + $callback1 = function() { }; + $callback2 = function() { }; + $ee->on('foo', $callback1, 200); + $ee->on('foo', $callback2, 100); + + $this->assertEquals([$callback2, $callback1], $ee->listeners('foo')); + + } + + /** + * @depends testInit + */ + function testHandleEvent() { + + $argResult = null; + + $ee = new EventEmitter(); + $ee->on('foo', function($arg) use (&$argResult) { + + $argResult = $arg; + + }); + + $this->assertTrue( + $ee->emit('foo', ['bar']) + ); + + $this->assertEquals('bar', $argResult); + + } + + /** + * @depends testHandleEvent + */ + function testCancelEvent() { + + $argResult = 0; + + $ee = new EventEmitter(); + $ee->on('foo', function($arg) use (&$argResult) { + + $argResult = 1; + return false; + + }); + $ee->on('foo', function($arg) use (&$argResult) { + + $argResult = 2; + + }); + + $this->assertFalse( + $ee->emit('foo', ['bar']) + ); + + $this->assertEquals(1, $argResult); + + } + + /** + * @depends testCancelEvent + */ + function testPriority() { + + $argResult = 0; + + $ee = new EventEmitter(); + $ee->on('foo', function($arg) use (&$argResult) { + + $argResult = 1; + return false; + + }); + $ee->on('foo', function($arg) use (&$argResult) { + + $argResult = 2; + return false; + + }, 1); + + $this->assertFalse( + $ee->emit('foo', ['bar']) + ); + + $this->assertEquals(2, $argResult); + + } + + /** + * @depends testPriority + */ + function testPriority2() { + + $result = []; + $ee = new EventEmitter(); + + $ee->on('foo', function() use (&$result) { + + $result[] = 'a'; + + }, 200); + $ee->on('foo', function() use (&$result) { + + $result[] = 'b'; + + }, 50); + $ee->on('foo', function() use (&$result) { + + $result[] = 'c'; + + }, 300); + $ee->on('foo', function() use (&$result) { + + $result[] = 'd'; + + }); + + $ee->emit('foo'); + $this->assertEquals(['b', 'd', 'a', 'c'], $result); + + } + + function testRemoveListener() { + + $result = false; + + $callBack = function() use (&$result) { + + $result = true; + + }; + + $ee = new EventEmitter(); + + $ee->on('foo', $callBack); + + $ee->emit('foo'); + $this->assertTrue($result); + $result = false; + + $this->assertTrue( + $ee->removeListener('foo', $callBack) + ); + + $ee->emit('foo'); + $this->assertFalse($result); + + } + + function testRemoveUnknownListener() { + + $result = false; + + $callBack = function() use (&$result) { + + $result = true; + + }; + + $ee = new EventEmitter(); + + $ee->on('foo', $callBack); + + $ee->emit('foo'); + $this->assertTrue($result); + $result = false; + + $this->assertFalse($ee->removeListener('bar', $callBack)); + + $ee->emit('foo'); + $this->assertTrue($result); + + } + + function testRemoveListenerTwice() { + + $result = false; + + $callBack = function() use (&$result) { + + $result = true; + + }; + + $ee = new EventEmitter(); + + $ee->on('foo', $callBack); + + $ee->emit('foo'); + $this->assertTrue($result); + $result = false; + + $this->assertTrue( + $ee->removeListener('foo', $callBack) + ); + $this->assertFalse( + $ee->removeListener('foo', $callBack) + ); + + $ee->emit('foo'); + $this->assertFalse($result); + + } + + function testRemoveAllListeners() { + + $result = false; + $callBack = function() use (&$result) { + + $result = true; + + }; + + $ee = new EventEmitter(); + $ee->on('foo', $callBack); + + $ee->emit('foo'); + $this->assertTrue($result); + $result = false; + + $ee->removeAllListeners('foo'); + + $ee->emit('foo'); + $this->assertFalse($result); + + } + + function testRemoveAllListenersNoArg() { + + $result = false; + + $callBack = function() use (&$result) { + + $result = true; + + }; + + + $ee = new EventEmitter(); + $ee->on('foo', $callBack); + + $ee->emit('foo'); + $this->assertTrue($result); + $result = false; + + $ee->removeAllListeners(); + + $ee->emit('foo'); + $this->assertFalse($result); + + } + + function testOnce() { + + $result = 0; + + $callBack = function() use (&$result) { + + $result++; + + }; + + $ee = new EventEmitter(); + $ee->once('foo', $callBack); + + $ee->emit('foo'); + $ee->emit('foo'); + + $this->assertEquals(1, $result); + + } + + /** + * @depends testCancelEvent + */ + function testPriorityOnce() { + + $argResult = 0; + + $ee = new EventEmitter(); + $ee->once('foo', function($arg) use (&$argResult) { + + $argResult = 1; + return false; + + }); + $ee->once('foo', function($arg) use (&$argResult) { + + $argResult = 2; + return false; + + }, 1); + + $this->assertFalse( + $ee->emit('foo', ['bar']) + ); + + $this->assertEquals(2, $argResult); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/tests/PromiseTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/tests/PromiseTest.php new file mode 100644 index 0000000..5b56237 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/tests/PromiseTest.php @@ -0,0 +1,226 @@ +<?php + +namespace Sabre\Event; + +class PromiseTest extends \PHPUnit_Framework_TestCase { + + function testSuccess() { + + $finalValue = 0; + $promise = new Promise(); + $promise->fulfill(1); + + $promise->then(function($value) use (&$finalValue) { + $finalValue = $value + 2; + }); + + $this->assertEquals(3, $finalValue); + + } + + function testFail() { + + $finalValue = 0; + $promise = new Promise(); + $promise->reject(1); + + $promise->then(null, function($value) use (&$finalValue) { + $finalValue = $value + 2; + }); + + $this->assertEquals(3, $finalValue); + + } + + function testChain() { + + $finalValue = 0; + $promise = new Promise(); + $promise->fulfill(1); + + $promise->then(function($value) use (&$finalValue) { + $finalValue = $value + 2; + return $finalValue; + })->then(function($value) use (&$finalValue) { + $finalValue = $value + 4; + return $finalValue; + }); + + $this->assertEquals(7, $finalValue); + + } + function testChainPromise() { + + $finalValue = 0; + $promise = new Promise(); + $promise->fulfill(1); + + $subPromise = new Promise(); + + $promise->then(function($value) use ($subPromise) { + return $subPromise; + })->then(function($value) use (&$finalValue) { + $finalValue = $value + 4; + return $finalValue; + }); + + $subPromise->fulfill(2); + + $this->assertEquals(6, $finalValue); + + } + + function testPendingResult() { + + $finalValue = 0; + $promise = new Promise(); + + $promise->then(function($value) use (&$finalValue) { + $finalValue = $value + 2; + }); + + $promise->fulfill(4); + $this->assertEquals(6, $finalValue); + + } + + function testPendingFail() { + + $finalValue = 0; + $promise = new Promise(); + + $promise->then(null, function($value) use (&$finalValue) { + $finalValue = $value + 2; + }); + + $promise->reject(4); + $this->assertEquals(6, $finalValue); + + } + + function testExecutorSuccess() { + + $promise = (new Promise(function($success, $fail) { + + $success('hi'); + + }))->then(function($result) use (&$realResult) { + + $realResult = $result; + + }); + + $this->assertEquals('hi', $realResult); + + } + + function testExecutorFail() { + + $promise = (new Promise(function($success, $fail) { + + $fail('hi'); + + }))->then(function($result) use (&$realResult) { + + $realResult = 'incorrect'; + + }, function($reason) use (&$realResult) { + + $realResult = $reason; + + }); + + $this->assertEquals('hi', $realResult); + + } + + /** + * @expectedException \Sabre\Event\PromiseAlreadyResolvedException + */ + function testFulfillTwice() { + + $promise = new Promise(); + $promise->fulfill(1); + $promise->fulfill(1); + + } + + /** + * @expectedException \Sabre\Event\PromiseAlreadyResolvedException + */ + function testRejectTwice() { + + $promise = new Promise(); + $promise->reject(1); + $promise->reject(1); + + } + + function testFromFailureHandler() { + + $ok = 0; + $promise = new Promise(); + $promise->error(function($reason) { + + $this->assertEquals('foo', $reason); + throw new \Exception('hi'); + + })->then(function() use (&$ok) { + + $ok = -1; + + }, function() use (&$ok) { + + $ok = 1; + + }); + + $this->assertEquals(0, $ok); + $promise->reject('foo'); + $this->assertEquals(1, $ok); + + } + + function testAll() { + + $promise1 = new Promise(); + $promise2 = new Promise(); + + $finalValue = 0; + Promise::all([$promise1, $promise2])->then(function($value) use (&$finalValue) { + + $finalValue = $value; + + }); + + $promise1->fulfill(1); + $this->assertEquals(0, $finalValue); + $promise2->fulfill(2); + $this->assertEquals([1, 2], $finalValue); + + } + + function testAllReject() { + + $promise1 = new Promise(); + $promise2 = new Promise(); + + $finalValue = 0; + Promise::all([$promise1, $promise2])->then( + function($value) use (&$finalValue) { + $finalValue = 'foo'; + return 'test'; + }, + function($value) use (&$finalValue) { + $finalValue = $value; + } + ); + + $promise1->reject(1); + $this->assertEquals(1, $finalValue); + $promise2->reject(2); + $this->assertEquals(1, $finalValue); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/tests/benchmark/bench.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/tests/benchmark/bench.php new file mode 100644 index 0000000..b1e6b1d --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/event/tests/benchmark/bench.php @@ -0,0 +1,116 @@ +<?php + +use Sabre\Event\EventEmitter; + +include __DIR__ . '/../../vendor/autoload.php'; + +abstract class BenchMark { + + protected $startTime; + protected $iterations = 10000; + protected $totalTime; + + function setUp() { + + } + + abstract function test(); + + function go() { + + $this->setUp(); + $this->startTime = microtime(true); + $this->test(); + $this->totalTime = microtime(true) - $this->startTime; + return $this->totalTime; + + } + +} + +class OneCallBack extends BenchMark { + + protected $emitter; + protected $iterations = 100000; + + function setUp() { + + $this->emitter = new EventEmitter(); + $this->emitter->on('foo', function() { + // NOOP + }); + + } + + function test() { + + for ($i = 0;$i < $this->iterations;$i++) { + $this->emitter->emit('foo', []); + } + + } + +} + +class ManyCallBacks extends BenchMark { + + protected $emitter; + + function setUp() { + + $this->emitter = new EventEmitter(); + for ($i = 0;$i < 100;$i++) { + $this->emitter->on('foo', function() { + // NOOP + }); + } + + } + + function test() { + + for ($i = 0;$i < $this->iterations;$i++) { + $this->emitter->emit('foo', []); + } + + } + +} + +class ManyPrioritizedCallBacks extends BenchMark { + + protected $emitter; + + function setUp() { + + $this->emitter = new EventEmitter(); + for ($i = 0;$i < 100;$i++) { + $this->emitter->on('foo', function() { + }, 1000 - $i); + } + + } + + function test() { + + for ($i = 0;$i < $this->iterations;$i++) { + $this->emitter->emit('foo', []); + } + + } + +} + +$tests = [ + 'OneCallBack', + 'ManyCallBacks', + 'ManyPrioritizedCallBacks', +]; + +foreach ($tests as $test) { + + $testObj = new $test(); + $result = $testObj->go(); + echo $test . " " . $result . "\n"; + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/.gitignore b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/.gitignore new file mode 100644 index 0000000..8c97686 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/.gitignore @@ -0,0 +1,15 @@ +# Composer +vendor/ +composer.lock + +# Tests +tests/cov/ + +# Composer binaries +bin/phpunit +bin/phpcs +bin/php-cs-fixer +bin/sabre-cs-fixer + +# Vim +.*.swp diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/.travis.yml b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/.travis.yml new file mode 100644 index 0000000..490e42e --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/.travis.yml @@ -0,0 +1,24 @@ +language: php +php: + - 5.4 + - 5.5 + - 5.6 + - 7 + - hhvm + +matrix: + fast_finish: true + +env: + matrix: + - PREFER_LOWEST="" + - PREFER_LOWEST="--prefer-lowest" + + +before_script: + - composer self-update + - composer update --prefer-source $PREFER_LOWEST + +script: + - ./bin/phpunit --configuration tests/phpunit.xml + - ./bin/sabre-cs-fixer fix . --dry-run --diff diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/CHANGELOG.md b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/CHANGELOG.md new file mode 100644 index 0000000..9a751d8 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/CHANGELOG.md @@ -0,0 +1,250 @@ +ChangeLog +========= + +4.2.1 (2016-01-06) +------------------ + +* #56: `getBodyAsString` now returns at most as many bytes as the contents of + the `Content-Length` header. This allows users to pass much larger strings + without having to copy and truncate them. + + +4.2.0 (2016-01-04) +------------------ + +* This package now supports sabre/event 3.0. +* The client now sets a default `User-Agent` header identifying this library. + + +4.1.0 (2015-09-04) +------------------ + +* The async client wouldn't `wait()` for new http requests being started + after the (previous) last request in the queue was resolved. +* Added `Sabre\HTTP\Auth\Bearer`, to easily extract a OAuth2 bearer token. + + +4.0.0 (2015-05-20) +------------------ + +* Deprecated: All static functions from `Sabre\HTTP\URLUtil` and + `Sabre\HTTP\Util` moved to a separate `functions.php`, which is also + autoloaded. The old functions are still there, but will be removed in a + future version. (#49) + + +4.0.0-alpha3 (2015-05-19) +------------------------- + +* Added a parser for the HTTP `Prefer` header, as defined in [RFC7240][rfc7240]. +* Deprecated `Sabre\HTTP\Util::parseHTTPDate`, use `Sabre\HTTP\parseDate()`. +* Deprecated `Sabre\HTTP\Util::toHTTPDate` use `Sabre\HTTP\toDate()`. + + +4.0.0-alpha2 (2015-05-18) +------------------------- + +* #45: Don't send more data than what is promised in the HTTP content-length. + (@dratini0). +* #43: `getCredentials` returns null if incomplete. (@Hywan) +* #48: Now using php-cs-fixer to make our CS consistent (yay!) +* This includes fixes released in version 3.0.5. + + +4.0.0-alpha1 (2015-02-25) +------------------------- + +* #41: Fixing bugs related to comparing URLs in `Request::getPath()`. +* #41: This library now uses the `sabre/uri` package for uri handling. +* Added `421 Misdirected Request` from the HTTP/2.0 spec. + + +3.0.5 (2015-05-11) +------------------ + +* #47 #35: When re-using the client and doing any request after a `HEAD` + request, the client discards the body. + + +3.0.4 (2014-12-10) +------------------ + +* #38: The Authentication helpers no longer overwrite any existing + `WWW-Authenticate` headers, but instead append new headers. This ensures + that multiple authentication systems can exist in the same environment. + + +3.0.3 (2014-12-03) +------------------ + +* Hiding `Authorization` header value from `Request::__toString`. + + +3.0.2 (2014-10-09) +------------------ + +* When parsing `Accept:` headers, we're ignoring invalid parts. Before we + would throw a PHP E_NOTICE. + + +3.0.1 (2014-09-29) +------------------ + +* Minor change in unittests. + + +3.0.0 (2014-09-23) +------------------ + +* `getHeaders()` now returns header values as an array, just like psr/http. +* Added `hasHeader()`. + + +2.1.0-alpha1 (2014-09-15) +------------------------- + +* Changed: Copied most of the header-semantics for the PSR draft for + representing HTTP messages. [Reference here][psr-http]. +* This means that `setHeaders()` does not wipe out every existing header + anymore. +* We also support multiple headers with the same name. +* Use `Request::getHeaderAsArray()` and `Response::getHeaderAsArray()` to + get a hold off multiple headers with the same name. +* If you use `getHeader()`, and there's more than 1 header with that name, we + concatenate all these with a comma. +* `addHeader()` will now preserve an existing header with that name, and add a + second header with the same name. +* The message class should be a lot faster now for looking up headers. No more + array traversal, because we maintain a tiny index. +* Added: `URLUtil::resolve()` to make resolving relative urls super easy. +* Switched to PSR-4. +* #12: Circumventing CURL's FOLLOW_LOCATION and doing it in PHP instead. This + fixes compatibility issues with people that have open_basedir turned on. +* Added: Content negotiation now correctly support mime-type parameters such as + charset. +* Changed: `Util::negotiate()` is now deprecated. Use + `Util::negotiateContentType()` instead. +* #14: The client now only follows http and https urls. + + +2.0.4 (2014-07-14) +------------------ + +* Changed: No longer escaping @ in urls when it's not needed. +* Fixed: #7: Client now correctly deals with responses without a body. + + +2.0.3 (2014-04-17) +------------------ + +* Now works on hhvm! +* Fixed: Now throwing an error when a Request object is being created with + arguments that were valid for sabre/http 1.0. Hopefully this will aid with + debugging for upgraders. + + +2.0.2 (2014-02-09) +------------------ + +* Fixed: Potential security problem in the client. + + +2.0.1 (2014-01-09) +------------------ + +* Fixed: getBodyAsString on an empty body now works. +* Fixed: Version string + + +2.0.0 (2014-01-08) +------------------ + +* Removed: Request::createFromPHPRequest. This is now handled by + Sapi::getRequest. + + +2.0.0alpha6 (2014-01-03) +------------------------ + +* Added: Asynchronous HTTP client. See examples/asyncclient.php. +* Fixed: Issue #4: Don't escape colon (:) when it's not needed. +* Fixed: Fixed a bug in the content negotation script. +* Fixed: Fallback for when CURLOPT_POSTREDIR is not defined (mainly for hhvm). +* Added: The Request and Response object now have a `__toString()` method that + serializes the objects into a standard HTTP message. This is mainly for + debugging purposes. +* Changed: Added Response::getStatusText(). This method returns the + human-readable HTTP status message. This part has been removed from + Response::getStatus(), which now always returns just the status code as an + int. +* Changed: Response::send() is now Sapi::sendResponse($response). +* Changed: Request::createFromPHPRequest is now Sapi::getRequest(). +* Changed: Message::getBodyAsStream and Message::getBodyAsString were added. The + existing Message::getBody changed it's behavior, so be careful. + + +2.0.0alpha5 (2013-11-07) +------------------------ + +* Added: HTTP Status 451 Unavailable For Legal Reasons. Fight government + censorship! +* Added: Ability to catch and retry http requests in the client when a curl + error occurs. +* Changed: Request::getPath does not return the query part of the url, so + everything after the ? is stripped. +* Added: a reverse proxy example. + + +2.0.0alpha4 (2013-08-07) +------------------------ + +* Fixed: Doing a GET request with the client uses the last used HTTP method + instead. +* Added: HttpException +* Added: The Client class can now automatically emit exceptions when HTTP errors + occurred. + + +2.0.0alpha3 (2013-07-24) +------------------------ + +* Changed: Now depends on sabre/event package. +* Changed: setHeaders() now overwrites any existing http headers. +* Added: getQueryParameters to RequestInterface. +* Added: Util::negotiate. +* Added: RequestDecorator, ResponseDecorator. +* Added: A very simple HTTP client. +* Added: addHeaders() to append a list of new headers. +* Fixed: Not erroring on unknown HTTP status codes. +* Fixed: Throwing exceptions on invalid HTTP status codes (not 3 digits). +* Fixed: Much better README.md +* Changed: getBody() now uses a bitfield to specify what type to return. + + +2.0.0alpha2 (2013-07-02) +------------------------ + +* Added: Digest & AWS Authentication. +* Added: Message::getHttpVersion and Message::setHttpVersion. +* Added: Request::setRawServerArray, getRawServerValue. +* Added: Request::createFromPHPRequest +* Added: Response::send +* Added: Request::getQueryParameters +* Added: Utility for dealing with HTTP dates. +* Added: Request::setPostData and Request::getPostData. +* Added: Request::setAbsoluteUrl and Request::getAbsoluteUrl. +* Added: URLUtil, methods for calculation relative and base urls. +* Removed: Response::sendBody + + +2.0.0alpha1 (2012-10-07) +------------------------ + +* Fixed: Lots of small naming improvements +* Added: Introduction of Message, MessageInterface, Response, ResponseInterface. + +Before 2.0.0, this package was built-into SabreDAV, where it first appeared in +January 2009. + +[psr-http]: https://github.com/php-fig/fig-standards/blob/master/proposed/http-message.md +[rfc-7240]: http://tools.ietf.org/html/rfc7240 diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/LICENSE b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/LICENSE new file mode 100644 index 0000000..19812ad --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/LICENSE @@ -0,0 +1,27 @@ +Copyright (C) 2009-2016 fruux GmbH (https://fruux.com/) + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name Sabre nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/README.md b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/README.md new file mode 100644 index 0000000..ae03a79 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/README.md @@ -0,0 +1,746 @@ +sabre/http +========== + +This library provides a toolkit to make working with the HTTP protocol easier. + +Most PHP scripts run within a HTTP request but accessing information about the +HTTP request is cumbersome at least. + +There's bad practices, inconsistencies and confusion. This library is +effectively a wrapper around the following PHP constructs: + +For Input: + +* `$_GET`, +* `$_POST`, +* `$_SERVER`, +* `php://input` or `$HTTP_RAW_POST_DATA`. + +For output: + +* `php://output` or `echo`, +* `header()`. + +What this library provides, is a `Request` object, and a `Response` object. + +The objects are extendable and easily mockable. + +Build status +------------ + +| branch | status | +| ------ | ------ | +| master | [![Build Status](https://travis-ci.org/fruux/sabre-http.svg?branch=master)](https://travis-ci.org/fruux/sabre-http) | +| 3.0 | [![Build Status](https://travis-ci.org/fruux/sabre-http.svg?branch=3.0)](https://travis-ci.org/fruux/sabre-http) | + +Installation +------------ + +Make sure you have [composer][1] installed. In your project directory, create, +or edit a `composer.json` file, and make sure it contains something like this: + +```json +{ + "require" : { + "sabre/http" : "~3.0.0" + } +} +``` + +After that, just hit `composer install` and you should be rolling. + +Quick history +------------- + +This library came to existence in 2009, as a part of the [`sabre/dav`][2] +project, which uses it heavily. + +It got split off into a separate library to make it easier to manage +releases and hopefully giving it use outside of the scope of just `sabre/dav`. + +Although completely independently developed, this library has a LOT of +overlap with [Symfony's `HttpFoundation`][3]. + +Said library does a lot more stuff and is significantly more popular, +so if you are looking for something to fulfill this particular requirement, +I'd recommend also considering [`HttpFoundation`][3]. + + +Getting started +--------------- + +First and foremost, this library wraps the superglobals. The easiest way to +instantiate a request object is as follows: + +```php +use Sabre\HTTP; + +include 'vendor/autoload.php'; + +$request = HTTP\Sapi::getRequest(); +``` + +This line should only happen once in your entire application. Everywhere else +you should pass this request object around using dependency injection. + +You should always typehint on it's interface: + +```php +function handleRequest(HTTP\RequestInterface $request) { + + // Do something with this request :) + +} +``` + +A response object you can just create as such: + +```php +use Sabre\HTTP; + +include 'vendor/autoload.php'; + +$response = new HTTP\Response(); +$response->setStatus(201); // created ! +$response->setHeader('X-Foo', 'bar'); +$response->setBody( + 'success!' +); + +``` + +After you fully constructed your response, you must call: + +```php +HTTP\Sapi::sendResponse($response); +``` + +This line should generally also appear once in your application (at the very +end). + +Decorators +---------- + +It may be useful to extend the `Request` and `Response` objects in your +application, if you for example would like them to carry a bit more +information about the current request. + +For instance, you may want to add an `isLoggedIn` method to the Request +object. + +Simply extending Request and Response may pose some problems: + +1. You may want to extend the objects with new behaviors differently, in + different subsystems of your application, +2. The `Sapi::getRequest` factory always returns a instance of + `Request` so you would have to override the factory method as well, +3. By controlling the instantation and depend on specific `Request` and + `Response` instances in your library or application, you make it harder to + work with other applications which also use `sabre/http`. + +In short: it would be bad design. Instead, it's recommended to use the +[decorator pattern][6] to add new behavior where you need it. `sabre/http` +provides helper classes to quickly do this. + +Example: + +```php +use Sabre\HTTP; + +class MyRequest extends HTTP\RequestDecorator { + + function isLoggedIn() { + + return true; + + } + +} +``` + +Our application assumes that the true `Request` object was instantiated +somewhere else, by some other subsystem. This could simply be a call like +`$request = Sapi::getRequest()` at the top of your application, +but could also be somewhere in a unittest. + +All we know in the current subsystem, is that we received a `$request` and +that it implements `Sabre\HTTP\RequestInterface`. To decorate this object, +all we need to do is: + +```php +$request = new MyRequest($request); +``` + +And that's it, we now have an `isLoggedIn` method, without having to mess +with the core instances. + + +Client +------ + +This package also contains a simple wrapper around [cURL][4], which will allow +you to write simple clients, using the `Request` and `Response` objects you're +already familiar with. + +It's by no means a replacement for something like [Guzzle][7], but it provides +a simple and lightweight API for making the occasional API call. + +### Usage + +```php +use Sabre\HTTP; + +$request = new HTTP\Request('GET', 'http://example.org/'); +$request->setHeader('X-Foo', 'Bar'); + +$client = new HTTP\Client(); +$response = $client->send($request); + +echo $response->getBodyAsString(); +``` + +The client emits 3 event using [`sabre/event`][5]. `beforeRequest`, +`afterRequest` and `error`. + +```php +$client = new HTTP\Client(); +$client->on('beforeRequest', function($request) { + + // You could use beforeRequest to for example inject a few extra headers. + // into the Request object. + +}); + +$client->on('afterRequest', function($request, $response) { + + // The afterRequest event could be a good time to do some logging, or + // do some rewriting in the response. + +}); + +$client->on('error', function($request, $response, &$retry, $retryCount) { + + // The error event is triggered for every response with a HTTP code higher + // than 399. + +}); + +$client->on('error:401', function($request, $response, &$retry, $retryCount) { + + // You can also listen for specific error codes. This example shows how + // to inject HTTP authentication headers if a 401 was returned. + + if ($retryCount > 1) { + // We're only going to retry exactly once. + } + + $request->setHeader('Authorization', 'Basic xxxxxxxxxx'); + $retry = true; + +}); +``` + +### Asynchronous requests + +The `Client` also supports doing asynchronous requests. This is especially handy +if you need to perform a number of requests, that are allowed to be executed +in parallel. + +The underlying system for this is simply [cURL's multi request handler][8], +but this provides a much nicer API to handle this. + +Sample usage: + +```php + +use Sabre\HTTP; + +$request = new Request('GET', 'http://localhost/'); +$client = new Client(); + +// Executing 1000 requests +for ($i = 0; $i < 1000; $i++) { + $client->sendAsync( + $request, + function(ResponseInterface $response) { + // Success handler + }, + function($error) { + // Error handler + } + ); +} + +// Wait for all requests to get a result. +$client->wait(); + +``` + +Check out `examples/asyncclient.php` for more information. + +Writing a reverse proxy +----------------------- + +With all these tools combined, it becomes very easy to write a simple reverse +http proxy. + +```php +use + Sabre\HTTP\Sapi, + Sabre\HTTP\Client; + +// The url we're proxying to. +$remoteUrl = 'http://example.org/'; + +// The url we're proxying from. Please note that this must be a relative url, +// and basically acts as the base url. +// +// If youre $remoteUrl doesn't end with a slash, this one probably shouldn't +// either. +$myBaseUrl = '/reverseproxy.php'; +// $myBaseUrl = '/~evert/sabre/http/examples/reverseproxy.php/'; + +$request = Sapi::getRequest(); +$request->setBaseUrl($myBaseUrl); + +$subRequest = clone $request; + +// Removing the Host header. +$subRequest->removeHeader('Host'); + +// Rewriting the url. +$subRequest->setUrl($remoteUrl . $request->getPath()); + +$client = new Client(); + +// Sends the HTTP request to the server +$response = $client->send($subRequest); + +// Sends the response back to the client that connected to the proxy. +Sapi::sendResponse($response); +``` + +The Request and Response API's +------------------------------ + +### Request + +```php + +/** + * Creates the request object + * + * @param string $method + * @param string $url + * @param array $headers + * @param resource $body + */ +public function __construct($method = null, $url = null, array $headers = null, $body = null); + +/** + * Returns the current HTTP method + * + * @return string + */ +function getMethod(); + +/** + * Sets the HTTP method + * + * @param string $method + * @return void + */ +function setMethod($method); + +/** + * Returns the request url. + * + * @return string + */ +function getUrl(); + +/** + * Sets the request url. + * + * @param string $url + * @return void + */ +function setUrl($url); + +/** + * Returns the absolute url. + * + * @return string + */ +function getAbsoluteUrl(); + +/** + * Sets the absolute url. + * + * @param string $url + * @return void + */ +function setAbsoluteUrl($url); + +/** + * Returns the current base url. + * + * @return string + */ +function getBaseUrl(); + +/** + * Sets a base url. + * + * This url is used for relative path calculations. + * + * The base url should default to / + * + * @param string $url + * @return void + */ +function setBaseUrl($url); + +/** + * Returns the relative path. + * + * This is being calculated using the base url. This path will not start + * with a slash, so it will always return something like + * 'example/path.html'. + * + * If the full path is equal to the base url, this method will return an + * empty string. + * + * This method will also urldecode the path, and if the url was incoded as + * ISO-8859-1, it will convert it to UTF-8. + * + * If the path is outside of the base url, a LogicException will be thrown. + * + * @return string + */ +function getPath(); + +/** + * Returns the list of query parameters. + * + * This is equivalent to PHP's $_GET superglobal. + * + * @return array + */ +function getQueryParameters(); + +/** + * Returns the POST data. + * + * This is equivalent to PHP's $_POST superglobal. + * + * @return array + */ +function getPostData(); + +/** + * Sets the post data. + * + * This is equivalent to PHP's $_POST superglobal. + * + * This would not have been needed, if POST data was accessible as + * php://input, but unfortunately we need to special case it. + * + * @param array $postData + * @return void + */ +function setPostData(array $postData); + +/** + * Returns an item from the _SERVER array. + * + * If the value does not exist in the array, null is returned. + * + * @param string $valueName + * @return string|null + */ +function getRawServerValue($valueName); + +/** + * Sets the _SERVER array. + * + * @param array $data + * @return void + */ +function setRawServerData(array $data); + +/** + * Returns the body as a readable stream resource. + * + * Note that the stream may not be rewindable, and therefore may only be + * read once. + * + * @return resource + */ +function getBodyAsStream(); + +/** + * Returns the body as a string. + * + * Note that because the underlying data may be based on a stream, this + * method could only work correctly the first time. + * + * @return string + */ +function getBodyAsString(); + +/** + * Returns the message body, as it's internal representation. + * + * This could be either a string or a stream. + * + * @return resource|string + */ +function getBody(); + +/** + * Updates the body resource with a new stream. + * + * @param resource $body + * @return void + */ +function setBody($body); + +/** + * Returns all the HTTP headers as an array. + * + * @return array + */ +function getHeaders(); + +/** + * Returns a specific HTTP header, based on it's name. + * + * The name must be treated as case-insensitive. + * + * If the header does not exist, this method must return null. + * + * @param string $name + * @return string|null + */ +function getHeader($name); + +/** + * Updates a HTTP header. + * + * The case-sensitity of the name value must be retained as-is. + * + * @param string $name + * @param string $value + * @return void + */ +function setHeader($name, $value); + +/** + * Resets HTTP headers + * + * This method overwrites all existing HTTP headers + * + * @param array $headers + * @return void + */ +function setHeaders(array $headers); + +/** + * Adds a new set of HTTP headers. + * + * Any header specified in the array that already exists will be + * overwritten, but any other existing headers will be retained. + * + * @param array $headers + * @return void + */ +function addHeaders(array $headers); + +/** + * Removes a HTTP header. + * + * The specified header name must be treated as case-insenstive. + * This method should return true if the header was successfully deleted, + * and false if the header did not exist. + * + * @return bool + */ +function removeHeader($name); + +/** + * Sets the HTTP version. + * + * Should be 1.0 or 1.1. + * + * @param string $version + * @return void + */ +function setHttpVersion($version); + +/** + * Returns the HTTP version. + * + * @return string + */ +function getHttpVersion(); +``` + +### Response + +```php +/** + * Returns the current HTTP status. + * + * This is the status-code as well as the human readable string. + * + * @return string + */ +function getStatus(); + +/** + * Sets the HTTP status code. + * + * This can be either the full HTTP status code with human readable string, + * for example: "403 I can't let you do that, Dave". + * + * Or just the code, in which case the appropriate default message will be + * added. + * + * @param string|int $status + * @throws \InvalidArgumentExeption + * @return void + */ +function setStatus($status); + +/** + * Returns the body as a readable stream resource. + * + * Note that the stream may not be rewindable, and therefore may only be + * read once. + * + * @return resource + */ +function getBodyAsStream(); + +/** + * Returns the body as a string. + * + * Note that because the underlying data may be based on a stream, this + * method could only work correctly the first time. + * + * @return string + */ +function getBodyAsString(); + +/** + * Returns the message body, as it's internal representation. + * + * This could be either a string or a stream. + * + * @return resource|string + */ +function getBody(); + + +/** + * Updates the body resource with a new stream. + * + * @param resource $body + * @return void + */ +function setBody($body); + +/** + * Returns all the HTTP headers as an array. + * + * @return array + */ +function getHeaders(); + +/** + * Returns a specific HTTP header, based on it's name. + * + * The name must be treated as case-insensitive. + * + * If the header does not exist, this method must return null. + * + * @param string $name + * @return string|null + */ +function getHeader($name); + +/** + * Updates a HTTP header. + * + * The case-sensitity of the name value must be retained as-is. + * + * @param string $name + * @param string $value + * @return void + */ +function setHeader($name, $value); + +/** + * Resets HTTP headers + * + * This method overwrites all existing HTTP headers + * + * @param array $headers + * @return void + */ +function setHeaders(array $headers); + +/** + * Adds a new set of HTTP headers. + * + * Any header specified in the array that already exists will be + * overwritten, but any other existing headers will be retained. + * + * @param array $headers + * @return void + */ +function addHeaders(array $headers); + +/** + * Removes a HTTP header. + * + * The specified header name must be treated as case-insenstive. + * This method should return true if the header was successfully deleted, + * and false if the header did not exist. + * + * @return bool + */ +function removeHeader($name); + +/** + * Sets the HTTP version. + * + * Should be 1.0 or 1.1. + * + * @param string $version + * @return void + */ +function setHttpVersion($version); + +/** + * Returns the HTTP version. + * + * @return string + */ +function getHttpVersion(); +``` + +Made at fruux +------------- + +This library is being developed by [fruux](https://fruux.com/). Drop us a line for commercial services or enterprise support. + +[1]: http://getcomposer.org/ +[2]: http://sabre.io/ +[3]: https://github.com/symfony/HttpFoundation +[4]: http://php.net/curl +[5]: https://github.com/fruux/sabre-event +[6]: http://en.wikipedia.org/wiki/Decorator_pattern +[7]: http://guzzlephp.org/ +[8]: http://php.net/curl_multi_init diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/bin/.empty b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/bin/.empty new file mode 100644 index 0000000..e69de29 diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/composer.json b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/composer.json new file mode 100644 index 0000000..b061194 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/composer.json @@ -0,0 +1,43 @@ +{ + "name": "sabre/http", + "description" : "The sabre/http library provides utilities for dealing with http requests and responses. ", + "keywords" : [ "HTTP" ], + "homepage" : "https://github.com/fruux/sabre-http", + "license" : "BSD-3-Clause", + "require" : { + "php" : ">=5.4", + "ext-mbstring" : "*", + "sabre/event" : ">=1.0.0,<4.0.0", + "sabre/uri" : "~1.0" + }, + "require-dev" : { + "phpunit/phpunit" : "~4.3", + "sabre/cs" : "~0.0.1" + }, + "suggest" : { + "ext-curl" : " to make http requests with the Client class" + }, + "authors" : [ + { + "name" : "Evert Pot", + "email" : "me@evertpot.com", + "homepage" : "http://evertpot.com/", + "role" : "Developer" + } + ], + "support" : { + "forum" : "https://groups.google.com/group/sabredav-discuss", + "source" : "https://github.com/fruux/sabre-http" + }, + "autoload" : { + "files" : [ + "lib/functions.php" + ], + "psr-4" : { + "Sabre\\HTTP\\" : "lib/" + } + }, + "config" : { + "bin-dir" : "bin/" + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/examples/asyncclient.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/examples/asyncclient.php new file mode 100644 index 0000000..4aa1a66 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/examples/asyncclient.php @@ -0,0 +1,65 @@ +<?php + +/** + * This example demonstrates the ability for clients to work asynchronously. + * + * By default up to 10 requests will be executed in paralel. HTTP connections + * are re-used and DNS is cached, all thanks to the power of curl. + * + * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +use Sabre\HTTP\Request; +use Sabre\HTTP\Client; + +// Find the autoloader +$paths = [ + __DIR__ . '/../vendor/autoload.php', + __DIR__ . '/../../../autoload.php', + __DIR__ . '/vendor/autoload.php', + +]; + +foreach ($paths as $path) { + if (file_exists($path)) { + include $path; + break; + } +} + +// This is the request we're repeating a 1000 times. +$request = new Request('GET', 'http://localhost/'); +$client = new Client(); + +for ($i = 0; $i < 1000; $i++) { + + echo "$i sending\n"; + $client->sendAsync( + $request, + + // This is the 'success' callback + function($response) use ($i) { + echo "$i -> " . $response->getStatus() . "\n"; + }, + + // This is the 'error' callback. It is called for general connection + // problems (such as not being able to connect to a host, dns errors, + // etc.) and also cases where a response was returned, but it had a + // status code of 400 or higher. + function($error) use ($i) { + + if ($error['status'] === Client::STATUS_CURLERROR) { + // Curl errors + echo "$i -> curl error: " . $error['curl_errmsg'] . "\n"; + } else { + // HTTP errors + echo "$i -> " . $error['response']->getStatus() . "\n"; + } + } + ); +} + +// After everything is done, we call 'wait'. This causes the client to wait for +// all outstanding http requests to complete. +$client->wait(); diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/examples/basicauth.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/examples/basicauth.php new file mode 100644 index 0000000..b872d6c --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/examples/basicauth.php @@ -0,0 +1,55 @@ +<?php + +/** + * This example shows how to do Basic authentication. + * * + * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +$userList = [ + "user1" => "password", + "user2" => "password", +]; + +use Sabre\HTTP\Sapi; +use Sabre\HTTP\Response; +use Sabre\HTTP\Auth; + +// Find the autoloader +$paths = [ + __DIR__ . '/../vendor/autoload.php', + __DIR__ . '/../../../autoload.php', + __DIR__ . '/vendor/autoload.php', + +]; + +foreach ($paths as $path) { + if (file_exists($path)) { + include $path; + break; + } +} + +$request = Sapi::getRequest(); +$response = new Response(); + +$basicAuth = new Auth\Basic("Locked down area", $request, $response); +if (!$userPass = $basicAuth->getCredentials()) { + + // No username or password given + $basicAuth->requireLogin(); + +} elseif (!isset($userList[$userPass[0]]) || $userList[$userPass[0]] !== $userPass[1]) { + + // Username or password are incorrect + $basicAuth->requireLogin(); +} else { + + // Success ! + $response->setBody('You are logged in!'); + +} + +// Sending the response +Sapi::sendResponse($response); diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/examples/client.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/examples/client.php new file mode 100644 index 0000000..bd77ad5 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/examples/client.php @@ -0,0 +1,38 @@ +<?php + +/** + * This example shows how to make a HTTP request with the Request and Response + * objects. + * + * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +use Sabre\HTTP\Request; +use Sabre\HTTP\Client; + +// Find the autoloader +$paths = [ + __DIR__ . '/../vendor/autoload.php', + __DIR__ . '/../../../autoload.php', + __DIR__ . '/vendor/autoload.php', + +]; + +foreach ($paths as $path) { + if (file_exists($path)) { + include $path; + break; + } +} + +// Constructing the request. +$request = new Request('GET', 'http://localhost/'); + +$client = new Client(); +//$client->addCurlSetting(CURLOPT_PROXY,'localhost:8888'); +$response = $client->send($request); + +echo "Response:\n"; + +echo (string)$response; diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/examples/digestauth.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/examples/digestauth.php new file mode 100644 index 0000000..bf929d9 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/examples/digestauth.php @@ -0,0 +1,56 @@ +<?php + +/** + * This example shows how to do Digest authentication. + * * + * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/). + * @author Markus Staab + * @license http://sabre.io/license/ Modified BSD License + */ +$userList = [ + "user1" => "password", + "user2" => "password", +]; + +use Sabre\HTTP\Sapi; +use Sabre\HTTP\Response; +use Sabre\HTTP\Auth; + +// Find the autoloader +$paths = [ + __DIR__ . '/../vendor/autoload.php', + __DIR__ . '/../../../autoload.php', + __DIR__ . '/vendor/autoload.php', + +]; + +foreach ($paths as $path) { + if (file_exists($path)) { + include $path; + break; + } +} + +$request = Sapi::getRequest(); +$response = new Response(); + +$digestAuth = new Auth\Digest("Locked down area", $request, $response); +$digestAuth->init(); +if (!$userName = $digestAuth->getUsername()) { + + // No username given + $digestAuth->requireLogin(); + +} elseif (!isset($userList[$userName]) || !$digestAuth->validatePassword($userList[$userName])) { + + // Username or password are incorrect + $digestAuth->requireLogin(); +} else { + + // Success ! + $response->setBody('You are logged in!'); + +} + +// Sending the response +Sapi::sendResponse($response); diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/examples/reverseproxy.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/examples/reverseproxy.php new file mode 100644 index 0000000..386bb00 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/examples/reverseproxy.php @@ -0,0 +1,50 @@ +<?php + +// The url we're proxying to. +$remoteUrl = 'http://example.org/'; + +// The url we're proxying from. Please note that this must be a relative url, +// and basically acts as the base url. +// +// If your $remoteUrl doesn't end with a slash, this one probably shouldn't +// either. +$myBaseUrl = '/reverseproxy.php'; +// $myBaseUrl = '/~evert/sabre/http/examples/reverseproxy.php/'; + +use Sabre\HTTP\Sapi; +use Sabre\HTTP\Client; + +// Find the autoloader +$paths = [ + __DIR__ . '/../vendor/autoload.php', + __DIR__ . '/../../../autoload.php', + __DIR__ . '/vendor/autoload.php', + +]; + +foreach ($paths as $path) { + if (file_exists($path)) { + include $path; + break; + } +} + + +$request = Sapi::getRequest(); +$request->setBaseUrl($myBaseUrl); + +$subRequest = clone $request; + +// Removing the Host header. +$subRequest->removeHeader('Host'); + +// Rewriting the url. +$subRequest->setUrl($remoteUrl . $request->getPath()); + +$client = new Client(); + +// Sends the HTTP request to the server +$response = $client->send($subRequest); + +// Sends the response back to the client that connected to the proxy. +Sapi::sendResponse($response); diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/examples/stringify.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/examples/stringify.php new file mode 100644 index 0000000..9f56201 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/examples/stringify.php @@ -0,0 +1,51 @@ +<?php + +/** + * This simple example shows the capability of Request and Response objects to + * serialize themselves as strings. + * + * This is mainly useful for debugging purposes. + * + * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +use Sabre\HTTP\Request; +use Sabre\HTTP\Response; + +// Find the autoloader +$paths = [ + __DIR__ . '/../vendor/autoload.php', + __DIR__ . '/../../../autoload.php', + __DIR__ . '/vendor/autoload.php', + +]; +foreach ($paths as $path) { + if (file_exists($path)) { + include $path; + break; + } +} + +$request = new Request('POST', '/foo'); +$request->setHeaders([ + 'Host' => 'example.org', + 'Content-Type' => 'application/json' + ]); + +$request->setBody(json_encode(['foo' => 'bar'])); + +echo $request; +echo "\r\n\r\n"; + +$response = new Response(424); +$response->setHeaders([ + 'Content-Type' => 'text/plain', + 'Connection' => 'close', + ]); + +$response->setBody("ABORT! ABORT!"); + +echo $response; + +echo "\r\n"; diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Auth/AWS.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Auth/AWS.php new file mode 100644 index 0000000..5e17664 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Auth/AWS.php @@ -0,0 +1,234 @@ +<?php + +namespace Sabre\HTTP\Auth; + +use Sabre\HTTP\Util; + +/** + * HTTP AWS Authentication handler + * + * Use this class to leverage amazon's AWS authentication header + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class AWS extends AbstractAuth { + + /** + * The signature supplied by the HTTP client + * + * @var string + */ + private $signature = null; + + /** + * The accesskey supplied by the HTTP client + * + * @var string + */ + private $accessKey = null; + + /** + * An error code, if any + * + * This value will be filled with one of the ERR_* constants + * + * @var int + */ + public $errorCode = 0; + + const ERR_NOAWSHEADER = 1; + const ERR_MD5CHECKSUMWRONG = 2; + const ERR_INVALIDDATEFORMAT = 3; + const ERR_REQUESTTIMESKEWED = 4; + const ERR_INVALIDSIGNATURE = 5; + + /** + * Gathers all information from the headers + * + * This method needs to be called prior to anything else. + * + * @return bool + */ + function init() { + + $authHeader = $this->request->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 + */ + function getAccessKey() { + + return $this->accessKey; + + } + + /** + * Validates the signature based on the secretKey + * + * @param string $secretKey + * @return bool + */ + function validate($secretKey) { + + $contentMD5 = $this->request->getHeader('Content-MD5'); + + if ($contentMD5) { + // We need to validate the integrity of the request + $body = $this->request->getBody(); + $this->request->setBody($body); + + 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->request->getHeader('x-amz-date')) + $requestDate = $this->request->getHeader('Date'); + + if (!$this->validateRFC2616Date($requestDate)) + return false; + + $amzHeaders = $this->getAmzHeaders(); + + $signature = base64_encode( + $this->hmacsha1($secretKey, + $this->request->getMethod() . "\n" . + $contentMD5 . "\n" . + $this->request->getHeader('Content-type') . "\n" . + $requestDate . "\n" . + $amzHeaders . + $this->request->getUrl() + ) + ); + + 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 + */ + function requireLogin() { + + $this->response->addHeader('WWW-Authenticate', 'AWS'); + $this->response->setStatus(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 = []; + $headers = $this->request->getHeaders(); + foreach ($headers as $headerName => $headerValue) { + if (strpos(strtolower($headerName), 'x-amz-') === 0) { + $amzHeaders[strtolower($headerName)] = str_replace(["\r\n"], [' '], $headerValue[0]) . "\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) { + + if (function_exists('hash_hmac')) { + return hash_hmac('sha1', $message, $key, true); + } + + $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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Auth/AbstractAuth.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Auth/AbstractAuth.php new file mode 100644 index 0000000..ae45b3e --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Auth/AbstractAuth.php @@ -0,0 +1,73 @@ +<?php + +namespace Sabre\HTTP\Auth; + +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; + +/** + * HTTP Authentication base class. + * + * This class provides some common functionality for the various base classes. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +abstract class AbstractAuth { + + /** + * Authentication realm + * + * @var string + */ + protected $realm; + + /** + * Request object + * + * @var RequestInterface + */ + protected $request; + + /** + * Response object + * + * @var ResponseInterface + */ + protected $response; + + /** + * Creates the object + * + * @param string $realm + * @return void + */ + function __construct($realm = 'SabreTooth', RequestInterface $request, ResponseInterface $response) { + + $this->realm = $realm; + $this->request = $request; + $this->response = $response; + + } + + /** + * This method sends the needed HTTP header and statuscode (401) to force + * the user to login. + * + * @return void + */ + abstract function requireLogin(); + + /** + * Returns the HTTP realm + * + * @return string + */ + function getRealm() { + + return $this->realm; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Auth/Basic.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Auth/Basic.php new file mode 100644 index 0000000..60633b9 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Auth/Basic.php @@ -0,0 +1,63 @@ +<?php + +namespace Sabre\HTTP\Auth; + +/** + * HTTP Basic authentication utility. + * + * This class helps you setup basic auth. The process is fairly simple: + * + * 1. Instantiate the class. + * 2. Call getCredentials (this will return null or a user/pass pair) + * 3. If you didn't get valid credentials, call 'requireLogin' + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Basic extends AbstractAuth { + + /** + * This method returns a numeric array with a username and password as the + * only elements. + * + * If no credentials were found, this method returns null. + * + * @return null|array + */ + function getCredentials() { + + $auth = $this->request->getHeader('Authorization'); + + if (!$auth) { + return null; + } + + if (strtolower(substr($auth, 0, 6)) !== 'basic ') { + return null; + } + + $credentials = explode(':', base64_decode(substr($auth, 6)), 2); + + if (2 !== count($credentials)) { + return null; + } + + return $credentials; + + } + + /** + * This method sends the needed HTTP header and statuscode (401) to force + * the user to login. + * + * @return void + */ + function requireLogin() { + + $this->response->addHeader('WWW-Authenticate', 'Basic realm="' . $this->realm . '"'); + $this->response->setStatus(401); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Auth/Bearer.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Auth/Bearer.php new file mode 100644 index 0000000..eefdf11 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Auth/Bearer.php @@ -0,0 +1,56 @@ +<?php + +namespace Sabre\HTTP\Auth; + +/** + * HTTP Bearer authentication utility. + * + * This class helps you setup bearer auth. The process is fairly simple: + * + * 1. Instantiate the class. + * 2. Call getToken (this will return null or a token as string) + * 3. If you didn't get a valid token, call 'requireLogin' + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author François Kooman (fkooman@tuxed.net) + * @license http://sabre.io/license/ Modified BSD License + */ +class Bearer extends AbstractAuth { + + /** + * This method returns a string with an access token. + * + * If no token was found, this method returns null. + * + * @return null|string + */ + function getToken() { + + $auth = $this->request->getHeader('Authorization'); + + if (!$auth) { + return null; + } + + if (strtolower(substr($auth, 0, 7)) !== 'bearer ') { + return null; + } + + return substr($auth, 7); + + } + + /** + * This method sends the needed HTTP header and statuscode (401) to force + * authentication. + * + * @return void + */ + function requireLogin() { + + $this->response->addHeader('WWW-Authenticate', 'Bearer realm="' . $this->realm . '"'); + $this->response->setStatus(401); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Auth/Digest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Auth/Digest.php new file mode 100644 index 0000000..4b3f074 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Auth/Digest.php @@ -0,0 +1,231 @@ +<?php + +namespace Sabre\HTTP\Auth; + +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; + +/** + * HTTP Digest Authentication handler + * + * Use this class for easy http digest authentication. + * Instructions: + * + * 1. Create the object + * 2. Call the setRealm() method with the realm you plan to use + * 3. Call the init method function. + * 4. Call the getUserName() function. This function may return null if no + * authentication information was supplied. Based on the username you + * should check your internal database for either the associated password, + * or the so-called A1 hash of the digest. + * 5. Call either validatePassword() or validateA1(). This will return true + * or false. + * 6. To make sure an authentication prompt is displayed, call the + * requireLogin() method. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Digest extends AbstractAuth { + + /** + * These constants are used in setQOP(); + */ + const QOP_AUTH = 1; + const QOP_AUTHINT = 2; + + protected $nonce; + protected $opaque; + protected $digestParts; + protected $A1; + protected $qop = self::QOP_AUTH; + + /** + * Initializes the object + */ + function __construct($realm = 'SabreTooth', RequestInterface $request, ResponseInterface $response) { + + $this->nonce = uniqid(); + $this->opaque = md5($realm); + parent::__construct($realm, $request, $response); + + } + + /** + * Gathers all information from the headers + * + * This method needs to be called prior to anything else. + * + * @return void + */ + 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 + */ + function setQOP($qop) { + + $this->qop = $qop; + + } + + /** + * Validates the user. + * + * The A1 parameter should be md5($username . ':' . $realm . ':' . $password); + * + * @param string $A1 + * @return bool + */ + 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 + */ + function validatePassword($password) { + + $this->A1 = md5($this->digestParts['username'] . ':' . $this->realm . ':' . $password); + return $this->validate(); + + } + + /** + * Returns the username for the request + * + * @return string + */ + function getUsername() { + + return $this->digestParts['username']; + + } + + /** + * Validates the digest challenge + * + * @return bool + */ + protected function validate() { + + $A2 = $this->request->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->request->getBody($asString = true); + $this->request->setBody($body); + $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 + */ + 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->response->addHeader('WWW-Authenticate', 'Digest realm="' . $this->realm . '",qop="' . $qop . '",nonce="' . $this->nonce . '",opaque="' . $this->opaque . '"'); + $this->response->setStatus(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 + */ + function getDigest() { + + return $this->request->getHeader('Authorization'); + + } + + + /** + * 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 = ['nonce' => 1, 'nc' => 1, 'cnonce' => 1, 'qop' => 1, 'username' => 1, 'uri' => 1, 'response' => 1]; + $data = []; + + 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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Client.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Client.php new file mode 100644 index 0000000..0810c4a --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Client.php @@ -0,0 +1,601 @@ +<?php + +namespace Sabre\HTTP; + +use Sabre\Event\EventEmitter; +use Sabre\Uri; + +/** + * A rudimentary HTTP client. + * + * This object wraps PHP's curl extension and provides an easy way to send it a + * Request object, and return a Response object. + * + * This is by no means intended as the next best HTTP client, but it does the + * job and provides a simple integration with the rest of sabre/http. + * + * This client emits the following events: + * beforeRequest(RequestInterface $request) + * afterRequest(RequestInterface $request, ResponseInterface $response) + * error(RequestInterface $request, ResponseInterface $response, bool &$retry, int $retryCount) + * exception(RequestInterface $request, ClientException $e, bool &$retry, int $retryCount) + * + * The beforeRequest event allows you to do some last minute changes to the + * request before it's done, such as adding authentication headers. + * + * The afterRequest event will be emitted after the request is completed + * succesfully. + * + * If a HTTP error is returned (status code higher than 399) the error event is + * triggered. It's possible using this event to retry the request, by setting + * retry to true. + * + * The amount of times a request has retried is passed as $retryCount, which + * can be used to avoid retrying indefinitely. The first time the event is + * called, this will be 0. + * + * It's also possible to intercept specific http errors, by subscribing to for + * example 'error:401'. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Client extends EventEmitter { + + /** + * List of curl settings + * + * @var array + */ + protected $curlSettings = []; + + /** + * Wether or not exceptions should be thrown when a HTTP error is returned. + * + * @var bool + */ + protected $throwExceptions = false; + + /** + * The maximum number of times we'll follow a redirect. + * + * @var int + */ + protected $maxRedirects = 5; + + /** + * Initializes the client. + * + * @return void + */ + function __construct() { + + $this->curlSettings = [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HEADER => true, + CURLOPT_NOBODY => false, + CURLOPT_USERAGENT => 'sabre-http/' . Version::VERSION . ' (http://sabre.io/)', + ]; + + } + + /** + * Sends a request to a HTTP server, and returns a response. + * + * @param RequestInterface $request + * @return ResponseInterface + */ + function send(RequestInterface $request) { + + $this->emit('beforeRequest', [$request]); + + $retryCount = 0; + $redirects = 0; + + do { + + $doRedirect = false; + $retry = false; + + try { + + $response = $this->doRequest($request); + + $code = (int)$response->getStatus(); + + // We are doing in-PHP redirects, because curl's + // FOLLOW_LOCATION throws errors when PHP is configured with + // open_basedir. + // + // https://github.com/fruux/sabre-http/issues/12 + if (in_array($code, [301, 302, 307, 308]) && $redirects < $this->maxRedirects) { + + $oldLocation = $request->getUrl(); + + // Creating a new instance of the request object. + $request = clone $request; + + // Setting the new location + $request->setUrl(Uri\resolve( + $oldLocation, + $response->getHeader('Location') + )); + + $doRedirect = true; + $redirects++; + + } + + // This was a HTTP error + if ($code >= 400) { + + $this->emit('error', [$request, $response, &$retry, $retryCount]); + $this->emit('error:' . $code, [$request, $response, &$retry, $retryCount]); + + } + + } catch (ClientException $e) { + + $this->emit('exception', [$request, $e, &$retry, $retryCount]); + + // If retry was still set to false, it means no event handler + // dealt with the problem. In this case we just re-throw the + // exception. + if (!$retry) { + throw $e; + } + + } + + if ($retry) { + $retryCount++; + } + + } while ($retry || $doRedirect); + + $this->emit('afterRequest', [$request, $response]); + + if ($this->throwExceptions && $code >= 400) { + throw new ClientHttpException($response); + } + + return $response; + + } + + /** + * Sends a HTTP request asynchronously. + * + * Due to the nature of PHP, you must from time to time poll to see if any + * new responses came in. + * + * After calling sendAsync, you must therefore occasionally call the poll() + * method, or wait(). + * + * @param RequestInterface $request + * @param callable $success + * @param callable $error + * @return void + */ + function sendAsync(RequestInterface $request, callable $success = null, callable $error = null) { + + $this->emit('beforeRequest', [$request]); + $this->sendAsyncInternal($request, $success, $error); + $this->poll(); + + } + + + /** + * This method checks if any http requests have gotten results, and if so, + * call the appropriate success or error handlers. + * + * This method will return true if there are still requests waiting to + * return, and false if all the work is done. + * + * @return bool + */ + function poll() { + + // nothing to do? + if (!$this->curlMultiMap) { + return false; + } + + do { + $r = curl_multi_exec( + $this->curlMultiHandle, + $stillRunning + ); + } while ($r === CURLM_CALL_MULTI_PERFORM); + + do { + + messageQueue: + + $status = curl_multi_info_read( + $this->curlMultiHandle, + $messagesInQueue + ); + + if ($status && $status['msg'] === CURLMSG_DONE) { + + $resourceId = intval($status['handle']); + list( + $request, + $successCallback, + $errorCallback, + $retryCount, + ) = $this->curlMultiMap[$resourceId]; + unset($this->curlMultiMap[$resourceId]); + $curlResult = $this->parseCurlResult(curl_multi_getcontent($status['handle']), $status['handle']); + $retry = false; + + if ($curlResult['status'] === self::STATUS_CURLERROR) { + + $e = new ClientException($curlResult['curl_errmsg'], $curlResult['curl_errno']); + $this->emit('exception', [$request, $e, &$retry, $retryCount]); + + if ($retry) { + $retryCount++; + $this->sendAsyncInternal($request, $successCallback, $errorCallback, $retryCount); + goto messageQueue; + } + + $curlResult['request'] = $request; + + if ($errorCallback) { + $errorCallback($curlResult); + } + + } elseif ($curlResult['status'] === self::STATUS_HTTPERROR) { + + $this->emit('error', [$request, $curlResult['response'], &$retry, $retryCount]); + $this->emit('error:' . $curlResult['http_code'], [$request, $curlResult['response'], &$retry, $retryCount]); + + if ($retry) { + + $retryCount++; + $this->sendAsyncInternal($request, $successCallback, $errorCallback, $retryCount); + goto messageQueue; + + } + + $curlResult['request'] = $request; + + if ($errorCallback) { + $errorCallback($curlResult); + } + + } else { + + $this->emit('afterRequest', [$request, $curlResult['response']]); + + if ($successCallback) { + $successCallback($curlResult['response']); + } + + } + } + + } while ($messagesInQueue > 0); + + return count($this->curlMultiMap) > 0; + + } + + /** + * Processes every HTTP request in the queue, and waits till they are all + * completed. + * + * @return void + */ + function wait() { + + do { + curl_multi_select($this->curlMultiHandle); + $stillRunning = $this->poll(); + } while ($stillRunning); + + } + + /** + * If this is set to true, the Client will automatically throw exceptions + * upon HTTP errors. + * + * This means that if a response came back with a status code greater than + * or equal to 400, we will throw a ClientHttpException. + * + * This only works for the send() method. Throwing exceptions for + * sendAsync() is not supported. + * + * @param bool $throwExceptions + * @return void + */ + function setThrowExceptions($throwExceptions) { + + $this->throwExceptions = $throwExceptions; + + } + + /** + * Adds a CURL setting. + * + * These settings will be included in every HTTP request. + * + * @param int $name + * @param mixed $value + * @return void + */ + function addCurlSetting($name, $value) { + + $this->curlSettings[$name] = $value; + + } + + /** + * This method is responsible for performing a single request. + * + * @param RequestInterface $request + * @return ResponseInterface + */ + protected function doRequest(RequestInterface $request) { + + $settings = $this->createCurlSettingsArray($request); + + if (!$this->curlHandle) { + $this->curlHandle = curl_init(); + } + + curl_setopt_array($this->curlHandle, $settings); + $response = $this->curlExec($this->curlHandle); + $response = $this->parseCurlResult($response, $this->curlHandle); + + if ($response['status'] === self::STATUS_CURLERROR) { + throw new ClientException($response['curl_errmsg'], $response['curl_errno']); + } + + return $response['response']; + + } + + /** + * Cached curl handle. + * + * By keeping this resource around for the lifetime of this object, things + * like persistent connections are possible. + * + * @var resource + */ + private $curlHandle; + + /** + * Handler for curl_multi requests. + * + * The first time sendAsync is used, this will be created. + * + * @var resource + */ + private $curlMultiHandle; + + /** + * Has a list of curl handles, as well as their associated success and + * error callbacks. + * + * @var array + */ + private $curlMultiMap = []; + + /** + * Turns a RequestInterface object into an array with settings that can be + * fed to curl_setopt + * + * @param RequestInterface $request + * @return array + */ + protected function createCurlSettingsArray(RequestInterface $request) { + + $settings = $this->curlSettings; + + switch ($request->getMethod()) { + case 'HEAD' : + $settings[CURLOPT_NOBODY] = true; + $settings[CURLOPT_CUSTOMREQUEST] = 'HEAD'; + $settings[CURLOPT_POSTFIELDS] = ''; + $settings[CURLOPT_PUT] = false; + break; + case 'GET' : + $settings[CURLOPT_CUSTOMREQUEST] = 'GET'; + $settings[CURLOPT_POSTFIELDS] = ''; + $settings[CURLOPT_PUT] = false; + break; + default : + $body = $request->getBody(); + if (is_resource($body)) { + // This needs to be set to PUT, regardless of the actual + // method used. Without it, INFILE will be ignored for some + // reason. + $settings[CURLOPT_PUT] = true; + $settings[CURLOPT_INFILE] = $request->getBody(); + } else { + // 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. + $settings[CURLOPT_POSTFIELDS] = (string)$body; + } + $settings[CURLOPT_CUSTOMREQUEST] = $request->getMethod(); + break; + + } + + $nHeaders = []; + foreach ($request->getHeaders() as $key => $values) { + + foreach ($values as $value) { + $nHeaders[] = $key . ': ' . $value; + } + + } + $settings[CURLOPT_HTTPHEADER] = $nHeaders; + $settings[CURLOPT_URL] = $request->getUrl(); + // FIXME: CURLOPT_PROTOCOLS is currently unsupported by HHVM + if (defined('CURLOPT_PROTOCOLS')) { + $settings[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; + } + // FIXME: CURLOPT_REDIR_PROTOCOLS is currently unsupported by HHVM + if (defined('CURLOPT_REDIR_PROTOCOLS')) { + $settings[CURLOPT_REDIR_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; + } + + return $settings; + + } + + const STATUS_SUCCESS = 0; + const STATUS_CURLERROR = 1; + const STATUS_HTTPERROR = 2; + + /** + * Parses the result of a curl call in a format that's a bit more + * convenient to work with. + * + * The method returns an array with the following elements: + * * status - one of the 3 STATUS constants. + * * curl_errno - A curl error number. Only set if status is + * STATUS_CURLERROR. + * * curl_errmsg - A current error message. Only set if status is + * STATUS_CURLERROR. + * * response - Response object. Only set if status is STATUS_SUCCESS, or + * STATUS_HTTPERROR. + * * http_code - HTTP status code, as an int. Only set if Only set if + * status is STATUS_SUCCESS, or STATUS_HTTPERROR + * + * @param string $response + * @param resource $curlHandle + * @return Response + */ + protected function parseCurlResult($response, $curlHandle) { + + list( + $curlInfo, + $curlErrNo, + $curlErrMsg + ) = $this->curlStuff($curlHandle); + + if ($curlErrNo) { + return [ + 'status' => self::STATUS_CURLERROR, + 'curl_errno' => $curlErrNo, + 'curl_errmsg' => $curlErrMsg, + ]; + } + + $headerBlob = substr($response, 0, $curlInfo['header_size']); + // In the case of 204 No Content, strlen($response) == $curlInfo['header_size]. + // This will cause substr($response, $curlInfo['header_size']) return FALSE instead of NULL + // An exception will be thrown when calling getBodyAsString then + $responseBody = substr($response, $curlInfo['header_size']) ?: null; + + unset($response); + + // 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); + + $response = new Response(); + $response->setStatus($curlInfo['http_code']); + + foreach ($headerBlob as $header) { + $parts = explode(':', $header, 2); + if (count($parts) == 2) { + $response->addHeader(trim($parts[0]), trim($parts[1])); + } + } + + $response->setBody($responseBody); + + $httpCode = intval($response->getStatus()); + + return [ + 'status' => $httpCode >= 400 ? self::STATUS_HTTPERROR : self::STATUS_SUCCESS, + 'response' => $response, + 'http_code' => $httpCode, + ]; + + } + + /** + * Sends an asynchronous HTTP request. + * + * We keep this in a separate method, so we can call it without triggering + * the beforeRequest event and don't do the poll(). + * + * @param RequestInterface $request + * @param callable $success + * @param callable $error + * @param int $retryCount + */ + protected function sendAsyncInternal(RequestInterface $request, callable $success, callable $error, $retryCount = 0) { + + if (!$this->curlMultiHandle) { + $this->curlMultiHandle = curl_multi_init(); + } + $curl = curl_init(); + curl_setopt_array( + $curl, + $this->createCurlSettingsArray($request) + ); + curl_multi_add_handle($this->curlMultiHandle, $curl); + $this->curlMultiMap[intval($curl)] = [ + $request, + $success, + $error, + $retryCount + ]; + + } + + // @codeCoverageIgnoreStart + + /** + * Calls curl_exec + * + * This method exists so it can easily be overridden and mocked. + * + * @param resource $curlHandle + * @return string + */ + protected function curlExec($curlHandle) { + + return curl_exec($curlHandle); + + } + + /** + * Returns a bunch of information about a curl request. + * + * This method exists so it can easily be overridden and mocked. + * + * @param resource $curlHandle + * @return array + */ + protected function curlStuff($curlHandle) { + + return [ + curl_getinfo($curlHandle), + curl_errno($curlHandle), + curl_error($curlHandle), + ]; + + } + // @codeCoverageIgnoreEnd + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/ClientException.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/ClientException.php new file mode 100644 index 0000000..69631f4 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/ClientException.php @@ -0,0 +1,15 @@ +<?php + +namespace Sabre\HTTP; + +/** + * This exception may be emitted by the HTTP\Client class, in case there was a + * problem emitting the request. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class ClientException extends \Exception { + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/ClientHttpException.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/ClientHttpException.php new file mode 100644 index 0000000..2923ef3 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/ClientHttpException.php @@ -0,0 +1,58 @@ +<?php + +namespace Sabre\HTTP; + +/** + * This exception represents a HTTP error coming from the Client. + * + * By default the Client will not emit these, this has to be explicitly enabled + * with the setThrowExceptions method. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class ClientHttpException extends \Exception implements HttpException { + + /** + * Response object + * + * @var ResponseInterface + */ + protected $response; + + /** + * Constructor + * + * @param ResponseInterface $response + */ + function __construct(ResponseInterface $response) { + + $this->response = $response; + parent::__construct($response->getStatusText(), $response->getStatus()); + + } + + /** + * The http status code for the error. + * + * @return int + */ + function getHttpStatus() { + + return $this->response->getStatus(); + + } + + /** + * Returns the full response object. + * + * @return ResponseInterface + */ + function getResponse() { + + return $this->response; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/HttpException.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/HttpException.php new file mode 100644 index 0000000..1303dec --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/HttpException.php @@ -0,0 +1,30 @@ +<?php + +namespace Sabre\HTTP; + +/** + * An exception representing a HTTP error. + * + * This can be used as a generic exception in your application, if you'd like + * to map HTTP errors to exceptions. + * + * If you'd like to use this, create a new exception class, extending Exception + * and implementing this interface. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +interface HttpException { + + /** + * The http status code for the error. + * + * This may either be just the number, or a number and a human-readable + * message, separated by a space. + * + * @return string|null + */ + function getHttpStatus(); + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Message.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Message.php new file mode 100644 index 0000000..5c6887d --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Message.php @@ -0,0 +1,314 @@ +<?php + +namespace Sabre\HTTP; + +/** + * This is the abstract base class for both the Request and Response objects. + * + * This object contains a few simple methods that are shared by both. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +abstract class Message implements MessageInterface { + + /** + * Request body + * + * This should be a stream resource + * + * @var resource + */ + protected $body; + + /** + * Contains the list of HTTP headers + * + * @var array + */ + protected $headers = []; + + /** + * HTTP message version (1.0 or 1.1) + * + * @var string + */ + protected $httpVersion = '1.1'; + + /** + * Returns the body as a readable stream resource. + * + * Note that the stream may not be rewindable, and therefore may only be + * read once. + * + * @return resource + */ + function getBodyAsStream() { + + $body = $this->getBody(); + if (is_string($body) || is_null($body)) { + $stream = fopen('php://temp', 'r+'); + fwrite($stream, $body); + rewind($stream); + return $stream; + } + return $body; + + } + + /** + * Returns the body as a string. + * + * Note that because the underlying data may be based on a stream, this + * method could only work correctly the first time. + * + * @return string + */ + function getBodyAsString() { + + $body = $this->getBody(); + if (is_string($body)) { + return $body; + } + if (is_null($body)) { + return ''; + } + $contentLength = $this->getHeader('Content-Length'); + if (null === $contentLength) { + return stream_get_contents($body); + } else { + return stream_get_contents($body, $contentLength); + } + + } + + /** + * Returns the message body, as it's internal representation. + * + * This could be either a string or a stream. + * + * @return resource|string + */ + function getBody() { + + return $this->body; + + } + + /** + * Replaces the body resource with a new stream or string. + * + * @param resource|string $body + */ + function setBody($body) { + + $this->body = $body; + + } + + /** + * Returns all the HTTP headers as an array. + * + * Every header is returned as an array, with one or more values. + * + * @return array + */ + function getHeaders() { + + $result = []; + foreach ($this->headers as $headerInfo) { + $result[$headerInfo[0]] = $headerInfo[1]; + } + return $result; + + } + + /** + * Will return true or false, depending on if a HTTP header exists. + * + * @param string $name + * @return bool + */ + function hasHeader($name) { + + return isset($this->headers[strtolower($name)]); + + } + + /** + * Returns a specific HTTP header, based on it's name. + * + * The name must be treated as case-insensitive. + * If the header does not exist, this method must return null. + * + * If a header appeared more than once in a HTTP request, this method will + * concatenate all the values with a comma. + * + * Note that this not make sense for all headers. Some, such as + * `Set-Cookie` cannot be logically combined with a comma. In those cases + * you *should* use getHeaderAsArray(). + * + * @param string $name + * @return string|null + */ + function getHeader($name) { + + $name = strtolower($name); + + if (isset($this->headers[$name])) { + return implode(',', $this->headers[$name][1]); + } + return null; + + } + + /** + * Returns a HTTP header as an array. + * + * For every time the HTTP header appeared in the request or response, an + * item will appear in the array. + * + * If the header did not exists, this method will return an empty array. + * + * @param string $name + * @return string[] + */ + function getHeaderAsArray($name) { + + $name = strtolower($name); + + if (isset($this->headers[$name])) { + return $this->headers[$name][1]; + } + + return []; + + } + + /** + * Updates a HTTP header. + * + * The case-sensitity of the name value must be retained as-is. + * + * If the header already existed, it will be overwritten. + * + * @param string $name + * @param string|string[] $value + * @return void + */ + function setHeader($name, $value) { + + $this->headers[strtolower($name)] = [$name, (array)$value]; + + } + + /** + * Sets a new set of HTTP headers. + * + * The headers array should contain headernames for keys, and their value + * should be specified as either a string or an array. + * + * Any header that already existed will be overwritten. + * + * @param array $headers + * @return void + */ + function setHeaders(array $headers) { + + foreach ($headers as $name => $value) { + $this->setHeader($name, $value); + } + + } + + /** + * Adds a HTTP header. + * + * This method will not overwrite any existing HTTP header, but instead add + * another value. Individual values can be retrieved with + * getHeadersAsArray. + * + * @param string $name + * @param string $value + * @return void + */ + function addHeader($name, $value) { + + $lName = strtolower($name); + if (isset($this->headers[$lName])) { + $this->headers[$lName][1] = array_merge( + $this->headers[$lName][1], + (array)$value + ); + } else { + $this->headers[$lName] = [ + $name, + (array)$value + ]; + } + + } + + /** + * Adds a new set of HTTP headers. + * + * Any existing headers will not be overwritten. + * + * @param array $headers + * @return void + */ + function addHeaders(array $headers) { + + foreach ($headers as $name => $value) { + $this->addHeader($name, $value); + } + + } + + + /** + * Removes a HTTP header. + * + * The specified header name must be treated as case-insenstive. + * This method should return true if the header was successfully deleted, + * and false if the header did not exist. + * + * @return bool + */ + function removeHeader($name) { + + $name = strtolower($name); + if (!isset($this->headers[$name])) { + return false; + } + unset($this->headers[$name]); + return true; + + } + + /** + * Sets the HTTP version. + * + * Should be 1.0 or 1.1. + * + * @param string $version + * @return void + */ + function setHttpVersion($version) { + + $this->httpVersion = $version; + + } + + /** + * Returns the HTTP version. + * + * @return string + */ + function getHttpVersion() { + + return $this->httpVersion; + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/MessageDecoratorTrait.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/MessageDecoratorTrait.php new file mode 100644 index 0000000..f104af3 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/MessageDecoratorTrait.php @@ -0,0 +1,250 @@ +<?php + +namespace Sabre\HTTP; + +/** + * This trait contains a bunch of methods, shared by both the RequestDecorator + * and the ResponseDecorator. + * + * Didn't seem needed to create a full class for this, so we're just + * implementing it as a trait. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +trait MessageDecoratorTrait { + + /** + * The inner request object. + * + * All method calls will be forwarded here. + * + * @var MessageInterface + */ + protected $inner; + + /** + * Returns the body as a readable stream resource. + * + * Note that the stream may not be rewindable, and therefore may only be + * read once. + * + * @return resource + */ + function getBodyAsStream() { + + return $this->inner->getBodyAsStream(); + + } + + /** + * Returns the body as a string. + * + * Note that because the underlying data may be based on a stream, this + * method could only work correctly the first time. + * + * @return string + */ + function getBodyAsString() { + + return $this->inner->getBodyAsString(); + + } + + /** + * Returns the message body, as it's internal representation. + * + * This could be either a string or a stream. + * + * @return resource|string + */ + function getBody() { + + return $this->inner->getBody(); + + } + + /** + * Updates the body resource with a new stream. + * + * @param resource $body + * @return void + */ + function setBody($body) { + + $this->inner->setBody($body); + + } + + /** + * Returns all the HTTP headers as an array. + * + * Every header is returned as an array, with one or more values. + * + * @return array + */ + function getHeaders() { + + return $this->inner->getHeaders(); + + } + + /** + * Will return true or false, depending on if a HTTP header exists. + * + * @param string $name + * @return bool + */ + function hasHeader($name) { + + return $this->inner->hasHeader($name); + + } + + /** + * Returns a specific HTTP header, based on it's name. + * + * The name must be treated as case-insensitive. + * If the header does not exist, this method must return null. + * + * If a header appeared more than once in a HTTP request, this method will + * concatenate all the values with a comma. + * + * Note that this not make sense for all headers. Some, such as + * `Set-Cookie` cannot be logically combined with a comma. In those cases + * you *should* use getHeaderAsArray(). + * + * @param string $name + * @return string|null + */ + function getHeader($name) { + + return $this->inner->getHeader($name); + + } + + /** + * Returns a HTTP header as an array. + * + * For every time the HTTP header appeared in the request or response, an + * item will appear in the array. + * + * If the header did not exists, this method will return an empty array. + * + * @param string $name + * @return string[] + */ + function getHeaderAsArray($name) { + + return $this->inner->getHeaderAsArray($name); + + } + + /** + * Updates a HTTP header. + * + * The case-sensitity of the name value must be retained as-is. + * + * If the header already existed, it will be overwritten. + * + * @param string $name + * @param string|string[] $value + * @return void + */ + function setHeader($name, $value) { + + $this->inner->setHeader($name, $value); + + } + + /** + * Sets a new set of HTTP headers. + * + * The headers array should contain headernames for keys, and their value + * should be specified as either a string or an array. + * + * Any header that already existed will be overwritten. + * + * @param array $headers + * @return void + */ + function setHeaders(array $headers) { + + $this->inner->setHeaders($headers); + + } + + /** + * Adds a HTTP header. + * + * This method will not overwrite any existing HTTP header, but instead add + * another value. Individual values can be retrieved with + * getHeadersAsArray. + * + * @param string $name + * @param string $value + * @return void + */ + function addHeader($name, $value) { + + $this->inner->addHeader($name, $value); + + } + + /** + * Adds a new set of HTTP headers. + * + * Any existing headers will not be overwritten. + * + * @param array $headers + * @return void + */ + function addHeaders(array $headers) { + + $this->inner->addHeaders($headers); + + } + + + /** + * Removes a HTTP header. + * + * The specified header name must be treated as case-insenstive. + * This method should return true if the header was successfully deleted, + * and false if the header did not exist. + * + * @return bool + */ + function removeHeader($name) { + + $this->inner->removeHeader($name); + + } + + /** + * Sets the HTTP version. + * + * Should be 1.0 or 1.1. + * + * @param string $version + * @return void + */ + function setHttpVersion($version) { + + $this->inner->setHttpVersion($version); + + } + + /** + * Returns the HTTP version. + * + * @return string + */ + function getHttpVersion() { + + return $this->inner->getHttpVersion(); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/MessageInterface.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/MessageInterface.php new file mode 100644 index 0000000..55d8485 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/MessageInterface.php @@ -0,0 +1,177 @@ +<?php + +namespace Sabre\HTTP; + +/** + * The MessageInterface is the base interface that's used by both + * the RequestInterface and ResponseInterface. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +interface MessageInterface { + + /** + * Returns the body as a readable stream resource. + * + * Note that the stream may not be rewindable, and therefore may only be + * read once. + * + * @return resource + */ + function getBodyAsStream(); + + /** + * Returns the body as a string. + * + * Note that because the underlying data may be based on a stream, this + * method could only work correctly the first time. + * + * @return string + */ + function getBodyAsString(); + + /** + * Returns the message body, as it's internal representation. + * + * This could be either a string or a stream. + * + * @return resource|string + */ + function getBody(); + + /** + * Updates the body resource with a new stream. + * + * @param resource $body + * @return void + */ + function setBody($body); + + /** + * Returns all the HTTP headers as an array. + * + * Every header is returned as an array, with one or more values. + * + * @return array + */ + function getHeaders(); + + /** + * Will return true or false, depending on if a HTTP header exists. + * + * @param string $name + * @return bool + */ + function hasHeader($name); + + /** + * Returns a specific HTTP header, based on it's name. + * + * The name must be treated as case-insensitive. + * If the header does not exist, this method must return null. + * + * If a header appeared more than once in a HTTP request, this method will + * concatenate all the values with a comma. + * + * Note that this not make sense for all headers. Some, such as + * `Set-Cookie` cannot be logically combined with a comma. In those cases + * you *should* use getHeaderAsArray(). + * + * @param string $name + * @return string|null + */ + function getHeader($name); + + /** + * Returns a HTTP header as an array. + * + * For every time the HTTP header appeared in the request or response, an + * item will appear in the array. + * + * If the header did not exists, this method will return an empty array. + * + * @param string $name + * @return string[] + */ + function getHeaderAsArray($name); + + /** + * Updates a HTTP header. + * + * The case-sensitity of the name value must be retained as-is. + * + * If the header already existed, it will be overwritten. + * + * @param string $name + * @param string|string[] $value + * @return void + */ + function setHeader($name, $value); + + /** + * Sets a new set of HTTP headers. + * + * The headers array should contain headernames for keys, and their value + * should be specified as either a string or an array. + * + * Any header that already existed will be overwritten. + * + * @param array $headers + * @return void + */ + function setHeaders(array $headers); + + /** + * Adds a HTTP header. + * + * This method will not overwrite any existing HTTP header, but instead add + * another value. Individual values can be retrieved with + * getHeadersAsArray. + * + * @param string $name + * @param string $value + * @return void + */ + function addHeader($name, $value); + + /** + * Adds a new set of HTTP headers. + * + * Any existing headers will not be overwritten. + * + * @param array $headers + * @return void + */ + function addHeaders(array $headers); + + /** + * Removes a HTTP header. + * + * The specified header name must be treated as case-insenstive. + * This method should return true if the header was successfully deleted, + * and false if the header did not exist. + * + * @return bool + */ + function removeHeader($name); + + /** + * Sets the HTTP version. + * + * Should be 1.0 or 1.1. + * + * @param string $version + * @return void + */ + function setHttpVersion($version); + + /** + * Returns the HTTP version. + * + * @return string + */ + function getHttpVersion(); + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Request.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Request.php new file mode 100644 index 0000000..8bcaf32 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Request.php @@ -0,0 +1,316 @@ +<?php + +namespace Sabre\HTTP; + +use InvalidArgumentException; +use Sabre\Uri; + +/** + * The Request class represents a single HTTP request. + * + * You can either simply construct the object from scratch, or if you need + * access to the current HTTP request, use Sapi::getRequest. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Request extends Message implements RequestInterface { + + /** + * HTTP Method + * + * @var string + */ + protected $method; + + /** + * Request Url + * + * @var string + */ + protected $url; + + /** + * Creates the request object + * + * @param string $method + * @param string $url + * @param array $headers + * @param resource $body + */ + function __construct($method = null, $url = null, array $headers = null, $body = null) { + + if (is_array($method)) { + throw new InvalidArgumentException('The first argument for this constructor should be a string or null, not an array. Did you upgrade from sabre/http 1.0 to 2.0?'); + } + if (!is_null($method)) $this->setMethod($method); + if (!is_null($url)) $this->setUrl($url); + if (!is_null($headers)) $this->setHeaders($headers); + if (!is_null($body)) $this->setBody($body); + + } + + /** + * Returns the current HTTP method + * + * @return string + */ + function getMethod() { + + return $this->method; + + } + + /** + * Sets the HTTP method + * + * @param string $method + * @return void + */ + function setMethod($method) { + + $this->method = $method; + + } + + /** + * Returns the request url. + * + * @return string + */ + function getUrl() { + + return $this->url; + + } + + /** + * Sets the request url. + * + * @param string $url + * @return void + */ + function setUrl($url) { + + $this->url = $url; + + } + + /** + * Returns the list of query parameters. + * + * This is equivalent to PHP's $_GET superglobal. + * + * @return array + */ + function getQueryParameters() { + + $url = $this->getUrl(); + if (($index = strpos($url, '?')) === false) { + return []; + } else { + parse_str(substr($url, $index + 1), $queryParams); + return $queryParams; + } + + } + + /** + * Sets the absolute url. + * + * @param string $url + * @return void + */ + function setAbsoluteUrl($url) { + + $this->absoluteUrl = $url; + + } + + /** + * Returns the absolute url. + * + * @return string + */ + function getAbsoluteUrl() { + + return $this->absoluteUrl; + + } + + /** + * Base url + * + * @var string + */ + protected $baseUrl = '/'; + + /** + * Sets a base url. + * + * This url is used for relative path calculations. + * + * @param string $url + * @return void + */ + function setBaseUrl($url) { + + $this->baseUrl = $url; + + } + + /** + * Returns the current base url. + * + * @return string + */ + function getBaseUrl() { + + return $this->baseUrl; + + } + + /** + * Returns the relative path. + * + * This is being calculated using the base url. This path will not start + * with a slash, so it will always return something like + * 'example/path.html'. + * + * If the full path is equal to the base url, this method will return an + * empty string. + * + * This method will also urldecode the path, and if the url was incoded as + * ISO-8859-1, it will convert it to UTF-8. + * + * If the path is outside of the base url, a LogicException will be thrown. + * + * @return string + */ + function getPath() { + + // Removing duplicated slashes. + $uri = str_replace('//', '/', $this->getUrl()); + + $uri = Uri\normalize($uri); + $baseUri = Uri\normalize($this->getBaseUrl()); + + if (strpos($uri, $baseUri) === 0) { + + // We're not interested in the query part (everything after the ?). + list($uri) = explode('?', $uri); + 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 ''; + + } + + throw new \LogicException('Requested uri (' . $this->getUrl() . ') is out of base uri (' . $this->getBaseUrl() . ')'); + } + + /** + * Equivalent of PHP's $_POST. + * + * @var array + */ + protected $postData = []; + + /** + * Sets the post data. + * + * This is equivalent to PHP's $_POST superglobal. + * + * This would not have been needed, if POST data was accessible as + * php://input, but unfortunately we need to special case it. + * + * @param array $postData + * @return void + */ + function setPostData(array $postData) { + + $this->postData = $postData; + + } + + /** + * Returns the POST data. + * + * This is equivalent to PHP's $_POST superglobal. + * + * @return array + */ + function getPostData() { + + return $this->postData; + + } + + /** + * An array containing the raw _SERVER array. + * + * @var array + */ + protected $rawServerData; + + /** + * Returns an item from the _SERVER array. + * + * If the value does not exist in the array, null is returned. + * + * @param string $valueName + * @return string|null + */ + function getRawServerValue($valueName) { + + if (isset($this->rawServerData[$valueName])) { + return $this->rawServerData[$valueName]; + } + + } + + /** + * Sets the _SERVER array. + * + * @param array $data + * @return void + */ + function setRawServerData(array $data) { + + $this->rawServerData = $data; + + } + + /** + * Serializes the request object as a string. + * + * This is useful for debugging purposes. + * + * @return string + */ + function __toString() { + + $out = $this->getMethod() . ' ' . $this->getUrl() . ' HTTP/' . $this->getHTTPVersion() . "\r\n"; + + foreach ($this->getHeaders() as $key => $value) { + foreach ($value as $v) { + if ($key === 'Authorization') { + list($v) = explode(' ', $v, 2); + $v .= ' REDACTED'; + } + $out .= $key . ": " . $v . "\r\n"; + } + } + $out .= "\r\n"; + $out .= $this->getBodyAsString(); + + return $out; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/RequestDecorator.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/RequestDecorator.php new file mode 100644 index 0000000..7ee3f6f --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/RequestDecorator.php @@ -0,0 +1,231 @@ +<?php + +namespace Sabre\HTTP; + +/** + * Request Decorator + * + * This helper class allows you to easily create decorators for the Request + * object. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class RequestDecorator implements RequestInterface { + + use MessageDecoratorTrait; + + /** + * Constructor. + * + * @param RequestInterface $inner + */ + function __construct(RequestInterface $inner) { + + $this->inner = $inner; + + } + + /** + * Returns the current HTTP method + * + * @return string + */ + function getMethod() { + + return $this->inner->getMethod(); + + } + + /** + * Sets the HTTP method + * + * @param string $method + * @return void + */ + function setMethod($method) { + + $this->inner->setMethod($method); + + } + + /** + * Returns the request url. + * + * @return string + */ + function getUrl() { + + return $this->inner->getUrl(); + + } + + /** + * Sets the request url. + * + * @param string $url + * @return void + */ + function setUrl($url) { + + $this->inner->setUrl($url); + + } + + /** + * Returns the absolute url. + * + * @return string + */ + function getAbsoluteUrl() { + + return $this->inner->getAbsoluteUrl(); + + } + + /** + * Sets the absolute url. + * + * @param string $url + * @return void + */ + function setAbsoluteUrl($url) { + + $this->inner->setAbsoluteUrl($url); + + } + + /** + * Returns the current base url. + * + * @return string + */ + function getBaseUrl() { + + return $this->inner->getBaseUrl(); + + } + + /** + * Sets a base url. + * + * This url is used for relative path calculations. + * + * The base url should default to / + * + * @param string $url + * @return void + */ + function setBaseUrl($url) { + + $this->inner->setBaseUrl($url); + + } + + /** + * Returns the relative path. + * + * This is being calculated using the base url. This path will not start + * with a slash, so it will always return something like + * 'example/path.html'. + * + * If the full path is equal to the base url, this method will return an + * empty string. + * + * This method will also urldecode the path, and if the url was incoded as + * ISO-8859-1, it will convert it to UTF-8. + * + * If the path is outside of the base url, a LogicException will be thrown. + * + * @return string + */ + function getPath() { + + return $this->inner->getPath(); + + } + + /** + * Returns the list of query parameters. + * + * This is equivalent to PHP's $_GET superglobal. + * + * @return array + */ + function getQueryParameters() { + + return $this->inner->getQueryParameters(); + + } + + /** + * Returns the POST data. + * + * This is equivalent to PHP's $_POST superglobal. + * + * @return array + */ + function getPostData() { + + return $this->inner->getPostData(); + + } + + /** + * Sets the post data. + * + * This is equivalent to PHP's $_POST superglobal. + * + * This would not have been needed, if POST data was accessible as + * php://input, but unfortunately we need to special case it. + * + * @param array $postData + * @return void + */ + function setPostData(array $postData) { + + $this->inner->setPostData($postData); + + } + + + /** + * Returns an item from the _SERVER array. + * + * If the value does not exist in the array, null is returned. + * + * @param string $valueName + * @return string|null + */ + function getRawServerValue($valueName) { + + return $this->inner->getRawServerValue($valueName); + + } + + /** + * Sets the _SERVER array. + * + * @param array $data + * @return void + */ + function setRawServerData(array $data) { + + $this->inner->setRawServerData($data); + + } + + /** + * Serializes the request object as a string. + * + * This is useful for debugging purposes. + * + * @return string + */ + function __toString() { + + return $this->inner->__toString(); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/RequestInterface.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/RequestInterface.php new file mode 100644 index 0000000..63d9cbb --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/RequestInterface.php @@ -0,0 +1,147 @@ +<?php + +namespace Sabre\HTTP; + +/** + * The RequestInterface represents a HTTP request. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +interface RequestInterface extends MessageInterface { + + /** + * Returns the current HTTP method + * + * @return string + */ + function getMethod(); + + /** + * Sets the HTTP method + * + * @param string $method + * @return void + */ + function setMethod($method); + + /** + * Returns the request url. + * + * @return string + */ + function getUrl(); + + /** + * Sets the request url. + * + * @param string $url + * @return void + */ + function setUrl($url); + + /** + * Returns the absolute url. + * + * @return string + */ + function getAbsoluteUrl(); + + /** + * Sets the absolute url. + * + * @param string $url + * @return void + */ + function setAbsoluteUrl($url); + + /** + * Returns the current base url. + * + * @return string + */ + function getBaseUrl(); + + /** + * Sets a base url. + * + * This url is used for relative path calculations. + * + * The base url should default to / + * + * @param string $url + * @return void + */ + function setBaseUrl($url); + + /** + * Returns the relative path. + * + * This is being calculated using the base url. This path will not start + * with a slash, so it will always return something like + * 'example/path.html'. + * + * If the full path is equal to the base url, this method will return an + * empty string. + * + * This method will also urldecode the path, and if the url was incoded as + * ISO-8859-1, it will convert it to UTF-8. + * + * If the path is outside of the base url, a LogicException will be thrown. + * + * @return string + */ + function getPath(); + + /** + * Returns the list of query parameters. + * + * This is equivalent to PHP's $_GET superglobal. + * + * @return array + */ + function getQueryParameters(); + + /** + * Returns the POST data. + * + * This is equivalent to PHP's $_POST superglobal. + * + * @return array + */ + function getPostData(); + + /** + * Sets the post data. + * + * This is equivalent to PHP's $_POST superglobal. + * + * This would not have been needed, if POST data was accessible as + * php://input, but unfortunately we need to special case it. + * + * @param array $postData + * @return void + */ + function setPostData(array $postData); + + /** + * Returns an item from the _SERVER array. + * + * If the value does not exist in the array, null is returned. + * + * @param string $valueName + * @return string|null + */ + function getRawServerValue($valueName); + + /** + * Sets the _SERVER array. + * + * @param array $data + * @return void + */ + function setRawServerData(array $data); + + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Response.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Response.php new file mode 100644 index 0000000..d2ba6d4 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Response.php @@ -0,0 +1,194 @@ +<?php + +namespace Sabre\HTTP; + +/** + * This class represents a single HTTP response. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Response extends Message implements ResponseInterface { + + /** + * This is the list of currently registered HTTP status codes. + * + * @var array + */ + static $statusCodes = [ + 100 => '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', + 307 => 'Temporary Redirect', + 308 => 'Permanent 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 + 421 => 'Misdirected Request', // RFC7540 (HTTP/2) + 422 => 'Unprocessable Entity', // RFC 4918 + 423 => 'Locked', // RFC 4918 + 424 => 'Failed Dependency', // RFC 4918 + 426 => 'Upgrade Required', + 428 => 'Precondition Required', // RFC 6585 + 429 => 'Too Many Requests', // RFC 6585 + 431 => 'Request Header Fields Too Large', // RFC 6585 + 451 => 'Unavailable For Legal Reasons', // draft-tbray-http-legally-restricted-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', // RFC 6585 + ]; + + /** + * HTTP status code + * + * @var int + */ + protected $status; + + /** + * HTTP status text + * + * @var string + */ + protected $statusText; + + /** + * Creates the response object + * + * @param string|int $status + * @param array $headers + * @param resource $body + * @return void + */ + function __construct($status = null, array $headers = null, $body = null) { + + if (!is_null($status)) $this->setStatus($status); + if (!is_null($headers)) $this->setHeaders($headers); + if (!is_null($body)) $this->setBody($body); + + } + + + /** + * Returns the current HTTP status code. + * + * @return int + */ + function getStatus() { + + return $this->status; + + } + + /** + * Returns the human-readable status string. + * + * In the case of a 200, this may for example be 'OK'. + * + * @return string + */ + function getStatusText() { + + return $this->statusText; + + } + + /** + * Sets the HTTP status code. + * + * This can be either the full HTTP status code with human readable string, + * for example: "403 I can't let you do that, Dave". + * + * Or just the code, in which case the appropriate default message will be + * added. + * + * @param string|int $status + * @throws \InvalidArgumentExeption + * @return void + */ + function setStatus($status) { + + if (ctype_digit($status) || is_int($status)) { + + $statusCode = $status; + $statusText = isset(self::$statusCodes[$status]) ? self::$statusCodes[$status] : 'Unknown'; + + } else { + list( + $statusCode, + $statusText + ) = explode(' ', $status, 2); + } + if ($statusCode < 100 || $statusCode > 999) { + throw new \InvalidArgumentException('The HTTP status code must be exactly 3 digits'); + } + + $this->status = $statusCode; + $this->statusText = $statusText; + + } + + /** + * Serializes the response object as a string. + * + * This is useful for debugging purposes. + * + * @return string + */ + function __toString() { + + $str = 'HTTP/' . $this->httpVersion . ' ' . $this->getStatus() . ' ' . $this->getStatusText() . "\r\n"; + foreach ($this->getHeaders() as $key => $value) { + foreach ($value as $v) { + $str .= $key . ": " . $v . "\r\n"; + } + } + $str .= "\r\n"; + $str .= $this->getBodyAsString(); + return $str; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/ResponseDecorator.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/ResponseDecorator.php new file mode 100644 index 0000000..db3a675 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/ResponseDecorator.php @@ -0,0 +1,84 @@ +<?php + +namespace Sabre\HTTP; + +/** + * Response Decorator + * + * This helper class allows you to easily create decorators for the Response + * object. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class ResponseDecorator implements ResponseInterface { + + use MessageDecoratorTrait; + + /** + * Constructor. + * + * @param ResponseInterface $inner + */ + function __construct(ResponseInterface $inner) { + + $this->inner = $inner; + + } + + /** + * Returns the current HTTP status code. + * + * @return int + */ + function getStatus() { + + return $this->inner->getStatus(); + + } + + + /** + * Returns the human-readable status string. + * + * In the case of a 200, this may for example be 'OK'. + * + * @return string + */ + function getStatusText() { + + return $this->inner->getStatusText(); + + } + /** + * Sets the HTTP status code. + * + * This can be either the full HTTP status code with human readable string, + * for example: "403 I can't let you do that, Dave". + * + * Or just the code, in which case the appropriate default message will be + * added. + * + * @param string|int $status + * @return void + */ + function setStatus($status) { + + $this->inner->setStatus($status); + + } + + /** + * Serializes the request object as a string. + * + * This is useful for debugging purposes. + * + * @return string + */ + function __toString() { + + return $this->inner->__toString(); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/ResponseInterface.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/ResponseInterface.php new file mode 100644 index 0000000..c0ecc35 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/ResponseInterface.php @@ -0,0 +1,45 @@ +<?php + +namespace Sabre\HTTP; + +/** + * This interface represents a HTTP response. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +interface ResponseInterface extends MessageInterface { + + /** + * Returns the current HTTP status code. + * + * @return int + */ + function getStatus(); + + /** + * Returns the human-readable status string. + * + * In the case of a 200, this may for example be 'OK'. + * + * @return string + */ + function getStatusText(); + + /** + * Sets the HTTP status code. + * + * This can be either the full HTTP status code with human readable string, + * for example: "403 I can't let you do that, Dave". + * + * Or just the code, in which case the appropriate default message will be + * added. + * + * @param string|int $status + * @throws \InvalidArgumentExeption + * @return void + */ + function setStatus($status); + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Sapi.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Sapi.php new file mode 100644 index 0000000..6c83c87 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Sapi.php @@ -0,0 +1,194 @@ +<?php + +namespace Sabre\HTTP; + +/** + * PHP SAPI + * + * This object is responsible for: + * 1. Constructing a Request object based on the current HTTP request sent to + * the PHP process. + * 2. Sending the Response object back to the client. + * + * It could be said that this class provides a mapping between the Request and + * Response objects, and php's: + * + * * $_SERVER + * * $_POST + * * $_FILES + * * php://input + * * echo() + * * header() + * * php://output + * + * You can choose to either call all these methods statically, but you can also + * instantiate this as an object to allow for polymorhpism. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Sapi { + + /** + * This static method will create a new Request object, based on the + * current PHP request. + * + * @return Request + */ + static function getRequest() { + + $r = self::createFromServerArray($_SERVER); + $r->setBody(fopen('php://input', 'r')); + $r->setPostData($_POST); + return $r; + + } + + /** + * Sends the HTTP response back to a HTTP client. + * + * This calls php's header() function and streams the body to php://output. + * + * @param ResponseInterface $response + * @return void + */ + static function sendResponse(ResponseInterface $response) { + + header('HTTP/' . $response->getHttpVersion() . ' ' . $response->getStatus() . ' ' . $response->getStatusText()); + foreach ($response->getHeaders() as $key => $value) { + + foreach ($value as $k => $v) { + if ($k === 0) { + header($key . ': ' . $v); + } else { + header($key . ': ' . $v, false); + } + } + + } + + $body = $response->getBody(); + if (is_null($body)) return; + + $contentLength = $response->getHeader('Content-Length'); + if ($contentLength !== null) { + $output = fopen('php://output', 'wb'); + if (is_resource($body) && get_resource_type($body) == 'stream') { + stream_copy_to_stream($body, $output, $contentLength); + } else { + fwrite($output, $body, $contentLength); + } + } else { + file_put_contents('php://output', $body); + } + + if (is_resource($body)) { + fclose($body); + } + + } + + /** + * This static method will create a new Request object, based on a PHP + * $_SERVER array. + * + * @param array $serverArray + * @return Request + */ + static function createFromServerArray(array $serverArray) { + + $headers = []; + $method = null; + $url = null; + $httpVersion = '1.1'; + + $protocol = 'http'; + $hostName = 'localhost'; + + foreach ($serverArray as $key => $value) { + + switch ($key) { + + case 'SERVER_PROTOCOL' : + if ($value === 'HTTP/1.0') { + $httpVersion = '1.0'; + } + break; + case 'REQUEST_METHOD' : + $method = $value; + break; + case 'REQUEST_URI' : + $url = $value; + break; + + // These sometimes show up without a HTTP_ prefix + case 'CONTENT_TYPE' : + $headers['Content-Type'] = $value; + break; + case 'CONTENT_LENGTH' : + $headers['Content-Length'] = $value; + break; + + // mod_php on apache will put credentials in these variables. + // (fast)cgi does not usually do this, however. + case 'PHP_AUTH_USER' : + if (isset($serverArray['PHP_AUTH_PW'])) { + $headers['Authorization'] = 'Basic ' . base64_encode($value . ':' . $serverArray['PHP_AUTH_PW']); + } + break; + + // Similarly, mod_php may also screw around with digest auth. + case 'PHP_AUTH_DIGEST' : + $headers['Authorization'] = 'Digest ' . $value; + break; + + // Apache may prefix the HTTP_AUTHORIZATION header with + // REDIRECT_, if mod_rewrite was used. + case 'REDIRECT_HTTP_AUTHORIZATION' : + $headers['Authorization'] = $value; + break; + + case 'HTTP_HOST' : + $hostName = $value; + $headers['Host'] = $value; + break; + + case 'HTTPS' : + if (!empty($value) && $value !== 'off') { + $protocol = 'https'; + } + break; + + default : + if (substr($key, 0, 5) === 'HTTP_') { + // It's a HTTP header + + // Normalizing it to be prettier + $header = strtolower(substr($key, 5)); + + // Transforming dashes into spaces, and uppercasing + // every first letter. + $header = ucwords(str_replace('_', ' ', $header)); + + // Turning spaces into dashes. + $header = str_replace(' ', '-', $header); + $headers[$header] = $value; + + } + break; + + + } + + } + + $r = new Request($method, $url, $headers); + $r->setHttpVersion($httpVersion); + $r->setRawServerData($serverArray); + $r->setAbsoluteUrl($protocol . '://' . $hostName . $url); + return $r; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/URLUtil.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/URLUtil.php new file mode 100644 index 0000000..4748563 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/URLUtil.php @@ -0,0 +1,103 @@ +<?php + +namespace Sabre\HTTP; + +use Sabre\URI; + +/** + * URL utility class + * + * Note: this class is deprecated. All its functionality moved to functions.php + * or sabre\uri. + * + * @deprectated + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class URLUtil { + + /** + * Encodes the path of a url. + * + * slashes (/) are treated as path-separators. + * + * @deprecated use \Sabre\HTTP\encodePath() + * @param string $path + * @return string + */ + static function encodePath($path) { + + return encodePath($path); + + } + + /** + * Encodes a 1 segment of a path + * + * Slashes are considered part of the name, and are encoded as %2f + * + * @deprecated use \Sabre\HTTP\encodePathSegment() + * @param string $pathSegment + * @return string + */ + static function encodePathSegment($pathSegment) { + + return encodePathSegment($pathSegment); + + } + + /** + * Decodes a url-encoded path + * + * @deprecated use \Sabre\HTTP\decodePath + * @param string $path + * @return string + */ + static function decodePath($path) { + + return decodePath($path); + + } + + /** + * Decodes a url-encoded path segment + * + * @deprecated use \Sabre\HTTP\decodePathSegment() + * @param string $path + * @return string + */ + static function decodePathSegment($path) { + + return decodePathSegment($path); + + } + + /** + * Returns the 'dirname' and 'basename' for a path. + * + * @deprecated Use Sabre\Uri\split(). + * @param string $path + * @return array + */ + static function splitPath($path) { + + return Uri\split($path); + + } + + /** + * Resolves relative urls, like a browser would. + * + * @deprecated Use Sabre\Uri\resolve(). + * @param string $basePath + * @param string $newPath + * @return string + */ + static function resolve($basePath, $newPath) { + + return Uri\resolve($basePath, $newPath); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Util.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Util.php new file mode 100644 index 0000000..83bde50 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Util.php @@ -0,0 +1,74 @@ +<?php + +namespace Sabre\HTTP; + +/** + * HTTP utility methods + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @author Paul Voegler + * @deprecated All these functions moved to functions.php + * @license http://sabre.io/license/ Modified BSD License + */ +class Util { + + /** + * Content negotiation + * + * @deprecated Use \Sabre\HTTP\negotiateContentType + * @param string|null $acceptHeaderValue + * @param array $availableOptions + * @return string|null + */ + static function negotiateContentType($acceptHeaderValue, array $availableOptions) { + + return negotiateContentType($acceptHeaderValue, $availableOptions); + + } + + /** + * Deprecated! Use negotiateContentType. + * + * @deprecated Use \Sabre\HTTP\NegotiateContentType + * @param string|null $acceptHeader + * @param array $availableOptions + * @return string|null + */ + static function negotiate($acceptHeaderValue, array $availableOptions) { + + return negotiateContentType($acceptHeaderValue, $availableOptions); + + } + + /** + * Parses a RFC2616-compatible date string + * + * This method returns false if the date is invalid + * + * @deprecated Use parseDate + * @param string $dateHeader + * @return bool|DateTime + */ + static function parseHTTPDate($dateHeader) { + + return parseDate($dateHeader); + + } + + /** + * 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. + * + * @deprecated Use toDate + * @param \DateTime $dateTime + * @return string + */ + static function toHTTPDate(\DateTime $dateTime) { + + return toDate($dateTime); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Version.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Version.php new file mode 100644 index 0000000..789ee45 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/Version.php @@ -0,0 +1,19 @@ +<?php + +namespace Sabre\HTTP; + +/** + * This class contains the version number for the HTTP package + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Version { + + /** + * Full version number + */ + const VERSION = '4.2.1'; + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/functions.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/functions.php new file mode 100644 index 0000000..1ec123f --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/lib/functions.php @@ -0,0 +1,445 @@ +<?php + +namespace Sabre\HTTP; + +use DateTime; + +/** + * A collection of useful helpers for parsing or generating various HTTP + * headers. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ + +/** + * Parses a HTTP date-string. + * + * This method returns false if the date is invalid. + * + * The following formats are supported: + * Sun, 06 Nov 1994 08:49:37 GMT ; IMF-fixdate + * Sunday, 06-Nov-94 08:49:37 GMT ; obsolete RFC 850 format + * Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format + * + * See: + * http://tools.ietf.org/html/rfc7231#section-7.1.1.1 + * + * @param string $dateString + * @return bool|DateTime + */ +function parseDate($dateString) { + + // Only the format is checked, valid ranges are checked by strtotime below + $month = '(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)'; + $weekday = '(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)'; + $wkday = '(Mon|Tue|Wed|Thu|Fri|Sat|Sun)'; + $time = '([0-1]\d|2[0-3])(\:[0-5]\d){2}'; + $date3 = $month . ' ([12]\d|3[01]| [1-9])'; + $date2 = '(0[1-9]|[12]\d|3[01])\-' . $month . '\-\d{2}'; + // 4-digit year cannot begin with 0 - unix timestamp begins in 1970 + $date1 = '(0[1-9]|[12]\d|3[01]) ' . $month . ' [1-9]\d{3}'; + + // ANSI C's asctime() format + // 4-digit year cannot begin with 0 - unix timestamp begins in 1970 + $asctime_date = $wkday . ' ' . $date3 . ' ' . $time . ' [1-9]\d{3}'; + // RFC 850, obsoleted by RFC 1036 + $rfc850_date = $weekday . ', ' . $date2 . ' ' . $time . ' GMT'; + // RFC 822, updated by RFC 1123 + $rfc1123_date = $wkday . ', ' . $date1 . ' ' . $time . ' GMT'; + // allowed date formats by RFC 2616 + $HTTP_date = "($rfc1123_date|$rfc850_date|$asctime_date)"; + + // allow for space around the string and strip it + $dateString = trim($dateString, ' '); + if (!preg_match('/^' . $HTTP_date . '$/', $dateString)) + return false; + + // append implicit GMT timezone to ANSI C time format + if (strpos($dateString, ' GMT') === false) + $dateString .= ' GMT'; + + try { + return new DateTime($dateString, new \DateTimeZone('UTC')); + } catch (\Exception $e) { + return false; + } + +} + +/** + * Transforms a DateTime object to a valid HTTP/1.1 Date header value + * + * @param DateTime $dateTime + * @return string + */ +function toDate(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'); + +} + +/** + * This function can be used to aid with content negotiation. + * + * It takes 2 arguments, the $acceptHeaderValue, which usually comes from + * an Accept header, and $availableOptions, which contains an array of + * items that the server can support. + * + * The result of this function will be the 'best possible option'. If no + * best possible option could be found, null is returned. + * + * When it's null you can according to the spec either return a default, or + * you can choose to emit 406 Not Acceptable. + * + * The method also accepts sending 'null' for the $acceptHeaderValue, + * implying that no accept header was sent. + * + * @param string|null $acceptHeaderValue + * @param array $availableOptions + * @return string|null + */ +function negotiateContentType($acceptHeaderValue, array $availableOptions) { + + if (!$acceptHeaderValue) { + // Grabbing the first in the list. + return reset($availableOptions); + } + + $proposals = array_map( + 'Sabre\HTTP\parseMimeType', + explode(',', $acceptHeaderValue) + ); + + // Ensuring array keys are reset. + $availableOptions = array_values($availableOptions); + + $options = array_map( + 'Sabre\HTTP\parseMimeType', + $availableOptions + ); + + $lastQuality = 0; + $lastSpecificity = 0; + $lastOptionIndex = 0; + $lastChoice = null; + + foreach ($proposals as $proposal) { + + // Ignoring broken values. + if (is_null($proposal)) continue; + + // If the quality is lower we don't have to bother comparing. + if ($proposal['quality'] < $lastQuality) { + continue; + } + + foreach ($options as $optionIndex => $option) { + + if ($proposal['type'] !== '*' && $proposal['type'] !== $option['type']) { + // no match on type. + continue; + } + if ($proposal['subType'] !== '*' && $proposal['subType'] !== $option['subType']) { + // no match on subtype. + continue; + } + + // Any parameters appearing on the options must appear on + // proposals. + foreach ($option['parameters'] as $paramName => $paramValue) { + if (!array_key_exists($paramName, $proposal['parameters'])) { + continue 2; + } + if ($paramValue !== $proposal['parameters'][$paramName]) { + continue 2; + } + } + + // If we got here, we have a match on parameters, type and + // subtype. We need to calculate a score for how specific the + // match was. + $specificity = + ($proposal['type'] !== '*' ? 20 : 0) + + ($proposal['subType'] !== '*' ? 10 : 0) + + count($option['parameters']); + + + // Does this entry win? + if ( + ($proposal['quality'] > $lastQuality) || + ($proposal['quality'] === $lastQuality && $specificity > $lastSpecificity) || + ($proposal['quality'] === $lastQuality && $specificity === $lastSpecificity && $optionIndex < $lastOptionIndex) + ) { + + $lastQuality = $proposal['quality']; + $lastSpecificity = $specificity; + $lastOptionIndex = $optionIndex; + $lastChoice = $availableOptions[$optionIndex]; + + } + + } + + } + + return $lastChoice; + +} + +/** + * Parses the Prefer header, as defined in RFC7240. + * + * Input can be given as a single header value (string) or multiple headers + * (array of string). + * + * This method will return a key->value array with the various Prefer + * parameters. + * + * Prefer: return=minimal will result in: + * + * [ 'return' => 'minimal' ] + * + * Prefer: foo, wait=10 will result in: + * + * [ 'foo' => true, 'wait' => '10'] + * + * This method also supports the formats from older drafts of RFC7240, and + * it will automatically map them to the new values, as the older values + * are still pretty common. + * + * Parameters are currently discarded. There's no known prefer value that + * uses them. + * + * @param string|string[] $header + * @return array + */ +function parsePrefer($input) { + + $token = '[!#$%&\'*+\-.^_`~A-Za-z0-9]+'; + + // Work in progress + $word = '(?: [a-zA-Z0-9]+ | "[a-zA-Z0-9]*" )'; + + $regex = <<<REGEX +/ +^ +(?<name> $token) # Prefer property name +\s* # Optional space +(?: = \s* # Prefer property value + (?<value> $word) +)? +(?: \s* ; (?: .*))? # Prefer parameters (ignored) +$ +/x +REGEX; + + $output = []; + foreach (getHeaderValues($input) as $value) { + + if (!preg_match($regex, $value, $matches)) { + // Ignore + continue; + } + + // Mapping old values to their new counterparts + switch ($matches['name']) { + case 'return-asynch' : + $output['respond-async'] = true; + break; + case 'return-representation' : + $output['return'] = 'representation'; + break; + case 'return-minimal' : + $output['return'] = 'minimal'; + break; + case 'strict' : + $output['handling'] = 'strict'; + break; + case 'lenient' : + $output['handling'] = 'lenient'; + break; + default : + if (isset($matches['value'])) { + $value = trim($matches['value'], '"'); + } else { + $value = true; + } + $output[strtolower($matches['name'])] = empty($value) ? true : $value; + break; + } + + } + + return $output; + +} + +/** + * This method splits up headers into all their individual values. + * + * A HTTP header may have more than one header, such as this: + * Cache-Control: private, no-store + * + * Header values are always split with a comma. + * + * You can pass either a string, or an array. The resulting value is always + * an array with each spliced value. + * + * If the second headers argument is set, this value will simply be merged + * in. This makes it quicker to merge an old list of values with a new set. + * + * @param string|string[] $values + * @param string|string[] $values2 + * @return string[] + */ +function getHeaderValues($values, $values2 = null) { + + $values = (array)$values; + if ($values2) { + $values = array_merge($values, (array)$values2); + } + foreach ($values as $l1) { + foreach (explode(',', $l1) as $l2) { + $result[] = trim($l2); + } + } + return $result; + +} + +/** + * Parses a mime-type and splits it into: + * + * 1. type + * 2. subtype + * 3. quality + * 4. parameters + * + * @param string $str + * @return array + */ +function parseMimeType($str) { + + $parameters = []; + // If no q= parameter appears, then quality = 1. + $quality = 1; + + $parts = explode(';', $str); + + // The first part is the mime-type. + $mimeType = array_shift($parts); + + $mimeType = explode('/', trim($mimeType)); + if (count($mimeType) !== 2) { + // Illegal value + return null; + } + list($type, $subType) = $mimeType; + + foreach ($parts as $part) { + + $part = trim($part); + if (strpos($part, '=')) { + list($partName, $partValue) = + explode('=', $part, 2); + } else { + $partName = $part; + $partValue = null; + } + + // The quality parameter, if it appears, also marks the end of + // the parameter list. Anything after the q= counts as an + // 'accept extension' and could introduce new semantics in + // content-negotation. + if ($partName !== 'q') { + $parameters[$partName] = $part; + } else { + $quality = (float)$partValue; + break; // Stop parsing parts + } + + } + + return [ + 'type' => $type, + 'subType' => $subType, + 'quality' => $quality, + 'parameters' => $parameters, + ]; + +} + +/** + * Encodes the path of a url. + * + * slashes (/) are treated as path-separators. + * + * @param string $path + * @return string + */ +function encodePath($path) { + + return preg_replace_callback('/([^A-Za-z0-9_\-\.~\(\)\/:@])/', function($match) { + + return '%' . sprintf('%02x', ord($match[0])); + + }, $path); + +} + +/** + * Encodes a 1 segment of a path + * + * Slashes are considered part of the name, and are encoded as %2f + * + * @param string $pathSegment + * @return string + */ +function encodePathSegment($pathSegment) { + + return preg_replace_callback('/([^A-Za-z0-9_\-\.~\(\):@])/', function($match) { + + return '%' . sprintf('%02x', ord($match[0])); + + }, $pathSegment); +} + +/** + * Decodes a url-encoded path + * + * @param string $path + * @return string + */ +function decodePath($path) { + + return decodePathSegment($path); + +} + +/** + * Decodes a url-encoded path segment + * + * @param string $path + * @return string + */ +function decodePathSegment($path) { + + $path = rawurldecode($path); + $encoding = mb_detect_encoding($path, ['UTF-8', 'ISO-8859-1']); + + switch ($encoding) { + + case 'ISO-8859-1' : + $path = utf8_encode($path); + + } + + return $path; + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/Auth/AWSTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/Auth/AWSTest.php new file mode 100644 index 0000000..650761a --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/Auth/AWSTest.php @@ -0,0 +1,235 @@ +<?php + +namespace Sabre\HTTP\Auth; + +use Sabre\HTTP\Request; +use Sabre\HTTP\Response; + +class AWSTest extends \PHPUnit_Framework_TestCase { + + /** + * @var Sabre\HTTP\Response + */ + private $response; + + /** + * @var Sabre\HTTP\Request + */ + private $request; + + /** + * @var Sabre\HTTP\Auth\AWS + */ + private $auth; + + const REALM = 'SabreDAV unittest'; + + function setUp() { + + $this->response = new Response(); + $this->request = new Request(); + $this->auth = new AWS(self::REALM, $this->request, $this->response); + + } + + function testNoHeader() { + + $this->request->setMethod('GET'); + $result = $this->auth->init(); + + $this->assertFalse($result, 'No AWS Authorization header was supplied, so we should have gotten false'); + $this->assertEquals(AWS::ERR_NOAWSHEADER, $this->auth->errorCode); + + } + + function testIncorrectContentMD5() { + + $accessKey = 'accessKey'; + $secretKey = 'secretKey'; + + $this->request->setMethod('GET'); + $this->request->setHeaders([ + 'Authorization' => "AWS $accessKey:sig", + 'Content-MD5' => 'garbage', + ]); + $this->request->setUrl('/'); + + $this->auth->init(); + $result = $this->auth->validate($secretKey); + + $this->assertFalse($result); + $this->assertEquals(AWS::ERR_MD5CHECKSUMWRONG, $this->auth->errorCode); + + } + + function testNoDate() { + + $accessKey = 'accessKey'; + $secretKey = 'secretKey'; + $content = 'thisisthebody'; + $contentMD5 = base64_encode(md5($content, true)); + + $this->request->setMethod('POST'); + $this->request->setHeaders([ + 'Authorization' => "AWS $accessKey:sig", + 'Content-MD5' => $contentMD5, + ]); + $this->request->setUrl('/'); + $this->request->setBody($content); + + $this->auth->init(); + $result = $this->auth->validate($secretKey); + + $this->assertFalse($result); + $this->assertEquals(AWS::ERR_INVALIDDATEFORMAT, $this->auth->errorCode); + + } + + function testFutureDate() { + + $accessKey = 'accessKey'; + $secretKey = 'secretKey'; + $content = 'thisisthebody'; + $contentMD5 = base64_encode(md5($content, true)); + + $date = new \DateTime('@' . (time() + (60 * 20))); + $date->setTimeZone(new \DateTimeZone('GMT')); + $date = $date->format('D, d M Y H:i:s \\G\\M\\T'); + + $this->request->setMethod('POST'); + $this->request->setHeaders([ + 'Authorization' => "AWS $accessKey:sig", + 'Content-MD5' => $contentMD5, + 'Date' => $date, + ]); + + $this->request->setBody($content); + + $this->auth->init(); + $result = $this->auth->validate($secretKey); + + $this->assertFalse($result); + $this->assertEquals(AWS::ERR_REQUESTTIMESKEWED, $this->auth->errorCode); + + } + + function testPastDate() { + + $accessKey = 'accessKey'; + $secretKey = 'secretKey'; + $content = 'thisisthebody'; + $contentMD5 = base64_encode(md5($content, true)); + + $date = new \DateTime('@' . (time() - (60 * 20))); + $date->setTimeZone(new \DateTimeZone('GMT')); + $date = $date->format('D, d M Y H:i:s \\G\\M\\T'); + + $this->request->setMethod('POST'); + $this->request->setHeaders([ + 'Authorization' => "AWS $accessKey:sig", + 'Content-MD5' => $contentMD5, + 'Date' => $date, + ]); + + $this->request->setBody($content); + + $this->auth->init(); + $result = $this->auth->validate($secretKey); + + $this->assertFalse($result); + $this->assertEquals(AWS::ERR_REQUESTTIMESKEWED, $this->auth->errorCode); + + } + + function testIncorrectSignature() { + + $accessKey = 'accessKey'; + $secretKey = 'secretKey'; + $content = 'thisisthebody'; + + $contentMD5 = base64_encode(md5($content, true)); + + $date = new \DateTime('now'); + $date->setTimeZone(new \DateTimeZone('GMT')); + $date = $date->format('D, d M Y H:i:s \\G\\M\\T'); + + $this->request->setUrl('/'); + $this->request->setMethod('POST'); + $this->request->setHeaders([ + 'Authorization' => "AWS $accessKey:sig", + 'Content-MD5' => $contentMD5, + 'X-amz-date' => $date, + ]); + $this->request->setBody($content); + + $this->auth->init(); + $result = $this->auth->validate($secretKey); + + $this->assertFalse($result); + $this->assertEquals(AWS::ERR_INVALIDSIGNATURE, $this->auth->errorCode); + + } + + function testValidRequest() { + + $accessKey = 'accessKey'; + $secretKey = 'secretKey'; + $content = 'thisisthebody'; + $contentMD5 = base64_encode(md5($content, true)); + + $date = new \DateTime('now'); + $date->setTimeZone(new \DateTimeZone('GMT')); + $date = $date->format('D, d M Y H:i:s \\G\\M\\T'); + + + $sig = base64_encode($this->hmacsha1($secretKey, + "POST\n$contentMD5\n\n$date\nx-amz-date:$date\n/evert" + )); + + $this->request->setUrl('/evert'); + $this->request->setMethod('POST'); + $this->request->setHeaders([ + 'Authorization' => "AWS $accessKey:$sig", + 'Content-MD5' => $contentMD5, + 'X-amz-date' => $date, + ]); + + $this->request->setBody($content); + + $this->auth->init(); + $result = $this->auth->validate($secretKey); + + $this->assertTrue($result, 'Signature did not validate, got errorcode ' . $this->auth->errorCode); + $this->assertEquals($accessKey, $this->auth->getAccessKey()); + + } + + function test401() { + + $this->auth->requireLogin(); + $test = preg_match('/^AWS$/', $this->response->getHeader('WWW-Authenticate'), $matches); + $this->assertTrue($test == true, 'The WWW-Authenticate response didn\'t match our pattern'); + + } + + /** + * 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/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/Auth/BasicTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/Auth/BasicTest.php new file mode 100644 index 0000000..7c25e59 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/Auth/BasicTest.php @@ -0,0 +1,69 @@ +<?php + +namespace Sabre\HTTP\Auth; + +use Sabre\HTTP\Request; +use Sabre\HTTP\Response; + +class BasicTest extends \PHPUnit_Framework_TestCase { + + function testGetCredentials() { + + $request = new Request('GET', '/', [ + 'Authorization' => 'Basic ' . base64_encode('user:pass:bla') + ]); + + $basic = new Basic('Dagger', $request, new Response()); + + $this->assertEquals([ + 'user', + 'pass:bla', + ], $basic->getCredentials()); + + } + + function testGetInvalidCredentialsColonMissing() { + + $request = new Request('GET', '/', [ + 'Authorization' => 'Basic ' . base64_encode('userpass') + ]); + + $basic = new Basic('Dagger', $request, new Response()); + + $this->assertNull($basic->getCredentials()); + + } + + function testGetCredentialsNoheader() { + + $request = new Request('GET', '/', []); + $basic = new Basic('Dagger', $request, new Response()); + + $this->assertNull($basic->getCredentials()); + + } + + function testGetCredentialsNotBasic() { + + $request = new Request('GET', '/', [ + 'Authorization' => 'QBasic ' . base64_encode('user:pass:bla') + ]); + $basic = new Basic('Dagger', $request, new Response()); + + $this->assertNull($basic->getCredentials()); + + } + + function testRequireLogin() { + + $response = new Response(); + $basic = new Basic('Dagger', new Request(), $response); + + $basic->requireLogin(); + + $this->assertEquals('Basic realm="Dagger"', $response->getHeader('WWW-Authenticate')); + $this->assertEquals(401, $response->getStatus()); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/Auth/BearerTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/Auth/BearerTest.php new file mode 100644 index 0000000..ee2e9e0 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/Auth/BearerTest.php @@ -0,0 +1,57 @@ +<?php + +namespace Sabre\HTTP\Auth; + +use Sabre\HTTP\Request; +use Sabre\HTTP\Response; + +class BearerTest extends \PHPUnit_Framework_TestCase { + + function testGetToken() { + + $request = new Request('GET', '/', [ + 'Authorization' => 'Bearer 12345' + ]); + + $bearer = new Bearer('Dagger', $request, new Response()); + + $this->assertEquals( + '12345', + $bearer->getToken() + ); + + } + + function testGetCredentialsNoheader() { + + $request = new Request('GET', '/', []); + $bearer = new Bearer('Dagger', $request, new Response()); + + $this->assertNull($bearer->getToken()); + + } + + function testGetCredentialsNotBearer() { + + $request = new Request('GET', '/', [ + 'Authorization' => 'QBearer 12345' + ]); + $bearer = new Bearer('Dagger', $request, new Response()); + + $this->assertNull($bearer->getToken()); + + } + + function testRequireLogin() { + + $response = new Response(); + $bearer = new Bearer('Dagger', new Request(), $response); + + $bearer->requireLogin(); + + $this->assertEquals('Bearer realm="Dagger"', $response->getHeader('WWW-Authenticate')); + $this->assertEquals(401, $response->getStatus()); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/Auth/DigestTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/Auth/DigestTest.php new file mode 100644 index 0000000..ffb69c7 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/Auth/DigestTest.php @@ -0,0 +1,191 @@ +<?php + +namespace Sabre\HTTP\Auth; + +use Sabre\HTTP\Request; +use Sabre\HTTP\Response; + +class DigestTest extends \PHPUnit_Framework_TestCase { + + /** + * @var Sabre\HTTP\Response + */ + private $response; + + /** + * request + * + * @var Sabre\HTTP\Request + */ + private $request; + + /** + * @var Sabre\HTTP\Auth\Digest + */ + private $auth; + + const REALM = 'SabreDAV unittest'; + + function setUp() { + + $this->response = new Response(); + $this->request = new Request(); + $this->auth = new Digest(self::REALM, $this->request, $this->response); + + + } + + function testDigest() { + + list($nonce, $opaque) = $this->getServerTokens(); + + $username = 'admin'; + $password = 12345; + $nc = '00002'; + $cnonce = uniqid(); + + $digestHash = md5( + md5($username . ':' . self::REALM . ':' . $password) . ':' . + $nonce . ':' . + $nc . ':' . + $cnonce . ':' . + 'auth:' . + md5('GET' . ':' . '/') + ); + + $this->request->setMethod('GET'); + $this->request->setHeader('Authorization', 'Digest username="' . $username . '", realm="' . self::REALM . '", nonce="' . $nonce . '", uri="/", response="' . $digestHash . '", opaque="' . $opaque . '", qop=auth,nc=' . $nc . ',cnonce="' . $cnonce . '"'); + + $this->auth->init(); + + $this->assertEquals($username, $this->auth->getUsername()); + $this->assertEquals(self::REALM, $this->auth->getRealm()); + $this->assertTrue($this->auth->validateA1(md5($username . ':' . self::REALM . ':' . $password)), 'Authentication is deemed invalid through validateA1'); + $this->assertTrue($this->auth->validatePassword($password), 'Authentication is deemed invalid through validatePassword'); + + } + + function testInvalidDigest() { + + list($nonce, $opaque) = $this->getServerTokens(); + + $username = 'admin'; + $password = 12345; + $nc = '00002'; + $cnonce = uniqid(); + + $digestHash = md5( + md5($username . ':' . self::REALM . ':' . $password) . ':' . + $nonce . ':' . + $nc . ':' . + $cnonce . ':' . + 'auth:' . + md5('GET' . ':' . '/') + ); + + $this->request->setMethod('GET'); + $this->request->setHeader('Authorization', 'Digest username="' . $username . '", realm="' . self::REALM . '", nonce="' . $nonce . '", uri="/", response="' . $digestHash . '", opaque="' . $opaque . '", qop=auth,nc=' . $nc . ',cnonce="' . $cnonce . '"'); + + $this->auth->init(); + + $this->assertFalse($this->auth->validateA1(md5($username . ':' . self::REALM . ':' . ($password . 'randomness'))), 'Authentication is deemed invalid through validateA1'); + + } + + function testInvalidDigest2() { + + $this->request->setMethod('GET'); + $this->request->setHeader('Authorization', 'basic blablabla'); + + $this->auth->init(); + $this->assertFalse($this->auth->validateA1(md5('user:realm:password'))); + + } + + + function testDigestAuthInt() { + + $this->auth->setQOP(Digest::QOP_AUTHINT); + list($nonce, $opaque) = $this->getServerTokens(Digest::QOP_AUTHINT); + + $username = 'admin'; + $password = 12345; + $nc = '00003'; + $cnonce = uniqid(); + + $digestHash = md5( + md5($username . ':' . self::REALM . ':' . $password) . ':' . + $nonce . ':' . + $nc . ':' . + $cnonce . ':' . + 'auth-int:' . + md5('POST' . ':' . '/' . ':' . md5('body')) + ); + + $this->request->setMethod('POST'); + $this->request->setHeader('Authorization', 'Digest username="' . $username . '", realm="' . self::REALM . '", nonce="' . $nonce . '", uri="/", response="' . $digestHash . '", opaque="' . $opaque . '", qop=auth-int,nc=' . $nc . ',cnonce="' . $cnonce . '"'); + $this->request->setBody('body'); + + $this->auth->init(); + + $this->assertTrue($this->auth->validateA1(md5($username . ':' . self::REALM . ':' . $password)), 'Authentication is deemed invalid through validateA1'); + + } + + function testDigestAuthBoth() { + + $this->auth->setQOP(Digest::QOP_AUTHINT | Digest::QOP_AUTH); + list($nonce, $opaque) = $this->getServerTokens(Digest::QOP_AUTHINT | Digest::QOP_AUTH); + + $username = 'admin'; + $password = 12345; + $nc = '00003'; + $cnonce = uniqid(); + + $digestHash = md5( + md5($username . ':' . self::REALM . ':' . $password) . ':' . + $nonce . ':' . + $nc . ':' . + $cnonce . ':' . + 'auth-int:' . + md5('POST' . ':' . '/' . ':' . md5('body')) + ); + + $this->request->setMethod('POST'); + $this->request->setHeader('Authorization', 'Digest username="' . $username . '", realm="' . self::REALM . '", nonce="' . $nonce . '", uri="/", response="' . $digestHash . '", opaque="' . $opaque . '", qop=auth-int,nc=' . $nc . ',cnonce="' . $cnonce . '"'); + $this->request->setBody('body'); + + $this->auth->init(); + + $this->assertTrue($this->auth->validateA1(md5($username . ':' . self::REALM . ':' . $password)), 'Authentication is deemed invalid through validateA1'); + + } + + + private function getServerTokens($qop = Digest::QOP_AUTH) { + + $this->auth->requireLogin(); + + switch ($qop) { + case Digest::QOP_AUTH : $qopstr = 'auth'; break; + case Digest::QOP_AUTHINT : $qopstr = 'auth-int'; break; + default : $qopstr = 'auth,auth-int'; break; + } + + $test = preg_match('/Digest realm="' . self::REALM . '",qop="' . $qopstr . '",nonce="([0-9a-f]*)",opaque="([0-9a-f]*)"/', + $this->response->getHeader('WWW-Authenticate'), $matches); + + $this->assertTrue($test == true, 'The WWW-Authenticate response didn\'t match our pattern. We received: ' . $this->response->getHeader('WWW-Authenticate')); + + $nonce = $matches[1]; + $opaque = $matches[2]; + + // Reset our environment + $this->setUp(); + $this->auth->setQOP($qop); + + return [$nonce,$opaque]; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/ClientTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/ClientTest.php new file mode 100644 index 0000000..ea25907 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/ClientTest.php @@ -0,0 +1,474 @@ +<?php + +namespace Sabre\HTTP; + +class ClientTest extends \PHPUnit_Framework_TestCase { + + function testCreateCurlSettingsArrayGET() { + + $client = new ClientMock(); + $client->addCurlSetting(CURLOPT_POSTREDIR, 0); + + $request = new Request('GET', 'http://example.org/', ['X-Foo' => 'bar']); + + $settings = [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HEADER => true, + CURLOPT_POSTREDIR => 0, + CURLOPT_HTTPHEADER => ['X-Foo: bar'], + CURLOPT_NOBODY => false, + CURLOPT_URL => 'http://example.org/', + CURLOPT_CUSTOMREQUEST => 'GET', + CURLOPT_POSTFIELDS => '', + CURLOPT_PUT => false, + CURLOPT_USERAGENT => 'sabre-http/' . Version::VERSION . ' (http://sabre.io/)', + ]; + + // FIXME: CURLOPT_PROTOCOLS and CURLOPT_REDIR_PROTOCOLS are currently unsupported by HHVM + // at least if this unit test fails in the future we know it is :) + if (defined('HHVM_VERSION') === false) { + $settings[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; + $settings[CURLOPT_REDIR_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; + } + + + $this->assertEquals($settings, $client->createCurlSettingsArray($request)); + + } + + function testCreateCurlSettingsArrayHEAD() { + + $client = new ClientMock(); + $request = new Request('HEAD', 'http://example.org/', ['X-Foo' => 'bar']); + + + $settings = [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HEADER => true, + CURLOPT_NOBODY => true, + CURLOPT_CUSTOMREQUEST => 'HEAD', + CURLOPT_HTTPHEADER => ['X-Foo: bar'], + CURLOPT_URL => 'http://example.org/', + CURLOPT_POSTFIELDS => '', + CURLOPT_PUT => false, + CURLOPT_USERAGENT => 'sabre-http/' . Version::VERSION . ' (http://sabre.io/)', + ]; + + // FIXME: CURLOPT_PROTOCOLS and CURLOPT_REDIR_PROTOCOLS are currently unsupported by HHVM + // at least if this unit test fails in the future we know it is :) + if (defined('HHVM_VERSION') === false) { + $settings[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; + $settings[CURLOPT_REDIR_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; + } + + $this->assertEquals($settings, $client->createCurlSettingsArray($request)); + + } + + function testCreateCurlSettingsArrayGETAfterHEAD() { + + $client = new ClientMock(); + $request = new Request('HEAD', 'http://example.org/', ['X-Foo' => 'bar']); + + // Parsing the settings for this method, and discarding the result. + // This will cause the client to automatically persist previous + // settings and will help us detect problems. + $client->createCurlSettingsArray($request); + + // This is the real request. + $request = new Request('GET', 'http://example.org/', ['X-Foo' => 'bar']); + + $settings = [ + CURLOPT_CUSTOMREQUEST => 'GET', + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HEADER => true, + CURLOPT_HTTPHEADER => ['X-Foo: bar'], + CURLOPT_NOBODY => false, + CURLOPT_URL => 'http://example.org/', + CURLOPT_POSTFIELDS => '', + CURLOPT_PUT => false, + CURLOPT_USERAGENT => 'sabre-http/' . Version::VERSION . ' (http://sabre.io/)', + ]; + + // FIXME: CURLOPT_PROTOCOLS and CURLOPT_REDIR_PROTOCOLS are currently unsupported by HHVM + // at least if this unit test fails in the future we know it is :) + if (defined('HHVM_VERSION') === false) { + $settings[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; + $settings[CURLOPT_REDIR_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; + } + + $this->assertEquals($settings, $client->createCurlSettingsArray($request)); + + } + + function testCreateCurlSettingsArrayPUTStream() { + + $client = new ClientMock(); + + $h = fopen('php://memory', 'r+'); + fwrite($h, 'booh'); + $request = new Request('PUT', 'http://example.org/', ['X-Foo' => 'bar'], $h); + + $settings = [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HEADER => true, + CURLOPT_PUT => true, + CURLOPT_INFILE => $h, + CURLOPT_NOBODY => false, + CURLOPT_CUSTOMREQUEST => 'PUT', + CURLOPT_HTTPHEADER => ['X-Foo: bar'], + CURLOPT_URL => 'http://example.org/', + CURLOPT_USERAGENT => 'sabre-http/' . Version::VERSION . ' (http://sabre.io/)', + ]; + + // FIXME: CURLOPT_PROTOCOLS and CURLOPT_REDIR_PROTOCOLS are currently unsupported by HHVM + // at least if this unit test fails in the future we know it is :) + if (defined('HHVM_VERSION') === false) { + $settings[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; + $settings[CURLOPT_REDIR_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; + } + + $this->assertEquals($settings, $client->createCurlSettingsArray($request)); + + } + + function testCreateCurlSettingsArrayPUTString() { + + $client = new ClientMock(); + $request = new Request('PUT', 'http://example.org/', ['X-Foo' => 'bar'], 'boo'); + + $settings = [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HEADER => true, + CURLOPT_NOBODY => false, + CURLOPT_POSTFIELDS => 'boo', + CURLOPT_CUSTOMREQUEST => 'PUT', + CURLOPT_HTTPHEADER => ['X-Foo: bar'], + CURLOPT_URL => 'http://example.org/', + CURLOPT_USERAGENT => 'sabre-http/' . Version::VERSION . ' (http://sabre.io/)', + ]; + + // FIXME: CURLOPT_PROTOCOLS and CURLOPT_REDIR_PROTOCOLS are currently unsupported by HHVM + // at least if this unit test fails in the future we know it is :) + if (defined('HHVM_VERSION') === false) { + $settings[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; + $settings[CURLOPT_REDIR_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; + } + + $this->assertEquals($settings, $client->createCurlSettingsArray($request)); + + } + + function testSend() { + + $client = new ClientMock(); + $request = new Request('GET', 'http://example.org/'); + + $client->on('doRequest', function($request, &$response) { + $response = new Response(200); + }); + + $response = $client->send($request); + + $this->assertEquals(200, $response->getStatus()); + + } + + function testSendClientError() { + + $client = new ClientMock(); + $request = new Request('GET', 'http://example.org/'); + + $client->on('doRequest', function($request, &$response) { + throw new ClientException('aaah', 1); + }); + $called = false; + $client->on('exception', function() use (&$called) { + $called = true; + }); + + try { + $client->send($request); + $this->fail('send() should have thrown an exception'); + } catch (ClientException $e) { + + } + $this->assertTrue($called); + + } + + function testSendHttpError() { + + $client = new ClientMock(); + $request = new Request('GET', 'http://example.org/'); + + $client->on('doRequest', function($request, &$response) { + $response = new Response(404); + }); + $called = 0; + $client->on('error', function() use (&$called) { + $called++; + }); + $client->on('error:404', function() use (&$called) { + $called++; + }); + + $client->send($request); + $this->assertEquals(2, $called); + + } + + function testSendRetry() { + + $client = new ClientMock(); + $request = new Request('GET', 'http://example.org/'); + + $called = 0; + $client->on('doRequest', function($request, &$response) use (&$called) { + $called++; + if ($called < 3) { + $response = new Response(404); + } else { + $response = new Response(200); + } + }); + + $errorCalled = 0; + $client->on('error', function($request, $response, &$retry, $retryCount) use (&$errorCalled) { + + $errorCalled++; + $retry = true; + + }); + + $response = $client->send($request); + $this->assertEquals(3, $called); + $this->assertEquals(2, $errorCalled); + $this->assertEquals(200, $response->getStatus()); + + } + + function testHttpErrorException() { + + $client = new ClientMock(); + $client->setThrowExceptions(true); + $request = new Request('GET', 'http://example.org/'); + + $client->on('doRequest', function($request, &$response) { + $response = new Response(404); + }); + + try { + $client->send($request); + $this->fail('An exception should have been thrown'); + } catch (ClientHttpException $e) { + $this->assertEquals(404, $e->getHttpStatus()); + $this->assertInstanceOf('Sabre\HTTP\Response', $e->getResponse()); + } + + } + + function testParseCurlResult() { + + $client = new ClientMock(); + $client->on('curlStuff', function(&$return) { + + $return = [ + [ + 'header_size' => 33, + 'http_code' => 200, + ], + 0, + '', + ]; + + }); + + $body = "HTTP/1.1 200 OK\r\nHeader1:Val1\r\n\r\nFoo"; + $result = $client->parseCurlResult($body, 'foobar'); + + $this->assertEquals(Client::STATUS_SUCCESS, $result['status']); + $this->assertEquals(200, $result['http_code']); + $this->assertEquals(200, $result['response']->getStatus()); + $this->assertEquals(['Header1' => ['Val1']], $result['response']->getHeaders()); + $this->assertEquals('Foo', $result['response']->getBodyAsString()); + + } + + function testParseCurlError() { + + $client = new ClientMock(); + $client->on('curlStuff', function(&$return) { + + $return = [ + [], + 1, + 'Curl error', + ]; + + }); + + $body = "HTTP/1.1 200 OK\r\nHeader1:Val1\r\n\r\nFoo"; + $result = $client->parseCurlResult($body, 'foobar'); + + $this->assertEquals(Client::STATUS_CURLERROR, $result['status']); + $this->assertEquals(1, $result['curl_errno']); + $this->assertEquals('Curl error', $result['curl_errmsg']); + + } + + function testDoRequest() { + + $client = new ClientMock(); + $request = new Request('GET', 'http://example.org/'); + $client->on('curlExec', function(&$return) { + + $return = "HTTP/1.1 200 OK\r\nHeader1:Val1\r\n\r\nFoo"; + + }); + $client->on('curlStuff', function(&$return) { + + $return = [ + [ + 'header_size' => 33, + 'http_code' => 200, + ], + 0, + '', + ]; + + }); + $response = $client->doRequest($request); + $this->assertEquals(200, $response->getStatus()); + $this->assertEquals(['Header1' => ['Val1']], $response->getHeaders()); + $this->assertEquals('Foo', $response->getBodyAsString()); + + } + + function testDoRequestCurlError() { + + $client = new ClientMock(); + $request = new Request('GET', 'http://example.org/'); + $client->on('curlExec', function(&$return) { + + $return = ""; + + }); + $client->on('curlStuff', function(&$return) { + + $return = [ + [], + 1, + 'Curl error', + ]; + + }); + + try { + $response = $client->doRequest($request); + $this->fail('This should have thrown an exception'); + } catch (ClientException $e) { + $this->assertEquals(1, $e->getCode()); + $this->assertEquals('Curl error', $e->getMessage()); + } + + } + +} + +class ClientMock extends Client { + + protected $persistedSettings = []; + + /** + * Making this method public. + * + * We are also going to persist all settings this method generates. While + * the underlying object doesn't behave exactly the same, it helps us + * simulate what curl does internally, and helps us identify problems with + * settings that are set by _some_ methods and not correctly reset by other + * methods after subsequent use. + * forces + */ + function createCurlSettingsArray(RequestInterface $request) { + + $settings = parent::createCurlSettingsArray($request); + $settings = $settings + $this->persistedSettings; + $this->persistedSettings = $settings; + return $settings; + + } + /** + * Making this method public. + */ + function parseCurlResult($response, $curlHandle) { + + return parent::parseCurlResult($response, $curlHandle); + + } + + /** + * This method is responsible for performing a single request. + * + * @param RequestInterface $request + * @return ResponseInterface + */ + function doRequest(RequestInterface $request) { + + $response = null; + $this->emit('doRequest', [$request, &$response]); + + // If nothing modified $response, we're using the default behavior. + if (is_null($response)) { + return parent::doRequest($request); + } else { + return $response; + } + + } + + /** + * Returns a bunch of information about a curl request. + * + * This method exists so it can easily be overridden and mocked. + * + * @param resource $curlHandle + * @return array + */ + protected function curlStuff($curlHandle) { + + $return = null; + $this->emit('curlStuff', [&$return]); + + // If nothing modified $return, we're using the default behavior. + if (is_null($return)) { + return parent::curlStuff($curlHandle); + } else { + return $return; + } + + } + + /** + * Calls curl_exec + * + * This method exists so it can easily be overridden and mocked. + * + * @param resource $curlHandle + * @return string + */ + protected function curlExec($curlHandle) { + + $return = null; + $this->emit('curlExec', [&$return]); + + // If nothing modified $return, we're using the default behavior. + if (is_null($return)) { + return parent::curlExec($curlHandle); + } else { + return $return; + } + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/FunctionsTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/FunctionsTest.php new file mode 100644 index 0000000..a107d1f --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/FunctionsTest.php @@ -0,0 +1,121 @@ +<?php + +namespace Sabre\HTTP; + +class FunctionsTest extends \PHPUnit_Framework_TestCase { + + /** + * @dataProvider getHeaderValuesData + */ + function testGetHeaderValues($input, $output) { + + $this->assertEquals( + $output, + getHeaderValues($input) + ); + + } + + function getHeaderValuesData() { + + return [ + [ + "a", + ["a"] + ], + [ + "a,b", + ["a", "b"] + ], + [ + "a, b", + ["a", "b"] + ], + [ + ["a, b"], + ["a", "b"] + ], + [ + ["a, b", "c", "d,e"], + ["a", "b", "c", "d", "e"] + ], + ]; + + } + + /** + * @dataProvider preferData + */ + function testPrefer($input, $output) { + + $this->assertEquals( + $output, + parsePrefer($input) + ); + + } + + function preferData() { + + return [ + [ + 'foo; bar', + ['foo' => true] + ], + [ + 'foo; bar=""', + ['foo' => true] + ], + [ + 'foo=""; bar', + ['foo' => true] + ], + [ + 'FOO', + ['foo' => true] + ], + [ + 'respond-async', + ['respond-async' => true] + ], + [ + + ['respond-async, wait=100', 'handling=lenient'], + ['respond-async' => true, 'wait' => 100, 'handling' => 'lenient'] + ], + [ + + ['respond-async, wait=100, handling=lenient'], + ['respond-async' => true, 'wait' => 100, 'handling' => 'lenient'] + ], + // Old values + [ + + 'return-asynch, return-representation', + ['respond-async' => true, 'return' => 'representation'], + ], + [ + + 'return-minimal', + ['return' => 'minimal'], + ], + [ + + 'strict', + ['handling' => 'strict'], + ], + [ + + 'lenient', + ['handling' => 'lenient'], + ], + // Invalid token + [ + ['foo=%bar%'], + [], + ] + ]; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/MessageDecoratorTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/MessageDecoratorTest.php new file mode 100644 index 0000000..6e2074c --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/MessageDecoratorTest.php @@ -0,0 +1,90 @@ +<?php + +namespace Sabre\HTTP; + +class MessageDecoratorTest extends \PHPUnit_Framework_TestCase { + + protected $inner; + protected $outer; + + function setUp() { + + $this->inner = new Request(); + $this->outer = new RequestDecorator($this->inner); + + } + + function testBody() { + + $this->outer->setBody('foo'); + $this->assertEquals('foo', stream_get_contents($this->inner->getBodyAsStream())); + $this->assertEquals('foo', stream_get_contents($this->outer->getBodyAsStream())); + $this->assertEquals('foo', $this->inner->getBodyAsString()); + $this->assertEquals('foo', $this->outer->getBodyAsString()); + $this->assertEquals('foo', $this->inner->getBody()); + $this->assertEquals('foo', $this->outer->getBody()); + + } + + function testHeaders() { + + $this->outer->setHeaders([ + 'a' => 'b', + ]); + + $this->assertEquals(['a' => ['b']], $this->inner->getHeaders()); + $this->assertEquals(['a' => ['b']], $this->outer->getHeaders()); + + $this->outer->setHeaders([ + 'c' => 'd', + ]); + + $this->assertEquals(['a' => ['b'], 'c' => ['d']], $this->inner->getHeaders()); + $this->assertEquals(['a' => ['b'], 'c' => ['d']], $this->outer->getHeaders()); + + $this->outer->addHeaders([ + 'e' => 'f', + ]); + + $this->assertEquals(['a' => ['b'], 'c' => ['d'], 'e' => ['f']], $this->inner->getHeaders()); + $this->assertEquals(['a' => ['b'], 'c' => ['d'], 'e' => ['f']], $this->outer->getHeaders()); + } + + function testHeader() { + + $this->assertFalse($this->outer->hasHeader('a')); + $this->assertFalse($this->inner->hasHeader('a')); + $this->outer->setHeader('a', 'c'); + $this->assertTrue($this->outer->hasHeader('a')); + $this->assertTrue($this->inner->hasHeader('a')); + + $this->assertEquals('c', $this->inner->getHeader('A')); + $this->assertEquals('c', $this->outer->getHeader('A')); + + $this->outer->addHeader('A', 'd'); + + $this->assertEquals( + ['c', 'd'], + $this->inner->getHeaderAsArray('A') + ); + $this->assertEquals( + ['c', 'd'], + $this->outer->getHeaderAsArray('A') + ); + + $this->outer->removeHeader('a'); + + $this->assertNull($this->inner->getHeader('A')); + $this->assertNull($this->outer->getHeader('A')); + } + + function testHttpVersion() { + + $this->outer->setHttpVersion('1.0'); + + $this->assertEquals('1.0', $this->inner->getHttpVersion()); + $this->assertEquals('1.0', $this->outer->getHttpVersion()); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/MessageTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/MessageTest.php new file mode 100644 index 0000000..0ca19ba --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/MessageTest.php @@ -0,0 +1,224 @@ +<?php + +namespace Sabre\HTTP; + +class MessageTest extends \PHPUnit_Framework_TestCase { + + function testConstruct() { + + $message = new MessageMock(); + $this->assertInstanceOf('Sabre\HTTP\Message', $message); + + } + + function testStreamBody() { + + $body = 'foo'; + $h = fopen('php://memory', 'r+'); + fwrite($h, $body); + rewind($h); + + $message = new MessageMock(); + $message->setBody($h); + + $this->assertEquals($body, $message->getBodyAsString()); + rewind($h); + $this->assertEquals($body, stream_get_contents($message->getBodyAsStream())); + rewind($h); + $this->assertEquals($body, stream_get_contents($message->getBody())); + + } + + function testStringBody() { + + $body = 'foo'; + + $message = new MessageMock(); + $message->setBody($body); + + $this->assertEquals($body, $message->getBodyAsString()); + $this->assertEquals($body, stream_get_contents($message->getBodyAsStream())); + $this->assertEquals($body, $message->getBody()); + + } + + /** + * It's possible that streams contains more data than the Content-Length. + * + * The request object should make sure to never emit more than + * Content-Length, if Content-Length is set. + * + * This is in particular useful when respoding to range requests with + * streams that represent files on the filesystem, as it's possible to just + * seek the stream to a certain point, set the content-length and let the + * request object do the rest. + */ + function testLongStreamToStringBody() { + + $body = fopen('php://memory', 'r+'); + fwrite($body, 'abcdefg'); + fseek($body, 2); + + $message = new MessageMock(); + $message->setBody($body); + $message->setHeader('Content-Length', '4'); + + $this->assertEquals( + 'cdef', + $message->getBodyAsString() + ); + + } + + function testGetEmptyBodyStream() { + + $message = new MessageMock(); + $body = $message->getBodyAsStream(); + + $this->assertEquals('', stream_get_contents($body)); + + } + + function testGetEmptyBodyString() { + + $message = new MessageMock(); + $body = $message->getBodyAsString(); + + $this->assertEquals('', $body); + + } + + function testHeaders() { + + $message = new MessageMock(); + $message->setHeader('X-Foo', 'bar'); + + // Testing caselessness + $this->assertEquals('bar', $message->getHeader('X-Foo')); + $this->assertEquals('bar', $message->getHeader('x-fOO')); + + $this->assertTrue( + $message->removeHeader('X-FOO') + ); + $this->assertNull($message->getHeader('X-Foo')); + $this->assertFalse( + $message->removeHeader('X-FOO') + ); + + } + + function testSetHeaders() { + + $message = new MessageMock(); + + $headers = [ + 'X-Foo' => ['1'], + 'X-Bar' => ['2'], + ]; + + $message->setHeaders($headers); + $this->assertEquals($headers, $message->getHeaders()); + + $message->setHeaders([ + 'X-Foo' => ['3', '4'], + 'X-Bar' => '5', + ]); + + $expected = [ + 'X-Foo' => ['3','4'], + 'X-Bar' => ['5'], + ]; + + $this->assertEquals($expected, $message->getHeaders()); + + } + + function testAddHeaders() { + + $message = new MessageMock(); + + $headers = [ + 'X-Foo' => ['1'], + 'X-Bar' => ['2'], + ]; + + $message->addHeaders($headers); + $this->assertEquals($headers, $message->getHeaders()); + + $message->addHeaders([ + 'X-Foo' => ['3', '4'], + 'X-Bar' => '5', + ]); + + $expected = [ + 'X-Foo' => ['1','3','4'], + 'X-Bar' => ['2','5'], + ]; + + $this->assertEquals($expected, $message->getHeaders()); + + } + + function testSendBody() { + + $message = new MessageMock(); + + // String + $message->setBody('foo'); + + // Stream + $h = fopen('php://memory', 'r+'); + fwrite($h, 'bar'); + rewind($h); + $message->setBody($h); + + $body = $message->getBody(); + rewind($body); + + $this->assertEquals('bar', stream_get_contents($body)); + + } + + function testMultipleHeaders() { + + $message = new MessageMock(); + $message->setHeader('a', '1'); + $message->addHeader('A', '2'); + + $this->assertEquals( + "1,2", + $message->getHeader('A') + ); + $this->assertEquals( + "1,2", + $message->getHeader('a') + ); + + $this->assertEquals( + ['1', '2'], + $message->getHeaderAsArray('a') + ); + $this->assertEquals( + ['1', '2'], + $message->getHeaderAsArray('A') + ); + $this->assertEquals( + [], + $message->getHeaderAsArray('B') + ); + + } + + function testHasHeaders() { + + $message = new MessageMock(); + + $this->assertFalse($message->hasHeader('X-Foo')); + $message->setHeader('X-Foo', 'Bar'); + $this->assertTrue($message->hasHeader('X-Foo')); + + } + +} + +class MessageMock extends Message { } diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/RequestDecoratorTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/RequestDecoratorTest.php new file mode 100644 index 0000000..08af487 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/RequestDecoratorTest.php @@ -0,0 +1,112 @@ +<?php + +namespace Sabre\HTTP; + +class RequestDecoratorTest extends \PHPUnit_Framework_TestCase { + + protected $inner; + protected $outer; + + function setUp() { + + $this->inner = new Request(); + $this->outer = new RequestDecorator($this->inner); + + } + + function testMethod() { + + $this->outer->setMethod('FOO'); + $this->assertEquals('FOO', $this->inner->getMethod()); + $this->assertEquals('FOO', $this->outer->getMethod()); + + } + + function testUrl() { + + $this->outer->setUrl('/foo'); + $this->assertEquals('/foo', $this->inner->getUrl()); + $this->assertEquals('/foo', $this->outer->getUrl()); + + } + + function testAbsoluteUrl() { + + $this->outer->setAbsoluteUrl('http://example.org/foo'); + $this->assertEquals('http://example.org/foo', $this->inner->getAbsoluteUrl()); + $this->assertEquals('http://example.org/foo', $this->outer->getAbsoluteUrl()); + + } + + function testBaseUrl() { + + $this->outer->setBaseUrl('/foo'); + $this->assertEquals('/foo', $this->inner->getBaseUrl()); + $this->assertEquals('/foo', $this->outer->getBaseUrl()); + + } + + function testPath() { + + $this->outer->setBaseUrl('/foo'); + $this->outer->setUrl('/foo/bar'); + $this->assertEquals('bar', $this->inner->getPath()); + $this->assertEquals('bar', $this->outer->getPath()); + + } + + function testQueryParams() { + + $this->outer->setUrl('/foo?a=b&c=d&e'); + $expected = [ + 'a' => 'b', + 'c' => 'd', + 'e' => null, + ]; + + $this->assertEquals($expected, $this->inner->getQueryParameters()); + $this->assertEquals($expected, $this->outer->getQueryParameters()); + + } + + function testPostData() { + + $postData = [ + 'a' => 'b', + 'c' => 'd', + 'e' => null, + ]; + + $this->outer->setPostData($postData); + $this->assertEquals($postData, $this->inner->getPostData()); + $this->assertEquals($postData, $this->outer->getPostData()); + + } + + + function testServerData() { + + $serverData = [ + 'HTTPS' => 'On', + ]; + + $this->outer->setRawServerData($serverData); + $this->assertEquals('On', $this->inner->getRawServerValue('HTTPS')); + $this->assertEquals('On', $this->outer->getRawServerValue('HTTPS')); + + $this->assertNull($this->inner->getRawServerValue('FOO')); + $this->assertNull($this->outer->getRawServerValue('FOO')); + } + + function testToString() { + + $this->inner->setMethod('POST'); + $this->inner->setUrl('/foo/bar/'); + $this->inner->setBody('foo'); + $this->inner->setHeader('foo', 'bar'); + + $this->assertEquals((string)$this->inner, (string)$this->outer); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/RequestTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/RequestTest.php new file mode 100644 index 0000000..e3daab4 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/RequestTest.php @@ -0,0 +1,167 @@ +<?php + +namespace Sabre\HTTP; + +class RequestTest extends \PHPUnit_Framework_TestCase { + + function testConstruct() { + + $request = new Request('GET', '/foo', [ + 'User-Agent' => 'Evert', + ]); + $this->assertEquals('GET', $request->getMethod()); + $this->assertEquals('/foo', $request->getUrl()); + $this->assertEquals([ + 'User-Agent' => ['Evert'], + ], $request->getHeaders()); + + } + + function testGetQueryParameters() { + + $request = new Request('GET', '/foo?a=b&c&d=e'); + $this->assertEquals([ + 'a' => 'b', + 'c' => null, + 'd' => 'e', + ], $request->getQueryParameters()); + + } + + function testGetQueryParametersNoData() { + + $request = new Request('GET', '/foo'); + $this->assertEquals([], $request->getQueryParameters()); + + } + + /** + * @backupGlobals + */ + function testCreateFromPHPRequest() { + + $_SERVER['REQUEST_METHOD'] = 'PUT'; + + $request = Sapi::getRequest(); + $this->assertEquals('PUT', $request->getMethod()); + + } + + function testGetAbsoluteUrl() { + + $s = [ + 'HTTP_HOST' => 'sabredav.org', + 'REQUEST_URI' => '/foo' + ]; + + $r = Sapi::createFromServerArray($s); + + $this->assertEquals('http://sabredav.org/foo', $r->getAbsoluteUrl()); + + $s = [ + 'HTTP_HOST' => 'sabredav.org', + 'REQUEST_URI' => '/foo', + 'HTTPS' => 'on', + ]; + + $r = Sapi::createFromServerArray($s); + + $this->assertEquals('https://sabredav.org/foo', $r->getAbsoluteUrl()); + + } + + function testGetPostData() { + + $post = [ + 'bla' => 'foo', + ]; + $r = new Request(); + $r->setPostData($post); + $this->assertEquals($post, $r->getPostData()); + + } + + function testGetPath() { + + $request = new Request(); + $request->setBaseUrl('/foo'); + $request->setUrl('/foo/bar/'); + + $this->assertEquals('bar', $request->getPath()); + + } + + function testGetPathStrippedQuery() { + + $request = new Request(); + $request->setBaseUrl('/foo'); + $request->setUrl('/foo/bar/?a=b'); + + $this->assertEquals('bar', $request->getPath()); + + } + + function testGetPathMissingSlash() { + + $request = new Request(); + $request->setBaseUrl('/foo/'); + $request->setUrl('/foo'); + + $this->assertEquals('', $request->getPath()); + + } + + /** + * @expectedException \LogicException + */ + function testGetPathOutsideBaseUrl() { + + $request = new Request(); + $request->setBaseUrl('/foo/'); + $request->setUrl('/bar/'); + + $request->getPath(); + + } + + function testToString() { + + $request = new Request('PUT', '/foo/bar', ['Content-Type' => 'text/xml']); + $request->setBody('foo'); + + $expected = <<<HI +PUT /foo/bar HTTP/1.1\r +Content-Type: text/xml\r +\r +foo +HI; + $this->assertEquals($expected, (string)$request); + + } + + function testToStringAuthorization() { + + $request = new Request('PUT', '/foo/bar', ['Content-Type' => 'text/xml', 'Authorization' => 'Basic foobar']); + $request->setBody('foo'); + + $expected = <<<HI +PUT /foo/bar HTTP/1.1\r +Content-Type: text/xml\r +Authorization: Basic REDACTED\r +\r +foo +HI; + $this->assertEquals($expected, (string)$request); + + } + + /** + * @expectedException \InvalidArgumentException + */ + function testConstructorWithArray() { + + $request = new Request([]); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/ResponseDecoratorTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/ResponseDecoratorTest.php new file mode 100644 index 0000000..838953b --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/ResponseDecoratorTest.php @@ -0,0 +1,37 @@ +<?php + +namespace Sabre\HTTP; + +class ResponseDecoratorTest extends \PHPUnit_Framework_TestCase { + + protected $inner; + protected $outer; + + function setUp() { + + $this->inner = new Response(); + $this->outer = new ResponseDecorator($this->inner); + + } + + function testStatus() { + + $this->outer->setStatus(201); + $this->assertEquals(201, $this->inner->getStatus()); + $this->assertEquals(201, $this->outer->getStatus()); + $this->assertEquals('Created', $this->inner->getStatusText()); + $this->assertEquals('Created', $this->outer->getStatusText()); + + } + + function testToString() { + + $this->inner->setStatus(201); + $this->inner->setBody('foo'); + $this->inner->setHeader('foo', 'bar'); + + $this->assertEquals((string)$this->inner, (string)$this->outer); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/ResponseTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/ResponseTest.php new file mode 100644 index 0000000..117551b --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/ResponseTest.php @@ -0,0 +1,48 @@ +<?php + +namespace Sabre\HTTP; + +class ResponseTest extends \PHPUnit_Framework_TestCase { + + function testConstruct() { + + $response = new Response(200, ['Content-Type' => 'text/xml']); + $this->assertEquals(200, $response->getStatus()); + $this->assertEquals('OK', $response->getStatusText()); + + } + + function testSetStatus() { + + $response = new Response(); + $response->setStatus('402 Where\'s my money?'); + $this->assertEquals(402, $response->getStatus()); + $this->assertEquals('Where\'s my money?', $response->getStatusText()); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testInvalidStatus() { + + $response = new Response(1000); + + } + + function testToString() { + + $response = new Response(200, ['Content-Type' => 'text/xml']); + $response->setBody('foo'); + + $expected = <<<HI +HTTP/1.1 200 OK\r +Content-Type: text/xml\r +\r +foo +HI; + $this->assertEquals($expected, (string)$response); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/SapiTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/SapiTest.php new file mode 100644 index 0000000..2dae3e0 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/SapiTest.php @@ -0,0 +1,167 @@ +<?php + +namespace Sabre\HTTP; + +class SapiTest extends \PHPUnit_Framework_TestCase { + + function testConstructFromServerArray() { + + $request = Sapi::createFromServerArray([ + 'REQUEST_URI' => '/foo', + 'REQUEST_METHOD' => 'GET', + 'HTTP_USER_AGENT' => 'Evert', + 'CONTENT_TYPE' => 'text/xml', + 'CONTENT_LENGTH' => '400', + 'SERVER_PROTOCOL' => 'HTTP/1.0', + ]); + + $this->assertEquals('GET', $request->getMethod()); + $this->assertEquals('/foo', $request->getUrl()); + $this->assertEquals([ + 'User-Agent' => ['Evert'], + 'Content-Type' => ['text/xml'], + 'Content-Length' => ['400'], + ], $request->getHeaders()); + + $this->assertEquals('1.0', $request->getHttpVersion()); + + $this->assertEquals('400', $request->getRawServerValue('CONTENT_LENGTH')); + $this->assertNull($request->getRawServerValue('FOO')); + + } + + function testConstructPHPAuth() { + + $request = Sapi::createFromServerArray([ + 'REQUEST_URI' => '/foo', + 'REQUEST_METHOD' => 'GET', + 'PHP_AUTH_USER' => 'user', + 'PHP_AUTH_PW' => 'pass', + ]); + + $this->assertEquals('GET', $request->getMethod()); + $this->assertEquals('/foo', $request->getUrl()); + $this->assertEquals([ + 'Authorization' => ['Basic ' . base64_encode('user:pass')], + ], $request->getHeaders()); + + } + + function testConstructPHPAuthDigest() { + + $request = Sapi::createFromServerArray([ + 'REQUEST_URI' => '/foo', + 'REQUEST_METHOD' => 'GET', + 'PHP_AUTH_DIGEST' => 'blabla', + ]); + + $this->assertEquals('GET', $request->getMethod()); + $this->assertEquals('/foo', $request->getUrl()); + $this->assertEquals([ + 'Authorization' => ['Digest blabla'], + ], $request->getHeaders()); + + } + + function testConstructRedirectAuth() { + + $request = Sapi::createFromServerArray([ + 'REQUEST_URI' => '/foo', + 'REQUEST_METHOD' => 'GET', + 'REDIRECT_HTTP_AUTHORIZATION' => 'Basic bla', + ]); + + $this->assertEquals('GET', $request->getMethod()); + $this->assertEquals('/foo', $request->getUrl()); + $this->assertEquals([ + 'Authorization' => ['Basic bla'], + ], $request->getHeaders()); + + } + + /** + * @runInSeparateProcess + * + * Unfortunately we have no way of testing if the HTTP response code got + * changed. + */ + function testSend() { + + if (!function_exists('xdebug_get_headers')) { + $this->markTestSkipped('XDebug needs to be installed for this test to run'); + } + + $response = new Response(204, ['Content-Type' => 'text/xml;charset=UTF-8']); + + // Second Content-Type header. Normally this doesn't make sense. + $response->addHeader('Content-Type', 'application/xml'); + $response->setBody('foo'); + + ob_start(); + + Sapi::sendResponse($response); + $headers = xdebug_get_headers(); + + $result = ob_get_clean(); + header_remove(); + + $this->assertEquals( + [ + "Content-Type: text/xml;charset=UTF-8", + "Content-Type: application/xml", + ], + $headers + ); + + $this->assertEquals('foo', $result); + + } + + /** + * @runInSeparateProcess + * @depends testSend + */ + function testSendLimitedByContentLengthString() { + + $response = new Response(200); + + $response->addHeader('Content-Length', 19); + $response->setBody('Send this sentence. Ignore this one.'); + + ob_start(); + + Sapi::sendResponse($response); + + $result = ob_get_clean(); + header_remove(); + + $this->assertEquals('Send this sentence.', $result); + + } + + /** + * @runInSeparateProcess + * @depends testSend + */ + function testSendLimitedByContentLengthStream() { + + $response = new Response(200, ['Content-Length' => 19]); + + $body = fopen('php://memory', 'w'); + fwrite($body, 'Ignore this. Send this sentence. Ignore this too.'); + rewind($body); + fread($body, 13); + $response->setBody($body); + + ob_start(); + + Sapi::sendResponse($response); + + $result = ob_get_clean(); + header_remove(); + + $this->assertEquals('Send this sentence.', $result); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/URLUtilTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/URLUtilTest.php new file mode 100644 index 0000000..e135f4f --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/URLUtilTest.php @@ -0,0 +1,187 @@ +<?php + +namespace Sabre\HTTP; + +class URLUtilTest extends \PHPUnit_Framework_TestCase{ + + function testEncodePath() { + + $str = ''; + for ($i = 0;$i < 128;$i++) $str .= chr($i); + + $newStr = URLUtil::encodePath($str); + + $this->assertEquals( + '%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f' . + '%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f' . + '%20%21%22%23%24%25%26%27()%2a%2b%2c-./' . + '0123456789:%3b%3c%3d%3e%3f' . + '@ABCDEFGHIJKLMNO' . + 'PQRSTUVWXYZ%5b%5c%5d%5e_' . + '%60abcdefghijklmno' . + 'pqrstuvwxyz%7b%7c%7d~%7f', + $newStr); + + $this->assertEquals($str, URLUtil::decodePath($newStr)); + + } + + function testEncodePathSegment() { + + $str = ''; + for ($i = 0;$i < 128;$i++) $str .= chr($i); + + $newStr = URLUtil::encodePathSegment($str); + + // Note: almost exactly the same as the last test, with the + // exception of the encoding of / (ascii code 2f) + $this->assertEquals( + '%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f' . + '%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f' . + '%20%21%22%23%24%25%26%27()%2a%2b%2c-.%2f' . + '0123456789:%3b%3c%3d%3e%3f' . + '@ABCDEFGHIJKLMNO' . + 'PQRSTUVWXYZ%5b%5c%5d%5e_' . + '%60abcdefghijklmno' . + 'pqrstuvwxyz%7b%7c%7d~%7f', + $newStr); + + $this->assertEquals($str, URLUtil::decodePathSegment($newStr)); + + } + + function testDecode() { + + $str = 'Hello%20Test+Test2.txt'; + $newStr = URLUtil::decodePath($str); + $this->assertEquals('Hello Test+Test2.txt', $newStr); + + } + + /** + * @depends testDecode + */ + function testDecodeUmlaut() { + + $str = 'Hello%C3%BC.txt'; + $newStr = URLUtil::decodePath($str); + $this->assertEquals("Hello\xC3\xBC.txt", $newStr); + + } + + /** + * @depends testDecodeUmlaut + */ + function testDecodeUmlautLatin1() { + + $str = 'Hello%FC.txt'; + $newStr = URLUtil::decodePath($str); + $this->assertEquals("Hello\xC3\xBC.txt", $newStr); + + } + + /** + * This testcase was sent by a bug reporter + * + * @depends testDecode + */ + function testDecodeAccentsWindows7() { + + $str = '/webdav/%C3%A0fo%C3%B3'; + $newStr = URLUtil::decodePath($str); + $this->assertEquals(strtolower($str), URLUtil::encodePath($newStr)); + + } + + function testSplitPath() { + + $strings = [ + + // input // expected result + '/foo/bar' => ['/foo','bar'], + '/foo/bar/' => ['/foo','bar'], + 'foo/bar/' => ['foo','bar'], + 'foo/bar' => ['foo','bar'], + 'foo/bar/baz' => ['foo/bar','baz'], + 'foo/bar/baz/' => ['foo/bar','baz'], + 'foo' => ['','foo'], + 'foo/' => ['','foo'], + '/foo/' => ['','foo'], + '/foo' => ['','foo'], + '' => [null,null], + + // UTF-8 + "/\xC3\xA0fo\xC3\xB3/bar" => ["/\xC3\xA0fo\xC3\xB3",'bar'], + "/\xC3\xA0foo/b\xC3\xBCr/" => ["/\xC3\xA0foo","b\xC3\xBCr"], + "foo/\xC3\xA0\xC3\xBCr" => ["foo","\xC3\xA0\xC3\xBCr"], + + ]; + + foreach ($strings as $input => $expected) { + + $output = URLUtil::splitPath($input); + $this->assertEquals($expected, $output, 'The expected output for \'' . $input . '\' was incorrect'); + + + } + + } + + /** + * @dataProvider resolveData + */ + function testResolve($base, $update, $expected) { + + $this->assertEquals( + $expected, + URLUtil::resolve($base, $update) + ); + + } + + function resolveData() { + + return [ + [ + 'http://example.org/foo/baz', + '/bar', + 'http://example.org/bar', + ], + [ + 'https://example.org/foo', + '//example.net/', + 'https://example.net/', + ], + [ + 'https://example.org/foo', + '?a=b', + 'https://example.org/foo?a=b', + ], + [ + '//example.org/foo', + '?a=b', + '//example.org/foo?a=b', + ], + // Ports and fragments + [ + 'https://example.org:81/foo#hey', + '?a=b#c=d', + 'https://example.org:81/foo?a=b#c=d', + ], + // Relative.. in-directory paths + [ + 'http://example.org/foo/bar', + 'bar2', + 'http://example.org/foo/bar2', + ], + // Now the base path ended with a slash + [ + 'http://example.org/foo/bar/', + 'bar2/bar3', + 'http://example.org/foo/bar/bar2/bar3', + ], + ]; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/UtilTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/UtilTest.php new file mode 100644 index 0000000..5659bdd --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/HTTP/UtilTest.php @@ -0,0 +1,206 @@ +<?php + +namespace Sabre\HTTP; + +class UtilTest extends \PHPUnit_Framework_TestCase { + + function testParseHTTPDate() { + + $times = [ + 'Wed, 13 Oct 2010 10:26:00 GMT', + 'Wednesday, 13-Oct-10 10:26:00 GMT', + 'Wed Oct 13 10:26:00 2010', + ]; + + $expected = 1286965560; + + foreach ($times as $time) { + $result = Util::parseHTTPDate($time); + $this->assertEquals($expected, $result->format('U')); + } + + $result = Util::parseHTTPDate('Wed Oct 6 10:26:00 2010'); + $this->assertEquals(1286360760, $result->format('U')); + + } + + function testParseHTTPDateFail() { + + $times = [ + //random string + 'NOW', + // not-GMT timezone + 'Wednesday, 13-Oct-10 10:26:00 UTC', + // No space before the 6 + 'Wed Oct 6 10:26:00 2010', + // Invalid day + 'Wed Oct 0 10:26:00 2010', + 'Wed Oct 32 10:26:00 2010', + 'Wed, 0 Oct 2010 10:26:00 GMT', + 'Wed, 32 Oct 2010 10:26:00 GMT', + 'Wednesday, 32-Oct-10 10:26:00 GMT', + // Invalid hour + 'Wed, 13 Oct 2010 24:26:00 GMT', + 'Wednesday, 13-Oct-10 24:26:00 GMT', + 'Wed Oct 13 24:26:00 2010', + ]; + + foreach ($times as $time) { + $this->assertFalse(Util::parseHTTPDate($time), 'We used the string: ' . $time); + } + + } + + function testTimezones() { + + $default = date_default_timezone_get(); + date_default_timezone_set('Europe/Amsterdam'); + + $this->testParseHTTPDate(); + + date_default_timezone_set($default); + + } + + function testToHTTPDate() { + + $dt = new \DateTime('2011-12-10 12:00:00 +0200'); + + $this->assertEquals( + 'Sat, 10 Dec 2011 10:00:00 GMT', + Util::toHTTPDate($dt) + ); + + } + + /** + * @dataProvider negotiateData + */ + function testNegotiate($acceptHeader, $available, $expected) { + + $this->assertEquals( + $expected, + Util::negotiate($acceptHeader, $available) + ); + + } + + function negotiateData() { + + return [ + [ // simple + 'application/xml', + ['application/xml'], + 'application/xml', + ], + [ // no header + null, + ['application/xml'], + 'application/xml', + ], + [ // 2 options + 'application/json', + ['application/xml', 'application/json'], + 'application/json', + ], + [ // 2 choices + 'application/json, application/xml', + ['application/xml'], + 'application/xml', + ], + [ // quality + 'application/xml;q=0.2, application/json', + ['application/xml', 'application/json'], + 'application/json', + ], + [ // wildcard + 'image/jpeg, image/png, */*', + ['application/xml', 'application/json'], + 'application/xml', + ], + [ // wildcard + quality + 'image/jpeg, image/png; q=0.5, */*', + ['application/xml', 'application/json', 'image/png'], + 'application/xml', + ], + [ // no match + 'image/jpeg', + ['application/xml'], + null, + ], + [ // This is used in sabre/dav + 'text/vcard; version=4.0', + [ + // 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', + ], + 'text/vcard; version=4.0', + + ], + [ // rfc7231 example 1 + 'audio/*; q=0.2, audio/basic', + [ + 'audio/pcm', + 'audio/basic', + ], + 'audio/basic', + ], + [ // Lower quality after + 'audio/pcm; q=0.2, audio/basic; q=0.1', + [ + 'audio/pcm', + 'audio/basic', + ], + 'audio/pcm', + ], + [ // Random parameter, should be ignored + 'audio/pcm; hello; q=0.2, audio/basic; q=0.1', + [ + 'audio/pcm', + 'audio/basic', + ], + 'audio/pcm', + ], + [ // No whitepace after type, should pick the one that is the most specific. + 'text/vcard;version=3.0, text/vcard', + [ + 'text/vcard', + 'text/vcard; version=3.0' + ], + 'text/vcard; version=3.0', + ], + [ // Same as last one, but order is different + 'text/vcard, text/vcard;version=3.0', + [ + 'text/vcard; version=3.0', + 'text/vcard', + ], + 'text/vcard; version=3.0', + ], + [ // Charset should be ignored here. + 'text/vcard; charset=utf-8; version=3.0, text/vcard', + [ + 'text/vcard', + 'text/vcard; version=3.0' + ], + 'text/vcard; version=3.0', + ], + [ // Undefined offset issue. + 'text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2', + ['application/xml', 'application/json', 'image/png'], + 'application/xml', + ], + + ]; + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/bootstrap.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/bootstrap.php new file mode 100644 index 0000000..74931b6 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/bootstrap.php @@ -0,0 +1,8 @@ +<?php + +date_default_timezone_set('UTC'); + +ini_set('error_reporting', E_ALL | E_STRICT | E_DEPRECATED); + +// Composer autoloader +include __DIR__ . '/../vendor/autoload.php'; diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/phpcs/ruleset.xml b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/phpcs/ruleset.xml new file mode 100644 index 0000000..ec2c4c8 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/phpcs/ruleset.xml @@ -0,0 +1,57 @@ +<?xml version="1.0"?> +<ruleset name="sabre.php"> + <description>sabre.io codesniffer ruleset</description> + + <!-- Include the whole PSR-1 standard --> + <rule ref="PSR1" /> + + <!-- All PHP files MUST use the Unix LF (linefeed) line ending. --> + <rule ref="Generic.Files.LineEndings"> + <properties> + <property name="eolChar" value="\n"/> + </properties> + </rule> + + <!-- The closing ?> tag MUST be omitted from files containing only PHP. --> + <rule ref="Zend.Files.ClosingTag"/> + + <!-- There MUST NOT be trailing whitespace at the end of non-blank lines. --> + <rule ref="Squiz.WhiteSpace.SuperfluousWhitespace"> + <properties> + <property name="ignoreBlankLines" value="true"/> + </properties> + </rule> + + <!-- There MUST NOT be more than one statement per line. --> + <rule ref="Generic.Formatting.DisallowMultipleStatements"/> + + <rule ref="Generic.WhiteSpace.ScopeIndent"> + <properties> + <property name="ignoreIndentationTokens" type="array" value="T_COMMENT,T_DOC_COMMENT"/> + </properties> + </rule> + <rule ref="Generic.WhiteSpace.DisallowTabIndent"/> + + <!-- PHP keywords MUST be in lower case. --> + <rule ref="Generic.PHP.LowerCaseKeyword"/> + + <!-- The PHP constants true, false, and null MUST be in lower case. --> + <rule ref="Generic.PHP.LowerCaseConstant"/> + + <!-- <rule ref="Squiz.Scope.MethodScope"/> --> + <rule ref="Squiz.WhiteSpace.ScopeKeywordSpacing"/> + + <!-- In the argument list, there MUST NOT be a space before each comma, and there MUST be one space after each comma. --> + <!-- + <rule ref="Squiz.Functions.FunctionDeclarationArgumentSpacing"> + <properties> + <property name="equalsSpacing" value="1"/> + </properties> + </rule> + <rule ref="Squiz.Functions.FunctionDeclarationArgumentSpacing.SpacingAfterHint"> + <severity>0</severity> + </rule> + --> + <rule ref="PEAR.WhiteSpace.ScopeClosingBrace"/> + +</ruleset> diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/phpunit.xml b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/phpunit.xml new file mode 100644 index 0000000..32d701a --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/http/tests/phpunit.xml @@ -0,0 +1,18 @@ +<phpunit + colors="true" + bootstrap="bootstrap.php" + convertErrorsToExceptions="true" + convertNoticesToExceptions="true" + convertWarningsToExceptions="true" + strict="true" + > + <testsuite name="Sabre_HTTP"> + <directory>HTTP/</directory> + </testsuite> + + <filter> + <whitelist addUncoveredFilesFromWhitelist="true"> + <directory suffix=".php">../lib/</directory> + </whitelist> + </filter> +</phpunit> diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/.gitignore b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/.gitignore new file mode 100644 index 0000000..19d1aff --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/.gitignore @@ -0,0 +1,13 @@ +# Composer +vendor/ +composer.lock + +# Tests +tests/cov/ + +# Composer binaries +bin/phpunit +bin/phpcs + +# Vim +.*.swp diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/.travis.yml b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/.travis.yml new file mode 100644 index 0000000..f7d1a06 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/.travis.yml @@ -0,0 +1,14 @@ +language: php +php: + - 5.4 + - 5.5 + - 5.6 + - hhvm + - 7 + +script: + - ./bin/phpunit --configuration tests/phpunit.xml.dist + - ./bin/sabre-cs-fixer fix lib/ --dry-run --diff + +before_script: composer install --dev + diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/CHANGELOG.md b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/CHANGELOG.md new file mode 100644 index 0000000..9fa510d --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/CHANGELOG.md @@ -0,0 +1,34 @@ +ChangeLog +========= + +1.1.0 (2016-03-07) +------------------ + +* #6: PHP's `parse_url()` corrupts strings if they contain certain + non ascii-characters such as Chinese or Hebrew. sabre/uri's `parse()` + function now percent-encodes these characters beforehand. + + +1.0.1 (2015-04-28) +------------------ + +* #4: Using php-cs-fixer to automatically enforce conding standards. +* #5: Resolving to and building `mailto:` urls were not correctly handled. + + +1.0.0 (2015-01-27) +------------------ + +* Added a `normalize` function. +* Added a `buildUri` function. +* Fixed a bug in the `resolve` when only a new fragment is specified. + +San José, CalConnect XXXII release! + +0.0.1 (2014-11-17) +------------------ + +* First version! +* Source was lifted from sabre/http package. +* Provides a `resolve` and a `split` function. +* Requires PHP 5.4.8 and up. diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/LICENSE b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/LICENSE new file mode 100644 index 0000000..9a3a919 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/LICENSE @@ -0,0 +1,27 @@ +Copyright (C) 2014-2016 fruux GmbH (https://fruux.com/) + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name Sabre nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/README.md b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/README.md new file mode 100644 index 0000000..76f55d8 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/README.md @@ -0,0 +1,55 @@ +sabre/uri +========= + +sabre/uri is a lightweight library that provides several functions for working +with URIs, staying true to the rules of [RFC3986][2]. + +Partially inspired by [Node.js URL library][3], and created to solve real +problems in PHP applications. 100% unitested and many tests are based on +examples from RFC3986. + +The library provides the following functions: + +1. `resolve` to resolve relative urls. +2. `normalize` to aid in comparing urls. +3. `parse`, which works like PHP's [parse_url][6]. +4. `build` to do the exact opposite of `parse`. +5. `split` to easily get the 'dirname' and 'basename' of a URL without all the + problems those two functions have. + + +Further reading +--------------- + +* [Installation][7] +* [Usage][8] + + +Build status +------------ + +| branch | status | +| ------ | ------ | +| master | [![Build Status](https://travis-ci.org/fruux/sabre-uri.svg?branch=master)](https://travis-ci.org/fruux/sabre-uri) | + + +Questions? +---------- + +Head over to the [sabre/dav mailinglist][4], or you can also just open a ticket +on [GitHub][5]. + + +Made at fruux +------------- + +This library is being developed by [fruux](https://fruux.com/). Drop us a line for commercial services or enterprise support. + +[1]: http://sabre.io/uri/ +[2]: https://tools.ietf.org/html/rfc3986/ +[3]: http://nodejs.org/api/url.html +[4]: http://groups.google.com/group/sabredav-discuss +[5]: https://github.com/fruux/sabre-uri/issues/ +[6]: http://php.net/manual/en/function.parse-url.php +[7]: http://sabre.io/uri/install/ +[8]: http://sabre.io/uri/usage/ diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/composer.json b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/composer.json new file mode 100644 index 0000000..7b48acb --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/composer.json @@ -0,0 +1,41 @@ +{ + "name": "sabre/uri", + "description": "Functions for making sense out of URIs.", + "keywords": [ + "URI", + "URL", + "rfc3986" + ], + "homepage": "http://sabre.io/uri/", + "license": "BSD-3-Clause", + "require": { + "php": ">=5.4.7" + }, + "authors": [ + { + "name": "Evert Pot", + "email": "me@evertpot.com", + "homepage": "http://evertpot.com/", + "role": "Developer" + } + ], + "support": { + "forum": "https://groups.google.com/group/sabredav-discuss", + "source": "https://github.com/fruux/sabre-uri" + }, + "autoload": { + "files" : [ + "lib/functions.php" + ], + "psr-4" : { + "Sabre\\Uri\\" : "lib/" + } + }, + "require-dev": { + "sabre/cs": "~0.0.1", + "phpunit/phpunit" : "*" + }, + "config" : { + "bin-dir" : "bin/" + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/lib/Version.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/lib/Version.php new file mode 100644 index 0000000..88678d4 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/lib/Version.php @@ -0,0 +1,19 @@ +<?php + +namespace Sabre\Uri; + +/** + * This class contains the version number for this package. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ + */ +class Version { + + /** + * Full version number + */ + const VERSION = '1.1.0'; + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/lib/functions.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/lib/functions.php new file mode 100644 index 0000000..06181ae --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/lib/functions.php @@ -0,0 +1,282 @@ +<?php + +namespace Sabre\Uri; + +/** + * This file contains all the uri handling functions. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ + */ + +/** + * Resolves relative urls, like a browser would. + * + * This function takes a basePath, which itself _may_ also be relative, and + * then applies the relative path on top of it. + * + * @param string $basePath + * @param string $newPath + * @return string + */ +function resolve($basePath, $newPath) { + + $base = parse($basePath); + $delta = parse($newPath); + + $pick = function($part) use ($base, $delta) { + + if ($delta[$part]) { + return $delta[$part]; + } elseif ($base[$part]) { + return $base[$part]; + } + return null; + + }; + + // If the new path defines a scheme, it's absolute and we can just return + // that. + if ($delta['scheme']) { + return build($delta); + } + + $newParts = []; + + $newParts['scheme'] = $pick('scheme'); + $newParts['host'] = $pick('host'); + $newParts['port'] = $pick('port'); + + $path = ''; + if ($delta['path']) { + // If the path starts with a slash + if ($delta['path'][0] === '/') { + $path = $delta['path']; + } else { + // Removing last component from base path. + $path = $base['path']; + if (strpos($path, '/') !== false) { + $path = substr($path, 0, strrpos($path, '/')); + } + $path .= '/' . $delta['path']; + } + } else { + $path = $base['path'] ?: '/'; + } + // Removing .. and . + $pathParts = explode('/', $path); + $newPathParts = []; + foreach ($pathParts as $pathPart) { + + switch ($pathPart) { + //case '' : + case '.' : + break; + case '..' : + array_pop($newPathParts); + break; + default : + $newPathParts[] = $pathPart; + break; + } + } + + $path = implode('/', $newPathParts); + + // If the source url ended with a /, we want to preserve that. + $newParts['path'] = $path; + if ($delta['query']) { + $newParts['query'] = $delta['query']; + } elseif (!empty($base['query']) && empty($delta['host']) && empty($delta['path'])) { + // Keep the old query if host and path didn't change + $newParts['query'] = $base['query']; + } + if ($delta['fragment']) { + $newParts['fragment'] = $delta['fragment']; + } + return build($newParts); + +} + +/** + * Takes a URI or partial URI as its argument, and normalizes it. + * + * After normalizing a URI, you can safely compare it to other URIs. + * This function will for instance convert a %7E into a tilde, according to + * rfc3986. + * + * It will also change a %3a into a %3A. + * + * @param string $uri + * @return string + */ +function normalize($uri) { + + $parts = parse($uri); + + if (!empty($parts['path'])) { + $pathParts = explode('/', ltrim($parts['path'], '/')); + $newPathParts = []; + foreach ($pathParts as $pathPart) { + switch ($pathPart) { + case '.': + // skip + break; + case '..' : + // One level up in the hierarchy + array_pop($newPathParts); + break; + default : + // Ensuring that everything is correctly percent-encoded. + $newPathParts[] = rawurlencode(rawurldecode($pathPart)); + break; + } + } + $parts['path'] = '/' . implode('/', $newPathParts); + } + + if ($parts['scheme']) { + $parts['scheme'] = strtolower($parts['scheme']); + $defaultPorts = [ + 'http' => '80', + 'https' => '443', + ]; + + if (!empty($parts['port']) && isset($defaultPorts[$parts['scheme']]) && $defaultPorts[$parts['scheme']] == $parts['port']) { + // Removing default ports. + unset($parts['port']); + } + // A few HTTP specific rules. + switch ($parts['scheme']) { + case 'http' : + case 'https' : + if (empty($parts['path'])) { + // An empty path is equivalent to / in http. + $parts['path'] = '/'; + } + break; + } + } + + if ($parts['host']) $parts['host'] = strtolower($parts['host']); + + return build($parts); + +} + +/** + * Parses a URI and returns its individual components. + * + * This method largely behaves the same as PHP's parse_url, except that it will + * return an array with all the array keys, including the ones that are not + * set by parse_url, which makes it a bit easier to work with. + * + * Unlike PHP's parse_url, it will also convert any non-ascii characters to + * percent-encoded strings. PHP's parse_url corrupts these characters on OS X. + * + * @param string $uri + * @return array + */ +function parse($uri) { + + // Normally a URI must be ASCII, however. However, often it's not and + // parse_url might corrupt these strings. + // + // For that reason we take any non-ascii characters from the uri and + // uriencode them first. + $uri = preg_replace_callback( + '/[^[:ascii:]]/u', + function($matches) { + return rawurlencode($matches[0]); + }, + $uri + ); + + return + parse_url($uri) + [ + 'scheme' => null, + 'host' => null, + 'path' => null, + 'port' => null, + 'user' => null, + 'query' => null, + 'fragment' => null, + ]; + +} + +/** + * This function takes the components returned from PHP's parse_url, and uses + * it to generate a new uri. + * + * @param array $parts + * @return string + */ +function build(array $parts) { + + $uri = ''; + + $authority = ''; + if (!empty($parts['host'])) { + $authority = $parts['host']; + if (!empty($parts['user'])) { + $authority = $parts['user'] . '@' . $authority; + } + if (!empty($parts['port'])) { + $authority = $authority . ':' . $parts['port']; + } + } + + if (!empty($parts['scheme'])) { + // If there's a scheme, there's also a host. + $uri = $parts['scheme'] . ':'; + + } + if ($authority) { + // No scheme, but there is a host. + $uri .= '//' . $authority; + + } + + if (!empty($parts['path'])) { + $uri .= $parts['path']; + } + if (!empty($parts['query'])) { + $uri .= '?' . $parts['query']; + } + if (!empty($parts['fragment'])) { + $uri .= '#' . $parts['fragment']; + } + + return $uri; + +} + +/** + * Returns the 'dirname' and 'basename' for a path. + * + * The reason there is a custom function for this purpose, is because + * basename() is locale aware (behaviour changes if C locale or a UTF-8 locale + * is used) and we need a method that just operates on UTF-8 characters. + * + * In addition basename and dirname are platform aware, and will treat + * backslash (\) as a directory separator on windows. + * + * This method returns the 2 components as an array. + * + * If there is no dirname, it will return an empty string. Any / appearing at + * the end of the string is stripped off. + * + * @param string $path + * @return array + */ +function split($path) { + + $matches = []; + if (preg_match('/^(?:(?:(.*)(?:\/+))?([^\/]+))(?:\/?)$/u', $path, $matches)) { + return [$matches[1], $matches[2]]; + } + return [null,null]; + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/tests/BuildTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/tests/BuildTest.php new file mode 100644 index 0000000..366d66f --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/tests/BuildTest.php @@ -0,0 +1,39 @@ +<?php + +namespace Sabre\Uri; + +class BuildTest extends \PHPUnit_Framework_TestCase{ + + /** + * @dataProvider buildUriData + */ + function testBuild($value) { + + $this->assertEquals( + $value, + build(parse_url($value)) + ); + + } + + function buildUriData() { + + return [ + [ 'http://example.org/'], + [ 'http://example.org/foo/bar'], + [ '//example.org/foo/bar'], + [ '/foo/bar'], + [ 'http://example.org:81/'], + [ 'http://user@example.org:81/'], + [ 'http://example.org:81/hi?a=b'], + [ 'http://example.org:81/hi?a=b#c=d'], + // [ '//example.org:81/hi?a=b#c=d'], // Currently fails due to a + // PHP bug. + [ '/hi?a=b#c=d'], + [ '?a=b#c=d'], + [ '#c=d'], + ]; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/tests/NormalizeTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/tests/NormalizeTest.php new file mode 100644 index 0000000..e2cfd56 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/tests/NormalizeTest.php @@ -0,0 +1,42 @@ +<?php + +namespace Sabre\Uri; + +class NormalizeTest extends \PHPUnit_Framework_TestCase{ + + /** + * @dataProvider normalizeData + */ + function testNormalize($in, $out) { + + $this->assertEquals( + $out, + normalize($in) + ); + + } + + function normalizeData() { + + return [ + [ 'http://example.org/', 'http://example.org/' ], + [ 'HTTP://www.EXAMPLE.com/', 'http://www.example.com/'], + [ 'http://example.org/%7Eevert', 'http://example.org/~evert'], + [ 'http://example.org/./evert', 'http://example.org/evert'], + [ 'http://example.org/../evert', 'http://example.org/evert'], + [ 'http://example.org/foo/../evert', 'http://example.org/evert'], + [ '/%41', '/A'], + [ '/%3F', '/%3F'], + [ '/%3f', '/%3F'], + [ 'http://example.org', 'http://example.org/'], + [ 'http://example.org:/', 'http://example.org/'], + [ 'http://example.org:80/', 'http://example.org/'], + // See issue #6. parse_url corrupts strings like this, but only on + // macs. + //[ 'http://example.org/有词法别名.zh','http://example.org/%E6%9C%89%E8%AF%8D%E6%B3%95%E5%88%AB%E5%90%8D.zh'], + + ]; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/tests/ParseTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/tests/ParseTest.php new file mode 100644 index 0000000..da4df3d --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/tests/ParseTest.php @@ -0,0 +1,53 @@ +<?php + +namespace Sabre\Uri; + +class ParseTest extends \PHPUnit_Framework_TestCase{ + + /** + * @dataProvider parseData + */ + function testParse($in, $out) { + + $this->assertEquals( + $out, + parse($in) + ); + + } + + function parseData() { + + return [ + [ + 'http://example.org/hello?foo=bar#test', + [ + 'scheme' => 'http', + 'host' => 'example.org', + 'path' => '/hello', + 'port' => null, + 'user' => null, + 'query' => 'foo=bar', + 'fragment' => 'test' + ] + ], + // See issue #6. parse_url corrupts strings like this, but only on + // macs. + [ + 'http://example.org/有词法别名.zh', + [ + 'scheme' => 'http', + 'host' => 'example.org', + 'path' => '/%E6%9C%89%E8%AF%8D%E6%B3%95%E5%88%AB%E5%90%8D.zh', + 'port' => null, + 'user' => null, + 'query' => null, + 'fragment' => null + ] + ], + + ]; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/tests/ResolveTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/tests/ResolveTest.php new file mode 100644 index 0000000..73e4dea --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/tests/ResolveTest.php @@ -0,0 +1,83 @@ +<?php + +namespace Sabre\Uri; + +class ResolveTest extends \PHPUnit_Framework_TestCase{ + + /** + * @dataProvider resolveData + */ + function testResolve($base, $update, $expected) { + + $this->assertEquals( + $expected, + resolve($base, $update) + ); + + } + + function resolveData() { + + return [ + [ + 'http://example.org/foo/baz', + '/bar', + 'http://example.org/bar', + ], + [ + 'https://example.org/foo', + '//example.net/', + 'https://example.net/', + ], + [ + 'https://example.org/foo', + '?a=b', + 'https://example.org/foo?a=b', + ], + [ + '//example.org/foo', + '?a=b', + '//example.org/foo?a=b', + ], + // Ports and fragments + [ + 'https://example.org:81/foo#hey', + '?a=b#c=d', + 'https://example.org:81/foo?a=b#c=d', + ], + // Relative.. in-directory paths + [ + 'http://example.org/foo/bar', + 'bar2', + 'http://example.org/foo/bar2', + ], + // Now the base path ended with a slash + [ + 'http://example.org/foo/bar/', + 'bar2/bar3', + 'http://example.org/foo/bar/bar2/bar3', + ], + // .. and . + [ + 'http://example.org/foo/bar/', + '../bar2/.././/bar3/', + 'http://example.org/foo//bar3/', + ], + // Only updating the fragment + [ + 'https://example.org/foo?a=b', + '#comments', + 'https://example.org/foo?a=b#comments', + ], + // Switching to mailto! + [ + 'https://example.org/foo?a=b', + 'mailto:foo@example.org', + 'mailto:foo@example.org', + ], + + ]; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/tests/SplitTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/tests/SplitTest.php new file mode 100644 index 0000000..d8f053f --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/tests/SplitTest.php @@ -0,0 +1,41 @@ +<?php + +namespace Sabre\Uri; + +class SplitTest extends \PHPUnit_Framework_TestCase{ + + function testSplit() { + + $strings = [ + + // input // expected result + '/foo/bar' => ['/foo','bar'], + '/foo/bar/' => ['/foo','bar'], + 'foo/bar/' => ['foo','bar'], + 'foo/bar' => ['foo','bar'], + 'foo/bar/baz' => ['foo/bar','baz'], + 'foo/bar/baz/' => ['foo/bar','baz'], + 'foo' => ['','foo'], + 'foo/' => ['','foo'], + '/foo/' => ['','foo'], + '/foo' => ['','foo'], + '' => [null,null], + + // UTF-8 + "/\xC3\xA0fo\xC3\xB3/bar" => ["/\xC3\xA0fo\xC3\xB3",'bar'], + "/\xC3\xA0foo/b\xC3\xBCr/" => ["/\xC3\xA0foo","b\xC3\xBCr"], + "foo/\xC3\xA0\xC3\xBCr" => ["foo","\xC3\xA0\xC3\xBCr"], + + ]; + + foreach($strings as $input => $expected) { + + $output = split($input); + $this->assertEquals($expected, $output, 'The expected output for \'' . $input . '\' was incorrect'); + + + } + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/tests/phpcs/ruleset.xml b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/tests/phpcs/ruleset.xml new file mode 100644 index 0000000..ec2c4c8 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/tests/phpcs/ruleset.xml @@ -0,0 +1,57 @@ +<?xml version="1.0"?> +<ruleset name="sabre.php"> + <description>sabre.io codesniffer ruleset</description> + + <!-- Include the whole PSR-1 standard --> + <rule ref="PSR1" /> + + <!-- All PHP files MUST use the Unix LF (linefeed) line ending. --> + <rule ref="Generic.Files.LineEndings"> + <properties> + <property name="eolChar" value="\n"/> + </properties> + </rule> + + <!-- The closing ?> tag MUST be omitted from files containing only PHP. --> + <rule ref="Zend.Files.ClosingTag"/> + + <!-- There MUST NOT be trailing whitespace at the end of non-blank lines. --> + <rule ref="Squiz.WhiteSpace.SuperfluousWhitespace"> + <properties> + <property name="ignoreBlankLines" value="true"/> + </properties> + </rule> + + <!-- There MUST NOT be more than one statement per line. --> + <rule ref="Generic.Formatting.DisallowMultipleStatements"/> + + <rule ref="Generic.WhiteSpace.ScopeIndent"> + <properties> + <property name="ignoreIndentationTokens" type="array" value="T_COMMENT,T_DOC_COMMENT"/> + </properties> + </rule> + <rule ref="Generic.WhiteSpace.DisallowTabIndent"/> + + <!-- PHP keywords MUST be in lower case. --> + <rule ref="Generic.PHP.LowerCaseKeyword"/> + + <!-- The PHP constants true, false, and null MUST be in lower case. --> + <rule ref="Generic.PHP.LowerCaseConstant"/> + + <!-- <rule ref="Squiz.Scope.MethodScope"/> --> + <rule ref="Squiz.WhiteSpace.ScopeKeywordSpacing"/> + + <!-- In the argument list, there MUST NOT be a space before each comma, and there MUST be one space after each comma. --> + <!-- + <rule ref="Squiz.Functions.FunctionDeclarationArgumentSpacing"> + <properties> + <property name="equalsSpacing" value="1"/> + </properties> + </rule> + <rule ref="Squiz.Functions.FunctionDeclarationArgumentSpacing.SpacingAfterHint"> + <severity>0</severity> + </rule> + --> + <rule ref="PEAR.WhiteSpace.ScopeClosingBrace"/> + +</ruleset> diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/tests/phpunit.xml.dist b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/tests/phpunit.xml.dist new file mode 100644 index 0000000..338d24d --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/uri/tests/phpunit.xml.dist @@ -0,0 +1,18 @@ +<phpunit + colors="true" + bootstrap="../vendor/autoload.php" + convertErrorsToExceptions="true" + convertNoticesToExceptions="true" + convertWarningsToExceptions="true" + strict="true" + > + <testsuite name="sabre-uri"> + <directory>.</directory> + </testsuite> + + <filter> + <whitelist addUncoveredFilesFromWhitelist="true"> + <directory suffix=".php">../lib/</directory> + </whitelist> + </filter> +</phpunit> diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/.gitignore b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/.gitignore new file mode 100644 index 0000000..1c46a29 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/.gitignore @@ -0,0 +1,18 @@ +# Composer stuff +vendor/ +composer.lock +tests/cov/ +tests/temp + +#vim +.*.swp + +#binaries +bin/phpunit +bin/phpcs + +# Development stuff +testdata/ + +# OS X +.DS_Store diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/.travis.yml b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/.travis.yml new file mode 100644 index 0000000..40a7055 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/.travis.yml @@ -0,0 +1,19 @@ +language: php +php: + - 5.3 + - 5.4 + - 5.5 + - 5.6 + - 7 + - hhvm + +matrix: + allow_failures: + - php: hhvm + +script: + - phpunit --configuration tests/phpunit.xml + - ./bin/phpcs -p --standard=tests/phpcs/ruleset.xml lib/ + + +before_script: composer install diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/ChangeLog.md b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/ChangeLog.md new file mode 100644 index 0000000..9793334 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/ChangeLog.md @@ -0,0 +1,610 @@ +ChangeLog +========= + +3.5.1 (2016-04-06) +------------------ + +* #309: When expanding recurring events, the first event should also have a + `RECURRENCE-ID` property. +* #306: iTip REPLYs to the first instance of a recurring event was not handled + correctly. + + +3.5.0 (2016-01-11) +------------------ + +* This release supports PHP 7, contrary to 3.4.x versions. +* BC Break: `Sabre\VObject\Property\Float` has been renamed to + `Sabre\VObject\Property\FloatValue`. +* BC Break: `Sabre\VObject\Property\Integer` has been renamed to + `Sabre\VObject\Property\IntegerValue`. + + +3.4.9 (2016-01-11) +------------------ + +* This package now specifies in composer.json that it does not support PHP 7. + For PHP 7, use version 3.5.x or 4.x. + + +3.4.8 (2016-01-04) +------------------ + +* #284: When generating `CANCEL` iTip messages, we now include `DTEND`. + (@kewisch). + + +3.4.7 (2015-09-05) +------------------ + +* #253: Handle `isInTimeRange` for recurring events that have 0 valid + instances. (@DominikTo, @migrax). + + +3.4.6 (2015-08-06) +------------------ + +* #250: Recurring all-day events are incorrectly included in time range + requests when not using UTC in the time range. (@armin-hackmann) + + +3.4.5 (2015-06-02) +------------------ + +* #229: Converting vcards from 3.0 to 4.0 that contained a `LANG` property + would throw an error. + + +3.4.4 (2015-05-27) +------------------ + +* #228: Fixed a 'party crasher' bug in the iTip broker. This would break + scheduling in some cases. + + +3.4.3 (2015-05-19) +------------------ + +* #219: Corrected validation of `EXDATE` properties with more than one value. +* #212: `BYSETPOS` with values below `-1` was broken and could cause infinite + loops. +* #211: Fix `BYDAY=-5TH` in recurrence iterator. (@lindquist) +* #216: `ENCODING` parameter is now validated for all document types. +* #217: Initializing vCard `DATE` objects with a PHP DateTime object will now + work correctly. (@thomascube) + + +3.4.2 (2015-02-25) +------------------ + +* #210: iTip: Replying to an event without a master event was broken. + + +3.4.1 (2015-02-24) +------------------ + +* A minor change to ensure that unittests work correctly in the sabre/dav + test-suite. + + +3.4.0 (2015-02-23) +------------------ + +* #196: Made parsing recurrence rules a lot faster on big calendars. +* Updated windows timezone mappings to latest unicode version. +* #202: Support for parsing and validating `VAVAILABILITY` components. (@Hywan) +* #195: PHP 5.3 compatibility in 'generatevcards' script. (@rickdenhaan) +* #205: Improving handling of multiple `EXDATE` when processing iTip changes. + (@armin-hackmann) +* #187: Fixed validator rules for `LAST-MODIFIED` properties. +* #188: Retain floating times when generating instances using + `Recur\EventIterator`. +* #203: Skip tests for timezones that are not supported on older PHP versions, + instead of a hard fail. +* #204: Dealing a bit better with vCard date-time values that contained + milliseconds. (which is normally invalid). (@armin-hackmann) + + +3.3.5 (2015-01-09) +------------------ + +* #168: Expanding calendars now removes objects with recurrence rules that + don't have a valid recurrence instance. +* #177: SCHEDULE-STATUS should not contain a reason phrase, only a status + code. +* #175: Parser can now read and skip the UTF-8 BOM. +* #179: Added `isFloating` to `DATE-TIME` properties. +* #179: Fixed jCal serialization of floating `DATE-TIME` properties. +* #173: vCard converter failed for `X-ABDATE` properties that had no + `X-ABLABEL`. +* #180: Added `PROFILE_CALDAV` and `PROFILE_CARDDAV` to enable validation rules + specific for CalDAV/CardDAV servers. +* #176: A missing `UID` is no longer an error, but a warning for the vCard + validator, unless `PROFILE_CARDDAV` is specified. + + +3.3.4 (2014-11-19) +------------------ + +* #154: Converting `ANNIVERSARY` to `X-ANNIVERSARY` and `X-ABDATE` and + vice-versa when converting to/from vCard 4. +* #154: It's now possible to easily select all vCard properties belonging to + a single group with `$vcard->{'ITEM1.'}` syntax. (@armin-hackmann) +* #156: Simpler way to check if a string is UTF-8. (@Hywan) +* Unittest improvements. +* #159: The recurrence iterator, freebusy generator and iCalendar DATE and + DATE-TIME properties can now all accept a reference timezone when working + floating times or all-day events. +* #159: Master events will no longer get a `RECURRENCE-ID` when expanding. +* #159: `RECURRENCE-ID` for all-day events will now be correct when expanding. +* #163: Added a `getTimeZone()` method to `VTIMEZONE` components. + + +3.3.3 (2014-10-09) +------------------ + +* #142: `CANCEL` and `REPLY` messages now include the `DTSTART` from the + original event. +* #143: `SCHEDULE-AGENT` on the `ORGANIZER` property is respected. +* #144: `PARTSTAT=NEEDS-ACTION` is now set for new invites, if no `PARTSTAT` is + set to support the inbox feature of iOS. +* #147: Bugs related to scheduling all-day events. +* #148: Ignore events that have attendees but no organizer. +* #149: Avoiding logging errors during timezone detection. This is a workaround + for a PHP bug. +* Support for "Line Islands Standard Time" windows timezone. +* #154: Correctly work around vCard parameters that have a value but no name. + + +3.3.2 (2014-09-19) +------------------ + +* Changed: iTip broker now sets RSVP status to false when replies are received. +* #118: iTip Message now has a `getScheduleStatus()` method. +* #119: Support for detecting 'significant changes'. +* #120: Support for `SCHEDULE-FORCE-SEND`. +* #121: iCal demands parameters containing the + sign to be quoted. +* #122: Don't generate REPLY messages for events that have been cancelled. +* #123: Added `SUMMARY` to iTip messages. +* #130: Incorrect validation rules for `RELATED` (should be `RELATED-TO`). +* #128: `ATTACH` in iCalendar is `URI` by default, not `BINARY`. +* #131: RRULE that doesn't provide a single valid instance now throws an + exception. +* #136: Validator rejects *all* control characters. We were missing a few. +* #133: Splitter objects will throw exceptions when receiving incompatible + objects. +* #127: Attendees who delete recurring event instances events they had already + declined earlier will no longer generate another reply. +* #125: Send CANCEL messages when ORGANIZER property gets deleted. + + +3.3.1 (2014-08-18) +------------------ + +* Changed: It's now possible to pass DateTime objects when using the magic + setters on properties. (`$event->DTSTART = new DateTime('now')`). +* #111: iTip Broker does not process attendee adding events to EXDATE. +* #112: EventIterator now sets TZID on RECURRENCE-ID. +* #113: Timezone support during creation of iTip REPLY messages. +* #114: VTIMEZONE is retained when generating new REQUEST objects. +* #114: Support for 'MAILTO:' style email addresses (in uppercase) in the iTip + broker. This improves evolution support. +* #115: Using REQUEST-STATUS from REPLY messages and now propegating that into + SCHEDULE-STATUS. + + +3.3.0 (2014-08-07) +------------------ + +* We now use PSR-4 for the directory structure. This means that everything + that was used to be in the `lib/Sabre/VObject` directory is now moved to + `lib/`. If you use composer to load this library, you shouldn't have to do + anything about that though. +* VEVENT now get populated with a DTSTAMP and UID property by default. +* BC Break: Removed the 'includes.php' file. Use composer instead. +* #103: Added support for processing [iTip][iTip] messages. This allows a user + to parse incoming iTip messages and apply the result on existing calendars, + or automatically generate invites/replies/cancellations based on changes that + a user made on objects. +* #75, #58, #18: Fixes related to overriding the first event in recurrences. +* Added: VCalendar::getBaseComponent to find the 'master' component in a + calendar. +* #51: Support for iterating RDATE properties. +* Fixed: Issue #101: RecurrenceIterator::nextMonthly() shows events that are + excluded events with wrong time + + +3.2.4 (2014-07-14) +------------------ + +* Added: Issue #98. The VCardConverter now takes `X-APPLE-OMIT-YEAR` into + consideration when converting between vCard 3 and 4. +* Fixed: Issue #96. Some support for Yahoo's broken vcards. +* Fixed: PHP 5.3 support was broken in the cli tool. + + +3.2.3 (2014-06-12) +------------------ + +* Validator now checks if DUE and DTSTART are of the same type in VTODO, and + ensures that DUE is always after DTSTART. +* Removed documentation from source repository, to http://sabre.io/vobject/ +* Expanded the vobject cli tool validation output to make it easier to find + issues. +* Fixed: vobject repair. It was not working for iCalendar objects. + + +3.2.2 (2014-05-07) +------------------ + +* Minor tweak in unittests to make it run on PHP 5.5.12. Json-prettifying + slightly changed which caused the test to fail. + + +3.2.1 (2014-05-03) +------------------ + +* Minor tweak to make the unittests run with the latest hhvm on travis. +* Updated timezone definitions. +* Updated copyright links to point to http://sabre.io/ + + +3.2.0 (2014-04-02) +------------------ + +* Now hhvm compatible! +* The validator can now detect a _lot_ more problems. Many rules for both + iCalendar and vCard were added. +* Added: bin/generate_vcards, a utility to generate random vcards for testing + purposes. Patches are welcome to add more data. +* Updated: Windows timezone mapping to latest version from unicode.org +* Changed: The timezone maps are now loaded in from external files, in + lib/Sabre/VObject/timezonedata. +* Added: Fixing badly encoded URL's from google contacts vcards. +* Fixed: Issue #68. Couldn't decode properties ending in a colon. +* Fixed: Issue #72. RecurrenceIterator should respect timezone in the UNTIL + clause. +* Fixed: Issue #67. BYMONTH limit on DAILY recurrences. +* Fixed: Issue #26. Return a more descriptive error when coming across broken + BYDAY rules. +* Fixed: Issue #28. Incorrect timezone detection for some timezones. +* Fixed: Issue #70. Casting a parameter with a null value to string would fail. +* Added: Support for rfc6715 and rfc6474. +* Added: Support for DateTime objects in the VCard DATE-AND-OR-TIME property. +* Added: UUIDUtil, for easily creating unique identifiers. +* Fixed: Issue #83. Creating new VALUE=DATE objects using php's DateTime. +* Fixed: Issue #86. Don't go into an infinite loop when php errors are + disabled and an invalid file is read. + + +3.1.4 (2014-03-30) +------------------ + +* Fixed: Issue #87: Several compatibility fixes related to timezone handling + changes in PHP 5.5.10. + + +3.1.3 (2013-10-02) +------------------ + +* Fixed: Support from properties from draft-daboo-valarm-extensions-04. Issue + #56. +* Fixed: Issue #54. Parsing a stream of multiple vcards separated by more than + one newline. Thanks @Vedmak for the patch. +* Fixed: Serializing vcard 2.1 parameters with no name caused a literal '1' to + be inserted. +* Added: VCardConverter removed properties that are no longer supported in vCard + 4.0. +* Added: vCards with a minimum number of values (such as N), but don't have that + many, are now automatically padded with empty components. +* Added: The vCard validator now also checks for a minimum number of components, + and has the ability to repair these. +* Added: Some support for vCard 2.1 in the VCard converter, to upgrade to vCard + 3.0 or 4.0. +* Fixed: Issue 60 Use Document::$componentMap when instantiating the top-level + VCalendar and VCard components. +* Fixed: Issue 62: Parsing iCalendar parameters with no value. +* Added: --forgiving option to vobject utility. +* Fixed: Compound properties such as ADR were not correctly split up in vCard + 2.1 quoted printable-encoded properties. +* Fixed: Issue 64: Encoding of binary properties of converted vCards. Thanks + @DominikTo for the patch. + + +3.1.2 (2013-08-13) +------------------ + +* Fixed: Setting correct property group on VCard conversion + + +3.1.1 (2013-08-02) +------------------ + +* Fixed: Issue #53. A regression in RecurrenceIterator. + + +3.1.0 (2013-07-27) +------------------ + +* Added: bad-ass new cli debugging utility (in bin/vobject). +* Added: jCal and jCard parser. +* Fixed: URI properties should not escape ; and ,. +* Fixed: VCard 4 documents now correctly use URI as a default value-type for + PHOTO and others. BINARY no longer exists in vCard 4. +* Added: Utility to convert between 2.1, 3.0 and 4.0 vCards. +* Added: You can now add() multiple parameters to a property in one call. +* Added: Parameter::has() for easily checking if a parameter value exists. +* Added: VCard::preferred() to find a preferred email, phone number, etc for a + contact. +* Changed: All $duration properties are now public. +* Added: A few validators for iCalendar documents. +* Fixed: Issue #50. RecurrenceIterator gives incorrect result when exception + events are out of order in the iCalendar file. +* Fixed: Issue #48. Overridden events in the recurrence iterator that were past + the UNTIL date were ignored. +* Added: getDuration for DURATION values such as TRIGGER. Thanks to + @SimonSimCity. +* Fixed: Issue #52. vCard 2.1 parameters with no name may lose values if there's + more than 1. Thanks to @Vedmak. + + +3.0.0 (2013-06-21) +------------------ + +* Fixed: includes.php file was still broken. Our tool to generate it had some + bugs. + + +3.0.0-beta4 (2013-06-21) +------------------------ + +* Fixed: includes.php was no longer up to date. + + +3.0.0-beta3 (2013-06-17) +------------------------ + +* Added: OPTION_FORGIVING now also allows slashes in property names. +* Fixed: DateTimeParser no longer fails on dates with years < 1000 & > 4999 +* Fixed: Issue 36: Workaround for the recurrenceiterator and caldav events with + a missing base event. +* Fixed: jCard encoding of TIME properties. +* Fixed: jCal encoding of REQUEST-STATUS, GEO and PERIOD values. + + +3.0.0-beta2 (2013-06-10) +------------------------ + +* Fixed: Corrected includes.php file. +* Fixed: vCard date-time parser supported extended-format dates as well. +* Changed: Properties have been moved to an ICalendar or VCard directory. +* Fixed: Couldn't parse vCard 3 extended format dates and times. +* Fixed: Couldn't export jCard DATE values correctly. +* Fixed: Recursive loop in ICalendar\DateTime property. + + +3.0.0-beta1 (2013-06-07) +------------------------ + +* Added: jsonSerialize() for creating jCal and jCard documents. +* Added: helper method to parse vCard dates and times. +* Added: Specialized classes for FLOAT, LANGUAGE-TAG, TIME, TIMESTAMP, + DATE-AND-OR-TIME, CAL-ADDRESS, UNKNOWN and UTC-OFFSET properties. +* Removed: CommaSeparatedText property. Now included into Text. +* Fixed: Multiple parameters with the same name are now correctly encoded. +* Fixed: Parameter values containing a comma are now enclosed in double-quotes. +* Fixed: Iterating parameter values should now fully work as expected. +* Fixed: Support for vCard 2.1 nameless parameters. +* Changed: $valueMap, $componentMap and $propertyMap now all use fully-qualified + class names, so they are actually overridable. +* Fixed: Updating DATE-TIME to DATE values now behaves like expected. + + +3.0.0-alpha4 (2013-05-31) +------------------------- + +* Added: It's now possible to send parser options to the splitter classes. +* Added: A few tweaks to improve component and property creation. + + +3.0.0-alpha3 (2013-05-13) +------------------------- + +* Changed: propertyMap, valueMap and componentMap are now static properties. +* Changed: Component::remove() will throw an exception when trying to a node + that's not a child of said component. +* Added: Splitter objects are now faster, line numbers are accurately reported + and use less memory. +* Added: MimeDir parser can now continue parsing with the same stream buffer. +* Fixed: vobjectvalidate.php is operational again. +* Fixed: \r is properly stripped in text values. +* Fixed: QUOTED-PRINTABLE is now correctly encoded as well as encoded, for + vCards 2.1. +* Fixed: Parser assumes vCard 2.1, if no version was supplied. + + +3.0.0-alpha2 (2013-05-22) +------------------------- + +* Fixed: vCard URL properties were referencing a non-existant class. + + +3.0.0-alpha1 (2013-05-21) +------------------------- + +* Fixed: Now correctly dealing with escaping of properties. This solves the + problem with double-backslashes where they don't belong. +* Added: Easy support for properties with more than one value, using setParts + and getParts. +* Added: Support for broken 2.1 vCards produced by microsoft. +* Added: Automatically decoding quoted-printable values. +* Added: Automatically decoding base64 values. +* Added: Decoding RFC6868 parameter values (uses ^ as an escape character). +* Added: Fancy new MimeDir parser that can also parse streams. +* Added: Automatically mapping many, many properties to a property-class with + specialized API's. +* Added: remove() method for easily removing properties and sub-components + components. +* Changed: Components, Properties and Parameters can no longer be created with + Component::create, Property::create and Parameter::create. They must instead + be created through the root component. (A VCalendar or VCard object). +* Changed: API for DateTime properties has slightly changed. +* Changed: the ->value property is now protected everywhere. Use getParts() and + getValue() instead. +* BC Break: No support for mac newlines (\r). Never came across these anyway. +* Added: add() method to the Property class. +* Added: It's now possible to easy set multi-value properties as arrays. +* Added: When setting date-time properties you can just pass PHP's DateTime + object. +* Added: New components automatically get a bunch of default properties, such as + VERSION and CALSCALE. +* Added: You can add new sub-components much quicker with the magic setters, and + add() method. + + +2.1.7 (2015-01-21) +------------------ + +* Fixed: Issue #94, a workaround for bad escaping of ; and , in compound + properties. It's not a full solution, but it's an improvement for those + stuck in the 2.1 versions. + + +2.1.6 (2014-12-10) +------------------ + +* Fixed: Minor change to make sure that unittests succeed on every PHP version. + + +2.1.5 (2014-06-03) +------------------ + +* Fixed: #94: Better parameter escaping. +* Changed: Documentation cleanups. + + +2.1.4 (2014-03-30) +------------------ + +* Fixed: Issue #87: Several compatibility fixes related to timezone handling + changes in PHP 5.5.10. + + +2.1.3 (2013-10-02) +------------------ + +* Fixed: Issue #55. \r must be stripped from property values. +* Fixed: Issue #65. Putting quotes around parameter values that contain a colon. + + +2.1.2 (2013-08-02) +------------------ + +* Fixed: Issue #53. A regression in RecurrenceIterator. + + +2.1.1 (2013-07-27) +------------------ + +* Fixed: Issue #50. RecurrenceIterator gives incorrect result when exception + events are out of order in the iCalendar file. +* Fixed: Issue #48. Overridden events in the recurrence iterator that were past + the UNTIL date were ignored. + + +2.1.0 (2013-06-17) +------------------ + +* This version is fully backwards compatible with 2.0.\*. However, it contains a + few new API's that mimic the VObject 3 API. This allows it to be used a + 'bridge' version. Specifically, this new version exists so SabreDAV 1.7 and + 1.8 can run with both the 2 and 3 versions of this library. +* Added: Property\DateTime::hasTime(). +* Added: Property\MultiDateTime::hasTime(). +* Added: Property::getValue(). +* Added: Document class. +* Added: Document::createComponent and Document::createProperty. +* Added: Parameter::getValue(). + + +2.0.7 (2013-03-05) +------------------ + +* Fixed: Microsoft re-uses their magic numbers for different timezones, + specifically id 2 for both Sarajevo and Lisbon). A workaround was added to + deal with this. + + +2.0.6 (2013-02-17) +------------------ + +* Fixed: The reader now properly parses parameters without a value. + + +2.0.5 (2012-11-05) +------------------ + +* Fixed: The FreeBusyGenerator is now properly using the factory methods for + creation of components and properties. + + +2.0.4 (2012-11-02) +------------------ + +* Added: Known Lotus Notes / Domino timezone id's. + + +2.0.3 (2012-10-29) +------------------ + +* Added: Support for 'GMT+????' format in TZID's. +* Added: Support for formats like SystemV/EST5EDT in TZID's. +* Fixed: RecurrenceIterator now repairs recurrence rules where UNTIL < DTSTART. +* Added: Support for BYHOUR in FREQ=DAILY (@hollodk). +* Added: Support for BYHOUR and BYDAY in FREQ=WEEKLY. + + +2.0.2 (2012-10-06) +------------------ + +* Added: includes.php file, to load the entire library in one go. +* Fixed: A problem with determining alarm triggers for TODO's. + + +2.0.1 (2012-09-22) +------------------ + +* Removed: Element class. It wasn't used. +* Added: Basic validation and repair methods for broken input data. +* Fixed: RecurrenceIterator could infinitely loop when an INTERVAL of 0 was + specified. +* Added: A cli script that can validate and automatically repair vcards and + iCalendar objects. +* Added: A new 'Compound' property, that can automatically split up parts for + properties such as N, ADR, ORG and CATEGORIES. +* Added: Splitter classes, that can split up large objects (such as exports) + into individual objects (thanks @DominikTO and @armin-hackmann). +* Added: VFREEBUSY component, which allows easily checking wether timeslots are + available. +* Added: The Reader class now has a 'FORGIVING' option, which allows it to parse + properties with incorrect characters in the name (at this time, it just allows + underscores). +* Added: Also added the 'IGNORE_INVALID_LINES' option, to completely disregard + any invalid lines. +* Fixed: A bug in Windows timezone-id mappings for times created in Greenlands + timezone (sorry Greenlanders! I do care!). +* Fixed: DTEND was not generated correctly for VFREEBUSY reports. +* Fixed: Parser is at least 25% faster with real-world data. + + +2.0.0 (2012-08-08) +------------------ + +* VObject is now a separate project from SabreDAV. See the SabreDAV changelog + for version information before 2.0. +* New: VObject library now uses PHP 5.3 namespaces. +* New: It's possible to specify lists of parameters when constructing + properties. +* New: made it easier to construct the FreeBusyGenerator. + +[iTip]: http://tools.ietf.org/html/rfc5546 diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/LICENSE b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/LICENSE new file mode 100644 index 0000000..a99c8da --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/LICENSE @@ -0,0 +1,27 @@ +Copyright (C) 2011-2016 fruux GmbH (https://fruux.com/) + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name Sabre nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/README.md b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/README.md new file mode 100644 index 0000000..6440a69 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/README.md @@ -0,0 +1,50 @@ +sabre/vobject +============= + +The VObject library allows you to easily parse and manipulate [iCalendar](https://tools.ietf.org/html/rfc5545) +and [vCard](https://tools.ietf.org/html/rfc6350) objects using PHP. + +The goal of the VObject library is to create a very complete library, with an easy to use API. + +Build status +------------ + +| branch | status | +| ------ | ------ | +| master | [![Build Status](https://travis-ci.org/fruux/sabre-vobject.svg?branch=master)](https://travis-ci.org/fruux/sabre-vobject) | +| 3.4 | [![Build Status](https://travis-ci.org/fruux/sabre-vobject.svg?branch=3.4)](https://travis-ci.org/fruux/sabre-vobject) | +| 3.1 | [![Build Status](https://travis-ci.org/fruux/sabre-vobject.svg?branch=3.1)](https://travis-ci.org/fruux/sabre-vobject) | +| 2.1 | [![Build Status](https://travis-ci.org/fruux/sabre-vobject.svg?branch=2.1)](https://travis-ci.org/fruux/sabre-vobject) | +| 2.0 | [![Build Status](https://travis-ci.org/fruux/sabre-vobject.svg?branch=2.0)](https://travis-ci.org/fruux/sabre-vobject) | + + +Installation +------------ + +VObject requires PHP 5.3, and should be installed using composer. +The general composer instructions can be found on the [composer website](http://getcomposer.org/doc/00-intro.md composer website). + +After that, just declare the vobject dependency as follows: + + "require" : { + "sabre/vobject" : "~3.4" + } + +Then, run `composer.phar update` and you should be good. + +Usage +----- + +* [3.x documentation](http://sabre.io/vobject/usage/) +* [2.x documentation](http://sabre.io/vobject/usage_2/) +* [Migrating from 2.x to 3.x](http://sabre.io/vobject/upgrade/) + +Support +------- + +Head over to the [SabreDAV mailing list](http://groups.google.com/group/sabredav-discuss) for any questions. + +Made at fruux +------------- + +This library is being developed by [fruux](https://fruux.com/). Drop us a line for commercial services or enterprise support. diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/bin/bench.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/bin/bench.php new file mode 100755 index 0000000..b949c8e --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/bin/bench.php @@ -0,0 +1,12 @@ +#!/usr/bin/env php +<?php + +include __DIR__ . '/../vendor/autoload.php'; + +$data = stream_get_contents(STDIN); + +$start = microtime(true); + +$lol = Sabre\VObject\Reader::read($data); + +echo "time: " . (microtime(true)-$start) . "\n"; diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/bin/fetch_windows_zones.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/bin/fetch_windows_zones.php new file mode 100755 index 0000000..a577c52 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/bin/fetch_windows_zones.php @@ -0,0 +1,47 @@ +#!/usr/bin/env php +<?php + +$windowsZonesUrl = 'http://unicode.org/repos/cldr/trunk/common/supplemental/windowsZones.xml'; +$outputFile = __DIR__ . '/../lib/timezonedata/windowszones.php'; + +echo "Fetching timezone map from: " . $windowsZonesUrl, "\n"; + +$data = file_get_contents($windowsZonesUrl); + +$xml = simplexml_load_string($data); + +$map = array(); + +foreach($xml->xpath('//mapZone') as $mapZone) { + + $from = (string)$mapZone['other']; + $to = (string)$mapZone['type']; + + list($to) = explode(' ', $to, 2); + + if (!isset($map[$from])) { + $map[$from] = $to; + } + +} + +ksort($map); +echo "Writing to: $outputFile\n"; + +$f = fopen($outputFile,'w'); +fwrite($f, "<?php\n\n"); +fwrite($f, "/**\n"); +fwrite($f, " * Automatically generated timezone file\n"); +fwrite($f, " *\n"); +fwrite($f, " * Last update: " . date(DATE_W3C) . "\n"); +fwrite($f, " * Source: " .$windowsZonesUrl . "\n"); +fwrite($f, " *\n"); +fwrite($f, " * @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).\n"); +fwrite($f, " * @license http://sabre.io/license/ Modified BSD License\n"); +fwrite($f, " */\n"); +fwrite($f, "\n"); +fwrite($f, "return "); +fwrite($f, var_export($map, true) . ';'); +fclose($f); + +echo "Done\n"; diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/bin/generate_vcards b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/bin/generate_vcards new file mode 100755 index 0000000..638f63b --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/bin/generate_vcards @@ -0,0 +1,241 @@ +#!/usr/bin/env php +<?php + +namespace Sabre\VObject; + +// This sucks.. we have to try to find the composer autoloader. But chances +// are, we can't find it this way. So we'll do our bestest +$paths = array( + __DIR__ . '/../vendor/autoload.php', // In case vobject is cloned directly + __DIR__ . '/../../../autoload.php', // In case vobject is a composer dependency. +); + +foreach($paths as $path) { + if (file_exists($path)) { + include $path; + break; + } +} + +if (!class_exists('Sabre\\VObject\\Version')) { + fwrite(STDERR, "Composer autoloader could not be properly loaded.\n"); + die(1); +} + +if ($argc < 2) { + + $version = Version::VERSION; + + $help = <<<HI +sabre/vobject $version +Usage: + generate_vcards [count] + +Options: + count The number of random vcards to generate + +Examples: + generate_vcards 1000 > testdata.vcf + +HI; + + fwrite(STDERR, $help); + exit(2); +} + +$count = (int)$argv[1]; +if ($count < 1) { + fwrite(STDERR, "Count must be at least 1\n"); + exit(2); +} + +fwrite(STDERR, "sabre/vobject " . Version::VERSION . "\n"); +fwrite(STDERR, "Generating " . $count . " vcards in vCard 4.0 format\n"); + +/** + * The following list is just some random data we compiled from various + * sources online. + * + * Very little thought went into compiling this list, and certainly nothing + * political or ethical. + * + * We would _love_ more additions to this to add more variation to this list. + * + * Send us PR's and don't be shy adding your own first and last name for fun. + */ + +$sets = array( + "nl" => array( + "country" => "Netherlands", + "boys" => array( + "Anno", + "Bram", + "Daan", + "Evert", + "Finn", + "Jayden", + "Jens", + "Jesse", + "Levi", + "Lucas", + "Luuk", + "Milan", + "René", + "Sem", + "Sibrand", + "Willem", + ), + "girls" => array( + "Celia", + "Emma", + "Fenna", + "Geke", + "Inge", + "Julia", + "Lisa", + "Lotte", + "Mila", + "Sara", + "Sophie", + "Tess", + "Zoë", + ), + "last" => array( + "Bakker", + "Bos", + "De Boer", + "De Groot", + "De Jong", + "De Vries", + "Jansen", + "Janssen", + "Meyer", + "Mulder", + "Peters", + "Smit", + "Van Dijk", + "Van den Berg", + "Visser", + "Vos", + ), + ), + "us" => array( + "country" => "United States", + "boys" => array( + "Aiden", + "Alexander", + "Charles", + "David", + "Ethan", + "Jacob", + "James", + "Jayden", + "John", + "Joseph", + "Liam", + "Mason", + "Michael", + "Noah", + "Richard", + "Robert", + "Thomas", + "William", + ), + "girls" => array( + "Ava", + "Barbara", + "Chloe", + "Dorothy", + "Elizabeth", + "Emily", + "Emma", + "Isabella", + "Jennifer", + "Lily", + "Linda", + "Margaret", + "Maria", + "Mary", + "Mia", + "Olivia", + "Patricia", + "Roxy", + "Sophia", + "Susan", + "Zoe", + ), + "last" => array( + "Smith", + "Johnson", + "Williams", + "Jones", + "Brown", + "Davis", + "Miller", + "Wilson", + "Moore", + "Taylor", + "Anderson", + "Thomas", + "Jackson", + "White", + "Harris", + "Martin", + "Thompson", + "Garcia", + "Martinez", + "Robinson", + ), + ), +); + +$current = 0; + +$r = function($arr) { + + return $arr[mt_rand(0,count($arr)-1)]; + +}; + +$bdayStart = strtotime('-85 years'); +$bdayEnd = strtotime('-20 years'); + +while($current < $count) { + + $current++; + fwrite(STDERR, "\033[100D$current/$count"); + + $country = array_rand($sets); + $gender = mt_rand(0,1)?'girls':'boys'; + + $vcard = new Component\VCard(array( + 'VERSION' => '4.0', + 'FN' => $r($sets[$country][$gender]) . ' ' . $r($sets[$country]['last']), + 'UID' => UUIDUtil::getUUID(), + )); + + $bdayRatio = mt_rand(0,9); + + if($bdayRatio < 2) { + // 20% has a birthday property with a full date + $dt = new \DateTime('@' . mt_rand($bdayStart, $bdayEnd)); + $vcard->add('BDAY', $dt->format('Ymd')); + + } elseif ($bdayRatio < 3) { + // 10% we only know the month and date of + $dt = new \DateTime('@' . mt_rand($bdayStart, $bdayEnd)); + $vcard->add('BDAY', '--' . $dt->format('md')); + } + if ($result = $vcard->validate()) { + ob_start(); + echo "\nWe produced an invalid vcard somehow!\n"; + foreach($result as $message) { + echo " " . $message['message'] . "\n"; + } + fwrite(STDERR, ob_get_clean()); + } + echo $vcard->serialize(); + +} + +fwrite(STDERR,"\nDone.\n"); diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/bin/generateicalendardata.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/bin/generateicalendardata.php new file mode 100755 index 0000000..92c8c10 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/bin/generateicalendardata.php @@ -0,0 +1,91 @@ +#!/usr/bin/env php +<?php + +use Sabre\VObject; + +if ($argc<2) { + $cmd = $argv[0]; + fwrite(STDERR, <<<HI +Fruux test data generator + +This script generates a lot of test data. This is used for profiling and stuff. +Currently it just generates events in a single calendar. + +The iCalendar output goes to stdout. Other messages to stderr. + +{$cmd} [events] + + +HI + ); + die(); +} + +$events = 100; + +if (isset($argv[1])) $events = (int)$argv[1]; + +include __DIR__ . '/../vendor/autoload.php'; + +fwrite(STDERR, "Generating " . $events . " events\n"); + +$currentDate = new DateTime('-' . round($events/2) . ' days'); + +$calendar = VObject\Component::create('VCALENDAR'); +$calendar->version = '2.0'; +$calendar->calscale = 'GREGORIAN'; + +$ii=0; + +while($ii < $events) { + + $ii++; + + $event = VObject\Component::create('VEVENT'); + $event->DTSTART = 'bla'; + $event->SUMMARY = 'Event #' . $ii; + $event->UID = md5(microtime(true)); + + $doctorRandom = mt_rand(1,1000); + + switch($doctorRandom) { + // All-day event + case 1 : + $event->DTEND = 'bla'; + $dtStart = clone $currentDate; + $dtEnd = clone $currentDate; + $dtEnd->modify('+' . mt_rand(1,3) . ' days'); + $event->DTSTART->setDateTime($dtStart, VObject\Property\DateTime::DATE); + $event->DTEND->setDateTime($dtEnd, VObject\Property\DateTime::DATE); + break; + case 2 : + $event->RRULE = 'FREQ=DAILY;COUNT=' . mt_rand(1,10); + // No break intentional + default : + $dtStart = clone $currentDate; + $dtStart->setTime(mt_rand(1,23), mt_rand(0,59), mt_rand(0,59)); + $event->DTSTART->setDateTime($dtStart, VObject\Property\DateTime::UTC); + $event->DURATION = 'PT'.mt_rand(1,3).'H'; + break; + + } + + $calendar->add($event); + $currentDate->modify('+ ' . mt_rand(0,3) . ' days'); + +} +fwrite(STDERR, "Validating\n"); + +$result = $calendar->validate(); +if ($result) { + fwrite(STDERR, "Errors!\n"); + fwrite(STDERR, print_r($result,true)); + die(-1); +} + +fwrite(STDERR, "Serializing this beast\n"); + +echo $calendar->serialize(); + +fwrite(STDERR, "done.\n"); + diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/bin/rrulebench.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/bin/rrulebench.php new file mode 100644 index 0000000..f151819 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/bin/rrulebench.php @@ -0,0 +1,32 @@ +<?php + +include __DIR__ . '/../vendor/autoload.php'; + +if ($argc < 4) { + echo "sabre/vobject ", Sabre\VObject\Version::VERSION, " RRULE benchmark\n"; + echo "\n"; + echo "This script can be used to measure the speed of the 'recurrence expansion'\n"; + echo "system."; + echo "\n"; + echo "Usage: " . $argv[0] . " inputfile.ics startdate enddate\n"; + die(); +} + +list(, $inputFile, $startDate, $endDate) = $argv; + +$bench = new Hoa\Bench\Bench(); +$bench->parse->start(); + +echo "Parsing.\n"; +$vobj = Sabre\VObject\Reader::read(fopen($inputFile,'r')); + +$bench->parse->stop(); + +echo "Expanding.\n"; +$bench->expand->start(); + +$vobj->expand(new DateTime($startDate), new DateTime($endDate)); + +$bench->expand->stop(); + +echo $bench,"\n"; diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/bin/vobject b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/bin/vobject new file mode 100755 index 0000000..e52b4fb --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/bin/vobject @@ -0,0 +1,27 @@ +#!/usr/bin/env php +<?php + +namespace Sabre\VObject; + +// This sucks.. we have to try to find the composer autoloader. But chances +// are, we can't find it this way. So we'll do our bestest +$paths = array( + __DIR__ . '/../vendor/autoload.php', // In case vobject is cloned directly + __DIR__ . '/../../../autoload.php', // In case vobject is a composer dependency. +); + +foreach($paths as $path) { + if (file_exists($path)) { + include $path; + break; + } +} + +if (!class_exists('Sabre\\VObject\\Version')) { + fwrite(STDERR, "Composer autoloader could not be loaded.\n"); + die(1); +} + +$cli = new Cli(); +exit($cli->main($argv)); + diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/composer.json b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/composer.json new file mode 100644 index 0000000..309c08a --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/composer.json @@ -0,0 +1,50 @@ +{ + "name": "sabre/vobject", + "description" : "The VObject library for PHP allows you to easily parse and manipulate iCalendar and vCard objects", + "keywords" : [ "VObject", "iCalendar", "vCard", "jCard", "jCal" ], + "homepage" : "http://sabre.io/vobject/", + "license" : "BSD-3-Clause", + "require" : { + "php" : ">=5.3.1", + "ext-mbstring" : "*" + }, + "require-dev" : { + "phpunit/phpunit" : "*", + "squizlabs/php_codesniffer": "*" + }, + "authors" : [ + { + "name" : "Evert Pot", + "email" : "me@evertpot.com", + "homepage" : "http://evertpot.com/", + "role" : "Developer" + }, + { + "name" : "Dominik Tobschall", + "email" : "dominik@fruux.com", + "homepage" : "http://tobschall.de/", + "role" : "Developer" + } + ], + "support" : { + "forum" : "https://groups.google.com/group/sabredav-discuss", + "source" : "https://github.com/fruux/sabre-vobject" + }, + "autoload" : { + "psr-4" : { + "Sabre\\VObject\\" : "lib/" + } + }, + "bin" : [ + "bin/vobject", + "bin/generate_vcards" + ], + "extra" : { + "branch-alias" : { + "dev-master" : "3.2.x-dev" + } + }, + "config" : { + "bin-dir" : "bin" + } +} diff --git a/src/serverlib/3rdparty/sabre/VObject/Cli.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Cli.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Cli.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Cli.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Component.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Component.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Component.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Component.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Component/Available.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Component/Available.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Component/Available.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Component/Available.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Component/VAlarm.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Component/VAlarm.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Component/VAlarm.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Component/VAlarm.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Component/VAvailability.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Component/VAvailability.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Component/VAvailability.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Component/VAvailability.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Component/VCalendar.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Component/VCalendar.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Component/VCalendar.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Component/VCalendar.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Component/VCard.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Component/VCard.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Component/VCard.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Component/VCard.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Component/VEvent.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Component/VEvent.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Component/VEvent.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Component/VEvent.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Component/VFreeBusy.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Component/VFreeBusy.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Component/VFreeBusy.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Component/VFreeBusy.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Component/VJournal.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Component/VJournal.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Component/VJournal.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Component/VJournal.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Component/VTimeZone.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Component/VTimeZone.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Component/VTimeZone.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Component/VTimeZone.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Component/VTodo.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Component/VTodo.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Component/VTodo.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Component/VTodo.php diff --git a/src/serverlib/3rdparty/sabre/VObject/DateTimeParser.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/DateTimeParser.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/DateTimeParser.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/DateTimeParser.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Document.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Document.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Document.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Document.php diff --git a/src/serverlib/3rdparty/sabre/VObject/ElementList.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/ElementList.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/ElementList.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/ElementList.php diff --git a/src/serverlib/3rdparty/sabre/VObject/EofException.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/EofException.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/EofException.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/EofException.php diff --git a/src/serverlib/3rdparty/sabre/VObject/FreeBusyGenerator.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/FreeBusyGenerator.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/FreeBusyGenerator.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/FreeBusyGenerator.php diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/ITip/Broker.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/ITip/Broker.php new file mode 100644 index 0000000..e439196 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/ITip/Broker.php @@ -0,0 +1,976 @@ +<?php + +namespace Sabre\VObject\ITip; + +use Sabre\VObject\Component\VCalendar; +use Sabre\VObject\DateTimeParser; +use Sabre\VObject\Reader; +use Sabre\VObject\Recur\EventIterator; + +/** + * The ITip\Broker class is a utility class that helps with processing + * so-called iTip messages. + * + * iTip is defined in rfc5546, stands for iCalendar Transport-Independent + * Interoperability Protocol, and describes the underlying mechanism for + * using iCalendar for scheduling for for example through email (also known as + * IMip) and CalDAV Scheduling. + * + * This class helps by: + * + * 1. Creating individual invites based on an iCalendar event for each + * attendee. + * 2. Generating invite updates based on an iCalendar update. This may result + * in new invites, updates and cancellations for attendees, if that list + * changed. + * 3. On the receiving end, it can create a local iCalendar event based on + * a received invite. + * 4. It can also process an invite update on a local event, ensuring that any + * overridden properties from attendees are retained. + * 5. It can create a accepted or declined iTip reply based on an invite. + * 6. It can process a reply from an invite and update an events attendee + * status based on a reply. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Broker { + + /** + * This setting determines whether the rules for the SCHEDULE-AGENT + * parameter should be followed. + * + * This is a parameter defined on ATTENDEE properties, introduced by RFC + * 6638. This parameter allows a caldav client to tell the server 'Don't do + * any scheduling operations'. + * + * If this setting is turned on, any attendees with SCHEDULE-AGENT set to + * CLIENT will be ignored. This is the desired behavior for a CalDAV + * server, but if you're writing an iTip application that doesn't deal with + * CalDAV, you may want to ignore this parameter. + * + * @var bool + */ + public $scheduleAgentServerRules = true; + + /** + * The broker will try during 'parseEvent' figure out whether the change + * was significant. + * + * It uses a few different ways to do this. One of these ways is seeing if + * certain properties changed values. This list of specified here. + * + * This list is taken from: + * * http://tools.ietf.org/html/rfc5546#section-2.1.4 + * + * @var string[] + */ + public $significantChangeProperties = array( + 'DTSTART', + 'DTEND', + 'DURATION', + 'DUE', + 'RRULE', + 'RDATE', + 'EXDATE', + 'STATUS', + ); + + /** + * This method is used to process an incoming itip message. + * + * Examples: + * + * 1. A user is an attendee to an event. The organizer sends an updated + * meeting using a new iTip message with METHOD:REQUEST. This function + * will process the message and update the attendee's event accordingly. + * + * 2. The organizer cancelled the event using METHOD:CANCEL. We will update + * the users event to state STATUS:CANCELLED. + * + * 3. An attendee sent a reply to an invite using METHOD:REPLY. We can + * update the organizers event to update the ATTENDEE with its correct + * PARTSTAT. + * + * The $existingObject is updated in-place. If there is no existing object + * (because it's a new invite for example) a new object will be created. + * + * If an existing object does not exist, and the method was CANCEL or + * REPLY, the message effectively gets ignored, and no 'existingObject' + * will be created. + * + * The updated $existingObject is also returned from this function. + * + * If the iTip message was not supported, we will always return false. + * + * @param Message $itipMessage + * @param VCalendar $existingObject + * @return VCalendar|null + */ + public function processMessage(Message $itipMessage, VCalendar $existingObject = null) { + + // We only support events at the moment. + if ($itipMessage->component !== 'VEVENT') { + return false; + } + + switch($itipMessage->method) { + + case 'REQUEST' : + return $this->processMessageRequest($itipMessage, $existingObject); + + case 'CANCEL' : + return $this->processMessageCancel($itipMessage, $existingObject); + + case 'REPLY' : + return $this->processMessageReply($itipMessage, $existingObject); + + default : + // Unsupported iTip message + return null; + + } + + return $existingObject; + + } + + /** + * This function parses a VCALENDAR object and figure out if any messages + * need to be sent. + * + * A VCALENDAR object will be created from the perspective of either an + * attendee, or an organizer. You must pass a string identifying the + * current user, so we can figure out who in the list of attendees or the + * organizer we are sending this message on behalf of. + * + * It's possible to specify the current user as an array, in case the user + * has more than one identifying href (such as multiple emails). + * + * It $oldCalendar is specified, it is assumed that the operation is + * updating an existing event, which means that we need to look at the + * differences between events, and potentially send old attendees + * cancellations, and current attendees updates. + * + * If $calendar is null, but $oldCalendar is specified, we treat the + * operation as if the user has deleted an event. If the user was an + * organizer, this means that we need to send cancellation notices to + * people. If the user was an attendee, we need to make sure that the + * organizer gets the 'declined' message. + * + * @param VCalendar|string $calendar + * @param string|array $userHref + * @param VCalendar|string $oldCalendar + * @return array + */ + public function parseEvent($calendar = null, $userHref, $oldCalendar = null) { + + if ($oldCalendar) { + if (is_string($oldCalendar)) { + $oldCalendar = Reader::read($oldCalendar); + } + if (!isset($oldCalendar->VEVENT)) { + // We only support events at the moment + return array(); + } + + $oldEventInfo = $this->parseEventInfo($oldCalendar); + } else { + $oldEventInfo = array( + 'organizer' => null, + 'significantChangeHash' => '', + 'attendees' => array(), + ); + } + + $userHref = (array)$userHref; + + if (!is_null($calendar)) { + + if (is_string($calendar)) { + $calendar = Reader::read($calendar); + } + if (!isset($calendar->VEVENT)) { + // We only support events at the moment + return array(); + } + $eventInfo = $this->parseEventInfo($calendar); + if (!$eventInfo['attendees'] && !$oldEventInfo['attendees']) { + // If there were no attendees on either side of the equation, + // we don't need to do anything. + return array(); + } + if (!$eventInfo['organizer'] && !$oldEventInfo['organizer']) { + // There was no organizer before or after the change. + return array(); + } + + $baseCalendar = $calendar; + + // If the new object didn't have an organizer, the organizer + // changed the object from a scheduling object to a non-scheduling + // object. We just copy the info from the old object. + if (!$eventInfo['organizer'] && $oldEventInfo['organizer']) { + $eventInfo['organizer'] = $oldEventInfo['organizer']; + $eventInfo['organizerName'] = $oldEventInfo['organizerName']; + } + + } else { + // The calendar object got deleted, we need to process this as a + // cancellation / decline. + if (!$oldCalendar) { + // No old and no new calendar, there's no thing to do. + return array(); + } + + $eventInfo = $oldEventInfo; + + if (in_array($eventInfo['organizer'], $userHref)) { + // This is an organizer deleting the event. + $eventInfo['attendees'] = array(); + // Increasing the sequence, but only if the organizer deleted + // the event. + $eventInfo['sequence']++; + } else { + // This is an attendee deleting the event. + foreach($eventInfo['attendees'] as $key=>$attendee) { + if (in_array($attendee['href'], $userHref)) { + $eventInfo['attendees'][$key]['instances'] = array('master' => + array('id'=>'master', 'partstat' => 'DECLINED') + ); + } + } + } + $baseCalendar = $oldCalendar; + + } + + if (in_array($eventInfo['organizer'], $userHref)) { + return $this->parseEventForOrganizer($baseCalendar, $eventInfo, $oldEventInfo); + } elseif ($oldCalendar) { + // We need to figure out if the user is an attendee, but we're only + // doing so if there's an oldCalendar, because we only want to + // process updates, not creation of new events. + foreach($eventInfo['attendees'] as $attendee) { + if (in_array($attendee['href'], $userHref)) { + return $this->parseEventForAttendee($baseCalendar, $eventInfo, $oldEventInfo, $attendee['href']); + } + } + } + return array(); + + } + + /** + * Processes incoming REQUEST messages. + * + * This is message from an organizer, and is either a new event + * invite, or an update to an existing one. + * + * + * @param Message $itipMessage + * @param VCalendar $existingObject + * @return VCalendar|null + */ + protected function processMessageRequest(Message $itipMessage, VCalendar $existingObject = null) { + + if (!$existingObject) { + // This is a new invite, and we're just going to copy over + // all the components from the invite. + $existingObject = new VCalendar(); + foreach($itipMessage->message->getComponents() as $component) { + $existingObject->add(clone $component); + } + } else { + // We need to update an existing object with all the new + // information. We can just remove all existing components + // and create new ones. + foreach($existingObject->getComponents() as $component) { + $existingObject->remove($component); + } + foreach($itipMessage->message->getComponents() as $component) { + $existingObject->add(clone $component); + } + } + return $existingObject; + + } + + /** + * Processes incoming CANCEL messages. + * + * This is a message from an organizer, and means that either an + * attendee got removed from an event, or an event got cancelled + * altogether. + * + * @param Message $itipMessage + * @param VCalendar $existingObject + * @return VCalendar|null + */ + protected function processMessageCancel(Message $itipMessage, VCalendar $existingObject = null) { + + if (!$existingObject) { + // The event didn't exist in the first place, so we're just + // ignoring this message. + } else { + foreach($existingObject->VEVENT as $vevent) { + $vevent->STATUS = 'CANCELLED'; + $vevent->SEQUENCE = $itipMessage->sequence; + } + } + return $existingObject; + + } + + /** + * Processes incoming REPLY messages. + * + * The message is a reply. This is for example an attendee telling + * an organizer he accepted the invite, or declined it. + * + * @param Message $itipMessage + * @param VCalendar $existingObject + * @return VCalendar|null + */ + protected function processMessageReply(Message $itipMessage, VCalendar $existingObject = null) { + + // A reply can only be processed based on an existing object. + // If the object is not available, the reply is ignored. + if (!$existingObject) { + return null; + } + $instances = array(); + $requestStatus = '2.0'; + + // Finding all the instances the attendee replied to. + foreach($itipMessage->message->VEVENT as $vevent) { + $recurId = isset($vevent->{'RECURRENCE-ID'})?$vevent->{'RECURRENCE-ID'}->getValue():'master'; + $attendee = $vevent->ATTENDEE; + $instances[$recurId] = $attendee['PARTSTAT']->getValue(); + if (isset($vevent->{'REQUEST-STATUS'})) { + $requestStatus = $vevent->{'REQUEST-STATUS'}->getValue(); + list($requestStatus) = explode(';', $requestStatus); + } + } + + // Now we need to loop through the original organizer event, to find + // all the instances where we have a reply for. + $masterObject = null; + foreach($existingObject->VEVENT as $vevent) { + $recurId = isset($vevent->{'RECURRENCE-ID'})?$vevent->{'RECURRENCE-ID'}->getValue():'master'; + if ($recurId==='master') { + $masterObject = $vevent; + } + if (isset($instances[$recurId])) { + $attendeeFound = false; + if (isset($vevent->ATTENDEE)) { + foreach($vevent->ATTENDEE as $attendee) { + if ($attendee->getValue() === $itipMessage->sender) { + $attendeeFound = true; + $attendee['PARTSTAT'] = $instances[$recurId]; + $attendee['SCHEDULE-STATUS'] = $requestStatus; + // Un-setting the RSVP status, because we now know + // that the attende already replied. + unset($attendee['RSVP']); + break; + } + } + } + if (!$attendeeFound) { + // Adding a new attendee. The iTip documentation calls this + // a party crasher. + $attendee = $vevent->add('ATTENDEE', $itipMessage->sender, array( + 'PARTSTAT' => $instances[$recurId] + )); + if ($itipMessage->senderName) $attendee['CN'] = $itipMessage->senderName; + } + unset($instances[$recurId]); + } + } + + if(!$masterObject) { + // No master object, we can't add new instances. + return null; + } + // If we got replies to instances that did not exist in the + // original list, it means that new exceptions must be created. + foreach($instances as $recurId=>$partstat) { + + $recurrenceIterator = new EventIterator($existingObject, $itipMessage->uid); + $found = false; + $iterations = 1000; + do { + + $newObject = $recurrenceIterator->getEventObject(); + $recurrenceIterator->next(); + + if (isset($newObject->{'RECURRENCE-ID'}) && $newObject->{'RECURRENCE-ID'}->getValue()===$recurId) { + $found = true; + } + $iterations--; + + } while($recurrenceIterator->valid() && !$found && $iterations); + + // Invalid recurrence id. Skipping this object. + if (!$found) continue; + + unset( + $newObject->RRULE, + $newObject->EXDATE, + $newObject->RDATE + ); + $attendeeFound = false; + if (isset($newObject->ATTENDEE)) { + foreach($newObject->ATTENDEE as $attendee) { + if ($attendee->getValue() === $itipMessage->sender) { + $attendeeFound = true; + $attendee['PARTSTAT'] = $partstat; + break; + } + } + } + if (!$attendeeFound) { + // Adding a new attendee + $attendee = $newObject->add('ATTENDEE', $itipMessage->sender, array( + 'PARTSTAT' => $partstat + )); + if ($itipMessage->senderName) { + $attendee['CN'] = $itipMessage->senderName; + } + } + $existingObject->add($newObject); + + } + return $existingObject; + + } + + /** + * This method is used in cases where an event got updated, and we + * potentially need to send emails to attendees to let them know of updates + * in the events. + * + * We will detect which attendees got added, which got removed and create + * specific messages for these situations. + * + * @param VCalendar $calendar + * @param array $eventInfo + * @param array $oldEventInfo + * @return array + */ + protected function parseEventForOrganizer(VCalendar $calendar, array $eventInfo, array $oldEventInfo) { + + // Merging attendee lists. + $attendees = array(); + foreach($oldEventInfo['attendees'] as $attendee) { + $attendees[$attendee['href']] = array( + 'href' => $attendee['href'], + 'oldInstances' => $attendee['instances'], + 'newInstances' => array(), + 'name' => $attendee['name'], + 'forceSend' => null, + ); + } + foreach($eventInfo['attendees'] as $attendee) { + if (isset($attendees[$attendee['href']])) { + $attendees[$attendee['href']]['name'] = $attendee['name']; + $attendees[$attendee['href']]['newInstances'] = $attendee['instances']; + $attendees[$attendee['href']]['forceSend'] = $attendee['forceSend']; + } else { + $attendees[$attendee['href']] = array( + 'href' => $attendee['href'], + 'oldInstances' => array(), + 'newInstances' => $attendee['instances'], + 'name' => $attendee['name'], + 'forceSend' => $attendee['forceSend'], + ); + } + } + + $messages = array(); + + foreach($attendees as $attendee) { + + // An organizer can also be an attendee. We should not generate any + // messages for those. + if ($attendee['href']===$eventInfo['organizer']) { + continue; + } + + $message = new Message(); + $message->uid = $eventInfo['uid']; + $message->component = 'VEVENT'; + $message->sequence = $eventInfo['sequence']; + $message->sender = $eventInfo['organizer']; + $message->senderName = $eventInfo['organizerName']; + $message->recipient = $attendee['href']; + $message->recipientName = $attendee['name']; + + if (!$attendee['newInstances']) { + + // If there are no instances the attendee is a part of, it + // means the attendee was removed and we need to send him a + // CANCEL. + $message->method = 'CANCEL'; + + // Creating the new iCalendar body. + $icalMsg = new VCalendar(); + $icalMsg->METHOD = $message->method; + $event = $icalMsg->add('VEVENT', array( + 'UID' => $message->uid, + 'SEQUENCE' => $message->sequence, + )); + if (isset($calendar->VEVENT->SUMMARY)) { + $event->add('SUMMARY', $calendar->VEVENT->SUMMARY->getValue()); + } + $event->add(clone $calendar->VEVENT->DTSTART); + if (isset($calendar->VEVENT->DTEND)) { + $event->add(clone $calendar->VEVENT->DTEND); + } elseif (isset($calendar->VEVENT->DURATION)) { + $event->add(clone $calendar->VEVENT->DURATION); + } + $org = $event->add('ORGANIZER', $eventInfo['organizer']); + if ($eventInfo['organizerName']) $org['CN'] = $eventInfo['organizerName']; + $event->add('ATTENDEE', $attendee['href'], array( + 'CN' => $attendee['name'], + )); + $message->significantChange = true; + + } else { + + // The attendee gets the updated event body + $message->method = 'REQUEST'; + + // Creating the new iCalendar body. + $icalMsg = new VCalendar(); + $icalMsg->METHOD = $message->method; + + foreach($calendar->select('VTIMEZONE') as $timezone) { + $icalMsg->add(clone $timezone); + } + + // We need to find out that this change is significant. If it's + // not, systems may opt to not send messages. + // + // We do this based on the 'significantChangeHash' which is + // some value that changes if there's a certain set of + // properties changed in the event, or simply if there's a + // difference in instances that the attendee is invited to. + + $message->significantChange = + $attendee['forceSend'] === 'REQUEST' || + array_keys($attendee['oldInstances']) != array_keys($attendee['newInstances']) || + $oldEventInfo['significantChangeHash']!==$eventInfo['significantChangeHash']; + + foreach($attendee['newInstances'] as $instanceId => $instanceInfo) { + + $currentEvent = clone $eventInfo['instances'][$instanceId]; + if ($instanceId === 'master') { + + // We need to find a list of events that the attendee + // is not a part of to add to the list of exceptions. + $exceptions = array(); + foreach($eventInfo['instances'] as $instanceId=>$vevent) { + if (!isset($attendee['newInstances'][$instanceId])) { + $exceptions[] = $instanceId; + } + } + + // If there were exceptions, we need to add it to an + // existing EXDATE property, if it exists. + if ($exceptions) { + if (isset($currentEvent->EXDATE)) { + $currentEvent->EXDATE->setParts(array_merge( + $currentEvent->EXDATE->getParts(), + $exceptions + )); + } else { + $currentEvent->EXDATE = $exceptions; + } + } + + // Cleaning up any scheduling information that + // shouldn't be sent along. + unset($currentEvent->ORGANIZER['SCHEDULE-FORCE-SEND']); + unset($currentEvent->ORGANIZER['SCHEDULE-STATUS']); + + foreach($currentEvent->ATTENDEE as $attendee) { + unset($attendee['SCHEDULE-FORCE-SEND']); + unset($attendee['SCHEDULE-STATUS']); + + // We're adding PARTSTAT=NEEDS-ACTION to ensure that + // iOS shows an "Inbox Item" + if (!isset($attendee['PARTSTAT'])) { + $attendee['PARTSTAT'] = 'NEEDS-ACTION'; + } + + } + + } + + $icalMsg->add($currentEvent); + + } + + } + + $message->message = $icalMsg; + $messages[] = $message; + + } + + return $messages; + + } + + /** + * Parse an event update for an attendee. + * + * This function figures out if we need to send a reply to an organizer. + * + * @param VCalendar $calendar + * @param array $eventInfo + * @param array $oldEventInfo + * @param string $attendee + * @return Message[] + */ + protected function parseEventForAttendee(VCalendar $calendar, array $eventInfo, array $oldEventInfo, $attendee) { + + if ($this->scheduleAgentServerRules && $eventInfo['organizerScheduleAgent']==='CLIENT') { + return array(); + } + + // Don't bother generating messages for events that have already been + // cancelled. + if ($eventInfo['status']==='CANCELLED') { + return array(); + } + + $oldInstances = !empty($oldEventInfo['attendees'][$attendee]['instances']) ? + $oldEventInfo['attendees'][$attendee]['instances'] : + array(); + + $instances = array(); + foreach($oldInstances as $instance) { + + $instances[$instance['id']] = array( + 'id' => $instance['id'], + 'oldstatus' => $instance['partstat'], + 'newstatus' => null, + ); + + } + foreach($eventInfo['attendees'][$attendee]['instances'] as $instance) { + + if (isset($instances[$instance['id']])) { + $instances[$instance['id']]['newstatus'] = $instance['partstat']; + } else { + $instances[$instance['id']] = array( + 'id' => $instance['id'], + 'oldstatus' => null, + 'newstatus' => $instance['partstat'], + ); + } + + } + + // We need to also look for differences in EXDATE. If there are new + // items in EXDATE, it means that an attendee deleted instances of an + // event, which means we need to send DECLINED specifically for those + // instances. + // We only need to do that though, if the master event is not declined. + if (isset($instances['master']) && $instances['master']['newstatus'] !== 'DECLINED') { + foreach($eventInfo['exdate'] as $exDate) { + + if (!in_array($exDate, $oldEventInfo['exdate'])) { + if (isset($instances[$exDate])) { + $instances[$exDate]['newstatus'] = 'DECLINED'; + } else { + $instances[$exDate] = array( + 'id' => $exDate, + 'oldstatus' => null, + 'newstatus' => 'DECLINED', + ); + } + } + + } + } + + // Gathering a few extra properties for each instance. + foreach($instances as $recurId=>$instanceInfo) { + + if (isset($eventInfo['instances'][$recurId])) { + $instances[$recurId]['dtstart'] = clone $eventInfo['instances'][$recurId]->DTSTART; + } else { + $instances[$recurId]['dtstart'] = $recurId; + } + + } + + $message = new Message(); + $message->uid = $eventInfo['uid']; + $message->method = 'REPLY'; + $message->component = 'VEVENT'; + $message->sequence = $eventInfo['sequence']; + $message->sender = $attendee; + $message->senderName = $eventInfo['attendees'][$attendee]['name']; + $message->recipient = $eventInfo['organizer']; + $message->recipientName = $eventInfo['organizerName']; + + $icalMsg = new VCalendar(); + $icalMsg->METHOD = 'REPLY'; + + $hasReply = false; + + foreach($instances as $instance) { + + if ($instance['oldstatus']==$instance['newstatus'] && $eventInfo['organizerForceSend'] !== 'REPLY') { + // Skip + continue; + } + + $event = $icalMsg->add('VEVENT', array( + 'UID' => $message->uid, + 'SEQUENCE' => $message->sequence, + )); + $summary = isset($calendar->VEVENT->SUMMARY)?$calendar->VEVENT->SUMMARY->getValue():''; + // Adding properties from the correct source instance + if (isset($eventInfo['instances'][$instance['id']])) { + $instanceObj = $eventInfo['instances'][$instance['id']]; + $event->add(clone $instanceObj->DTSTART); + if (isset($instanceObj->DTEND)) { + $event->add(clone $instanceObj->DTEND); + } elseif (isset($instanceObj->DURATION)) { + $event->add(clone $instanceObj->DURATION); + } + if (isset($instanceObj->SUMMARY)) { + $event->add('SUMMARY', $instanceObj->SUMMARY->getValue()); + } elseif ($summary) { + $event->add('SUMMARY', $summary); + } + } else { + // This branch of the code is reached, when a reply is + // generated for an instance of a recurring event, through the + // fact that the instance has disappeared by showing up in + // EXDATE + $dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']); + // Treat is as a DATE field + if (strlen($instance['id']) <= 8) { + $recur = $event->add('DTSTART', $dt, array('VALUE' => 'DATE')); + } else { + $recur = $event->add('DTSTART', $dt); + } + if ($summary) { + $event->add('SUMMARY', $summary); + } + } + if ($instance['id'] !== 'master') { + $dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']); + // Treat is as a DATE field + if (strlen($instance['id']) <= 8) { + $recur = $event->add('RECURRENCE-ID', $dt, array('VALUE' => 'DATE')); + } else { + $recur = $event->add('RECURRENCE-ID', $dt); + } + } + $organizer = $event->add('ORGANIZER', $message->recipient); + if ($message->recipientName) { + $organizer['CN'] = $message->recipientName; + } + $attendee = $event->add('ATTENDEE', $message->sender, array( + 'PARTSTAT' => $instance['newstatus'] + )); + if ($message->senderName) { + $attendee['CN'] = $message->senderName; + } + $hasReply = true; + + } + + if ($hasReply) { + $message->message = $icalMsg; + return array($message); + } else { + return array(); + } + + } + + /** + * Returns attendee information and information about instances of an + * event. + * + * Returns an array with the following keys: + * + * 1. uid + * 2. organizer + * 3. organizerName + * 4. organizerScheduleAgent + * 5. organizerForceSend + * 6. instances + * 7. attendees + * 8. sequence + * 9. exdate + * 10. timezone + * 11. significantChangeHash + * 12. status + * @param VCalendar $calendar + * @return array + */ + protected function parseEventInfo(VCalendar $calendar = null) { + + $uid = null; + $organizer = null; + $organizerName = null; + $organizerForceSend = null; + $sequence = null; + $timezone = null; + $status = null; + $organizerScheduleAgent = 'SERVER'; + + $significantChangeHash = ''; + + // Now we need to collect a list of attendees, and which instances they + // are a part of. + $attendees = array(); + + $instances = array(); + $exdate = array(); + + foreach($calendar->VEVENT as $vevent) { + + if (is_null($uid)) { + $uid = $vevent->UID->getValue(); + } else { + if ($uid !== $vevent->UID->getValue()) { + throw new ITipException('If a calendar contained more than one event, they must have the same UID.'); + } + } + + if (!isset($vevent->DTSTART)) { + throw new ITipException('An event MUST have a DTSTART property.'); + } + + if (isset($vevent->ORGANIZER)) { + if (is_null($organizer)) { + $organizer = $vevent->ORGANIZER->getNormalizedValue(); + $organizerName = isset($vevent->ORGANIZER['CN'])?$vevent->ORGANIZER['CN']:null; + } else { + if ($organizer !== $vevent->ORGANIZER->getNormalizedValue()) { + throw new SameOrganizerForAllComponentsException('Every instance of the event must have the same organizer.'); + } + } + $organizerForceSend = + isset($vevent->ORGANIZER['SCHEDULE-FORCE-SEND']) ? + strtoupper($vevent->ORGANIZER['SCHEDULE-FORCE-SEND']) : + null; + $organizerScheduleAgent = + isset($vevent->ORGANIZER['SCHEDULE-AGENT']) ? + strtoupper((string)$vevent->ORGANIZER['SCHEDULE-AGENT']) : + 'SERVER'; + } + if (is_null($sequence) && isset($vevent->SEQUENCE)) { + $sequence = $vevent->SEQUENCE->getValue(); + } + if (isset($vevent->EXDATE)) { + foreach ($vevent->select('EXDATE') as $val) { + $exdate = array_merge($exdate, $val->getParts()); + } + sort($exdate); + } + if (isset($vevent->STATUS)) { + $status = strtoupper($vevent->STATUS->getValue()); + } + + $recurId = isset($vevent->{'RECURRENCE-ID'})?$vevent->{'RECURRENCE-ID'}->getValue():'master'; + if ($recurId==='master') { + $timezone = $vevent->DTSTART->getDateTime()->getTimeZone(); + } + if(isset($vevent->ATTENDEE)) { + foreach($vevent->ATTENDEE as $attendee) { + + if ($this->scheduleAgentServerRules && + isset($attendee['SCHEDULE-AGENT']) && + strtoupper($attendee['SCHEDULE-AGENT']->getValue()) === 'CLIENT' + ) { + continue; + } + $partStat = + isset($attendee['PARTSTAT']) ? + strtoupper($attendee['PARTSTAT']) : + 'NEEDS-ACTION'; + + $forceSend = + isset($attendee['SCHEDULE-FORCE-SEND']) ? + strtoupper($attendee['SCHEDULE-FORCE-SEND']) : + null; + + + if (isset($attendees[$attendee->getNormalizedValue()])) { + $attendees[$attendee->getNormalizedValue()]['instances'][$recurId] = array( + 'id' => $recurId, + 'partstat' => $partStat, + 'force-send' => $forceSend, + ); + } else { + $attendees[$attendee->getNormalizedValue()] = array( + 'href' => $attendee->getNormalizedValue(), + 'instances' => array( + $recurId => array( + 'id' => $recurId, + 'partstat' => $partStat, + ), + ), + 'name' => isset($attendee['CN'])?(string)$attendee['CN']:null, + 'forceSend' => $forceSend, + ); + } + + } + $instances[$recurId] = $vevent; + + } + + foreach($this->significantChangeProperties as $prop) { + if (isset($vevent->$prop)) { + $propertyValues = $vevent->select($prop); + + $significantChangeHash.=$prop.':'; + + if ($prop === 'EXDATE') { + + $significantChangeHash.= implode(',', $exdate).';'; + + } else { + + foreach($propertyValues as $val) { + $significantChangeHash.= $val->getValue().';'; + } + + } + } + } + + } + $significantChangeHash = md5($significantChangeHash); + + return compact( + 'uid', + 'organizer', + 'organizerName', + 'organizerScheduleAgent', + 'organizerForceSend', + 'instances', + 'attendees', + 'sequence', + 'exdate', + 'timezone', + 'significantChangeHash', + 'status' + ); + + } + +} diff --git a/src/serverlib/3rdparty/sabre/VObject/ITip/ITipException.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/ITip/ITipException.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/ITip/ITipException.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/ITip/ITipException.php diff --git a/src/serverlib/3rdparty/sabre/VObject/ITip/Message.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/ITip/Message.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/ITip/Message.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/ITip/Message.php diff --git a/src/serverlib/3rdparty/sabre/VObject/ITip/SameOrganizerForAllComponentsException.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/ITip/SameOrganizerForAllComponentsException.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/ITip/SameOrganizerForAllComponentsException.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/ITip/SameOrganizerForAllComponentsException.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Node.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Node.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Node.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Node.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Parameter.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Parameter.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Parameter.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Parameter.php diff --git a/src/serverlib/3rdparty/sabre/VObject/ParseException.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/ParseException.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/ParseException.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/ParseException.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Parser/Json.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Parser/Json.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Parser/Json.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Parser/Json.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Parser/MimeDir.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Parser/MimeDir.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Parser/MimeDir.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Parser/MimeDir.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Parser/Parser.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Parser/Parser.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Parser/Parser.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Parser/Parser.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Property.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Property.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Property/Binary.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/Binary.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Property/Binary.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/Binary.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Property/Boolean.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/Boolean.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Property/Boolean.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/Boolean.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Property/FlatText.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/FlatText.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Property/FlatText.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/FlatText.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Property/FloatValue.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/FloatValue.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Property/FloatValue.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/FloatValue.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Property/ICalendar/CalAddress.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/ICalendar/CalAddress.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Property/ICalendar/CalAddress.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/ICalendar/CalAddress.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Property/ICalendar/Date.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/ICalendar/Date.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Property/ICalendar/Date.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/ICalendar/Date.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Property/ICalendar/DateTime.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/ICalendar/DateTime.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Property/ICalendar/DateTime.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/ICalendar/DateTime.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Property/ICalendar/Duration.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/ICalendar/Duration.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Property/ICalendar/Duration.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/ICalendar/Duration.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Property/ICalendar/Period.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/ICalendar/Period.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Property/ICalendar/Period.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/ICalendar/Period.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Property/ICalendar/Recur.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/ICalendar/Recur.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Property/ICalendar/Recur.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/ICalendar/Recur.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Property/IntegerValue.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/IntegerValue.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Property/IntegerValue.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/IntegerValue.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Property/Text.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/Text.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Property/Text.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/Text.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Property/Time.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/Time.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Property/Time.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/Time.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Property/Unknown.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/Unknown.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Property/Unknown.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/Unknown.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Property/Uri.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/Uri.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Property/Uri.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/Uri.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Property/UtcOffset.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/UtcOffset.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Property/UtcOffset.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/UtcOffset.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Property/VCard/Date.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/VCard/Date.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Property/VCard/Date.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/VCard/Date.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Property/VCard/DateAndOrTime.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/VCard/DateAndOrTime.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Property/VCard/DateAndOrTime.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/VCard/DateAndOrTime.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Property/VCard/DateTime.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/VCard/DateTime.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Property/VCard/DateTime.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/VCard/DateTime.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Property/VCard/LanguageTag.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/VCard/LanguageTag.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Property/VCard/LanguageTag.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/VCard/LanguageTag.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Property/VCard/TimeStamp.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/VCard/TimeStamp.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Property/VCard/TimeStamp.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Property/VCard/TimeStamp.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Reader.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Reader.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Reader.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Reader.php diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Recur/EventIterator.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Recur/EventIterator.php new file mode 100644 index 0000000..7b1c19d --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Recur/EventIterator.php @@ -0,0 +1,496 @@ +<?php + +namespace Sabre\VObject\Recur; + +use InvalidArgumentException; +use DateTime; +use DateTimeZone; +use Sabre\VObject\Component; +use Sabre\VObject\Component\VEvent; + +/** + * This class is used to determine new for a recurring event, when the next + * events occur. + * + * This iterator may loop infinitely in the future, therefore it is important + * that if you use this class, you set hard limits for the amount of iterations + * you want to handle. + * + * Note that currently there is not full support for the entire iCalendar + * specification, as it's very complex and contains a lot of permutations + * that's not yet used very often in software. + * + * For the focus has been on features as they actually appear in Calendaring + * software, but this may well get expanded as needed / on demand + * + * The following RRULE properties are supported + * * UNTIL + * * INTERVAL + * * COUNT + * * FREQ=DAILY + * * BYDAY + * * BYHOUR + * * BYMONTH + * * FREQ=WEEKLY + * * BYDAY + * * BYHOUR + * * WKST + * * FREQ=MONTHLY + * * BYMONTHDAY + * * BYDAY + * * BYSETPOS + * * FREQ=YEARLY + * * BYMONTH + * * BYMONTHDAY (only if BYMONTH is also set) + * * BYDAY (only if BYMONTH is also set) + * + * Anything beyond this is 'undefined', which means that it may get ignored, or + * you may get unexpected results. The effect is that in some applications the + * specified recurrence may look incorrect, or is missing. + * + * The recurrence iterator also does not yet support THISANDFUTURE. + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class EventIterator implements \Iterator { + + /** + * Reference timeZone for floating dates and times. + * + * @var DateTimeZone + */ + protected $timeZone; + + /** + * True if we're iterating an all-day event. + * + * @var bool + */ + protected $allDay = false; + + /** + * Creates the iterator + * + * There's three ways to set up the iterator. + * + * 1. You can pass a VCALENDAR component and a UID. + * 2. You can pass an array of VEVENTs (all UIDS should match). + * 3. You can pass a single VEVENT component. + * + * Only the second method is recomended. The other 1 and 3 will be removed + * at some point in the future. + * + * The $uid parameter is only required for the first method. + * + * @param Component|array $input + * @param string|null $uid + * @param DateTimeZone $timeZone Reference timezone for floating dates and + * times. + */ + public function __construct($input, $uid = null, DateTimeZone $timeZone = null) { + + if (is_null($this->timeZone)) { + $timeZone = new DateTimeZone('UTC'); + } + $this->timeZone = $timeZone; + + if (is_array($input)) { + $events = $input; + } elseif ($input instanceof VEvent) { + // Single instance mode. + $events = array($input); + } else { + // Calendar + UID mode. + $uid = (string)$uid; + if (!$uid) { + throw new InvalidArgumentException('The UID argument is required when a VCALENDAR is passed to this constructor'); + } + if (!isset($input->VEVENT)) { + throw new InvalidArgumentException('No events found in this calendar'); + } + $events = $input->getByUID($uid); + + } + + foreach($events as $vevent) { + + if (!isset($vevent->{'RECURRENCE-ID'})) { + + $this->masterEvent = $vevent; + + } else { + + $this->exceptions[ + $vevent->{'RECURRENCE-ID'}->getDateTime($this->timeZone)->getTimeStamp() + ] = true; + $this->overriddenEvents[] = $vevent; + + } + + } + + if (!$this->masterEvent) { + // No base event was found. CalDAV does allow cases where only + // overridden instances are stored. + // + // In this particular case, we're just going to grab the first + // event and use that instead. This may not always give the + // desired result. + if (!count($this->overriddenEvents)) { + throw new InvalidArgumentException('This VCALENDAR did not have an event with UID: ' . $uid); + } + $this->masterEvent = array_shift($this->overriddenEvents); + } + + $this->startDate = $this->masterEvent->DTSTART->getDateTime($this->timeZone); + $this->allDay = !$this->masterEvent->DTSTART->hasTime(); + + if (isset($this->masterEvent->EXDATE)) { + + foreach($this->masterEvent->EXDATE as $exDate) { + + foreach($exDate->getDateTimes($this->timeZone) as $dt) { + $this->exceptions[$dt->getTimeStamp()] = true; + } + + } + + } + + if (isset($this->masterEvent->DTEND)) { + $this->eventDuration = + $this->masterEvent->DTEND->getDateTime($this->timeZone)->getTimeStamp() - + $this->startDate->getTimeStamp(); + } elseif (isset($this->masterEvent->DURATION)) { + $duration = $this->masterEvent->DURATION->getDateInterval(); + $end = clone $this->startDate; + $end->add($duration); + $this->eventDuration = $end->getTimeStamp() - $this->startDate->getTimeStamp(); + } elseif ($this->allDay) { + $this->eventDuration = 3600 * 24; + } else { + $this->eventDuration = 0; + } + + if (isset($this->masterEvent->RDATE)) { + $this->recurIterator = new RDateIterator( + $this->masterEvent->RDATE->getParts(), + $this->startDate + ); + } elseif (isset($this->masterEvent->RRULE)) { + $this->recurIterator = new RRuleIterator( + $this->masterEvent->RRULE->getParts(), + $this->startDate + ); + } else { + $this->recurIterator = new RRuleIterator( + array( + 'FREQ' => 'DAILY', + 'COUNT' => 1, + ), + $this->startDate + ); + } + + $this->rewind(); + if (!$this->valid()) { + throw new NoInstancesException('This recurrence rule does not generate any valid instances'); + } + + } + + /** + * Returns the date for the current position of the iterator. + * + * @return DateTime + */ + public function current() { + + if ($this->currentDate) { + return clone $this->currentDate; + } + + } + + /** + * This method returns the start date for the current iteration of the + * event. + * + * @return DateTime + */ + public function getDtStart() { + + if ($this->currentDate) { + return clone $this->currentDate; + } + + } + + /** + * This method returns the end date for the current iteration of the + * event. + * + * @return DateTime + */ + public function getDtEnd() { + + if (!$this->valid()) { + return null; + } + $end = clone $this->currentDate; + $end->modify('+' . $this->eventDuration . ' seconds'); + return $end; + + } + + /** + * Returns a VEVENT for the current iterations of the event. + * + * This VEVENT will have a recurrence id, and it's DTSTART and DTEND + * altered. + * + * @return VEvent + */ + public function getEventObject() { + + if ($this->currentOverriddenEvent) { + return $this->currentOverriddenEvent; + } + + $event = clone $this->masterEvent; + + // Ignoring the following block, because PHPUnit's code coverage + // ignores most of these lines, and this messes with our stats. + // + // @codeCoverageIgnoreStart + unset( + $event->RRULE, + $event->EXDATE, + $event->RDATE, + $event->EXRULE, + $event->{'RECURRENCE-ID'} + ); + // @codeCoverageIgnoreEnd + + $event->DTSTART->setDateTime($this->getDtStart(), $event->DTSTART->isFloating()); + if (isset($event->DTEND)) { + $event->DTEND->setDateTime($this->getDtEnd(), $event->DTEND->isFloating()); + } + $recurid = clone $event->DTSTART; + $recurid->name = 'RECURRENCE-ID'; + $event->add($recurid); + return $event; + + } + + /** + * Returns the current position of the iterator. + * + * This is for us simply a 0-based index. + * + * @return int + */ + public function key() { + + // The counter is always 1 ahead. + return $this->counter - 1; + + } + + /** + * This is called after next, to see if the iterator is still at a valid + * position, or if it's at the end. + * + * @return bool + */ + public function valid() { + + return !!$this->currentDate; + + } + + /** + * Sets the iterator back to the starting point. + */ + public function rewind() { + + $this->recurIterator->rewind(); + // re-creating overridden event index. + $index = array(); + foreach($this->overriddenEvents as $key=>$event) { + $stamp = $event->DTSTART->getDateTime($this->timeZone)->getTimeStamp(); + $index[$stamp] = $key; + } + krsort($index); + $this->counter = 0; + $this->overriddenEventsIndex = $index; + $this->currentOverriddenEvent = null; + + $this->nextDate = null; + $this->currentDate = clone $this->startDate; + + $this->next(); + + } + + /** + * Advances the iterator with one step. + * + * @return void + */ + public function next() { + + $this->currentOverriddenEvent = null; + $this->counter++; + if ($this->nextDate) { + // We had a stored value. + $nextDate = $this->nextDate; + $this->nextDate = null; + } else { + // We need to ask rruleparser for the next date. + // We need to do this until we find a date that's not in the + // exception list. + do { + if (!$this->recurIterator->valid()) { + $nextDate = null; + break; + } + $nextDate = $this->recurIterator->current(); + $this->recurIterator->next(); + } while(isset($this->exceptions[$nextDate->getTimeStamp()])); + + } + + + // $nextDate now contains what rrule thinks is the next one, but an + // overridden event may cut ahead. + if ($this->overriddenEventsIndex) { + + $offset = end($this->overriddenEventsIndex); + $timestamp = key($this->overriddenEventsIndex); + if (!$nextDate || $timestamp < $nextDate->getTimeStamp()) { + // Overridden event comes first. + $this->currentOverriddenEvent = $this->overriddenEvents[$offset]; + + // Putting the rrule next date aside. + $this->nextDate = $nextDate; + $this->currentDate = $this->currentOverriddenEvent->DTSTART->getDateTime($this->timeZone); + + // Ensuring that this item will only be used once. + array_pop($this->overriddenEventsIndex); + + // Exit point! + return; + + } + + } + + $this->currentDate = $nextDate; + + } + + /** + * Quickly jump to a date in the future. + * + * @param DateTime $dateTime + */ + public function fastForward(DateTime $dateTime) { + + while($this->valid() && $this->getDtEnd() < $dateTime ) { + $this->next(); + } + + } + + /** + * Returns true if this recurring event never ends. + * + * @return bool + */ + public function isInfinite() { + + return $this->recurIterator->isInfinite(); + + } + + /** + * RRULE parser + * + * @var RRuleIterator + */ + protected $recurIterator; + + /** + * The duration, in seconds, of the master event. + * + * We use this to calculate the DTEND for subsequent events. + */ + protected $eventDuration; + + /** + * A reference to the main (master) event. + * + * @var VEVENT + */ + protected $masterEvent; + + /** + * List of overridden events. + * + * @var array + */ + protected $overriddenEvents = array(); + + /** + * Overridden event index. + * + * Key is timestamp, value is the index of the item in the $overriddenEvent + * property. + * + * @var array + */ + protected $overriddenEventsIndex; + + /** + * A list of recurrence-id's that are either part of EXDATE, or are + * overridden. + * + * @var array + */ + protected $exceptions = array(); + + /** + * Internal event counter + * + * @var int + */ + protected $counter; + + /** + * The very start of the iteration process. + * + * @var DateTime + */ + protected $startDate; + + /** + * Where we are currently in the iteration process + * + * @var DateTime + */ + protected $currentDate; + + /** + * The next date from the rrule parser. + * + * Sometimes we need to temporary store the next date, because an + * overridden event came before. + * + * @var DateTime + */ + protected $nextDate; + +} diff --git a/src/serverlib/3rdparty/sabre/VObject/Recur/NoInstancesException.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Recur/NoInstancesException.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Recur/NoInstancesException.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Recur/NoInstancesException.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Recur/RDateIterator.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Recur/RDateIterator.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Recur/RDateIterator.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Recur/RDateIterator.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Recur/RRuleIterator.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Recur/RRuleIterator.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Recur/RRuleIterator.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Recur/RRuleIterator.php diff --git a/src/serverlib/3rdparty/sabre/VObject/RecurrenceIterator.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/RecurrenceIterator.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/RecurrenceIterator.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/RecurrenceIterator.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Splitter/ICalendar.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Splitter/ICalendar.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Splitter/ICalendar.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Splitter/ICalendar.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Splitter/SplitterInterface.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Splitter/SplitterInterface.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Splitter/SplitterInterface.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Splitter/SplitterInterface.php diff --git a/src/serverlib/3rdparty/sabre/VObject/Splitter/VCard.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Splitter/VCard.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/Splitter/VCard.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Splitter/VCard.php diff --git a/src/serverlib/3rdparty/sabre/VObject/StringUtil.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/StringUtil.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/StringUtil.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/StringUtil.php diff --git a/src/serverlib/3rdparty/sabre/VObject/TimeZoneUtil.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/TimeZoneUtil.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/TimeZoneUtil.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/TimeZoneUtil.php diff --git a/src/serverlib/3rdparty/sabre/VObject/UUIDUtil.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/UUIDUtil.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/UUIDUtil.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/UUIDUtil.php diff --git a/src/serverlib/3rdparty/sabre/VObject/VCardConverter.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/VCardConverter.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/VCardConverter.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/VCardConverter.php diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Version.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Version.php new file mode 100644 index 0000000..3b14056 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/Version.php @@ -0,0 +1,19 @@ +<?php + +namespace Sabre\VObject; + +/** + * This class contains the version number for the VObject package + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Version { + + /** + * Full version number + */ + const VERSION = '3.5.1'; + +} diff --git a/src/serverlib/3rdparty/sabre/VObject/timezonedata/exchangezones.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/timezonedata/exchangezones.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/timezonedata/exchangezones.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/timezonedata/exchangezones.php diff --git a/src/serverlib/3rdparty/sabre/VObject/timezonedata/lotuszones.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/timezonedata/lotuszones.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/timezonedata/lotuszones.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/timezonedata/lotuszones.php diff --git a/src/serverlib/3rdparty/sabre/VObject/timezonedata/php-bc.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/timezonedata/php-bc.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/timezonedata/php-bc.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/timezonedata/php-bc.php diff --git a/src/serverlib/3rdparty/sabre/VObject/timezonedata/php-workaround.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/timezonedata/php-workaround.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/timezonedata/php-workaround.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/timezonedata/php-workaround.php diff --git a/src/serverlib/3rdparty/sabre/VObject/timezonedata/windowszones.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/timezonedata/windowszones.php similarity index 100% rename from src/serverlib/3rdparty/sabre/VObject/timezonedata/windowszones.php rename to src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/lib/timezonedata/windowszones.php diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/AttachIssueTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/AttachIssueTest.php new file mode 100644 index 0000000..68c9872 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/AttachIssueTest.php @@ -0,0 +1,22 @@ +<?php + +namespace Sabre\VObject; + +class AttachIssueTest extends \PHPUnit_Framework_TestCase { + + function testRead() { + + $event = <<<ICS +BEGIN:VCALENDAR\r +BEGIN:VEVENT\r +ATTACH;FMTTYPE=;ENCODING=:Zm9v\r +END:VEVENT\r +END:VCALENDAR\r + +ICS; + $obj = Reader::read($event); + $this->assertEquals($event, $obj->serialize()); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/CliTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/CliTest.php new file mode 100644 index 0000000..9049c00 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/CliTest.php @@ -0,0 +1,650 @@ +<?php + +namespace Sabre\VObject; + +/** + * Tests the cli. + * + * Warning: these tests are very rudimentary. + */ +class CliTest extends \PHPUnit_Framework_TestCase { + + public function setUp() { + + $this->cli = new CliMock(); + $this->cli->stderr = fopen('php://memory','r+'); + $this->cli->stdout = fopen('php://memory','r+'); + + } + + public function testInvalidArg() { + + $this->assertEquals( + 1, + $this->cli->main(array('vobject', '--hi')) + ); + rewind($this->cli->stderr); + $this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100); + + } + + public function testQuiet() { + + $this->assertEquals( + 1, + $this->cli->main(array('vobject', '-q')) + ); + $this->assertTrue($this->cli->quiet); + + rewind($this->cli->stderr); + $this->assertEquals(0, strlen(stream_get_contents($this->cli->stderr))); + + } + + public function testHelp() { + + $this->assertEquals( + 0, + $this->cli->main(array('vobject', '-h')) + ); + rewind($this->cli->stderr); + $this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100); + + } + + public function testFormat() { + + $this->assertEquals( + 1, + $this->cli->main(array('vobject', '--format=jcard')) + ); + + rewind($this->cli->stderr); + $this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100); + + $this->assertEquals('jcard', $this->cli->format); + + } + + public function testFormatInvalid() { + + $this->assertEquals( + 1, + $this->cli->main(array('vobject', '--format=foo')) + ); + + rewind($this->cli->stderr); + $this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100); + + $this->assertNull($this->cli->format); + + } + + public function testInputFormatInvalid() { + + $this->assertEquals( + 1, + $this->cli->main(array('vobject', '--inputformat=foo')) + ); + + rewind($this->cli->stderr); + $this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100); + + $this->assertNull($this->cli->format); + + } + + + public function testNoInputFile() { + + $this->assertEquals( + 1, + $this->cli->main(array('vobject', 'color')) + ); + + rewind($this->cli->stderr); + $this->assertTrue(strlen(stream_get_contents($this->cli->stderr)) > 100); + + } + + public function testTooManyArgs() { + + $this->assertEquals( + 1, + $this->cli->main(array('vobject', 'color', 'a', 'b', 'c')) + ); + + } + + public function testUnknownCommand() { + + $this->assertEquals( + 1, + $this->cli->main(array('vobject', 'foo', '-')) + ); + + } + + public function testConvertJson() { + + $inputStream = fopen('php://memory','r+'); + + fwrite($inputStream, <<<ICS +BEGIN:VCARD +VERSION:3.0 +FN:Cowboy Henk +END:VCARD +ICS + ); + rewind($inputStream); + $this->cli->stdin = $inputStream; + + $this->assertEquals( + 0, + $this->cli->main(array('vobject', 'convert','--format=json', '-')) + ); + + rewind($this->cli->stdout); + $version = Version::VERSION; + $this->assertEquals( + '["vcard",[["version",{},"text","4.0"],["prodid",{},"text","-\/\/Sabre\/\/Sabre VObject '. $version .'\/\/EN"],["fn",{},"text","Cowboy Henk"]]]', + stream_get_contents($this->cli->stdout) + ); + + } + + public function testConvertJCardPretty() { + + if (version_compare(PHP_VERSION, '5.4.0') < 0) { + $this->markTestSkipped('This test required PHP 5.4.0'); + } + + $inputStream = fopen('php://memory','r+'); + + fwrite($inputStream, <<<ICS +BEGIN:VCARD +VERSION:3.0 +FN:Cowboy Henk +END:VCARD +ICS + ); + rewind($inputStream); + $this->cli->stdin = $inputStream; + + $this->assertEquals( + 0, + $this->cli->main(array('vobject', 'convert','--format=jcard', '--pretty', '-')) + ); + + rewind($this->cli->stdout); + $version = Version::VERSION; + + // PHP 5.5.12 changed the output + + $expected = <<<JCARD +[ + "vcard", + [ + [ + "versi +JCARD; + + $this->assertStringStartsWith( + $expected, + stream_get_contents($this->cli->stdout) + ); + + } + + public function testConvertJCalFail() { + + $inputStream = fopen('php://memory','r+'); + + fwrite($inputStream, <<<ICS +BEGIN:VCARD +VERSION:3.0 +FN:Cowboy Henk +END:VCARD +ICS + ); + rewind($inputStream); + $this->cli->stdin = $inputStream; + + $this->assertEquals( + 2, + $this->cli->main(array('vobject', 'convert','--format=jcal', '--inputformat=mimedir', '-')) + ); + + } + + public function testConvertMimeDir() { + + $inputStream = fopen('php://memory','r+'); + + fwrite($inputStream, <<<JCARD +[ + "vcard", + [ + [ + "version", + { + + }, + "text", + "4.0" + ], + [ + "prodid", + { + + }, + "text", + "-\/\/Sabre\/\/Sabre VObject 3.1.0\/\/EN" + ], + [ + "fn", + { + + }, + "text", + "Cowboy Henk" + ] + ] +] +JCARD + ); + rewind($inputStream); + $this->cli->stdin = $inputStream; + + $this->assertEquals( + 0, + $this->cli->main(array('vobject', 'convert','--format=mimedir', '--inputformat=json', '--pretty', '-')) + ); + + rewind($this->cli->stdout); + $expected = <<<VCF +BEGIN:VCARD +VERSION:4.0 +PRODID:-//Sabre//Sabre VObject 3.1.0//EN +FN:Cowboy Henk +END:VCARD + +VCF; + + $this->assertEquals( + strtr($expected, array("\n"=>"\r\n")), + stream_get_contents($this->cli->stdout) + ); + + } + + public function testConvertDefaultFormats() { + + $inputStream = fopen('php://memory','r+'); + $outputFile = SABRE_TEMPDIR . 'bar.json'; + + $this->assertEquals( + 2, + $this->cli->main(array('vobject', 'convert','foo.json',$outputFile)) + ); + + $this->assertEquals('json', $this->cli->inputFormat); + $this->assertEquals('json', $this->cli->format); + + } + + public function testConvertDefaultFormats2() { + + $outputFile = SABRE_TEMPDIR . 'bar.ics'; + + $this->assertEquals( + 2, + $this->cli->main(array('vobject', 'convert','foo.ics',$outputFile)) + ); + + $this->assertEquals('mimedir', $this->cli->inputFormat); + $this->assertEquals('mimedir', $this->cli->format); + + } + + public function testVCard3040() { + + $inputStream = fopen('php://memory','r+'); + + fwrite($inputStream, <<<VCARD +BEGIN:VCARD +VERSION:3.0 +PRODID:-//Sabre//Sabre VObject 3.1.0//EN +FN:Cowboy Henk +END:VCARD + +VCARD + ); + rewind($inputStream); + $this->cli->stdin = $inputStream; + + $this->assertEquals( + 0, + $this->cli->main(array('vobject', 'convert','--format=vcard40', '--pretty', '-')) + ); + + rewind($this->cli->stdout); + + $version = Version::VERSION; + $expected = <<<VCF +BEGIN:VCARD +VERSION:4.0 +PRODID:-//Sabre//Sabre VObject $version//EN +FN:Cowboy Henk +END:VCARD + +VCF; + + $this->assertEquals( + strtr($expected, array("\n"=>"\r\n")), + stream_get_contents($this->cli->stdout) + ); + + } + + public function testVCard4030() { + + $inputStream = fopen('php://memory','r+'); + + fwrite($inputStream, <<<VCARD +BEGIN:VCARD +VERSION:4.0 +PRODID:-//Sabre//Sabre VObject 3.1.0//EN +FN:Cowboy Henk +END:VCARD + +VCARD + ); + rewind($inputStream); + $this->cli->stdin = $inputStream; + + $this->assertEquals( + 0, + $this->cli->main(array('vobject', 'convert','--format=vcard30', '--pretty', '-')) + ); + + $version = Version::VERSION; + + rewind($this->cli->stdout); + $expected = <<<VCF +BEGIN:VCARD +VERSION:3.0 +PRODID:-//Sabre//Sabre VObject $version//EN +FN:Cowboy Henk +END:VCARD + +VCF; + + $this->assertEquals( + strtr($expected, array("\n"=>"\r\n")), + stream_get_contents($this->cli->stdout) + ); + + } + + public function testVCard4021() { + + $inputStream = fopen('php://memory','r+'); + + fwrite($inputStream, <<<VCARD +BEGIN:VCARD +VERSION:4.0 +PRODID:-//Sabre//Sabre VObject 3.1.0//EN +FN:Cowboy Henk +END:VCARD + +VCARD + ); + rewind($inputStream); + $this->cli->stdin = $inputStream; + + // vCard 2.1 is not supported yet, so this returns a failure. + $this->assertEquals( + 2, + $this->cli->main(array('vobject', 'convert','--format=vcard21', '--pretty', '-')) + ); + + } + + function testValidate() { + + $inputStream = fopen('php://memory','r+'); + + fwrite($inputStream, <<<VCARD +BEGIN:VCARD +VERSION:4.0 +PRODID:-//Sabre//Sabre VObject 3.1.0//EN +UID:foo +FN:Cowboy Henk +END:VCARD + +VCARD + ); + rewind($inputStream); + $this->cli->stdin = $inputStream; + $result = $this->cli->main(array('vobject', 'validate', '-')); + + $this->assertEquals( + 0, + $result + ); + + } + + function testValidateFail() { + + $inputStream = fopen('php://memory','r+'); + + fwrite($inputStream, <<<VCARD +BEGIN:VCALENDAR +VERSION:2.0 +END:VCARD + +VCARD + ); + rewind($inputStream); + $this->cli->stdin = $inputStream; + // vCard 2.1 is not supported yet, so this returns a failure. + $this->assertEquals( + 2, + $this->cli->main(array('vobject', 'validate', '-')) + ); + + } + + function testValidateFail2() { + + $inputStream = fopen('php://memory','r+'); + + fwrite($inputStream, <<<VCARD +BEGIN:VCALENDAR +VERSION:5.0 +END:VCALENDAR + +VCARD + ); + rewind($inputStream); + $this->cli->stdin = $inputStream; + // vCard 2.1 is not supported yet, so this returns a failure. + $this->assertEquals( + 2, + $this->cli->main(array('vobject', 'validate', '-')) + ); + + } + + function testRepair() { + + $inputStream = fopen('php://memory','r+'); + + fwrite($inputStream, <<<VCARD +BEGIN:VCARD +VERSION:5.0 +END:VCARD + +VCARD + ); + rewind($inputStream); + $this->cli->stdin = $inputStream; + // vCard 2.1 is not supported yet, so this returns a failure. + $this->assertEquals( + 2, + $this->cli->main(array('vobject', 'repair', '-')) + ); + + rewind($this->cli->stdout); + $this->assertRegExp("/^BEGIN:VCARD\r\nVERSION:2.1\r\nUID:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\r\nEND:VCARD\r\n$/", stream_get_contents($this->cli->stdout)); + } + + function testRepairNothing() { + + $inputStream = fopen('php://memory','r+'); + + fwrite($inputStream, <<<VCARD +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject 3.1.0//EN +BEGIN:VEVENT +UID:foo +DTSTAMP:20140122T233226Z +DTSTART:20140101T120000Z +END:VEVENT +END:VCALENDAR + +VCARD + ); + rewind($inputStream); + $this->cli->stdin = $inputStream; + // vCard 2.1 is not supported yet, so this returns a failure. + + $result = $this->cli->main(array('vobject', 'repair', '-')); + + rewind($this->cli->stderr); + $error = stream_get_contents($this->cli->stderr); + + $this->assertEquals( + 0, + $result, + "This should have been error free. stderr output:\n" . $error + ); + + } + + /** + * Note: this is a very shallow test, doesn't dig into the actual output, + * but just makes sure there's no errors thrown. + * + * The colorizer is not a critical component, it's mostly a debugging tool. + */ + function testColorCalendar() { + + $inputStream = fopen('php://memory','r+'); + + $version = Version::VERSION; + + /** + * This object is not valid, but it's designed to hit every part of the + * colorizer source. + */ + fwrite($inputStream, <<<VCARD +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject {$version}//EN +BEGIN:VTIMEZONE +END:VTIMEZONE +BEGIN:VEVENT +ATTENDEE;RSVP=TRUE:mailto:foo@example.org +REQUEST-STATUS:5;foo +ATTACH:blabla +END:VEVENT +END:VCALENDAR + +VCARD + ); + rewind($inputStream); + $this->cli->stdin = $inputStream; + // vCard 2.1 is not supported yet, so this returns a failure. + + $result = $this->cli->main(array('vobject', 'color', '-')); + + rewind($this->cli->stderr); + $error = stream_get_contents($this->cli->stderr); + + $this->assertEquals( + 0, + $result, + "This should have been error free. stderr output:\n" . $error + ); + + } + + /** + * Note: this is a very shallow test, doesn't dig into the actual output, + * but just makes sure there's no errors thrown. + * + * The colorizer is not a critical component, it's mostly a debugging tool. + */ + function testColorVCard() { + + $inputStream = fopen('php://memory','r+'); + + $version = Version::VERSION; + + /** + * This object is not valid, but it's designed to hit every part of the + * colorizer source. + */ + fwrite($inputStream, <<<VCARD +BEGIN:VCARD +VERSION:4.0 +PRODID:-//Sabre//Sabre VObject {$version}//EN +ADR:1;2;3;4a,4b;5;6 +group.TEL:123454768 +END:VCARD + +VCARD + ); + rewind($inputStream); + $this->cli->stdin = $inputStream; + // vCard 2.1 is not supported yet, so this returns a failure. + + $result = $this->cli->main(array('vobject', 'color', '-')); + + rewind($this->cli->stderr); + $error = stream_get_contents($this->cli->stderr); + + $this->assertEquals( + 0, + $result, + "This should have been error free. stderr output:\n" . $error + ); + + } +} + +class CliMock extends Cli { + + public $log = array(); + + public $quiet = false; + + public $format; + + public $pretty; + + public $stdin; + + public $stdout; + + public $stderr; + + public $inputFormat; + + public $outputFormat; + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VAlarmTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VAlarmTest.php new file mode 100644 index 0000000..76dd2e3 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VAlarmTest.php @@ -0,0 +1,179 @@ +<?php + +namespace Sabre\VObject\Component; + +use Sabre\VObject\Component; +use DateTime; +use Sabre\VObject\Reader; + +class VAlarmTest extends \PHPUnit_Framework_TestCase { + + /** + * @dataProvider timeRangeTestData + */ + public function testInTimeRange(VAlarm $valarm,$start,$end,$outcome) { + + $this->assertEquals($outcome, $valarm->isInTimeRange($start, $end)); + + } + + public function timeRangeTestData() { + + $tests = array(); + + $calendar = new VCalendar(); + + // Hard date and time + $valarm1 = $calendar->createComponent('VALARM'); + $valarm1->add( + $calendar->createProperty('TRIGGER', '20120312T130000Z', array('VALUE' => 'DATE-TIME')) + ); + + $tests[] = array($valarm1, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-04-01 01:00:00'), true); + $tests[] = array($valarm1, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-03-10 01:00:00'), false); + + // Relation to start time of event + $valarm2 = $calendar->createComponent('VALARM'); + $valarm2->add( + $calendar->createProperty('TRIGGER', '-P1D', array('VALUE' => 'DURATION')) + ); + + $vevent2 = $calendar->createComponent('VEVENT'); + $vevent2->DTSTART = '20120313T130000Z'; + $vevent2->add($valarm2); + + $tests[] = array($valarm2, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-04-01 01:00:00'), true); + $tests[] = array($valarm2, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-03-10 01:00:00'), false); + + // Relation to end time of event + $valarm3 = $calendar->createComponent('VALARM'); + $valarm3->add( $calendar->createProperty('TRIGGER', '-P1D', array('VALUE'=>'DURATION', 'RELATED' => 'END')) ); + + $vevent3 = $calendar->createComponent('VEVENT'); + $vevent3->DTSTART = '20120301T130000Z'; + $vevent3->DTEND = '20120401T130000Z'; + $vevent3->add($valarm3); + + $tests[] = array($valarm3, new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'), false); + $tests[] = array($valarm3, new DateTime('2012-03-25 01:00:00'), new DateTime('2012-04-05 01:00:00'), true); + + // Relation to end time of todo + $valarm4 = $calendar->createComponent('VALARM'); + $valarm4->TRIGGER = '-P1D'; + $valarm4->TRIGGER['VALUE'] = 'DURATION'; + $valarm4->TRIGGER['RELATED']= 'END'; + + $vtodo4 = $calendar->createComponent('VTODO'); + $vtodo4->DTSTART = '20120301T130000Z'; + $vtodo4->DUE = '20120401T130000Z'; + $vtodo4->add($valarm4); + + $tests[] = array($valarm4, new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'), false); + $tests[] = array($valarm4, new DateTime('2012-03-25 01:00:00'), new DateTime('2012-04-05 01:00:00'), true); + + // Relation to start time of event + repeat + $valarm5 = $calendar->createComponent('VALARM'); + $valarm5->TRIGGER = '-P1D'; + $valarm5->TRIGGER['VALUE'] = 'DURATION'; + $valarm5->REPEAT = 10; + $valarm5->DURATION = 'P1D'; + + $vevent5 = $calendar->createComponent('VEVENT'); + $vevent5->DTSTART = '20120301T130000Z'; + $vevent5->add($valarm5); + + $tests[] = array($valarm5, new DateTime('2012-03-09 01:00:00'), new DateTime('2012-03-10 01:00:00'), true); + + // Relation to start time of event + duration, but no repeat + $valarm6 = $calendar->createComponent('VALARM'); + $valarm6->TRIGGER = '-P1D'; + $valarm6->TRIGGER['VALUE'] = 'DURATION'; + $valarm6->DURATION = 'P1D'; + + $vevent6 = $calendar->createComponent('VEVENT'); + $vevent6->DTSTART = '20120313T130000Z'; + $vevent6->add($valarm6); + + $tests[] = array($valarm6, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-04-01 01:00:00'), true); + $tests[] = array($valarm6, new DateTime('2012-03-01 01:00:00'), new DateTime('2012-03-10 01:00:00'), false); + + + // Relation to end time of event (DURATION instead of DTEND) + $valarm7 = $calendar->createComponent('VALARM'); + $valarm7->TRIGGER = '-P1D'; + $valarm7->TRIGGER['VALUE'] = 'DURATION'; + $valarm7->TRIGGER['RELATED']= 'END'; + + $vevent7 = $calendar->createComponent('VEVENT'); + $vevent7->DTSTART = '20120301T130000Z'; + $vevent7->DURATION = 'P30D'; + $vevent7->add($valarm7); + + $tests[] = array($valarm7, new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'), false); + $tests[] = array($valarm7, new DateTime('2012-03-25 01:00:00'), new DateTime('2012-04-05 01:00:00'), true); + + // Relation to end time of event (No DTEND or DURATION) + $valarm7 = $calendar->createComponent('VALARM'); + $valarm7->TRIGGER = '-P1D'; + $valarm7->TRIGGER['VALUE'] = 'DURATION'; + $valarm7->TRIGGER['RELATED']= 'END'; + + $vevent7 = $calendar->createComponent('VEVENT'); + $vevent7->DTSTART = '20120301T130000Z'; + $vevent7->add($valarm7); + + $tests[] = array($valarm7, new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00'), true); + $tests[] = array($valarm7, new DateTime('2012-03-25 01:00:00'), new DateTime('2012-04-05 01:00:00'), false); + + + return $tests; + } + + /** + * @expectedException LogicException + */ + public function testInTimeRangeInvalidComponent() { + + $calendar = new VCalendar(); + $valarm = $calendar->createComponent('VALARM'); + $valarm->TRIGGER = '-P1D'; + $valarm->TRIGGER['RELATED'] = 'END'; + + $vjournal = $calendar->createComponent('VJOURNAL'); + $vjournal->add($valarm); + + $valarm->isInTimeRange(new DateTime('2012-02-25 01:00:00'), new DateTime('2012-03-05 01:00:00')); + + } + + /** + * This bug was found and reported on the mailing list. + */ + public function testInTimeRangeBuggy() { + +$input = <<<BLA +BEGIN:VCALENDAR +BEGIN:VTODO +DTSTAMP:20121003T064931Z +UID:b848cb9a7bb16e464a06c222ca1f8102@examle.com +STATUS:NEEDS-ACTION +DUE:20121005T000000Z +SUMMARY:Task 1 +CATEGORIES:AlarmCategory +BEGIN:VALARM +TRIGGER:-PT10M +ACTION:DISPLAY +DESCRIPTION:Task 1 +END:VALARM +END:VTODO +END:VCALENDAR +BLA; + + $vobj = Reader::read($input); + + $this->assertTrue($vobj->VTODO->VALARM->isInTimeRange(new \DateTime('2012-10-01 00:00:00'), new \DateTime('2012-11-01 00:00:00'))); + + } + +} + diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VAvailabilityTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VAvailabilityTest.php new file mode 100644 index 0000000..979a853 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VAvailabilityTest.php @@ -0,0 +1,385 @@ +<?php + +namespace Sabre\VObject\Component; + +use Sabre\VObject; +use Sabre\VObject\Reader; +use Sabre\VObject\Component; +use Sabre\VObject\Component\VAvailability; + +/** + * We use `RFCxxx` has a placeholder for the + * https://tools.ietf.org/html/draft-daboo-calendar-availability-05 name. + */ +class VAvailabilityTest extends \PHPUnit_Framework_TestCase { + + function testVAvailabilityComponent() { + + $vcal = <<<VCAL +BEGIN:VCALENDAR +BEGIN:VAVAILABILITY +END:VAVAILABILITY +END:VCALENDAR +VCAL; + $document = Reader::read($vcal); + + $this->assertInstanceOf(__NAMESPACE__ . '\VAvailability', $document->VAVAILABILITY); + + } + + function testRFCxxxSection3_1_availabilityprop_required() { + + // UID and DTSTAMP are present. + $this->assertIsValid(Reader::read( +<<<VCAL +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//id +BEGIN:VAVAILABILITY +UID:foo@test +DTSTAMP:20111005T133225Z +END:VAVAILABILITY +END:VCALENDAR +VCAL + )); + + // UID and DTSTAMP are missing. + $this->assertIsNotValid(Reader::read( +<<<VCAL +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//id +BEGIN:VAVAILABILITY +END:VAVAILABILITY +END:VCALENDAR +VCAL + )); + + // DTSTAMP is missing. + $this->assertIsNotValid(Reader::read( +<<<VCAL +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//id +BEGIN:VAVAILABILITY +UID:foo@test +END:VAVAILABILITY +END:VCALENDAR +VCAL + )); + + // UID is missing. + $this->assertIsNotValid(Reader::read( +<<<VCAL +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//id +BEGIN:VAVAILABILITY +DTSTAMP:20111005T133225Z +END:VAVAILABILITY +END:VCALENDAR +VCAL + )); + + } + + function testRFCxxxSection3_1_availabilityprop_optional_once() { + + $properties = array( + 'BUSYTYPE:BUSY', + 'CLASS:PUBLIC', + 'CREATED:20111005T135125Z', + 'DESCRIPTION:Long bla bla', + 'DTSTART:20111005T020000', + 'LAST-MODIFIED:20111005T135325Z', + 'ORGANIZER:mailto:foo@example.com', + 'PRIORITY:1', + 'SEQUENCE:0', + 'SUMMARY:Bla bla', + 'URL:http://example.org/' + ); + + // They are all present, only once. + $this->assertIsValid(Reader::read($this->template($properties))); + + // We duplicate each one to see if it fails. + foreach ($properties as $property) { + $this->assertIsNotValid(Reader::read($this->template(array( + $property, + $property + )))); + } + + } + + function testRFCxxxSection3_1_availabilityprop_dtend_duration() { + + // Only DTEND. + $this->assertIsValid(Reader::read($this->template(array( + 'DTEND:21111005T133225Z' + )))); + + // Only DURATION. + $this->assertIsValid(Reader::read($this->template(array( + 'DURATION:PT1H' + )))); + + // Both (not allowed). + $this->assertIsNotValid(Reader::read($this->template(array( + 'DTEND:21111005T133225Z', + 'DURATION:PT1H' + )))); + } + + function testAvailableSubComponent() { + + $vcal = <<<VCAL +BEGIN:VCALENDAR +BEGIN:VAVAILABILITY +BEGIN:AVAILABLE +END:AVAILABLE +END:VAVAILABILITY +END:VCALENDAR +VCAL; + $document = Reader::read($vcal); + + $this->assertInstanceOf(__NAMESPACE__, $document->VAVAILABILITY->AVAILABLE); + + } + + function testRFCxxxSection3_1_availableprop_required() { + + // UID, DTSTAMP and DTSTART are present. + $this->assertIsValid(Reader::read( +<<<VCAL +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//id +BEGIN:VAVAILABILITY +UID:foo@test +DTSTAMP:20111005T133225Z +BEGIN:AVAILABLE +UID:foo@test +DTSTAMP:20111005T133225Z +DTSTART:20111005T133225Z +END:AVAILABLE +END:VAVAILABILITY +END:VCALENDAR +VCAL + )); + + // UID, DTSTAMP and DTSTART are missing. + $this->assertIsNotValid(Reader::read( +<<<VCAL +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//id +BEGIN:VAVAILABILITY +UID:foo@test +DTSTAMP:20111005T133225Z +BEGIN:AVAILABLE +END:AVAILABLE +END:VAVAILABILITY +END:VCALENDAR +VCAL + )); + + // UID is missing. + $this->assertIsNotValid(Reader::read( +<<<VCAL +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//id +BEGIN:VAVAILABILITY +UID:foo@test +DTSTAMP:20111005T133225Z +BEGIN:AVAILABLE +DTSTAMP:20111005T133225Z +DTSTART:20111005T133225Z +END:AVAILABLE +END:VAVAILABILITY +END:VCALENDAR +VCAL + )); + + // DTSTAMP is missing. + $this->assertIsNotValid(Reader::read( +<<<VCAL +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//id +BEGIN:VAVAILABILITY +UID:foo@test +DTSTAMP:20111005T133225Z +BEGIN:AVAILABLE +UID:foo@test +DTSTART:20111005T133225Z +END:AVAILABLE +END:VAVAILABILITY +END:VCALENDAR +VCAL + )); + + // DTSTART is missing. + $this->assertIsNotValid(Reader::read( +<<<VCAL +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//id +BEGIN:VAVAILABILITY +UID:foo@test +DTSTAMP:20111005T133225Z +BEGIN:AVAILABLE +UID:foo@test +DTSTAMP:20111005T133225Z +END:AVAILABLE +END:VAVAILABILITY +END:VCALENDAR +VCAL + )); + + } + + function testRFCxxxSection3_1_available_dtend_duration() { + + // Only DTEND. + $this->assertIsValid(Reader::read($this->templateAvailable(array( + 'DTEND:21111005T133225Z' + )))); + + // Only DURATION. + $this->assertIsValid(Reader::read($this->templateAvailable(array( + 'DURATION:PT1H' + )))); + + // Both (not allowed). + $this->assertIsNotValid(Reader::read($this->templateAvailable(array( + 'DTEND:21111005T133225Z', + 'DURATION:PT1H' + )))); + } + + function testRFCxxxSection3_1_available_optional_once() { + + $properties = array( + 'CREATED:20111005T135125Z', + 'DESCRIPTION:Long bla bla', + 'LAST-MODIFIED:20111005T135325Z', + 'RECURRENCE-ID;RANGE=THISANDFUTURE:19980401T133000Z', + 'RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR', + 'SUMMARY:Bla bla' + ); + + // They are all present, only once. + $this->assertIsValid(Reader::read($this->templateAvailable($properties))); + + // We duplicate each one to see if it fails. + foreach ($properties as $property) { + $this->assertIsNotValid(Reader::read($this->templateAvailable(array( + $property, + $property + )))); + } + + } + function testRFCxxxSection3_2() { + + $this->assertEquals( + 'BUSY', + Reader::read($this->templateAvailable(array( + 'BUSYTYPE:BUSY' + ))) + ->VAVAILABILITY + ->AVAILABLE + ->BUSYTYPE + ->getValue() + ); + + $this->assertEquals( + 'BUSY-UNAVAILABLE', + Reader::read($this->templateAvailable(array( + 'BUSYTYPE:BUSY-UNAVAILABLE' + ))) + ->VAVAILABILITY + ->AVAILABLE + ->BUSYTYPE + ->getValue() + ); + + $this->assertEquals( + 'BUSY-TENTATIVE', + Reader::read($this->templateAvailable(array( + 'BUSYTYPE:BUSY-TENTATIVE' + ))) + ->VAVAILABILITY + ->AVAILABLE + ->BUSYTYPE + ->getValue() + ); + + } + + protected function assertIsValid(VObject\Document $document) { + + $this->assertEmpty($document->validate()); + + } + + protected function assertIsNotValid(VObject\Document $document) { + + $this->assertNotEmpty($document->validate()); + + } + + protected function template(array $properties) { + + return $this->_template( + <<<VCAL +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//id +BEGIN:VAVAILABILITY +UID:foo@test +DTSTAMP:20111005T133225Z +… +END:VAVAILABILITY +END:VCALENDAR +VCAL +, + $properties + ); + + } + + protected function templateAvailable(array $properties) { + + return $this->_template( + <<<VCAL +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//id +BEGIN:VAVAILABILITY +UID:foo@test +DTSTAMP:20111005T133225Z +BEGIN:AVAILABLE +UID:foo@test +DTSTAMP:20111005T133225Z +DTSTART:20111005T133225Z +… +END:AVAILABLE +END:VAVAILABILITY +END:VCALENDAR +VCAL +, + $properties + ); + + } + + protected function _template($template, array $properties) { + + return str_replace('…', implode("\r\n", $properties), $template); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VCalendarTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VCalendarTest.php new file mode 100644 index 0000000..f3ecfc2 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VCalendarTest.php @@ -0,0 +1,697 @@ +<?php + +namespace Sabre\VObject\Component; + +use DateTimeZone; +use Sabre\VObject; + +class VCalendarTest extends \PHPUnit_Framework_TestCase { + + /** + * @dataProvider expandData + */ + public function testExpand($input, $output, $timeZone = 'UTC', $start = '2011-12-01', $end = '2011-12-31') { + + $vcal = VObject\Reader::read($input); + + $timeZone = new DateTimeZone($timeZone); + + $vcal->expand( + new \DateTime($start), + new \DateTime($end), + $timeZone + ); + + // This will normalize the output + $output = VObject\Reader::read($output)->serialize(); + + $this->assertEquals($output, $vcal->serialize()); + + } + + public function expandData() { + + $tests = array(); + + // No data + $input = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +END:VCALENDAR +'; + + $output = $input; + $tests[] = array($input,$output); + + + // Simple events + $input = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +BEGIN:VEVENT +UID:bla +SUMMARY:InExpand +DTSTART;VALUE=DATE:20111202 +END:VEVENT +BEGIN:VEVENT +UID:bla2 +SUMMARY:NotInExpand +DTSTART;VALUE=DATE:20120101 +END:VEVENT +END:VCALENDAR +'; + + $output = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +BEGIN:VEVENT +UID:bla +SUMMARY:InExpand +DTSTART;VALUE=DATE:20111202 +END:VEVENT +END:VCALENDAR +'; + + $tests[] = array($input, $output); + + // Removing timezone info + $input = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +BEGIN:VTIMEZONE +TZID:Europe/Paris +END:VTIMEZONE +BEGIN:VEVENT +UID:bla4 +SUMMARY:RemoveTZ info +DTSTART;TZID=Europe/Paris:20111203T130102 +END:VEVENT +END:VCALENDAR +'; + + $output = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +BEGIN:VEVENT +UID:bla4 +SUMMARY:RemoveTZ info +DTSTART:20111203T120102Z +END:VEVENT +END:VCALENDAR +'; + + $tests[] = array($input, $output); + + // Recurrence rule + $input = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +BEGIN:VEVENT +UID:bla6 +SUMMARY:Testing RRule +DTSTART:20111125T120000Z +DTEND:20111125T130000Z +RRULE:FREQ=WEEKLY +END:VEVENT +END:VCALENDAR +'; + + $output = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +BEGIN:VEVENT +UID:bla6 +SUMMARY:Testing RRule +DTSTART:20111202T120000Z +DTEND:20111202T130000Z +RECURRENCE-ID:20111202T120000Z +END:VEVENT +BEGIN:VEVENT +UID:bla6 +SUMMARY:Testing RRule +DTSTART:20111209T120000Z +DTEND:20111209T130000Z +RECURRENCE-ID:20111209T120000Z +END:VEVENT +BEGIN:VEVENT +UID:bla6 +SUMMARY:Testing RRule +DTSTART:20111216T120000Z +DTEND:20111216T130000Z +RECURRENCE-ID:20111216T120000Z +END:VEVENT +BEGIN:VEVENT +UID:bla6 +SUMMARY:Testing RRule +DTSTART:20111223T120000Z +DTEND:20111223T130000Z +RECURRENCE-ID:20111223T120000Z +END:VEVENT +BEGIN:VEVENT +UID:bla6 +SUMMARY:Testing RRule +DTSTART:20111230T120000Z +DTEND:20111230T130000Z +RECURRENCE-ID:20111230T120000Z +END:VEVENT +END:VCALENDAR +'; + + $tests[] = array($input, $output); + + // Recurrence rule + override + $input = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +BEGIN:VEVENT +UID:bla6 +SUMMARY:Testing RRule2 +DTSTART:20111125T120000Z +DTEND:20111125T130000Z +RRULE:FREQ=WEEKLY +END:VEVENT +BEGIN:VEVENT +UID:bla6 +RECURRENCE-ID:20111209T120000Z +DTSTART:20111209T140000Z +DTEND:20111209T150000Z +SUMMARY:Override! +END:VEVENT +END:VCALENDAR +'; + + $output = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +BEGIN:VEVENT +UID:bla6 +SUMMARY:Testing RRule2 +DTSTART:20111202T120000Z +DTEND:20111202T130000Z +RECURRENCE-ID:20111202T120000Z +END:VEVENT +BEGIN:VEVENT +UID:bla6 +RECURRENCE-ID:20111209T120000Z +DTSTART:20111209T140000Z +DTEND:20111209T150000Z +SUMMARY:Override! +END:VEVENT +BEGIN:VEVENT +UID:bla6 +SUMMARY:Testing RRule2 +DTSTART:20111216T120000Z +DTEND:20111216T130000Z +RECURRENCE-ID:20111216T120000Z +END:VEVENT +BEGIN:VEVENT +UID:bla6 +SUMMARY:Testing RRule2 +DTSTART:20111223T120000Z +DTEND:20111223T130000Z +RECURRENCE-ID:20111223T120000Z +END:VEVENT +BEGIN:VEVENT +UID:bla6 +SUMMARY:Testing RRule2 +DTSTART:20111230T120000Z +DTEND:20111230T130000Z +RECURRENCE-ID:20111230T120000Z +END:VEVENT +END:VCALENDAR +'; + + $tests[] = array($input, $output); + + // Floating dates and times. + $input = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:bla1 +DTSTART:20141112T195000 +END:VEVENT +BEGIN:VEVENT +UID:bla2 +DTSTART;VALUE=DATE:20141112 +END:VEVENT +BEGIN:VEVENT +UID:bla3 +DTSTART;VALUE=DATE:20141112 +RRULE:FREQ=DAILY;COUNT=2 +END:VEVENT +END:VCALENDAR +ICS; + + $output = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:bla1 +DTSTART:20141112T225000Z +END:VEVENT +BEGIN:VEVENT +UID:bla2 +DTSTART;VALUE=DATE:20141112 +END:VEVENT +BEGIN:VEVENT +UID:bla3 +DTSTART;VALUE=DATE:20141112 +RECURRENCE-ID;VALUE=DATE:20141112 +END:VEVENT +BEGIN:VEVENT +UID:bla3 +DTSTART;VALUE=DATE:20141113 +RECURRENCE-ID;VALUE=DATE:20141113 +END:VEVENT +END:VCALENDAR +ICS; + + $tests[] = array($input, $output, 'America/Argentina/Buenos_Aires', '2014-01-01', '2015-01-01'); + + // Recurrence rule with no valid instances + $input = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +BEGIN:VEVENT +UID:bla6 +SUMMARY:Testing RRule3 +DTSTART:20111125T120000Z +DTEND:20111125T130000Z +RRULE:FREQ=WEEKLY;COUNT=1 +EXDATE:20111125T120000Z +END:VEVENT +END:VCALENDAR +'; + + $output = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +END:VCALENDAR +'; + + $tests[] = array($input, $output); + return $tests; + + } + + /** + * @expectedException LogicException + */ + public function testBrokenEventExpand() { + + $input = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +BEGIN:VEVENT +RRULE:FREQ=WEEKLY +DTSTART;VALUE=DATE:20111202 +END:VEVENT +END:VCALENDAR +'; + $vcal = VObject\Reader::read($input); + $vcal->expand( + new \DateTime('2011-12-01'), + new \DateTime('2011-12-31') + ); + + } + + function testGetDocumentType() { + + $vcard = new VCalendar(); + $vcard->VERSION = '2.0'; + $this->assertEquals(VCalendar::ICALENDAR20, $vcard->getDocumentType()); + + } + + function testValidateCorrect() { + + $input = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +PRODID:foo +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +DTSTAMP:20140122T233226Z +UID:foo +END:VEVENT +END:VCALENDAR +'; + + $vcal = VObject\Reader::read($input); + $this->assertEquals(array(), $vcal->validate(), 'Got an error'); + + } + + function testValidateNoVersion() { + + $input = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +PRODID:foo +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +END:VEVENT +END:VCALENDAR +'; + + $vcal = VObject\Reader::read($input); + $this->assertEquals(1, count($vcal->validate())); + + } + + function testValidateWrongVersion() { + + $input = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:3.0 +PRODID:foo +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +END:VEVENT +END:VCALENDAR +'; + + $vcal = VObject\Reader::read($input); + $this->assertEquals(1, count($vcal->validate())); + + } + + function testValidateNoProdId() { + + $input = 'BEGIN:VCALENDAR +CALSCALE:GREGORIAN +VERSION:2.0 +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +END:VEVENT +END:VCALENDAR +'; + + $vcal = VObject\Reader::read($input); + $this->assertEquals(1, count($vcal->validate())); + + } + + function testValidateDoubleCalScale() { + + $input = 'BEGIN:VCALENDAR +VERSION:2.0 +PRODID:foo +CALSCALE:GREGORIAN +CALSCALE:GREGORIAN +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +END:VEVENT +END:VCALENDAR +'; + + $vcal = VObject\Reader::read($input); + $this->assertEquals(1, count($vcal->validate())); + + } + + function testValidateDoubleMethod() { + + $input = 'BEGIN:VCALENDAR +VERSION:2.0 +PRODID:foo +METHOD:REQUEST +METHOD:REQUEST +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +END:VEVENT +END:VCALENDAR +'; + + $vcal = VObject\Reader::read($input); + $this->assertEquals(1, count($vcal->validate())); + + } + + function testValidateTwoMasterEvents() { + + $input = 'BEGIN:VCALENDAR +VERSION:2.0 +PRODID:foo +METHOD:REQUEST +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +END:VEVENT +END:VCALENDAR +'; + + $vcal = VObject\Reader::read($input); + $this->assertEquals(1, count($vcal->validate())); + + } + + function testValidateOneMasterEvent() { + + $input = 'BEGIN:VCALENDAR +VERSION:2.0 +PRODID:foo +METHOD:REQUEST +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +RECURRENCE-ID;VALUE=DATE:20111202 +END:VEVENT +END:VCALENDAR +'; + + $vcal = VObject\Reader::read($input); + $this->assertEquals(0, count($vcal->validate())); + + } + + function testGetBaseComponent() { + + $input = 'BEGIN:VCALENDAR +VERSION:2.0 +PRODID:foo +METHOD:REQUEST +BEGIN:VEVENT +SUMMARY:test +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +RECURRENCE-ID;VALUE=DATE:20111202 +END:VEVENT +END:VCALENDAR +'; + + $vcal = VObject\Reader::read($input); + + $result = $vcal->getBaseComponent(); + $this->assertEquals('test', $result->SUMMARY->getValue()); + + } + + function testGetBaseComponentNoResult() { + + $input = 'BEGIN:VCALENDAR +VERSION:2.0 +PRODID:foo +METHOD:REQUEST +BEGIN:VEVENT +SUMMARY:test +RECURRENCE-ID;VALUE=DATE:20111202 +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20111202 +UID:foo +DTSTAMP:20140122T234434Z +RECURRENCE-ID;VALUE=DATE:20111202 +END:VEVENT +END:VCALENDAR +'; + + $vcal = VObject\Reader::read($input); + + $result = $vcal->getBaseComponent(); + $this->assertNull($result); + + } + + function testNoComponents() { + + $input = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:vobject +END:VCALENDAR +ICS; + + $this->assertValidate( + $input, + 0, + 3, + "An iCalendar object must have at least 1 component." + ); + + } + + function testCalDAVNoComponents() { + + $input = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:vobject +BEGIN:VTIMEZONE +TZID:America/Toronto +END:VTIMEZONE +END:VCALENDAR +ICS; + + $this->assertValidate( + $input, + VCalendar::PROFILE_CALDAV, + 3, + "A calendar object on a CalDAV server must have at least 1 component (VTODO, VEVENT, VJOURNAL)." + ); + + } + + function testCalDAVMultiUID() { + + $input = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:vobject +BEGIN:VEVENT +UID:foo +DTSTAMP:20150109T184500Z +DTSTART:20150109T184500Z +END:VEVENT +BEGIN:VEVENT +UID:bar +DTSTAMP:20150109T184500Z +DTSTART:20150109T184500Z +END:VEVENT +END:VCALENDAR +ICS; + + $this->assertValidate( + $input, + VCalendar::PROFILE_CALDAV, + 3, + "A calendar object on a CalDAV server may only have components with the same UID." + ); + + } + + function testCalDAVMultiComponent() { + + $input = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:vobject +BEGIN:VEVENT +UID:foo +RECURRENCE-ID:20150109T185200Z +DTSTAMP:20150109T184500Z +DTSTART:20150109T184500Z +END:VEVENT +BEGIN:VTODO +UID:foo +DTSTAMP:20150109T184500Z +DTSTART:20150109T184500Z +END:VTODO +END:VCALENDAR +ICS; + + $this->assertValidate( + $input, + VCalendar::PROFILE_CALDAV, + 3, + "A calendar object on a CalDAV server may only have 1 type of component (VEVENT, VTODO or VJOURNAL)." + ); + + } + + function testCalDAVMETHOD() { + + $input = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:PUBLISH +PRODID:vobject +BEGIN:VEVENT +UID:foo +RECURRENCE-ID:20150109T185200Z +DTSTAMP:20150109T184500Z +DTSTART:20150109T184500Z +END:VEVENT +END:VCALENDAR +ICS; + + $this->assertValidate( + $input, + VCalendar::PROFILE_CALDAV, + 3, + "A calendar object on a CalDAV server MUST NOT have a METHOD property." + ); + + } + + function assertValidate($ics, $options, $expectedLevel, $expectedMessage = null) { + + $vcal = VObject\Reader::read($ics); + $result = $vcal->validate($options); + + $this->assertValidateResult($result, $expectedLevel, $expectedMessage); + + } + + function assertValidateResult($input, $expectedLevel, $expectedMessage = null) { + + $messages = array(); + foreach($input as $warning) { + $messages[] = $warning['message']; + } + + if ($expectedLevel === 0) { + $this->assertEquals(0, count($input), 'No validation messages were expected. We got: ' . implode(', ', $messages)); + } else { + $this->assertEquals(1, count($input), 'We expected exactly 1 validation message, We got: ' . implode(', ', $messages)); + + $this->assertEquals($expectedMessage, $input[0]['message']); + $this->assertEquals($expectedLevel, $input[0]['level']); + } + + } + + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VCardTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VCardTest.php new file mode 100644 index 0000000..d460ddc --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VCardTest.php @@ -0,0 +1,288 @@ +<?php + +namespace Sabre\VObject\Component; + +use Sabre\VObject; + +class VCardTest extends \PHPUnit_Framework_TestCase { + + /** + * @dataProvider validateData + */ + function testValidate($input, $expectedWarnings, $expectedRepairedOutput) { + + $vcard = VObject\Reader::read($input); + + $warnings = $vcard->validate(); + + $warnMsg = array(); + foreach($warnings as $warning) { + $warnMsg[] = $warning['message']; + } + + $this->assertEquals($expectedWarnings, $warnMsg); + + $vcard->validate(VObject\Component::REPAIR); + + $this->assertEquals( + $expectedRepairedOutput, + $vcard->serialize() + ); + + } + + public function validateData() { + + $tests = array(); + + // Correct + $tests[] = array( + "BEGIN:VCARD\r\nVERSION:4.0\r\nFN:John Doe\r\nUID:foo\r\nEND:VCARD\r\n", + array(), + "BEGIN:VCARD\r\nVERSION:4.0\r\nFN:John Doe\r\nUID:foo\r\nEND:VCARD\r\n", + ); + + // No VERSION + $tests[] = array( + "BEGIN:VCARD\r\nFN:John Doe\r\nUID:foo\r\nEND:VCARD\r\n", + array( + 'VERSION MUST appear exactly once in a VCARD component', + ), + "BEGIN:VCARD\r\nVERSION:3.0\r\nFN:John Doe\r\nUID:foo\r\nEND:VCARD\r\n", + ); + + // Unknown version + $tests[] = array( + "BEGIN:VCARD\r\nVERSION:2.2\r\nFN:John Doe\r\nUID:foo\r\nEND:VCARD\r\n", + array( + 'Only vcard version 4.0 (RFC6350), version 3.0 (RFC2426) or version 2.1 (icm-vcard-2.1) are supported.', + ), + "BEGIN:VCARD\r\nVERSION:2.1\r\nFN:John Doe\r\nUID:foo\r\nEND:VCARD\r\n", + ); + + // No FN + $tests[] = array( + "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nEND:VCARD\r\n", + array( + 'The FN property must appear in the VCARD component exactly 1 time', + ), + "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nEND:VCARD\r\n", + ); + // No FN, N fallback + $tests[] = array( + "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nN:Doe;John;;;;;\r\nEND:VCARD\r\n", + array( + 'The FN property must appear in the VCARD component exactly 1 time', + ), + "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nN:Doe;John;;;;;\r\nFN:John Doe\r\nEND:VCARD\r\n", + ); + // No FN, N fallback, no first name + $tests[] = array( + "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nN:Doe;;;;;;\r\nEND:VCARD\r\n", + array( + 'The FN property must appear in the VCARD component exactly 1 time', + ), + "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nN:Doe;;;;;;\r\nFN:Doe\r\nEND:VCARD\r\n", + ); + + // No FN, ORG fallback + $tests[] = array( + "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nORG:Acme Co.\r\nEND:VCARD\r\n", + array( + 'The FN property must appear in the VCARD component exactly 1 time', + ), + "BEGIN:VCARD\r\nVERSION:4.0\r\nUID:foo\r\nORG:Acme Co.\r\nFN:Acme Co.\r\nEND:VCARD\r\n", + ); + return $tests; + + } + + function testGetDocumentType() { + + $vcard = new VCard(array(), false); + $vcard->VERSION = '2.1'; + $this->assertEquals(VCard::VCARD21, $vcard->getDocumentType()); + + $vcard = new VCard(array(), false); + $vcard->VERSION = '3.0'; + $this->assertEquals(VCard::VCARD30, $vcard->getDocumentType()); + + $vcard = new VCard(array(), false); + $vcard->VERSION = '4.0'; + $this->assertEquals(VCard::VCARD40, $vcard->getDocumentType()); + + $vcard = new VCard(array(), false); + $this->assertEquals(VCard::UNKNOWN, $vcard->getDocumentType()); + } + + function testPreferredNoPref() { + + $vcard = <<<VCF +BEGIN:VCARD +VERSION:3.0 +EMAIL:1@example.org +EMAIL:2@example.org +END:VCARD +VCF; + + $vcard = VObject\Reader::read($vcard); + $this->assertEquals('1@example.org', $vcard->preferred('EMAIL')->getValue()); + + } + + function testPreferredWithPref() { + + $vcard = <<<VCF +BEGIN:VCARD +VERSION:3.0 +EMAIL:1@example.org +EMAIL;TYPE=PREF:2@example.org +END:VCARD +VCF; + + $vcard = VObject\Reader::read($vcard); + $this->assertEquals('2@example.org', $vcard->preferred('EMAIL')->getValue()); + + } + + function testPreferredWith40Pref() { + + $vcard = <<<VCF +BEGIN:VCARD +VERSION:4.0 +EMAIL:1@example.org +EMAIL;PREF=3:2@example.org +EMAIL;PREF=2:3@example.org +END:VCARD +VCF; + + $vcard = VObject\Reader::read($vcard); + $this->assertEquals('3@example.org', $vcard->preferred('EMAIL')->getValue()); + + } + + function testPreferredNotFound() { + + $vcard = <<<VCF +BEGIN:VCARD +VERSION:4.0 +END:VCARD +VCF; + + $vcard = VObject\Reader::read($vcard); + $this->assertNull($vcard->preferred('EMAIL')); + + } + + function testNoUIDCardDAV() { + + $vcard = <<<VCF +BEGIN:VCARD +VERSION:4.0 +FN:John Doe +END:VCARD +VCF; + $this->assertValidate( + $vcard, + VCARD::PROFILE_CARDDAV, + 3, + 'vCards on CardDAV servers MUST have a UID property.' + ); + + } + + function testNoUIDNoCardDAV() { + + $vcard = <<<VCF +BEGIN:VCARD +VERSION:4.0 +FN:John Doe +END:VCARD +VCF; + $this->assertValidate( + $vcard, + 0, + 2, + 'Adding a UID to a vCard property is recommended.' + ); + + } + function testNoUIDNoCardDAVRepair() { + + $vcard = <<<VCF +BEGIN:VCARD +VERSION:4.0 +FN:John Doe +END:VCARD +VCF; + $this->assertValidate( + $vcard, + VCARD::REPAIR, + 1, + 'Adding a UID to a vCard property is recommended.' + ); + + } + + function testVCard21CardDAV() { + + $vcard = <<<VCF +BEGIN:VCARD +VERSION:2.1 +FN:John Doe +UID:foo +END:VCARD +VCF; + $this->assertValidate( + $vcard, + VCARD::PROFILE_CARDDAV, + 3, + 'CardDAV servers are not allowed to accept vCard 2.1.' + ); + + } + + function testVCard21NoCardDAV() { + + $vcard = <<<VCF +BEGIN:VCARD +VERSION:2.1 +FN:John Doe +UID:foo +END:VCARD +VCF; + $this->assertValidate( + $vcard, + 0, + 0 + ); + + } + + function assertValidate($vcf, $options, $expectedLevel, $expectedMessage = null) { + + $vcal = VObject\Reader::read($vcf); + $result = $vcal->validate($options); + + $this->assertValidateResult($result, $expectedLevel, $expectedMessage); + + } + + function assertValidateResult($input, $expectedLevel, $expectedMessage = null) { + + $messages = array(); + foreach($input as $warning) { + $messages[] = $warning['message']; + } + + if ($expectedLevel === 0) { + $this->assertEquals(0, count($input), 'No validation messages were expected. We got: ' . implode(', ', $messages)); + } else { + $this->assertEquals(1, count($input), 'We expected exactly 1 validation message, We got: ' . implode(', ', $messages)); + + $this->assertEquals($expectedMessage, $input[0]['message']); + $this->assertEquals($expectedLevel, $input[0]['level']); + } + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VEventTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VEventTest.php new file mode 100644 index 0000000..b3eec3e --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VEventTest.php @@ -0,0 +1,90 @@ +<?php + +namespace Sabre\VObject\Component; + +use Sabre\VObject; + +class VEventTest extends \PHPUnit_Framework_TestCase { + + /** + * @dataProvider timeRangeTestData + */ + public function testInTimeRange(VEvent $vevent,$start,$end,$outcome) { + + $this->assertEquals($outcome, $vevent->isInTimeRange($start, $end)); + + } + + public function timeRangeTestData() { + + $tests = array(); + + $calendar = new VCalendar(); + + $vevent = $calendar->createComponent('VEVENT'); + $vevent->DTSTART = '20111223T120000Z'; + $tests[] = array($vevent, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true); + $tests[] = array($vevent, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false); + + $vevent2 = clone $vevent; + $vevent2->DTEND = '20111225T120000Z'; + $tests[] = array($vevent2, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true); + $tests[] = array($vevent2, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false); + + $vevent3 = clone $vevent; + $vevent3->DURATION = 'P1D'; + $tests[] = array($vevent3, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true); + $tests[] = array($vevent3, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false); + + $vevent4 = clone $vevent; + $vevent4->DTSTART = '20111225'; + $vevent4->DTSTART['VALUE'] = 'DATE'; + $tests[] = array($vevent4, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true); + $tests[] = array($vevent4, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false); + // Event with no end date should be treated as lasting the entire day. + $tests[] = array($vevent4, new \DateTime('2011-12-25 16:00:00'), new \DateTime('2011-12-25 17:00:00'), true); + // DTEND is non inclusive so all day events should not be returned on the next day. + $tests[] = array($vevent4, new \DateTime('2011-12-26 00:00:00'), new \DateTime('2011-12-26 17:00:00'), false); + // The timezone of timerange in question also needs to be considered. + $tests[] = array($vevent4, new \DateTime('2011-12-26 00:00:00', new \DateTimeZone('Europe/Berlin')), new \DateTime('2011-12-26 17:00:00', new \DateTimeZone('Europe/Berlin')), false); + + $vevent5 = clone $vevent; + $vevent5->DURATION = 'P1D'; + $vevent5->RRULE = 'FREQ=YEARLY'; + $tests[] = array($vevent5, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true); + $tests[] = array($vevent5, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false); + $tests[] = array($vevent5, new \DateTime('2013-12-01'), new \DateTime('2013-12-31'), true); + + $vevent6 = clone $vevent; + $vevent6->DTSTART = '20111225'; + $vevent6->DTSTART['VALUE'] = 'DATE'; + $vevent6->DTEND = '20111225'; + $vevent6->DTEND['VALUE'] = 'DATE'; + + $tests[] = array($vevent6, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true); + $tests[] = array($vevent6, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false); + + // Added this test to ensure that recurrence rules with no DTEND also + // get checked for the entire day. + $vevent7 = clone $vevent; + $vevent7->DTSTART = '20120101'; + $vevent7->DTSTART['VALUE'] = 'DATE'; + $vevent7->RRULE = 'FREQ=MONTHLY'; + $tests[] = array($vevent7, new \DateTime('2012-02-01 15:00:00'), new \DateTime('2012-02-02'), true); + // The timezone of timerange in question should also be considered. + + // Added this test to check recurring events that have no instances. + $vevent8 = clone $vevent; + $vevent8->DTSTART = '20130329T140000'; + $vevent8->DTEND = '20130329T153000'; + $vevent8->RRULE = array('FREQ' => 'WEEKLY', 'BYDAY' => array('FR'), 'UNTIL' => '20130412T115959Z'); + $vevent8->add('EXDATE', '20130405T140000'); + $vevent8->add('EXDATE', '20130329T140000'); + $tests[] = array($vevent8, new \DateTime('2013-03-01'), new \DateTime('2013-04-01'), false); + + return $tests; + + } + +} + diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VFreeBusyTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VFreeBusyTest.php new file mode 100644 index 0000000..46acc69 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VFreeBusyTest.php @@ -0,0 +1,66 @@ +<?php + +namespace Sabre\VObject\Component; + +use Sabre\VObject; +use Sabre\VObject\Reader; + +class VFreeBusyTest extends \PHPUnit_Framework_TestCase { + + function testIsFree() { + + $input = <<<BLA +BEGIN:VCALENDAR +BEGIN:VFREEBUSY +FREEBUSY;FBTYPE=FREE:20120912T000500Z/PT1H +FREEBUSY;FBTYPE=BUSY:20120912T010000Z/20120912T020000Z +FREEBUSY;FBTYPE=BUSY-TENTATIVE:20120912T020000Z/20120912T030000Z +FREEBUSY;FBTYPE=BUSY-UNAVAILABLE:20120912T030000Z/20120912T040000Z +FREEBUSY;FBTYPE=BUSY:20120912T050000Z/20120912T060000Z,20120912T080000Z/20120912T090000Z +FREEBUSY;FBTYPE=BUSY:20120912T100000Z/PT1H +END:VFREEBUSY +END:VCALENDAR +BLA; + + $obj = VObject\Reader::read($input); + $vfb = $obj->VFREEBUSY; + + $tz = new \DateTimeZone('UTC'); + + $this->assertFalse($vfb->isFree(new \DateTime('2012-09-12 01:15:00', $tz), new \DateTime('2012-09-12 01:45:00', $tz))); + $this->assertFalse($vfb->isFree(new \DateTime('2012-09-12 08:05:00', $tz), new \DateTime('2012-09-12 08:10:00', $tz))); + $this->assertFalse($vfb->isFree(new \DateTime('2012-09-12 10:15:00', $tz), new \DateTime('2012-09-12 10:45:00', $tz))); + + // Checking whether the end time is treated as non-inclusive + $this->assertTrue($vfb->isFree(new \DateTime('2012-09-12 09:00:00', $tz), new \DateTime('2012-09-12 09:15:00', $tz))); + $this->assertTrue($vfb->isFree(new \DateTime('2012-09-12 09:45:00', $tz), new \DateTime('2012-09-12 10:00:00', $tz))); + $this->assertTrue($vfb->isFree(new \DateTime('2012-09-12 11:00:00', $tz), new \DateTime('2012-09-12 12:00:00', $tz))); + + } + + public function testValidate() { + + $input = <<<HI +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:YoYo +BEGIN:VFREEBUSY +UID:some-random-id +DTSTAMP:20140402T180200Z +END:VFREEBUSY +END:VCALENDAR +HI; + + $obj = Reader::read($input); + + $warnings = $obj->validate(); + $messages = array(); + foreach($warnings as $warning) { + $messages[] = $warning['message']; + } + + $this->assertEquals(array(), $messages); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VJournalTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VJournalTest.php new file mode 100644 index 0000000..10fc49f --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VJournalTest.php @@ -0,0 +1,101 @@ +<?php + +namespace Sabre\VObject\Component; + +use Sabre\VObject\Component; +use Sabre\VObject\Reader; + +class VJournalTest extends \PHPUnit_Framework_TestCase { + + /** + * @dataProvider timeRangeTestData + */ + public function testInTimeRange(VJournal $vtodo,$start,$end,$outcome) { + + $this->assertEquals($outcome, $vtodo->isInTimeRange($start, $end)); + + } + + public function testValidate() { + + $input = <<<HI +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:YoYo +BEGIN:VJOURNAL +UID:12345678 +DTSTAMP:20140402T174100Z +END:VJOURNAL +END:VCALENDAR +HI; + + $obj = Reader::read($input); + + $warnings = $obj->validate(); + $messages = array(); + foreach($warnings as $warning) { + $messages[] = $warning['message']; + } + + $this->assertEquals(array(), $messages); + + } + + public function testValidateBroken() { + + $input = <<<HI +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:YoYo +BEGIN:VJOURNAL +UID:12345678 +DTSTAMP:20140402T174100Z +URL:http://example.org/ +URL:http://example.com/ +END:VJOURNAL +END:VCALENDAR +HI; + + $obj = Reader::read($input); + + $warnings = $obj->validate(); + $messages = array(); + foreach($warnings as $warning) { + $messages[] = $warning['message']; + } + + $this->assertEquals( + array("URL MUST NOT appear more than once in a VJOURNAL component"), + $messages + ); + + } + + public function timeRangeTestData() { + + $calendar = new VCalendar(); + + $tests = array(); + + $vjournal = $calendar->createComponent('VJOURNAL'); + $vjournal->DTSTART = '20111223T120000Z'; + $tests[] = array($vjournal, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true); + $tests[] = array($vjournal, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false); + + $vjournal2 = $calendar->createComponent('VJOURNAL'); + $vjournal2->DTSTART = '20111223'; + $vjournal2->DTSTART['VALUE'] = 'DATE'; + $tests[] = array($vjournal2, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true); + $tests[] = array($vjournal2, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false); + + $vjournal3 = $calendar->createComponent('VJOURNAL'); + $tests[] = array($vjournal3, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), false); + $tests[] = array($vjournal3, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false); + + return $tests; + } + + + +} + diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VTimeZoneTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VTimeZoneTest.php new file mode 100644 index 0000000..92d07f8 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VTimeZoneTest.php @@ -0,0 +1,57 @@ +<?php + +namespace Sabre\VObject\Component; + +use Sabre\VObject; +use Sabre\VObject\Reader; + +class VTimeZoneTest extends \PHPUnit_Framework_TestCase { + + function testValidate() { + + $input = <<<HI +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:YoYo +BEGIN:VTIMEZONE +TZID:America/Toronto +END:VTIMEZONE +END:VCALENDAR +HI; + + $obj = Reader::read($input); + + $warnings = $obj->validate(); + $messages = array(); + foreach($warnings as $warning) { + $messages[] = $warning['message']; + } + + $this->assertEquals(array(), $messages); + + } + + function testGetTimeZone() { + + $input = <<<HI +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:YoYo +BEGIN:VTIMEZONE +TZID:America/Toronto +END:VTIMEZONE +END:VCALENDAR +HI; + + $obj = Reader::read($input); + + $tz = new \DateTimeZone('America/Toronto'); + + $this->assertEquals( + $tz, + $obj->VTIMEZONE->getTimeZone() + ); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VTodoTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VTodoTest.php new file mode 100644 index 0000000..e536e7b --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Component/VTodoTest.php @@ -0,0 +1,180 @@ +<?php + +namespace Sabre\VObject\Component; + +use + Sabre\VObject\Component, + Sabre\VObject\Reader; + +class VTodoTest extends \PHPUnit_Framework_TestCase { + + /** + * @dataProvider timeRangeTestData + */ + public function testInTimeRange(VTodo $vtodo,$start,$end,$outcome) { + + $this->assertEquals($outcome, $vtodo->isInTimeRange($start, $end)); + + } + + public function timeRangeTestData() { + + $tests = array(); + + $calendar = new VCalendar(); + + $vtodo = $calendar->createComponent('VTODO'); + $vtodo->DTSTART = '20111223T120000Z'; + $tests[] = array($vtodo, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true); + $tests[] = array($vtodo, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false); + + $vtodo2 = clone $vtodo; + $vtodo2->DURATION = 'P1D'; + $tests[] = array($vtodo2, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true); + $tests[] = array($vtodo2, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false); + + $vtodo3 = clone $vtodo; + $vtodo3->DUE = '20111225'; + $tests[] = array($vtodo3, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true); + $tests[] = array($vtodo3, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false); + + $vtodo4 = $calendar->createComponent('VTODO'); + $vtodo4->DUE = '20111225'; + $tests[] = array($vtodo4, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true); + $tests[] = array($vtodo4, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false); + + $vtodo5 = $calendar->createComponent('VTODO'); + $vtodo5->COMPLETED = '20111225'; + $tests[] = array($vtodo5, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true); + $tests[] = array($vtodo5, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false); + + $vtodo6 = $calendar->createComponent('VTODO'); + $vtodo6->CREATED = '20111225'; + $tests[] = array($vtodo6, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true); + $tests[] = array($vtodo6, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false); + + $vtodo7 = $calendar->createComponent('VTODO'); + $vtodo7->CREATED = '20111225'; + $vtodo7->COMPLETED = '20111226'; + $tests[] = array($vtodo7, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true); + $tests[] = array($vtodo7, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), false); + + $vtodo7 = $calendar->createComponent('VTODO'); + $tests[] = array($vtodo7, new \DateTime('2011-01-01'), new \DateTime('2012-01-01'), true); + $tests[] = array($vtodo7, new \DateTime('2011-01-01'), new \DateTime('2011-11-01'), true); + + return $tests; + + } + + public function testValidate() { + + $input = <<<HI +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:YoYo +BEGIN:VTODO +UID:1234-21355-123156 +DTSTAMP:20140402T183400Z +END:VTODO +END:VCALENDAR +HI; + + $obj = Reader::read($input); + + $warnings = $obj->validate(); + $messages = array(); + foreach($warnings as $warning) { + $messages[] = $warning['message']; + } + + $this->assertEquals(array(), $messages); + + } + + public function testValidateInvalid() { + + $input = <<<HI +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:YoYo +BEGIN:VTODO +END:VTODO +END:VCALENDAR +HI; + + $obj = Reader::read($input); + + $warnings = $obj->validate(); + $messages = array(); + foreach($warnings as $warning) { + $messages[] = $warning['message']; + } + + $this->assertEquals(array( + "UID MUST appear exactly once in a VTODO component", + "DTSTAMP MUST appear exactly once in a VTODO component", + ), $messages); + + } + + public function testValidateDUEDTSTARTMisMatch() { + + $input = <<<HI +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:YoYo +BEGIN:VTODO +UID:FOO +DTSTART;VALUE=DATE-TIME:20140520T131600Z +DUE;VALUE=DATE:20140520 +DTSTAMP;VALUE=DATE-TIME:20140520T131600Z +END:VTODO +END:VCALENDAR +HI; + + $obj = Reader::read($input); + + $warnings = $obj->validate(); + $messages = array(); + foreach($warnings as $warning) { + $messages[] = $warning['message']; + } + + $this->assertEquals(array( + "The value type (DATE or DATE-TIME) must be identical for DUE and DTSTART", + ), $messages); + + } + + public function testValidateDUEbeforeDTSTART() { + + $input = <<<HI +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:YoYo +BEGIN:VTODO +UID:FOO +DTSTART;VALUE=DATE:20140520 +DUE;VALUE=DATE:20140518 +DTSTAMP;VALUE=DATE-TIME:20140520T131600Z +END:VTODO +END:VCALENDAR +HI; + + $obj = Reader::read($input); + + $warnings = $obj->validate(); + $messages = array(); + foreach($warnings as $warning) { + $messages[] = $warning['message']; + } + + $this->assertEquals(array( + "DUE must occur after DTSTART", + ), $messages); + + } + +} + diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ComponentTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ComponentTest.php new file mode 100644 index 0000000..3bbe4d8 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ComponentTest.php @@ -0,0 +1,528 @@ +<?php + +namespace Sabre\VObject; + +use + Sabre\VObject\Component\VCalendar, + Sabre\VObject\Component\VCard; + +class ComponentTest extends \PHPUnit_Framework_TestCase { + + function testIterate() { + + $comp = new VCalendar(array(), false); + + $sub = $comp->createComponent('VEVENT'); + $comp->add($sub); + + $sub = $comp->createComponent('VTODO'); + $comp->add($sub); + + $count = 0; + foreach($comp->children() as $key=>$subcomponent) { + + $count++; + $this->assertInstanceOf('Sabre\\VObject\\Component',$subcomponent); + + } + $this->assertEquals(2,$count); + $this->assertEquals(1,$key); + + } + + function testMagicGet() { + + $comp = new VCalendar(array(), false); + + $sub = $comp->createComponent('VEVENT'); + $comp->add($sub); + + $sub = $comp->createComponent('VTODO'); + $comp->add($sub); + + $event = $comp->vevent; + $this->assertInstanceOf('Sabre\\VObject\\Component', $event); + $this->assertEquals('VEVENT', $event->name); + + $this->assertInternalType('null', $comp->vjournal); + + } + + function testMagicGetGroups() { + + $comp = new VCard(); + + $sub = $comp->createProperty('GROUP1.EMAIL','1@1.com'); + $comp->add($sub); + + $sub = $comp->createProperty('GROUP2.EMAIL','2@2.com'); + $comp->add($sub); + + $sub = $comp->createProperty('EMAIL','3@3.com'); + $comp->add($sub); + + $emails = $comp->email; + $this->assertEquals(3, count($emails)); + + $email1 = $comp->{"group1.email"}; + $this->assertEquals('EMAIL', $email1[0]->name); + $this->assertEquals('GROUP1', $email1[0]->group); + + $email3 = $comp->{".email"}; + $this->assertEquals('EMAIL', $email3[0]->name); + $this->assertEquals(null, $email3[0]->group); + + } + + function testMagicIsset() { + + $comp = new VCalendar(); + + $sub = $comp->createComponent('VEVENT'); + $comp->add($sub); + + $sub = $comp->createComponent('VTODO'); + $comp->add($sub); + + $this->assertTrue(isset($comp->vevent)); + $this->assertTrue(isset($comp->vtodo)); + $this->assertFalse(isset($comp->vjournal)); + + } + + function testMagicSetScalar() { + + $comp = new VCalendar(); + $comp->myProp = 'myValue'; + + $this->assertInstanceOf('Sabre\\VObject\\Property',$comp->MYPROP); + $this->assertEquals('myValue',(string)$comp->MYPROP); + + + } + + function testMagicSetScalarTwice() { + + $comp = new VCalendar(array(), false); + $comp->myProp = 'myValue'; + $comp->myProp = 'myValue'; + + $this->assertEquals(1,count($comp->children())); + $this->assertInstanceOf('Sabre\\VObject\\Property',$comp->MYPROP); + $this->assertEquals('myValue',(string)$comp->MYPROP); + + } + + function testMagicSetArray() { + + $comp = new VCalendar(); + $comp->ORG = array('Acme Inc', 'Section 9'); + + $this->assertInstanceOf('Sabre\\VObject\\Property',$comp->ORG); + $this->assertEquals(array('Acme Inc', 'Section 9'),$comp->ORG->getParts()); + + } + + function testMagicSetComponent() { + + $comp = new VCalendar(); + + // Note that 'myProp' is ignored here. + $comp->myProp = $comp->createComponent('VEVENT'); + + $this->assertEquals(1, count($comp)); + + $this->assertEquals('VEVENT',$comp->VEVENT->name); + + } + + function testMagicSetTwice() { + + $comp = new VCalendar(array(), false); + + $comp->VEVENT = $comp->createComponent('VEVENT'); + $comp->VEVENT = $comp->createComponent('VEVENT'); + + $this->assertEquals(1, count($comp->children())); + + $this->assertEquals('VEVENT',$comp->VEVENT->name); + + } + + function testArrayAccessGet() { + + $comp = new VCalendar(array(), false); + + $event = $comp->createComponent('VEVENT'); + $event->summary = 'Event 1'; + + $comp->add($event); + + $event2 = clone $event; + $event2->summary = 'Event 2'; + + $comp->add($event2); + + $this->assertEquals(2,count($comp->children())); + $this->assertTrue($comp->vevent[1] instanceof Component); + $this->assertEquals('Event 2', (string)$comp->vevent[1]->summary); + + } + + function testArrayAccessExists() { + + $comp = new VCalendar(); + + $event = $comp->createComponent('VEVENT'); + $event->summary = 'Event 1'; + + $comp->add($event); + + $event2 = clone $event; + $event2->summary = 'Event 2'; + + $comp->add($event2); + + $this->assertTrue(isset($comp->vevent[0])); + $this->assertTrue(isset($comp->vevent[1])); + + } + + /** + * @expectedException LogicException + */ + function testArrayAccessSet() { + + $comp = new VCalendar(); + $comp['hey'] = 'hi there'; + + } + /** + * @expectedException LogicException + */ + function testArrayAccessUnset() { + + $comp = new VCalendar(); + unset($comp[0]); + + } + + function testAddScalar() { + + $comp = new VCalendar(array(), false); + + $comp->add('myprop','value'); + + $this->assertEquals(1, count($comp->children())); + + $bla = $comp->children[0]; + + $this->assertTrue($bla instanceof Property); + $this->assertEquals('MYPROP',$bla->name); + $this->assertEquals('value',(string)$bla); + + } + + function testAddScalarParams() { + + $comp = new VCalendar(array(), false); + + $comp->add('myprop','value',array('param1'=>'value1')); + + $this->assertEquals(1, count($comp->children())); + + $bla = $comp->children[0]; + + $this->assertInstanceOf('Sabre\\VObject\\Property', $bla); + $this->assertEquals('MYPROP',$bla->name); + $this->assertEquals('value', (string)$bla); + + $this->assertEquals(1, count($bla->parameters())); + + $this->assertEquals('PARAM1',$bla->parameters['PARAM1']->name); + $this->assertEquals('value1',$bla->parameters['PARAM1']->getValue()); + + } + + + function testAddComponent() { + + $comp = new VCalendar(array(), false); + + $comp->add($comp->createComponent('VEVENT')); + + $this->assertEquals(1, count($comp->children())); + + $this->assertEquals('VEVENT',$comp->VEVENT->name); + + } + + function testAddComponentTwice() { + + $comp = new VCalendar(array(), false); + + $comp->add($comp->createComponent('VEVENT')); + $comp->add($comp->createComponent('VEVENT')); + + $this->assertEquals(2, count($comp->children())); + + $this->assertEquals('VEVENT',$comp->VEVENT->name); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testAddArgFail() { + + $comp = new VCalendar(); + $comp->add($comp->createComponent('VEVENT'),'hello'); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testAddArgFail2() { + + $comp = new VCalendar(); + $comp->add(array()); + + } + + function testMagicUnset() { + + $comp = new VCalendar(array(), false); + $comp->add($comp->createComponent('VEVENT')); + + unset($comp->vevent); + + $this->assertEquals(0, count($comp->children())); + + } + + + function testCount() { + + $comp = new VCalendar(); + $this->assertEquals(1,$comp->count()); + + } + + function testChildren() { + + $comp = new VCalendar(array(), false); + + // Note that 'myProp' is ignored here. + $comp->add($comp->createComponent('VEVENT')); + $comp->add($comp->createComponent('VTODO')); + + $r = $comp->children(); + $this->assertInternalType('array', $r); + $this->assertEquals(2,count($r)); + } + + function testGetComponents() { + + $comp = new VCalendar(); + + $comp->add($comp->createProperty('FOO','BAR')); + $comp->add($comp->createComponent('VTODO')); + + $r = $comp->getComponents(); + $this->assertInternalType('array', $r); + $this->assertEquals(1, count($r)); + $this->assertEquals('VTODO', $r[0]->name); + } + + function testSerialize() { + + $comp = new VCalendar(array(), false); + $this->assertEquals("BEGIN:VCALENDAR\r\nEND:VCALENDAR\r\n", $comp->serialize()); + + } + + function testSerializeChildren() { + + $comp = new VCalendar(array(), false); + $event = $comp->add($comp->createComponent('VEVENT')); + unset($event->DTSTAMP, $event->UID); + $comp->add($comp->createComponent('VTODO')); + + $str = $comp->serialize(); + + $this->assertEquals("BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nEND:VEVENT\r\nBEGIN:VTODO\r\nEND:VTODO\r\nEND:VCALENDAR\r\n", $str); + + } + + function testSerializeOrderCompAndProp() { + + $comp = new VCalendar(array(), false); + $comp->add($event = $comp->createComponent('VEVENT')); + $comp->add('PROP1','BLABLA'); + $comp->add('VERSION','2.0'); + $comp->add($comp->createComponent('VTIMEZONE')); + + unset($event->DTSTAMP, $event->UID); + $str = $comp->serialize(); + + $this->assertEquals("BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPROP1:BLABLA\r\nBEGIN:VTIMEZONE\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", $str); + + } + + function testAnotherSerializeOrderProp() { + + $prop4s=array('1', '2', '3', '4', '5', '6', '7', '8', '9', '10'); + + $comp = new VCard(array(), false); + + $comp->__set('SOMEPROP','FOO'); + $comp->__set('ANOTHERPROP','FOO'); + $comp->__set('THIRDPROP','FOO'); + foreach ($prop4s as $prop4) { + $comp->add('PROP4', 'FOO '.$prop4); + } + $comp->__set('PROPNUMBERFIVE', 'FOO'); + $comp->__set('PROPNUMBERSIX', 'FOO'); + $comp->__set('PROPNUMBERSEVEN', 'FOO'); + $comp->__set('PROPNUMBEREIGHT', 'FOO'); + $comp->__set('PROPNUMBERNINE', 'FOO'); + $comp->__set('PROPNUMBERTEN', 'FOO'); + $comp->__set('VERSION','2.0'); + $comp->__set('UID', 'FOO'); + + $str = $comp->serialize(); + + $this->assertEquals("BEGIN:VCARD\r\nVERSION:2.0\r\nSOMEPROP:FOO\r\nANOTHERPROP:FOO\r\nTHIRDPROP:FOO\r\nPROP4:FOO 1\r\nPROP4:FOO 2\r\nPROP4:FOO 3\r\nPROP4:FOO 4\r\nPROP4:FOO 5\r\nPROP4:FOO 6\r\nPROP4:FOO 7\r\nPROP4:FOO 8\r\nPROP4:FOO 9\r\nPROP4:FOO 10\r\nPROPNUMBERFIVE:FOO\r\nPROPNUMBERSIX:FOO\r\nPROPNUMBERSEVEN:FOO\r\nPROPNUMBEREIGHT:FOO\r\nPROPNUMBERNINE:FOO\r\nPROPNUMBERTEN:FOO\r\nUID:FOO\r\nEND:VCARD\r\n", $str); + + } + + function testInstantiateWithChildren() { + + $comp = new VCard(array( + 'ORG' => array('Acme Inc.', 'Section 9'), + 'FN' => 'Finn The Human', + )); + + $this->assertEquals(array('Acme Inc.', 'Section 9'), $comp->ORG->getParts()); + $this->assertEquals('Finn The Human', $comp->FN->getValue()); + + } + + function testInstantiateSubComponent() { + + $comp = new VCalendar(); + $event = $comp->createComponent('VEVENT', array( + $comp->createProperty('UID', '12345'), + )); + $comp->add($event); + + $this->assertEquals('12345', $comp->VEVENT->UID->getValue()); + + } + + function testRemoveByName() { + + $comp = new VCalendar(array(), false); + $comp->add('prop1','val1'); + $comp->add('prop2','val2'); + $comp->add('prop2','val2'); + + $comp->remove('prop2'); + $this->assertFalse(isset($comp->prop2)); + $this->assertTrue(isset($comp->prop1)); + + } + + function testRemoveByObj() { + + $comp = new VCalendar(array(), false); + $comp->add('prop1','val1'); + $prop = $comp->add('prop2','val2'); + + $comp->remove($prop); + $this->assertFalse(isset($comp->prop2)); + $this->assertTrue(isset($comp->prop1)); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testRemoveNotFound() { + + $comp = new VCalendar(array(), false); + $prop = $comp->createProperty('A','B'); + $comp->remove($prop); + + } + + /** + * @dataProvider ruleData + */ + function testValidateRules($componentList, $errorCount) { + + $vcard = new Component\VCard(); + + $component = new FakeComponent($vcard,'Hi', array(), $defaults = false ); + foreach($componentList as $v) { + $component->add($v,'Hello.'); + } + + $this->assertEquals($errorCount, count($component->validate())); + + } + + function testValidateRepair() { + + $vcard = new Component\VCard(); + + $component = new FakeComponent($vcard,'Hi', array(), $defaults = false ); + $component->validate(Component::REPAIR); + $this->assertEquals('yow', $component->BAR->getValue()); + + } + + function ruleData() { + + return array( + + array(array(), 2), + array(array('FOO'), 3), + array(array('BAR'), 1), + array(array('BAZ'), 1), + array(array('BAR','BAZ'), 0), + array(array('BAR','BAZ','ZIM',), 0), + array(array('BAR','BAZ','ZIM','GIR'), 0), + array(array('BAR','BAZ','ZIM','GIR','GIR'), 1), + + ); + + } + +} + +class FakeComponent extends Component { + + public function getValidationRules() { + + return array( + 'FOO' => '0', + 'BAR' => '1', + 'BAZ' => '+', + 'ZIM' => '*', + 'GIR' => '?', + ); + + } + + public function getDefaults() { + + return array( + 'BAR' => 'yow', + ); + + } + +} + diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/DateTimeParserTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/DateTimeParserTest.php new file mode 100644 index 0000000..4b90d87 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/DateTimeParserTest.php @@ -0,0 +1,417 @@ +<?php + +namespace Sabre\VObject; + +use DateTime; +use DateTimeZone; +use DateInterval; + +class DateTimeParserTest extends \PHPUnit_Framework_TestCase { + + function testParseICalendarDuration() { + + $this->assertEquals('+1 weeks', DateTimeParser::parseDuration('P1W',true)); + $this->assertEquals('+5 days', DateTimeParser::parseDuration('P5D',true)); + $this->assertEquals('+5 days 3 hours 50 minutes 12 seconds', DateTimeParser::parseDuration('P5DT3H50M12S',true)); + $this->assertEquals('-1 weeks 50 minutes', DateTimeParser::parseDuration('-P1WT50M',true)); + $this->assertEquals('+50 days 3 hours 2 seconds', DateTimeParser::parseDuration('+P50DT3H2S',true)); + $this->assertEquals('+0 seconds', DateTimeParser::parseDuration('+PT0S',true)); + $this->assertEquals(new DateInterval('PT0S'), DateTimeParser::parseDuration('PT0S')); + + } + + function testParseICalendarDurationDateInterval() { + + $expected = new DateInterval('P7D'); + $this->assertEquals($expected, DateTimeParser::parseDuration('P1W')); + $this->assertEquals($expected, DateTimeParser::parse('P1W')); + + $expected = new DateInterval('PT3M'); + $expected->invert = true; + $this->assertEquals($expected, DateTimeParser::parseDuration('-PT3M')); + + } + + /** + * @expectedException LogicException + */ + function testParseICalendarDurationFail() { + + DateTimeParser::parseDuration('P1X',true); + + } + + function testParseICalendarDateTime() { + + $dateTime = DateTimeParser::parseDateTime('20100316T141405'); + + $compare = new DateTime('2010-03-16 14:14:05',new DateTimeZone('UTC')); + + $this->assertEquals($compare, $dateTime); + + } + + /** + * @depends testParseICalendarDateTime + * @expectedException LogicException + */ + function testParseICalendarDateTimeBadFormat() { + + $dateTime = DateTimeParser::parseDateTime('20100316T141405 '); + + } + + /** + * @depends testParseICalendarDateTime + */ + function testParseICalendarDateTimeUTC() { + + $dateTime = DateTimeParser::parseDateTime('20100316T141405Z'); + + $compare = new DateTime('2010-03-16 14:14:05',new DateTimeZone('UTC')); + $this->assertEquals($compare, $dateTime); + + } + + /** + * @depends testParseICalendarDateTime + */ + function testParseICalendarDateTimeUTC2() { + + $dateTime = DateTimeParser::parseDateTime('20101211T160000Z'); + + $compare = new DateTime('2010-12-11 16:00:00',new DateTimeZone('UTC')); + $this->assertEquals($compare, $dateTime); + + } + + /** + * @depends testParseICalendarDateTime + */ + function testParseICalendarDateTimeCustomTimeZone() { + + $dateTime = DateTimeParser::parseDateTime('20100316T141405', new DateTimeZone('Europe/Amsterdam')); + + $compare = new DateTime('2010-03-16 14:14:05',new DateTimeZone('Europe/Amsterdam')); + $this->assertEquals($compare, $dateTime); + + } + + function testParseICalendarDate() { + + $dateTime = DateTimeParser::parseDate('20100316'); + + $expected = new DateTime('2010-03-16 00:00:00',new DateTimeZone('UTC')); + + $this->assertEquals($expected, $dateTime); + + $dateTime = DateTimeParser::parse('20100316'); + $this->assertEquals($expected, $dateTime); + + } + + /** + * TCheck if a date with year > 4000 will not throw an exception. iOS seems to use 45001231 in yearly recurring events + */ + function testParseICalendarDateGreaterThan4000() { + + $dateTime = DateTimeParser::parseDate('45001231'); + + $expected = new DateTime('4500-12-31 00:00:00',new DateTimeZone('UTC')); + + $this->assertEquals($expected, $dateTime); + + $dateTime = DateTimeParser::parse('45001231'); + $this->assertEquals($expected, $dateTime); + + } + + /** + * Check if a datetime with year > 4000 will not throw an exception. iOS seems to use 45001231T235959 in yearly recurring events + */ + function testParseICalendarDateTimeGreaterThan4000() { + + $dateTime = DateTimeParser::parseDateTime('45001231T235959'); + + $expected = new DateTime('4500-12-31 23:59:59',new DateTimeZone('UTC')); + + $this->assertEquals($expected, $dateTime); + + $dateTime = DateTimeParser::parse('45001231T235959'); + $this->assertEquals($expected, $dateTime); + + } + + /** + * @depends testParseICalendarDate + * @expectedException LogicException + */ + function testParseICalendarDateBadFormat() { + + $dateTime = DateTimeParser::parseDate('20100316T141405'); + + } + + /** + * @dataProvider vcardDates + */ + function testVCardDate($input, $output) { + + $this->assertEquals( + $output, + DateTimeParser::parseVCardDateTime($input) + ); + + } + + /** + * @dataProvider vcardDates + * @expectedException \InvalidArgumentException + */ + function testBadVCardDate() { + + DateTimeParser::parseVCardDateTime('1985---01'); + + } + + /** + * @dataProvider vcardDates + * @expectedException \InvalidArgumentException + */ + function testBadVCardTime() { + + DateTimeParser::parseVCardTime('23:12:166'); + + } + + function vcardDates() { + + return array( + array( + "19961022T140000", + array( + "year" => 1996, + "month" => 10, + "date" => 22, + "hour" => 14, + "minute" => 00, + "second" => 00, + "timezone" => null + ), + ), + array( + "--1022T1400", + array( + "year" => null, + "month" => 10, + "date" => 22, + "hour" => 14, + "minute" => 00, + "second" => null, + "timezone" => null + ), + ), + array( + "---22T14", + array( + "year" => null, + "month" => null, + "date" => 22, + "hour" => 14, + "minute" => null, + "second" => null, + "timezone" => null + ), + ), + array( + "19850412", + array( + "year" => 1985, + "month" => 4, + "date" => 12, + "hour" => null, + "minute" => null, + "second" => null, + "timezone" => null + ), + ), + array( + "1985-04", + array( + "year" => 1985, + "month" => 04, + "date" => null, + "hour" => null, + "minute" => null, + "second" => null, + "timezone" => null + ), + ), + array( + "1985", + array( + "year" => 1985, + "month" => null, + "date" => null, + "hour" => null, + "minute" => null, + "second" => null, + "timezone" => null + ), + ), + array( + "--0412", + array( + "year" => null, + "month" => 4, + "date" => 12, + "hour" => null, + "minute" => null, + "second" => null, + "timezone" => null + ), + ), + array( + "---12", + array( + "year" => null, + "month" => null, + "date" => 12, + "hour" => null, + "minute" => null, + "second" => null, + "timezone" => null + ), + ), + array( + "T102200", + array( + "year" => null, + "month" => null, + "date" => null, + "hour" => 10, + "minute" => 22, + "second" => 0, + "timezone" => null + ), + ), + array( + "T1022", + array( + "year" => null, + "month" => null, + "date" => null, + "hour" => 10, + "minute" => 22, + "second" => null, + "timezone" => null + ), + ), + array( + "T10", + array( + "year" => null, + "month" => null, + "date" => null, + "hour" => 10, + "minute" => null, + "second" => null, + "timezone" => null + ), + ), + array( + "T-2200", + array( + "year" => null, + "month" => null, + "date" => null, + "hour" => null, + "minute" => 22, + "second" => 00, + "timezone" => null + ), + ), + array( + "T--00", + array( + "year" => null, + "month" => null, + "date" => null, + "hour" => null, + "minute" => null, + "second" => 00, + "timezone" => null + ), + ), + array( + "T102200Z", + array( + "year" => null, + "month" => null, + "date" => null, + "hour" => 10, + "minute" => 22, + "second" => 00, + "timezone" => 'Z' + ), + ), + array( + "T102200-0800", + array( + "year" => null, + "month" => null, + "date" => null, + "hour" => 10, + "minute" => 22, + "second" => 00, + "timezone" => '-0800' + ), + ), + + // extended format + array( + "2012-11-29T15:10:53Z", + array( + "year" => 2012, + "month" => 11, + "date" => 29, + "hour" => 15, + "minute" => 10, + "second" => 53, + "timezone" => 'Z' + ), + ), + + // with milliseconds + array( + "20121129T151053.123Z", + array( + "year" => 2012, + "month" => 11, + "date" => 29, + "hour" => 15, + "minute" => 10, + "second" => 53, + "timezone" => 'Z' + ), + ), + + // extended format with milliseconds + array( + "2012-11-29T15:10:53.123Z", + array( + "year" => 2012, + "month" => 11, + "date" => 29, + "hour" => 15, + "minute" => 10, + "second" => 53, + "timezone" => 'Z' + ), + ), + + ); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/DocumentTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/DocumentTest.php new file mode 100644 index 0000000..d019b39 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/DocumentTest.php @@ -0,0 +1,70 @@ +<?php + +namespace Sabre\VObject; + +class DocumentTest extends \PHPUnit_Framework_TestCase { + + function testGetDocumentType() { + + $doc = new MockDocument(); + $this->assertEquals(Document::UNKNOWN, $doc->getDocumentType()); + + } + + function testConstruct() { + + $doc = new MockDocument('VLIST'); + $this->assertEquals('VLIST', $doc->name); + + } + + function testCreateComponent() { + + $vcal = new Component\VCalendar(array(), false); + + $event = $vcal->createComponent('VEVENT'); + + $this->assertInstanceOf('Sabre\VObject\Component\VEvent', $event); + $vcal->add($event); + + $prop = $vcal->createProperty('X-PROP','1234256',array('X-PARAM' => '3')); + $this->assertInstanceOf('Sabre\VObject\Property', $prop); + + $event->add($prop); + + unset( + $event->DTSTAMP, + $event->UID + ); + + $out = $vcal->serialize(); + $this->assertEquals("BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nX-PROP;X-PARAM=3:1234256\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", $out); + + } + + function testCreate() { + + $vcal = new Component\VCalendar(array(), false); + + $event = $vcal->create('VEVENT'); + $this->assertInstanceOf('Sabre\VObject\Component\VEvent', $event); + + $event = $vcal->create('CALSCALE'); + $this->assertInstanceOf('Sabre\VObject\Property\Text', $event); + + } + + function testGetClassNameForPropertyValue() { + + $vcal = new Component\VCalendar(array(), false); + $this->assertEquals('Sabre\\VObject\\Property\\Text', $vcal->getClassNameForPropertyValue('TEXT')); + $this->assertNull($vcal->getClassNameForPropertyValue('FOO')); + + } + +} + + +class MockDocument extends Document { + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ElementListTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ElementListTest.php new file mode 100644 index 0000000..48cea36 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ElementListTest.php @@ -0,0 +1,33 @@ +<?php + +namespace Sabre\VObject; + +class ElementListTest extends \PHPUnit_Framework_TestCase { + + function testIterate() { + + $cal = new Component\VCalendar(); + $sub = $cal->createComponent('VEVENT'); + + $elems = array( + $sub, + clone $sub, + clone $sub + ); + + $elemList = new ElementList($elems); + + $count = 0; + foreach($elemList as $key=>$subcomponent) { + + $count++; + $this->assertInstanceOf('Sabre\\VObject\\Component',$subcomponent); + + } + $this->assertEquals(3,$count); + $this->assertEquals(2,$key); + + } + + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/EmClientTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/EmClientTest.php new file mode 100644 index 0000000..69d410f --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/EmClientTest.php @@ -0,0 +1,55 @@ +<?php + +namespace Sabre\VObject; + +class EmClientTest extends \PHPUnit_Framework_TestCase { + + function testParseTz() { + + $str = 'BEGIN:VCALENDAR +X-WR-CALNAME:Blackhawks Schedule 2011-12 +X-APPLE-CALENDAR-COLOR:#E51717 +X-WR-TIMEZONE:America/Chicago +CALSCALE:GREGORIAN +PRODID:-//eM Client/4.0.13961.0 +VERSION:2.0 +BEGIN:VTIMEZONE +TZID:America/Chicago +BEGIN:DAYLIGHT +TZOFFSETFROM:-0600 +RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3 +DTSTART:20070311T020000 +TZNAME:CDT +TZOFFSETTO:-0500 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:-0500 +RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11 +DTSTART:20071104T020000 +TZNAME:CST +TZOFFSETTO:-0600 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +CREATED:20110624T181236Z +UID:be3bbfff-96e8-4c66-9908-ab791a62231d +DTEND;TZID="America/Chicago":20111008T223000 +TRANSP:OPAQUE +SUMMARY:Stars @ Blackhawks (Home Opener) +DTSTART;TZID="America/Chicago":20111008T193000 +DTSTAMP:20120330T013232Z +SEQUENCE:2 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +LAST-MODIFIED:20120330T013237Z +CLASS:PUBLIC +END:VEVENT +END:VCALENDAR'; + + $vObject = Reader::read($str); + $dt = $vObject->VEVENT->DTSTART->getDateTime(); + $this->assertEquals(new \DateTime('2011-10-08 19:30:00', new \DateTimeZone('America/Chicago')), $dt); + + } + +} + diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/EmptyParameterTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/EmptyParameterTest.php new file mode 100644 index 0000000..0dc1d69 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/EmptyParameterTest.php @@ -0,0 +1,69 @@ +<?php + +namespace Sabre\VObject; + +class IssueEmptyParameterTest extends \PHPUnit_Framework_TestCase { + + function testRead() { + + $input = <<<VCF +BEGIN:VCARD +VERSION:2.1 +N:Doe;Jon;;; +FN:Jon Doe +EMAIL;X-INTERN:foo@example.org +UID:foo +END:VCARD +VCF; + + $vcard = Reader::read($input); + + $this->assertInstanceOf('Sabre\\VObject\\Component\\VCard', $vcard); + $vcard = $vcard->convert(\Sabre\VObject\Document::VCARD30); + $vcard = $vcard->serialize(); + + $converted = Reader::read($vcard); + $converted->validate(); + + $this->assertTrue(isset($converted->EMAIL['X-INTERN'])); + + $version = Version::VERSION; + + $expected = <<<VCF +BEGIN:VCARD +VERSION:3.0 +PRODID:-//Sabre//Sabre VObject $version//EN +N:Doe;Jon;;; +FN:Jon Doe +EMAIL;X-INTERN=:foo@example.org +UID:foo +END:VCARD + +VCF; + + $this->assertEquals($expected, str_replace("\r","", $vcard)); + + } + + function testVCard21Parameter() { + + $vcard = new Component\VCard(array(), false); + $vcard->VERSION = '2.1'; + $vcard->PHOTO = 'random_stuff'; + $vcard->PHOTO->add(null,'BASE64'); + $vcard->UID = 'foo-bar'; + + $result = $vcard->serialize(); + $expected = array( + "BEGIN:VCARD", + "VERSION:2.1", + "PHOTO;BASE64:" . base64_encode('random_stuff'), + "UID:foo-bar", + "END:VCARD", + "", + ); + + $this->assertEquals(implode("\r\n", $expected), $result); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/EmptyValueIssueTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/EmptyValueIssueTest.php new file mode 100644 index 0000000..1f993ea --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/EmptyValueIssueTest.php @@ -0,0 +1,31 @@ +<?php + +namespace Sabre\VObject; + +/** + * This test is written for Issue 68: + * + * https://github.com/fruux/sabre-vobject/issues/68 + */ +class EmptyValueIssueTest extends \PHPUnit_Framework_TestCase { + + function testDecodeValue() { + + $input = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +DESCRIPTION:This is a descpription\\nwith a linebreak and a \\; \\, and : +END:VEVENT +END:VCALENDAR +ICS; + + $vobj = Reader::read($input); + + // Before this bug was fixed, getValue() would return nothing. + $this->assertEquals("This is a descpription\nwith a linebreak and a ; , and :", $vobj->VEVENT->DESCRIPTION->getValue()); + + } + +} + diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/FreeBusyGeneratorTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/FreeBusyGeneratorTest.php new file mode 100644 index 0000000..d01ef3a --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/FreeBusyGeneratorTest.php @@ -0,0 +1,394 @@ +<?php + +namespace Sabre\VObject; + +class FreeBusyGeneratorTest extends \PHPUnit_Framework_TestCase { + + function getInput() { + + $tests = array(); + + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar +DTSTART:20110101T120000Z +DTEND:20110101T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + $tests[] = array( + $blob, + "20110101T120000Z/20110101T130000Z" + ); + + // opaque, shows up + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar2 +TRANSP:OPAQUE +DTSTART:20110101T130000Z +DTEND:20110101T140000Z +END:VEVENT +END:VCALENDAR +ICS; + + $tests[] = array( + $blob, + "20110101T130000Z/20110101T140000Z" + ); + + // transparent, hidden + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar3 +TRANSP:TRANSPARENT +DTSTART:20110101T140000Z +DTEND:20110101T150000Z +END:VEVENT +END:VCALENDAR +ICS; + + $tests[] = array( + $blob, + null, + ); + + // cancelled, hidden + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar4 +STATUS:CANCELLED +DTSTART:20110101T160000Z +DTEND:20110101T170000Z +END:VEVENT +END:VCALENDAR +ICS; + + $tests[] = array( + $blob, + null, + ); + + // tentative, shows up + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar5 +STATUS:TENTATIVE +DTSTART:20110101T180000Z +DTEND:20110101T190000Z +END:VEVENT +END:VCALENDAR +ICS; + + $tests[] = array( + $blob, + '20110101T180000Z/20110101T190000Z', + ); + + // outside of time-range, hidden + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar6 +DTSTART:20110101T090000Z +DTEND:20110101T100000Z +END:VEVENT +END:VCALENDAR +ICS; + + $tests[] = array( + $blob, + null, + ); + + // outside of time-range, hidden + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar7 +DTSTART:20110104T090000Z +DTEND:20110104T100000Z +END:VEVENT +END:VCALENDAR +ICS; + + $tests[] = array( + $blob, + null, + ); + + // using duration, shows up + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar8 +DTSTART:20110101T190000Z +DURATION:PT1H +END:VEVENT +END:VCALENDAR +ICS; + + $tests[] = array( + $blob, + '20110101T190000Z/20110101T200000Z', + ); + + // Day-long event, shows up + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar9 +DTSTART;VALUE=DATE:20110102 +END:VEVENT +END:VCALENDAR +ICS; + + $tests[] = array( + $blob, + '20110102T000000Z/20110103T000000Z', + ); + + + // No duration, does not show up + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar10 +DTSTART:20110101T200000Z +END:VEVENT +END:VCALENDAR +ICS; + + $tests[] = array( + $blob, + null, + ); + + // encoded as object, shows up + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar11 +DTSTART:20110101T210000Z +DURATION:PT1H +END:VEVENT +END:VCALENDAR +ICS; + + $tests[] = array( + Reader::read($blob), + '20110101T210000Z/20110101T220000Z', + ); + + // Freebusy. Some parts show up + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VFREEBUSY +FREEBUSY:20110103T010000Z/20110103T020000Z +FREEBUSY;FBTYPE=FREE:20110103T020000Z/20110103T030000Z +FREEBUSY:20110103T030000Z/20110103T040000Z,20110103T040000Z/20110103T050000Z +FREEBUSY:20120101T000000Z/20120101T010000Z +FREEBUSY:20110103T050000Z/PT1H +END:VFREEBUSY +END:VCALENDAR +ICS; + + $tests[] = array( + Reader::read($blob), + array( + '20110103T010000Z/20110103T020000Z', + '20110103T030000Z/20110103T040000Z', + '20110103T040000Z/20110103T050000Z', + '20110103T050000Z/20110103T060000Z', + ) + ); + + + // Yearly recurrence rule, shows up + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar13 +DTSTART:20100101T220000Z +DTEND:20100101T230000Z +RRULE:FREQ=YEARLY +END:VEVENT +END:VCALENDAR +ICS; + + $tests[] = array( + Reader::read($blob), + '20110101T220000Z/20110101T230000Z', + ); + + + // Yearly recurrence rule + duration, shows up + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar14 +DTSTART:20100101T230000Z +DURATION:PT1H +RRULE:FREQ=YEARLY +END:VEVENT +END:VCALENDAR +ICS; + + $tests[] = array( + Reader::read($blob), + '20110101T230000Z/20110102T000000Z', + ); + + // Floating time, no timezone + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar +DTSTART:20110101T120000 +DTEND:20110101T130000 +END:VEVENT +END:VCALENDAR +ICS; + + $tests[] = array( + $blob, + "20110101T120000Z/20110101T130000Z" + ); + + // Floating time + reference timezone + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar +DTSTART:20110101T120000 +DTEND:20110101T130000 +END:VEVENT +END:VCALENDAR +ICS; + + $tests[] = array( + $blob, + "20110101T170000Z/20110101T180000Z", + new \DateTimeZone('America/Toronto') + ); + + // All-day event + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar +DTSTART;VALUE=DATE:20110101 +END:VEVENT +END:VCALENDAR +ICS; + + $tests[] = array( + $blob, + "20110101T000000Z/20110102T000000Z" + ); + + // All-day event + reference timezone + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar +DTSTART;VALUE=DATE:20110101 +END:VEVENT +END:VCALENDAR +ICS; + + $tests[] = array( + $blob, + "20110101T050000Z/20110102T050000Z", + new \DateTimeZone('America/Toronto') + ); + + // Recurrence rule with no valid instances + $blob = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar +DTSTART:20110101T100000Z +DTEND:20110103T120000Z +RRULE:FREQ=WEEKLY;COUNT=1 +EXDATE:20110101T100000Z +END:VEVENT +END:VCALENDAR +ICS; + + $tests[] = array( + $blob, + array() + ); + return $tests; + + } + + /** + * @dataProvider getInput + */ + function testGenerator($input, $expected, $timeZone = null) { + + $gen = new FreeBusyGenerator( + new \DateTime('20110101T110000Z', new \DateTimeZone('UTC')), + new \DateTime('20110103T110000Z', new \DateTimeZone('UTC')), + $input, + $timeZone + ); + + $result = $gen->getResult(); + + $expected = (array)$expected; + + $freebusy = $result->VFREEBUSY->select('FREEBUSY'); + + foreach($freebusy as $fb) { + + $this->assertContains((string)$fb, $expected, "$fb did not appear in our list of expected freebusy strings. This is concerning!"); + + $k = array_search((string)$fb, $expected); + unset($expected[$k]); + + } + $this->assertTrue( + count($expected) === 0, + 'There were elements in the expected array that were not found in the output: ' . "\n" . print_r($expected,true) . "\n" . $result->serialize() + ); + + } + + function testGeneratorBaseObject() { + + $obj = new Component\VCalendar(); + $obj->METHOD = 'PUBLISH'; + + $gen = new FreeBusyGenerator(); + $gen->setObjects(array()); + $gen->setBaseObject($obj); + + $result = $gen->getResult(); + + $this->assertEquals('PUBLISH', $result->METHOD->getValue()); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testInvalidArg() { + + $gen = new FreeBusyGenerator( + new \DateTime('2012-01-01'), + new \DateTime('2012-12-31'), + new \StdClass() + ); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/GoogleColonEscapingTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/GoogleColonEscapingTest.php new file mode 100644 index 0000000..0961ec1 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/GoogleColonEscapingTest.php @@ -0,0 +1,31 @@ +<?php + +namespace Sabre\VObject; + +/** + * Google produces vcards with a weird escaping of urls. + * + * VObject will provide a workaround for this, so end-user still get expected + * values. + */ +class GoogleColonEscaping extends \PHPUnit_Framework_TestCase { + + function testDecode() { + + $vcard = <<<VCF +BEGIN:VCARD +VERSION:3.0 +FN:Evert Pot +N:Pot;Evert;;; +EMAIL;TYPE=INTERNET;TYPE=WORK:evert@fruux.com +BDAY:1985-04-07 +item7.URL:http\://www.rooftopsolutions.nl/ +END:VCARD +VCF; + + $vobj = Reader::read($vcard); + $this->assertEquals('http://www.rooftopsolutions.nl/', $vobj->URL->getValue()); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ICalendar/AttachParseTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ICalendar/AttachParseTest.php new file mode 100644 index 0000000..0c4fc87 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ICalendar/AttachParseTest.php @@ -0,0 +1,31 @@ +<?php + +namespace Sabre\VObject\ICalendar; + +use Sabre\VObject\Reader; + +class AttachParseTest extends \PHPUnit_Framework_TestCase { + + /** + * See issue #128 for more info. + */ + function testParseAttach() { + + $vcal = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +ATTACH;FMTTYPE=application/postscript:ftp://example.com/pub/reports/r-960812.ps +END:VEVENT +END:VCALENDAR +ICS; + + $vcal = Reader::read($vcal); + $prop = $vcal->VEVENT->ATTACH; + + $this->assertInstanceOf('Sabre\\VObject\\Property\\URI', $prop); + $this->assertEquals('ftp://example.com/pub/reports/r-960812.ps', $prop->getValue()); + + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/BrokerAttendeeReplyTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/BrokerAttendeeReplyTest.php new file mode 100644 index 0000000..6fb0367 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/BrokerAttendeeReplyTest.php @@ -0,0 +1,1130 @@ +<?php + +namespace Sabre\VObject\ITip; + +class BrokerAttendeeReplyTest extends BrokerTester { + + function testAccepted() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SUMMARY:B-day party +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SUMMARY:B-day party +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = array( + array( + 'uid' => 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => 'One', + 'recipient' => 'mailto:strunk@example.org', + 'recipientName' => 'Strunk', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REPLY +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART:20140716T120000Z +SUMMARY:B-day party +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + + ), + + ); + + $result = $this->parse($oldMessage, $newMessage, $expected); + + } + + function testRecurringReply() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +DTSTART:20140724T120000Z +SUMMARY:Daily sprint +RRULE;FREQ=DAILY +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=NEEDS-ACTION;CN=One:mailto:one@example.org +DTSTART:20140724T120000Z +SUMMARY:Daily sprint +END:VEVENT +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org +DTSTART:20140726T120000Z +RECURRENCE-ID:20140726T120000Z +END:VEVENT +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=DECLINED;CN=One:mailto:one@example.org +DTSTART:20140724T120000Z +RECURRENCE-ID:20140724T120000Z +END:VEVENT +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=TENTATIVE;CN=One:mailto:one@example.org +DTSTART:20140728T120000Z +RECURRENCE-ID:20140728T120000Z +END:VEVENT +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org +DTSTART:20140729T120000Z +RECURRENCE-ID:20140729T120000Z +END:VEVENT +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=DECLINED;CN=One:mailto:one@example.org +DTSTART:20140725T120000Z +RECURRENCE-ID:20140725T120000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = array( + array( + 'uid' => 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => 'One', + 'recipient' => 'mailto:strunk@example.org', + 'recipientName' => 'Strunk', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REPLY +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART:20140726T120000Z +SUMMARY:Daily sprint +RECURRENCE-ID:20140726T120000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org +END:VEVENT +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART:20140724T120000Z +SUMMARY:Daily sprint +RECURRENCE-ID:20140724T120000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=DECLINED;CN=One:mailto:one@example.org +END:VEVENT +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART:20140728T120000Z +SUMMARY:Daily sprint +RECURRENCE-ID:20140728T120000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=TENTATIVE;CN=One:mailto:one@example.org +END:VEVENT +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART:20140729T120000Z +SUMMARY:Daily sprint +RECURRENCE-ID:20140729T120000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org +END:VEVENT +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART:20140725T120000Z +SUMMARY:Daily sprint +RECURRENCE-ID:20140725T120000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=DECLINED;CN=One:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + + ), + + ); + + $result = $this->parse($oldMessage, $newMessage, $expected); + + } + + function testRecurringAllDay() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +DTSTART;VALUE=DATE:20140724 +RRULE;FREQ=DAILY +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=NEEDS-ACTION;CN=One:mailto:one@example.org +DTSTART;VALUE=DATE:20140724 +END:VEVENT +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org +DTSTART;VALUE=DATE:20140726 +RECURRENCE-ID;VALUE=DATE:20140726 +END:VEVENT +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=DECLINED;CN=One:mailto:one@example.org +DTSTART;VALUE=DATE:20140724 +RECURRENCE-ID;VALUE=DATE:20140724 +END:VEVENT +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=TENTATIVE;CN=One:mailto:one@example.org +DTSTART;VALUE=DATE:20140728 +RECURRENCE-ID;VALUE=DATE:20140728 +END:VEVENT +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org +DTSTART;VALUE=DATE:20140729 +RECURRENCE-ID;VALUE=DATE:20140729 +END:VEVENT +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=DECLINED;CN=One:mailto:one@example.org +DTSTART;VALUE=DATE:20140725 +RECURRENCE-ID;VALUE=DATE:20140725 +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = array( + array( + 'uid' => 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => 'One', + 'recipient' => 'mailto:strunk@example.org', + 'recipientName' => 'Strunk', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REPLY +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART;VALUE=DATE:20140726 +RECURRENCE-ID;VALUE=DATE:20140726 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org +END:VEVENT +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART;VALUE=DATE:20140724 +RECURRENCE-ID;VALUE=DATE:20140724 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=DECLINED;CN=One:mailto:one@example.org +END:VEVENT +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART;VALUE=DATE:20140728 +RECURRENCE-ID;VALUE=DATE:20140728 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=TENTATIVE;CN=One:mailto:one@example.org +END:VEVENT +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART;VALUE=DATE:20140729 +RECURRENCE-ID;VALUE=DATE:20140729 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org +END:VEVENT +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART;VALUE=DATE:20140725 +RECURRENCE-ID;VALUE=DATE:20140725 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=DECLINED;CN=One:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + + ), + + ); + + $result = $this->parse($oldMessage, $newMessage, $expected); + + } + + function testNoChange() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=NEEDS-ACTION;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $expected = array(); + $result = $this->parse($oldMessage, $newMessage, $expected); + + } + + function testNoChangeForceSend() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;SCHEDULE-FORCE-SEND=REPLY;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=NEEDS-ACTION;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + $expected = array( + array( + 'uid' => 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => 'One', + 'recipient' => 'mailto:strunk@example.org', + 'recipientName' => 'Strunk', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REPLY +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART:20140716T120000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=NEEDS-ACTION;CN=One:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + ) + + ); + $result = $this->parse($oldMessage, $newMessage, $expected); + + } + + function testNoRelevantAttendee() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Two:mailto:two@example.org +DTSTART:20140716T120000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=Two:mailto:two@example.org +DTSTART:20140716T120000Z +END:VEVENT +END:VCALENDAR +ICS; + + $expected = array(); + $result = $this->parse($oldMessage, $newMessage, $expected); + + } + + /** + * In this test, an event exists in an attendees calendar. The event + * is recurring, and the attendee deletes 1 instance of the event. + * This instance shows up in EXDATE + * + * This should automatically generate a DECLINED message for that + * specific instance. + */ + function testCreateReplyByException() { + + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART:20140811T200000Z +RRULE:FREQ=WEEKLY +ORGANIZER:mailto:organizer@example.org +ATTENDEE:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART:20140811T200000Z +RRULE:FREQ=WEEKLY +ORGANIZER:mailto:organizer@example.org +ATTENDEE:mailto:one@example.org +EXDATE:20140818T200000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + $expected = array( + array( + 'uid' => 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => null, + 'recipient' => 'mailto:organizer@example.org', + 'recipientName' => null, + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REPLY +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART:20140818T200000Z +RECURRENCE-ID:20140818T200000Z +ORGANIZER:mailto:organizer@example.org +ATTENDEE;PARTSTAT=DECLINED:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + + ), + ); + $result = $this->parse($oldMessage, $newMessage, $expected); + + } + + /** + * This test is identical to the last, but now we're working with + * timezones. + * + * @depends testCreateReplyByException + */ + function testCreateReplyByExceptionTz() { + + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART;TZID=America/Toronto:20140811T200000 +RRULE:FREQ=WEEKLY +ORGANIZER:mailto:organizer@example.org +ATTENDEE:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART;TZID=America/Toronto:20140811T200000 +RRULE:FREQ=WEEKLY +ORGANIZER:mailto:organizer@example.org +ATTENDEE:mailto:one@example.org +EXDATE;TZID=America/Toronto:20140818T200000 +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + $expected = array( + array( + 'uid' => 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => null, + 'recipient' => 'mailto:organizer@example.org', + 'recipientName' => null, + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REPLY +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART;TZID=America/Toronto:20140818T200000 +RECURRENCE-ID;TZID=America/Toronto:20140818T200000 +ORGANIZER:mailto:organizer@example.org +ATTENDEE;PARTSTAT=DECLINED:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + + ), + ); + $result = $this->parse($oldMessage, $newMessage, $expected); + + } + + /** + * @depends testCreateReplyByException + */ + function testCreateReplyByExceptionAllDay() { + + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SUMMARY:Weekly meeting +UID:foobar +SEQUENCE:1 +DTSTART;VALUE=DATE:20140811 +RRULE:FREQ=WEEKLY +ORGANIZER:mailto:organizer@example.org +ATTENDEE:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SUMMARY:Weekly meeting +UID:foobar +SEQUENCE:1 +DTSTART;VALUE=DATE:20140811 +RRULE:FREQ=WEEKLY +ORGANIZER:mailto:organizer@example.org +ATTENDEE:mailto:one@example.org +EXDATE;VALUE=DATE:20140818 +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + $expected = array( + array( + 'uid' => 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => null, + 'recipient' => 'mailto:organizer@example.org', + 'recipientName' => null, + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REPLY +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART;VALUE=DATE:20140818 +SUMMARY:Weekly meeting +RECURRENCE-ID;VALUE=DATE:20140818 +ORGANIZER:mailto:organizer@example.org +ATTENDEE;PARTSTAT=DECLINED:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + + ), + ); + $result = $this->parse($oldMessage, $newMessage, $expected); + + } + + function testDeclined() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=DECLINED;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = array( + array( + 'uid' => 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => 'One', + 'recipient' => 'mailto:strunk@example.org', + 'recipientName' => 'Strunk', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REPLY +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART:20140716T120000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=DECLINED;CN=One:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + + ), + + ); + + $result = $this->parse($oldMessage, $newMessage, $expected); + + } + + function testDeclinedCancelledEvent() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +STATUS:CANCELLED +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +STATUS:CANCELLED +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=DECLINED;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = array(); + + $result = $this->parse($oldMessage, $newMessage, $expected); + + } + + /** + * In this test, a new exception is created by an attendee as well. + * + * Except in this case, there was already an overridden event, and the + * overridden event was marked as cancelled by the attendee. + * + * For any other attendence status, the new status would have been + * declined, but for this, no message should we sent. + */ + function testDontCreateReplyWhenEventWasDeclined() { + + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART:20140811T200000Z +RRULE:FREQ=WEEKLY +ORGANIZER:mailto:organizer@example.org +ATTENDEE:mailto:one@example.org +END:VEVENT +BEGIN:VEVENT +RECURRENCE-ID:20140818T200000Z +UID:foobar +SEQUENCE:1 +DTSTART:20140818T200000Z +RRULE:FREQ=WEEKLY +ORGANIZER:mailto:organizer@example.org +ATTENDEE;PARTSTAT=DECLINED:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART:20140811T200000Z +RRULE:FREQ=WEEKLY +ORGANIZER:mailto:organizer@example.org +ATTENDEE:mailto:one@example.org +EXDATE:20140818T200000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + $expected = array(); + + $result = $this->parse($oldMessage, $newMessage, $expected); + + } + + function testScheduleAgentOnOrganizer() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;SCHEDULE-AGENT=CLIENT;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = array(); + $result = $this->parse($oldMessage, $newMessage, $expected); + + } + + function testAcceptedAllDay() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +DTSTART;VALUE=DATE:20140716 +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org +DTSTART;VALUE=DATE:20140716 +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = array( + array( + 'uid' => 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => 'One', + 'recipient' => 'mailto:strunk@example.org', + 'recipientName' => 'Strunk', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REPLY +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART;VALUE=DATE:20140716 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + + ), + + ); + + $result = $this->parse($oldMessage, $newMessage, $expected); + + } + + /** + * This function tests an attendee updating their status to an event where + * they don't have the master event of. + * + * This is possible in cases an organizer created a recurring event, and + * invited an attendee for one instance of the event. + */ + function testReplyNoMasterEvent() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +RECURRENCE-ID:20140724T120000Z +DTSTART:20140724T120000Z +SUMMARY:Daily sprint +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org +RECURRENCE-ID:20140724T120000Z +DTSTART:20140724T120000Z +SUMMARY:Daily sprint +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = array( + array( + 'uid' => 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => 'One', + 'recipient' => 'mailto:strunk@example.org', + 'recipientName' => 'Strunk', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +CALSCALE:GREGORIAN +METHOD:REPLY +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART:20140724T120000Z +SUMMARY:Daily sprint +RECURRENCE-ID:20140724T120000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + + ), + + ); + + $result = $this->parse($oldMessage, $newMessage, $expected); + + } + + /** + * A party crasher is an attendee that accepted an event, but was not in + * any original invite. + * + * @depends testAccepted + */ + function testPartyCrasher() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SUMMARY:B-day party +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +DTSTART:20140716T120000Z +RRULE:FREQ=DAILY +END:VEVENT +BEGIN:VEVENT +UID:foobar +RECURRENCE-ID:20140717T120000Z +SUMMARY:B-day party +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +DTSTART:20140717T120000Z +RRULE:FREQ=DAILY +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SUMMARY:B-day party +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +DTSTART:20140716T120000Z +RRULE:FREQ=DAILY +END:VEVENT +BEGIN:VEVENT +UID:foobar +RECURRENCE-ID:20140717T120000Z +SUMMARY:B-day party +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org +DTSTART:20140717T120000Z +RRULE:FREQ=DAILY +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = array( + array( + 'uid' => 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => 'One', + 'recipient' => 'mailto:strunk@example.org', + 'recipientName' => 'Strunk', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +CALSCALE:GREGORIAN +METHOD:REPLY +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART:20140717T120000Z +SUMMARY:B-day party +RECURRENCE-ID:20140717T120000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=ACCEPTED;CN=One:mailto:one@example.org +END:VEVENT +END:VCALENDAR + +ICS + + ), + + ); + + $result = $this->parse($oldMessage, $newMessage, $expected); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/BrokerDeleteEventTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/BrokerDeleteEventTest.php new file mode 100644 index 0000000..3d483b8 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/BrokerDeleteEventTest.php @@ -0,0 +1,340 @@ +<?php + +namespace Sabre\VObject\ITip; + +class BrokerDeleteEventTest extends BrokerTester { + + function testOrganizerDeleteWithDtend() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +SUMMARY:foo +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +ATTENDEE;CN=Two:mailto:two@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = null; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = array( + array( + 'uid' => 'foobar', + 'method' => 'CANCEL', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:CANCEL +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +SUMMARY:foo +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + ), + + array( + 'uid' => 'foobar', + 'method' => 'CANCEL', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:two@example.org', + 'recipientName' => 'Two', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:CANCEL +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +SUMMARY:foo +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Two:mailto:two@example.org +END:VEVENT +END:VCALENDAR +ICS + + ), + ); + + $result = $this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } + + function testOrganizerDeleteWithDuration() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +SUMMARY:foo +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +ATTENDEE;CN=Two:mailto:two@example.org +DTSTART:20140716T120000Z +DURATION:PT1H +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = null; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = array( + array( + 'uid' => 'foobar', + 'method' => 'CANCEL', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:CANCEL +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +SUMMARY:foo +DTSTART:20140716T120000Z +DURATION:PT1H +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + ), + + array( + 'uid' => 'foobar', + 'method' => 'CANCEL', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:two@example.org', + 'recipientName' => 'Two', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:CANCEL +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +SUMMARY:foo +DTSTART:20140716T120000Z +DURATION:PT1H +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Two:mailto:two@example.org +END:VEVENT +END:VCALENDAR +ICS + + ), + ); + + $result = $this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } + + function testAttendeeDeleteWithDtend() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +SUMMARY:foo +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +ATTENDEE;CN=Two:mailto:two@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = null; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = array( + array( + 'uid' => 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => 'One', + 'recipient' => 'mailto:strunk@example.org', + 'recipientName' => 'Strunk', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REPLY +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +SUMMARY:foo +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=DECLINED;CN=One:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + ), + ); + + $result = $this->parse($oldMessage, $newMessage, $expected, 'mailto:one@example.org'); + + + } + + function testAttendeeDeleteWithDuration() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +SUMMARY:foo +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +ATTENDEE;CN=Two:mailto:two@example.org +DTSTART:20140716T120000Z +DURATION:PT1H +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = null; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = array( + array( + 'uid' => 'foobar', + 'method' => 'REPLY', + 'component' => 'VEVENT', + 'sender' => 'mailto:one@example.org', + 'senderName' => 'One', + 'recipient' => 'mailto:strunk@example.org', + 'recipientName' => 'Strunk', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REPLY +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART:20140716T120000Z +DURATION:PT1H +SUMMARY:foo +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;PARTSTAT=DECLINED;CN=One:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + ), + ); + + $result = $this->parse($oldMessage, $newMessage, $expected, 'mailto:one@example.org'); + + + } + + function testAttendeeDeleteCancelledEvent() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +STATUS:CANCELLED +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +ATTENDEE;CN=Two:mailto:two@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = null; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = array(); + + $result = $this->parse($oldMessage, $newMessage, $expected, 'mailto:one@example.org'); + + + } + + function testNoCalendar() { + + $this->parse(null, null, array(), 'mailto:one@example.org'); + + } + + function testVTodo() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VTODO +UID:foobar +SEQUENCE:1 +END:VTODO +END:VCALENDAR +ICS; + $this->parse($oldMessage, null, array(), 'mailto:one@example.org'); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/BrokerNewEventTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/BrokerNewEventTest.php new file mode 100644 index 0000000..e37bee6 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/BrokerNewEventTest.php @@ -0,0 +1,528 @@ +<?php + +namespace Sabre\VObject\ITip; + +class BrokerNewEventTest extends \PHPUnit_Framework_TestCase { + + function testNoAttendee() { + + $message = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar +DTSTART:20140811T220000Z +DTEND:20140811T230000Z +END:VEVENT +END:VCALENDAR +ICS; + + $result = $this->parse($message); + + } + + function testVTODO() { + + $message = <<<ICS +BEGIN:VCALENDAR +BEGIN:VTODO +UID:foobar +END:VTODO +END:VCALENDAR +ICS; + + $result = $this->parse($message); + + } + + function testSimpleInvite() { + + $message = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +DTSTART:20140811T220000Z +DTEND:20140811T230000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=White:mailto:white@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + $expectedMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +UID:foobar +DTSTART:20140811T220000Z +DTEND:20140811T230000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=White;PARTSTAT=NEEDS-ACTION:mailto:white@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $expected = array( + array( + 'uid' => 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:white@example.org', + 'recipientName' => 'White', + 'message' => $expectedMessage, + ), + ); + + $result = $this->parse($message, $expected); + + } + + /** + * @expectedException \Sabre\VObject\ITip\ITipException + */ + function testBrokenEventUIDMisMatch() { + + $message = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=White:mailto:white@example.org +END:VEVENT +BEGIN:VEVENT +UID:foobar2 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=White:mailto:white@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $expected = array(); + $this->parse($message, array()); + + } + /** + * @expectedException \Sabre\VObject\ITip\ITipException + */ + function testBrokenEventOrganizerMisMatch() { + + $message = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=White:mailto:white@example.org +END:VEVENT +BEGIN:VEVENT +UID:foobar +ORGANIZER:mailto:foo@example.org +ATTENDEE;CN=White:mailto:white@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $expected = array(); + $this->parse($message, array()); + + } + + function testRecurrenceInvite() { + + $message = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +ATTENDEE;CN=Two:mailto:two@example.org +DTSTART:20140716T120000Z +DURATION:PT1H +RRULE:FREQ=DAILY +EXDATE:20140717T120000Z +END:VEVENT +BEGIN:VEVENT +UID:foobar +RECURRENCE-ID:20140718T120000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Two:mailto:two@example.org +ATTENDEE;CN=Three:mailto:three@example.org +DTSTART:20140718T120000Z +DURATION:PT1H +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = array( + array( + 'uid' => 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +UID:foobar +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One;PARTSTAT=NEEDS-ACTION:mailto:one@example.org +ATTENDEE;CN=Two;PARTSTAT=NEEDS-ACTION:mailto:two@example.org +DTSTART:20140716T120000Z +DURATION:PT1H +RRULE:FREQ=DAILY +EXDATE:20140717T120000Z,20140718T120000Z +END:VEVENT +END:VCALENDAR +ICS + + ), + array( + 'uid' => 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:two@example.org', + 'recipientName' => 'Two', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +UID:foobar +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One;PARTSTAT=NEEDS-ACTION:mailto:one@example.org +ATTENDEE;CN=Two;PARTSTAT=NEEDS-ACTION:mailto:two@example.org +DTSTART:20140716T120000Z +DURATION:PT1H +RRULE:FREQ=DAILY +EXDATE:20140717T120000Z +END:VEVENT +BEGIN:VEVENT +UID:foobar +RECURRENCE-ID:20140718T120000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Two:mailto:two@example.org +ATTENDEE;CN=Three:mailto:three@example.org +DTSTART:20140718T120000Z +DURATION:PT1H +END:VEVENT +END:VCALENDAR +ICS + + ), + array( + 'uid' => 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:three@example.org', + 'recipientName' => 'Three', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +UID:foobar +RECURRENCE-ID:20140718T120000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Two:mailto:two@example.org +ATTENDEE;CN=Three:mailto:three@example.org +DTSTART:20140718T120000Z +DURATION:PT1H +END:VEVENT +END:VCALENDAR +ICS + + ), + ); + + $result = $this->parse($message, $expected); + + } + + function testRecurrenceInvite2() { + + // This method tests a nearly identical path, but in this case the + // master event does not have an EXDATE. + $message = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +ATTENDEE;CN=Two:mailto:two@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +RRULE:FREQ=DAILY +END:VEVENT +BEGIN:VEVENT +UID:foobar +RECURRENCE-ID:20140718T120000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Two:mailto:two@example.org +ATTENDEE;CN=Three:mailto:three@example.org +DTSTART:20140718T120000Z +DTEND:20140718T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = array( + array( + 'uid' => 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +UID:foobar +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One;PARTSTAT=NEEDS-ACTION:mailto:one@example.org +ATTENDEE;CN=Two;PARTSTAT=NEEDS-ACTION:mailto:two@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +RRULE:FREQ=DAILY +EXDATE:20140718T120000Z +END:VEVENT +END:VCALENDAR +ICS + + ), + array( + 'uid' => 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:two@example.org', + 'recipientName' => 'Two', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +UID:foobar +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One;PARTSTAT=NEEDS-ACTION:mailto:one@example.org +ATTENDEE;CN=Two;PARTSTAT=NEEDS-ACTION:mailto:two@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +RRULE:FREQ=DAILY +END:VEVENT +BEGIN:VEVENT +UID:foobar +RECURRENCE-ID:20140718T120000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Two:mailto:two@example.org +ATTENDEE;CN=Three:mailto:three@example.org +DTSTART:20140718T120000Z +DTEND:20140718T130000Z +END:VEVENT +END:VCALENDAR +ICS + + ), + array( + 'uid' => 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:three@example.org', + 'recipientName' => 'Three', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +UID:foobar +RECURRENCE-ID:20140718T120000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Two:mailto:two@example.org +ATTENDEE;CN=Three:mailto:three@example.org +DTSTART:20140718T120000Z +DTEND:20140718T130000Z +END:VEVENT +END:VCALENDAR +ICS + + ), + ); + + $result = $this->parse($message, $expected); + + } + + function testScheduleAgentClient() { + + $message = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +DTSTART:20140811T220000Z +DTEND:20140811T230000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=White;SCHEDULE-AGENT=CLIENT:mailto:white@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = array(); + $result = $this->parse($message, $expected); + + } + + /** + * @expectedException Sabre\VObject\ITip\ITipException + */ + function testMultipleUID() { + + $message = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +ATTENDEE;CN=Two:mailto:two@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +RRULE:FREQ=DAILY +END:VEVENT +BEGIN:VEVENT +UID:foobar2 +RECURRENCE-ID:20140718T120000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Two:mailto:two@example.org +ATTENDEE;CN=Three:mailto:three@example.org +DTSTART:20140718T120000Z +DTEND:20140718T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + $result = $this->parse($message, array()); + + } + + /** + * @expectedException Sabre\VObject\ITip\SameOrganizerForAllComponentsException + * + */ + function testChangingOrganizers() { + + $message = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +ATTENDEE;CN=Two:mailto:two@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +RRULE:FREQ=DAILY +END:VEVENT +BEGIN:VEVENT +UID:foobar +RECURRENCE-ID:20140718T120000Z +ORGANIZER;CN=Strunk:mailto:ew@example.org +ATTENDEE;CN=Two:mailto:two@example.org +ATTENDEE;CN=Three:mailto:three@example.org +DTSTART:20140718T120000Z +DTEND:20140718T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + $result = $this->parse($message, array()); + + } + function testNoOrganizerHasAttendee() { + + $message = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar +DTSTART:20140811T220000Z +DTEND:20140811T230000Z +ATTENDEE;CN=Two:mailto:two@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $this->parse($message, array()); + + } + + function parse($message, $expected = array()) { + + $broker = new Broker(); + $result = $broker->parseEvent($message, 'mailto:strunk@example.org'); + + $this->assertEquals(count($expected), count($result)); + + foreach($expected as $index=>$ex) { + + $message = $result[$index]; + + foreach($ex as $key=>$val) { + + if ($key==='message') { + $this->assertEquals( + str_replace("\n", "\r\n", $val), + rtrim($message->message->serialize(), "\r\n") + ); + } else { + $this->assertEquals($val, $message->$key); + } + + } + + } + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/BrokerProcessMessageTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/BrokerProcessMessageTest.php new file mode 100644 index 0000000..649df98 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/BrokerProcessMessageTest.php @@ -0,0 +1,168 @@ +<?php + +namespace Sabre\VObject\ITip; + +class BrokerProcessMessageTest extends BrokerTester { + + function testRequestNew() { + + $itip = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:REQUEST +BEGIN:VEVENT +SEQUENCE:1 +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $expected = <<<ICS +BEGIN:VCALENDAR +%foo% +BEGIN:VEVENT +SEQUENCE:1 +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $result = $this->process($itip, null, $expected); + + } + + function testRequestUpdate() { + + $itip = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:REQUEST +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $old = <<<ICS +BEGIN:VCALENDAR +%foo% +BEGIN:VEVENT +SEQUENCE:1 +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $expected = <<<ICS +BEGIN:VCALENDAR +%foo% +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $result = $this->process($itip, $old, $expected); + + } + + function testCancel() { + + $itip = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:CANCEL +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $old = <<<ICS +BEGIN:VCALENDAR +%foo% +BEGIN:VEVENT +SEQUENCE:1 +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $expected = <<<ICS +BEGIN:VCALENDAR +%foo% +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +STATUS:CANCELLED +END:VEVENT +END:VCALENDAR +ICS; + + $result = $this->process($itip, $old, $expected); + + } + + function testCancelNoExistingEvent() { + + $itip = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:CANCEL +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $old = null; + $expected = null; + + $result = $this->process($itip, $old, $expected); + + } + + function testUnsupportedComponent() { + + $itip = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VTODO +SEQUENCE:2 +UID:foobar +END:VTODO +END:VCALENDAR +ICS; + + $old = null; + $expected = null; + + $result = $this->process($itip, $old, $expected); + + } + + function testUnsupportedMethod() { + + $itip = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:PUBLISH +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $old = null; + $expected = null; + + $result = $this->process($itip, $old, $expected); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/BrokerProcessReplyTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/BrokerProcessReplyTest.php new file mode 100644 index 0000000..533fdce --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/BrokerProcessReplyTest.php @@ -0,0 +1,496 @@ +<?php + +namespace Sabre\VObject\ITip; + +class BrokerProcessReplyTest extends BrokerTester { + + function testReplyNoOriginal() { + + $itip = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:REPLY +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +ATTENDEE;PARTSTAT=ACCEPTED:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $old = null; + $expected = null; + + $result = $this->process($itip, $old, $expected); + + } + + function testReplyAccept() { + + $itip = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:REPLY +BEGIN:VEVENT +ATTENDEE;PARTSTAT=ACCEPTED:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +SEQUENCE:2 +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $old = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +ATTENDEE:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $expected = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +ATTENDEE;PARTSTAT=ACCEPTED;SCHEDULE-STATUS=2.0:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $result = $this->process($itip, $old, $expected); + + } + + function testReplyRequestStatus() { + + $itip = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:REPLY +BEGIN:VEVENT +UID:foobar +REQUEST-STATUS:2.3;foo-bar! +ATTENDEE;PARTSTAT=ACCEPTED:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +SEQUENCE:2 +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $old = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +ATTENDEE:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $expected = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +ATTENDEE;PARTSTAT=ACCEPTED;SCHEDULE-STATUS=2.3:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $result = $this->process($itip, $old, $expected); + + } + + + function testReplyPartyCrasher() { + + $itip = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:REPLY +BEGIN:VEVENT +ATTENDEE;PARTSTAT=ACCEPTED:mailto:crasher@example.org +ORGANIZER:mailto:bar@example.org +SEQUENCE:2 +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $old = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +ATTENDEE:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $expected = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +ATTENDEE:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +ATTENDEE;PARTSTAT=ACCEPTED:mailto:crasher@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $result = $this->process($itip, $old, $expected); + + } + + function testReplyNewException() { + + // This is a reply to 1 instance of a recurring event. This should + // automatically create an exception. + $itip = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:REPLY +BEGIN:VEVENT +ATTENDEE;PARTSTAT=ACCEPTED:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +SEQUENCE:2 +RECURRENCE-ID:20140725T000000Z +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $old = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +RRULE:FREQ=DAILY +DTSTART:20140724T000000Z +DTEND:20140724T010000Z +ATTENDEE:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $expected = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +RRULE:FREQ=DAILY +DTSTART:20140724T000000Z +DTEND:20140724T010000Z +ATTENDEE:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +END:VEVENT +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +DTSTART:20140725T000000Z +DTEND:20140725T010000Z +ATTENDEE;PARTSTAT=ACCEPTED:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +RECURRENCE-ID:20140725T000000Z +END:VEVENT +END:VCALENDAR +ICS; + + $result = $this->process($itip, $old, $expected); + + } + + function testReplyNewExceptionTz() { + + // This is a reply to 1 instance of a recurring event. This should + // automatically create an exception. + $itip = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:REPLY +BEGIN:VEVENT +ATTENDEE;PARTSTAT=ACCEPTED:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +SEQUENCE:2 +RECURRENCE-ID;TZID=America/Toronto:20140725T000000 +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $old = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +RRULE:FREQ=DAILY +DTSTART;TZID=America/Toronto:20140724T000000 +DTEND;TZID=America/Toronto:20140724T010000 +ATTENDEE:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $expected = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +RRULE:FREQ=DAILY +DTSTART;TZID=America/Toronto:20140724T000000 +DTEND;TZID=America/Toronto:20140724T010000 +ATTENDEE:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +END:VEVENT +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +DTSTART;TZID=America/Toronto:20140725T000000 +DTEND;TZID=America/Toronto:20140725T010000 +ATTENDEE;PARTSTAT=ACCEPTED:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +RECURRENCE-ID;TZID=America/Toronto:20140725T000000 +END:VEVENT +END:VCALENDAR +ICS; + + $result = $this->process($itip, $old, $expected); + + } + + function testReplyPartyCrashCreateExcepton() { + + // IN this test there's a recurring event that has an exception. The + // exception is missing the attendee. + // + // The attendee party crashes the instance, so it should show up in the + // resulting object. + $itip = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:REPLY +BEGIN:VEVENT +ATTENDEE;PARTSTAT=ACCEPTED;CN=Crasher!:mailto:crasher@example.org +ORGANIZER:mailto:bar@example.org +SEQUENCE:2 +RECURRENCE-ID:20140725T000000Z +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $old = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +RRULE:FREQ=DAILY +DTSTART:20140724T000000Z +DTEND:20140724T010000Z +ORGANIZER:mailto:bar@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $expected = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +RRULE:FREQ=DAILY +DTSTART:20140724T000000Z +DTEND:20140724T010000Z +ORGANIZER:mailto:bar@example.org +END:VEVENT +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +DTSTART:20140725T000000Z +DTEND:20140725T010000Z +ORGANIZER:mailto:bar@example.org +RECURRENCE-ID:20140725T000000Z +ATTENDEE;PARTSTAT=ACCEPTED;CN=Crasher!:mailto:crasher@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $result = $this->process($itip, $old, $expected); + + } + + function testReplyNewExceptionNoMasterEvent() { + + /** + * This iTip message would normally create a new exception, but the + * server is not able to create this new instance, because there's no + * master event to clone from. + * + * This test checks if the message is ignored. + */ + $itip = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:REPLY +BEGIN:VEVENT +ATTENDEE;PARTSTAT=ACCEPTED;CN=Crasher!:mailto:crasher@example.org +ORGANIZER:mailto:bar@example.org +SEQUENCE:2 +RECURRENCE-ID:20140725T000000Z +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $old = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +RRULE:FREQ=DAILY +DTSTART:20140724T000000Z +DTEND:20140724T010000Z +RECURRENCE-ID:20140724T000000Z +ORGANIZER:mailto:bar@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $expected = null; + $result = $this->process($itip, $old, $expected); + + } + + /** + * @depends testReplyAccept + */ + function testReplyAcceptUpdateRSVP() { + + $itip = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:REPLY +BEGIN:VEVENT +ATTENDEE;PARTSTAT=ACCEPTED:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +SEQUENCE:2 +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $old = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +ATTENDEE;RSVP=TRUE:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $expected = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +ATTENDEE;PARTSTAT=ACCEPTED;SCHEDULE-STATUS=2.0:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $result = $this->process($itip, $old, $expected); + + } + + function testReplyNewExceptionFirstOccurence() { + + // This is a reply to 1 instance of a recurring event. This should + // automatically create an exception. + $itip = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +METHOD:REPLY +BEGIN:VEVENT +ATTENDEE;PARTSTAT=ACCEPTED:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +SEQUENCE:2 +RECURRENCE-ID:20140724T000000Z +UID:foobar +END:VEVENT +END:VCALENDAR +ICS; + + $old = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +RRULE:FREQ=DAILY +DTSTART:20140724T000000Z +DTEND:20140724T010000Z +ATTENDEE:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $expected = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +RRULE:FREQ=DAILY +DTSTART:20140724T000000Z +DTEND:20140724T010000Z +ATTENDEE:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +END:VEVENT +BEGIN:VEVENT +SEQUENCE:2 +UID:foobar +DTSTART:20140724T000000Z +DTEND:20140724T010000Z +ATTENDEE;PARTSTAT=ACCEPTED:mailto:foo@example.org +ORGANIZER:mailto:bar@example.org +RECURRENCE-ID:20140724T000000Z +END:VEVENT +END:VCALENDAR +ICS; + + $result = $this->process($itip, $old, $expected); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/BrokerTester.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/BrokerTester.php new file mode 100644 index 0000000..54c1702 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/BrokerTester.php @@ -0,0 +1,103 @@ +<?php + +namespace Sabre\VObject\ITip; + +use Sabre\VObject\Reader; + +/** + * Utilities for testing the broker + * + * @copyright Copyright (C) fruux GmbH (https://fruux.com/) + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +abstract class BrokerTester extends \Sabre\VObject\TestCase { + + function parse($oldMessage, $newMessage, $expected = array(), $currentUser = 'mailto:one@example.org') { + + $broker = new Broker(); + $result = $broker->parseEvent($newMessage, $currentUser, $oldMessage); + + $this->assertEquals(count($expected), count($result)); + + foreach($expected as $index=>$ex) { + + $message = $result[$index]; + + foreach($ex as $key=>$val) { + + if ($key==='message') { + $this->assertVObjEquals( + $val, + $message->message->serialize() + ); + } else { + $this->assertEquals($val, $message->$key); + } + + } + + } + + } + + function process($input, $existingObject = null, $expected = false) { + + $version = \Sabre\VObject\Version::VERSION; + + $vcal = Reader::read($input); + + foreach($vcal->getComponents() as $mainComponent) { + break; + } + + $message = new Message(); + $message->message = $vcal; + $message->method = isset($vcal->METHOD)?$vcal->METHOD->getValue():null; + $message->component = $mainComponent->name; + $message->uid = $mainComponent->uid->getValue(); + $message->sequence = isset($vcal->VEVENT[0])?(string)$vcal->VEVENT[0]->SEQUENCE:null; + + if ($message->method === 'REPLY') { + + $message->sender = $mainComponent->ATTENDEE->getValue(); + $message->senderName = isset($mainComponent->ATTENDEE['CN'])?$mainComponent->ATTENDEE['CN']->getValue():null; + $message->recipient = $mainComponent->ORGANIZER->getValue(); + $message->recipientName = isset($mainComponent->ORGANIZER['CN'])?$mainComponent->ORGANIZER['CN']:null; + + } + + $broker = new Broker(); + + if (is_string($existingObject)) { + $existingObject = str_replace( + '%foo%', + "VERSION:2.0\nPRODID:-//Sabre//Sabre VObject $version//EN\nCALSCALE:GREGORIAN", + $existingObject + ); + $existingObject = Reader::read($existingObject); + } + + $result = $broker->processMessage($message, $existingObject); + + if (is_string($expected)) { + $expected = str_replace( + '%foo%', + "VERSION:2.0\nPRODID:-//Sabre//Sabre VObject $version//EN\nCALSCALE:GREGORIAN", + $expected + ); + $expected = str_replace("\n", "\r\n", $expected); + + } + if ($result instanceof \Sabre\VObject\Component\VCalendar) { + $result = $result->serialize(); + $result = rtrim($result,"\r\n"); + } + + $this->assertEquals( + $expected, + $result + ); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/BrokerUpdateEventTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/BrokerUpdateEventTest.php new file mode 100644 index 0000000..1d7de7c --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/BrokerUpdateEventTest.php @@ -0,0 +1,841 @@ +<?php + +namespace Sabre\VObject\ITip; + +class BrokerUpdateTest extends BrokerTester { + + function testInviteChange() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +SUMMARY:foo +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +ATTENDEE;CN=Two:mailto:two@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +SUMMARY:foo +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org +ATTENDEE;CN=Two:mailto:two@example.org +ATTENDEE;CN=Three:mailto:three@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = array( + array( + 'uid' => 'foobar', + 'method' => 'CANCEL', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'significantChange' => true, + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:CANCEL +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +SUMMARY:foo +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + + ), + array( + 'uid' => 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:two@example.org', + 'recipientName' => 'Two', + 'significantChange' => false, + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +SUMMARY:foo +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org +ATTENDEE;CN=Two;PARTSTAT=NEEDS-ACTION:mailto:two@example.org +ATTENDEE;CN=Three;PARTSTAT=NEEDS-ACTION:mailto:three@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS + + ), + array( + 'uid' => 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:three@example.org', + 'recipientName' => 'Three', + 'significantChange' => true, + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +SUMMARY:foo +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org +ATTENDEE;CN=Two;PARTSTAT=NEEDS-ACTION:mailto:two@example.org +ATTENDEE;CN=Three;PARTSTAT=NEEDS-ACTION:mailto:three@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS + + ), + ); + + $this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } + + function testInviteChangeFromNonSchedulingToSchedulingObject() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = array( + array( + 'uid' => 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One;PARTSTAT=NEEDS-ACTION:mailto:one@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS + + ), + + ); + + $this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } + + function testInviteChangeFromSchedulingToNonSchedulingObject() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = array( + array( + 'uid' => 'foobar', + 'method' => 'CANCEL', + 'component' => 'VEVENT', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:CANCEL +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + + ), + + ); + + $this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } + + function testNoAttendees() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = array(); + $this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } + + function testRemoveInstance() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +DTSTART;TZID=America/Toronto:20140716T120000 +DTEND;TZID=America/Toronto:20140716T130000 +RRULE:FREQ=WEEKLY +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +DTSTART;TZID=America/Toronto:20140716T120000 +DTEND;TZID=America/Toronto:20140716T130000 +RRULE:FREQ=WEEKLY +EXDATE;TZID=America/Toronto:20140724T120000 +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = array( + array( + 'uid' => 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One;PARTSTAT=NEEDS-ACTION:mailto:one@example.org +DTSTART;TZID=America/Toronto:20140716T120000 +DTEND;TZID=America/Toronto:20140716T130000 +RRULE:FREQ=WEEKLY +EXDATE;TZID=America/Toronto:20140724T120000 +END:VEVENT +END:VCALENDAR +ICS + + ), + ); + + $this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } + + /** + * This test is identical to the first test, except this time we change the + * DURATION property. + * + * This should ensure that the message is significant for every attendee, + */ + function testInviteChangeSignificantChange() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +DURATION:PT1H +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +ATTENDEE;CN=Two:mailto:two@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +DURATION:PT2H +SEQUENCE:2 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org +ATTENDEE;CN=Two:mailto:two@example.org +ATTENDEE;CN=Three:mailto:three@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = array( + array( + 'uid' => 'foobar', + 'method' => 'CANCEL', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'significantChange' => true, + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:CANCEL +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + + ), + array( + 'uid' => 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:two@example.org', + 'recipientName' => 'Two', + 'significantChange' => true, + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +UID:foobar +DURATION:PT2H +SEQUENCE:2 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org +ATTENDEE;CN=Two;PARTSTAT=NEEDS-ACTION:mailto:two@example.org +ATTENDEE;CN=Three;PARTSTAT=NEEDS-ACTION:mailto:three@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS + + ), + array( + 'uid' => 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:three@example.org', + 'recipientName' => 'Three', + 'significantChange' => true, + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +UID:foobar +DURATION:PT2H +SEQUENCE:2 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org +ATTENDEE;CN=Two;PARTSTAT=NEEDS-ACTION:mailto:two@example.org +ATTENDEE;CN=Three;PARTSTAT=NEEDS-ACTION:mailto:three@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS + + ), + ); + + $this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } + + function testInviteNoChange() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = array( + array( + 'uid' => 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'significantChange' => false, + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org +ATTENDEE;CN=One;PARTSTAT=NEEDS-ACTION:mailto:one@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS + + ), + + ); + + $this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } + + function testInviteNoChangeForceSend() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org +ATTENDEE;SCHEDULE-FORCE-SEND=REQUEST;CN=One:mailto:one@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = array( + array( + 'uid' => 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'significantChange' => true, + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk;PARTSTAT=ACCEPTED:mailto:strunk@example.org +ATTENDEE;CN=One;PARTSTAT=NEEDS-ACTION:mailto:one@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS + + ), + + ); + + $this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } + + function testInviteRemoveAttendees() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +SUMMARY:foo +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +ATTENDEE;CN=Two:mailto:two@example.org +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +SUMMARY:foo +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = array( + array( + 'uid' => 'foobar', + 'method' => 'CANCEL', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'significantChange' => true, + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:CANCEL +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +SUMMARY:foo +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=One:mailto:one@example.org +END:VEVENT +END:VCALENDAR +ICS + + ), + array( + 'uid' => 'foobar', + 'method' => 'CANCEL', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:two@example.org', + 'recipientName' => 'Two', + 'significantChange' => true, + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:CANCEL +BEGIN:VEVENT +UID:foobar +SEQUENCE:2 +SUMMARY:foo +DTSTART:20140716T120000Z +DTEND:20140716T130000Z +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Two:mailto:two@example.org +END:VEVENT +END:VCALENDAR +ICS + + ), + ); + + $result = $this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } + + function testInviteChangeExdateOrder() { + + $oldMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Apple Inc.//Mac OS X 10.10.1//EN +CALSCALE:GREGORIAN +BEGIN:VEVENT +UID:foobar +SEQUENCE:0 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk;CUTYPE=INDIVIDUAL;EMAIL=strunk@example.org;PARTSTAT=ACCE + PTED:mailto:strunk@example.org +ATTENDEE;CN=One;CUTYPE=INDIVIDUAL;EMAIL=one@example.org;PARTSTAT=ACCEPTED;R + OLE=REQ-PARTICIPANT;SCHEDULE-STATUS="1.2;Message delivered locally":mailto + :one@example.org +SUMMARY:foo +DTSTART:20141211T160000Z +DTEND:20141211T170000Z +RRULE:FREQ=WEEKLY +EXDATE:20141225T160000Z,20150101T160000Z +EXDATE:20150108T160000Z +END:VEVENT +END:VCALENDAR +ICS; + + + $newMessage = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Apple Inc.//Mac OS X 10.10.1//EN +CALSCALE:GREGORIAN +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk;CUTYPE=INDIVIDUAL;EMAIL=strunk@example.org;PARTSTAT=ACCE + PTED:mailto:strunk@example.org +ATTENDEE;CN=One;CUTYPE=INDIVIDUAL;EMAIL=one@example.org;PARTSTAT=ACCEPTED;R + OLE=REQ-PARTICIPANT;SCHEDULE-STATUS=1.2:mailto:one@example.org +DTSTART:20141211T160000Z +DTEND:20141211T170000Z +RRULE:FREQ=WEEKLY +EXDATE:20150101T160000Z +EXDATE:20150108T160000Z,20141225T160000Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + + $expected = array( + array( + 'uid' => 'foobar', + 'method' => 'REQUEST', + 'component' => 'VEVENT', + 'sender' => 'mailto:strunk@example.org', + 'senderName' => 'Strunk', + 'recipient' => 'mailto:one@example.org', + 'recipientName' => 'One', + 'significantChange' => false, + 'message' => <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VEVENT +UID:foobar +SEQUENCE:1 +ORGANIZER;CN=Strunk:mailto:strunk@example.org +ATTENDEE;CN=Strunk;CUTYPE=INDIVIDUAL;EMAIL=strunk@example.org;PARTSTAT=ACCE + PTED:mailto:strunk@example.org +ATTENDEE;CN=One;CUTYPE=INDIVIDUAL;EMAIL=one@example.org;PARTSTAT=ACCEPTED;R + OLE=REQ-PARTICIPANT:mailto:one@example.org +DTSTART:20141211T160000Z +DTEND:20141211T170000Z +RRULE:FREQ=WEEKLY +EXDATE:20150101T160000Z +EXDATE:20150108T160000Z,20141225T160000Z +END:VEVENT +END:VCALENDAR +ICS + + ), + ); + + $this->parse($oldMessage, $newMessage, $expected, 'mailto:strunk@example.org'); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/EvolutionTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/EvolutionTest.php new file mode 100644 index 0000000..76c47ca --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/EvolutionTest.php @@ -0,0 +1,2653 @@ +<?php + +namespace Sabre\VObject\ITip; + +class EvolutionTest extends BrokerTester { + + /** + * Evolution does things as usual a little bit differently. + * + * We're adding a seprate test just for it. + */ + function testNewEvolutionEvent() { + + $ics = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +CALSCALE:GREGORIAN +PRODID:-//Ximian//NONSGML Evolution Calendar//EN +BEGIN:VTIMEZONE +TZID:/freeassociation.sourceforge.net/Tzfile/America/Toronto +X-LIC-LOCATION:America/Toronto +BEGIN:STANDARD +TZNAME:EST +DTSTART:19691026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19700426T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19701025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19710425T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19711031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19720430T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19721029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19730429T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19731028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19740428T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19741027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19750427T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19751026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19760425T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19761031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19770424T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19771030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19780430T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19781029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19790429T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19791028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19800427T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19801026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19810426T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19811025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19820425T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19821031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19830424T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19831030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19840429T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19841028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19850428T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19851027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19860427T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19861026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19870405T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19871025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19880403T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19881030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19890402T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19891029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19900401T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19901028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19910407T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19911027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19920405T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19921025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19930404T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19931031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19940403T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19941030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19950402T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19951029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19960407T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19961027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19970406T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19971026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19980405T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19981025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19990404T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19991031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20000402T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20001029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20010401T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20011028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20020407T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20021027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20030406T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20031026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20040404T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20041031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20050403T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20051030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20060402T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20061029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20070311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20071104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20080309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20081102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20090308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20091101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20100314T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20101107T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20110313T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20111106T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20120311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20121104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20130310T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20131103T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20140309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20141102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20150308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20151101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20160313T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20161106T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20170312T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20171105T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20180311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20181104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20190310T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20191103T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20200308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20201101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20210314T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20211107T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20220313T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20221106T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20230312T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20231105T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20240310T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20241103T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20250309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20251102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20260308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20261101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20270314T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20271107T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20280312T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20281105T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20290311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20291104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20300310T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20301103T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20310309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20311102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20320314T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20321107T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20330313T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20331106T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20340312T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20341105T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20350311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20351104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20360309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20361102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20370308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20371101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +UID:20140813T153116Z-12176-1000-1065-6@johnny-lubuntu +DTSTAMP:20140813T142829Z +DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/America/Toronto:201408 + 15T110000 +DTEND;TZID=/freeassociation.sourceforge.net/Tzfile/America/Toronto:20140815 + T113000 +TRANSP:OPAQUE +SEQUENCE:2 +SUMMARY:Evo makes a Meeting (fruux HQ) (fruux HQ) +LOCATION:fruux HQ +CLASS:PUBLIC +ORGANIZER;SENT-BY="MAILTO:martin+johnny@fruux.com":MAILTO:martin@fruux.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;RSVP=TRUE + ;SENT-BY="MAILTO:martin+johnny@fruux.com";LANGUAGE=en:MAILTO:martin@fruux. + com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP= + TRUE;LANGUAGE=en:MAILTO:dominik@fruux.com +CREATED:20140813T153211Z +LAST-MODIFIED:20140813T155353Z +END:VEVENT +END:VCALENDAR +ICS; + + $version = \Sabre\VObject\Version::VERSION; + $expectedICS = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +METHOD:REQUEST +BEGIN:VTIMEZONE +TZID:/freeassociation.sourceforge.net/Tzfile/America/Toronto +X-LIC-LOCATION:America/Toronto +BEGIN:STANDARD +TZNAME:EST +DTSTART:19691026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19700426T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19701025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19710425T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19711031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19720430T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19721029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19730429T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19731028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19740428T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19741027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19750427T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19751026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19760425T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19761031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19770424T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19771030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19780430T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19781029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19790429T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19791028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19800427T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19801026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19810426T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19811025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19820425T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19821031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19830424T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19831030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19840429T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19841028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19850428T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19851027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19860427T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19861026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19870405T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19871025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19880403T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19881030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19890402T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19891029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19900401T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19901028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19910407T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19911027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19920405T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19921025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19930404T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19931031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19940403T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19941030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19950402T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19951029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19960407T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19961027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19970406T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19971026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19980405T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19981025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19990404T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19991031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20000402T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20001029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20010401T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20011028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20020407T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20021027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20030406T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20031026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20040404T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20041031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20050403T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20051030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20060402T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20061029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20070311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20071104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20080309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20081102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20090308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20091101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20100314T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20101107T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20110313T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20111106T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20120311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20121104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20130310T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20131103T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20140309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20141102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20150308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20151101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20160313T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20161106T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20170312T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20171105T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20180311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20181104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20190310T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20191103T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20200308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20201101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20210314T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20211107T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20220313T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20221106T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20230312T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20231105T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20240310T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20241103T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20250309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20251102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20260308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20261101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20270314T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20271107T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20280312T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20281105T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20290311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20291104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20300310T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20301103T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20310309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20311102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20320314T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20321107T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20330313T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20331106T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20340312T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20341105T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20350311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20351104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20360309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20361102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20370308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20371101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +UID:20140813T153116Z-12176-1000-1065-6@johnny-lubuntu +DTSTAMP:20140813T142829Z +DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/America/Toronto:201408 + 15T110000 +DTEND;TZID=/freeassociation.sourceforge.net/Tzfile/America/Toronto:20140815 + T113000 +TRANSP:OPAQUE +SEQUENCE:2 +SUMMARY:Evo makes a Meeting (fruux HQ) (fruux HQ) +LOCATION:fruux HQ +CLASS:PUBLIC +ORGANIZER;SENT-BY="MAILTO:martin+johnny@fruux.com":MAILTO:martin@fruux.com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;RSVP=TRUE + ;SENT-BY="MAILTO:martin+johnny@fruux.com";LANGUAGE=en:MAILTO:martin@fruux. + com +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP= + TRUE;LANGUAGE=en:MAILTO:dominik@fruux.com +CREATED:20140813T153211Z +LAST-MODIFIED:20140813T155353Z +END:VEVENT +END:VCALENDAR +ICS; + + $expected = array( + array( + 'uid' => '20140813T153116Z-12176-1000-1065-6@johnny-lubuntu', + 'method' => 'REQUEST', + 'sender' => 'mailto:martin@fruux.com', + 'senderName' => null, + 'recipient' => 'mailto:dominik@fruux.com', + 'recipientName' => null, + 'message' => $expectedICS, + ) + ); + $this->parse(null, $ics, $expected, 'mailto:martin@fruux.com'); + + } + + /** + * This is an event originally from evolution, then parsed by sabredav and + * again mangled by iCal. This triggered a few bugs related to email + * address scheme casing. + */ + public function testAttendeeModify() { + + $old = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject 3.3.1//EN +CALSCALE:GREGORIAN +BEGIN:VTIMEZONE +TZID:/freeassociation.sourceforge.net/Tzfile/America/Toronto +X-LIC-LOCATION:America/Toronto +BEGIN:STANDARD +TZNAME:EST +DTSTART:19691026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19700426T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19701025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19710425T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19711031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19720430T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19721029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19730429T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19731028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19740428T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19741027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19750427T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19751026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19760425T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19761031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19770424T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19771030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19780430T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19781029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19790429T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19791028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19800427T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19801026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19810426T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19811025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19820425T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19821031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19830424T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19831030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19840429T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19841028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19850428T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19851027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19860427T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19861026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19870405T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19871025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19880403T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19881030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19890402T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19891029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19900401T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19901028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19910407T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19911027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19920405T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19921025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19930404T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19931031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19940403T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19941030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19950402T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19951029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19960407T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19961027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19970406T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19971026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19980405T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19981025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19990404T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19991031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20000402T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20001029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20010401T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20011028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20020407T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20021027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20030406T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20031026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20040404T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20041031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20050403T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20051030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20060402T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20061029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20070311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20071104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20080309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20081102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20090308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20091101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20100314T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20101107T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20110313T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20111106T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20120311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20121104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20130310T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20131103T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20140309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20141102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20150308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20151101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20160313T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20161106T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20170312T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20171105T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20180311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20181104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20190310T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20191103T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20200308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20201101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20210314T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20211107T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20220313T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20221106T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20230312T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20231105T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20240310T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20241103T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20250309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20251102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20260308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20261101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20270314T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20271107T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20280312T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20281105T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20290311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20291104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20300310T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20301103T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20310309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20311102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20320314T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20321107T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20330313T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20331106T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20340312T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20341105T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20350311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20351104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20360309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20361102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20370308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20371101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +UID:20140813T212317Z-6646-1000-1221-23@evert-ubuntu +DTSTAMP:20140813T212221Z +DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/America/Toronto:201408 + 13T180000 +DTEND;TZID=/freeassociation.sourceforge.net/Tzfile/America/Toronto:20140813 + T200000 +TRANSP:OPAQUE +SEQUENCE:4 +SUMMARY:Testing evolution +LOCATION:Online +CLASS:PUBLIC +ORGANIZER:MAILTO:o@example.org +CREATED:20140813T212510Z +LAST-MODIFIED:20140813T212541Z +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;RSVP=TRUE;LANGUAGE=en:MAILTO:o@example.org +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;LANGUAGE=en:MAILTO:a1@example.org +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;LANGUAGE=en:MAILTO:a2@example.org +ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE;LANGUAGE=en:MAILTO:a3@example.org +STATUS:CANCELLED +END:VEVENT +END:VCALENDAR +ICS; + + $new = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Apple Inc.//Mac OS X 10.9.4//EN +CALSCALE:GREGORIAN +BEGIN:VTIMEZONE +TZID:America/Toronto +BEGIN:DAYLIGHT +TZOFFSETFROM:-0500 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU +DTSTART:20070311T020000 +TZNAME:EDT +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:-0400 +RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU +DTSTART:20071104T020000 +TZNAME:EST +TZOFFSETTO:-0500 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +TRANSP:OPAQUE +DTEND;TZID=America/Toronto:20140813T200000 +ORGANIZER:MAILTO:o@example.org +UID:20140813T212317Z-6646-1000-1221-23@evert-ubuntu +DTSTAMP:20140813T212221Z +LOCATION:Online +STATUS:CANCELLED +SEQUENCE:4 +CLASS:PUBLIC +SUMMARY:Testing evolution +LAST-MODIFIED:20140813T212541Z +DTSTART;TZID=America/Toronto:20140813T180000 +CREATED:20140813T212510Z +ATTENDEE;CUTYPE=INDIVIDUAL;LANGUAGE=en;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:MAILTO:a2@example.org +ATTENDEE;CUTYPE=INDIVIDUAL;LANGUAGE=en;PARTSTAT=ACCEPTED;ROLE=REQ-PARTICIPANT;RSVP=TRUE:MAILTO:o@example.org +ATTENDEE;CUTYPE=INDIVIDUAL;LANGUAGE=en;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:MAILTO:a1@example.org +ATTENDEE;CUTYPE=INDIVIDUAL;LANGUAGE=en;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:MAILTO:a3@example.org +END:VEVENT +END:VCALENDAR +ICS; + + $this->parse($old, $new, array(), 'mailto:a1@example.org'); + + + } + + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/MessageTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/MessageTest.php new file mode 100644 index 0000000..735d9e9 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ITip/MessageTest.php @@ -0,0 +1,32 @@ +<?php + +namespace Sabre\VObject\ITip; + +class MessageTest extends \PHPUnit_Framework_TestCase { + + public function testNoScheduleStatus() { + + $message = new Message(); + $this->assertFalse($message->getScheduleStatus()); + + } + + public function testScheduleStatus() { + + $message = new Message(); + $message->scheduleStatus = '1.2;Delivered'; + + $this->assertEquals('1.2', $message->getScheduleStatus()); + + } + + public function testUnexpectedScheduleStatus() { + + $message = new Message(); + $message->scheduleStatus = '9.9.9'; + + $this->assertEquals('9.9.9', $message->getScheduleStatus()); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Issue153Test.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Issue153Test.php new file mode 100644 index 0000000..1cc14c1 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Issue153Test.php @@ -0,0 +1,14 @@ +<?php + +namespace Sabre\VObject; + +class Issue153Test extends \PHPUnit_Framework_TestCase { + + function testRead() { + + $obj = Reader::read(file_get_contents(dirname(__FILE__) . '/issue153.vcf')); + $this->assertEquals('Test Benutzer', (string)$obj->fn); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Issue26Test.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Issue26Test.php new file mode 100644 index 0000000..f080f39 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Issue26Test.php @@ -0,0 +1,36 @@ +<?php + +namespace Sabre\VObject; + +use + DateTime, + DateTimeZone; + +class Issue26Test extends \PHPUnit_Framework_TestCase { + + /** + * @expectedException \InvalidArgumentException + */ + function testExpand() { + + $input = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:bae5d57a98 +RRULE:FREQ=MONTHLY;BYDAY=0MO,0TU,0WE,0TH,0FR;INTERVAL=1 +DTSTART;VALUE=DATE:20130401 +DTEND;VALUE=DATE:20130402 +END:VEVENT +END:VCALENDAR +ICS; + + $vcal = Reader::read($input); + $this->assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + + $it = new Recur\EventIterator($vcal, 'bae5d57a98'); + iterator_to_array($it); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Issue36WorkAroundTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Issue36WorkAroundTest.php new file mode 100644 index 0000000..cc98716 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Issue36WorkAroundTest.php @@ -0,0 +1,39 @@ +<?php + +namespace Sabre\VObject; + +class Issue36WorkAroundTest extends \PHPUnit_Framework_TestCase { + + function testWorkaround() { + + // See https://github.com/fruux/sabre-vobject/issues/36 + $event = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +SUMMARY:Titel +SEQUENCE:1 +TRANSP:TRANSPARENT +RRULE:FREQ=YEARLY +LAST-MODIFIED:20130323T225737Z +DTSTAMP:20130323T225737Z +UID:1833bd44-188b-405c-9f85-1a12105318aa +CATEGORIES:Jubiläum +X-MOZ-GENERATION:3 +RECURRENCE-ID;RANGE=THISANDFUTURE;VALUE=DATE:20131013 +DTSTART;VALUE=DATE:20131013 +CREATED:20100721T121914Z +DURATION:P1D +END:VEVENT +END:VCALENDAR +ICS; + + $obj = Reader::read($event); + + // If this does not throw an exception, it's all good. + $it = new Recur\EventIterator($obj,'1833bd44-188b-405c-9f85-1a12105318aa'); + $this->assertInstanceOf('Sabre\\VObject\\Recur\EventIterator', $it); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Issue40Test.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Issue40Test.php new file mode 100644 index 0000000..92f04ff --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Issue40Test.php @@ -0,0 +1,30 @@ +<?php + +namespace Sabre\VObject; + +/** + * This test is created to handle the issues brought forward by issue 40. + * + * https://github.com/fruux/sabre-vobject/issues/40 + */ +class Issue40Test extends \PHPUnit_Framework_TestCase { + + function testEncode() { + + $card = new Component\VCard(); + $card->add('N', array('van der Harten', array('Rene','J.'), "", 'Sir','R.D.O.N.'), array('SORT-AS' => array('Harten','Rene'))); + + $expected = implode("\r\n", array( + "BEGIN:VCARD", + "VERSION:3.0", + "PRODID:-//Sabre//Sabre VObject " . Version::VERSION . '//EN', + "N;SORT-AS=Harten,Rene:van der Harten;Rene,J.;;Sir;R.D.O.N.", + "END:VCARD", + "" + )); + + $this->assertEquals($expected, $card->serialize()); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Issue64Test.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Issue64Test.php new file mode 100644 index 0000000..986a248 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Issue64Test.php @@ -0,0 +1,19 @@ +<?php + +namespace Sabre\VObject; + +class Issue64Test extends \PHPUnit_Framework_TestCase { + + function testRead() { + + $vcard = Reader::read(file_get_contents(dirname(__FILE__) . '/issue64.vcf')); + $vcard = $vcard->convert(\Sabre\VObject\Document::VCARD30); + $vcard = $vcard->serialize(); + + $converted = Reader::read($vcard); + + $this->assertInstanceOf('Sabre\\VObject\\Component\\VCard', $converted); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Issue96Test.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Issue96Test.php new file mode 100644 index 0000000..4300b86 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Issue96Test.php @@ -0,0 +1,24 @@ +<?php + +namespace Sabre\VObject; + +class Issue96Test extends \PHPUnit_Framework_TestCase { + + function testRead() { + + $input = <<<VCF +BEGIN:VCARD +VERSION:2.1 +SOURCE:Yahoo Contacts (http://contacts.yahoo.com) +URL;CHARSET=utf-8;ENCODING=QUOTED-PRINTABLE:= +http://www.example.org +END:VCARD +VCF; + + $vcard = Reader::read($input, Reader::OPTION_FORGIVING); + $this->assertInstanceOf('Sabre\\VObject\\Component\\VCard', $vcard); + $this->assertEquals("http://www.example.org", $vcard->url->getValue()); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/JCalTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/JCalTest.php new file mode 100644 index 0000000..f23165b --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/JCalTest.php @@ -0,0 +1,150 @@ +<?php + +namespace Sabre\VObject; + +class JCalTest extends \PHPUnit_Framework_TestCase { + + function testToJCal() { + + $cal = new Component\VCalendar(); + + $event = $cal->add('VEVENT', array( + "UID" => "foo", + "DTSTART" => new \DateTime("2013-05-26 18:10:00Z"), + "DURATION" => "P1D", + "CATEGORIES" => array('home', 'testing'), + "CREATED" => new \DateTime("2013-05-26 18:10:00Z"), + + "ATTENDEE" => "mailto:armin@example.org", + "GEO" => array(51.96668, 7.61876), + "SEQUENCE" => 5, + "FREEBUSY" => array("20130526T210213Z/PT1H", "20130626T120000Z/20130626T130000Z"), + "URL" => "http://example.org/", + "TZOFFSETFROM" => "+05:00", + "RRULE" => array('FREQ' => 'WEEKLY', 'BYDAY' => array('MO','TU')), + )); + + // Modifying DTSTART to be a date-only. + $event->dtstart['VALUE'] = 'DATE'; + $event->add("X-BOOL", true, array('VALUE' => 'BOOLEAN')); + $event->add("X-TIME", "08:00:00", array('VALUE' => 'TIME')); + $event->add("ATTACH", "attachment", array('VALUE' => 'BINARY')); + $event->add("ATTENDEE", "mailto:dominik@example.org", array("CN" => "Dominik", "PARTSTAT" => "DECLINED")); + + $event->add('REQUEST-STATUS', array("2.0", "Success")); + $event->add('REQUEST-STATUS', array("3.7", "Invalid Calendar User", "ATTENDEE:mailto:jsmith@example.org")); + + $event->add('DTEND', '20150108T133000'); + + $expected = array( + "vcalendar", + array( + array( + "version", + new \StdClass(), + "text", + "2.0" + ), + array( + "prodid", + new \StdClass(), + "text", + "-//Sabre//Sabre VObject " . Version::VERSION . "//EN", + ), + array( + "calscale", + new \StdClass(), + "text", + "GREGORIAN" + ), + ), + array( + array("vevent", + array( + array( + "uid", new \StdClass(), "text", "foo", + ), + array( + "dtstart", new \StdClass(), "date", "2013-05-26", + ), + array( + "duration", new \StdClass(), "duration", "P1D", + ), + array( + "categories", new \StdClass(), "text", "home", "testing", + ), + array( + "created", new \StdClass(), "date-time", "2013-05-26T18:10:00Z", + ), + + array( + "attendee", new \StdClass(), "cal-address", "mailto:armin@example.org", + ), + array( + "geo", new \StdClass(), "float", array(51.96668, 7.61876), + ), + array( + "sequence", new \StdClass(), "integer", 5 + ), + array( + "freebusy", new \StdClass(), "period", array("2013-05-26T21:02:13", "PT1H"), array("2013-06-26T12:00:00", "2013-06-26T13:00:00"), + ), + array( + "url", new \StdClass(), "uri", "http://example.org/", + ), + array( + "tzoffsetfrom", new \StdClass(), "utc-offset", "+05:00", + ), + array( + "rrule", new \StdClass(), "recur", array( + 'freq' => 'WEEKLY', + 'byday' => array('MO', 'TU'), + ), + ), + array( + "x-bool", new \StdClass(), "boolean", true + ), + array( + "x-time", new \StdClass(), "time", "08:00:00", + ), + array( + "attach", new \StdClass(), "binary", base64_encode('attachment') + ), + array( + "attendee", + (object)array( + "cn" => "Dominik", + "partstat" => "DECLINED", + ), + "cal-address", + "mailto:dominik@example.org" + ), + array( + "request-status", + new \StdClass(), + "text", + array("2.0", "Success"), + ), + array( + "request-status", + new \StdClass(), + "text", + array("3.7", "Invalid Calendar User", "ATTENDEE:mailto:jsmith@example.org"), + ), + array( + 'dtend', + new \StdClass(), + "date-time", + "2015-01-08T13:30:00", + ), + ), + array(), + ) + ), + ); + + $this->assertEquals($expected, $cal->jsonSerialize()); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/JCardTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/JCardTest.php new file mode 100644 index 0000000..9bd0f9a --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/JCardTest.php @@ -0,0 +1,195 @@ +<?php + +namespace Sabre\VObject; + +class JCardTest extends \PHPUnit_Framework_TestCase { + + function testToJCard() { + + $card = new Component\VCard(array( + "VERSION" => "4.0", + "UID" => "foo", + "BDAY" => "19850407", + "REV" => "19951031T222710Z", + "LANG" => "nl", + "N" => array("Last", "First", "Middle", "", ""), + "item1.TEL" => "+1 555 123456", + "item1.X-AB-LABEL" => "Walkie Talkie", + "ADR" => array( + "", + "", + array("My Street", "Left Side", "Second Shack"), + "Hometown", + "PA", + "18252", + "U.S.A", + ), + )); + + $card->add('BDAY', '1979-12-25', array('VALUE' => 'DATE', 'X-PARAM' => array(1,2))); + $card->add('BDAY', '1979-12-25T02:00:00', array('VALUE' => 'DATE-TIME')); + + + $card->add('X-TRUNCATED', '--1225', array('VALUE' => 'DATE')); + $card->add('X-TIME-LOCAL', '123000', array('VALUE' => 'TIME')); + $card->add('X-TIME-UTC', '12:30:00Z', array('VALUE' => 'TIME')); + $card->add('X-TIME-OFFSET', '12:30:00-08:00', array('VALUE' => 'TIME')); + $card->add('X-TIME-REDUCED', '23', array('VALUE' => 'TIME')); + $card->add('X-TIME-TRUNCATED', '--30', array('VALUE' => 'TIME')); + + $card->add('X-KARMA-POINTS', '42', array('VALUE' => 'INTEGER')); + $card->add('X-GRADE', '1.3', array('VALUE' => 'FLOAT')); + + $card->add('TZ', '-05:00', array('VALUE' => 'UTC-OFFSET')); + + $expected = array( + "vcard", + array( + array( + "version", + new \StdClass(), + "text", + "4.0" + ), + array( + "prodid", + new \StdClass(), + "text", + "-//Sabre//Sabre VObject " . Version::VERSION . "//EN", + ), + array( + "uid", + new \StdClass(), + "text", + "foo", + ), + array( + "bday", + new \StdClass(), + "date-and-or-time", + "1985-04-07", + ), + array( + "rev", + new \StdClass(), + "timestamp", + "1995-10-31T22:27:10Z", + ), + array( + "lang", + new \StdClass(), + "language-tag", + "nl", + ), + array( + "n", + new \StdClass(), + "text", + array("Last", "First", "Middle", "", ""), + ), + array( + "tel", + (object)array( + "group" => "item1", + ), + "text", + "+1 555 123456", + ), + array( + "x-ab-label", + (object)array( + "group" => "item1", + ), + "unknown", + "Walkie Talkie", + ), + array( + "adr", + new \StdClass(), + "text", + array( + "", + "", + array("My Street", "Left Side", "Second Shack"), + "Hometown", + "PA", + "18252", + "U.S.A", + ), + ), + array( + "bday", + (object)array( + 'x-param' => array(1,2), + ), + "date", + "1979-12-25", + ), + array( + "bday", + new \StdClass(), + "date-time", + "1979-12-25T02:00:00", + ), + array( + "x-truncated", + new \StdClass(), + "date", + "--12-25", + ), + array( + "x-time-local", + new \StdClass(), + "time", + "12:30:00" + ), + array( + "x-time-utc", + new \StdClass(), + "time", + "12:30:00Z" + ), + array( + "x-time-offset", + new \StdClass(), + "time", + "12:30:00-08:00" + ), + array( + "x-time-reduced", + new \StdClass(), + "time", + "23" + ), + array( + "x-time-truncated", + new \StdClass(), + "time", + "--30" + ), + array( + "x-karma-points", + new \StdClass(), + "integer", + 42 + ), + array( + "x-grade", + new \StdClass(), + "float", + 1.3 + ), + array( + "tz", + new \StdClass(), + "utc-offset", + "-05:00", + ), + ), + ); + + $this->assertEquals($expected, $card->jsonSerialize()); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/LineFoldingIssueTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/LineFoldingIssueTest.php new file mode 100644 index 0000000..47fef1c --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/LineFoldingIssueTest.php @@ -0,0 +1,23 @@ +<?php + +namespace Sabre\VObject; + +class LineFoldingIssueTest extends \PHPUnit_Framework_TestCase { + + function testRead() { + + $event = <<<ICS +BEGIN:VCALENDAR\r +BEGIN:VEVENT\r +DESCRIPTION:TEST\\n\\n \\n\\nTEST\\n\\n \\n\\nTEST\\n\\n \\n\\nTEST\\n\\nTEST\\nTEST, TEST\r +END:VEVENT\r +END:VCALENDAR\r + +ICS; + + $obj = Reader::read($event); + $this->assertEquals($event, $obj->serialize()); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ParameterTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ParameterTest.php new file mode 100644 index 0000000..96f016d --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ParameterTest.php @@ -0,0 +1,135 @@ +<?php + +namespace Sabre\VObject; + +class ParameterTest extends \PHPUnit_Framework_TestCase { + + function testSetup() { + + $cal = new Component\VCalendar(); + + $param = new Parameter($cal, 'name','value'); + $this->assertEquals('NAME',$param->name); + $this->assertEquals('value',$param->getValue()); + + } + + function testSetupNameLess() { + + $card = new Component\VCard(); + + $param = new Parameter($card, null,'URL'); + $this->assertEquals('VALUE',$param->name); + $this->assertEquals('URL',$param->getValue()); + $this->assertTrue($param->noName); + + } + + function testModify() { + + $cal = new Component\VCalendar(); + + $param = new Parameter($cal, 'name', null); + $param->addValue(1); + $this->assertEquals(array(1), $param->getParts()); + + $param->setParts(array(1,2)); + $this->assertEquals(array(1,2), $param->getParts()); + + $param->addValue(3); + $this->assertEquals(array(1,2,3), $param->getParts()); + + $param->setValue(4); + $param->addValue(5); + $this->assertEquals(array(4,5), $param->getParts()); + + } + + function testCastToString() { + + $cal = new Component\VCalendar(); + $param = new Parameter($cal, 'name', 'value'); + $this->assertEquals('value',$param->__toString()); + $this->assertEquals('value',(string)$param); + + } + + function testCastNullToString() { + + $cal = new Component\VCalendar(); + $param = new Parameter($cal, 'name', null); + $this->assertEquals('',$param->__toString()); + $this->assertEquals('',(string)$param); + + } + + function testSerialize() { + + $cal = new Component\VCalendar(); + $param = new Parameter($cal, 'name', 'value'); + $this->assertEquals('NAME=value',$param->serialize()); + + } + + function testSerializeEmpty() { + + $cal = new Component\VCalendar(); + $param = new Parameter($cal, 'name', null); + $this->assertEquals('NAME=',$param->serialize()); + + } + + function testSerializeComplex() { + + $cal = new Component\VCalendar(); + $param = new Parameter($cal, 'name',array("val1", "val2;", "val3^", "val4\n", "val5\"")); + $this->assertEquals('NAME=val1,"val2;","val3^^","val4^n","val5^\'"',$param->serialize()); + + } + + /** + * iCal 7.0 (OSX 10.9) has major issues with the EMAIL property, when the + * value contains a plus sign, and it's not quoted. + * + * So we specifically added support for that. + */ + function testSerializePlusSign() { + + $cal = new Component\VCalendar(); + $param = new Parameter($cal, 'EMAIL',"user+something@example.org"); + $this->assertEquals('EMAIL="user+something@example.org"',$param->serialize()); + + } + + function testIterate() { + + $cal = new Component\VCalendar(); + + $param = new Parameter($cal, 'name', array(1,2,3,4)); + $result = array(); + + foreach($param as $value) { + $result[] = $value; + } + + $this->assertEquals(array(1,2,3,4), $result); + + } + + function testSerializeColon() { + + $cal = new Component\VCalendar(); + $param = new Parameter($cal, 'name','va:lue'); + $this->assertEquals('NAME="va:lue"',$param->serialize()); + + } + + function testSerializeSemiColon() { + + $cal = new Component\VCalendar(); + $param = new Parameter($cal, 'name','va;lue'); + $this->assertEquals('NAME="va;lue"',$param->serialize()); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Parser/JsonTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Parser/JsonTest.php new file mode 100644 index 0000000..adbbda3 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Parser/JsonTest.php @@ -0,0 +1,395 @@ +<?php + +namespace Sabre\VObject\Parser; + +use + Sabre\VObject; + +class JsonTest extends \PHPUnit_Framework_TestCase { + + function testRoundTripJCard() { + + $input = array( + "vcard", + array( + array( + "version", + new \StdClass(), + "text", + "4.0" + ), + array( + "prodid", + new \StdClass(), + "text", + "-//Sabre//Sabre VObject " . VObject\Version::VERSION . "//EN", + ), + array( + "uid", + new \StdClass(), + "text", + "foo", + ), + array( + "bday", + new \StdClass(), + "date-and-or-time", + "1985-04-07", + ), + array( + "rev", + new \StdClass(), + "timestamp", + "1995-10-31T22:27:10Z", + ), + array( + "lang", + new \StdClass(), + "language-tag", + "nl", + ), + array( + "n", + new \StdClass(), + "text", + array("Last", "First", "Middle", "", ""), + ), + array( + "tel", + (object)array( + "group" => "item1", + ), + "text", + "+1 555 123456", + ), + array( + "x-ab-label", + (object)array( + "group" => "item1", + ), + "unknown", + "Walkie Talkie", + ), + array( + "adr", + new \StdClass(), + "text", + array( + "", + "", + array("My Street", "Left Side", "Second Shack"), + "Hometown", + "PA", + "18252", + "U.S.A", + ), + ), + array( + "bday", + (object)array( + 'x-param' => array(1,2), + ), + "date", + "1979-12-25", + ), + array( + "bday", + new \StdClass(), + "date-time", + "1979-12-25T02:00:00", + ), + array( + "x-truncated", + new \StdClass(), + "date", + "--12-25", + ), + array( + "x-time-local", + new \StdClass(), + "time", + "12:30:00" + ), + array( + "x-time-utc", + new \StdClass(), + "time", + "12:30:00Z" + ), + array( + "x-time-offset", + new \StdClass(), + "time", + "12:30:00-08:00" + ), + array( + "x-time-reduced", + new \StdClass(), + "time", + "23" + ), + array( + "x-time-truncated", + new \StdClass(), + "time", + "--30" + ), + array( + "x-karma-points", + new \StdClass(), + "integer", + 42 + ), + array( + "x-grade", + new \StdClass(), + "float", + 1.3 + ), + array( + "tz", + new \StdClass(), + "utc-offset", + "-05:00", + ), + ), + ); + + $parser = new Json(json_encode($input)); + $vobj = $parser->parse(); + + $version = VObject\Version::VERSION; + + + $result = $vobj->serialize(); + $expected = <<<VCF +BEGIN:VCARD +VERSION:4.0 +PRODID:-//Sabre//Sabre VObject $version//EN +UID:foo +BDAY:1985-04-07 +REV:1995-10-31T22:27:10Z +LANG:nl +N:Last;First;Middle;; +item1.TEL:+1 555 123456 +item1.X-AB-LABEL:Walkie Talkie +ADR:;;My Street,Left Side,Second Shack;Hometown;PA;18252;U.S.A +BDAY;X-PARAM=1,2;VALUE=DATE:1979-12-25 +BDAY;VALUE=DATE-TIME:1979-12-25T02:00:00 +X-TRUNCATED;VALUE=DATE:--12-25 +X-TIME-LOCAL;VALUE=TIME:12:30:00 +X-TIME-UTC;VALUE=TIME:12:30:00Z +X-TIME-OFFSET;VALUE=TIME:12:30:00-08:00 +X-TIME-REDUCED;VALUE=TIME:23 +X-TIME-TRUNCATED;VALUE=TIME:--30 +X-KARMA-POINTS;VALUE=INTEGER:42 +X-GRADE;VALUE=FLOAT:1.3 +TZ;VALUE=UTC-OFFSET:-05:00 +END:VCARD + +VCF; + $this->assertEquals($expected, str_replace("\r", "", $result)); + + $this->assertEquals( + $input, + $vobj->jsonSerialize() + ); + + } + + function testRoundTripJCal() { + + $input = array( + "vcalendar", + array( + array( + "version", + new \StdClass(), + "text", + "2.0" + ), + array( + "prodid", + new \StdClass(), + "text", + "-//Sabre//Sabre VObject " . VObject\Version::VERSION . "//EN", + ), + array( + "calscale", + new \StdClass(), + "text", + "GREGORIAN" + ), + ), + array( + array("vevent", + array( + array( + "uid", new \StdClass(), "text", "foo", + ), + array( + "dtstart", new \StdClass(), "date", "2013-05-26", + ), + array( + "duration", new \StdClass(), "duration", "P1D", + ), + array( + "categories", new \StdClass(), "text", "home", "testing", + ), + array( + "created", new \StdClass(), "date-time", "2013-05-26T18:10:00Z", + ), + array( + "attach", new \StdClass(), "binary", base64_encode('attachment') + ), + array( + "attendee", new \StdClass(), "cal-address", "mailto:armin@example.org", + ), + array( + "geo", new \StdClass(), "float", array(51.96668, 7.61876), + ), + array( + "sequence", new \StdClass(), "integer", 5 + ), + array( + "freebusy", new \StdClass(), "period", array("2013-05-26T21:02:13", "PT1H"), array("2013-06-26T12:00:00", "2013-06-26T13:00:00"), + ), + array( + "url", new \StdClass(), "uri", "http://example.org/", + ), + array( + "tzoffsetfrom", new \StdClass(), "utc-offset", "+05:00", + ), + array( + "rrule", new \StdClass(), "recur", array( + 'freq' => 'WEEKLY', + 'byday' => array('MO', 'TU'), + ), + ), + array( + "x-bool", new \StdClass(), "boolean", true + ), + array( + "x-time", new \StdClass(), "time", "08:00:00", + ), + array( + "attendee", + (object)array( + "cn" => "Dominik", + "partstat" => "DECLINED", + ), + "cal-address", + "mailto:dominik@example.org" + ), + array( + "request-status", + new \StdClass(), + "text", + array("2.0", "Success"), + ), + array( + "request-status", + new \StdClass(), + "text", + array("3.7", "Invalid Calendar User", "ATTENDEE:mailto:jsmith@example.org"), + ), + ), + array( + array("valarm", + array( + array( + "action", new \StdClass(), "text", "DISPLAY", + ), + ), + array(), + ), + ), + ) + ), + ); + + $parser = new Json(json_encode($input)); + $vobj = $parser->parse(); + $result = $vobj->serialize(); + + $version = VObject\Version::VERSION; + + $expected = <<<VCF +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $version//EN +CALSCALE:GREGORIAN +BEGIN:VEVENT +UID:foo +DTSTART;VALUE=DATE:20130526 +DURATION:P1D +CATEGORIES:home,testing +CREATED:20130526T181000Z +ATTACH;VALUE=BINARY:YXR0YWNobWVudA== +ATTENDEE:mailto:armin@example.org +GEO:51.96668;7.61876 +SEQUENCE:5 +FREEBUSY:20130526T210213/PT1H,20130626T120000/20130626T130000 +URL:http://example.org/ +TZOFFSETFROM:+05:00 +RRULE:FREQ=WEEKLY;BYDAY=MO,TU +X-BOOL;VALUE=BOOLEAN:TRUE +X-TIME;VALUE=TIME:08:00:00 +ATTENDEE;CN=Dominik;PARTSTAT=DECLINED:mailto:dominik@example.org +REQUEST-STATUS:2.0;Success +REQUEST-STATUS:3.7;Invalid Calendar User;ATTENDEE:mailto:jsmith@example.org +BEGIN:VALARM +ACTION:DISPLAY +END:VALARM +END:VEVENT +END:VCALENDAR + +VCF; + $this->assertEquals($expected, str_replace("\r", "", $result)); + + $this->assertEquals( + $input, + $vobj->jsonSerialize() + ); + + } + + function testParseStreamArg() { + + $input = array( + "vcard", + array( + array( + "FN", new \StdClass(), 'text', "foo", + ), + ), + ); + + $stream = fopen('php://memory','r+'); + fwrite($stream, json_encode($input)); + rewind($stream); + + $result = VObject\Reader::readJson($stream,0); + $this->assertEquals('foo', $result->FN->getValue()); + + } + + /** + * @expectedException \Sabre\VObject\ParseException + */ + function testParseInvalidData() { + + $json = new Json(); + $input = array( + "vlist", + array( + array( + "FN", new \StdClass(), 'text', "foo", + ), + ), + ); + + $json->parse(json_encode($input), 0); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Parser/MimeDirTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Parser/MimeDirTest.php new file mode 100644 index 0000000..b4ee47c --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Parser/MimeDirTest.php @@ -0,0 +1,21 @@ +<?php + +namespace Sabre\VObject\Parser; + +/** + * Note that most MimeDir related tests can actually be found in the ReaderTest + * class one level up. + */ +class MimeDirTest extends \PHPUnit_Framework_TestCase { + + /** + * @expectedException \Sabre\VObject\ParseException + */ + function testParseError() { + + $mimeDir = new MimeDir(); + $mimeDir->parse(fopen(__FILE__,'a')); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Parser/QuotedPrintableTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Parser/QuotedPrintableTest.php new file mode 100644 index 0000000..a66af69 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Parser/QuotedPrintableTest.php @@ -0,0 +1,108 @@ +<?php + +namespace Sabre\VObject\Parser; + +use + Sabre\VObject\Reader; + +class QuotedPrintableTest extends \PHPUnit_Framework_TestCase { + + function testReadQuotedPrintableSimple() { + + $data = "BEGIN:VCARD\r\nLABEL;ENCODING=QUOTED-PRINTABLE:Aach=65n\r\nEND:VCARD"; + + $result = Reader::read($data); + + $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertEquals('VCARD', $result->name); + $this->assertEquals(1, count($result->children())); + $this->assertEquals("Aachen", $this->getPropertyValue($result->label)); + + } + + function testReadQuotedPrintableNewlineSoft() { + + $data = "BEGIN:VCARD\r\nLABEL;ENCODING=QUOTED-PRINTABLE:Aa=\r\n ch=\r\n en\r\nEND:VCARD"; + $result = Reader::read($data); + + $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertEquals('VCARD', $result->name); + $this->assertEquals(1, count($result->children())); + $this->assertEquals("Aachen", $this->getPropertyValue($result->label)); + + } + + function testReadQuotedPrintableNewlineHard() { + + $data = "BEGIN:VCARD\r\nLABEL;ENCODING=QUOTED-PRINTABLE:Aachen=0D=0A=\r\n Germany\r\nEND:VCARD"; + $result = Reader::read($data); + + $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertEquals('VCARD', $result->name); + $this->assertEquals(1, count($result->children())); + $this->assertEquals("Aachen\r\nGermany", $this->getPropertyValue($result->label)); + + + } + + function testReadQuotedPrintableCompatibilityMS() { + + $data = "BEGIN:VCARD\r\nLABEL;ENCODING=QUOTED-PRINTABLE:Aachen=0D=0A=\r\nDeutschland:okay\r\nEND:VCARD"; + $result = Reader::read($data, Reader::OPTION_FORGIVING); + + $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertEquals('VCARD', $result->name); + $this->assertEquals(1, count($result->children())); + $this->assertEquals("Aachen\r\nDeutschland:okay", $this->getPropertyValue($result->label)); + + } + + function testReadQuotesPrintableCompoundValues() { + + $data = <<<VCF +BEGIN:VCARD +VERSION:2.1 +N:Doe;John;;; +FN:John Doe +ADR;WORK;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:;;M=C3=BCnster = +Str. 1;M=C3=BCnster;;48143;Deutschland +END:VCARD +VCF; + + $result = Reader::read($data, Reader::OPTION_FORGIVING); + $this->assertEquals(array( + '','','Münster Str. 1','Münster','','48143','Deutschland' + ), $result->ADR->getParts()); + + + } + + private function getPropertyValue(\Sabre\VObject\Property $property) { + + return (string)$property; + + /* + $param = $property['encoding']; + if ($param !== null) { + $encoding = strtoupper((string)$param); + if ($encoding === 'QUOTED-PRINTABLE') { + $value = quoted_printable_decode($value); + } else { + throw new Exception(); + } + } + + $param = $property['charset']; + if ($param !== null) { + $charset = strtoupper((string)$param); + if ($charset !== 'UTF-8') { + $value = mb_convert_encoding($value, 'UTF-8', $charset); + } + } else { + $value = StringUtil::convertToUTF8($value); + } + + return $value; + */ + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/BinaryTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/BinaryTest.php new file mode 100644 index 0000000..0a8c79c --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/BinaryTest.php @@ -0,0 +1,19 @@ +<?php + +namespace Sabre\VObject\Property; + +use Sabre\VObject; + +class BinaryTest extends \PHPUnit_Framework_TestCase { + + /** + * @expectedException \InvalidArgumentException + */ + function testMimeDir() { + + $vcard = new VObject\Component\VCard(); + $vcard->add('PHOTO', array('a','b')); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/BooleanTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/BooleanTest.php new file mode 100644 index 0000000..0c885d3 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/BooleanTest.php @@ -0,0 +1,22 @@ +<?php + +namespace Sabre\VObject\Property; + +use Sabre\VObject; + +class BooleanTest extends \PHPUnit_Framework_TestCase { + + function testMimeDir() { + + $input = "BEGIN:VCARD\r\nX-AWESOME;VALUE=BOOLEAN:TRUE\r\nX-SUCKS;VALUE=BOOLEAN:FALSE\r\nEND:VCARD\r\n"; + + $vcard = VObject\Reader::read($input); + $this->assertTrue($vcard->{'X-AWESOME'}->getValue()); + $this->assertFalse($vcard->{'X-SUCKS'}->getValue()); + + $this->assertEquals('BOOLEAN', $vcard->{'X-AWESOME'}->getValueType()); + $this->assertEquals($input, $vcard->serialize()); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/CompoundTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/CompoundTest.php new file mode 100644 index 0000000..448a478 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/CompoundTest.php @@ -0,0 +1,50 @@ +<?php + +namespace Sabre\VObject\Property; + +use Sabre\VObject\Component\VCard; + +class CompoundTest extends \PHPUnit_Framework_TestCase { + + function testSetParts() { + + $arr = array( + 'ABC, Inc.', + 'North American Division', + 'Marketing;Sales', + ); + + $vcard = new VCard(); + $elem = $vcard->createProperty('ORG'); + $elem->setParts($arr); + + $this->assertEquals('ABC\, Inc.;North American Division;Marketing\;Sales', $elem->getValue()); + $this->assertEquals(3, count($elem->getParts())); + $parts = $elem->getParts(); + $this->assertEquals('Marketing;Sales', $parts[2]); + + } + + function testGetParts() { + + $str = 'ABC\, Inc.;North American Division;Marketing\;Sales'; + + $vcard = new VCard(); + $elem = $vcard->createProperty('ORG'); + $elem->setRawMimeDirValue($str); + + $this->assertEquals(3, count($elem->getParts())); + $parts = $elem->getParts(); + $this->assertEquals('Marketing;Sales', $parts[2]); + } + + function testGetPartsNull() { + + $vcard = new VCard(); + $elem = $vcard->createProperty('ORG', null); + + $this->assertEquals(0, count($elem->getParts())); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/FloatTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/FloatTest.php new file mode 100644 index 0000000..9df806e --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/FloatTest.php @@ -0,0 +1,30 @@ +<?php + +namespace Sabre\VObject\Property; + +use Sabre\VObject; + +class FloatTest extends \PHPUnit_Framework_TestCase { + + function testMimeDir() { + + $input = "BEGIN:VCARD\r\nVERSION:4.0\r\nX-FLOAT;VALUE=FLOAT:0.234;1.245\r\nEND:VCARD\r\n"; + $mimeDir = new VObject\Parser\MimeDir($input); + + $result = $mimeDir->parse($input); + + $this->assertInstanceOf('Sabre\VObject\Property\FloatValue', $result->{'X-FLOAT'}); + + $this->assertEquals(array( + 0.234, + 1.245, + ), $result->{'X-FLOAT'}->getParts()); + + $this->assertEquals( + $input, + $result->serialize() + ); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/ICalendar/CalAddressTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/ICalendar/CalAddressTest.php new file mode 100644 index 0000000..168751a --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/ICalendar/CalAddressTest.php @@ -0,0 +1,32 @@ +<?php + +namespace Sabre\VObject\Property\ICalendar; + +class CalAddressTest extends \PHPUnit_Framework_TestCase { + + /** + * @dataProvider values + */ + function testGetNormalizedValue($expected, $input) { + + $vobj = new \Sabre\VObject\Component\VCalendar(); + $property = $vobj->add('ATTENDEE', $input); + + $this->assertEquals( + $expected, + $property->getNormalizedValue() + ); + + } + + function values() { + + return array( + array('mailto:a@b.com', 'mailto:a@b.com'), + array('mailto:a@b.com', 'MAILTO:a@b.com'), + array('/foo/bar', '/foo/bar'), + ); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/ICalendar/DateTimeTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/ICalendar/DateTimeTest.php new file mode 100644 index 0000000..c7ddfb8 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/ICalendar/DateTimeTest.php @@ -0,0 +1,359 @@ +<?php + +namespace Sabre\VObject\Property\ICalendar; + +use Sabre\VObject\Component; +use Sabre\VObject\Component\VCalendar; + + +class DateTimeTest extends \PHPUnit_Framework_TestCase { + + protected $vcal; + + function setUp() { + + $this->vcal = new VCalendar(); + + } + + function testSetDateTime() { + + $tz = new \DateTimeZone('Europe/Amsterdam'); + $dt = new \DateTime('1985-07-04 01:30:00', $tz); + $dt->setTimeZone($tz); + + $elem = $this->vcal->createProperty('DTSTART'); + $elem->setDateTime($dt); + + $this->assertEquals('19850704T013000', (string)$elem); + $this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']); + $this->assertNull($elem['VALUE']); + + $this->assertTrue($elem->hasTime()); + + } + + function testSetDateTimeLOCAL() { + + $tz = new \DateTimeZone('Europe/Amsterdam'); + $dt = new \DateTime('1985-07-04 01:30:00', $tz); + $dt->setTimeZone($tz); + + $elem = $this->vcal->createProperty('DTSTART'); + $elem->setDateTime($dt, $isFloating = true); + + $this->assertEquals('19850704T013000', (string)$elem); + $this->assertNull($elem['TZID']); + + $this->assertTrue($elem->hasTime()); + } + + function testSetDateTimeUTC() { + + $tz = new \DateTimeZone('GMT'); + $dt = new \DateTime('1985-07-04 01:30:00', $tz); + $dt->setTimeZone($tz); + + $elem = $this->vcal->createProperty('DTSTART'); + $elem->setDateTime($dt); + + $this->assertEquals('19850704T013000Z', (string)$elem); + $this->assertNull($elem['TZID']); + + $this->assertTrue($elem->hasTime()); + } + + function testSetDateTimeLOCALTZ() { + + $tz = new \DateTimeZone('Europe/Amsterdam'); + $dt = new \DateTime('1985-07-04 01:30:00', $tz); + $dt->setTimeZone($tz); + + $elem = $this->vcal->createProperty('DTSTART'); + $elem->setDateTime($dt); + + $this->assertEquals('19850704T013000', (string)$elem); + $this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']); + + $this->assertTrue($elem->hasTime()); + } + + function testSetDateTimeDATE() { + + $tz = new \DateTimeZone('Europe/Amsterdam'); + $dt = new \DateTime('1985-07-04 01:30:00', $tz); + $dt->setTimeZone($tz); + + $elem = $this->vcal->createProperty('DTSTART'); + $elem['VALUE'] = 'DATE'; + $elem->setDateTime($dt); + + $this->assertEquals('19850704', (string)$elem); + $this->assertNull($elem['TZID']); + $this->assertEquals('DATE', (string)$elem['VALUE']); + + $this->assertFalse($elem->hasTime()); + } + + function testSetValue() { + + $tz = new \DateTimeZone('Europe/Amsterdam'); + $dt = new \DateTime('1985-07-04 01:30:00', $tz); + $dt->setTimeZone($tz); + + $elem = $this->vcal->createProperty('DTSTART'); + $elem->setValue($dt); + + $this->assertEquals('19850704T013000', (string)$elem); + $this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']); + $this->assertNull($elem['VALUE']); + + $this->assertTrue($elem->hasTime()); + + } + + function testSetValueArray() { + + $tz = new \DateTimeZone('Europe/Amsterdam'); + $dt1 = new \DateTime('1985-07-04 01:30:00', $tz); + $dt2 = new \DateTime('1985-07-04 02:30:00', $tz); + $dt1->setTimeZone($tz); + $dt2->setTimeZone($tz); + + $elem = $this->vcal->createProperty('DTSTART'); + $elem->setValue(array($dt1, $dt2)); + + $this->assertEquals('19850704T013000,19850704T023000', (string)$elem); + $this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']); + $this->assertNull($elem['VALUE']); + + $this->assertTrue($elem->hasTime()); + + } + + function testSetParts() { + + $tz = new \DateTimeZone('Europe/Amsterdam'); + $dt1 = new \DateTime('1985-07-04 01:30:00', $tz); + $dt2 = new \DateTime('1985-07-04 02:30:00', $tz); + $dt1->setTimeZone($tz); + $dt2->setTimeZone($tz); + + $elem = $this->vcal->createProperty('DTSTART'); + $elem->setParts(array($dt1, $dt2)); + + $this->assertEquals('19850704T013000,19850704T023000', (string)$elem); + $this->assertEquals('Europe/Amsterdam', (string)$elem['TZID']); + $this->assertNull($elem['VALUE']); + + $this->assertTrue($elem->hasTime()); + + } + function testSetPartsStrings() { + + $dt1 = '19850704T013000Z'; + $dt2 = '19850704T023000Z'; + + $elem = $this->vcal->createProperty('DTSTART'); + $elem->setParts(array($dt1, $dt2)); + + $this->assertEquals('19850704T013000Z,19850704T023000Z', (string)$elem); + $this->assertNull($elem['VALUE']); + + $this->assertTrue($elem->hasTime()); + + } + + + function testGetDateTimeCached() { + + $tz = new \DateTimeZone('Europe/Amsterdam'); + $dt = new \DateTime('1985-07-04 01:30:00', $tz); + $dt->setTimeZone($tz); + + $elem = $this->vcal->createProperty('DTSTART'); + $elem->setDateTime($dt); + + $this->assertEquals($elem->getDateTime(), $dt); + + } + + function testGetDateTimeDateNULL() { + + $elem = $this->vcal->createProperty('DTSTART'); + $dt = $elem->getDateTime(); + + $this->assertNull($dt); + + } + + function testGetDateTimeDateDATE() { + + $elem = $this->vcal->createProperty('DTSTART','19850704'); + $dt = $elem->getDateTime(); + + $this->assertInstanceOf('DateTime', $dt); + $this->assertEquals('1985-07-04 00:00:00', $dt->format('Y-m-d H:i:s')); + + } + + function testGetDateTimeDateDATEReferenceTimeZone() { + + $elem = $this->vcal->createProperty('DTSTART','19850704'); + + $tz = new \DateTimeZone('America/Toronto'); + $dt = $elem->getDateTime($tz); + $dt->setTimeZone(new \DateTimeZone('UTC')); + + $this->assertInstanceOf('DateTime', $dt); + $this->assertEquals('1985-07-04 04:00:00', $dt->format('Y-m-d H:i:s')); + + } + + function testGetDateTimeDateFloating() { + + $elem = $this->vcal->createProperty('DTSTART','19850704T013000'); + $dt = $elem->getDateTime(); + + $this->assertInstanceOf('DateTime', $dt); + $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s')); + + } + + function testGetDateTimeDateFloatingReferenceTimeZone() { + + $elem = $this->vcal->createProperty('DTSTART','19850704T013000'); + + $tz = new \DateTimeZone('America/Toronto'); + $dt = $elem->getDateTime($tz); + $dt->setTimeZone(new \DateTimeZone('UTC')); + + $this->assertInstanceOf('DateTime', $dt); + $this->assertEquals('1985-07-04 05:30:00', $dt->format('Y-m-d H:i:s')); + + } + + function testGetDateTimeDateUTC() { + + $elem = $this->vcal->createProperty('DTSTART','19850704T013000Z'); + $dt = $elem->getDateTime(); + + $this->assertInstanceOf('DateTime', $dt); + $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s')); + $this->assertEquals('UTC', $dt->getTimeZone()->getName()); + + } + + function testGetDateTimeDateLOCALTZ() { + + $elem = $this->vcal->createProperty('DTSTART','19850704T013000'); + $elem['TZID'] = 'Europe/Amsterdam'; + + $dt = $elem->getDateTime(); + + $this->assertInstanceOf('DateTime', $dt); + $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s')); + $this->assertEquals('Europe/Amsterdam', $dt->getTimeZone()->getName()); + + } + + /** + * @expectedException LogicException + */ + function testGetDateTimeDateInvalid() { + + $elem = $this->vcal->createProperty('DTSTART','bla'); + $dt = $elem->getDateTime(); + + } + + function testGetDateTimeWeirdTZ() { + + $elem = $this->vcal->createProperty('DTSTART','19850704T013000'); + $elem['TZID'] = '/freeassociation.sourceforge.net/Tzfile/Europe/Amsterdam'; + + + $event = $this->vcal->createComponent('VEVENT'); + $event->add($elem); + + $timezone = $this->vcal->createComponent('VTIMEZONE'); + $timezone->TZID = '/freeassociation.sourceforge.net/Tzfile/Europe/Amsterdam'; + $timezone->{'X-LIC-LOCATION'} = 'Europe/Amsterdam'; + + $this->vcal->add($event); + $this->vcal->add($timezone); + + $dt = $elem->getDateTime(); + + $this->assertInstanceOf('DateTime', $dt); + $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s')); + $this->assertEquals('Europe/Amsterdam', $dt->getTimeZone()->getName()); + + } + + function testGetDateTimeBadTimeZone() { + + $default = date_default_timezone_get(); + date_default_timezone_set('Canada/Eastern'); + + $elem = $this->vcal->createProperty('DTSTART','19850704T013000'); + $elem['TZID'] = 'Moon'; + + + $event = $this->vcal->createComponent('VEVENT'); + $event->add($elem); + + $timezone = $this->vcal->createComponent('VTIMEZONE'); + $timezone->TZID = 'Moon'; + $timezone->{'X-LIC-LOCATION'} = 'Moon'; + + + $this->vcal->add($event); + $this->vcal->add($timezone); + + $dt = $elem->getDateTime(); + + $this->assertInstanceOf('DateTime', $dt); + $this->assertEquals('1985-07-04 01:30:00', $dt->format('Y-m-d H:i:s')); + $this->assertEquals('Canada/Eastern', $dt->getTimeZone()->getName()); + date_default_timezone_set($default); + + } + + function testUpdateValueParameter() { + + $dtStart = $this->vcal->createProperty('DTSTART', new \DateTime('2013-06-07 15:05:00')); + $dtStart['VALUE'] = 'DATE'; + + $this->assertEquals("DTSTART;VALUE=DATE:20130607\r\n", $dtStart->serialize()); + + } + + function testValidate() { + + $exDate = $this->vcal->createProperty('EXDATE', '-00011130T143000Z'); + $messages = $exDate->validate(); + $this->assertEquals(1, count($messages)); + $this->assertEquals(3, $messages[0]['level']); + + } + + /** + * This issue was discovered on the sabredav mailing list. + */ + function testCreateDatePropertyThroughAdd() { + + $vcal = new VCalendar(); + $vevent = $vcal->add('VEVENT'); + + $dtstart = $vevent->add( + 'DTSTART', + new \DateTime('2014-03-07'), + array('VALUE' => 'DATE') + ); + + $this->assertEquals("DTSTART;VALUE=DATE:20140307\r\n", $dtstart->serialize()); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/ICalendar/DurationTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/ICalendar/DurationTest.php new file mode 100644 index 0000000..c5c96e4 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/ICalendar/DurationTest.php @@ -0,0 +1,20 @@ +<?php + +namespace Sabre\VObject\Property\ICalendar; + +use Sabre\VObject\Component\VCalendar; +use Sabre\VObject\Component\VEvent; + +class DurationTest extends \PHPUnit_Framework_TestCase { + + function testGetDateInterval() { + + $vcal = new VCalendar(); + $event = $vcal->add('VEVENT', array('DURATION' => array('PT1H'))); + + $this->assertEquals( + new \DateInterval('PT1H'), + $event->{'DURATION'}->getDateInterval() + ); + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/ICalendar/RecurTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/ICalendar/RecurTest.php new file mode 100644 index 0000000..3eb04e4 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/ICalendar/RecurTest.php @@ -0,0 +1,46 @@ +<?php + +namespace Sabre\VObject\Property\ICalendar; + +use Sabre\VObject\Component\VCalendar; + +class RecurTest extends \PHPUnit_Framework_TestCase { + + function testParts() { + + $vcal = new VCalendar(); + $recur = $vcal->add('RRULE', 'FREQ=Daily'); + + $this->assertInstanceOf('Sabre\VObject\Property\ICalendar\Recur', $recur); + + $this->assertEquals(array('FREQ'=>'DAILY'), $recur->getParts()); + $recur->setParts(array('freq'=>'MONTHLY')); + + $this->assertEquals(array('FREQ'=>'MONTHLY'), $recur->getParts()); + + } + + /** + * @expectedException \InvalidArgumentException + */ + function testSetValueBadVal() { + + $vcal = new VCalendar(); + $recur = $vcal->add('RRULE', 'FREQ=Daily'); + $recur->setValue(new \Exception()); + + } + + function testSetSubParts() { + + $vcal = new VCalendar(); + $recur = $vcal->add('RRULE', array('FREQ'=>'DAILY', 'BYDAY'=>'mo,tu', 'BYMONTH' => array(0,1))); + + $this->assertEquals(array( + 'FREQ'=>'DAILY', + 'BYDAY' => array('MO','TU'), + 'BYMONTH' => array(0,1), + ), $recur->getParts()); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/TextTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/TextTest.php new file mode 100644 index 0000000..8f698f7 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/TextTest.php @@ -0,0 +1,96 @@ +<?php + +namespace Sabre\VObject\Property; + +use Sabre\VObject\Component\VCard; + +class TextTest extends \PHPUnit_Framework_TestCase { + + function assertVCard21serialization($propValue, $expected) { + + $doc = new VCard(array( + 'VERSION'=>'2.1', + 'PROP' => $propValue + ), false); + + // Adding quoted-printable, because we're testing if it gets removed + // automatically. + $doc->PROP['ENCODING'] = 'QUOTED-PRINTABLE'; + $doc->PROP['P1'] = 'V1'; + + + $output = $doc->serialize(); + + + $this->assertEquals("BEGIN:VCARD\r\nVERSION:2.1\r\n$expected\r\nEND:VCARD\r\n", $output); + + } + + function testSerializeVCard21() { + + $this->assertVCard21Serialization( + 'f;oo', + 'PROP;P1=V1:f;oo' + ); + + } + + function testSerializeVCard21Array() { + + $this->assertVCard21Serialization( + array('f;oo','bar'), + 'PROP;P1=V1:f\;oo;bar' + ); + + } + function testSerializeVCard21Fold() { + + $this->assertVCard21Serialization( + str_repeat('x',80), + 'PROP;P1=V1:' . str_repeat('x',64) . "\r\n " . str_repeat('x',16) + ); + + } + + + + function testSerializeQuotedPrintable() { + + $this->assertVCard21Serialization( + "foo\r\nbar", + 'PROP;P1=V1;ENCODING=QUOTED-PRINTABLE:foo=0D=0Abar' + ); + } + + function testSerializeQuotedPrintableFold() { + + $this->assertVCard21Serialization( + "foo\r\nbarxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "PROP;P1=V1;ENCODING=QUOTED-PRINTABLE:foo=0D=0Abarxxxxxxxxxxxxxxxxxxxxxxxxxx=\r\n xxx" + ); + + } + + function testValidateMinimumPropValue() { + + $vcard = <<<IN +BEGIN:VCARD +VERSION:4.0 +UID:foo +FN:Hi! +N:A +END:VCARD +IN; + + $vcard = \Sabre\VObject\Reader::read($vcard); + $this->assertEquals(1, count($vcard->validate())); + + $this->assertEquals(1, count($vcard->N->getParts())); + + $vcard->validate(\Sabre\VObject\Node::REPAIR); + + $this->assertEquals(5, count($vcard->N->getParts())); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/VCard/DateAndOrTimeTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/VCard/DateAndOrTimeTest.php new file mode 100644 index 0000000..0f9a32c --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/VCard/DateAndOrTimeTest.php @@ -0,0 +1,245 @@ +<?php + +namespace Sabre\VObject\Property\VCard; + +use + Sabre\VObject, + Sabre\VObject\Reader; + +class DateAndOrTimeTest extends \PHPUnit_Framework_TestCase { + + /** + * @dataProvider dates + */ + function testGetJsonValue($input, $output) { + + $vcard = new VObject\Component\VCard(); + $prop = $vcard->createProperty('BDAY', $input); + + $this->assertEquals(array($output), $prop->getJsonValue()); + + } + + function dates() { + + return array( + array( + "19961022T140000", + "1996-10-22T14:00:00", + ), + array( + "--1022T1400", + "--10-22T14:00", + ), + array( + "---22T14", + "---22T14", + ), + array( + "19850412", + "1985-04-12", + ), + array( + "1985-04", + "1985-04", + ), + array( + "1985", + "1985", + ), + array( + "--0412", + "--04-12", + ), + array( + "T102200", + "T10:22:00", + ), + array( + "T1022", + "T10:22", + ), + array( + "T10", + "T10", + ), + array( + "T-2200", + "T-22:00", + ), + array( + "T102200Z", + "T10:22:00Z", + ), + array( + "T102200-0800", + "T10:22:00-0800", + ), + array( + "T--00", + "T--00", + ), + ); + + } + + public function testSetParts() { + + $vcard = new VObject\Component\VCard(); + + $prop = $vcard->createProperty('BDAY'); + $prop->setParts(array( + new \DateTime('2014-04-02 18:37:00') + )); + + $this->assertEquals('20140402T183700Z', $prop->getValue()); + + } + + /** + * @expectedException InvalidArgumentException + */ + public function testSetPartsTooMany() { + + $vcard = new VObject\Component\VCard(); + + $prop = $vcard->createProperty('BDAY'); + $prop->setParts(array( + 1, + 2 + )); + + } + + public function testSetPartsString() { + + $vcard = new VObject\Component\VCard(); + + $prop = $vcard->createProperty('BDAY'); + $prop->setParts(array( + "20140402T183700Z" + )); + + $this->assertEquals('20140402T183700Z', $prop->getValue()); + + } + + public function testSetValueDateTime() { + + $vcard = new VObject\Component\VCard(); + + $prop = $vcard->createProperty('BDAY'); + $prop->setValue( + new \DateTime('2014-04-02 18:37:00') + ); + + $this->assertEquals('20140402T183700Z', $prop->getValue()); + + } + + public function testSetDateTimeOffset() { + + $vcard = new VObject\Component\VCard(); + + $prop = $vcard->createProperty('BDAY'); + $prop->setValue( + new \DateTime('2014-04-02 18:37:00', new \DateTimeZone('America/Toronto')) + ); + + $this->assertEquals('20140402T183700-0400', $prop->getValue()); + + } + + public function testGetDateTime() { + + $datetime = new \DateTime('2014-04-02 18:37:00', new \DateTimeZone('America/Toronto')); + + $vcard = new VObject\Component\VCard(); + $prop = $vcard->createProperty('BDAY', $datetime); + + $dt = $prop->getDateTime(); + $this->assertEquals('2014-04-02T18:37:00-04:00', $dt->format('c'), "For some reason this one failed. Current default timezone is: " . date_default_timezone_get()); + + } + + public function testGetDate() { + + $datetime = new \DateTime('2014-04-02'); + + $vcard = new VObject\Component\VCard(); + $prop = $vcard->createProperty('BDAY', $datetime, null, 'DATE'); + + $this->assertEquals('DATE', $prop->getValueType()); + $this->assertEquals('BDAY:20140402', rtrim($prop->serialize())); + + } + + public function testGetDateIncomplete() { + + $datetime = '--0407'; + + $vcard = new VObject\Component\VCard(); + $prop = $vcard->add('BDAY', $datetime); + + $dt = $prop->getDateTime(); + // Note: if the year changes between the last line and the next line of + // code, this test may fail. + // + // If that happens, head outside and have a drink. + $current = new \DateTime('now'); + $year = $current->format('Y'); + + $this->assertEquals($year . '0407', $dt->format('Ymd')); + + } + + public function testGetDateIncompleteFromVCard() { + + $vcard = <<<VCF +BEGIN:VCARD +VERSION:4.0 +BDAY:--0407 +END:VCARD +VCF; + $vcard = Reader::read($vcard); + $prop = $vcard->BDAY; + + $dt = $prop->getDateTime(); + // Note: if the year changes between the last line and the next line of + // code, this test may fail. + // + // If that happens, head outside and have a drink. + $current = new \DateTime('now'); + $year = $current->format('Y'); + + $this->assertEquals($year . '0407', $dt->format('Ymd')); + + } + + public function testValidate() { + + $datetime = '--0407'; + + $vcard = new VObject\Component\VCard(); + $prop = $vcard->add('BDAY', $datetime); + + $this->assertEquals(array(), $prop->validate()); + + } + + public function testValidateBroken() { + + $datetime = '123'; + + $vcard = new VObject\Component\VCard(); + $prop = $vcard->add('BDAY', $datetime); + + $this->assertEquals(array(array( + 'level' => 3, + 'message' => 'The supplied value (123) is not a correct DATE-AND-OR-TIME property', + 'node' => $prop, + )), $prop->validate()); + + } +} + diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/VCard/LanguageTagTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/VCard/LanguageTagTest.php new file mode 100644 index 0000000..8cae8a7 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Property/VCard/LanguageTagTest.php @@ -0,0 +1,48 @@ +<?php + +namespace Sabre\VObject\Property\VCard; + +use Sabre\VObject; + +class LanguageTagTest extends \PHPUnit_Framework_TestCase { + + function testMimeDir() { + + $input = "BEGIN:VCARD\r\nVERSION:4.0\r\nLANG:nl\r\nEND:VCARD\r\n"; + $mimeDir = new VObject\Parser\MimeDir($input); + + $result = $mimeDir->parse($input); + + $this->assertInstanceOf('Sabre\VObject\Property\VCard\LanguageTag', $result->LANG); + + $this->assertEquals('nl', $result->LANG->getValue()); + + $this->assertEquals( + $input, + $result->serialize() + ); + + } + + function testChangeAndSerialize() { + + $input = "BEGIN:VCARD\r\nVERSION:4.0\r\nLANG:nl\r\nEND:VCARD\r\n"; + $mimeDir = new VObject\Parser\MimeDir($input); + + $result = $mimeDir->parse($input); + + $this->assertInstanceOf('Sabre\VObject\Property\VCard\LanguageTag', $result->LANG); + // This replicates what the vcard converter does and triggered a bug in + // the past. + $result->LANG->setValue(array('de')); + + $this->assertEquals('de', $result->LANG->getValue()); + + $expected = "BEGIN:VCARD\r\nVERSION:4.0\r\nLANG:de\r\nEND:VCARD\r\n"; + $this->assertEquals( + $expected, + $result->serialize() + ); + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/PropertyTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/PropertyTest.php new file mode 100644 index 0000000..7e600bc --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/PropertyTest.php @@ -0,0 +1,411 @@ +<?php + +namespace Sabre\VObject; + +use + Sabre\VObject\Component\VCalendar, + Sabre\VObject\Component\VCard; + +class PropertyTest extends \PHPUnit_Framework_TestCase { + + function testToString() { + + $cal = new VCalendar(); + + $property = $cal->createProperty('propname','propvalue'); + $this->assertEquals('PROPNAME', $property->name); + $this->assertEquals('propvalue', $property->__toString()); + $this->assertEquals('propvalue', (string)$property); + $this->assertEquals('propvalue', $property->getValue()); + + } + + function testCreate() { + + $cal = new VCalendar(); + + $params = array( + 'param1' => 'value1', + 'param2' => 'value2', + ); + + $property = $cal->createProperty('propname','propvalue', $params); + + $this->assertEquals('value1', $property['param1']->getValue()); + $this->assertEquals('value2', $property['param2']->getValue()); + + } + + function testSetValue() { + + $cal = new VCalendar(); + + $property = $cal->createProperty('propname','propvalue'); + $property->setValue('value2'); + + $this->assertEquals('PROPNAME', $property->name); + $this->assertEquals('value2', $property->__toString()); + + } + + function testParameterExists() { + + $cal = new VCalendar(); + $property = $cal->createProperty('propname','propvalue'); + $property['paramname'] = 'paramvalue'; + + $this->assertTrue(isset($property['PARAMNAME'])); + $this->assertTrue(isset($property['paramname'])); + $this->assertFalse(isset($property['foo'])); + + } + + function testParameterGet() { + + $cal = new VCalendar(); + $property = $cal->createProperty('propname','propvalue'); + $property['paramname'] = 'paramvalue'; + + $this->assertInstanceOf('Sabre\\VObject\\Parameter',$property['paramname']); + + } + + function testParameterNotExists() { + + $cal = new VCalendar(); + $property = $cal->createProperty('propname','propvalue'); + $property['paramname'] = 'paramvalue'; + + $this->assertInternalType('null',$property['foo']); + + } + + function testParameterMultiple() { + + $cal = new VCalendar(); + $property = $cal->createProperty('propname','propvalue'); + $property['paramname'] = 'paramvalue'; + $property->add('paramname', 'paramvalue'); + + $this->assertInstanceOf('Sabre\\VObject\\Parameter',$property['paramname']); + $this->assertEquals(2,count($property['paramname']->getParts())); + + } + + function testSetParameterAsString() { + + $cal = new VCalendar(); + $property = $cal->createProperty('propname','propvalue'); + $property['paramname'] = 'paramvalue'; + + $this->assertEquals(1,count($property->parameters())); + $this->assertInstanceOf('Sabre\\VObject\\Parameter', $property->parameters['PARAMNAME']); + $this->assertEquals('PARAMNAME',$property->parameters['PARAMNAME']->name); + $this->assertEquals('paramvalue',$property->parameters['PARAMNAME']->getValue()); + + } + + function testUnsetParameter() { + + $cal = new VCalendar(); + $property = $cal->createProperty('propname','propvalue'); + $property['paramname'] = 'paramvalue'; + + unset($property['PARAMNAME']); + $this->assertEquals(0,count($property->parameters())); + + } + + function testSerialize() { + + $cal = new VCalendar(); + $property = $cal->createProperty('propname','propvalue'); + + $this->assertEquals("PROPNAME:propvalue\r\n",$property->serialize()); + + } + + function testSerializeParam() { + + $cal = new VCalendar(); + $property = $cal->createProperty('propname','propvalue', array( + 'paramname' => 'paramvalue', + 'paramname2' => 'paramvalue2', + )); + + $this->assertEquals("PROPNAME;PARAMNAME=paramvalue;PARAMNAME2=paramvalue2:propvalue\r\n",$property->serialize()); + + } + + function testSerializeNewLine() { + + $cal = new VCalendar(); + $property = $cal->createProperty('SUMMARY',"line1\nline2"); + + $this->assertEquals("SUMMARY:line1\\nline2\r\n",$property->serialize()); + + } + + function testSerializeLongLine() { + + $cal = new VCalendar(); + $value = str_repeat('!',200); + $property = $cal->createProperty('propname',$value); + + $expected = "PROPNAME:" . str_repeat('!',66) . "\r\n " . str_repeat('!',74) . "\r\n " . str_repeat('!',60) . "\r\n"; + + $this->assertEquals($expected,$property->serialize()); + + } + + function testSerializeUTF8LineFold() { + + $cal = new VCalendar(); + $value = str_repeat('!',65) . "\xc3\xa4bla"; // inserted umlaut-a + $property = $cal->createProperty('propname', $value); + $expected = "PROPNAME:" . str_repeat('!',65) . "\r\n \xc3\xa4bla\r\n"; + $this->assertEquals($expected, $property->serialize()); + + } + + function testGetIterator() { + + $cal = new VCalendar(); + $it = new ElementList(array()); + $property = $cal->createProperty('propname','propvalue'); + $property->setIterator($it); + $this->assertEquals($it,$property->getIterator()); + + } + + + function testGetIteratorDefault() { + + $cal = new VCalendar(); + $property = $cal->createProperty('propname','propvalue'); + $it = $property->getIterator(); + $this->assertTrue($it instanceof ElementList); + $this->assertEquals(1,count($it)); + + } + + function testAddScalar() { + + $cal = new VCalendar(); + $property = $cal->createProperty('EMAIL'); + + $property->add('myparam','value'); + + $this->assertEquals(1, count($property->parameters())); + + $this->assertTrue($property->parameters['MYPARAM'] instanceof Parameter); + $this->assertEquals('MYPARAM',$property->parameters['MYPARAM']->name); + $this->assertEquals('value',$property->parameters['MYPARAM']->getValue()); + + } + + function testAddParameter() { + + $cal = new VCalendar(); + $prop = $cal->createProperty('EMAIL'); + + $prop->add('MYPARAM','value'); + + $this->assertEquals(1, count($prop->parameters())); + $this->assertEquals('MYPARAM',$prop['myparam']->name); + + } + + function testAddParameterTwice() { + + $cal = new VCalendar(); + $prop = $cal->createProperty('EMAIL'); + + $prop->add('MYPARAM', 'value1'); + $prop->add('MYPARAM', 'value2'); + + $this->assertEquals(1, count($prop->parameters)); + $this->assertEquals(2, count($prop->parameters['MYPARAM']->getParts())); + + $this->assertEquals('MYPARAM',$prop['MYPARAM']->name); + + } + + + function testClone() { + + $cal = new VCalendar(); + $property = $cal->createProperty('EMAIL','value'); + $property['FOO'] = 'BAR'; + + $property2 = clone $property; + + $property['FOO'] = 'BAZ'; + $this->assertEquals('BAR', (string)$property2['FOO']); + + } + + function testCreateParams() { + + $cal = new VCalendar(); + $property = $cal->createProperty('X-PROP','value', array( + 'param1' => 'value1', + 'param2' => array('value2', 'value3') + )); + + $this->assertEquals(1, count($property['PARAM1']->getParts())); + $this->assertEquals(2, count($property['PARAM2']->getParts())); + + } + + function testValidateNonUTF8() { + + $calendar = new VCalendar(); + $property = $calendar->createProperty('X-PROP', "Bla\x00"); + $result = $property->validate(Property::REPAIR); + + $this->assertEquals('Property contained a control character (0x00)', $result[0]['message']); + $this->assertEquals('Bla', $property->getValue()); + + } + + function testValidateControlChars() { + + $s = "chars["; + foreach (array( + 0x7F, 0x5E, 0x5C, 0x3B, 0x3A, 0x2C, 0x22, 0x20, + 0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x19, 0x18, + 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, + 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, + 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, + ) as $c) { + $s .= sprintf('%02X(%c)', $c, $c); + } + $s .= "]end"; + + $calendar = new VCalendar(); + $property = $calendar->createProperty('X-PROP', $s); + $result = $property->validate(Property::REPAIR); + + $this->assertEquals('Property contained a control character (0x7f)', $result[0]['message']); + $this->assertEquals("chars[7F()5E(^)5C(\\\\)3B(\\;)3A(:)2C(\\,)22(\")20( )1F()1E()1D()1C()1B()1A()19()18()17()16()15()14()13()12()11()10()0F()0E()0D()0C()0B()0A(\\n)09( )08()07()06()05()04()03()02()01()00()]end", $property->getRawMimeDirValue()); + + } + + function testValidateBadPropertyName() { + + $calendar = new VCalendar(); + $property = $calendar->createProperty("X_*&PROP*", "Bla"); + $result = $property->validate(Property::REPAIR); + + $this->assertEquals($result[0]['message'], 'The propertyname: X_*&PROP* contains invalid characters. Only A-Z, 0-9 and - are allowed'); + $this->assertEquals('X-PROP', $property->name); + + } + + function testGetValue() { + + $calendar = new VCalendar(); + $property = $calendar->createProperty("SUMMARY", null); + $this->assertEquals(array(), $property->getParts()); + $this->assertNull($property->getValue()); + + $property->setValue(array()); + $this->assertEquals(array(), $property->getParts()); + $this->assertNull($property->getValue()); + + $property->setValue(array(1)); + $this->assertEquals(array(1), $property->getParts()); + $this->assertEquals(1, $property->getValue()); + + $property->setValue(array(1,2)); + $this->assertEquals(array(1,2), $property->getParts()); + $this->assertEquals('1,2', $property->getValue()); + + $property->setValue('str'); + $this->assertEquals(array('str'), $property->getParts()); + $this->assertEquals('str', $property->getValue()); + } + + /** + * ElementList should reject this. + * + * @expectedException \LogicException + */ + function testArrayAccessSetInt() { + + $calendar = new VCalendar(); + $property = $calendar->createProperty("X-PROP", null); + + $calendar->add($property); + $calendar->{'X-PROP'}[0] = 'Something!'; + + } + + /** + * ElementList should reject this. + * + * @expectedException \LogicException + */ + function testArrayAccessUnsetInt() { + + $calendar = new VCalendar(); + $property = $calendar->createProperty("X-PROP", null); + + $calendar->add($property); + unset($calendar->{'X-PROP'}[0]); + + } + + function testValidateBadEncoding() { + + $document = new VCalendar(); + $property = $document->add('X-FOO','value'); + $property['ENCODING'] = 'invalid'; + + $result = $property->validate(); + + $this->assertEquals('ENCODING=INVALID is not valid for this document type.', $result[0]['message']); + $this->assertEquals(1, $result[0]['level']); + + } + + function testValidateBadEncodingVCard4() { + + $document = new VCard(array('VERSION' => '4.0')); + $property = $document->add('X-FOO','value'); + $property['ENCODING'] = 'BASE64'; + + $result = $property->validate(); + + $this->assertEquals('ENCODING parameter is not valid in vCard 4.', $result[0]['message']); + $this->assertEquals(1, $result[0]['level']); + + } + + function testValidateBadEncodingVCard3() { + + $document = new VCard(array('VERSION' => '3.0')); + $property = $document->add('X-FOO','value'); + $property['ENCODING'] = 'BASE64'; + + $result = $property->validate(); + + $this->assertEquals('ENCODING=BASE64 is not valid for this document type.', $result[0]['message']); + $this->assertEquals(1, $result[0]['level']); + + } + + function testValidateBadEncodingVCard21() { + + $document = new VCard(array('VERSION' => '2.1')); + $property = $document->add('X-FOO','value'); + $property['ENCODING'] = 'B'; + + $result = $property->validate(); + + $this->assertEquals('ENCODING=B is not valid for this document type.', $result[0]['message']); + $this->assertEquals(1, $result[0]['level']); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ReaderTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ReaderTest.php new file mode 100644 index 0000000..f530d81 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/ReaderTest.php @@ -0,0 +1,449 @@ +<?php + +namespace Sabre\VObject; + +class ReaderTest extends \PHPUnit_Framework_TestCase { + + function testReadComponent() { + + $data = "BEGIN:VCALENDAR\r\nEND:VCALENDAR"; + + $result = Reader::read($data); + + $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertEquals('VCALENDAR', $result->name); + $this->assertEquals(0, count($result->children)); + + } + function testReadStream() { + + $data = "BEGIN:VCALENDAR\r\nEND:VCALENDAR"; + + $stream = fopen('php://memory', 'r+'); + fwrite($stream, $data); + rewind($stream); + + $result = Reader::read($stream); + + $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertEquals('VCALENDAR', $result->name); + $this->assertEquals(0, count($result->children)); + + } + + function testReadComponentUnixNewLine() { + + $data = "BEGIN:VCALENDAR\nEND:VCALENDAR"; + + $result = Reader::read($data); + + $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertEquals('VCALENDAR', $result->name); + $this->assertEquals(0, count($result->children)); + + } + + function testReadComponentLineFold() { + + $data = "BEGIN:\r\n\tVCALENDAR\r\nE\r\n ND:VCALENDAR"; + + $result = Reader::read($data); + + $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertEquals('VCALENDAR', $result->name); + $this->assertEquals(0, count($result->children)); + + } + + /** + * @expectedException Sabre\VObject\ParseException + */ + function testReadCorruptComponent() { + + $data = "BEGIN:VCALENDAR\r\nEND:FOO"; + + $result = Reader::read($data); + + } + + /** + * @expectedException Sabre\VObject\ParseException + */ + function testReadCorruptSubComponent() { + + $data = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nEND:FOO\r\nEND:VCALENDAR"; + + $result = Reader::read($data); + + } + + function testReadProperty() { + + $data = "BEGIN:VCALENDAR\r\nSUMMARY:propValue\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->SUMMARY; + $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertEquals('SUMMARY', $result->name); + $this->assertEquals('propValue', $result->getValue()); + + } + + function testReadPropertyWithNewLine() { + + $data = "BEGIN:VCALENDAR\r\nSUMMARY:Line1\\nLine2\\NLine3\\\\Not the 4th line!\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->SUMMARY; + $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertEquals('SUMMARY', $result->name); + $this->assertEquals("Line1\nLine2\nLine3\\Not the 4th line!", $result->getValue()); + + } + + function testReadMappedProperty() { + + $data = "BEGIN:VCALENDAR\r\nDTSTART:20110529\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->DTSTART; + $this->assertInstanceOf('Sabre\\VObject\\Property\\ICalendar\\DateTime', $result); + $this->assertEquals('DTSTART', $result->name); + $this->assertEquals('20110529', $result->getValue()); + + } + + function testReadMappedPropertyGrouped() { + + $data = "BEGIN:VCALENDAR\r\nfoo.DTSTART:20110529\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->DTSTART; + $this->assertInstanceOf('Sabre\\VObject\\Property\\ICalendar\\DateTime', $result); + $this->assertEquals('DTSTART', $result->name); + $this->assertEquals('20110529', $result->getValue()); + + } + + /** + * @expectedException Sabre\VObject\ParseException + */ + function testReadBrokenLine() { + + $data = "BEGIN:VCALENDAR\r\nPROPNAME;propValue"; + $result = Reader::read($data); + + } + + function testReadPropertyInComponent() { + + $data = array( + "BEGIN:VCALENDAR", + "PROPNAME:propValue", + "END:VCALENDAR" + ); + + $result = Reader::read(implode("\r\n",$data)); + + $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertEquals('VCALENDAR', $result->name); + $this->assertEquals(1, count($result->children())); + $this->assertInstanceOf('Sabre\\VObject\\Property', $result->children[0]); + $this->assertEquals('PROPNAME', $result->children[0]->name); + $this->assertEquals('propValue', $result->children[0]->getValue()); + + } + + function testReadNestedComponent() { + + $data = array( + "BEGIN:VCALENDAR", + "BEGIN:VTIMEZONE", + "BEGIN:DAYLIGHT", + "END:DAYLIGHT", + "END:VTIMEZONE", + "END:VCALENDAR" + ); + + $result = Reader::read(implode("\r\n",$data)); + + $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertEquals('VCALENDAR', $result->name); + $this->assertEquals(1, count($result->children())); + $this->assertInstanceOf('Sabre\\VObject\\Component', $result->children[0]); + $this->assertEquals('VTIMEZONE', $result->children[0]->name); + $this->assertEquals(1, count($result->children[0]->children())); + $this->assertInstanceOf('Sabre\\VObject\\Component', $result->children[0]->children[0]); + $this->assertEquals('DAYLIGHT', $result->children[0]->children[0]->name); + + + } + + function testReadPropertyParameter() { + + $data = "BEGIN:VCALENDAR\r\nPROPNAME;PARAMNAME=paramvalue:propValue\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->PROPNAME; + + $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertEquals('PROPNAME', $result->name); + $this->assertEquals('propValue', $result->getValue()); + $this->assertEquals(1, count($result->parameters())); + $this->assertEquals('PARAMNAME', $result->parameters['PARAMNAME']->name); + $this->assertEquals('paramvalue', $result->parameters['PARAMNAME']->getValue()); + + } + + function testReadPropertyRepeatingParameter() { + + $data = "BEGIN:VCALENDAR\r\nPROPNAME;N=1;N=2;N=3,4;N=\"5\",6;N=\"7,8\";N=9,10;N=^'11^':propValue\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->PROPNAME; + + $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertEquals('PROPNAME', $result->name); + $this->assertEquals('propValue', $result->getValue()); + $this->assertEquals(1, count($result->parameters())); + $this->assertEquals('N', $result->parameters['N']->name); + $this->assertEquals('1,2,3,4,5,6,7,8,9,10,"11"', $result->parameters['N']->getValue()); + $this->assertEquals(array(1,2,3,4,5,6,"7,8",9,10,'"11"'), $result->parameters['N']->getParts()); + + } + + function testReadPropertyRepeatingNamelessGuessedParameter() { + $data = "BEGIN:VCALENDAR\r\nPROPNAME;WORK;VOICE;PREF:propValue\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->PROPNAME; + + $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertEquals('PROPNAME', $result->name); + $this->assertEquals('propValue', $result->getValue()); + $this->assertEquals(1, count($result->parameters())); + $this->assertEquals('TYPE', $result->parameters['TYPE']->name); + $this->assertEquals('WORK,VOICE,PREF', $result->parameters['TYPE']->getValue()); + $this->assertEquals(array('WORK', 'VOICE', 'PREF'), $result->parameters['TYPE']->getParts()); + + } + + function testReadPropertyNoName() { + + $data = "BEGIN:VCALENDAR\r\nPROPNAME;PRODIGY:propValue\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->PROPNAME; + + $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertEquals('PROPNAME', $result->name); + $this->assertEquals('propValue', $result->getValue()); + $this->assertEquals(1, count($result->parameters())); + $this->assertEquals('TYPE', $result->parameters['TYPE']->name); + $this->assertTrue($result->parameters['TYPE']->noName); + $this->assertEquals('PRODIGY', $result->parameters['TYPE']); + + } + + function testReadPropertyParameterExtraColon() { + + $data = "BEGIN:VCALENDAR\r\nPROPNAME;PARAMNAME=paramvalue:propValue:anotherrandomstring\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->PROPNAME; + + $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertEquals('PROPNAME', $result->name); + $this->assertEquals('propValue:anotherrandomstring', $result->getValue()); + $this->assertEquals(1, count($result->parameters())); + $this->assertEquals('PARAMNAME', $result->parameters['PARAMNAME']->name); + $this->assertEquals('paramvalue', $result->parameters['PARAMNAME']->getValue()); + + } + + function testReadProperty2Parameters() { + + $data = "BEGIN:VCALENDAR\r\nPROPNAME;PARAMNAME=paramvalue;PARAMNAME2=paramvalue2:propValue\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->PROPNAME; + + $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertEquals('PROPNAME', $result->name); + $this->assertEquals('propValue', $result->getValue()); + $this->assertEquals(2, count($result->parameters())); + $this->assertEquals('PARAMNAME', $result->parameters['PARAMNAME']->name); + $this->assertEquals('paramvalue', $result->parameters['PARAMNAME']->getValue()); + $this->assertEquals('PARAMNAME2', $result->parameters['PARAMNAME2']->name); + $this->assertEquals('paramvalue2', $result->parameters['PARAMNAME2']->getValue()); + + } + + function testReadPropertyParameterQuoted() { + + $data = "BEGIN:VCALENDAR\r\nPROPNAME;PARAMNAME=\"paramvalue\":propValue\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->PROPNAME; + + $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertEquals('PROPNAME', $result->name); + $this->assertEquals('propValue', $result->getValue()); + $this->assertEquals(1, count($result->parameters())); + $this->assertEquals('PARAMNAME', $result->parameters['PARAMNAME']->name); + $this->assertEquals('paramvalue', $result->parameters['PARAMNAME']->getValue()); + + } + + function testReadPropertyParameterNewLines() { + + $data = "BEGIN:VCALENDAR\r\nPROPNAME;PARAMNAME=paramvalue1^nvalue2^^nvalue3:propValue\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $result = $result->propname; + + $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertEquals('PROPNAME', $result->name); + $this->assertEquals('propValue', $result->getValue()); + + $this->assertEquals(1, count($result->parameters())); + $this->assertEquals('PARAMNAME', $result->parameters['PARAMNAME']->name); + $this->assertEquals("paramvalue1\nvalue2^nvalue3", $result->parameters['PARAMNAME']->getValue()); + + } + + function testReadPropertyParameterQuotedColon() { + + $data = "BEGIN:VCALENDAR\r\nPROPNAME;PARAMNAME=\"param:value\":propValue\r\nEND:VCALENDAR"; + $result = Reader::read($data); + $result = $result->propname; + + $this->assertInstanceOf('Sabre\\VObject\\Property', $result); + $this->assertEquals('PROPNAME', $result->name); + $this->assertEquals('propValue', $result->getValue()); + $this->assertEquals(1, count($result->parameters())); + $this->assertEquals('PARAMNAME', $result->parameters['PARAMNAME']->name); + $this->assertEquals('param:value', $result->parameters['PARAMNAME']->getValue()); + + } + + function testReadForgiving() { + + $data = array( + "BEGIN:VCALENDAR", + "X_PROP:propValue", + "END:VCALENDAR" + ); + + $caught = false; + try { + $result = Reader::read(implode("\r\n",$data)); + } catch (ParseException $e) { + $caught = true; + } + + $this->assertEquals(true, $caught); + + $result = Reader::read(implode("\r\n",$data), Reader::OPTION_FORGIVING); + + $expected = implode("\r\n", array( + "BEGIN:VCALENDAR", + "X_PROP:propValue", + "END:VCALENDAR", + "" + )); + + $this->assertEquals($expected, $result->serialize()); + + } + + function testReadWithInvalidLine() { + + $data = array( + "BEGIN:VCALENDAR", + "DESCRIPTION:propValue", + "Yes, we've actually seen a file with non-idented property values on multiple lines", + "END:VCALENDAR" + ); + + $caught = false; + try { + $result = Reader::read(implode("\r\n",$data)); + } catch (ParseException $e) { + $caught = true; + } + + $this->assertEquals(true, $caught); + + $result = Reader::read(implode("\r\n",$data), Reader::OPTION_IGNORE_INVALID_LINES); + + $expected = implode("\r\n", array( + "BEGIN:VCALENDAR", + "DESCRIPTION:propValue", + "END:VCALENDAR", + "" + )); + + $this->assertEquals($expected, $result->serialize()); + + } + + /** + * Reported as Issue 32. + * + * @expectedException \Sabre\VObject\ParseException + */ + public function testReadIncompleteFile() { + + $input = <<<ICS +BEGIN:VCALENDAR +VERSION:1.0 +BEGIN:VEVENT +X-FUNAMBOL-FOLDER:DEFAULT_FOLDER +X-FUNAMBOL-ALLDAY:0 +DTSTART:20111017T110000Z +DTEND:20111017T123000Z +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +CATEGORIES: +LOCATION;ENCODING=QUOTED-PRINTABLE;CHARSET=UTF-8:Netviewer Meeting +PRIORITY:1 +STATUS:3 +X-MICROSOFT-CDO-REPLYTIME:20111017T064200Z +SUMMARY;ENCODING=QUOTED-PRINTABLE;CHARSET=UTF-8:Kopieren: test +CLASS:PUBLIC +AALARM: +RRULE: +X-FUNAMBOL-BILLINGINFO: +X-FUNAMBOL-COMPANIES: +X-FUNAMBOL-MILEAGE: +X-FUNAMBOL-NOAGING:0 +ATTENDEE;STATUS=NEEDS ACTION;ENCODING=QUOTED-PRINTABLE;CHARSET=UTF-8:'Heino' heino@test.com +ATTENDEE;STATUS=NEEDS ACTION;ENCODING=QUOTED-PRINTABLE;CHARSET=UTF-8:'Markus' test@test.com +ATTENDEE;STATUS=NEEDS AC +ICS; + + Reader::read($input); + + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testReadBrokenInput() { + + Reader::read(false); + + } + + public function testReadBOM() { + + $data = chr(0xef) . chr(0xbb) . chr(0xbf) . "BEGIN:VCALENDAR\r\nEND:VCALENDAR"; + $result = Reader::read($data); + + $this->assertInstanceOf('Sabre\\VObject\\Component', $result); + $this->assertEquals('VCALENDAR', $result->name); + $this->assertEquals(0, count($result->children)); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/ByMonthInDailyTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/ByMonthInDailyTest.php new file mode 100644 index 0000000..4f3f852 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/ByMonthInDailyTest.php @@ -0,0 +1,59 @@ +<?php + +namespace Sabre\VObject\Recur; + +use + Sabre\VObject\Reader, + DateTime; + +class ByMonthInDailyTest extends \PHPUnit_Framework_TestCase { + + /** + * This tests the expansion of dates with DAILY frequency in RRULE with BYMONTH restrictions + */ + function testExpand() { + + $ics = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Apple Inc.//iCal 4.0.4//EN +CALSCALE:GREGORIAN +BEGIN:VEVENT +TRANSP:OPAQUE +DTEND:20070925T183000Z +UID:uuid +DTSTAMP:19700101T000000Z +LOCATION: +DESCRIPTION: +STATUS:CONFIRMED +SEQUENCE:18 +SUMMARY:Stuff +DTSTART:20070925T160000Z +CREATED:20071004T144642Z +RRULE:FREQ=DAILY;BYMONTH=9,10;BYDAY=SU +END:VEVENT +END:VCALENDAR +ICS; + + $vcal = Reader::read($ics); + $this->assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + + $vcal->expand(new DateTime('2013-09-28'), new DateTime('2014-09-11')); + + foreach ($vcal->VEVENT as $event) { + $dates[] = $event->DTSTART->getValue(); + } + + $expectedDates = array( + "20130929T160000Z", + "20131006T160000Z", + "20131013T160000Z", + "20131020T160000Z", + "20131027T160000Z", + "20140907T160000Z" + ); + + $this->assertEquals($expectedDates, $dates, 'Recursed dates are restricted by month'); + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/BySetPosHangTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/BySetPosHangTest.php new file mode 100644 index 0000000..bf346a5 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/BySetPosHangTest.php @@ -0,0 +1,61 @@ +<?php + +namespace Sabre\VObject\Recur; + +use + Sabre\VObject\Reader, + DateTime; + +class BySetPosHangTest extends \PHPUnit_Framework_TestCase { + + /** + * Using this iCalendar object, including BYSETPOS=-2 causes the iterator + * to hang, as reported in ticket #212. + * + * See: https://github.com/fruux/sabre-vobject/issues/212 + */ + function testExpand() { + + $ics = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject 3.4.2//EN +CALSCALE:GREGORIAN +BEGIN:VEVENT +SUMMARY:Test event 1 +DTSTART;TZID=Europe/Copenhagen:20150101T170000 +RRULE:FREQ=MONTHLY;BYDAY=TH;BYSETPOS=-2 +UID:b4071499-6fe4-418a-83b8-2b8d5ebb38e4 +END:VEVENT +END:VCALENDAR +ICS; + + $vcal = Reader::read($ics); + $this->assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + + $vcal->expand(new DateTime('2015-01-01'), new DateTime('2016-01-01')); + + foreach ($vcal->VEVENT as $event) { + $dates[] = $event->DTSTART->getValue(); + } + + $expectedDates = array( + "20150101T160000Z", + "20150122T160000Z", + "20150219T160000Z", + "20150319T160000Z", + "20150423T150000Z", + "20150521T150000Z", + "20150618T150000Z", + "20150723T150000Z", + "20150820T150000Z", + "20150917T150000Z", + "20151022T150000Z", + "20151119T160000Z", + "20151224T160000Z", + ); + + $this->assertEquals($expectedDates, $dates); + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/ExpandFloatingTimesTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/ExpandFloatingTimesTest.php new file mode 100644 index 0000000..63c91ba --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/ExpandFloatingTimesTest.php @@ -0,0 +1,122 @@ +<?php + +namespace Sabre\VObject\Recur\EventIterator; + +use + DateTime, + DateTimeZone, + Sabre\VObject\Reader; + +class ExpandFloatingTimesTest extends \PHPUnit_Framework_TestCase { + + function testExpand() { + + $input = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foo +DTSTART:20150109T090000 +DTEND:20150109T100000 +RRULE:FREQ=WEEKLY;INTERVAL=1;UNTIL=20191002T070000Z;BYDAY=FR +END:VEVENT +END:VCALENDAR +ICS; + + $vcal = Reader::read($input); + $this->assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + + $vcal->expand(new DateTime('2015-01-01'), new DateTime('2015-01-31')); + + $result = $vcal->serialize(); + + $output = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foo +DTSTART:20150109T090000Z +DTEND:20150109T100000Z +RECURRENCE-ID:20150109T090000Z +END:VEVENT +BEGIN:VEVENT +UID:foo +DTSTART:20150116T090000Z +DTEND:20150116T100000Z +RECURRENCE-ID:20150116T090000Z +END:VEVENT +BEGIN:VEVENT +UID:foo +DTSTART:20150123T090000Z +DTEND:20150123T100000Z +RECURRENCE-ID:20150123T090000Z +END:VEVENT +BEGIN:VEVENT +UID:foo +DTSTART:20150130T090000Z +DTEND:20150130T100000Z +RECURRENCE-ID:20150130T090000Z +END:VEVENT +END:VCALENDAR + +ICS; + $this->assertEquals($output, str_replace("\r", "", $result)); + + } + + function testExpandWithReferenceTimezone() { + + $input = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foo +DTSTART:20150109T090000 +DTEND:20150109T100000 +RRULE:FREQ=WEEKLY;INTERVAL=1;UNTIL=20191002T070000Z;BYDAY=FR +END:VEVENT +END:VCALENDAR +ICS; + + $vcal = Reader::read($input); + $this->assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + + $vcal->expand(new DateTime('2015-01-01'), new DateTime('2015-01-31'), new \DateTimeZone('Europe/Berlin')); + + $result = $vcal->serialize(); + + $output = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foo +DTSTART:20150109T080000Z +DTEND:20150109T090000Z +RECURRENCE-ID:20150109T080000Z +END:VEVENT +BEGIN:VEVENT +UID:foo +DTSTART:20150116T080000Z +DTEND:20150116T090000Z +RECURRENCE-ID:20150116T080000Z +END:VEVENT +BEGIN:VEVENT +UID:foo +DTSTART:20150123T080000Z +DTEND:20150123T090000Z +RECURRENCE-ID:20150123T080000Z +END:VEVENT +BEGIN:VEVENT +UID:foo +DTSTART:20150130T080000Z +DTEND:20150130T090000Z +RECURRENCE-ID:20150130T080000Z +END:VEVENT +END:VCALENDAR + +ICS; + $this->assertEquals($output, str_replace("\r", "", $result)); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/FifthTuesdayProblemTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/FifthTuesdayProblemTest.php new file mode 100644 index 0000000..c1546a0 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/FifthTuesdayProblemTest.php @@ -0,0 +1,54 @@ +<?php + +namespace Sabre\VObject\Recur\EventIterator; + +use Sabre\VObject\Recur; +use Sabre\VObject\Reader; + +class FifthTuesdayProblemTest extends \PHPUnit_Framework_TestCase { + + /** + * A pretty slow test. Had to be marked as 'medium' for phpunit to not die + * after 1 second. Would be good to optimize later. + * + * @medium + */ + function testGetDTEnd() { + + $ics = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Apple Inc.//iCal 4.0.4//EN +CALSCALE:GREGORIAN +BEGIN:VEVENT +TRANSP:OPAQUE +DTEND;TZID=America/New_York:20070925T170000 +UID:uuid +DTSTAMP:19700101T000000Z +LOCATION: +DESCRIPTION: +STATUS:CONFIRMED +SEQUENCE:18 +SUMMARY:Stuff +DTSTART;TZID=America/New_York:20070925T160000 +CREATED:20071004T144642Z +RRULE:FREQ=MONTHLY;INTERVAL=1;UNTIL=20071030T035959Z;BYDAY=5TU +END:VEVENT +END:VCALENDAR +ICS; + + $vObject = Reader::read($ics); + $it = new Recur\EventIterator($vObject, (string)$vObject->VEVENT->UID); + + while($it->valid()) { + $it->next(); + } + + // If we got here, it means we were successful. The bug that was in the + // system before would fail on the 5th tuesday of the month, if the 5th + // tuesday did not exist. + $this->assertTrue(true); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/IncorrectExpandTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/IncorrectExpandTest.php new file mode 100644 index 0000000..6622dbd --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/IncorrectExpandTest.php @@ -0,0 +1,64 @@ +<?php + +namespace Sabre\VObject\Recur\EventIterator; + +use + DateTime, + DateTimeZone, + Sabre\VObject\Reader; + +/** + * This is a unittest for Issue #53. + */ +class RecurrenceIteratorIncorrectExpandTest extends \PHPUnit_Framework_TestCase { + + function testExpand() { + + $input = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foo +DTSTART:20130711T050000Z +DTEND:20130711T053000Z +RRULE:FREQ=DAILY;INTERVAL=1;COUNT=2 +END:VEVENT +BEGIN:VEVENT +UID:foo +DTSTART:20130719T050000Z +DTEND:20130719T053000Z +RECURRENCE-ID:20130712T050000Z +END:VEVENT +END:VCALENDAR +ICS; + + $vcal = Reader::read($input); + $this->assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + + $vcal->expand(new DateTime('2011-01-01'), new DateTime('2014-01-01')); + + $result = $vcal->serialize(); + + $output = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foo +DTSTART:20130711T050000Z +DTEND:20130711T053000Z +RECURRENCE-ID:20130711T050000Z +END:VEVENT +BEGIN:VEVENT +UID:foo +DTSTART:20130719T050000Z +DTEND:20130719T053000Z +RECURRENCE-ID:20130712T050000Z +END:VEVENT +END:VCALENDAR + +ICS; + $this->assertEquals($output, str_replace("\r", "", $result)); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/InfiniteLoopProblemTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/InfiniteLoopProblemTest.php new file mode 100644 index 0000000..c4f115b --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/InfiniteLoopProblemTest.php @@ -0,0 +1,99 @@ +<?php + +namespace Sabre\VObject\Recur\EventIterator; + +use + DateTime, + DateTimeZone, + Sabre\VObject\Component\VCalendar, + Sabre\VObject\Recur; + +class EventIteratorInfiniteLoopProblemTest extends \PHPUnit_Framework_TestCase { + + public function setUp() { + + $this->vcal = new VCalendar(); + + } + + /** + * This bug came from a Fruux customer. This would result in a never-ending + * request. + */ + function testFastForwardTooFar() { + + $ev = $this->vcal->createComponent('VEVENT'); + $ev->UID = 'foobar'; + $ev->DTSTART = '20090420T180000Z'; + $ev->RRULE = 'FREQ=WEEKLY;BYDAY=MO;UNTIL=20090704T205959Z;INTERVAL=1'; + + $this->assertFalse($ev->isInTimeRange(new DateTime('2012-01-01 12:00:00'),new DateTime('3000-01-01 00:00:00'))); + + } + + /** + * Different bug, also likely an infinite loop. + */ + function testYearlyByMonthLoop() { + + $ev = $this->vcal->createComponent('VEVENT'); + $ev->UID = 'uuid'; + $ev->DTSTART = '20120101T154500'; + $ev->DTSTART['TZID'] = 'Europe/Berlin'; + $ev->RRULE = 'FREQ=YEARLY;INTERVAL=1;UNTIL=20120203T225959Z;BYMONTH=2;BYSETPOS=1;BYDAY=SU,MO,TU,WE,TH,FR,SA'; + $ev->DTEND = '20120101T164500'; + $ev->DTEND['TZID'] = 'Europe/Berlin'; + + // This recurrence rule by itself is a yearly rule that should happen + // every february. + // + // The BYDAY part expands this to every day of the month, but the + // BYSETPOS limits this to only the 1st day of the month. Very crazy + // way to specify this, and could have certainly been a lot easier. + $this->vcal->add($ev); + + $it = new Recur\EventIterator($this->vcal,'uuid'); + $it->fastForward(new DateTime('2012-01-29 23:00:00', new DateTimeZone('UTC'))); + + $collect = array(); + + while($it->valid()) { + $collect[] = $it->getDTSTART(); + if ($it->getDTSTART() > new DateTime('2013-02-05 22:59:59', new DateTimeZone('UTC'))) { + break; + } + $it->next(); + + } + + $this->assertEquals( + array(new DateTime('2012-02-01 15:45:00', new DateTimeZone('Europe/Berlin'))), + $collect + ); + + } + + /** + * Something, somewhere produced an ics with an interval set to 0. Because + * this means we increase the current day (or week, month) by 0, this also + * results in an infinite loop. + * + * @expectedException InvalidArgumentException + * @return void + */ + function testZeroInterval() { + + $ev = $this->vcal->createComponent('VEVENT'); + $ev->UID = 'uuid'; + $ev->DTSTART = '20120824T145700Z'; + $ev->RRULE = 'FREQ=YEARLY;INTERVAL=0'; + $this->vcal->add($ev); + + $it = new Recur\EventIterator($this->vcal,'uuid'); + $it->fastForward(new DateTime('2013-01-01 23:00:00', new DateTimeZone('UTC'))); + + // if we got this far.. it means we are no longer infinitely looping + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/Issue48Test.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/Issue48Test.php new file mode 100644 index 0000000..435a10c --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/Issue48Test.php @@ -0,0 +1,49 @@ +<?php + +namespace Sabre\VObject; + +use + DateTime, + DateTimeZone; + +class Issue48Test extends \PHPUnit_Framework_TestCase { + + function testExpand() { + + $input = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foo +DTEND;TZID=Europe/Moscow:20130710T120000 +DTSTART;TZID=Europe/Moscow:20130710T110000 +RRULE:FREQ=DAILY;UNTIL=20130712T195959Z +END:VEVENT +BEGIN:VEVENT +UID:foo +DTEND;TZID=Europe/Moscow:20130713T120000 +DTSTART;TZID=Europe/Moscow:20130713T110000 +RECURRENCE-ID;TZID=Europe/Moscow:20130711T110000 +END:VEVENT +END:VCALENDAR +ICS; + + $vcal = Reader::read($input); + $this->assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + + $it = new Recur\EventIterator($vcal, 'foo'); + + $result = iterator_to_array($it); + + $tz = new DateTimeZone('Europe/Moscow'); + + $expected = array( + new DateTime('2013-07-10 11:00:00', $tz), + new DateTime('2013-07-12 11:00:00', $tz), + new DateTime('2013-07-13 11:00:00', $tz), + ); + + $this->assertEquals($expected, $result); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/Issue50Test.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/Issue50Test.php new file mode 100644 index 0000000..7a81295 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/Issue50Test.php @@ -0,0 +1,128 @@ +<?php + +namespace Sabre\VObject; + +use + DateTime, + DateTimeZone; + +class Issue50Test extends \PHPUnit_Framework_TestCase { + + function testExpand() { + + $input = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN +BEGIN:VTIMEZONE +TZID:Europe/Brussels +X-LIC-LOCATION:Europe/Brussels +BEGIN:DAYLIGHT +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +TZNAME:CEST +DTSTART:19700329T020000 +RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +TZNAME:CET +DTSTART:19701025T030000 +RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +CREATED:20130705T142510Z +LAST-MODIFIED:20130715T132556Z +DTSTAMP:20130715T132556Z +UID:1aef0b27-3d92-4581-829a-11999dd36724 +SUMMARY:Werken +RRULE:FREQ=DAILY;COUNT=5 +DTSTART;TZID=Europe/Brussels:20130715T090000 +DTEND;TZID=Europe/Brussels:20130715T170000 +LOCATION:Job +DESCRIPTION:Vrij +X-MOZ-GENERATION:9 +END:VEVENT +BEGIN:VEVENT +CREATED:20130715T081654Z +LAST-MODIFIED:20130715T110931Z +DTSTAMP:20130715T110931Z +UID:1aef0b27-3d92-4581-829a-11999dd36724 +SUMMARY:Werken +RECURRENCE-ID;TZID=Europe/Brussels:20130719T090000 +DTSTART;TZID=Europe/Brussels:20130719T070000 +DTEND;TZID=Europe/Brussels:20130719T150000 +SEQUENCE:1 +LOCATION:Job +DESCRIPTION:Vrij +X-MOZ-GENERATION:1 +END:VEVENT +BEGIN:VEVENT +CREATED:20130715T111654Z +LAST-MODIFIED:20130715T132556Z +DTSTAMP:20130715T132556Z +UID:1aef0b27-3d92-4581-829a-11999dd36724 +SUMMARY:Werken +RECURRENCE-ID;TZID=Europe/Brussels:20130716T090000 +DTSTART;TZID=Europe/Brussels:20130716T070000 +DTEND;TZID=Europe/Brussels:20130716T150000 +SEQUENCE:1 +LOCATION:Job +X-MOZ-GENERATION:2 +END:VEVENT +BEGIN:VEVENT +CREATED:20130715T125942Z +LAST-MODIFIED:20130715T130023Z +DTSTAMP:20130715T130023Z +UID:1aef0b27-3d92-4581-829a-11999dd36724 +SUMMARY:Werken +RECURRENCE-ID;TZID=Europe/Brussels:20130717T090000 +DTSTART;TZID=Europe/Brussels:20130717T070000 +DTEND;TZID=Europe/Brussels:20130717T150000 +SEQUENCE:1 +LOCATION:Job +X-MOZ-GENERATION:3 +END:VEVENT +BEGIN:VEVENT +CREATED:20130715T130024Z +LAST-MODIFIED:20130715T130034Z +DTSTAMP:20130715T130034Z +UID:1aef0b27-3d92-4581-829a-11999dd36724 +SUMMARY:Werken +RECURRENCE-ID;TZID=Europe/Brussels:20130718T090000 +DTSTART;TZID=Europe/Brussels:20130718T090000 +DTEND;TZID=Europe/Brussels:20130718T170000 +LOCATION:Job +X-MOZ-GENERATION:5 +DESCRIPTION:Vrij +END:VEVENT +END:VCALENDAR +ICS; + + $vcal = Reader::read($input); + $this->assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + + $it = new Recur\EventIterator($vcal, '1aef0b27-3d92-4581-829a-11999dd36724'); + + $result = array(); + foreach($it as $instance) { + + $result[] = $instance; + + } + + $tz = new DateTimeZone('Europe/Brussels'); + + $this->assertEquals(array( + new DateTime('2013-07-15 09:00:00', $tz), + new DateTime('2013-07-16 07:00:00', $tz), + new DateTime('2013-07-17 07:00:00', $tz), + new DateTime('2013-07-18 09:00:00', $tz), + new DateTime('2013-07-19 07:00:00', $tz), + ), $result); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/MainTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/MainTest.php new file mode 100644 index 0000000..3ffe35b --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/MainTest.php @@ -0,0 +1,1427 @@ +<?php + +namespace Sabre\VObject\Recur\EventIterator; + +use DateTime; +use DateTimeZone; +use Sabre\VObject\Recur\EventIterator; +use Sabre\VObject\Component\VCalendar; + +class MainTest extends \PHPUnit_Framework_TestCase { + + function testValues() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=DAILY;BYHOUR=10;BYMINUTE=5;BYSECOND=16;BYWEEKNO=32;BYYEARDAY=100,200'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-10-07')); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + $this->assertTrue($it->isInfinite()); + + } + + /** + * @expectedException InvalidArgumentException + * @depends testValues + */ + function testInvalidFreq() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + $ev->RRULE = 'FREQ=SMONTHLY;INTERVAL=3;UNTIL=20111025T000000Z'; + $ev->UID = 'foo'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-10-07', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testVCalendarNoUID() { + + $vcal = new VCalendar(); + $it = new EventIterator($vcal); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testVCalendarInvalidUID() { + + $vcal = new VCalendar(); + $it = new EventIterator($vcal,'foo'); + + } + + /** + * @depends testValues + */ + function testHourly() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=HOURLY;INTERVAL=3;UNTIL=20111025T000000Z'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-10-07 12:00:00', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + $vcal->add($ev); + + $it = new EventIterator($vcal,$ev->uid); + + // Max is to prevent overflow + $max = 12; + $result = array(); + foreach($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-10-07 12:00:00', $tz), + new DateTime('2011-10-07 15:00:00', $tz), + new DateTime('2011-10-07 18:00:00', $tz), + new DateTime('2011-10-07 21:00:00', $tz), + new DateTime('2011-10-08 00:00:00', $tz), + new DateTime('2011-10-08 03:00:00', $tz), + new DateTime('2011-10-08 06:00:00', $tz), + new DateTime('2011-10-08 09:00:00', $tz), + new DateTime('2011-10-08 12:00:00', $tz), + new DateTime('2011-10-08 15:00:00', $tz), + new DateTime('2011-10-08 18:00:00', $tz), + new DateTime('2011-10-08 21:00:00', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testDaily() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=DAILY;INTERVAL=3;UNTIL=20111025T000000Z'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-10-07', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,$ev->uid); + + // Max is to prevent overflow + $max = 12; + $result = array(); + foreach($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-10-07', $tz), + new DateTime('2011-10-10', $tz), + new DateTime('2011-10-13', $tz), + new DateTime('2011-10-16', $tz), + new DateTime('2011-10-19', $tz), + new DateTime('2011-10-22', $tz), + new DateTime('2011-10-25', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testNoRRULE() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-10-07', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,$ev->uid); + + // Max is to prevent overflow + $max = 12; + $result = array(); + foreach($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-10-07', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testDailyByDayByHour() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=DAILY;BYDAY=SA,SU;BYHOUR=6,7'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-10-08 06:00:00', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + // Grabbing the next 12 items + $max = 12; + $result = array(); + foreach($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new datetime('2011-10-08 06:00:00', $tz), + new datetime('2011-10-08 07:00:00', $tz), + new datetime('2011-10-09 06:00:00', $tz), + new datetime('2011-10-09 07:00:00', $tz), + new datetime('2011-10-15 06:00:00', $tz), + new datetime('2011-10-15 07:00:00', $tz), + new datetime('2011-10-16 06:00:00', $tz), + new datetime('2011-10-16 07:00:00', $tz), + new datetime('2011-10-22 06:00:00', $tz), + new datetime('2011-10-22 07:00:00', $tz), + new datetime('2011-10-23 06:00:00', $tz), + new datetime('2011-10-23 07:00:00', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testDailyByHour() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=DAILY;INTERVAL=2;BYHOUR=10,11,12,13,14,15'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2012-10-11 12:00:00', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + // Grabbing the next 12 items + $max = 12; + $result = array(); + foreach($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new datetime('2012-10-11 12:00:00', $tz), + new datetime('2012-10-11 13:00:00', $tz), + new datetime('2012-10-11 14:00:00', $tz), + new datetime('2012-10-11 15:00:00', $tz), + new datetime('2012-10-13 10:00:00', $tz), + new datetime('2012-10-13 11:00:00', $tz), + new datetime('2012-10-13 12:00:00', $tz), + new datetime('2012-10-13 13:00:00', $tz), + new datetime('2012-10-13 14:00:00', $tz), + new datetime('2012-10-13 15:00:00', $tz), + new datetime('2012-10-15 10:00:00', $tz), + new datetime('2012-10-15 11:00:00', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testDailyByDay() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=DAILY;INTERVAL=2;BYDAY=TU,WE,FR'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-10-07', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + // Grabbing the next 12 items + $max = 12; + $result = array(); + foreach($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-10-07', $tz), + new DateTime('2011-10-11', $tz), + new DateTime('2011-10-19', $tz), + new DateTime('2011-10-21', $tz), + new DateTime('2011-10-25', $tz), + new DateTime('2011-11-02', $tz), + new DateTime('2011-11-04', $tz), + new DateTime('2011-11-08', $tz), + new DateTime('2011-11-16', $tz), + new DateTime('2011-11-18', $tz), + new DateTime('2011-11-22', $tz), + new DateTime('2011-11-30', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testWeekly() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=WEEKLY;INTERVAL=2;COUNT=10'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-10-07', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + // Max is to prevent overflow + $max = 12; + $result = array(); + foreach($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-10-07', $tz), + new DateTime('2011-10-21', $tz), + new DateTime('2011-11-04', $tz), + new DateTime('2011-11-18', $tz), + new DateTime('2011-12-02', $tz), + new DateTime('2011-12-16', $tz), + new DateTime('2011-12-30', $tz), + new DateTime('2012-01-13', $tz), + new DateTime('2012-01-27', $tz), + new DateTime('2012-02-10', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testWeeklyByDayByHour() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=MO;BYHOUR=8,9,10'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-10-07 08:00:00', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + // Grabbing the next 12 items + $max = 15; + $result = array(); + foreach($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-10-07 08:00:00', $tz), + new DateTime('2011-10-07 09:00:00', $tz), + new DateTime('2011-10-07 10:00:00', $tz), + new DateTime('2011-10-18 08:00:00', $tz), + new DateTime('2011-10-18 09:00:00', $tz), + new DateTime('2011-10-18 10:00:00', $tz), + new DateTime('2011-10-19 08:00:00', $tz), + new DateTime('2011-10-19 09:00:00', $tz), + new DateTime('2011-10-19 10:00:00', $tz), + new DateTime('2011-10-21 08:00:00', $tz), + new DateTime('2011-10-21 09:00:00', $tz), + new DateTime('2011-10-21 10:00:00', $tz), + new DateTime('2011-11-01 08:00:00', $tz), + new DateTime('2011-11-01 09:00:00', $tz), + new DateTime('2011-11-01 10:00:00', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testWeeklyByDaySpecificHour() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=SU'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-10-07 18:00:00', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + // Grabbing the next 12 items + $max = 12; + $result = array(); + foreach($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-10-07 18:00:00', $tz), + new DateTime('2011-10-18 18:00:00', $tz), + new DateTime('2011-10-19 18:00:00', $tz), + new DateTime('2011-10-21 18:00:00', $tz), + new DateTime('2011-11-01 18:00:00', $tz), + new DateTime('2011-11-02 18:00:00', $tz), + new DateTime('2011-11-04 18:00:00', $tz), + new DateTime('2011-11-15 18:00:00', $tz), + new DateTime('2011-11-16 18:00:00', $tz), + new DateTime('2011-11-18 18:00:00', $tz), + new DateTime('2011-11-29 18:00:00', $tz), + new DateTime('2011-11-30 18:00:00', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testWeeklyByDay() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=SU'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-10-07', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + // Grabbing the next 12 items + $max = 12; + $result = array(); + foreach($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-10-07', $tz), + new DateTime('2011-10-18', $tz), + new DateTime('2011-10-19', $tz), + new DateTime('2011-10-21', $tz), + new DateTime('2011-11-01', $tz), + new DateTime('2011-11-02', $tz), + new DateTime('2011-11-04', $tz), + new DateTime('2011-11-15', $tz), + new DateTime('2011-11-16', $tz), + new DateTime('2011-11-18', $tz), + new DateTime('2011-11-29', $tz), + new DateTime('2011-11-30', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testMonthly() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=MONTHLY;INTERVAL=3;COUNT=5'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-12-05', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + $max = 14; + $result = array(); + foreach($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-12-05', $tz), + new DateTime('2012-03-05', $tz), + new DateTime('2012-06-05', $tz), + new DateTime('2012-09-05', $tz), + new DateTime('2012-12-05', $tz), + ), + $result + ); + + + } + + /** + * @depends testValues + */ + function testMonthlyEndOfMonth() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=MONTHLY;INTERVAL=2;COUNT=12'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-12-31', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + $max = 14; + $result = array(); + foreach($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-12-31', $tz), + new DateTime('2012-08-31', $tz), + new DateTime('2012-10-31', $tz), + new DateTime('2012-12-31', $tz), + new DateTime('2013-08-31', $tz), + new DateTime('2013-10-31', $tz), + new DateTime('2013-12-31', $tz), + new DateTime('2014-08-31', $tz), + new DateTime('2014-10-31', $tz), + new DateTime('2014-12-31', $tz), + new DateTime('2015-08-31', $tz), + new DateTime('2015-10-31', $tz), + ), + $result + ); + + + } + + /** + * @depends testValues + */ + function testMonthlyByMonthDay() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=MONTHLY;INTERVAL=5;COUNT=9;BYMONTHDAY=1,31,-7'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-01-01', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + $max = 14; + $result = array(); + foreach($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-01-01', $tz), + new DateTime('2011-01-25', $tz), + new DateTime('2011-01-31', $tz), + new DateTime('2011-06-01', $tz), + new DateTime('2011-06-24', $tz), + new DateTime('2011-11-01', $tz), + new DateTime('2011-11-24', $tz), + new DateTime('2012-04-01', $tz), + new DateTime('2012-04-24', $tz), + ), + $result + ); + + } + + /** + * A pretty slow test. Had to be marked as 'medium' for phpunit to not die + * after 1 second. Would be good to optimize later. + * + * @depends testValues + * @medium + */ + function testMonthlyByDay() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=MONTHLY;INTERVAL=2;COUNT=16;BYDAY=MO,-2TU,+1WE,3TH'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-01-03', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + $max = 20; + $result = array(); + foreach($it as $k=>$item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-01-03', $tz), + new DateTime('2011-01-05', $tz), + new DateTime('2011-01-10', $tz), + new DateTime('2011-01-17', $tz), + new DateTime('2011-01-18', $tz), + new DateTime('2011-01-20', $tz), + new DateTime('2011-01-24', $tz), + new DateTime('2011-01-31', $tz), + new DateTime('2011-03-02', $tz), + new DateTime('2011-03-07', $tz), + new DateTime('2011-03-14', $tz), + new DateTime('2011-03-17', $tz), + new DateTime('2011-03-21', $tz), + new DateTime('2011-03-22', $tz), + new DateTime('2011-03-28', $tz), + new DateTime('2011-05-02', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testMonthlyByDayByMonthDay() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=MONTHLY;COUNT=10;BYDAY=MO;BYMONTHDAY=1'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-08-01', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + $max = 20; + $result = array(); + foreach($it as $k=>$item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-08-01', $tz), + new DateTime('2012-10-01', $tz), + new DateTime('2013-04-01', $tz), + new DateTime('2013-07-01', $tz), + new DateTime('2014-09-01', $tz), + new DateTime('2014-12-01', $tz), + new DateTime('2015-06-01', $tz), + new DateTime('2016-02-01', $tz), + new DateTime('2016-08-01', $tz), + new DateTime('2017-05-01', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testMonthlyByDayBySetPos() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=MONTHLY;COUNT=10;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=1,-1'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-01-03', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + $max = 20; + $result = array(); + foreach($it as $k=>$item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-01-03', $tz), + new DateTime('2011-01-31', $tz), + new DateTime('2011-02-01', $tz), + new DateTime('2011-02-28', $tz), + new DateTime('2011-03-01', $tz), + new DateTime('2011-03-31', $tz), + new DateTime('2011-04-01', $tz), + new DateTime('2011-04-29', $tz), + new DateTime('2011-05-02', $tz), + new DateTime('2011-05-31', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testYearly() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=YEARLY;COUNT=10;INTERVAL=3'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-01-01', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + $max = 20; + $result = array(); + foreach($it as $k=>$item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-01-01', $tz), + new DateTime('2014-01-01', $tz), + new DateTime('2017-01-01', $tz), + new DateTime('2020-01-01', $tz), + new DateTime('2023-01-01', $tz), + new DateTime('2026-01-01', $tz), + new DateTime('2029-01-01', $tz), + new DateTime('2032-01-01', $tz), + new DateTime('2035-01-01', $tz), + new DateTime('2038-01-01', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testYearlyLeapYear() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=YEARLY;COUNT=3'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2012-02-29', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + $max = 20; + $result = array(); + foreach($it as $k=>$item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2012-02-29', $tz), + new DateTime('2016-02-29', $tz), + new DateTime('2020-02-29', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testYearlyByMonth() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=YEARLY;COUNT=8;INTERVAL=4;BYMONTH=4,10'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-04-07', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + $max = 20; + $result = array(); + foreach($it as $k=>$item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-04-07', $tz), + new DateTime('2011-10-07', $tz), + new DateTime('2015-04-07', $tz), + new DateTime('2015-10-07', $tz), + new DateTime('2019-04-07', $tz), + new DateTime('2019-10-07', $tz), + new DateTime('2023-04-07', $tz), + new DateTime('2023-10-07', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testYearlyByMonthByDay() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=YEARLY;COUNT=8;INTERVAL=5;BYMONTH=4,10;BYDAY=1MO,-1SU'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-04-04', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + $max = 20; + $result = array(); + foreach($it as $k=>$item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-04-04', $tz), + new DateTime('2011-04-24', $tz), + new DateTime('2011-10-03', $tz), + new DateTime('2011-10-30', $tz), + new DateTime('2016-04-04', $tz), + new DateTime('2016-04-24', $tz), + new DateTime('2016-10-03', $tz), + new DateTime('2016-10-30', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testFastForward() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=YEARLY;COUNT=8;INTERVAL=5;BYMONTH=4,10;BYDAY=1MO,-1SU'; + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-04-04', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + // The idea is that we're fast-forwarding too far in the future, so + // there will be no results left. + $it->fastForward(new DateTime('2020-05-05', new DateTimeZone('UTC'))); + + $max = 20; + $result = array(); + while($item = $it->current()) { + + $result[] = $item; + $max--; + + if (!$max) break; + $it->next(); + + } + + $tz = new DateTimeZone('UTC'); + $this->assertEquals(array(), $result); + + } + + /** + * @depends testValues + */ + function testComplexExclusions() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RRULE = 'FREQ=YEARLY;COUNT=10'; + $dtStart = $vcal->createProperty('DTSTART'); + + $tz = new DateTimeZone('Canada/Eastern'); + $dtStart->setDateTime(new DateTime('2011-01-01 13:50:20', $tz)); + + $exDate1 = $vcal->createProperty('EXDATE'); + $exDate1->setDateTimes(array(new DateTime('2012-01-01 13:50:20', $tz), new DateTime('2014-01-01 13:50:20', $tz))); + $exDate2 = $vcal->createProperty('EXDATE'); + $exDate2->setDateTimes(array(new DateTime('2016-01-01 13:50:20', $tz))); + + $ev->add($dtStart); + $ev->add($exDate1); + $ev->add($exDate2); + + $vcal->add($ev); + + $it = new EventIterator($vcal,(string)$ev->uid); + + $max = 20; + $result = array(); + foreach($it as $k=>$item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $this->assertEquals( + array( + new DateTime('2011-01-01 13:50:20', $tz), + new DateTime('2013-01-01 13:50:20', $tz), + new DateTime('2015-01-01 13:50:20', $tz), + new DateTime('2017-01-01 13:50:20', $tz), + new DateTime('2018-01-01 13:50:20', $tz), + new DateTime('2019-01-01 13:50:20', $tz), + new DateTime('2020-01-01 13:50:20', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + */ + function testOverridenEvent() { + + $vcal = new VCalendar(); + + $ev1 = $vcal->createComponent('VEVENT'); + $ev1->UID = 'overridden'; + $ev1->RRULE = 'FREQ=DAILY;COUNT=10'; + $ev1->DTSTART = '20120107T120000Z'; + $ev1->SUMMARY = 'baseEvent'; + + $vcal->add($ev1); + + // ev2 overrides an event, and puts it on 2pm instead. + $ev2 = $vcal->createComponent('VEVENT'); + $ev2->UID = 'overridden'; + $ev2->{'RECURRENCE-ID'} = '20120110T120000Z'; + $ev2->DTSTART = '20120110T140000Z'; + $ev2->SUMMARY = 'Event 2'; + + $vcal->add($ev2); + + // ev3 overrides an event, and puts it 2 days and 2 hours later + $ev3 = $vcal->createComponent('VEVENT'); + $ev3->UID = 'overridden'; + $ev3->{'RECURRENCE-ID'} = '20120113T120000Z'; + $ev3->DTSTART = '20120115T140000Z'; + $ev3->SUMMARY = 'Event 3'; + + $vcal->add($ev3); + + $it = new EventIterator($vcal,'overridden'); + + $dates = array(); + $summaries = array(); + while($it->valid()) { + + $dates[] = $it->getDTStart(); + $summaries[] = (string)$it->getEventObject()->SUMMARY; + $it->next(); + + } + + $tz = new DateTimeZone('UTC'); + $this->assertEquals(array( + new DateTime('2012-01-07 12:00:00',$tz), + new DateTime('2012-01-08 12:00:00',$tz), + new DateTime('2012-01-09 12:00:00',$tz), + new DateTime('2012-01-10 14:00:00',$tz), + new DateTime('2012-01-11 12:00:00',$tz), + new DateTime('2012-01-12 12:00:00',$tz), + new DateTime('2012-01-14 12:00:00',$tz), + new DateTime('2012-01-15 12:00:00',$tz), + new DateTime('2012-01-15 14:00:00',$tz), + new DateTime('2012-01-16 12:00:00',$tz), + ), $dates); + + $this->assertEquals(array( + 'baseEvent', + 'baseEvent', + 'baseEvent', + 'Event 2', + 'baseEvent', + 'baseEvent', + 'baseEvent', + 'baseEvent', + 'Event 3', + 'baseEvent', + ), $summaries); + + } + + /** + * @depends testValues + */ + function testOverridenEvent2() { + + $vcal = new VCalendar(); + + $ev1 = $vcal->createComponent('VEVENT'); + $ev1->UID = 'overridden'; + $ev1->RRULE = 'FREQ=WEEKLY;COUNT=3'; + $ev1->DTSTART = '20120112T120000Z'; + $ev1->SUMMARY = 'baseEvent'; + + $vcal->add($ev1); + + // ev2 overrides an event, and puts it 6 days earlier instead. + $ev2 = $vcal->createComponent('VEVENT'); + $ev2->UID = 'overridden'; + $ev2->{'RECURRENCE-ID'} = '20120119T120000Z'; + $ev2->DTSTART = '20120113T120000Z'; + $ev2->SUMMARY = 'Override!'; + + $vcal->add($ev2); + + $it = new EventIterator($vcal,'overridden'); + + $dates = array(); + $summaries = array(); + while($it->valid()) { + + $dates[] = $it->getDTStart(); + $summaries[] = (string)$it->getEventObject()->SUMMARY; + $it->next(); + + } + + $tz = new DateTimeZone('UTC'); + $this->assertEquals(array( + new DateTime('2012-01-12 12:00:00',$tz), + new DateTime('2012-01-13 12:00:00',$tz), + new DateTime('2012-01-26 12:00:00',$tz), + + ), $dates); + + $this->assertEquals(array( + 'baseEvent', + 'Override!', + 'baseEvent', + ), $summaries); + + } + + /** + * @depends testValues + */ + function testOverridenEventNoValuesExpected() { + + $vcal = new VCalendar(); + $ev1 = $vcal->createComponent('VEVENT'); + + $ev1->UID = 'overridden'; + $ev1->RRULE = 'FREQ=WEEKLY;COUNT=3'; + $ev1->DTSTART = '20120124T120000Z'; + $ev1->SUMMARY = 'baseEvent'; + + $vcal->add($ev1); + + // ev2 overrides an event, and puts it 6 days earlier instead. + $ev2 = $vcal->createComponent('VEVENT'); + $ev2->UID = 'overridden'; + $ev2->{'RECURRENCE-ID'} = '20120131T120000Z'; + $ev2->DTSTART = '20120125T120000Z'; + $ev2->SUMMARY = 'Override!'; + + $vcal->add($ev2); + + $it = new EventIterator($vcal,'overridden'); + + $dates = array(); + $summaries = array(); + + // The reported problem was specifically related to the VCALENDAR + // expansion. In this parcitular case, we had to forward to the 28th of + // january. + $it->fastForward(new DateTime('2012-01-28 23:00:00')); + + // We stop the loop when it hits the 6th of februari. Normally this + // iterator would hit 24, 25 (overriden from 31) and 7 feb but because + // we 'filter' from the 28th till the 6th, we should get 0 results. + while($it->valid() && $it->getDTSTart() < new DateTime('2012-02-06 23:00:00')) { + + $dates[] = $it->getDTStart(); + $summaries[] = (string)$it->getEventObject()->SUMMARY; + $it->next(); + + } + + $this->assertEquals(array(), $dates); + $this->assertEquals(array(), $summaries); + + } + + /** + * @depends testValues + */ + function testRDATE() { + + $vcal = new VCalendar(); + $ev = $vcal->createComponent('VEVENT'); + + $ev->UID = 'bla'; + $ev->RDATE = array( + new DateTime('2014-08-07', new DateTimeZone('UTC')), + new DateTime('2014-08-08', new DateTimeZone('UTC')), + ); + $dtStart = $vcal->createProperty('DTSTART'); + $dtStart->setDateTime(new DateTime('2011-10-07', new DateTimeZone('UTC'))); + + $ev->add($dtStart); + + $vcal->add($ev); + + $it = new EventIterator($vcal,$ev->uid); + + // Max is to prevent overflow + $max = 12; + $result = array(); + foreach($it as $item) { + + $result[] = $item; + $max--; + + if (!$max) break; + + } + + $tz = new DateTimeZone('UTC'); + + $this->assertEquals( + array( + new DateTime('2011-10-07', $tz), + new DateTime('2014-08-07', $tz), + new DateTime('2014-08-08', $tz), + ), + $result + ); + + } + + /** + * @depends testValues + * @expectedException \InvalidArgumentException + */ + function testNoMasterBadUID() { + + $vcal = new VCalendar(); + // ev2 overrides an event, and puts it on 2pm instead. + $ev2 = $vcal->createComponent('VEVENT'); + $ev2->UID = 'overridden'; + $ev2->{'RECURRENCE-ID'} = '20120110T120000Z'; + $ev2->DTSTART = '20120110T140000Z'; + $ev2->SUMMARY = 'Event 2'; + + $vcal->add($ev2); + + // ev3 overrides an event, and puts it 2 days and 2 hours later + $ev3 = $vcal->createComponent('VEVENT'); + $ev3->UID = 'overridden'; + $ev3->{'RECURRENCE-ID'} = '20120113T120000Z'; + $ev3->DTSTART = '20120115T140000Z'; + $ev3->SUMMARY = 'Event 3'; + + $vcal->add($ev3); + + $it = new EventIterator($vcal,'broken'); + + } +} + diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/MissingOverriddenTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/MissingOverriddenTest.php new file mode 100644 index 0000000..2ab4eff --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/MissingOverriddenTest.php @@ -0,0 +1,65 @@ +<?php + +namespace Sabre\VObject\Recur\EventIterator; + +use + DateTime, + DateTimeZone, + Sabre\VObject\Reader; + +class RecurrenceIteratorMissingOverriddenTest extends \PHPUnit_Framework_TestCase { + + function testExpand() { + + $input = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foo +DTSTART:20130727T120000Z +DURATION:PT1H +RRULE:FREQ=DAILY;COUNT=2 +SUMMARY:A +END:VEVENT +BEGIN:VEVENT +RECURRENCE-ID:20130728T120000Z +UID:foo +DTSTART:20140101T120000Z +DURATION:PT1H +SUMMARY:B +END:VEVENT +END:VCALENDAR +ICS; + + $vcal = Reader::read($input); + $this->assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + + $vcal->expand(new DateTime('2011-01-01'), new DateTime('2015-01-01')); + + $result = $vcal->serialize(); + + $output = <<<ICS +BEGIN:VCALENDAR +VERSION:2.0 +BEGIN:VEVENT +UID:foo +DTSTART:20130727T120000Z +DURATION:PT1H +SUMMARY:A +RECURRENCE-ID:20130727T120000Z +END:VEVENT +BEGIN:VEVENT +RECURRENCE-ID:20130728T120000Z +UID:foo +DTSTART:20140101T120000Z +DURATION:PT1H +SUMMARY:B +END:VEVENT +END:VCALENDAR + +ICS; + $this->assertEquals($output, str_replace("\r","",$result)); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/NoInstancesTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/NoInstancesTest.php new file mode 100644 index 0000000..81c8ac9 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/NoInstancesTest.php @@ -0,0 +1,40 @@ +<?php + +namespace Sabre\VObject\Recur; + +use + Sabre\VObject\Reader; + +class IssueEXDATETest extends \PHPUnit_Framework_TestCase { + + /** + * @expectedException \Sabre\VObject\Recur\NoInstancesException + */ + function testRecurrence() { + + $input = <<<ICS +BEGIN:VCALENDAR +PRODID:-//Google Inc//Google Calendar 70.9054//EN +VERSION:2.0 +BEGIN:VEVENT +DTSTART;TZID=Europe/Berlin:20130329T140000 +DTEND;TZID=Europe/Berlin:20130329T153000 +RRULE:FREQ=WEEKLY;BYDAY=FR;UNTIL=20130412T115959Z +EXDATE;TZID=Europe/Berlin:20130405T140000 +EXDATE;TZID=Europe/Berlin:20130329T140000 +DTSTAMP:20140916T201215Z +UID:foo +SEQUENCE:1 +SUMMARY:foo +END:VEVENT +END:VCALENDAR +ICS; + + $vcal = Reader::read($input); + $this->assertInstanceOf('Sabre\\VObject\\Component\\VCalendar', $vcal); + + $it = new EventIterator($vcal, 'foo'); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/OverrideFirstEventTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/OverrideFirstEventTest.php new file mode 100644 index 0000000..5751493 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/EventIterator/OverrideFirstEventTest.php @@ -0,0 +1,122 @@ +<?php + +namespace Sabre\VObject\RecurrenceIterator; + +use Sabre\VObject\Reader; +use DateTime; + +class OverrideFirstEventTest extends \PHPUnit_Framework_TestCase { + + function testOverrideFirstEvent() { + + $input = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar +DTSTART:20140803T120000Z +RRULE:FREQ=WEEKLY +SUMMARY:Original +END:VEVENT +BEGIN:VEVENT +UID:foobar +RECURRENCE-ID:20140803T120000Z +DTSTART:20140803T120000Z +SUMMARY:Overridden +END:VEVENT +END:VCALENDAR +ICS; + + $vcal = Reader::read($input); + $vcal->expand(new DateTime('2014-08-01'), new DateTime('2014-09-01')); + + $expected = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar +RECURRENCE-ID:20140803T120000Z +DTSTART:20140803T120000Z +SUMMARY:Overridden +END:VEVENT +BEGIN:VEVENT +UID:foobar +DTSTART:20140810T120000Z +SUMMARY:Original +RECURRENCE-ID:20140810T120000Z +END:VEVENT +BEGIN:VEVENT +UID:foobar +DTSTART:20140817T120000Z +SUMMARY:Original +RECURRENCE-ID:20140817T120000Z +END:VEVENT +BEGIN:VEVENT +UID:foobar +DTSTART:20140824T120000Z +SUMMARY:Original +RECURRENCE-ID:20140824T120000Z +END:VEVENT +BEGIN:VEVENT +UID:foobar +DTSTART:20140831T120000Z +SUMMARY:Original +RECURRENCE-ID:20140831T120000Z +END:VEVENT +END:VCALENDAR + +ICS; + + $newIcs = $vcal->serialize(); + $newIcs = str_replace("\r\n","\n", $newIcs); + $this->assertEquals( + $expected, + $newIcs + ); + + + } + + function testRemoveFirstEvent() { + + $input = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar +DTSTART:20140803T120000Z +RRULE:FREQ=WEEKLY +EXDATE:20140803T120000Z +SUMMARY:Original +END:VEVENT +END:VCALENDAR +ICS; + + $vcal = Reader::read($input); + $vcal->expand(new DateTime('2014-08-01'), new DateTime('2014-08-19')); + + $expected = <<<ICS +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foobar +DTSTART:20140810T120000Z +SUMMARY:Original +RECURRENCE-ID:20140810T120000Z +END:VEVENT +BEGIN:VEVENT +UID:foobar +DTSTART:20140817T120000Z +SUMMARY:Original +RECURRENCE-ID:20140817T120000Z +END:VEVENT +END:VCALENDAR + +ICS; + + $newIcs = $vcal->serialize(); + $newIcs = str_replace("\r\n","\n", $newIcs); + $this->assertEquals( + $expected, + $newIcs + ); + + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/RDateIteratorTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/RDateIteratorTest.php new file mode 100644 index 0000000..5663c9a --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/RDateIteratorTest.php @@ -0,0 +1,56 @@ +<?php + +namespace Sabre\VObject\Recur; + +use DateTime; +use DateTimeZone; + +class RDateIteratorTest extends \PHPUnit_Framework_TestCase { + + function testSimple() { + + $utc = new DateTimeZone('UTC'); + $it = new RDateIterator('20140901T000000Z,20141001T000000Z', new DateTime('2014-08-01 00:00:00', $utc)); + + $expected = array( + new DateTime('2014-08-01 00:00:00', $utc), + new DateTime('2014-09-01 00:00:00', $utc), + new DateTime('2014-10-01 00:00:00', $utc), + ); + + $this->assertEquals( + $expected, + iterator_to_array($it) + ); + + $this->assertFalse($it->isInfinite()); + + } + + function testFastForward() { + + $utc = new DateTimeZone('UTC'); + $it = new RDateIterator('20140901T000000Z,20141001T000000Z', new DateTime('2014-08-01 00:00:00', $utc)); + + $it->fastForward(new DateTime('2014-08-15 00:00:00')); + + $result = array(); + while($it->valid()) { + $result[] = $it->current(); + $it->next(); + } + + $expected = array( + new DateTime('2014-09-01 00:00:00', $utc), + new DateTime('2014-10-01 00:00:00', $utc), + ); + + $this->assertEquals( + $expected, + $result + ); + + $this->assertFalse($it->isInfinite()); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/RRuleIteratorTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/RRuleIteratorTest.php new file mode 100644 index 0000000..9122934 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Recur/RRuleIteratorTest.php @@ -0,0 +1,713 @@ +<?php + +namespace Sabre\VObject\Recur; + +use DateTime; +use DateTimeZone; + +class RRuleIteratorTest extends \PHPUnit_Framework_TestCase { + + function testHourly() { + + $this->parse( + 'FREQ=HOURLY;INTERVAL=3;COUNT=12', + '2011-10-07 12:00:00', + array( + '2011-10-07 12:00:00', + '2011-10-07 15:00:00', + '2011-10-07 18:00:00', + '2011-10-07 21:00:00', + '2011-10-08 00:00:00', + '2011-10-08 03:00:00', + '2011-10-08 06:00:00', + '2011-10-08 09:00:00', + '2011-10-08 12:00:00', + '2011-10-08 15:00:00', + '2011-10-08 18:00:00', + '2011-10-08 21:00:00', + ) + ); + + } + + function testDaily() { + + $this->parse( + 'FREQ=DAILY;INTERVAL=3;UNTIL=20111025T000000Z', + '2011-10-07', + array( + '2011-10-07 00:00:00', + '2011-10-10 00:00:00', + '2011-10-13 00:00:00', + '2011-10-16 00:00:00', + '2011-10-19 00:00:00', + '2011-10-22 00:00:00', + '2011-10-25 00:00:00', + ) + ); + + } + + function testDailyByDayByHour() { + + $this->parse( + 'FREQ=DAILY;BYDAY=SA,SU;BYHOUR=6,7', + '2011-10-08 06:00:00', + array( + '2011-10-08 06:00:00', + '2011-10-08 07:00:00', + '2011-10-09 06:00:00', + '2011-10-09 07:00:00', + '2011-10-15 06:00:00', + '2011-10-15 07:00:00', + '2011-10-16 06:00:00', + '2011-10-16 07:00:00', + '2011-10-22 06:00:00', + '2011-10-22 07:00:00', + '2011-10-23 06:00:00', + '2011-10-23 07:00:00', + ) + ); + + } + + function testDailyByHour() { + + $this->parse( + 'FREQ=DAILY;INTERVAL=2;BYHOUR=10,11,12,13,14,15', + '2012-10-11 12:00:00', + array( + '2012-10-11 12:00:00', + '2012-10-11 13:00:00', + '2012-10-11 14:00:00', + '2012-10-11 15:00:00', + '2012-10-13 10:00:00', + '2012-10-13 11:00:00', + '2012-10-13 12:00:00', + '2012-10-13 13:00:00', + '2012-10-13 14:00:00', + '2012-10-13 15:00:00', + '2012-10-15 10:00:00', + '2012-10-15 11:00:00', + ) + ); + + } + + function testDailyByDay() { + + $this->parse( + 'FREQ=DAILY;INTERVAL=2;BYDAY=TU,WE,FR', + '2011-10-07 12:00:00', + array( + '2011-10-07 12:00:00', + '2011-10-11 12:00:00', + '2011-10-19 12:00:00', + '2011-10-21 12:00:00', + '2011-10-25 12:00:00', + '2011-11-02 12:00:00', + '2011-11-04 12:00:00', + '2011-11-08 12:00:00', + '2011-11-16 12:00:00', + '2011-11-18 12:00:00', + '2011-11-22 12:00:00', + '2011-11-30 12:00:00', + ) + ); + + } + + function testDailyCount() { + + $this->parse( + 'FREQ=DAILY;COUNT=5', + '2014-08-01 18:03:00', + array( + '2014-08-01 18:03:00', + '2014-08-02 18:03:00', + '2014-08-03 18:03:00', + '2014-08-04 18:03:00', + '2014-08-05 18:03:00', + ) + ); + + } + + function testDailyByMonth() { + + $this->parse( + 'FREQ=DAILY;BYMONTH=9,10;BYDAY=SU', + '2007-10-04 16:00:00', + array( + "2013-09-29 16:00:00", + "2013-10-06 16:00:00", + "2013-10-13 16:00:00", + "2013-10-20 16:00:00", + "2013-10-27 16:00:00", + "2014-09-07 16:00:00" + ), + '2013-09-28' + ); + + } + + function testWeekly() { + + $this->parse( + 'FREQ=WEEKLY;INTERVAL=2;COUNT=10', + '2011-10-07 00:00:00', + array( + '2011-10-07 00:00:00', + '2011-10-21 00:00:00', + '2011-11-04 00:00:00', + '2011-11-18 00:00:00', + '2011-12-02 00:00:00', + '2011-12-16 00:00:00', + '2011-12-30 00:00:00', + '2012-01-13 00:00:00', + '2012-01-27 00:00:00', + '2012-02-10 00:00:00', + ) + ); + + } + + function testWeeklyByDay() { + + $this->parse( + 'FREQ=WEEKLY;INTERVAL=1;COUNT=4;BYDAY=MO;WKST=SA', + '2014-08-01 00:00:00', + array( + '2014-08-01 00:00:00', + '2014-08-04 00:00:00', + '2014-08-11 00:00:00', + '2014-08-18 00:00:00', + ) + ); + + } + + function testWeeklyByDay2() { + + $this->parse( + 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=SU', + '2011-10-07 00:00:00', + array( + '2011-10-07 00:00:00', + '2011-10-18 00:00:00', + '2011-10-19 00:00:00', + '2011-10-21 00:00:00', + '2011-11-01 00:00:00', + '2011-11-02 00:00:00', + '2011-11-04 00:00:00', + '2011-11-15 00:00:00', + '2011-11-16 00:00:00', + '2011-11-18 00:00:00', + '2011-11-29 00:00:00', + '2011-11-30 00:00:00', + ) + ); + + } + + function testWeeklyByDayByHour() { + + $this->parse( + 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=MO;BYHOUR=8,9,10', + '2011-10-07 08:00:00', + array( + '2011-10-07 08:00:00', + '2011-10-07 09:00:00', + '2011-10-07 10:00:00', + '2011-10-18 08:00:00', + '2011-10-18 09:00:00', + '2011-10-18 10:00:00', + '2011-10-19 08:00:00', + '2011-10-19 09:00:00', + '2011-10-19 10:00:00', + '2011-10-21 08:00:00', + '2011-10-21 09:00:00', + '2011-10-21 10:00:00', + '2011-11-01 08:00:00', + '2011-11-01 09:00:00', + '2011-11-01 10:00:00', + ) + ); + + } + + function testWeeklyByDaySpecificHour() { + + $this->parse( + 'FREQ=WEEKLY;INTERVAL=2;BYDAY=TU,WE,FR;WKST=SU', + '2011-10-07 18:00:00', + array( + '2011-10-07 18:00:00', + '2011-10-18 18:00:00', + '2011-10-19 18:00:00', + '2011-10-21 18:00:00', + '2011-11-01 18:00:00', + '2011-11-02 18:00:00', + '2011-11-04 18:00:00', + '2011-11-15 18:00:00', + '2011-11-16 18:00:00', + '2011-11-18 18:00:00', + '2011-11-29 18:00:00', + '2011-11-30 18:00:00', + ) + ); + + } + + function testMonthly() { + + $this->parse( + 'FREQ=MONTHLY;INTERVAL=3;COUNT=5', + '2011-12-05 00:00:00', + array( + '2011-12-05 00:00:00', + '2012-03-05 00:00:00', + '2012-06-05 00:00:00', + '2012-09-05 00:00:00', + '2012-12-05 00:00:00', + ) + ); + + } + + function testMonlthyEndOfMonth() { + + $this->parse( + 'FREQ=MONTHLY;INTERVAL=2;COUNT=12', + '2011-12-31 00:00:00', + array( + '2011-12-31 00:00:00', + '2012-08-31 00:00:00', + '2012-10-31 00:00:00', + '2012-12-31 00:00:00', + '2013-08-31 00:00:00', + '2013-10-31 00:00:00', + '2013-12-31 00:00:00', + '2014-08-31 00:00:00', + '2014-10-31 00:00:00', + '2014-12-31 00:00:00', + '2015-08-31 00:00:00', + '2015-10-31 00:00:00', + ) + ); + + } + + function testMonthlyByMonthDay() { + + $this->parse( + 'FREQ=MONTHLY;INTERVAL=5;COUNT=9;BYMONTHDAY=1,31,-7', + '2011-01-01 00:00:00', + array( + '2011-01-01 00:00:00', + '2011-01-25 00:00:00', + '2011-01-31 00:00:00', + '2011-06-01 00:00:00', + '2011-06-24 00:00:00', + '2011-11-01 00:00:00', + '2011-11-24 00:00:00', + '2012-04-01 00:00:00', + '2012-04-24 00:00:00', + ) + ); + + } + + function testMonthlyByDay() { + + $this->parse( + 'FREQ=MONTHLY;INTERVAL=2;COUNT=16;BYDAY=MO,-2TU,+1WE,3TH', + '2011-01-03 00:00:00', + array( + '2011-01-03 00:00:00', + '2011-01-05 00:00:00', + '2011-01-10 00:00:00', + '2011-01-17 00:00:00', + '2011-01-18 00:00:00', + '2011-01-20 00:00:00', + '2011-01-24 00:00:00', + '2011-01-31 00:00:00', + '2011-03-02 00:00:00', + '2011-03-07 00:00:00', + '2011-03-14 00:00:00', + '2011-03-17 00:00:00', + '2011-03-21 00:00:00', + '2011-03-22 00:00:00', + '2011-03-28 00:00:00', + '2011-05-02 00:00:00', + ) + ); + + } + + function testMonthlyByDayByMonthDay() { + + $this->parse( + 'FREQ=MONTHLY;COUNT=10;BYDAY=MO;BYMONTHDAY=1', + '2011-08-01 00:00:00', + array( + '2011-08-01 00:00:00', + '2012-10-01 00:00:00', + '2013-04-01 00:00:00', + '2013-07-01 00:00:00', + '2014-09-01 00:00:00', + '2014-12-01 00:00:00', + '2015-06-01 00:00:00', + '2016-02-01 00:00:00', + '2016-08-01 00:00:00', + '2017-05-01 00:00:00', + ) + ); + + } + + function testMonthlyByDayBySetPos() { + + $this->parse( + 'FREQ=MONTHLY;COUNT=10;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=1,-1', + '2011-01-03 00:00:00', + array( + '2011-01-03 00:00:00', + '2011-01-31 00:00:00', + '2011-02-01 00:00:00', + '2011-02-28 00:00:00', + '2011-03-01 00:00:00', + '2011-03-31 00:00:00', + '2011-04-01 00:00:00', + '2011-04-29 00:00:00', + '2011-05-02 00:00:00', + '2011-05-31 00:00:00', + ) + ); + + } + + function testYearly() { + + $this->parse( + 'FREQ=YEARLY;COUNT=10;INTERVAL=3', + '2011-01-01 00:00:00', + array( + '2011-01-01 00:00:00', + '2014-01-01 00:00:00', + '2017-01-01 00:00:00', + '2020-01-01 00:00:00', + '2023-01-01 00:00:00', + '2026-01-01 00:00:00', + '2029-01-01 00:00:00', + '2032-01-01 00:00:00', + '2035-01-01 00:00:00', + '2038-01-01 00:00:00', + ) + ); + } + + function testYearlyLeapYear() { + + $this->parse( + 'FREQ=YEARLY;COUNT=3', + '2012-02-29 00:00:00', + array( + '2012-02-29 00:00:00', + '2016-02-29 00:00:00', + '2020-02-29 00:00:00', + ) + ); + } + + function testYearlyByMonth() { + + $this->parse( + 'FREQ=YEARLY;COUNT=8;INTERVAL=4;BYMONTH=4,10', + '2011-04-07 00:00:00', + array( + '2011-04-07 00:00:00', + '2011-10-07 00:00:00', + '2015-04-07 00:00:00', + '2015-10-07 00:00:00', + '2019-04-07 00:00:00', + '2019-10-07 00:00:00', + '2023-04-07 00:00:00', + '2023-10-07 00:00:00', + ) + ); + + } + + function testYearlyByMonthByDay() { + + $this->parse( + 'FREQ=YEARLY;COUNT=8;INTERVAL=5;BYMONTH=4,10;BYDAY=1MO,-1SU', + '2011-04-04 00:00:00', + array( + '2011-04-04 00:00:00', + '2011-04-24 00:00:00', + '2011-10-03 00:00:00', + '2011-10-30 00:00:00', + '2016-04-04 00:00:00', + '2016-04-24 00:00:00', + '2016-10-03 00:00:00', + '2016-10-30 00:00:00', + ) + ); + + } + + function testFastForward() { + + // The idea is that we're fast-forwarding too far in the future, so + // there will be no results left. + $this->parse( + 'FREQ=YEARLY;COUNT=8;INTERVAL=5;BYMONTH=4,10;BYDAY=1MO,-1SU', + '2011-04-04 00:00:00', + array(), + '2020-05-05 00:00:00' + ); + + } + + /** + * The bug that was in the + * system before would fail on the 5th tuesday of the month, if the 5th + * tuesday did not exist. + * + * A pretty slow test. Had to be marked as 'medium' for phpunit to not die + * after 1 second. Would be good to optimize later. + * + * @medium + */ + function testFifthTuesdayProblem() { + + $this->parse( + 'FREQ=MONTHLY;INTERVAL=1;UNTIL=20071030T035959Z;BYDAY=5TU', + '2007-10-04 14:46:42', + array( + "2007-10-04 14:46:42", + ) + ); + + } + + /** + * This bug came from a Fruux customer. This would result in a never-ending + * request. + */ + function testFastFowardTooFar() { + + $this->parse( + 'FREQ=WEEKLY;BYDAY=MO;UNTIL=20090704T205959Z;INTERVAL=1', + '2009-04-20 18:00:00', + array( + '2009-04-20 18:00:00', + '2009-04-27 18:00:00', + '2009-05-04 18:00:00', + '2009-05-11 18:00:00', + '2009-05-18 18:00:00', + '2009-05-25 18:00:00', + '2009-06-01 18:00:00', + '2009-06-08 18:00:00', + '2009-06-15 18:00:00', + '2009-06-22 18:00:00', + '2009-06-29 18:00:00', + ) + ); + + } + + /** + * This also at one point caused an infinite loop. We're keeping the test. + */ + function testYearlyByMonthLoop() { + + $this->parse( + 'FREQ=YEARLY;INTERVAL=1;UNTIL=20120203T225959Z;BYMONTH=2;BYSETPOS=1;BYDAY=SU,MO,TU,WE,TH,FR,SA', + '2012-01-01 15:45:00', + array( + '2012-02-01 15:45:00', + ), + '2012-01-29 23:00:00' + ); + + + } + + /** + * Something, somewhere produced an ics with an interval set to 0. Because + * this means we increase the current day (or week, month) by 0, this also + * results in an infinite loop. + * + * @expectedException InvalidArgumentException + */ + function testZeroInterval() { + + $this->parse( + 'FREQ=YEARLY;INTERVAL=0', + '2012-08-24 14:57:00', + array(), + '2013-01-01 23:00:00' + ); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testInvalidFreq() { + + $this->parse( + 'FREQ=SMONTHLY;INTERVAL=3;UNTIL=20111025T000000Z', + '2011-10-07', + array() + ); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testByDayBadOffset() { + + $this->parse( + 'FREQ=WEEKLY;INTERVAL=1;COUNT=4;BYDAY=0MO;WKST=SA', + '2014-08-01 00:00:00', + array() + ); + + } + + function testUntilBeginHAsTimezone() { + + $this->parse( + 'FREQ=WEEKLY;UNTIL=20131118T183000', + '2013-09-23 18:30:00', + array( + '2013-09-23 18:30:00', + '2013-09-30 18:30:00', + '2013-10-07 18:30:00', + '2013-10-14 18:30:00', + '2013-10-21 18:30:00', + '2013-10-28 18:30:00', + '2013-11-04 18:30:00', + '2013-11-11 18:30:00', + '2013-11-18 18:30:00', + ), + null, + 'America/New_York' + ); + + } + + function testUntilBeforeDtStart() { + + $this->parse( + 'FREQ=DAILY;UNTIL=20140101T000000Z', + '2014-08-02 00:15:00', + array( + '2014-08-02 00:15:00', + ) + ); + + } + + function testIgnoredStuff() { + + $this->parse( + 'FREQ=DAILY;BYSECOND=1;BYMINUTE=1;BYYEARDAY=1;BYWEEKNO=1;COUNT=2', + '2014-08-02 00:15:00', + array( + '2014-08-02 00:15:00', + '2014-08-03 00:15:00', + ) + ); + + } + + function testMinusFifthThursday() { + + $this->parse( + 'FREQ=MONTHLY;BYDAY=-4TH,-5TH;COUNT=4', + '2015-01-01 00:15:00', + array( + '2015-01-01 00:15:00', + '2015-01-08 00:15:00', + '2015-02-05 00:15:00', + '2015-03-05 00:15:00' + ) + ); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testUnsupportedPart() { + + $this->parse( + 'FREQ=DAILY;BYWODAN=1', + '2014-08-02 00:15:00', + array() + ); + + } + + function testIteratorFunctions() { + + $parser = new RRuleIterator('FREQ=DAILY', new DateTime('2014-08-02 00:00:13')); + $parser->next(); + $this->assertEquals( + new DateTime('2014-08-03 00:00:13'), + $parser->current() + ); + $this->assertEquals( + 1, + $parser->key() + ); + + $parser->rewind(); + + $this->assertEquals( + new DateTime('2014-08-02 00:00:13'), + $parser->current() + ); + $this->assertEquals( + 0, + $parser->key() + ); + + } + + function parse($rule, $start, $expected, $fastForward = null, $tz = 'UTC') { + + $dt = new DateTime($start, new DateTimeZone($tz)); + $parser = new RRuleIterator($rule, $dt); + + if ($fastForward) { + $parser->fastForward(new DateTime($fastForward)); + } + + $result = array(); + while($parser->valid()) { + + $item = $parser->current(); + $result[] = $item->format('Y-m-d H:i:s'); + + if ($parser->isInfinite() && count($result) >= count($expected)) { + break; + } + $parser->next(); + + } + + $this->assertEquals( + $expected, + $result + ); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/RecurrenceIterator/UntilRespectsTimezoneTest.ics b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/RecurrenceIterator/UntilRespectsTimezoneTest.ics new file mode 100644 index 0000000..6b2cccb --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/RecurrenceIterator/UntilRespectsTimezoneTest.ics @@ -0,0 +1,39 @@ +BEGIN:VCALENDAR +VERSION:2.0 +X-WR-TIMEZONE:America/New_York +PRODID:-//www.churchcommunitybuilder.com//Church Community Builder//EN +CALSCALE:GREGORIAN +METHOD:PUBLISH +X-WR-CALNAME:Test Event +BEGIN:VTIMEZONE +TZID:America/New_York +X-LIC-LOCATION:America/New_York +BEGIN:DAYLIGHT +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +TZNAME:EDT +DTSTART:19700308T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +TZNAME:EST +DTSTART:19701101T020000 +RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +UID:10621-1440@ccbchurch.com +DTSTART;TZID=America/New_York:20130923T183000 +DTEND;TZID=America/New_York:20130923T203000 +DTSTAMP:20131216T170211 +RRULE:FREQ=WEEKLY;UNTIL=20131118T183000 +CREATED:20130423T161111 +DESCRIPTION:Test Event ending November 11, 2013 +LAST-MODIFIED:20131126T163428 +SEQUENCE:1387231331 +SUMMARY:Test +TRANSP:OPAQUE +END:VEVENT +END:VCALENDAR diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/SlashRTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/SlashRTest.php new file mode 100644 index 0000000..fd4dd78 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/SlashRTest.php @@ -0,0 +1,20 @@ +<?php + +namespace Sabre\VObject; + +/** + * This issue was pointed out in Issue 55. \r should be stripped completely + * when encoding property values. + */ +class SlashRTest extends \PHPUnit_Framework_TestCase { + + function testEncode() { + + $vcal = new Component\VCalendar(); + $prop = $vcal->add('test', "abc\r\ndef"); + $this->assertEquals("TEST:abc\\ndef\r\n", $prop->serialize()); + + } + + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Splitter/ICalendarTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Splitter/ICalendarTest.php new file mode 100644 index 0000000..58a2691 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Splitter/ICalendarTest.php @@ -0,0 +1,325 @@ +<?php + +namespace Sabre\VObject\Splitter; + +use Sabre\VObject; + +class ICalendarTest extends \PHPUnit_Framework_TestCase { + + protected $version; + + function setUp() { + $this->version = VObject\Version::VERSION; + } + + function createStream($data) { + + $stream = fopen('php://memory','r+'); + fwrite($stream, $data); + rewind($stream); + return $stream; + + } + + function testICalendarImportValidEvent() { + + $data = <<<EOT +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foo +DTSTAMP:20140122T233226Z +DTSTART:20140101T070000Z +END:VEVENT +END:VCALENDAR +EOT; + $tempFile = $this->createStream($data); + + $objects = new ICalendar($tempFile); + + $return = ""; + while($object=$objects->getNext()) { + $return .= $object->serialize(); + } + $this->assertEquals(array(), VObject\Reader::read($return)->validate()); + } + + /** + * @expectedException Sabre\VObject\ParseException + */ + function testICalendarImportWrongType() { + + $data = <<<EOT +BEGIN:VCARD +UID:foo1 +END:VCARD +BEGIN:VCARD +UID:foo2 +END:VCARD +EOT; + $tempFile = $this->createStream($data); + + $objects = new ICalendar($tempFile); + + } + + function testICalendarImportEndOfData() { + $data = <<<EOT +BEGIN:VCALENDAR +BEGIN:VEVENT +UID:foo +DTSTAMP:20140122T233226Z +END:VEVENT +END:VCALENDAR +EOT; + $tempFile = $this->createStream($data); + + $objects = new ICalendar($tempFile); + + $return = ""; + while($object=$objects->getNext()) { + $return .= $object->serialize(); + } + $this->assertNull($object=$objects->getNext()); + } + + /** + * @expectedException Sabre\VObject\ParseException + */ + function testICalendarImportInvalidEvent() { + $data = <<<EOT +EOT; + $tempFile = $this->createStream($data); + $objects = new ICalendar($tempFile); + + } + + function testICalendarImportMultipleValidEvents() { + + $event[] = <<<EOT +BEGIN:VEVENT +UID:foo1 +DTSTAMP:20140122T233226Z +DTSTART:20140101T050000Z +END:VEVENT +EOT; + +$event[] = <<<EOT +BEGIN:VEVENT +UID:foo2 +DTSTAMP:20140122T233226Z +DTSTART:20140101T060000Z +END:VEVENT +EOT; + + $data = <<<EOT +BEGIN:VCALENDAR +$event[0] +$event[1] +END:VCALENDAR + +EOT; + $tempFile = $this->createStream($data); + + $objects = new ICalendar($tempFile); + + $return = ""; + $i = 0; + while($object=$objects->getNext()) { + + $expected = <<<EOT +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $this->version//EN +CALSCALE:GREGORIAN +$event[$i] +END:VCALENDAR + +EOT; + + $return .= $object->serialize(); + $expected = str_replace("\n", "\r\n", $expected); + $this->assertEquals($expected, $object->serialize()); + $i++; + } + $this->assertEquals(array(), VObject\Reader::read($return)->validate()); + } + + function testICalendarImportEventWithoutUID() { + + $data = <<<EOT +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $this->version//EN +CALSCALE:GREGORIAN +BEGIN:VEVENT +DTSTART:20140101T040000Z +DTSTAMP:20140122T233226Z +END:VEVENT +END:VCALENDAR + +EOT; + $tempFile = $this->createStream($data); + + $objects = new ICalendar($tempFile); + + $return = ""; + while($object=$objects->getNext()) { + $return .= $object->serialize(); + } + + $messages = VObject\Reader::read($return)->validate(); + + if ($messages) { + $messages = array_map( + function($item) { return $item['message']; }, + $messages + ); + $this->fail('Validation errors: ' . implode("\n", $messages)); + } else { + $this->assertEquals(array(), $messages); + } + } + + function testICalendarImportMultipleVTIMEZONESAndMultipleValidEvents() { + + $timezones = <<<EOT +BEGIN:VTIMEZONE +TZID:Europe/Berlin +BEGIN:DAYLIGHT +TZOFFSETFROM:+0100 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU +DTSTART:19810329T020000 +TZNAME:MESZ +TZOFFSETTO:+0200 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:+0200 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU +DTSTART:19961027T030000 +TZNAME:MEZ +TZOFFSETTO:+0100 +END:STANDARD +END:VTIMEZONE +BEGIN:VTIMEZONE +TZID:Europe/London +BEGIN:DAYLIGHT +TZOFFSETFROM:+0000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU +DTSTART:19810329T010000 +TZNAME:GMT+01:00 +TZOFFSETTO:+0100 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:+0100 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU +DTSTART:19961027T020000 +TZNAME:GMT +TZOFFSETTO:+0000 +END:STANDARD +END:VTIMEZONE +EOT; + + $event[] = <<<EOT +BEGIN:VEVENT +UID:foo1 +DTSTAMP:20140122T232710Z +DTSTART:20140101T010000Z +END:VEVENT +EOT; + + $event[] = <<<EOT +BEGIN:VEVENT +UID:foo2 +DTSTAMP:20140122T232710Z +DTSTART:20140101T020000Z +END:VEVENT +EOT; + + $event[] = <<<EOT +BEGIN:VEVENT +UID:foo3 +DTSTAMP:20140122T232710Z +DTSTART:20140101T030000Z +END:VEVENT +EOT; + + $data = <<<EOT +BEGIN:VCALENDAR +$timezones +$event[0] +$event[1] +$event[2] +END:VCALENDAR + +EOT; + $tempFile = $this->createStream($data); + + $objects = new ICalendar($tempFile); + + $return = ""; + $i = 0; + while($object=$objects->getNext()) { + + $expected = <<<EOT +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Sabre//Sabre VObject $this->version//EN +CALSCALE:GREGORIAN +$timezones +$event[$i] +END:VCALENDAR + +EOT; + $expected = str_replace("\n", "\r\n", $expected); + + $this->assertEquals($expected, $object->serialize()); + $return .= $object->serialize(); + $i++; + + } + + $this->assertEquals(array(), VObject\Reader::read($return)->validate()); + } + + function testICalendarImportWithOutVTIMEZONES() { + + $data = <<<EOT +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Apple Inc.//Mac OS X 10.8//EN +CALSCALE:GREGORIAN +BEGIN:VEVENT +CREATED:20120605T072109Z +UID:D6716295-C10F-4B20-82F9-E1A3026C7DCF +DTEND;VALUE=DATE:20120717 +TRANSP:TRANSPARENT +SUMMARY:Start Vorbereitung +DTSTART;VALUE=DATE:20120716 +DTSTAMP:20120605T072115Z +SEQUENCE:2 +BEGIN:VALARM +X-WR-ALARMUID:A99EDA6A-35EB-4446-B8BC-CDA3C60C627D +UID:A99EDA6A-35EB-4446-B8BC-CDA3C60C627D +TRIGGER:-PT15H +X-APPLE-DEFAULT-ALARM:TRUE +ATTACH;VALUE=URI:Basso +ACTION:AUDIO +END:VALARM +END:VEVENT +END:VCALENDAR + +EOT; + $tempFile = $this->createStream($data); + + $objects = new ICalendar($tempFile); + + $return = ""; + while($object=$objects->getNext()) { + $return .= $object->serialize(); + } + + $messages = VObject\Reader::read($return)->validate(); + $this->assertEquals(array(), $messages); + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Splitter/VCardTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Splitter/VCardTest.php new file mode 100644 index 0000000..7fa559d --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/Splitter/VCardTest.php @@ -0,0 +1,195 @@ +<?php + +namespace Sabre\VObject\Splitter; + +use Sabre\VObject; + +class VCardTest extends \PHPUnit_Framework_TestCase { + + function createStream($data) { + + $stream = fopen('php://memory','r+'); + fwrite($stream, $data); + rewind($stream); + return $stream; + + } + + function testVCardImportValidVCard() { + $data = <<<EOT +BEGIN:VCARD +UID:foo +END:VCARD +EOT; + $tempFile = $this->createStream($data); + + $objects = new VCard($tempFile); + + $count = 0; + while($objects->getNext()) { + $count++; + } + $this->assertEquals(1, $count); + + } + + /** + * @expectedException Sabre\VObject\ParseException + */ + function testVCardImportWrongType() { + $event[] = <<<EOT +BEGIN:VEVENT +UID:foo1 +DTSTAMP:20140122T233226Z +DTSTART:20140101T050000Z +END:VEVENT +EOT; + +$event[] = <<<EOT +BEGIN:VEVENT +UID:foo2 +DTSTAMP:20140122T233226Z +DTSTART:20140101T060000Z +END:VEVENT +EOT; + + $data = <<<EOT +BEGIN:VCALENDAR +$event[0] +$event[1] +END:VCALENDAR + +EOT; + $tempFile = $this->createStream($data); + + $splitter = new VCard($tempFile); + + while($object=$splitter->getNext()) { + } + + } + + function testVCardImportValidVCardsWithCategories() { + $data = <<<EOT +BEGIN:VCARD +UID:card-in-foo1-and-foo2 +CATEGORIES:foo1,foo2 +END:VCARD +BEGIN:VCARD +UID:card-in-foo1 +CATEGORIES:foo1 +END:VCARD +BEGIN:VCARD +UID:card-in-foo3 +CATEGORIES:foo3 +END:VCARD +BEGIN:VCARD +UID:card-in-foo1-and-foo3 +CATEGORIES:foo1\,foo3 +END:VCARD +EOT; + $tempFile = $this->createStream($data); + + $splitter = new VCard($tempFile); + + $count = 0; + while($object=$splitter->getNext()) { + $count++; + } + $this->assertEquals(4, $count); + + } + + function testVCardImportEndOfData() { + $data = <<<EOT +BEGIN:VCARD +UID:foo +END:VCARD +EOT; + $tempFile = $this->createStream($data); + + $objects = new VCard($tempFile); + $object=$objects->getNext(); + + $this->assertNull($objects->getNext()); + + + } + + /** + * @expectedException \Sabre\VObject\ParseException + */ + function testVCardImportCheckInvalidArgumentException() { + $data = <<<EOT +BEGIN:FOO +END:FOO +EOT; + $tempFile = $this->createStream($data); + + $objects = new VCard($tempFile); + while($objects->getNext()) { } + + } + + function testVCardImportMultipleValidVCards() { + $data = <<<EOT +BEGIN:VCARD +UID:foo +END:VCARD +BEGIN:VCARD +UID:foo +END:VCARD +EOT; + $tempFile = $this->createStream($data); + + $objects = new VCard($tempFile); + + $count = 0; + while($objects->getNext()) { + $count++; + } + $this->assertEquals(2, $count); + + } + + function testImportMultipleSeparatedWithNewLines() { + $data = <<<EOT +BEGIN:VCARD +UID:foo +END:VCARD + + +BEGIN:VCARD +UID:foo +END:VCARD + + +EOT; + $tempFile = $this->createStream($data); + $objects = new VCard($tempFile); + + $count = 0; + while ($objects->getNext()) { + $count++; + } + $this->assertEquals(2, $count); + } + + function testVCardImportVCardWithoutUID() { + $data = <<<EOT +BEGIN:VCARD +END:VCARD +EOT; + $tempFile = $this->createStream($data); + + $objects = new VCard($tempFile); + + $count = 0; + while($objects->getNext()) { + $count++; + } + + $this->assertEquals(1, $count); + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/StringUtilTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/StringUtilTest.php new file mode 100644 index 0000000..8e0bc48 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/StringUtilTest.php @@ -0,0 +1,55 @@ +<?php + +namespace Sabre\VObject; + +class StringUtilTest extends \PHPUnit_Framework_TestCase { + + function testNonUTF8() { + + $string = StringUtil::isUTF8(chr(0xbf)); + + $this->assertEquals(false, $string); + + } + + function testIsUTF8() { + + $string = StringUtil::isUTF8('I 💚 SabreDAV'); + + $this->assertEquals(true, $string); + + } + + function testUTF8ControlChar() { + + $string = StringUtil::isUTF8(chr(0x00)); + + $this->assertEquals(false, $string); + + } + + function testConvertToUTF8nonUTF8() { + + $string = StringUtil::convertToUTF8(chr(0xbf)); + + $this->assertEquals(utf8_encode(chr(0xbf)), $string); + + } + + function testConvertToUTF8IsUTF8() { + + $string = StringUtil::convertToUTF8('I 💚 SabreDAV'); + + $this->assertEquals('I 💚 SabreDAV', $string); + + } + + function testConvertToUTF8ControlChar() { + + $string = StringUtil::convertToUTF8(chr(0x00)); + + $this->assertEquals('', $string); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/TestCase.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/TestCase.php new file mode 100644 index 0000000..d81559d --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/TestCase.php @@ -0,0 +1,50 @@ +<?php + +namespace Sabre\VObject; + +class TestCase extends \PHPUnit_Framework_TestCase { + + /** + * This method tests wether two vcards or icalendar objects are + * semantically identical. + * + * It supports objects being supplied as strings, streams or + * Sabre\VObject\Component instances. + * + * PRODID is removed from both objects as this is often variable. + * + * @param resource|string|Component $expected + * @param resource|string|Component $actual + * @param string $message + */ + function assertVObjEquals($expected, $actual, $message = '') { + + $self = $this; + $getObj = function($input) use ($self) { + + if (is_resource($input)) { + $input = stream_get_contents($input); + } + if (is_string($input)) { + $input = Reader::read($input); + } + if (!$input instanceof Component) { + $this->fail('Input must be a string, stream or VObject component'); + } + unset($input->PRODID); + return $input; + + }; + + $expected = $getObj($expected); + $actual = $getObj($actual); + + $this->assertEquals( + $expected->serialize(), + $actual->serialize(), + $message + ); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/TimeZoneUtilTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/TimeZoneUtilTest.php new file mode 100644 index 0000000..9c747e8 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/TimeZoneUtilTest.php @@ -0,0 +1,369 @@ +<?php + +namespace Sabre\VObject; + +class TimezoneUtilTest extends \PHPUnit_Framework_TestCase { + + function setUp() { + + // clearning the tz cache + TimezoneUtil::$map = null; + + } + + /** + * @dataProvider getMapping + */ + function testCorrectTZ($timezoneName) { + + try { + $tz = new \DateTimeZone($timezoneName); + $this->assertInstanceOf('DateTimeZone', $tz); + } catch (\Exception $e) { + if (strpos($e->getMessage(), "Unknown or bad timezone")!==false) { + $this->markTestSkipped($timezoneName . ' is not (yet) supported in this PHP version. Update pecl/timezonedb'); + } else { + throw $e; + } + + } + + } + + function getMapping() { + + TimeZoneUtil::loadTzMaps(); + + // PHPUNit requires an array of arrays + return array_map( + function($value) { + return array($value); + }, + TimeZoneUtil::$map + ); + + } + + function testExchangeMap() { + + $vobj = <<<HI +BEGIN:VCALENDAR +METHOD:REQUEST +VERSION:2.0 +BEGIN:VTIMEZONE +TZID:foo +X-MICROSOFT-CDO-TZID:2 +BEGIN:STANDARD +DTSTART:16010101T030000 +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=10;BYDAY=-1SU +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:16010101T020000 +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=3;BYDAY=-1SU +END:DAYLIGHT +END:VTIMEZONE +BEGIN:VEVENT +DTSTAMP:20120416T092149Z +DTSTART;TZID="foo":20120418T1 + 00000 +SUMMARY:Begin Unterhaltsreinigung +UID:040000008200E00074C5B7101A82E0080000000010DA091DC31BCD01000000000000000 + 0100000008FECD2E607780649BE5A4C9EE6418CBC + 000 +END:VEVENT +END:VCALENDAR +HI; + + $tz = TimeZoneUtil::getTimeZone('foo', Reader::read($vobj)); + $ex = new \DateTimeZone('Europe/Lisbon'); + + $this->assertEquals($ex->getName(), $tz->getName()); + + } + + function testWetherMicrosoftIsStillInsane() { + + $vobj = <<<HI +BEGIN:VCALENDAR +METHOD:REQUEST +VERSION:2.0 +BEGIN:VTIMEZONE +TZID:(GMT+01.00) Sarajevo/Warsaw/Zagreb +X-MICROSOFT-CDO-TZID:2 +BEGIN:STANDARD +DTSTART:16010101T030000 +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=10;BYDAY=-1SU +END:STANDARD +END:VTIMEZONE +END:VCALENDAR +HI; + + $tz = TimeZoneUtil::getTimeZone('(GMT+01.00) Sarajevo/Warsaw/Zagreb', Reader::read($vobj)); + $ex = new \DateTimeZone('Europe/Sarajevo'); + + $this->assertEquals($ex->getName(), $tz->getName()); + + } + + function testUnknownExchangeId() { + + $vobj = <<<HI +BEGIN:VCALENDAR +METHOD:REQUEST +VERSION:2.0 +BEGIN:VTIMEZONE +TZID:foo +X-MICROSOFT-CDO-TZID:2000 +BEGIN:STANDARD +DTSTART:16010101T030000 +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=10;BYDAY=-1SU +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:16010101T020000 +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=3;BYDAY=-1SU +END:DAYLIGHT +END:VTIMEZONE +BEGIN:VEVENT +DTSTAMP:20120416T092149Z +DTSTART;TZID="foo":20120418T1 + 00000 +SUMMARY:Begin Unterhaltsreinigung +UID:040000008200E00074C5B7101A82E0080000000010DA091DC31BCD01000000000000000 + 0100000008FECD2E607780649BE5A4C9EE6418CBC +DTEND;TZID="Sarajevo, Skopje, Sofija, Vilnius, Warsaw, Zagreb":20120418T103 + 000 +END:VEVENT +END:VCALENDAR +HI; + + $tz = TimeZoneUtil::getTimeZone('foo', Reader::read($vobj)); + $ex = new \DateTimeZone(date_default_timezone_get()); + $this->assertEquals($ex->getName(), $tz->getName()); + + } + + function testWindowsTimeZone() { + + $tz = TimeZoneUtil::getTimeZone('Eastern Standard Time'); + $ex = new \DateTimeZone('America/New_York'); + $this->assertEquals($ex->getName(), $tz->getName()); + + } + + /** + * @dataProvider getPHPTimeZoneIdentifiers + */ + function testTimeZoneIdentifiers($tzid) { + + $tz = TimeZoneUtil::getTimeZone($tzid); + $ex = new \DateTimeZone($tzid); + + $this->assertEquals($ex->getName(), $tz->getName()); + + } + + /** + * @dataProvider getPHPTimeZoneBCIdentifiers + */ + function testTimeZoneBCIdentifiers($tzid) { + + $tz = TimeZoneUtil::getTimeZone($tzid); + $ex = new \DateTimeZone($tzid); + + $this->assertEquals($ex->getName(), $tz->getName()); + + } + + function getPHPTimeZoneIdentifiers() { + + // PHPUNit requires an array of arrays + return array_map( + function($value) { + return array($value); + }, + \DateTimeZone::listIdentifiers() + ); + + } + + function getPHPTimeZoneBCIdentifiers() { + + // PHPUNit requires an array of arrays + return array_map( + function($value) { + return array($value); + }, + TimeZoneUtil::getIdentifiersBC() + ); + + } + + function testTimezoneOffset() { + + $tz = TimeZoneUtil::getTimeZone('GMT-0400', null, true); + + if (version_compare(PHP_VERSION, '5.5.10', '>=') && !defined('HHVM_VERSION')) { + $ex = new \DateTimeZone('-04:00'); + } else { + $ex = new \DateTimeZone('Etc/GMT-4'); + } + $this->assertEquals($ex->getName(), $tz->getName()); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testTimezoneFail() { + + $tz = TimeZoneUtil::getTimeZone('FooBar', null, true); + + } + + function testFallBack() { + + $vobj = <<<HI +BEGIN:VCALENDAR +METHOD:REQUEST +VERSION:2.0 +BEGIN:VTIMEZONE +TZID:foo +BEGIN:STANDARD +DTSTART:16010101T030000 +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=10;BYDAY=-1SU +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:16010101T020000 +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +RRULE:FREQ=YEARLY;WKST=MO;INTERVAL=1;BYMONTH=3;BYDAY=-1SU +END:DAYLIGHT +END:VTIMEZONE +BEGIN:VEVENT +DTSTAMP:20120416T092149Z +DTSTART;TZID="foo":20120418T1 + 00000 +SUMMARY:Begin Unterhaltsreinigung +UID:040000008200E00074C5B7101A82E0080000000010DA091DC31BCD01000000000000000 + 0100000008FECD2E607780649BE5A4C9EE6418CBC + 000 +END:VEVENT +END:VCALENDAR +HI; + + $tz = TimeZoneUtil::getTimeZone('foo', Reader::read($vobj)); + $ex = new \DateTimeZone(date_default_timezone_get()); + $this->assertEquals($ex->getName(), $tz->getName()); + + } + + function testLjubljanaBug() { + + $vobj = <<<HI +BEGIN:VCALENDAR +CALSCALE:GREGORIAN +PRODID:-//Ximian//NONSGML Evolution Calendar//EN +VERSION:2.0 +BEGIN:VTIMEZONE +TZID:/freeassociation.sourceforge.net/Tzfile/Europe/Ljubljana +X-LIC-LOCATION:Europe/Ljubljana +BEGIN:STANDARD +TZNAME:CET +DTSTART:19701028T030000 +RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:CEST +DTSTART:19700325T020000 +RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3 +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +END:DAYLIGHT +END:VTIMEZONE +BEGIN:VEVENT +UID:foo +DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/Europe/Ljubljana: + 20121003T080000 +DTEND;TZID=/freeassociation.sourceforge.net/Tzfile/Europe/Ljubljana: + 20121003T083000 +TRANSP:OPAQUE +SEQUENCE:2 +SUMMARY:testing +CREATED:20121002T172613Z +LAST-MODIFIED:20121002T172613Z +END:VEVENT +END:VCALENDAR + +HI; + + + $tz = TimeZoneUtil::getTimeZone('/freeassociation.sourceforge.net/Tzfile/Europe/Ljubljana', Reader::read($vobj)); + $ex = new \DateTimeZone('Europe/Ljubljana'); + $this->assertEquals($ex->getName(), $tz->getName()); + + } + + function testWeirdSystemVLICs() { + +$vobj = <<<HI +BEGIN:VCALENDAR +CALSCALE:GREGORIAN +PRODID:-//Ximian//NONSGML Evolution Calendar//EN +VERSION:2.0 +BEGIN:VTIMEZONE +TZID:/freeassociation.sourceforge.net/Tzfile/SystemV/EST5EDT +X-LIC-LOCATION:SystemV/EST5EDT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19701104T020000 +RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19700311T020000 +RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +END:VTIMEZONE +BEGIN:VEVENT +UID:20121026T021107Z-6301-1000-1-0@chAir +DTSTAMP:20120905T172126Z +DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/SystemV/EST5EDT: + 20121026T153000 +DTEND;TZID=/freeassociation.sourceforge.net/Tzfile/SystemV/EST5EDT: + 20121026T160000 +TRANSP:OPAQUE +SEQUENCE:5 +SUMMARY:pick up Ibby +CLASS:PUBLIC +CREATED:20121026T021108Z +LAST-MODIFIED:20121026T021118Z +X-EVOLUTION-MOVE-CALENDAR:1 +END:VEVENT +END:VCALENDAR +HI; + + $tz = TimeZoneUtil::getTimeZone('/freeassociation.sourceforge.net/Tzfile/SystemV/EST5EDT', Reader::read($vobj), true); + $ex = new \DateTimeZone('America/New_York'); + $this->assertEquals($ex->getName(), $tz->getName()); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/UUIDUtilTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/UUIDUtilTest.php new file mode 100644 index 0000000..d33a879 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/UUIDUtilTest.php @@ -0,0 +1,37 @@ +<?php + +namespace Sabre\VObject; + +class UUIDUtilTest extends \PHPUnit_Framework_TestCase { + + function testValidateUUID() { + + $this->assertTrue( + UUIDUtil::validateUUID('11111111-2222-3333-4444-555555555555') + ); + $this->assertFalse( + UUIDUtil::validateUUID(' 11111111-2222-3333-4444-555555555555') + ); + $this->assertTrue( + UUIDUtil::validateUUID('ffffffff-2222-3333-4444-555555555555') + ); + $this->assertFalse( + UUIDUtil::validateUUID('fffffffg-2222-3333-4444-555555555555') + ); + + } + + /** + * @depends testValidateUUID + */ + function testGetUUID() { + + $this->assertTrue( + UUIDUtil::validateUUID( + UUIDUtil::getUUID() + ) + ); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/VCard21Test.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/VCard21Test.php new file mode 100644 index 0000000..9c6994d --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/VCard21Test.php @@ -0,0 +1,52 @@ +<?php + +namespace Sabre\VObject; + +/** + * Assorted vcard 2.1 tests. + */ +class VCard21Test extends \PHPUnit_Framework_TestCase { + + function testPropertyWithNoName() { + + $input = <<<VCF +BEGIN:VCARD\r +VERSION:2.1\r +EMAIL;HOME;WORK:evert@fruux.com\r +END:VCARD\r + +VCF; + + $vobj = Reader::read($input); + $output = $vobj->serialize($input); + + $this->assertEquals($input, $output); + + } + + function testPropertyPadValueCount() { + + $input = <<<VCF +BEGIN:VCARD +VERSION:2.1 +N:Foo +END:VCARD + +VCF; + + $vobj = Reader::read($input); + $output = $vobj->serialize($input); + + $expected = <<<VCF +BEGIN:VCARD\r +VERSION:2.1\r +N:Foo;;;;\r +END:VCARD\r + +VCF; + + + $this->assertEquals($expected, $output); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/VCardConverterTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/VCardConverterTest.php new file mode 100644 index 0000000..9e7b2df --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/VCardConverterTest.php @@ -0,0 +1,531 @@ +<?php + +namespace Sabre\VObject; + +class VCardConverterTest extends TestCase { + + function testConvert30to40() { + + $input = <<<IN +BEGIN:VCARD +VERSION:3.0 +PRODID:foo +FN;CHARSET=UTF-8:Steve +TEL;TYPE=PREF,HOME:+1 555 666 777 +ITEM1.TEL:+1 444 555 666 +ITEM1.X-ABLABEL:CustomLabel +PHOTO;ENCODING=b;TYPE=JPEG,HOME:Zm9v +PHOTO;ENCODING=b;TYPE=GIF:Zm9v +PHOTO;X-PARAM=FOO;ENCODING=b;TYPE=PNG:Zm9v +PHOTO;VALUE=URI:http://example.org/foo.png +X-ABShowAs:COMPANY +END:VCARD +IN; + + $output = <<<OUT +BEGIN:VCARD +VERSION:4.0 +FN:Steve +TEL;PREF=1;TYPE=HOME:+1 555 666 777 +ITEM1.TEL:+1 444 555 666 +ITEM1.X-ABLABEL:CustomLabel +PHOTO;TYPE=HOME:data:image/jpeg;base64,Zm9v +PHOTO:data:image/gif;base64,Zm9v +PHOTO;X-PARAM=FOO:data:image/png;base64,Zm9v +PHOTO:http://example.org/foo.png +KIND:ORG +END:VCARD +OUT; + + $vcard = Reader::read($input); + $vcard = $vcard->convert(Document::VCARD40); + + $this->assertVObjEquals( + $output, + $vcard + ); + + } + + function testConvert40to40() { + + $input = <<<IN +BEGIN:VCARD +VERSION:4.0 +FN:Steve +TEL;PREF=1;TYPE=HOME:+1 555 666 777 +PHOTO:data:image/jpeg;base64,Zm9v +PHOTO:data:image/gif;base64,Zm9v +PHOTO;X-PARAM=FOO:data:image/png;base64,Zm9v +PHOTO:http://example.org/foo.png +END:VCARD + +IN; + + $output = <<<OUT +BEGIN:VCARD +VERSION:4.0 +FN:Steve +TEL;PREF=1;TYPE=HOME:+1 555 666 777 +PHOTO:data:image/jpeg;base64,Zm9v +PHOTO:data:image/gif;base64,Zm9v +PHOTO;X-PARAM=FOO:data:image/png;base64,Zm9v +PHOTO:http://example.org/foo.png +END:VCARD + +OUT; + + $vcard = Reader::read($input); + $vcard = $vcard->convert(Document::VCARD40); + + $this->assertVObjEquals( + $output, + $vcard + ); + + } + + function testConvert21to40() { + + $input = <<<IN +BEGIN:VCARD +VERSION:2.1 +N:Family;Johnson +FN:Johnson Family +TEL;HOME;VOICE:555-12345-345 +ADR;HOME:;;100 Street Lane;Saubel Beach;ON;H0H0H0 +LABEL;HOME;ENCODING=QUOTED-PRINTABLE:100 Street Lane=0D=0ASaubel Beach, + ON H0H0H0 +REV:20110731T040251Z +UID:12345678 +END:VCARD +IN; + + $output = <<<OUT +BEGIN:VCARD +VERSION:4.0 +N:Family;Johnson;;; +FN:Johnson Family +TEL;TYPE=HOME,VOICE:555-12345-345 +ADR;TYPE=HOME:;;100 Street Lane;Saubel Beach;ON;H0H0H0; +REV:20110731T040251Z +UID:12345678 +END:VCARD + +OUT; + + $vcard = Reader::read($input); + $vcard = $vcard->convert(Document::VCARD40); + + $this->assertVObjEquals( + $output, + $vcard + ); + + } + + function testConvert30to30() { + + $input = <<<IN +BEGIN:VCARD +VERSION:3.0 +PRODID:foo +FN;CHARSET=UTF-8:Steve +TEL;TYPE=PREF,HOME:+1 555 666 777 +PHOTO;ENCODING=b;TYPE=JPEG:Zm9v +PHOTO;ENCODING=b;TYPE=GIF:Zm9v +PHOTO;X-PARAM=FOO;ENCODING=b;TYPE=PNG:Zm9v +PHOTO;VALUE=URI:http://example.org/foo.png +END:VCARD + +IN; + + $output = <<<OUT +BEGIN:VCARD +VERSION:3.0 +PRODID:foo +FN;CHARSET=UTF-8:Steve +TEL;TYPE=PREF,HOME:+1 555 666 777 +PHOTO;ENCODING=b;TYPE=JPEG:Zm9v +PHOTO;ENCODING=b;TYPE=GIF:Zm9v +PHOTO;X-PARAM=FOO;ENCODING=b;TYPE=PNG:Zm9v +PHOTO;VALUE=URI:http://example.org/foo.png +END:VCARD + +OUT; + + $vcard = Reader::read($input); + $vcard = $vcard->convert(Document::VCARD30); + + $this->assertVObjEquals( + $output, + $vcard + ); + + } + + function testConvert40to30() { + + $input = <<<IN +BEGIN:VCARD +VERSION:4.0 +PRODID:foo +FN:Steve +TEL;PREF=1;TYPE=HOME:+1 555 666 777 +PHOTO:data:image/jpeg;base64,Zm9v +PHOTO:data:image/gif,foo +PHOTO;X-PARAM=FOO:data:image/png;base64,Zm9v +PHOTO:http://example.org/foo.png +KIND:ORG +END:VCARD + +IN; + + $output = <<<OUT +BEGIN:VCARD +VERSION:3.0 +FN:Steve +TEL;TYPE=PREF,HOME:+1 555 666 777 +PHOTO;ENCODING=b;TYPE=JPEG:Zm9v +PHOTO;ENCODING=b;TYPE=GIF:Zm9v +PHOTO;ENCODING=b;TYPE=PNG;X-PARAM=FOO:Zm9v +PHOTO;VALUE=URI:http://example.org/foo.png +X-ABSHOWAS:COMPANY +END:VCARD + +OUT; + + $vcard = Reader::read($input); + $vcard = $vcard->convert(Document::VCARD30); + + $this->assertVObjEquals( + $output, + $vcard + ); + + } + + function testConvertGroupCard() { + + $input = <<<IN +BEGIN:VCARD +VERSION:3.0 +PRODID:foo +X-ADDRESSBOOKSERVER-KIND:GROUP +END:VCARD + +IN; + + $output = <<<OUT +BEGIN:VCARD +VERSION:4.0 +KIND:GROUP +END:VCARD + +OUT; + + $vcard = Reader::read($input); + $vcard = $vcard->convert(Document::VCARD40); + + $this->assertVObjEquals( + $output, + $vcard + ); + + $input = $output; + $output = <<<OUT +BEGIN:VCARD +VERSION:3.0 +X-ADDRESSBOOKSERVER-KIND:GROUP +END:VCARD + +OUT; + + $vcard = Reader::read($input); + $vcard = $vcard->convert(Document::VCARD30); + + $this->assertVObjEquals( + $output, + $vcard + ); + + } + + function testBDAYConversion() { + + $input = <<<IN +BEGIN:VCARD +VERSION:3.0 +PRODID:foo +BDAY;X-APPLE-OMIT-YEAR=1604:1604-04-16 +END:VCARD + +IN; + + $output = <<<OUT +BEGIN:VCARD +VERSION:4.0 +BDAY:--04-16 +END:VCARD + +OUT; + + $vcard = Reader::read($input); + $vcard = $vcard->convert(Document::VCARD40); + + $this->assertVObjEquals( + $output, + $vcard + ); + + $input = $output; + $output = <<<OUT +BEGIN:VCARD +VERSION:3.0 +BDAY;X-APPLE-OMIT-YEAR=1604:1604-04-16 +END:VCARD + +OUT; + + $vcard = Reader::read($input); + $vcard = $vcard->convert(Document::VCARD30); + + $this->assertVObjEquals( + $output, + $vcard + ); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testUnknownSourceVCardVersion() { + + $input = <<<IN +BEGIN:VCARD +VERSION:4.2 +PRODID:foo +FN;CHARSET=UTF-8:Steve +TEL;TYPE=PREF,HOME:+1 555 666 777 +ITEM1.TEL:+1 444 555 666 +ITEM1.X-ABLABEL:CustomLabel +PHOTO;ENCODING=b;TYPE=JPEG,HOME:Zm9v +PHOTO;ENCODING=b;TYPE=GIF:Zm9v +PHOTO;X-PARAM=FOO;ENCODING=b;TYPE=PNG:Zm9v +PHOTO;VALUE=URI:http://example.org/foo.png +X-ABShowAs:COMPANY +END:VCARD + +IN; + + $vcard = Reader::read($input); + $vcard->convert(Document::VCARD40); + + } + + /** + * @expectedException InvalidArgumentException + */ + function testUnknownTargetVCardVersion() { + + $input = <<<IN +BEGIN:VCARD +VERSION:3.0 +PRODID:foo +END:VCARD + +IN; + + $vcard = Reader::read($input); + $vcard->convert(Document::VCARD21); + + } + + function testConvertIndividualCard() { + + $input = <<<IN +BEGIN:VCARD +VERSION:4.0 +PRODID:foo +KIND:INDIVIDUAL +END:VCARD + +IN; + + $output = <<<OUT +BEGIN:VCARD +VERSION:3.0 +END:VCARD + +OUT; + + $vcard = Reader::read($input); + $vcard = $vcard->convert(Document::VCARD30); + + $this->assertVObjEquals( + $output, + $vcard + ); + + $input = $output; + $output = <<<OUT +BEGIN:VCARD +VERSION:4.0 +END:VCARD + +OUT; + + $vcard = Reader::read($input); + $vcard = $vcard->convert(Document::VCARD40); + + $this->assertVObjEquals( + $output, + $vcard + ); + + } + + function testAnniversary() { + + $input = <<<IN +BEGIN:VCARD +VERSION:4.0 +ITEM1.ANNIVERSARY:20081210 +END:VCARD + +IN; + + $output = <<<'OUT' +BEGIN:VCARD +VERSION:3.0 +ITEM1.X-ABDATE;VALUE=DATE-AND-OR-TIME:20081210 +ITEM1.X-ABLABEL:_$!<Anniversary>!$_ +ITEM1.X-ANNIVERSARY;VALUE=DATE-AND-OR-TIME:20081210 +END:VCARD + +OUT; + + $vcard = Reader::read($input); + $vcard = $vcard->convert(Document::VCARD30); + + $this->assertVObjEquals( + $output, + $vcard + ); + + // Swapping input and output + list( + $input, + $output + ) = array( + $output, + $input + ); + + $vcard = Reader::read($input); + $vcard = $vcard->convert(Document::VCARD40); + + $this->assertVObjEquals( + $output, + $vcard + ); + + } + + function testMultipleAnniversaries() { + + $input = <<<IN +BEGIN:VCARD +VERSION:4.0 +ITEM1.ANNIVERSARY:20081210 +ITEM2.ANNIVERSARY:20091210 +ITEM3.ANNIVERSARY:20101210 +END:VCARD + +IN; + + $output = <<<'OUT' +BEGIN:VCARD +VERSION:3.0 +ITEM1.X-ABDATE;VALUE=DATE-AND-OR-TIME:20081210 +ITEM1.X-ABLABEL:_$!<Anniversary>!$_ +ITEM1.X-ANNIVERSARY;VALUE=DATE-AND-OR-TIME:20081210 +ITEM2.X-ABDATE;VALUE=DATE-AND-OR-TIME:20091210 +ITEM2.X-ABLABEL:_$!<Anniversary>!$_ +ITEM2.X-ANNIVERSARY;VALUE=DATE-AND-OR-TIME:20091210 +ITEM3.X-ABDATE;VALUE=DATE-AND-OR-TIME:20101210 +ITEM3.X-ABLABEL:_$!<Anniversary>!$_ +ITEM3.X-ANNIVERSARY;VALUE=DATE-AND-OR-TIME:20101210 +END:VCARD + +OUT; + + $vcard = Reader::read($input); + $vcard = $vcard->convert(Document::VCARD30); + + $this->assertVObjEquals( + $output, + $vcard + ); + + // Swapping input and output + list( + $input, + $output + ) = array( + $output, + $input + ); + + $vcard = Reader::read($input); + $vcard = $vcard->convert(Document::VCARD40); + + $this->assertVObjEquals( + $output, + $vcard + ); + + } + + function testNoLabel() { + + $input = <<<VCF +BEGIN:VCARD +VERSION:3.0 +UID:foo +N:Doe;John;;; +FN:John Doe +item1.X-ABDATE;type=pref:2008-12-11 +END:VCARD + +VCF; + + $vcard = Reader::read($input); + + $this->assertInstanceOf('Sabre\\VObject\\Component\\VCard', $vcard); + $vcard = $vcard->convert(Document::VCARD40); + $vcard = $vcard->serialize(); + + $converted = Reader::read($vcard); + $converted->validate(); + + $version = Version::VERSION; + + $expected = <<<VCF +BEGIN:VCARD +VERSION:4.0 +PRODID:-//Sabre//Sabre VObject $version//EN +UID:foo +N:Doe;John;;; +FN:John Doe +ITEM1.X-ABDATE;PREF=1:2008-12-11 +END:VCARD + +VCF; + + $this->assertEquals($expected, str_replace("\r","", $vcard)); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/VersionTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/VersionTest.php new file mode 100644 index 0000000..b3d41b2 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/VersionTest.php @@ -0,0 +1,14 @@ +<?php + +namespace Sabre\VObject; + +class VersionTest extends \PHPUnit_Framework_TestCase { + + function testString() { + + $v = Version::VERSION; + $this->assertEquals(-1, version_compare('2.0.0',$v)); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/issue153.vcf b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/issue153.vcf new file mode 100644 index 0000000..5fb0fa2 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/issue153.vcf @@ -0,0 +1,352 @@ +BEGIN:VCARD +VERSION:3.0 +N:Benutzer;Test;;; +FN:Test Benutzer +PHOTO;BASE64: + /9j/4AAQSkZJRgABAQAAAQABAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQA + AAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAABQKADAAQAAAABAAABQAAAAAD/2wBD + AAIBAQIBAQICAQICAgICAwUDAwMDAwYEBAMFBwYHBwcGBgYHCAsJBwgKCAYGCQ0JCgsLDAwMBwkN + Dg0MDgsMDAv/2wBDAQICAgMCAwUDAwULCAYICwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsL + CwsLCwsLCwsLCwsLCwsLCwsLCwv/wAARCAFAAUADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAA + AAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKB + kaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZn + aGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT + 1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcI + CQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAV + YnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6 + goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk + 5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD8J7JbO8tYo1tIFCDLOVG5qfdaVZRwmSOFWzyA + F4H1rLt5WViMhdp6HgmtKK8O3B+4Rhx6fSgBI9FtjaNN5aErwRjilSys7lFAt41xyTtqc2yJCVlY + 7eqgGqv2jyLcebjZnGPWncdzT0+w0u5eQXtrGiBcIyoPmNMXwpb/AGMTSRRbH6YAyPwqK21GKdfL + BAVfu+1SQX4jnjKFsp03dPypCKN9oEaKSkC7R0bGKpnSlSPdHErZOORXV3Ouy337sCLB6kpx+FY0 + t+VfyrgcbuCB1oAfoMemrcImq2sZX+I7ATXS618PdK1DRlvvDEaMq5LoV2nisx4LVrUfu5BOePau + m8EQS6PY3HmFXjljKhTzjOf1oA4mz8OxvMrLbW5RD8wbByKg1LRrRriRYY408w/KAMba1pRaWt/H + a6a7CVm2u7N8lUPEujzaRekzSK6tgqVNAGNBZJauY5Yon92GTRJp0ROY0Un0A4q3c2odkaYOMjii + KL7NIDGcj1NDAZBplmmWv1xnoFHStfS/DFpewqYoYm3DutZ8lv8AapdyOqk8EVteEbSe3KBSrDrQ + BT8S+HbawiiWGCAPjsuMnPesqHS4JSFlSMP7DitbXbvfrkkM2eGw3p+FMfTh5X+hr8w7t3oAhOhW + u8MkMZUY3fL0Heo9UsrN5FFrbxKmMBgoG41fWFra0Acjpzg9aoXjtgRoo29vagCoun27kbY059qn + bwykskYjRArdTT7GEl2UqMr2q/JtVU27iR15NADdK8DC/wBPle2iicxNg5ALH6Umm6FZ/a3ttQt4 + g2Cqnb0PbJ+tamn3j6ZCW0nILfeBORWVfO4dhLw7fMW7560AZuqeHf7MuTFcRpv6qVGVx70q2Eci + QwyW0SsPvOqjJrUtb6S9tHQKGeMZYuM8VUs7gRxbrncy9mWgB1x4QtTHvsQWkHJVhhax3tkhugHh + UkfeAXIFdPZ3v2uxkQ9G4jI6/j+tYun3r2Fy6yxeb2Py5IoAqXenJ5xaGNNvXH/1qcLSGeBdkSg9 + CcdaswC3be0pfexOMnpn2qaS1KQkQASKoydvLCgDNi09RKTNCuO2BxVjSobc6gqXMERQHkleDUsc + u9VADbG6qOWAp11bLbptkjlCkZRsde9AFi5sbO3kKfZYTnkHaOlVbuO2F5thtYcADjaKXUpHj8ku + Co2VDFL5wLeg696YFwQ2z7Qtlb8HJO0c1Zsr7T7a9kL6XazZ4CmMFRWfHdkEgjGRjPpU9raP5LSP + j5h2pAWdQ0+z1KdG+y21qvcRqBn8qXSvC+iTu63ssqyE/IAuR+NQwSrGm1g+c8E9qiSQW9wPNYYP + OR2oAW68GNa28k3lwGNHwvzDJGfSqM9nHBgm3j59QMVdmma4zIjsUBHy5OKp6o8s2BJjZjjAoAro + /nysbgYY9zWmLPCR+WQQwyaz4k2F/Pbft/GtKxvUeFN+B2x+NAEptsWpZSdo9etZe8su2X7pPFdU + LeOazKqVwevNYt7pw5EA5HIxQBQA8tAIeGz1NWIJvJlhW5OQBzjrUMR/eN9pwoXjB4qQ3ERJeYcy + 9P8AZoA0jf8AmybVxsHAFS6jp63ixmwjIwOfrWfaou12GcDpmt/w5qJhXc6hh2GM0AZkHiRpblVl + G0RjGMdxXQ+H/E0Rm+bjdw1crqEHm3EksY4Y9PTmq0cskc42qUOfpmgDovHOhLBOZ9O+aEnIUdRW + QZft1sgum/1Ywua3fDfiFDL5WoEPEwxzzirPizwTFPZC60kYUjcAp4NAHPSq91EoRS3061DHD9nb + 94Mkfw020v57GbcCRt4IIqzNcedIH2jc3JyOaAIYrRZmJxtNdB4fkGn2hluBgBR+NZ2n2X9ozAQD + 5qvaxGbKIRXkuFU4C96AMDxBKZdQkuEUkStuUegpNM1eWScAkqpHTHNPlwbjMzExZ4Pal1PS/s6+ + dY/6vuwPSgC9G8c0A+1xEknrnpUVxaeXNm2dVUfjVazvEZAEkMrccZzV1YYyBIhJP8SZ6fhQBSmV + 4JfMVT+96UJdSQdcMO4A6fjVmTUoJiqTOMJ/q+elRyQs0TtaxF0PVhzmgCzpd55r7YI2HHPTmrV0 + sDTF7gnJXGO4OKyNKgn80NbFhjoBzWjqdg6SISPmIBOaAKVnI1leyhsMJOD7CqOqRtZqotjiFulW + rhsSMshKH1ogsZbmF475TKifdf0oApabevHIAhCYOdxp0t59luS0I+995uxqpdRyWsrqmXGeCR/K + rVlZfaogqv8AvD/CaAIY42kV3K5zzn1p9jNLp6u/A80YPNWWsJNPAVpC4JAZT2HfFWJoVmVVjhVk + HTPrQBPoi2wsoo4APtBHL+tP1mS5uVEFxgJGNqH15plp5WmyBriMRsowM8UybXTNdbrpd6A/KKAD + xbJAGs44FIPlnd9c/wD16ynt/LiDW2SR2qa5vP7RnMs6BNuQMd6jhkAUb2K8+tADYp0fhj8w6itC + yQ3CFYeAOoqi8Uew+UMuf4u9T2NwIW+UgMetO4FmS6RJ1ik6HqxHAqC+gimUiA8DvjrU0kcE8ieY + itu+8c0+bShaWxksSZoM4b0SkBTgha0cq33Cuc1SvrrLFV6jpWqbuGe1HnnDdAKy7i3WSY7OT2NN + AMulWSV8ZDNzxV7SlbaFjClx69Kpww7W3ct7jpUtnNJHd5UjZnt1NIDdt7h7NQ7qGfpt7VR1XVEh + dhEpP94/4VpafexTy7ZlbBGDVHxFbQh1j04HaOTkdKAM5ZVlYso3E+tVp4w8gx0Bqd7QxNu+6D6V + DIoVySxAx2NAFyNmli2pjYBz61paW3lWrFS3BwP8/hWJbTBFJy2D6HgfWtiTWPsqxraBHyOeBg0A + RSoLSTdIepzz0606exTWyQGMXljORTNT1B7+ECZR5fHzDqapfbHjbFkTsIwSTQA43ptyyS44Paun + 8N64Z7Bre4YlZBtU5+7XLTQbjwN4Pb+IfWn2lw9uyrIw2Z5HpQBv3GirHc7LxWVZOVI71FNp7WDg + QYlIIGD6VvaPdi+tljb5yeAzcn8DT9YtbPSpVhDM87jJ3Htjnn6UAUIrJreD7Si7MDoKhv8AUxqt + pGt5GqIOr9zRfLM8ZFgZGtex2nGe4zWKN8rsDhYx2JpJ3Atx+HxcRSzWcpcL/CRwaj0zW1sQy3cS + nsFPSoYJpbIl7dm8tT8wzV7+0hqEO1Y4lQ9cqMn9KoCp9kW7kaaxU+Yx+5j5etWrb/RGxfr5bkdu + lW7KFILpfspDbVyc1fjNnrLtHqOYWP8AFjGfxpAc/e6Ql/GzW4AfqBWfpupS6Xer5vPlHmMjg10V + 5pp0u4JhYNGvAYHrUn2WLWrVo41AvSMRZAC/8CPr1oAvafdWOuNG+lqDekY+zg8MPXPX/wDXWZrF + tcWNw0erKElB4Rf4R6c1BpqyaBdbrnEcwyAc4x06H0rQS9a9jUTgOXPzMwycexoAw7u1jYb3zkU3 + Srtgdk54PFamv2C2pDQbWjcfKCeSa56aJld23YA6ZOKFqBrXGjjULuOKxKuZOTn+H/OKwr/ztOvs + uCrg7RgVLYapPbXAEW4EkHJNdBNBH4gtgyhFmXuw60AVpbT7VpiPJ94jLetQWsDRSIYz8mec1c0+ + 1nexdrw7GjJXk/epsFtDPG0bOdw+b5SaAKWsXA+14Y71FQi5S4RvlAC8A0y5hHmHarhvQ9BVGSQx + sUXPHX3oAmDCJ8rzgHg96gQ+ZGWbg9vahNRG7EnalkkF6hEXyD270MCWF3aEhdue1OsmNnMAih/r + VaBgAUY8561PaubdnMxJXseuKANhIY5Assp2v12itZtAgubEi2nb5xuKYHWubstQaO6SVzujTqpP + X8K2rXWLRF8xZJPMfjAzgUAcxcNiaRSpUocc96sW+yNgZCMVF4lvJdRvTOYkj52jbgZ98D6VWmlY + 2qCUnJOKaVwCzviibANwYc8Utkdl7tbKhjxmpUspvm8tgn16ipigSEG4G4pxu9TSA27GeFbRlGGm + P3cdhUN8GEP2hV3JjafrWfpU/wBmuAcZLA4/Sr1trkarJHcRmSEZO3uTQBmrcbZCLoDZ2x1qOHSi + yebJIAPQipp4kmbzI1EQJ6GtCxsoHP8Ap91GB2yDQBlSWO+M/ZsBHHzZ71XkfMIWNgGU9vSt3U9N + t9m21uonz0Iz/hVCfRkjg82FhtHDGgCuZ8EMjDZjBzSZ8pAwU7XbGT0pWtEjjAZgV4PFOml2QKqk + OoOcU1qBNYRSrdkrhw3BIrah8KwXoV/m3PyVzyDWNp999kccgZq/ea7PFAGgZlJ6EUgN23thpdi4 + V1Eucr7ev9K53V/ER1a/MkuWdBtG04zioLrXJ5wDK2XAxmqVqmZ2YPtHJ/GgDsvC3i0ppr2d2ish + yFAHIz706bRLNdOPnErKw4y3NcvZ3pjA8o4kB61o3OpSX9nbx3QIkU/MwoAj/sGaPzFjlWSJjk46 + ioYYwqssjIHHAHpWm4ESN9nYDIFZV+I7uVI1wrY5b1oAtafcvb3W4MM9Nx6U/VZpNRys54ToU4zW + KXaDKrJuC8cVdtpi1gzs43HNAD9N195bdYtRIUR4wD1NX2KuA9uThuSQelcsZwzq9xyzfezV/SdX + e3m8pXJhkPKkUAdYZk8RywjVVJES7U2cE/WtA+HDHohuY3Uxg7RF/GeaPBlxaawMW6rHKnAU9SOO + lX/FFv8A2bpzTQk+cpAAz93nrQBx+r4c5CODEOA3Y+wrKu5V1C1GFKznkk9K6Wzv49fs8Xf7y7DY + MhGNgrmtX0s2t66WknnKvUp0/WgCnbrJFdot0NwJxkDFdDYp86oMjjIArJivxbR7LuMyEjKitS21 + MW8auuW44H93/PFAG15aXdr5Uv7uULkA/wCFc+Yvstw0at8+eoq/p+rm6vRJMNwIx9KranYySXSy + WEZZHOCw7UARXFyj5STAk7ntWVf2gALLyfUVoataLbfLO2SO/Ws2c+VwhLK3QDpQBmz2xAyCG56d + 6uWPlnCkFcjoTzUBkMc/3cZpwn8oZkDFs8HsKALN1apDIHOeaiLkRkMOtSXE6yxAsRUcdxldswIJ + HANMCuJW8xQgOP51oacWPPGAeRUUOIZQzDhecd6mbIcbPusM0gLmq6bHPohlhDeZuH4c1zzF1+Rs + HByDXTae0s0IhjjZg3GPWqOs+HpLCTbNGyb+cHrQBZitjPEzW/LL97vinw2v2m2aORec9AKXQbsw + ygBBiX72TWxfaS8kiGFQAwz8vWkncDlbqNraT5cjb/n+lMGckx8kjOa1tU2TxkPkMpxyKyrhJ4Wa + KIDbTAkgvIp7URzgBwe/BpZYrd4vmZWNZ81x5cgBXDdzVlIvtUOGIBHpQA2aEROpR8DsB2q3bvG9 + iySzEsTkLnrVMqViCZzt7nrT7GBVuQRnODQA6Q+Sx80A4HApEJB3BAR9K19EmhkvCJ0ZsKe3tUc8 + Mc1yy7cpn6YoAzoUiclnYYY8AHpUl8zRxqpPy9qtC2tULgSMAvQ460lzIl9b7YiDt4GaAKMMQlJ5 + z9Kj8gIW5yKnS3Crlzhh6d6k0mbyZT565Q5z60ANtrRpPmhzWhbwy7DJcDhhwMdKlt7aK+gb+z33 + yKdxVuMCqaz5cqGYfWgB6yu8rBB8o6Gs/UpjGQXBGPTvVmSfyImyepqrqjbIw3WgCDz1ib9yOTg4 + NbVlNBJYvlVBHt1rBaPzQWU4IHSn2FwRJslJxQA6e3M0O4oAzdB6VXR2iKGQENGOK0ms1eAkFjF/ + BjrVGaAo371smgC7pety2kwl06Vo5AOWXmuwm+Itv4g8Ota30aWlySAJQfmkP/1zXIeG4Y5SVBB3 + evamXGly2tydwG0nKkHpQBZ86fRbpBLI252y4PGRWhO8Ml1IbJhHn+BTnNU9O1oRwvDqqhB2lHJP + 4U6awb+z4JdKbzdh5ZurDHtQBat5LaRHiaOP7QejEZKD/Oauy+FI7W3Bsroyhxkq3QH8q5a7ujM8 + nWOQnBqTR9burCT98xdR60AbbaHc6ZG3ymJsZC/3hVnw/fNIXt7hygHzZp2oeIBqCxzqfmCgEe3+ + RVdrmLVAEtf3bxfOW/ve36UAV7+7DXMu5Q4/Os2e3eRWkiAGOijtWrPodxfQmeNVAPOPWsppJIpi + JxsKcY9aAMwRyTSbpflx68VOYvOXb97OKtXAiZdzkqT0AGc037BIIRLHjsR60AVprZrZwGj4qTY0 + xyRj3PUVMJDduFfqvFRzxJCzrCzEr60ALEu+YI53c4qeGB7lGCnBU4FUopTBLvfk1at9R2sAMjNA + GtaXsnhy2FzPHvC46jgnNQ33imTXrkz3oVFAwo9Kfrtq03hAzEfJ5gyc81hWM5hhKrhgT0NPcByS + P5g2uVI98Vp6X4uuNGlyzCQIQR0bI7/1rNQxqW+05J7Y4qK5ZYUP2ZCW9TSA7SR9M8V30X9nMFZw + WfcNi5qPWPDtjo0pE7O03U/Mf055rmtFmN9E0DEox+atPWbiW7lSO8Ja4jQbcDC4A9PXFADYtM0+ + 6nc3u7aOm3IP6Vnak9tYt/xL/M445zTIbieOdmWNsE46cip42EkyC4hYx469KAFsrT7XEJgFPOT6 + 1s+H9PD3XlzxnL/MDtqn9pghgb7GjL/eJORWqfEnmrA9oFRoxjJ5BoAp6NqDW2pzRXtuyIAw3FMf + rVS4iF08pydmeCDxWvqeuC+Ro9qglcMw71mwReXD5aAlFJPPU0AZ0cEsbkSZKH15FD2xJJiJVj6c + VfnzLGEXAA71PFpDPaebE6/KOh60AYVws8TBgrFe57CmHUG25RVJA7AVozzSLbNvX5T1AHNY/m/Z + nPlqwDetAEtvqzJNu3FZBwQBjI96vPqkd3mRtokH31UYx+VZqWruxaFl+frkZxT1tvs1ujJgEH5m + PR/pQAXl2S371XAHI+Wkaf7VD8hGR2arKySylRccQ98DmiS0jifdsdgeODQBQd9x3IBx1xTYlBm3 + En86sXUAwPswKg9QeaBErIEj6nrQC0NHRtUjt0K3AHzDABGcVW1fTzJL51jyOpz0NVooispebBI4 + wK2YFEthk8qR07igDAgJil+TKtnnHFaP2h5yI3ZsgdSfaqd2P3im3BGM9aktsjmRgCOaAJZrMwR7 + 3A5PT0pdMvZtOning+byzuVDyh/A8VHczSzDPy7RwOKgiuHEewjKeoFAzp7TUNM8XXEw8RhYNQmP + 7ny18uNeOM7cCtMfDiS8uY0tDEYghyynjPbn864htP8ANhLIehzWzovxDvtFsDB9+PI4I/rQI0r3 + wNc6DO0N2VaQqW2q24YxmqFhYRgE/vkkDfMGBBP4GrSeJ7tZd6SxvIfmK4yQP84p0XiyC71gS65G + 00zAKGX5Qv4UAbFpd28WnIsBLsDzmub1+AXt1LJEoQqfu4xu+lbWsWgs4/NsCXjPIbqK5+5kklmE + rDD54BFAGb5cjybCrAnnB6ipEvXil2sM4GMVpFY7m4UNmNyOWJ4qteaM0BISVZe+RQBFHC2/zISg + B69KlIVhIHA3HuR70lqotlBulY5P4Vcls44k3u6N5oyoHb60wM6O1SRir5LemOKv2vhuW4iLg7VA + 6k4FTR2ax4aaVIwR3HWqGua5PcQm1WRBH6jqaQFzWbE2nhzynuIi+8HaHyKweJSEQEN6jpVcKyOw + cMVznOeKmtZvOPDKuOKAJbi0JYFf4eue9IW8sncfvdqnlvVFyFyu09abI0bysMZx0oArC4eCTcgb + juK2dNvE1N1M0ohljGQzc5A7cfSs6aweWAk7kTuapQysIT9mOSvG49aAOkvzLMxk06QNuG1l7j3r + PlnnJAuGJij+nNQ6XqT7wEYqyn5v9utLULaW7j321uiEjLqMkKKAIotbghb/AI8hKGPIBHNXLG6t + 7uzk3RLbKG/iP+Fc+8f2d1eFztzyD2q5p2oCFWRoxOX52nPFAGgLyC2lyZFKdB70r69buxRJBHjr + nvWVdeXLE7xE8fwnoPpVKZUnQPkBhwRmgDq7a9tLyARWiiWYngL1qG4gurJ28+NowO2a5a3v3smD + aa5WUd1HNbC6zI0KSX13JO7D5lbHFAE4V7pi0b5x1GazdUtXSM7v4iPw5rQ0/XrcXX75FgUdxzuq + /qFrp+sWRe3uDkc4BFAHLRDY42ycd6uPOXiiV+RGPlWnXOg3IQvEmIB/Ft6/jUUEZmMcgydvzECg + C1G2+Ly3YAvyM9qY88kaFcmmp807uwPJ4FS3do+Fzn5ulAFVrjbgS8Z4yah2C03SMffNWZdPknVA + iluQOnHWmX9pILvyY13HHK46UAVre7LSyOCTmtjSiy7VijLeZ0IqO08OzPIUiTI74Ga6bRP7O01F + h1KYJOv3V4BoA4zU1lExMrkbOAvpVcSifhjgrzmtjxPp7pO7SggOcqfUViy25hG5fSgC8rrLAojb + d7d6SexlEgwpRfTNV7e5LFBbKAwPNWHeX7TguxI7GmBPBExhaNVIJ6egqOVknO1fkx1J61aj1gLC + UEKlk4LVWvozC67kCFxkD1pAQ24e3uDLC3z9CR3H/wCqrczJdOGiOxvYc5/CocMYhtUBj3xU8Qjk + XbKPIZOjqclvzoAu2HiO60xPKvd7wY/1fGBWnJo8WuW6y6XIPMYZEAzuH9KxISonAuzuRzgk9qtR + 79KmMuhTt5cRyxznFADLzS2tMw6pAY5OoDEZ/Sm20TQQ74YwVQckGtMatB4kUpqreVIRw5+8aqXF + jc6bAsbD9yThWz94UAOmmjvrRCMJjOQRVS0sD9pLyABM5Of6Vdtrdn+RUGcZqO6uRBG0MuFI79KA + MfV7r7ZqDI7kohAVT6U2eJNimJQOuTnpSXFussrMvBz1pJov3YUsR9O9ABblRncQ3bAqY2EUwIiA + Vqr20ojfYqZx3q9bSKAGcYJPIoAoq7OCEQBffrRDGEcleM8nNPjuGkhHmbB74ApvmxltsuTnuDQA + +SFEjDwu5buD0qpLL5vMg2kEdOlXECMAyZGOMMePyprQRI5N0rt3BXO326UAV4b0Wt0pC5HrXS2W + qq9zE7jcO+OhFc81kbg7iMqeAFHSpLa8eymaNOUIwD6UAavjPQYYybq1bBmXcF9O39Kw4iXdDKcE + DAxW3q7NdWELISdiYIz71kz6ZNZNHI0cjqQfujIFAEtzAtu/7vODzmqlyzNyAo9vWp7uWSWJd+AM + jjGGqOWCSWRVVW2+uKAKskpWU5TP0p8c+ExsPPNTmCVD+5U/QrzRJHJGymeOQc45HFAFczh497KR + jirWlEsAudvII9znitEeBp7yAPZvEVPJUsP5ZqCO3j0yYDUNwliI6dOPpQBt/wDCR3Wj6eHFujvI + do3DIX9KoHXoL6J11CJYZAONlaWueIYtY8Nwx6ZHu2MdxVeTXKG0eaXKRuCeuBQB0mn+HRe2Yeze + MqRkFmwfyra0rwsIrRmvZICcgDLVw7xXFuFd2uEQfeAJAxUkkjSxh4J7gjPAErf40Abvjq1i0y4S + KByCdrfL+FUI7SR4Wc+WzMOCW5qhf3Mt9cCV2ZiihRk5qpdTSBgRI+R2DnFAFw2k6AqJZMjuD1qn + cxzyyAkPuiP3ieT/AJzV+01R7a2RpMZPVmGQ1WVuTqLDCptcfMBwRQBEkst/YMCSTH8vJqtJaoYQ + JPv1o+ZDZKAo+UnBpmrCBpRNp4/0crgZ9f8A9dAzCdGgkOynxSus2xjkj+L1qW5/fxYj+8D+NRWz + R4fzCd2O9Ai0lzI6mPaMOcZqW4uI7rbtJ3IMc1XScKqncQT0olPlKWfBz6UATKjSDcmdoFWtPCyR + kzckHiqUV0623lKVIPzHHWp7Ic/vSRz0zQBcCqdyT4J7YqC3uZdKv1a2UupO7B6H2NMglMUsmcnd + 0Lc4q3BmaMBiDjr60AWJRBfyb9P2RueWJ6KfQVLHqMdtcEysxJXayN0x0yKyWihWQBdwTOSdxHNb + zWEF5ErXhX7QQAMNge2f0oAnhs4rq2kksHwirkg9SfauXnJnmL3AbL9jXSRWh0N28x1cEfMqtnA/ + Cs+70+O9/fWRIb+76fhSTuBimbyyyKDgnipLk7AML1pZbCWO7Hnjn26U6ZykRL+veqAryuvm/Jwf + Sk3mo2AyHyCT6Ux5pLU5Gwg88gGkBPNAILUO3KmooyjL8ueegzTvPMsRjG4qBwKrW1sxJZzsIPGa + AJbmfp5q7MZx71NZawEi8qZSyHg4NRGLzCPtB3eme1R3Nutocodyd8UAaVtqEUDlI8/N3PaqV2Ht + X2x4lIOSwHFSWkEFyo+cD1BpbmNbNdkh20AMh1UiJ1c9RzWj/wAJa1vYiK1RmRvvetY5gDENxgnp + UlhN5TiI4O4845oAmu51lXzFDGQ8jnpTra4uJkBAOQavXvhG8tIhPawvJAfmY9gKE1COwgIiAZiO + 3rQBV866T52Qsw6YrXguZNTs0WSJ8IPnHr9KwZNamNumZSpPU4pbPxBeRy/uJjtXqfWgDodMtnXK + QjYeo3VnalpiXjMzXMKS9O9VV1ydCXkmLY/SorWwTVJTmQEt81AHTeCY49Mik+0SJKmOg71W1bxH + HLdgaXaSRNnjdzWapGlBBG2ec4GKtQ6yZD5hjLMvbIzQBfutWC2ajV4ywwN2OM/Sql/JY2kKGzU/ + McnBBqlf3Lam5e8lKMv3Yz2FU4VjgzsGQ3WgDa0ya0u7kxzgqCCcn1q43hizkEjRkOoXcAOua5Ka + 6Mc3ygEVb0nW57ac/ZC4Xuo5zQBBeZjcwuMxRn5fUUmnySx6kv2cgg98deK1LjT31pTLpymSVuWi + Xqv17U2GzFgFBUCVOo7igCTT7cnTp/ty5ZnyCvGOKz2uwimOY7geQB0FWY7tzu8xiqk8A96qOvmy + MSowOc0AVpkkgk3uAiP39KkjtonYtnO4cKOP1q1Z3K+X5V2N6OeM8gfWiewaxiKhDsAyJB2oAk0u + 1juAwniYshwoB61FLZfaJDv/AHWexpulXRNwpjkP7s8nu1Wd4uC7zfezxQBTjxZTHzlMigbdy8Up + YXEv7nPvk1aNqbhDhgARnFZMCvbzuWZgc/nQBo2l6qs63AJA6VIsiG4DI4jXP8XeqcbrK5JH3xkH + 0pWhWVR52CF6UAa8kUd7H8rD5f1p5txHAfNPasWRCjgh8D0BrV0a+DgCdfM3DaB9RigCml/JFPyB + 159xV+C/wfNHAbtUN9orxO3k5dhycfw1XmT7JarIjb1k6U2BcuNSVGDSAPu6be1QTXcO0CVSwbPA + 7VRtpftEmxW2Mx6HvUv2V1J2jkdaQBFJB5jBVYemetRyW6SqTKCfTFNllCHBX5vWkLBPvk4NADTG + 0ePKB5qdLN5NjycqvNQIpZAFVj71LsaJQBuGaAH3aCVwycKODUMsZgJjxv8AXIzUs0DpHhmBycjm + gOd37wdRjNAFETeTcARAbSeTViApfrhjufHXNJNCsUu18Z61Xit3Q5JxQBdW0MYKyn5hSf2BPIjS + 24I29T6f5xUMMrs5HOF71ooVmtMyu3ynAAzQBqeCfG7aaPsmuYkiYFG3HseKq67YQW2rSNpLCS0l + GQ5GSh74xWZc2SyxK4OZl5x7d/0rV0K+j+xPFOu4Pwpx0oAo3OnFreM7AR9Kp/2eYpxtyCx6VoXd + g2nSlQzMh6UxJdjqSpKgfN6mgCOLSZGkKyYw/wCn+c1YltRodoWA+Y8Z+taPhWz866DQqxLdmq34 + x0ZbS23yY3NgkUAcZcSyrjcc7zw3YU62meOeTazdOhrZ07TYLkYvSFVfmqveQWkDj7CW9zg0AZs9 + 8wbO3L8ZpvmGRsyZQDsO9WLu0EwZojwMc1DJCrsA5we1AFmGVZLc7Y1bA6nvU1gIyNzgxtnoKr7I + NgHO8dx0pJ3AYG3UnHegDRS+NpL5lsxh3dQverj38OtL/pKCKSPhWU/f+tYEt98xMnC9qgludrrJ + GzFl7DvQBq6pYNGdzHGO3aqS33kEBhlSME0+01z7OcXGXRupJ5H0q5fafFqNuJLLnofmGDRsBmJe + DzMEZGevpW7o8sN/bzLqTBML8oB71k/2YYh83FQRqbdtr7sDv60AX7jSo4ZsiVo067hj9anuNHey + jVizMj8gkdaqQyi+UxjO7O0A96tXDz6rEFucp5HygUANGEQKjDJGaqzWbzgyn5QOPY1p2xZtOaGN + VMo5BPoKqxa1NHHtmij+Q4xkUAUraZFiYScMOgNMf76CIZHf2q5KRq8arEjK4OTsGaki0oKwAEhP + uDmgCohEsqq/O6rrMNMj3AEdgfQmn3tqUgEcaYz1JFMtLdn0wpFGxYHhjQBa026M0XM2WQ/NnHzU + 6Yw6tCPt6rbpH0CdvzrPtrZ45ceU4cHk9qtzW6XLOjqwY9+1AEa+HWun8zR28xU5LAZx+VLaGSV9 + jrkr145amvEY4hGkjKMg5XoPY/571vaHFDr95HHqDMkoU4C9G+uKAOevoo5iSBjBxVYwLdRkL1Xt + XSeK/CdzpkjRMqyJ95SjbsD3rmJbUwoeuGOCfSgC9eWc9rcbbdA0KHPmhcq39Ka8e9DkBS5zk1X0 + /wAR3dvEtuTm3AwVzW/D4w0xIEivbOaSTAVWBAH40AYMu6CZDkFcHcTz6UrtkYlwVHIwOtb91olr + qtuRZSL5h5EX8VY97pc1jKAqZ2jB/wA/nQBRJhubjE4YOOnNMC+S+DzmrMkIA819wPTbjmqwfzcM + 4w3vQA9mbYwgIz/ENvSm2t+6jZsYKeTkVYjn/eqwGAOp9aeW+2sdkgVf5UAQLKY5MHGferNv+6IM + XT07CmyaeZIS1vtmkUdQKbZ+akOZoyqMe45oAvRzjUJPLLgSds8/zqyPDzwETagy4U8YwARWMbcw + NuDDePenPrbXEfkTn5hwrdqAO709LPSbbzlZdvqD0Ncnr/iufX793uWQrGdmFGBjpmstdQeFRHKx + 2Nn5f73+f61E7iLCxDnrjvQBaubtNypAxyRzg0q263DMsJIzzyc1mwyDeSD82e9XIGUIrSyBNw+X + 2+tAD3tSpcFvufrVZbdL2XbnDdjnGKnhs2nkYtcIEJ6461HMiJIApBVe5HWgB8mmtpzDzSrrkZYU + 65mRGYoBgirEkCStiJlC7c5IqjLNsYhtu0d6AKkshbAZcAdc81Gdwb5SD6cVZjYy5WXBVu/pWppn + h63urfdLdxR47MDk0AYjnhehxntVq11OVANuTj8q2/8AhBZ7mwkm00CYKQBtHXrWe+kTWS7J4zE+ + OQ1ACQX/ANrkC3DD0wODV280KQwM0jxheueKdZWcCrvkjYYHUHvRe6jFLapHtLKeDjg0AVrDQ5xd + xuhIUEMHx8pH1roZtH+2W+dPIbHDMOcms+81YNoqWltlFKhQD1HNP0e5udHsHFkcyMRkDoaALUPh + aa1n8yUgqRgjPOO/eq+reDkvHzoQYIB85JzzW5HBLqWmCSWQJM3UEdB3/Sk0S3uNPmIkBlgJyXAw + o/Ci4EHh3QYfDsfm3mHklGGLdFqS91HSYpvMw0jjkhTx/KqXjLUg8hihYiMn746H6Vg+QYxuV9vH + 1oA3xrem38TNe28rqp+VUyD+gpbTU7O6ylvEYoEBPzjDAjp2HeuUk1aeyfNqMH+8BTrvVhqEAMuP + O7n1oA3X1Q3U0klp5S7OGHFZt7rj4DwxlTJ6riqMTiDZsHTn6/WpbfU5EP8AxMVMqdFIOMfWgCZb + lpEO/GDgn9K6bwZpktjcC7lUsAMYPvj/AArBi0lrpc2sqbZsHbjkV20SvDp8UUZBcDp60AY+ueIZ + dIu3Frh0lbD+YNxAPXBPSqLrpuunyNPBSSM7mZyQpJ/KtWQ2uqvNDcjypQjAFjnJx0rhNYhntbvy + 7jcucgIe9AEUMOy5ImYgg4xViVVa4UFSoToc9a6DxZoEdqv2rTsHzDlx/dFcujFpG27vlPGe9AEi + anPpV359o7b143jqo/yP0rWs/FSavF9l1JltlB3tOerd+axl3XGfMXC9896iu7UbtyYIxg0AdTc2 + Vrqe3+zZxIF4Uj+I1S1Hwpexu0kts8aL7Vg2t9JZ8REjJ+UD+Guh0TxjeaW3/EwAuFAxh260AY8y + ujfLkBOCOuabHcqgCxYAbrz0rsbSysfHdzks1rO33Y0AwTWd4h+D2r6M5mmt0ER5D85P1oAxLfWZ + LSYrbnAb5eKnudVnyELFkHOcCqUmjzRzBWyD9K6W38JtLo6TtkLzmgDHtryGZiZUDZqDU1Vl3wp8 + g+9jsf8AOKmGnw2cpE8jFR1I7VdGjRXMQa0kdoSPmHrQBn6bYnWz5NydjgZVgORWeztBK8ZBJQld + x6nFdZ4ZtoNI1QPI7O+OB7VX8faO9rdC7ESrC4BJHqaAOcgUTtuORiraW0M9yiXLAIeoPc+1RWar + u6Haxq7e6ekEZkBGzGVz1ptgVprUw3ku3iJDgDPUYFEzAwZRN2CDgUw3JEkezD7+xolvytwn2pVV + RkADv060gLVlMk4aLIDHp7+1Vbu1+yzgThiHOOelElyIZl8v5CDkVtxWkGtaYs0bMblCcr/KgDCe + 3LzsN20L2HepUQJnHI9KsX+gT29pHKCd79qWw0u4aPcwU4796AL+meIr2G1aDSbiWHOMhR1qxZXz + xXBl1n/iYBBlg/FR6VZW1nciS9mdJADgYGO1Q3pIOOu5hz60AO1vxLDqluP7Pt47eJSQ2KzvtiSg + eWuPpU89gsfzH5cc+1ZaSpbXRZT8tAGjjz237gNuPwrc0O48uUPOM4GBXORXC3HmJD1bB/QVZivZ + fLwp+71oA6fVfEiwXC+UBGjfKTj14qZbi7gtJWjkY2zx5C9s4rnbCRdZiaOUkFQTke3P9KbYa1c6 + XcBARLEWxhzwBU2AotqzH5Ls5YdFPOKmiu1KgxfvCOqHrXTL4EXxLbl9MO6bGRkYzXPal4TuNLu2 + ju/3csfUD9KoDO19yChhO3OcqO1VoZEUbHVckZL9x3q09s8a5uDkZxUDWX2i4OzgHvQBLCwkwyEF + c4z6VNDZm7utkROCfwqCzAhuGRhhV/WtR5okjjkQ7ST2oAlSRtMdUjHzR1p2OuOI2Ly4kHQViS3K + iYBMsW5zSNF9klEjPnPSgC1dzm4uVKSMZd4JP41oeJPD8+r6ZHLbwmW5H3yCMqvr/Os6xu/tDfvU + CqSOfWuj0yf7OxLO2CAG9x6UAZs6vcIqSiVw3GQMisR7RVvpFkGFU46e1dN4c1hYmCXm0quDIO9c + 54quVl16drdDHGzZX6UAV5bTzWIi4Ws6/DQEoQSpI5q9BfywxkS7WU9OOlMa3F8hG7bj5sn86AKc + ErggKVA96lFwLcYHX3NQPAHnYD5e26pAnluA/JoAu6JevFqsEqs4YN0HQV39p8aL+CJVnWKWOP5c + OAf6VwCzrbxAIMMefpT48zEFD9RQB6hZ+PNE8YqsfiJFt5GOC0abcH6ioPF+i2/hiGK50xmuLOQ4 + AjO9s/T8a8wlzLIdxKkHIwcc1s6R43vdJi2xurxsdriQbto9RnpQBal1C1urtzcIVjfqu3FRMNM8 + zbpplViehyAKnuU0/X4N+ixtFdR/67e2fN+g4xzWPcWzWFyDL8gP3Qw+9+NAGhqulSWzpJHt/wBn + Bzj2NejeHLG28f8Ahox6/HsmA2DHBGO9eTrrksUTKSOD0Par+n/EnVdMRVsZYgpHIK9u9KwEvjn4 + eTeF9UY2Jie3HI+bJFc6b6eMkt909j2rsrTxpYa7bGHWYpXlc8Ord/yrOu/B8gEjQul3Ao6RjLL9 + cGhaAcu0skr7mK8HtTjEAcMMk881Zm0l7JXxg7uQBywqqzysygDBPr1qgHSWqzANL6UunXjWBOxW + KsaZcggbu4HSlindrf5ANxNIDqblPteiWrESNC2fujJ7Vd0bRY7KLfZswWYZYSdT2/pWJ4Q8ST21 + 1b2krIYj8pBFdd4k024ht0nsdpjA4AHNAHO6npkSs2SwPase6ieJcSYdenB+atGbWykgF9G2cHvi + qGqMxiWW0GFyCSRnFAFeSN4yGiLE9we1QXYEhzMo+bnAqaC9YzbpSGY8CoL/ACwDQ80AV1mxdJwQ + q9h1qd71WHU/QdqgDO0gJAyevFE4WI8dW60AafhzUHt5v3ZAzxVzXNFku/38Odg9KwbK4ELA4z+N + ddourgQKJsMv92gCr4Y8Qy6VGUmkdLcDjn5/8a6vS5tM8SWTG3kkaZeP3xIyfxrmPEuk/ZXF9akG + CY/LHj7tZy38tvcxSwnYw7DpQB0viLwrIigwhcHqAeKxDpbmcgJtKjOfStXRPHgjlEeuAzZ6bf4e + lajX+navE4gZIyQcFmxQBxd5ZPG+9iuDxmqitHGR5oO09M+tdDqmjNsDl90YPBHSsJ4N7uH7dOOt + MByxj+EkE/d5qwYGkUNu+VetUgxVz6gVNAryx7Y84J5PpSAeZWjG8A/Lg1sabqn2hF8wnniqPkK6 + qk/z/TilaEWo/cgqKANPSbRba8zM6MXGDzVPxHYPPOzOOVPy471R03XmSRXlQEHv6VstqaakgJKh + h0X1oA5jBjYrP8uTkA9TQ0qoxLHqPyrQ1+z6TMu104x65/8A1ViSsVc5GdwoAseWbkDyQWC01QVv + S+5WGcbe9OguTFZqIjhxnPHWnWTCO6LyKjPnpQBDfs4n3sMc8Y7VPBKWT922498U7X0RCjRnJmAL + KP4aq2rtA/ycBu5HXFAGkYg0GT8rY5J5qIw5jyMORxU28zwAou5jxj1pnktAzCUlT1xQBHFP/Z8w + dpNsg6ccj8a6jQPFNjqdqbfxJbvPM/yxTE/LF9c1zsNsJ1U3EYIP8VPe1iicCORsnnHTBoAtat4Z + mS92Wn79WBK7aw0ia3uXW4jdChxkjvW/Z+KLjTZFd4hKwyAc44qy+nwazpxEOPNdvMdx1UdTQBzb + AbSNyqGPf+lWvDPiW58IXDtZzOIpRiVVON4qS/0ePcG04/aYV4Z8YwaoPGJrgq2AqnAPY0AdVdww + eJLX7XoxSKfbnyRwzn61zGooyMzsreYpwQTyn+P/ANap9NvX0S4DQtzu7dhW/rel2viWzWfRiPtC + L88a/wAfuaAOQEvyDepIOOamtbFJZWKzrH7Gpk02QRBLgYYHkDtSTaf5LBgM7u1AEVxbS2aiSNfm + xw3St7RfiTLFZi2vUe4VRt44xWJDczTzoLoFgvO096bMomlkaJfI5ztFAG7Jqdlrcm2WNYHA+82C + KidbiCAoVLWzfKoHOawo1dyGO4bQcc9frWppOvSwQLDcDzQSOvbmgCjcWBQsqDYwOTmo44BdAZfG + OeuK1NYdZLjzCdu8dAKzpLYQt+6OKAK88ciXREQ3AY5/Ckmt3dlMoznPSrMU2zJxgD2zSSRmX5kY + gdiO9AFWO3KSDgqMjrXQ6fYuUAjG3HO7rWRawNeSDLYKnHPeunVG0bR4ruTnc20g96AHxn7ZbNA7 + qzgcVzup2s2mzOl0CAT8jYzvrb1TxpZ3tgr6fBFFL/EUqpp+pJqpxeqJAPulucfSgDDfcjgxAqSP + mB60xXXlZFBPXpV2+tms5W2oTnpk1nht0uZCAfTFAG9oOvCJBb6jueJj8qj+Grer6XFCqvHMvHTA + zmuajlMUmWHznoKvQ6tLDEPtKeZnsT0oAkaBVLGX7x54qOG6NvkEEA/rV2dYLi08y3fMhH3e4rMR + mkDLOMkHg9KALcN7vXI4Iq9ZyG5jw7An1rFuWMWMAopxTzqMkIxZAuOpINAD7ZAcg9F6VqaXdRFg + pX5h92sPzRbfKQdvr61c0+4MjDyxsYHkkUAdA2lvdQ+ZcDIPGOuawNY0wWNywjwVbocdK2E1ubTF + +T5gw5yM1Lc2kOqaX5kXMxG4nPT8KAOSUSKu5VGM03aZmRo22k9Tird26Fgp+6hwcVAZfNmCnBVu + mKAJp7N71FDcuOI8d6pJlLlt+d44PoK0dTZLKCI2HmCZQCd33c+1R6iqXKpJBu34+bPQGmBNpzND + bgH7zHjPapLiXMhEvzMRwarQXG+ILcfMP7w7VZjdHj+QgMOmaQCRF7AsVBZO2am2G5t2kIAJ9O1V + 2vzM21l+UU9Cjj5M8eh4NAAIXjUeRl8/pUa6k1hGFtWyG6n+lWYX25Y8dsUs9t5tkVkK7Tz7+tAE + 9l4hAj8q/RUf+Db0P1qZ/DUWrTO0paK9cfLGg+Qn61zc0SeYc53DgVr+HNfk0u623LgwSDaxHLY9 + QaYFa80a60G58vU1VmbqF5AFWdC1k6PqaTW6qyEbSD+FdRJd2s8IikZJbO46MTmRB7nr2/WsrxD4 + QjtohLo+9kHXPb0pAd6uh6Lrekm6hkkQSRgNtQfK/p+dc1f/AAsuGUnSWSVScgynbisHQfGFxpki + RKw8tRyD0z/nNWPFHji/1lFihkCxKMAocUAaNt8NNSt3bzYrYsnT5xTLvwZYQTIuqzlLh/vqigqP + xrk/7QuIwRHcXG4jnMpP9ary3kzhvtUkrSH7p3E0AdXqPgvT1vI47K4kfcCcYAx0/wAar2ngu2uW + ZIJX3pnjHFc3DqUikfPIGHU5PFb2ka3PDe7dPZGGzGW7/wCc0AX7LRLSzcxb3eXrhhxVG78JeVcA + bvvcVfEgudqaoyrOrbiV9Pwpmo311pMnmWmySH3w1AGRrXh6TRfLMq8yfcHGPxqxZ6fpmnmNddml + jlk5+RQRx/8ArqO51ptT3vMwWU9iOF/CsOZHnkIkYu3YnmgDo7qPTtPszcWTu5LcAr1ycVl6p4hk + 1BRbsCEXkCqEGqz20wEWGEZGAeRxVy+vRqV2JpUVJiACQMAUAZ0+mvaNuuz88hwAOmaktbt7C4Ub + c8jvW5rGkp/YUEsRM0nLSf7PFYogSWEF/lJ6CgDWcjXyuMhwOAO9Y09hLbSyKy9+pqzpM9xo90Jr + co2OMMM5ropr2PxBYGK7VVXBbIXG4jnrQByUI8xSADs6HPWpPLIjGxssvr3pxQmcqx+VGwFHenJI + gOF5oAW0jZB5nQnnH6Usnzjrg0rW2/8AeISD1x2pWR5VySNo60AQBX2EzHIXpSQJ5kjOOFpLgrtI + iLFvWi2Y3CFYuoNAEt4myTBBQ46Gq6OyHKjGTzSyyyXUm+/cnHc0+PY42RtuDcDigDS03UzdQlHG + WHFSw3/2CX99lo+hA64NUorOeyG9FJA68VJFaLqNu0hkIlXkgelAF3VtEjvNMF1pKOctyPTFc/bw + tGVeMfMRzW54f119M8yJ2IjlGzk9B/k1p6f4fsmi2xXsUmeP88U7gYV5Et3aQlWCsox+NR2eUnWG + 7bdvrZ1TRY7FXjuQsatzHJ7VkyeXbxnz38xl6NmkBFfiXR3MDKQjHI9xUMV0ijMnNdBZWbeJbUcC + SZU+U454rFu/DF7byNJcW0qxqeeOtAE0EcbI+4nax49qnKNY7CCG46Vjw3DRHO1gtaNrqPnBRKu1 + R0Y80AXYDHPAzlPmzzTWG2Evn8KafMMWIsFfamKxcAyjAHbNAFSeRJpOBg0xrXykVjyp6VLqFv5b + AqwTI6dal02ZZ5VjuMNGentQBJZxXFtFuUZDcitDSPFrwOYrkFkfj6Vl30l7p87RpKRDn92eoIqG + 31gRxk3qMzqRnmgC/wCJtIa2uzLYfMjgEj2rNs70woyIMjPLHtW7Y3y38gkUnGBke1R6p4dS/mNx + obeZgfvIVH3Pf3oAz7W3EmGzgrSSRqszF13+4/hqOOLdGSrk5HO0d6WCUxYaUMYhw4HegCM6TLcy + Ztkd0wckd6jtZZbPiI+aqnlem2tTStXNvcbYZyiSA4QcdMf41Y8Taf8A2dZieGMR7sAkc7s8H+dA + GVJqTT3AKtjIxtrStNVy/kyLuUj1rAlhG4NtKqOc/wB+l+2SpP8AcKMn3s07gdJdeHPtLRS2zpCr + csD171laro72bGSFWZRwzHpQdUe8hTDEMg5xU0N7Pcx7GVpIf4lzSAwlk2yAoevUDpWpa2hvYeTg + 0mo2UM8w8lPs4HUDvRpsFz9oYW6NKB07U0BbjvptGhkgJDRMu01VLRyyIYQSgA3HstVdVMiSlZyx + bPKiksbyS1hdWUmKQ5K0gJpt8UgAw69iKn0/UyJdrdOmKIPIvW/cyLEqj7p4zUEUIEr+blHXJBx1 + oAk1O28q6VoSFVhk1GbZQ25TzUlvcfakIucKAcAnqaWK1cyFkQlB70AJvJdNq5I4+tBcbCnCjv71 + LIVcAowVhxj0qO2t9zkXHKt0bsKAIpbPIHlKWUjk06wgaNiqIBzViF/kKKwBHA9aguI5oX3REk9j + TQErWypGPOGc/pTLTy47gMFyob5fetB7EmcG3G6N8hSTjNWRpgsws/y7ouWB70gKd5dGSRcfKnIP + HFXrHSYL61e4kfyVVcYA61lC7OrxurAKxbIHtUtxfC2sTDA/A49KAEazRmkEw+TqG9as+H7YSTeX + bvu7ccYrIt7qRdobPLc59K6jw9pf2KUXcJBVjuI/z9aALF88MsJh1AiRoPl54Iqt5GmXUG3ABx1x + 0/WneMbGfTryO8VB5d2N6qfTJHP5VBoNtFqUb/b28uU/d2d6AJLPV4dGtP8AQyokHGKgu/Fwu9wl + PXgj0pmpaSmnOxmYEdu5rOht2knZ4FX3oAimiju3AtlAznrVWSAW7OC2HQ/d7VdNjLaMjurbSeMC + s+4WS41BjyEB5zQBcgnk2ARnJbqKZcydmZt3fFVxB+9DRkjHfNWLh/KKGTp/6FQBGLg3C5PzFeBT + LeT5yEzlB0p1zb7wGtzt9RTNhWVQOHPWgDc0iUajbPbTgM5GE9aydTtPKk8sKcDrk9adZX5+0FLc + FZM/K1dPpmgReJLR2nOyZDhQT1z60AYWgXYtrvy5cFXBXA9+OtGpLceH9YIsZ3BwGI4+YHsaNR09 + 9C1ERTFTMjBgE6YyO9S+IoDqHlag5++RGPfGKALelpb+IbtA+Ldk+ZkXofxqHxFpn2Vpv7OXdGOW + 56Vk3GpCBQB8pB429a0bHXN8kX2gKY1ILju1AGakfmFfJXLN0/z+VdZYQG503yda5xyPp/8AqqXw + 2LKJJvsqbjIdwDL936Viarq8u9nhA8sNg88/TFAGrdeFbeWBHscSL/AM9DWRqnhObyS7KUYdfetH + wkx1Gdnm3rECAB6Vu674psYbIRxeZuHBJHWgDzZw2nybQMluDVnT9T2PsJK56Ve1OS1vJ/OhOfXj + pWVdWctu/mJhgTxQBeYrOS0xAxTojJHKHspCQ3GPSqaXCTuqpnf+lTQIJ5XRXwy0AaN7YxzWzT3I + /fSHp6VnS2LI8Yt13kj5ucAU17me4hYbvkHXJ5qvJfDMYDNlevqeaAJTAVJGBuHPFSWuoMN32iNW + UgjOelVo5vNUvg8HGKVollOIG4HNAGhb6dHewhrVy8gPK4qaFTZZRssT1GKzLWd7C5zDlS1a9rq5 + vU2uFAIznuaAK93po2GSIEjqefu1C8QZApc+uBxWnbQpeyCG1OB1cnjmi5sUuTlxgpTQFBAYCWEQ + bjrmmsHvDypH0qYqYGPlk56DPSnWFuz3BN2MCkB0niGK10bw/ExCyMxwhVskH8K5O98SPfWixqPm + AxkjBNEkkz2iQSzgqn3U54rPm4RkY4YEfhQBd0gPBMGnwc8fSpvElpFBIGU5Y4Ix0qjcanIkKBG5 + 7VGzPdIHvF3P9aAHpGtymc4Ira0fU5YYUG7KA5P0rAEgjOFjfHtVqzndD8ilFkGKAPTri4h1fRrW + DVAojmjwjdwPY/XNcJK6aTfubdjhDgc9a19PnbUYLW2upsRJ8o61S8WeH1sryKJ2AeRSUb1oApTX + TXpaQMWJGcdal8PSf6UTcj5WOKz5YW0zgTKZG44Bq4THLpSqj7LhWJdsdfSgDo9e16OGFba0ji3p + wZCBzXOoYZp2N2u0Mecd6Zp12cIbkfIBzTbwRG53W4wp5oAbeWVmgY2ZYeuTVC4SWFAzjdGO5qws + HmK28jaTVi1vhaR+XfRGeJhtVR69jz6dfwpgZEcrPcAp92pl2IzMxLuRwamfSJZCXtnRhnLgcFR6 + VWc7J9mNpbtikAW9w0MheQj5ea3NG1Y2sPmWhCvjuf5Vk7UadY48RseW960rDS11C3b7EMzL3oAt + 6hpn9pZu4GzGq7djH5g2PzpPDsMV/Y3Fveg/uVZl+vNJYRy2KhXfcB972q5aRw310/2eZLbcuCWH + X8qaA4yTeT845B4qaEqjZlVtzflV+80qY31z/Z8T3ENqMs8ZAAGcd6zoZMncEwH6H0pAdDpusLZQ + 7Rjc3ApkFoZJHmY4iAPXpms8R7oh/Gc5HtXQaALbUtGMN6ApPHrzQA/TvEdsdOWD92rRk8gcmud8 + QXkl1cZzlfapr3QP7NujGjfKTlSKzr2Jmdgx/wBX096AIkn8ucBQQjdat/bWMLZKOOnOOKzdjL0P + BoiXe2Cu7vQBpxC0KAyK2488Hiql3LskbaDtbpjrV+3tlubYC2TExGBVe+tJNOAF4PmHNAFO0meG + R1bI9jU0iK23zcbsdagWYO+xOH7mrkMWYcNgkUAQwKGA4JC5pzyFmPlEADt61asYIgSJWA3dOKv6 + zosFpdxPaBGVlG445BwKAMwuWADAbqs6eI/3hl++Pu1cj8NFyrRncAdxb0psElpY37NMhljD4YKe + poAsWmm/aIjKknlsvUnoalhtHLcbiueucA1Uu9UMs8wt4SsOfkUnkCrOmXcotj9rkV0HSLnmgDoD + 4JSXSzPNNFJhdwCkZX9a5+K9gD+XPgDdjNTpez6ZZywwPskcZbk/KK5qZ2llPmvvYnrQATr8zE5D + N1zxRbou7951anhZNYuUVFw7dvSp59IltXdZ1IZKAGvpLNGfLAfufaqDCSKUEkgdMkVd07VWs7oG + XLL0x60+7ePUjyCpByMUAV3bBGxsk1ZikV4gAMkHOKpzW5SUmN849qjjnlil3KODxj0oA6KykW7t + yJW8pk4BFdxrGhwax4TS5JWWaEBEY9QDn/CvNrPUfJmBcZDHLV0s2vsfDMwt2ZYy4z7cGgDHv9NK + yjfD+8bgYFUNRtTps4S6HlkjIBPU/wCcVeN86xKZmJlyMc5p/ifU5L/RYVmto9wJUyZ5oAy01Dfb + qZV2xnoKbfX6NEv2ZcHHWmPLFJYQx2ZLTL1U1EIJA+2bAJ6Y5oAIboyDb0PU1c8xLkBJLna4Hy44 + 5x06VAbZbdcyZ3elNBXeCRjnOaAG2808N5syYmJ7fx+5q7tW5QCZQso/iqsULT7rXLr6k4xVi0dX + +9kmgBlxpbI7SxqZAoGWz0p+i3txZ3AezJAHXjrWlZ26mFyzEnPC+vStzTLO3vZ1M8Yjwp6Hr0oA + 5/xFqyrIggQKrLlsdc96xpQZ5wySbu2DVnVYQ9/MJCSitxVOQFW4G1aAOm+H3iGPSbie1upBDBqC + CKRugwOfwrI8VWsenazNHZtvs0fEb/3h6j171Elg02N65x6Gt200i18VwwwXcjQ3Fou2NQMiTvye + 3WgDn4riKEhkfKf3h6+9aFlGLeyS8eT5DIMoDnv3FXZ9I0iwhJFxJLMpwY2ACg1TvvISzMs77S5w + EUcUAW9dH9qW6y6ZKBgcgdawoNOu7iWMmNiWOMDtT4Jxb5e1bKuMEHsfWpNM1ZrG4WWFmct0BHSg + CprWivp0u193mMeR6VHa2jmQbVH0zV3WNRkv5mkn5YnjFRJGBMjRMScdKANvR7OO1u4pS+SGGV68 + d61/GnhSHUYReQyqsZXiPI64rK0S5hRNzfePXvWr5w1KIwwucAccUAefW1q8kqiT+WK0RpdzFFuE + bFT0bHBqxrFj/Z87LjDZ/Km2ctw7Kgk3KO3SgDPQPuHmqNynv2rRs7hrhjDIcDqD6VPeafDfWbbC + UnUjav8AeHfn8qsaL4bl2pLcYWJT85PYdzQBq6dfjRtKX7QnmC4JQH07f1rIl0SztbsSrcoQnJQH + qaseJ7mBVT7PIXtDwrYwQ3esOO4RrxvLZmjI+90P5UAXrm881T9lHOeAOareXPH+8BKOB19Kb9rF + pcq0ILDPc8mp7m+S6k3fdKj7vWgB8Gtj7Oq3AZ3fCs7DmorqxQTbl+oAqJJlu4gJMKwIxT3kNq+H + G5/7o7D1zTA7Pwpd6NBrk5vQwMv3Pl+7UnjAwwXX7tFe3l5UjBbHvXP3GnCOxhuo2IL1G+qPcFYX + cknoT/n2pbgVZtGFxZvNbH5VOBk+vt+FZ8lrPakrcqyHGcEYzWidWS3lCxAlVPUdDWxf6pa6nLH/ + AGlH99QoI4wTwKbA45pHEirjk1asbxYZCsoDYH1rV17wyumSKVbeGG4Y6gVk/wBn7UdgCpPc0gLw + aEwtLKMDtWhoNykVwHdd8JGCjDIrDkSW1g2zOhVhkVLo+puSVlKlccYoA6Dxf4PbSLRb21wto7DG + W7ntj61mpKdXtxaOQvlfMCSBuJrqLfWIfEvhg2muKzQoN4CnBJHT9cVyU5hEjNbB0CHABPNAGTPa + fZriQONjqcZ6flUtqqB1SRmMr/dJzWlDaLrEUh1Qbnx+628ZNZE1s9nfctxEccjpQBO9tLcy7Zjw + vfNQ31q9oee3A75qe2Yyzby5OKiutRMsjKQDg4FG4EVvEyfM5xnsD1q5bbzKHBAB9KrCJN4YMd3p + V+wt8szRZUCnYDXsWSGPz7jGI+SMVVuvErXKEWuRk9QMYqXVyLXTUyRmRcmsSC4EAO8D2pAXxbma + IMR8w7+tVdRtkUAT9ew71as7wsF2nFGsKodDOMzHo/YU0rgULe7j098qW545Gaki1FIbwzeYyzfw + EdvyqkyGSfaw+bvRcQLayqyEnAyaQHR6gi6/pXnBER0IGFHzN15rnmlXyTGRuQHByeQau2GrS20G + 9OhO3H1//VWhf6RprXbXmnrMtuYsOjNk78DkfiDQBi2rpHIVQjb1otHPnBZAMAdRVUQiW6Bgyis2 + Buq29q2nXJjn/eDsycUAOLCG8yg9zkcVCzeVIZY+cenekN0LqYRSHAHA9aLMCOTy5BlTyPegCxa6 + ltkL2+ORzxjFWbTXpLSV3Y84+XFVJvLilKjgVFMpAyBxQBq6prEF7bQSzA+ZJ97jpVRGjDbUJAB+ + U+tUywlJUdE6VteHLK3kuoDqQZ0zyAcYFAG3feVo+io90u2d13R/LyR35rm77VZNSmzC5SEj5hnH + 14/Otu+hv/FN3gTWywW4KRqQM4/OsUeFZp5miaVAc9R0oAaXWa0EUWCIjuA9PeqEMbCYM3G77oAr + bi8Gz2YDmeLc3ygev61X1CxnnuTE8TvPb9fKXigDMuIJFlBdtzHnAPSrEF0IwDCm5hw2VNRzxTWt + 0BeKVMnTIxj8KZ/ahtgY49uT7UAX7VH1K63oERVOTxiuu0ex0nS7L7chJkm+R1kwwyPQZrh4JJDw + zbVbk4/OrNpefLsnyyg5UUAf/9k= +END:VCARD diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/issue64.vcf b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/issue64.vcf new file mode 100644 index 0000000..6110529 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/VObject/issue64.vcf @@ -0,0 +1,351 @@ +BEGIN:VCARD +VERSION:2.1 +PHOTO;ENCODING=BASE64;JPEG: + /9j/4AAQSkZJRgABAQAAAQABAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQA + AAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAABQKADAAQAAAABAAABQAAAAAD/2wBD + AAIBAQIBAQICAQICAgICAwUDAwMDAwYEBAMFBwYHBwcGBgYHCAsJBwgKCAYGCQ0JCgsLDAwMBwkN + Dg0MDgsMDAv/2wBDAQICAgMCAwUDAwULCAYICwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsL + CwsLCwsLCwsLCwsLCwsLCwsLCwv/wAARCAFAAUADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAA + AAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKB + kaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZn + aGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT + 1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcI + CQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAV + YnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6 + goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk + 5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD8J7JbO8tYo1tIFCDLOVG5qfdaVZRwmSOFWzyA + F4H1rLt5WViMhdp6HgmtKK8O3B+4Rhx6fSgBI9FtjaNN5aErwRjilSys7lFAt41xyTtqc2yJCVlY + 7eqgGqv2jyLcebjZnGPWncdzT0+w0u5eQXtrGiBcIyoPmNMXwpb/AGMTSRRbH6YAyPwqK21GKdfL + BAVfu+1SQX4jnjKFsp03dPypCKN9oEaKSkC7R0bGKpnSlSPdHErZOORXV3Ouy337sCLB6kpx+FY0 + t+VfyrgcbuCB1oAfoMemrcImq2sZX+I7ATXS618PdK1DRlvvDEaMq5LoV2nisx4LVrUfu5BOePau + m8EQS6PY3HmFXjljKhTzjOf1oA4mz8OxvMrLbW5RD8wbByKg1LRrRriRYY408w/KAMba1pRaWt/H + a6a7CVm2u7N8lUPEujzaRekzSK6tgqVNAGNBZJauY5Yon92GTRJp0ROY0Un0A4q3c2odkaYOMjii + KL7NIDGcj1NDAZBplmmWv1xnoFHStfS/DFpewqYoYm3DutZ8lv8AapdyOqk8EVteEbSe3KBSrDrQ + BT8S+HbawiiWGCAPjsuMnPesqHS4JSFlSMP7DitbXbvfrkkM2eGw3p+FMfTh5X+hr8w7t3oAhOhW + u8MkMZUY3fL0Heo9UsrN5FFrbxKmMBgoG41fWFra0Acjpzg9aoXjtgRoo29vagCoun27kbY059qn + bwykskYjRArdTT7GEl2UqMr2q/JtVU27iR15NADdK8DC/wBPle2iicxNg5ALH6Umm6FZ/a3ttQt4 + g2Cqnb0PbJ+tamn3j6ZCW0nILfeBORWVfO4dhLw7fMW7560AZuqeHf7MuTFcRpv6qVGVx70q2Eci + QwyW0SsPvOqjJrUtb6S9tHQKGeMZYuM8VUs7gRxbrncy9mWgB1x4QtTHvsQWkHJVhhax3tkhugHh + UkfeAXIFdPZ3v2uxkQ9G4jI6/j+tYun3r2Fy6yxeb2Py5IoAqXenJ5xaGNNvXH/1qcLSGeBdkSg9 + CcdaswC3be0pfexOMnpn2qaS1KQkQASKoydvLCgDNi09RKTNCuO2BxVjSobc6gqXMERQHkleDUsc + u9VADbG6qOWAp11bLbptkjlCkZRsde9AFi5sbO3kKfZYTnkHaOlVbuO2F5thtYcADjaKXUpHj8ku + Co2VDFL5wLeg696YFwQ2z7Qtlb8HJO0c1Zsr7T7a9kL6XazZ4CmMFRWfHdkEgjGRjPpU9raP5LSP + j5h2pAWdQ0+z1KdG+y21qvcRqBn8qXSvC+iTu63ssqyE/IAuR+NQwSrGm1g+c8E9qiSQW9wPNYYP + OR2oAW68GNa28k3lwGNHwvzDJGfSqM9nHBgm3j59QMVdmma4zIjsUBHy5OKp6o8s2BJjZjjAoAro + /nysbgYY9zWmLPCR+WQQwyaz4k2F/Pbft/GtKxvUeFN+B2x+NAEptsWpZSdo9etZe8su2X7pPFdU + LeOazKqVwevNYt7pw5EA5HIxQBQA8tAIeGz1NWIJvJlhW5OQBzjrUMR/eN9pwoXjB4qQ3ERJeYcy + 9P8AZoA0jf8AmybVxsHAFS6jp63ixmwjIwOfrWfaou12GcDpmt/w5qJhXc6hh2GM0AZkHiRpblVl + G0RjGMdxXQ+H/E0Rm+bjdw1crqEHm3EksY4Y9PTmq0cskc42qUOfpmgDovHOhLBOZ9O+aEnIUdRW + QZft1sgum/1Ywua3fDfiFDL5WoEPEwxzzirPizwTFPZC60kYUjcAp4NAHPSq91EoRS3061DHD9nb + 94Mkfw020v57GbcCRt4IIqzNcedIH2jc3JyOaAIYrRZmJxtNdB4fkGn2hluBgBR+NZ2n2X9ozAQD + 5qvaxGbKIRXkuFU4C96AMDxBKZdQkuEUkStuUegpNM1eWScAkqpHTHNPlwbjMzExZ4Pal1PS/s6+ + dY/6vuwPSgC9G8c0A+1xEknrnpUVxaeXNm2dVUfjVazvEZAEkMrccZzV1YYyBIhJP8SZ6fhQBSmV + 4JfMVT+96UJdSQdcMO4A6fjVmTUoJiqTOMJ/q+elRyQs0TtaxF0PVhzmgCzpd55r7YI2HHPTmrV0 + sDTF7gnJXGO4OKyNKgn80NbFhjoBzWjqdg6SISPmIBOaAKVnI1leyhsMJOD7CqOqRtZqotjiFulW + rhsSMshKH1ogsZbmF475TKifdf0oApabevHIAhCYOdxp0t59luS0I+995uxqpdRyWsrqmXGeCR/K + rVlZfaogqv8AvD/CaAIY42kV3K5zzn1p9jNLp6u/A80YPNWWsJNPAVpC4JAZT2HfFWJoVmVVjhVk + HTPrQBPoi2wsoo4APtBHL+tP1mS5uVEFxgJGNqH15plp5WmyBriMRsowM8UybXTNdbrpd6A/KKAD + xbJAGs44FIPlnd9c/wD16ynt/LiDW2SR2qa5vP7RnMs6BNuQMd6jhkAUb2K8+tADYp0fhj8w6itC + yQ3CFYeAOoqi8Uew+UMuf4u9T2NwIW+UgMetO4FmS6RJ1ik6HqxHAqC+gimUiA8DvjrU0kcE8ieY + itu+8c0+bShaWxksSZoM4b0SkBTgha0cq33Cuc1SvrrLFV6jpWqbuGe1HnnDdAKy7i3WSY7OT2NN + AMulWSV8ZDNzxV7SlbaFjClx69Kpww7W3ct7jpUtnNJHd5UjZnt1NIDdt7h7NQ7qGfpt7VR1XVEh + dhEpP94/4VpafexTy7ZlbBGDVHxFbQh1j04HaOTkdKAM5ZVlYso3E+tVp4w8gx0Bqd7QxNu+6D6V + DIoVySxAx2NAFyNmli2pjYBz61paW3lWrFS3BwP8/hWJbTBFJy2D6HgfWtiTWPsqxraBHyOeBg0A + RSoLSTdIepzz0606exTWyQGMXljORTNT1B7+ECZR5fHzDqapfbHjbFkTsIwSTQA43ptyyS44Paun + 8N64Z7Bre4YlZBtU5+7XLTQbjwN4Pb+IfWn2lw9uyrIw2Z5HpQBv3GirHc7LxWVZOVI71FNp7WDg + QYlIIGD6VvaPdi+tljb5yeAzcn8DT9YtbPSpVhDM87jJ3Htjnn6UAUIrJreD7Si7MDoKhv8AUxqt + pGt5GqIOr9zRfLM8ZFgZGtex2nGe4zWKN8rsDhYx2JpJ3Atx+HxcRSzWcpcL/CRwaj0zW1sQy3cS + nsFPSoYJpbIl7dm8tT8wzV7+0hqEO1Y4lQ9cqMn9KoCp9kW7kaaxU+Yx+5j5etWrb/RGxfr5bkdu + lW7KFILpfspDbVyc1fjNnrLtHqOYWP8AFjGfxpAc/e6Ql/GzW4AfqBWfpupS6Xer5vPlHmMjg10V + 5pp0u4JhYNGvAYHrUn2WLWrVo41AvSMRZAC/8CPr1oAvafdWOuNG+lqDekY+zg8MPXPX/wDXWZrF + tcWNw0erKElB4Rf4R6c1BpqyaBdbrnEcwyAc4x06H0rQS9a9jUTgOXPzMwycexoAw7u1jYb3zkU3 + Srtgdk54PFamv2C2pDQbWjcfKCeSa56aJld23YA6ZOKFqBrXGjjULuOKxKuZOTn+H/OKwr/ztOvs + uCrg7RgVLYapPbXAEW4EkHJNdBNBH4gtgyhFmXuw60AVpbT7VpiPJ94jLetQWsDRSIYz8mec1c0+ + 1nexdrw7GjJXk/epsFtDPG0bOdw+b5SaAKWsXA+14Y71FQi5S4RvlAC8A0y5hHmHarhvQ9BVGSQx + sUXPHX3oAmDCJ8rzgHg96gQ+ZGWbg9vahNRG7EnalkkF6hEXyD270MCWF3aEhdue1OsmNnMAih/r + VaBgAUY8561PaubdnMxJXseuKANhIY5Assp2v12itZtAgubEi2nb5xuKYHWubstQaO6SVzujTqpP + X8K2rXWLRF8xZJPMfjAzgUAcxcNiaRSpUocc96sW+yNgZCMVF4lvJdRvTOYkj52jbgZ98D6VWmlY + 2qCUnJOKaVwCzviibANwYc8Utkdl7tbKhjxmpUspvm8tgn16ipigSEG4G4pxu9TSA27GeFbRlGGm + P3cdhUN8GEP2hV3JjafrWfpU/wBmuAcZLA4/Sr1trkarJHcRmSEZO3uTQBmrcbZCLoDZ2x1qOHSi + yebJIAPQipp4kmbzI1EQJ6GtCxsoHP8Ap91GB2yDQBlSWO+M/ZsBHHzZ71XkfMIWNgGU9vSt3U9N + t9m21uonz0Iz/hVCfRkjg82FhtHDGgCuZ8EMjDZjBzSZ8pAwU7XbGT0pWtEjjAZgV4PFOml2QKqk + OoOcU1qBNYRSrdkrhw3BIrah8KwXoV/m3PyVzyDWNp999kccgZq/ea7PFAGgZlJ6EUgN23thpdi4 + V1Eucr7ev9K53V/ER1a/MkuWdBtG04zioLrXJ5wDK2XAxmqVqmZ2YPtHJ/GgDsvC3i0ppr2d2ish + yFAHIz706bRLNdOPnErKw4y3NcvZ3pjA8o4kB61o3OpSX9nbx3QIkU/MwoAj/sGaPzFjlWSJjk46 + ioYYwqssjIHHAHpWm4ESN9nYDIFZV+I7uVI1wrY5b1oAtafcvb3W4MM9Nx6U/VZpNRys54ToU4zW + KXaDKrJuC8cVdtpi1gzs43HNAD9N195bdYtRIUR4wD1NX2KuA9uThuSQelcsZwzq9xyzfezV/SdX + e3m8pXJhkPKkUAdYZk8RywjVVJES7U2cE/WtA+HDHohuY3Uxg7RF/GeaPBlxaawMW6rHKnAU9SOO + lX/FFv8A2bpzTQk+cpAAz93nrQBx+r4c5CODEOA3Y+wrKu5V1C1GFKznkk9K6Wzv49fs8Xf7y7DY + MhGNgrmtX0s2t66WknnKvUp0/WgCnbrJFdot0NwJxkDFdDYp86oMjjIArJivxbR7LuMyEjKitS21 + MW8auuW44H93/PFAG15aXdr5Uv7uULkA/wCFc+Yvstw0at8+eoq/p+rm6vRJMNwIx9KranYySXSy + WEZZHOCw7UARXFyj5STAk7ntWVf2gALLyfUVoataLbfLO2SO/Ws2c+VwhLK3QDpQBmz2xAyCG56d + 6uWPlnCkFcjoTzUBkMc/3cZpwn8oZkDFs8HsKALN1apDIHOeaiLkRkMOtSXE6yxAsRUcdxldswIJ + HANMCuJW8xQgOP51oacWPPGAeRUUOIZQzDhecd6mbIcbPusM0gLmq6bHPohlhDeZuH4c1zzF1+Rs + HByDXTae0s0IhjjZg3GPWqOs+HpLCTbNGyb+cHrQBZitjPEzW/LL97vinw2v2m2aORec9AKXQbsw + ygBBiX72TWxfaS8kiGFQAwz8vWkncDlbqNraT5cjb/n+lMGckx8kjOa1tU2TxkPkMpxyKyrhJ4Wa + KIDbTAkgvIp7URzgBwe/BpZYrd4vmZWNZ81x5cgBXDdzVlIvtUOGIBHpQA2aEROpR8DsB2q3bvG9 + iySzEsTkLnrVMqViCZzt7nrT7GBVuQRnODQA6Q+Sx80A4HApEJB3BAR9K19EmhkvCJ0ZsKe3tUc8 + Mc1yy7cpn6YoAzoUiclnYYY8AHpUl8zRxqpPy9qtC2tULgSMAvQ460lzIl9b7YiDt4GaAKMMQlJ5 + z9Kj8gIW5yKnS3Crlzhh6d6k0mbyZT565Q5z60ANtrRpPmhzWhbwy7DJcDhhwMdKlt7aK+gb+z33 + yKdxVuMCqaz5cqGYfWgB6yu8rBB8o6Gs/UpjGQXBGPTvVmSfyImyepqrqjbIw3WgCDz1ib9yOTg4 + NbVlNBJYvlVBHt1rBaPzQWU4IHSn2FwRJslJxQA6e3M0O4oAzdB6VXR2iKGQENGOK0ms1eAkFjF/ + BjrVGaAo371smgC7pety2kwl06Vo5AOWXmuwm+Itv4g8Ota30aWlySAJQfmkP/1zXIeG4Y5SVBB3 + evamXGly2tydwG0nKkHpQBZ86fRbpBLI252y4PGRWhO8Ml1IbJhHn+BTnNU9O1oRwvDqqhB2lHJP + 4U6awb+z4JdKbzdh5ZurDHtQBat5LaRHiaOP7QejEZKD/Oauy+FI7W3Bsroyhxkq3QH8q5a7ujM8 + nWOQnBqTR9burCT98xdR60AbbaHc6ZG3ymJsZC/3hVnw/fNIXt7hygHzZp2oeIBqCxzqfmCgEe3+ + RVdrmLVAEtf3bxfOW/ve36UAV7+7DXMu5Q4/Os2e3eRWkiAGOijtWrPodxfQmeNVAPOPWsppJIpi + JxsKcY9aAMwRyTSbpflx68VOYvOXb97OKtXAiZdzkqT0AGc037BIIRLHjsR60AVprZrZwGj4qTY0 + xyRj3PUVMJDduFfqvFRzxJCzrCzEr60ALEu+YI53c4qeGB7lGCnBU4FUopTBLvfk1at9R2sAMjNA + GtaXsnhy2FzPHvC46jgnNQ33imTXrkz3oVFAwo9Kfrtq03hAzEfJ5gyc81hWM5hhKrhgT0NPcByS + P5g2uVI98Vp6X4uuNGlyzCQIQR0bI7/1rNQxqW+05J7Y4qK5ZYUP2ZCW9TSA7SR9M8V30X9nMFZw + WfcNi5qPWPDtjo0pE7O03U/Mf055rmtFmN9E0DEox+atPWbiW7lSO8Ja4jQbcDC4A9PXFADYtM0+ + 6nc3u7aOm3IP6Vnak9tYt/xL/M445zTIbieOdmWNsE46cip42EkyC4hYx469KAFsrT7XEJgFPOT6 + 1s+H9PD3XlzxnL/MDtqn9pghgb7GjL/eJORWqfEnmrA9oFRoxjJ5BoAp6NqDW2pzRXtuyIAw3FMf + rVS4iF08pydmeCDxWvqeuC+Ro9qglcMw71mwReXD5aAlFJPPU0AZ0cEsbkSZKH15FD2xJJiJVj6c + VfnzLGEXAA71PFpDPaebE6/KOh60AYVws8TBgrFe57CmHUG25RVJA7AVozzSLbNvX5T1AHNY/m/Z + nPlqwDetAEtvqzJNu3FZBwQBjI96vPqkd3mRtokH31UYx+VZqWruxaFl+frkZxT1tvs1ujJgEH5m + PR/pQAXl2S371XAHI+Wkaf7VD8hGR2arKySylRccQ98DmiS0jifdsdgeODQBQd9x3IBx1xTYlBm3 + En86sXUAwPswKg9QeaBErIEj6nrQC0NHRtUjt0K3AHzDABGcVW1fTzJL51jyOpz0NVooispebBI4 + wK2YFEthk8qR07igDAgJil+TKtnnHFaP2h5yI3ZsgdSfaqd2P3im3BGM9aktsjmRgCOaAJZrMwR7 + 3A5PT0pdMvZtOning+byzuVDyh/A8VHczSzDPy7RwOKgiuHEewjKeoFAzp7TUNM8XXEw8RhYNQmP + 7ny18uNeOM7cCtMfDiS8uY0tDEYghyynjPbn864htP8ANhLIehzWzovxDvtFsDB9+PI4I/rQI0r3 + wNc6DO0N2VaQqW2q24YxmqFhYRgE/vkkDfMGBBP4GrSeJ7tZd6SxvIfmK4yQP84p0XiyC71gS65G + 00zAKGX5Qv4UAbFpd28WnIsBLsDzmub1+AXt1LJEoQqfu4xu+lbWsWgs4/NsCXjPIbqK5+5kklmE + rDD54BFAGb5cjybCrAnnB6ipEvXil2sM4GMVpFY7m4UNmNyOWJ4qteaM0BISVZe+RQBFHC2/zISg + B69KlIVhIHA3HuR70lqotlBulY5P4Vcls44k3u6N5oyoHb60wM6O1SRir5LemOKv2vhuW4iLg7VA + 6k4FTR2ax4aaVIwR3HWqGua5PcQm1WRBH6jqaQFzWbE2nhzynuIi+8HaHyKweJSEQEN6jpVcKyOw + cMVznOeKmtZvOPDKuOKAJbi0JYFf4eue9IW8sncfvdqnlvVFyFyu09abI0bysMZx0oArC4eCTcgb + juK2dNvE1N1M0ohljGQzc5A7cfSs6aweWAk7kTuapQysIT9mOSvG49aAOkvzLMxk06QNuG1l7j3r + PlnnJAuGJij+nNQ6XqT7wEYqyn5v9utLULaW7j321uiEjLqMkKKAIotbghb/AI8hKGPIBHNXLG6t + 7uzk3RLbKG/iP+Fc+8f2d1eFztzyD2q5p2oCFWRoxOX52nPFAGgLyC2lyZFKdB70r69buxRJBHjr + nvWVdeXLE7xE8fwnoPpVKZUnQPkBhwRmgDq7a9tLyARWiiWYngL1qG4gurJ28+NowO2a5a3v3smD + aa5WUd1HNbC6zI0KSX13JO7D5lbHFAE4V7pi0b5x1GazdUtXSM7v4iPw5rQ0/XrcXX75FgUdxzuq + /qFrp+sWRe3uDkc4BFAHLRDY42ycd6uPOXiiV+RGPlWnXOg3IQvEmIB/Ft6/jUUEZmMcgydvzECg + C1G2+Ly3YAvyM9qY88kaFcmmp807uwPJ4FS3do+Fzn5ulAFVrjbgS8Z4yah2C03SMffNWZdPknVA + iluQOnHWmX9pILvyY13HHK46UAVre7LSyOCTmtjSiy7VijLeZ0IqO08OzPIUiTI74Ga6bRP7O01F + h1KYJOv3V4BoA4zU1lExMrkbOAvpVcSifhjgrzmtjxPp7pO7SggOcqfUViy25hG5fSgC8rrLAojb + d7d6SexlEgwpRfTNV7e5LFBbKAwPNWHeX7TguxI7GmBPBExhaNVIJ6egqOVknO1fkx1J61aj1gLC + UEKlk4LVWvozC67kCFxkD1pAQ24e3uDLC3z9CR3H/wCqrczJdOGiOxvYc5/CocMYhtUBj3xU8Qjk + XbKPIZOjqclvzoAu2HiO60xPKvd7wY/1fGBWnJo8WuW6y6XIPMYZEAzuH9KxISonAuzuRzgk9qtR + 79KmMuhTt5cRyxznFADLzS2tMw6pAY5OoDEZ/Sm20TQQ74YwVQckGtMatB4kUpqreVIRw5+8aqXF + jc6bAsbD9yThWz94UAOmmjvrRCMJjOQRVS0sD9pLyABM5Of6Vdtrdn+RUGcZqO6uRBG0MuFI79KA + MfV7r7ZqDI7kohAVT6U2eJNimJQOuTnpSXFussrMvBz1pJov3YUsR9O9ABblRncQ3bAqY2EUwIiA + Vqr20ojfYqZx3q9bSKAGcYJPIoAoq7OCEQBffrRDGEcleM8nNPjuGkhHmbB74ApvmxltsuTnuDQA + +SFEjDwu5buD0qpLL5vMg2kEdOlXECMAyZGOMMePyprQRI5N0rt3BXO326UAV4b0Wt0pC5HrXS2W + qq9zE7jcO+OhFc81kbg7iMqeAFHSpLa8eymaNOUIwD6UAavjPQYYybq1bBmXcF9O39Kw4iXdDKcE + DAxW3q7NdWELISdiYIz71kz6ZNZNHI0cjqQfujIFAEtzAtu/7vODzmqlyzNyAo9vWp7uWSWJd+AM + jjGGqOWCSWRVVW2+uKAKskpWU5TP0p8c+ExsPPNTmCVD+5U/QrzRJHJGymeOQc45HFAFczh497KR + jirWlEsAudvII9znitEeBp7yAPZvEVPJUsP5ZqCO3j0yYDUNwliI6dOPpQBt/wDCR3Wj6eHFujvI + do3DIX9KoHXoL6J11CJYZAONlaWueIYtY8Nwx6ZHu2MdxVeTXKG0eaXKRuCeuBQB0mn+HRe2Yeze + MqRkFmwfyra0rwsIrRmvZICcgDLVw7xXFuFd2uEQfeAJAxUkkjSxh4J7gjPAErf40Abvjq1i0y4S + KByCdrfL+FUI7SR4Wc+WzMOCW5qhf3Mt9cCV2ZiihRk5qpdTSBgRI+R2DnFAFw2k6AqJZMjuD1qn + cxzyyAkPuiP3ieT/AJzV+01R7a2RpMZPVmGQ1WVuTqLDCptcfMBwRQBEkst/YMCSTH8vJqtJaoYQ + JPv1o+ZDZKAo+UnBpmrCBpRNp4/0crgZ9f8A9dAzCdGgkOynxSus2xjkj+L1qW5/fxYj+8D+NRWz + R4fzCd2O9Ai0lzI6mPaMOcZqW4uI7rbtJ3IMc1XScKqncQT0olPlKWfBz6UATKjSDcmdoFWtPCyR + kzckHiqUV0623lKVIPzHHWp7Ic/vSRz0zQBcCqdyT4J7YqC3uZdKv1a2UupO7B6H2NMglMUsmcnd + 0Lc4q3BmaMBiDjr60AWJRBfyb9P2RueWJ6KfQVLHqMdtcEysxJXayN0x0yKyWihWQBdwTOSdxHNb + zWEF5ErXhX7QQAMNge2f0oAnhs4rq2kksHwirkg9SfauXnJnmL3AbL9jXSRWh0N28x1cEfMqtnA/ + Cs+70+O9/fWRIb+76fhSTuBimbyyyKDgnipLk7AML1pZbCWO7Hnjn26U6ZykRL+veqAryuvm/Jwf + Sk3mo2AyHyCT6Ux5pLU5Gwg88gGkBPNAILUO3KmooyjL8ueegzTvPMsRjG4qBwKrW1sxJZzsIPGa + AJbmfp5q7MZx71NZawEi8qZSyHg4NRGLzCPtB3eme1R3Nutocodyd8UAaVtqEUDlI8/N3PaqV2Ht + X2x4lIOSwHFSWkEFyo+cD1BpbmNbNdkh20AMh1UiJ1c9RzWj/wAJa1vYiK1RmRvvetY5gDENxgnp + UlhN5TiI4O4845oAmu51lXzFDGQ8jnpTra4uJkBAOQavXvhG8tIhPawvJAfmY9gKE1COwgIiAZiO + 3rQBV866T52Qsw6YrXguZNTs0WSJ8IPnHr9KwZNamNumZSpPU4pbPxBeRy/uJjtXqfWgDodMtnXK + QjYeo3VnalpiXjMzXMKS9O9VV1ydCXkmLY/SorWwTVJTmQEt81AHTeCY49Mik+0SJKmOg71W1bxH + HLdgaXaSRNnjdzWapGlBBG2ec4GKtQ6yZD5hjLMvbIzQBfutWC2ajV4ywwN2OM/Sql/JY2kKGzU/ + McnBBqlf3Lam5e8lKMv3Yz2FU4VjgzsGQ3WgDa0ya0u7kxzgqCCcn1q43hizkEjRkOoXcAOua5Ka + 6Mc3ygEVb0nW57ac/ZC4Xuo5zQBBeZjcwuMxRn5fUUmnySx6kv2cgg98deK1LjT31pTLpymSVuWi + Xqv17U2GzFgFBUCVOo7igCTT7cnTp/ty5ZnyCvGOKz2uwimOY7geQB0FWY7tzu8xiqk8A96qOvmy + MSowOc0AVpkkgk3uAiP39KkjtonYtnO4cKOP1q1Z3K+X5V2N6OeM8gfWiewaxiKhDsAyJB2oAk0u + 1juAwniYshwoB61FLZfaJDv/AHWexpulXRNwpjkP7s8nu1Wd4uC7zfezxQBTjxZTHzlMigbdy8Up + YXEv7nPvk1aNqbhDhgARnFZMCvbzuWZgc/nQBo2l6qs63AJA6VIsiG4DI4jXP8XeqcbrK5JH3xkH + 0pWhWVR52CF6UAa8kUd7H8rD5f1p5txHAfNPasWRCjgh8D0BrV0a+DgCdfM3DaB9RigCml/JFPyB + 159xV+C/wfNHAbtUN9orxO3k5dhycfw1XmT7JarIjb1k6U2BcuNSVGDSAPu6be1QTXcO0CVSwbPA + 7VRtpftEmxW2Mx6HvUv2V1J2jkdaQBFJB5jBVYemetRyW6SqTKCfTFNllCHBX5vWkLBPvk4NADTG + 0ePKB5qdLN5NjycqvNQIpZAFVj71LsaJQBuGaAH3aCVwycKODUMsZgJjxv8AXIzUs0DpHhmBycjm + gOd37wdRjNAFETeTcARAbSeTViApfrhjufHXNJNCsUu18Z61Xit3Q5JxQBdW0MYKyn5hSf2BPIjS + 24I29T6f5xUMMrs5HOF71ooVmtMyu3ynAAzQBqeCfG7aaPsmuYkiYFG3HseKq67YQW2rSNpLCS0l + GQ5GSh74xWZc2SyxK4OZl5x7d/0rV0K+j+xPFOu4Pwpx0oAo3OnFreM7AR9Kp/2eYpxtyCx6VoXd + g2nSlQzMh6UxJdjqSpKgfN6mgCOLSZGkKyYw/wCn+c1YltRodoWA+Y8Z+taPhWz866DQqxLdmq34 + x0ZbS23yY3NgkUAcZcSyrjcc7zw3YU62meOeTazdOhrZ07TYLkYvSFVfmqveQWkDj7CW9zg0AZs9 + 8wbO3L8ZpvmGRsyZQDsO9WLu0EwZojwMc1DJCrsA5we1AFmGVZLc7Y1bA6nvU1gIyNzgxtnoKr7I + NgHO8dx0pJ3AYG3UnHegDRS+NpL5lsxh3dQverj38OtL/pKCKSPhWU/f+tYEt98xMnC9qgludrrJ + GzFl7DvQBq6pYNGdzHGO3aqS33kEBhlSME0+01z7OcXGXRupJ5H0q5fafFqNuJLLnofmGDRsBmJe + DzMEZGevpW7o8sN/bzLqTBML8oB71k/2YYh83FQRqbdtr7sDv60AX7jSo4ZsiVo067hj9anuNHey + jVizMj8gkdaqQyi+UxjO7O0A96tXDz6rEFucp5HygUANGEQKjDJGaqzWbzgyn5QOPY1p2xZtOaGN + VMo5BPoKqxa1NHHtmij+Q4xkUAUraZFiYScMOgNMf76CIZHf2q5KRq8arEjK4OTsGaki0oKwAEhP + uDmgCohEsqq/O6rrMNMj3AEdgfQmn3tqUgEcaYz1JFMtLdn0wpFGxYHhjQBa026M0XM2WQ/NnHzU + 6Yw6tCPt6rbpH0CdvzrPtrZ45ceU4cHk9qtzW6XLOjqwY9+1AEa+HWun8zR28xU5LAZx+VLaGSV9 + jrkr145amvEY4hGkjKMg5XoPY/571vaHFDr95HHqDMkoU4C9G+uKAOevoo5iSBjBxVYwLdRkL1Xt + XSeK/CdzpkjRMqyJ95SjbsD3rmJbUwoeuGOCfSgC9eWc9rcbbdA0KHPmhcq39Ka8e9DkBS5zk1X0 + /wAR3dvEtuTm3AwVzW/D4w0xIEivbOaSTAVWBAH40AYMu6CZDkFcHcTz6UrtkYlwVHIwOtb91olr + qtuRZSL5h5EX8VY97pc1jKAqZ2jB/wA/nQBRJhubjE4YOOnNMC+S+DzmrMkIA819wPTbjmqwfzcM + 4w3vQA9mbYwgIz/ENvSm2t+6jZsYKeTkVYjn/eqwGAOp9aeW+2sdkgVf5UAQLKY5MHGferNv+6IM + XT07CmyaeZIS1vtmkUdQKbZ+akOZoyqMe45oAvRzjUJPLLgSds8/zqyPDzwETagy4U8YwARWMbcw + NuDDePenPrbXEfkTn5hwrdqAO709LPSbbzlZdvqD0Ncnr/iufX793uWQrGdmFGBjpmstdQeFRHKx + 2Nn5f73+f61E7iLCxDnrjvQBaubtNypAxyRzg0q263DMsJIzzyc1mwyDeSD82e9XIGUIrSyBNw+X + 2+tAD3tSpcFvufrVZbdL2XbnDdjnGKnhs2nkYtcIEJ6461HMiJIApBVe5HWgB8mmtpzDzSrrkZYU + 65mRGYoBgirEkCStiJlC7c5IqjLNsYhtu0d6AKkshbAZcAdc81Gdwb5SD6cVZjYy5WXBVu/pWppn + h63urfdLdxR47MDk0AYjnhehxntVq11OVANuTj8q2/8AhBZ7mwkm00CYKQBtHXrWe+kTWS7J4zE+ + OQ1ACQX/ANrkC3DD0wODV280KQwM0jxheueKdZWcCrvkjYYHUHvRe6jFLapHtLKeDjg0AVrDQ5xd + xuhIUEMHx8pH1roZtH+2W+dPIbHDMOcms+81YNoqWltlFKhQD1HNP0e5udHsHFkcyMRkDoaALUPh + aa1n8yUgqRgjPOO/eq+reDkvHzoQYIB85JzzW5HBLqWmCSWQJM3UEdB3/Sk0S3uNPmIkBlgJyXAw + o/Ci4EHh3QYfDsfm3mHklGGLdFqS91HSYpvMw0jjkhTx/KqXjLUg8hihYiMn746H6Vg+QYxuV9vH + 1oA3xrem38TNe28rqp+VUyD+gpbTU7O6ylvEYoEBPzjDAjp2HeuUk1aeyfNqMH+8BTrvVhqEAMuP + O7n1oA3X1Q3U0klp5S7OGHFZt7rj4DwxlTJ6riqMTiDZsHTn6/WpbfU5EP8AxMVMqdFIOMfWgCZb + lpEO/GDgn9K6bwZpktjcC7lUsAMYPvj/AArBi0lrpc2sqbZsHbjkV20SvDp8UUZBcDp60AY+ueIZ + dIu3Frh0lbD+YNxAPXBPSqLrpuunyNPBSSM7mZyQpJ/KtWQ2uqvNDcjypQjAFjnJx0rhNYhntbvy + 7jcucgIe9AEUMOy5ImYgg4xViVVa4UFSoToc9a6DxZoEdqv2rTsHzDlx/dFcujFpG27vlPGe9AEi + anPpV359o7b143jqo/yP0rWs/FSavF9l1JltlB3tOerd+axl3XGfMXC9896iu7UbtyYIxg0AdTc2 + Vrqe3+zZxIF4Uj+I1S1Hwpexu0kts8aL7Vg2t9JZ8REjJ+UD+Guh0TxjeaW3/EwAuFAxh260AY8y + ujfLkBOCOuabHcqgCxYAbrz0rsbSysfHdzks1rO33Y0AwTWd4h+D2r6M5mmt0ER5D85P1oAxLfWZ + LSYrbnAb5eKnudVnyELFkHOcCqUmjzRzBWyD9K6W38JtLo6TtkLzmgDHtryGZiZUDZqDU1Vl3wp8 + g+9jsf8AOKmGnw2cpE8jFR1I7VdGjRXMQa0kdoSPmHrQBn6bYnWz5NydjgZVgORWeztBK8ZBJQld + x6nFdZ4ZtoNI1QPI7O+OB7VX8faO9rdC7ESrC4BJHqaAOcgUTtuORiraW0M9yiXLAIeoPc+1RWar + u6Haxq7e6ekEZkBGzGVz1ptgVprUw3ku3iJDgDPUYFEzAwZRN2CDgUw3JEkezD7+xolvytwn2pVV + RkADv060gLVlMk4aLIDHp7+1Vbu1+yzgThiHOOelElyIZl8v5CDkVtxWkGtaYs0bMblCcr/KgDCe + 3LzsN20L2HepUQJnHI9KsX+gT29pHKCd79qWw0u4aPcwU4796AL+meIr2G1aDSbiWHOMhR1qxZXz + xXBl1n/iYBBlg/FR6VZW1nciS9mdJADgYGO1Q3pIOOu5hz60AO1vxLDqluP7Pt47eJSQ2KzvtiSg + eWuPpU89gsfzH5cc+1ZaSpbXRZT8tAGjjz237gNuPwrc0O48uUPOM4GBXORXC3HmJD1bB/QVZivZ + fLwp+71oA6fVfEiwXC+UBGjfKTj14qZbi7gtJWjkY2zx5C9s4rnbCRdZiaOUkFQTke3P9KbYa1c6 + XcBARLEWxhzwBU2AotqzH5Ls5YdFPOKmiu1KgxfvCOqHrXTL4EXxLbl9MO6bGRkYzXPal4TuNLu2 + ju/3csfUD9KoDO19yChhO3OcqO1VoZEUbHVckZL9x3q09s8a5uDkZxUDWX2i4OzgHvQBLCwkwyEF + c4z6VNDZm7utkROCfwqCzAhuGRhhV/WtR5okjjkQ7ST2oAlSRtMdUjHzR1p2OuOI2Ly4kHQViS3K + iYBMsW5zSNF9klEjPnPSgC1dzm4uVKSMZd4JP41oeJPD8+r6ZHLbwmW5H3yCMqvr/Os6xu/tDfvU + CqSOfWuj0yf7OxLO2CAG9x6UAZs6vcIqSiVw3GQMisR7RVvpFkGFU46e1dN4c1hYmCXm0quDIO9c + 54quVl16drdDHGzZX6UAV5bTzWIi4Ws6/DQEoQSpI5q9BfywxkS7WU9OOlMa3F8hG7bj5sn86AKc + ErggKVA96lFwLcYHX3NQPAHnYD5e26pAnluA/JoAu6JevFqsEqs4YN0HQV39p8aL+CJVnWKWOP5c + OAf6VwCzrbxAIMMefpT48zEFD9RQB6hZ+PNE8YqsfiJFt5GOC0abcH6ioPF+i2/hiGK50xmuLOQ4 + AjO9s/T8a8wlzLIdxKkHIwcc1s6R43vdJi2xurxsdriQbto9RnpQBal1C1urtzcIVjfqu3FRMNM8 + zbpplViehyAKnuU0/X4N+ixtFdR/67e2fN+g4xzWPcWzWFyDL8gP3Qw+9+NAGhqulSWzpJHt/wBn + Bzj2NejeHLG28f8Ahox6/HsmA2DHBGO9eTrrksUTKSOD0Par+n/EnVdMRVsZYgpHIK9u9KwEvjn4 + eTeF9UY2Jie3HI+bJFc6b6eMkt909j2rsrTxpYa7bGHWYpXlc8Ord/yrOu/B8gEjQul3Ao6RjLL9 + cGhaAcu0skr7mK8HtTjEAcMMk881Zm0l7JXxg7uQBywqqzysygDBPr1qgHSWqzANL6UunXjWBOxW + KsaZcggbu4HSlindrf5ANxNIDqblPteiWrESNC2fujJ7Vd0bRY7KLfZswWYZYSdT2/pWJ4Q8ST21 + 1b2krIYj8pBFdd4k024ht0nsdpjA4AHNAHO6npkSs2SwPase6ieJcSYdenB+atGbWykgF9G2cHvi + qGqMxiWW0GFyCSRnFAFeSN4yGiLE9we1QXYEhzMo+bnAqaC9YzbpSGY8CoL/ACwDQ80AV1mxdJwQ + q9h1qd71WHU/QdqgDO0gJAyevFE4WI8dW60AafhzUHt5v3ZAzxVzXNFku/38Odg9KwbK4ELA4z+N + ddourgQKJsMv92gCr4Y8Qy6VGUmkdLcDjn5/8a6vS5tM8SWTG3kkaZeP3xIyfxrmPEuk/ZXF9akG + CY/LHj7tZy38tvcxSwnYw7DpQB0viLwrIigwhcHqAeKxDpbmcgJtKjOfStXRPHgjlEeuAzZ6bf4e + lajX+navE4gZIyQcFmxQBxd5ZPG+9iuDxmqitHGR5oO09M+tdDqmjNsDl90YPBHSsJ4N7uH7dOOt + MByxj+EkE/d5qwYGkUNu+VetUgxVz6gVNAryx7Y84J5PpSAeZWjG8A/Lg1sabqn2hF8wnniqPkK6 + qk/z/TilaEWo/cgqKANPSbRba8zM6MXGDzVPxHYPPOzOOVPy471R03XmSRXlQEHv6VstqaakgJKh + h0X1oA5jBjYrP8uTkA9TQ0qoxLHqPyrQ1+z6TMu104x65/8A1ViSsVc5GdwoAseWbkDyQWC01QVv + S+5WGcbe9OguTFZqIjhxnPHWnWTCO6LyKjPnpQBDfs4n3sMc8Y7VPBKWT922498U7X0RCjRnJmAL + KP4aq2rtA/ycBu5HXFAGkYg0GT8rY5J5qIw5jyMORxU28zwAou5jxj1pnktAzCUlT1xQBHFP/Z8w + dpNsg6ccj8a6jQPFNjqdqbfxJbvPM/yxTE/LF9c1zsNsJ1U3EYIP8VPe1iicCORsnnHTBoAtat4Z + mS92Wn79WBK7aw0ia3uXW4jdChxkjvW/Z+KLjTZFd4hKwyAc44qy+nwazpxEOPNdvMdx1UdTQBzb + AbSNyqGPf+lWvDPiW58IXDtZzOIpRiVVON4qS/0ePcG04/aYV4Z8YwaoPGJrgq2AqnAPY0AdVdww + eJLX7XoxSKfbnyRwzn61zGooyMzsreYpwQTyn+P/ANap9NvX0S4DQtzu7dhW/rel2viWzWfRiPtC + L88a/wAfuaAOQEvyDepIOOamtbFJZWKzrH7Gpk02QRBLgYYHkDtSTaf5LBgM7u1AEVxbS2aiSNfm + xw3St7RfiTLFZi2vUe4VRt44xWJDczTzoLoFgvO096bMomlkaJfI5ztFAG7Jqdlrcm2WNYHA+82C + KidbiCAoVLWzfKoHOawo1dyGO4bQcc9frWppOvSwQLDcDzQSOvbmgCjcWBQsqDYwOTmo44BdAZfG + OeuK1NYdZLjzCdu8dAKzpLYQt+6OKAK88ciXREQ3AY5/Ckmt3dlMoznPSrMU2zJxgD2zSSRmX5kY + gdiO9AFWO3KSDgqMjrXQ6fYuUAjG3HO7rWRawNeSDLYKnHPeunVG0bR4ruTnc20g96AHxn7ZbNA7 + qzgcVzup2s2mzOl0CAT8jYzvrb1TxpZ3tgr6fBFFL/EUqpp+pJqpxeqJAPulucfSgDDfcjgxAqSP + mB60xXXlZFBPXpV2+tms5W2oTnpk1nht0uZCAfTFAG9oOvCJBb6jueJj8qj+Grer6XFCqvHMvHTA + zmuajlMUmWHznoKvQ6tLDEPtKeZnsT0oAkaBVLGX7x54qOG6NvkEEA/rV2dYLi08y3fMhH3e4rMR + mkDLOMkHg9KALcN7vXI4Iq9ZyG5jw7An1rFuWMWMAopxTzqMkIxZAuOpINAD7ZAcg9F6VqaXdRFg + pX5h92sPzRbfKQdvr61c0+4MjDyxsYHkkUAdA2lvdQ+ZcDIPGOuawNY0wWNywjwVbocdK2E1ubTF + +T5gw5yM1Lc2kOqaX5kXMxG4nPT8KAOSUSKu5VGM03aZmRo22k9Tird26Fgp+6hwcVAZfNmCnBVu + mKAJp7N71FDcuOI8d6pJlLlt+d44PoK0dTZLKCI2HmCZQCd33c+1R6iqXKpJBu34+bPQGmBNpzND + bgH7zHjPapLiXMhEvzMRwarQXG+ILcfMP7w7VZjdHj+QgMOmaQCRF7AsVBZO2am2G5t2kIAJ9O1V + 2vzM21l+UU9Cjj5M8eh4NAAIXjUeRl8/pUa6k1hGFtWyG6n+lWYX25Y8dsUs9t5tkVkK7Tz7+tAE + 9l4hAj8q/RUf+Db0P1qZ/DUWrTO0paK9cfLGg+Qn61zc0SeYc53DgVr+HNfk0u623LgwSDaxHLY9 + QaYFa80a60G58vU1VmbqF5AFWdC1k6PqaTW6qyEbSD+FdRJd2s8IikZJbO46MTmRB7nr2/WsrxD4 + QjtohLo+9kHXPb0pAd6uh6Lrekm6hkkQSRgNtQfK/p+dc1f/AAsuGUnSWSVScgynbisHQfGFxpki + RKw8tRyD0z/nNWPFHji/1lFihkCxKMAocUAaNt8NNSt3bzYrYsnT5xTLvwZYQTIuqzlLh/vqigqP + xrk/7QuIwRHcXG4jnMpP9ary3kzhvtUkrSH7p3E0AdXqPgvT1vI47K4kfcCcYAx0/wAar2ngu2uW + ZIJX3pnjHFc3DqUikfPIGHU5PFb2ka3PDe7dPZGGzGW7/wCc0AX7LRLSzcxb3eXrhhxVG78JeVcA + bvvcVfEgudqaoyrOrbiV9Pwpmo311pMnmWmySH3w1AGRrXh6TRfLMq8yfcHGPxqxZ6fpmnmNddml + jlk5+RQRx/8ArqO51ptT3vMwWU9iOF/CsOZHnkIkYu3YnmgDo7qPTtPszcWTu5LcAr1ycVl6p4hk + 1BRbsCEXkCqEGqz20wEWGEZGAeRxVy+vRqV2JpUVJiACQMAUAZ0+mvaNuuz88hwAOmaktbt7C4Ub + c8jvW5rGkp/YUEsRM0nLSf7PFYogSWEF/lJ6CgDWcjXyuMhwOAO9Y09hLbSyKy9+pqzpM9xo90Jr + co2OMMM5ropr2PxBYGK7VVXBbIXG4jnrQByUI8xSADs6HPWpPLIjGxssvr3pxQmcqx+VGwFHenJI + gOF5oAW0jZB5nQnnH6Usnzjrg0rW2/8AeISD1x2pWR5VySNo60AQBX2EzHIXpSQJ5kjOOFpLgrtI + iLFvWi2Y3CFYuoNAEt4myTBBQ46Gq6OyHKjGTzSyyyXUm+/cnHc0+PY42RtuDcDigDS03UzdQlHG + WHFSw3/2CX99lo+hA64NUorOeyG9FJA68VJFaLqNu0hkIlXkgelAF3VtEjvNMF1pKOctyPTFc/bw + tGVeMfMRzW54f119M8yJ2IjlGzk9B/k1p6f4fsmi2xXsUmeP88U7gYV5Et3aQlWCsox+NR2eUnWG + 7bdvrZ1TRY7FXjuQsatzHJ7VkyeXbxnz38xl6NmkBFfiXR3MDKQjHI9xUMV0ijMnNdBZWbeJbUcC + SZU+U454rFu/DF7byNJcW0qxqeeOtAE0EcbI+4nax49qnKNY7CCG46Vjw3DRHO1gtaNrqPnBRKu1 + R0Y80AXYDHPAzlPmzzTWG2Evn8KafMMWIsFfamKxcAyjAHbNAFSeRJpOBg0xrXykVjyp6VLqFv5b + AqwTI6dal02ZZ5VjuMNGentQBJZxXFtFuUZDcitDSPFrwOYrkFkfj6Vl30l7p87RpKRDn92eoIqG + 31gRxk3qMzqRnmgC/wCJtIa2uzLYfMjgEj2rNs70woyIMjPLHtW7Y3y38gkUnGBke1R6p4dS/mNx + obeZgfvIVH3Pf3oAz7W3EmGzgrSSRqszF13+4/hqOOLdGSrk5HO0d6WCUxYaUMYhw4HegCM6TLcy + Ztkd0wckd6jtZZbPiI+aqnlem2tTStXNvcbYZyiSA4QcdMf41Y8Taf8A2dZieGMR7sAkc7s8H+dA + GVJqTT3AKtjIxtrStNVy/kyLuUj1rAlhG4NtKqOc/wB+l+2SpP8AcKMn3s07gdJdeHPtLRS2zpCr + csD171laro72bGSFWZRwzHpQdUe8hTDEMg5xU0N7Pcx7GVpIf4lzSAwlk2yAoevUDpWpa2hvYeTg + 0mo2UM8w8lPs4HUDvRpsFz9oYW6NKB07U0BbjvptGhkgJDRMu01VLRyyIYQSgA3HstVdVMiSlZyx + bPKiksbyS1hdWUmKQ5K0gJpt8UgAw69iKn0/UyJdrdOmKIPIvW/cyLEqj7p4zUEUIEr+blHXJBx1 + oAk1O28q6VoSFVhk1GbZQ25TzUlvcfakIucKAcAnqaWK1cyFkQlB70AJvJdNq5I4+tBcbCnCjv71 + LIVcAowVhxj0qO2t9zkXHKt0bsKAIpbPIHlKWUjk06wgaNiqIBzViF/kKKwBHA9aguI5oX3REk9j + TQErWypGPOGc/pTLTy47gMFyob5fetB7EmcG3G6N8hSTjNWRpgsws/y7ouWB70gKd5dGSRcfKnIP + HFXrHSYL61e4kfyVVcYA61lC7OrxurAKxbIHtUtxfC2sTDA/A49KAEazRmkEw+TqG9as+H7YSTeX + bvu7ccYrIt7qRdobPLc59K6jw9pf2KUXcJBVjuI/z9aALF88MsJh1AiRoPl54Iqt5GmXUG3ABx1x + 0/WneMbGfTryO8VB5d2N6qfTJHP5VBoNtFqUb/b28uU/d2d6AJLPV4dGtP8AQyokHGKgu/Fwu9wl + PXgj0pmpaSmnOxmYEdu5rOht2knZ4FX3oAimiju3AtlAznrVWSAW7OC2HQ/d7VdNjLaMjurbSeMC + s+4WS41BjyEB5zQBcgnk2ARnJbqKZcydmZt3fFVxB+9DRkjHfNWLh/KKGTp/6FQBGLg3C5PzFeBT + LeT5yEzlB0p1zb7wGtzt9RTNhWVQOHPWgDc0iUajbPbTgM5GE9aydTtPKk8sKcDrk9adZX5+0FLc + FZM/K1dPpmgReJLR2nOyZDhQT1z60AYWgXYtrvy5cFXBXA9+OtGpLceH9YIsZ3BwGI4+YHsaNR09 + 9C1ERTFTMjBgE6YyO9S+IoDqHlag5++RGPfGKALelpb+IbtA+Ldk+ZkXofxqHxFpn2Vpv7OXdGOW + 56Vk3GpCBQB8pB429a0bHXN8kX2gKY1ILju1AGakfmFfJXLN0/z+VdZYQG503yda5xyPp/8AqqXw + 2LKJJvsqbjIdwDL936Viarq8u9nhA8sNg88/TFAGrdeFbeWBHscSL/AM9DWRqnhObyS7KUYdfetH + wkx1Gdnm3rECAB6Vu674psYbIRxeZuHBJHWgDzZw2nybQMluDVnT9T2PsJK56Ve1OS1vJ/OhOfXj + pWVdWctu/mJhgTxQBeYrOS0xAxTojJHKHspCQ3GPSqaXCTuqpnf+lTQIJ5XRXwy0AaN7YxzWzT3I + /fSHp6VnS2LI8Yt13kj5ucAU17me4hYbvkHXJ5qvJfDMYDNlevqeaAJTAVJGBuHPFSWuoMN32iNW + UgjOelVo5vNUvg8HGKVollOIG4HNAGhb6dHewhrVy8gPK4qaFTZZRssT1GKzLWd7C5zDlS1a9rq5 + vU2uFAIznuaAK93po2GSIEjqefu1C8QZApc+uBxWnbQpeyCG1OB1cnjmi5sUuTlxgpTQFBAYCWEQ + bjrmmsHvDypH0qYqYGPlk56DPSnWFuz3BN2MCkB0niGK10bw/ExCyMxwhVskH8K5O98SPfWixqPm + AxkjBNEkkz2iQSzgqn3U54rPm4RkY4YEfhQBd0gPBMGnwc8fSpvElpFBIGU5Y4Ix0qjcanIkKBG5 + 7VGzPdIHvF3P9aAHpGtymc4Ira0fU5YYUG7KA5P0rAEgjOFjfHtVqzndD8ilFkGKAPTri4h1fRrW + DVAojmjwjdwPY/XNcJK6aTfubdjhDgc9a19PnbUYLW2upsRJ8o61S8WeH1sryKJ2AeRSUb1oApTX + TXpaQMWJGcdal8PSf6UTcj5WOKz5YW0zgTKZG44Bq4THLpSqj7LhWJdsdfSgDo9e16OGFba0ji3p + wZCBzXOoYZp2N2u0Mecd6Zp12cIbkfIBzTbwRG53W4wp5oAbeWVmgY2ZYeuTVC4SWFAzjdGO5qws + HmK28jaTVi1vhaR+XfRGeJhtVR69jz6dfwpgZEcrPcAp92pl2IzMxLuRwamfSJZCXtnRhnLgcFR6 + VWc7J9mNpbtikAW9w0MheQj5ea3NG1Y2sPmWhCvjuf5Vk7UadY48RseW960rDS11C3b7EMzL3oAt + 6hpn9pZu4GzGq7djH5g2PzpPDsMV/Y3Fveg/uVZl+vNJYRy2KhXfcB972q5aRw310/2eZLbcuCWH + X8qaA4yTeT845B4qaEqjZlVtzflV+80qY31z/Z8T3ENqMs8ZAAGcd6zoZMncEwH6H0pAdDpusLZQ + 7Rjc3ApkFoZJHmY4iAPXpms8R7oh/Gc5HtXQaALbUtGMN6ApPHrzQA/TvEdsdOWD92rRk8gcmud8 + QXkl1cZzlfapr3QP7NujGjfKTlSKzr2Jmdgx/wBX096AIkn8ucBQQjdat/bWMLZKOOnOOKzdjL0P + BoiXe2Cu7vQBpxC0KAyK2488Hiql3LskbaDtbpjrV+3tlubYC2TExGBVe+tJNOAF4PmHNAFO0meG + R1bI9jU0iK23zcbsdagWYO+xOH7mrkMWYcNgkUAQwKGA4JC5pzyFmPlEADt61asYIgSJWA3dOKv6 + zosFpdxPaBGVlG445BwKAMwuWADAbqs6eI/3hl++Pu1cj8NFyrRncAdxb0psElpY37NMhljD4YKe + poAsWmm/aIjKknlsvUnoalhtHLcbiueucA1Uu9UMs8wt4SsOfkUnkCrOmXcotj9rkV0HSLnmgDoD + 4JSXSzPNNFJhdwCkZX9a5+K9gD+XPgDdjNTpez6ZZywwPskcZbk/KK5qZ2llPmvvYnrQATr8zE5D + N1zxRbou7951anhZNYuUVFw7dvSp59IltXdZ1IZKAGvpLNGfLAfufaqDCSKUEkgdMkVd07VWs7oG + XLL0x60+7ePUjyCpByMUAV3bBGxsk1ZikV4gAMkHOKpzW5SUmN849qjjnlil3KODxj0oA6KykW7t + yJW8pk4BFdxrGhwax4TS5JWWaEBEY9QDn/CvNrPUfJmBcZDHLV0s2vsfDMwt2ZYy4z7cGgDHv9NK + yjfD+8bgYFUNRtTps4S6HlkjIBPU/wCcVeN86xKZmJlyMc5p/ifU5L/RYVmto9wJUyZ5oAy01Dfb + qZV2xnoKbfX6NEv2ZcHHWmPLFJYQx2ZLTL1U1EIJA+2bAJ6Y5oAIboyDb0PU1c8xLkBJLna4Hy44 + 5x06VAbZbdcyZ3elNBXeCRjnOaAG2808N5syYmJ7fx+5q7tW5QCZQso/iqsULT7rXLr6k4xVi0dX + +9kmgBlxpbI7SxqZAoGWz0p+i3txZ3AezJAHXjrWlZ26mFyzEnPC+vStzTLO3vZ1M8Yjwp6Hr0oA + 5/xFqyrIggQKrLlsdc96xpQZ5wySbu2DVnVYQ9/MJCSitxVOQFW4G1aAOm+H3iGPSbie1upBDBqC + CKRugwOfwrI8VWsenazNHZtvs0fEb/3h6j171Elg02N65x6Gt200i18VwwwXcjQ3Fou2NQMiTvye + 3WgDn4riKEhkfKf3h6+9aFlGLeyS8eT5DIMoDnv3FXZ9I0iwhJFxJLMpwY2ACg1TvvISzMs77S5w + EUcUAW9dH9qW6y6ZKBgcgdawoNOu7iWMmNiWOMDtT4Jxb5e1bKuMEHsfWpNM1ZrG4WWFmct0BHSg + CprWivp0u193mMeR6VHa2jmQbVH0zV3WNRkv5mkn5YnjFRJGBMjRMScdKANvR7OO1u4pS+SGGV68 + d61/GnhSHUYReQyqsZXiPI64rK0S5hRNzfePXvWr5w1KIwwucAccUAefW1q8kqiT+WK0RpdzFFuE + bFT0bHBqxrFj/Z87LjDZ/Km2ctw7Kgk3KO3SgDPQPuHmqNynv2rRs7hrhjDIcDqD6VPeafDfWbbC + UnUjav8AeHfn8qsaL4bl2pLcYWJT85PYdzQBq6dfjRtKX7QnmC4JQH07f1rIl0SztbsSrcoQnJQH + qaseJ7mBVT7PIXtDwrYwQ3esOO4RrxvLZmjI+90P5UAXrm881T9lHOeAOareXPH+8BKOB19Kb9rF + pcq0ILDPc8mp7m+S6k3fdKj7vWgB8Gtj7Oq3AZ3fCs7DmorqxQTbl+oAqJJlu4gJMKwIxT3kNq+H + G5/7o7D1zTA7Pwpd6NBrk5vQwMv3Pl+7UnjAwwXX7tFe3l5UjBbHvXP3GnCOxhuo2IL1G+qPcFYX + cknoT/n2pbgVZtGFxZvNbH5VOBk+vt+FZ8lrPakrcqyHGcEYzWidWS3lCxAlVPUdDWxf6pa6nLH/ + AGlH99QoI4wTwKbA45pHEirjk1asbxYZCsoDYH1rV17wyumSKVbeGG4Y6gVk/wBn7UdgCpPc0gLw + aEwtLKMDtWhoNykVwHdd8JGCjDIrDkSW1g2zOhVhkVLo+puSVlKlccYoA6Dxf4PbSLRb21wto7DG + W7ntj61mpKdXtxaOQvlfMCSBuJrqLfWIfEvhg2muKzQoN4CnBJHT9cVyU5hEjNbB0CHABPNAGTPa + fZriQONjqcZ6flUtqqB1SRmMr/dJzWlDaLrEUh1Qbnx+628ZNZE1s9nfctxEccjpQBO9tLcy7Zjw + vfNQ31q9oee3A75qe2Yyzby5OKiutRMsjKQDg4FG4EVvEyfM5xnsD1q5bbzKHBAB9KrCJN4YMd3p + V+wt8szRZUCnYDXsWSGPz7jGI+SMVVuvErXKEWuRk9QMYqXVyLXTUyRmRcmsSC4EAO8D2pAXxbma + IMR8w7+tVdRtkUAT9ew71as7wsF2nFGsKodDOMzHo/YU0rgULe7j098qW545Gaki1FIbwzeYyzfw + EdvyqkyGSfaw+bvRcQLayqyEnAyaQHR6gi6/pXnBER0IGFHzN15rnmlXyTGRuQHByeQau2GrS20G + 9OhO3H1//VWhf6RprXbXmnrMtuYsOjNk78DkfiDQBi2rpHIVQjb1otHPnBZAMAdRVUQiW6Bgyis2 + Buq29q2nXJjn/eDsycUAOLCG8yg9zkcVCzeVIZY+cenekN0LqYRSHAHA9aLMCOTy5BlTyPegCxa6 + ltkL2+ORzxjFWbTXpLSV3Y84+XFVJvLilKjgVFMpAyBxQBq6prEF7bQSzA+ZJ97jpVRGjDbUJAB+ + U+tUywlJUdE6VteHLK3kuoDqQZ0zyAcYFAG3feVo+io90u2d13R/LyR35rm77VZNSmzC5SEj5hnH + 14/Otu+hv/FN3gTWywW4KRqQM4/OsUeFZp5miaVAc9R0oAaXWa0EUWCIjuA9PeqEMbCYM3G77oAr + bi8Gz2YDmeLc3ygev61X1CxnnuTE8TvPb9fKXigDMuIJFlBdtzHnAPSrEF0IwDCm5hw2VNRzxTWt + 0BeKVMnTIxj8KZ/ahtgY49uT7UAX7VH1K63oERVOTxiuu0ex0nS7L7chJkm+R1kwwyPQZrh4JJDw + zbVbk4/OrNpefLsnyyg5UUAf/9k= + +END:VCARD diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/bootstrap.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/bootstrap.php new file mode 100644 index 0000000..a01f8f9 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/bootstrap.php @@ -0,0 +1,25 @@ +<?php + +date_default_timezone_set('UTC'); + +$try = array( + __DIR__ . '/../vendor/autoload.php', + __DIR__ . '/../../../autoload.php', +); + +foreach($try as $path) { + if (file_exists($path)) { + $autoLoader = include $path; + break; + } +} + +$autoLoader->addPsr4('Sabre\\VObject\\',__DIR__ . '/VObject'); + +if (!defined('SABRE_TEMPDIR')) { + define('SABRE_TEMPDIR', __DIR__ . '/temp/'); +} + +if (!file_exists(SABRE_TEMPDIR)) { + mkdir(SABRE_TEMPDIR); +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/phpcs/ruleset.xml b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/phpcs/ruleset.xml new file mode 100644 index 0000000..ec2c4c8 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/phpcs/ruleset.xml @@ -0,0 +1,57 @@ +<?xml version="1.0"?> +<ruleset name="sabre.php"> + <description>sabre.io codesniffer ruleset</description> + + <!-- Include the whole PSR-1 standard --> + <rule ref="PSR1" /> + + <!-- All PHP files MUST use the Unix LF (linefeed) line ending. --> + <rule ref="Generic.Files.LineEndings"> + <properties> + <property name="eolChar" value="\n"/> + </properties> + </rule> + + <!-- The closing ?> tag MUST be omitted from files containing only PHP. --> + <rule ref="Zend.Files.ClosingTag"/> + + <!-- There MUST NOT be trailing whitespace at the end of non-blank lines. --> + <rule ref="Squiz.WhiteSpace.SuperfluousWhitespace"> + <properties> + <property name="ignoreBlankLines" value="true"/> + </properties> + </rule> + + <!-- There MUST NOT be more than one statement per line. --> + <rule ref="Generic.Formatting.DisallowMultipleStatements"/> + + <rule ref="Generic.WhiteSpace.ScopeIndent"> + <properties> + <property name="ignoreIndentationTokens" type="array" value="T_COMMENT,T_DOC_COMMENT"/> + </properties> + </rule> + <rule ref="Generic.WhiteSpace.DisallowTabIndent"/> + + <!-- PHP keywords MUST be in lower case. --> + <rule ref="Generic.PHP.LowerCaseKeyword"/> + + <!-- The PHP constants true, false, and null MUST be in lower case. --> + <rule ref="Generic.PHP.LowerCaseConstant"/> + + <!-- <rule ref="Squiz.Scope.MethodScope"/> --> + <rule ref="Squiz.WhiteSpace.ScopeKeywordSpacing"/> + + <!-- In the argument list, there MUST NOT be a space before each comma, and there MUST be one space after each comma. --> + <!-- + <rule ref="Squiz.Functions.FunctionDeclarationArgumentSpacing"> + <properties> + <property name="equalsSpacing" value="1"/> + </properties> + </rule> + <rule ref="Squiz.Functions.FunctionDeclarationArgumentSpacing.SpacingAfterHint"> + <severity>0</severity> + </rule> + --> + <rule ref="PEAR.WhiteSpace.ScopeClosingBrace"/> + +</ruleset> diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/phpunit.xml b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/phpunit.xml new file mode 100644 index 0000000..2288151 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/vobject/tests/phpunit.xml @@ -0,0 +1,21 @@ +<phpunit + colors="true" + bootstrap="bootstrap.php" + convertErrorsToExceptions="true" + convertNoticesToExceptions="true" + convertWarningsToExceptions="true" + strict="true" + > + <testsuite name="Sabre\VObject"> + <directory>VObject/</directory> + </testsuite> + + <filter> + <whitelist addUncoveredFilesFromWhitelist="true"> + <directory suffix=".php">../lib/</directory> + <exclude> + <file>../lib/Sabre/VObject/includes.php</file> + </exclude> + </whitelist> + </filter> +</phpunit> diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/.gitignore b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/.gitignore new file mode 100644 index 0000000..accb586 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/.gitignore @@ -0,0 +1,9 @@ +vendor +composer.lock +tests/cov +.*.swp + +# Composer binaries +bin/phpunit +bin/php-cs-fixer +bin/sabre-cs-fixer diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/.travis.yml b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/.travis.yml new file mode 100644 index 0000000..19a61a2 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/.travis.yml @@ -0,0 +1,20 @@ +language: php +php: + - 5.4 + - 5.5 + - 5.6 + - 7 + - hhvm + +matrix: + fast_finish: true + +sudo: false + +cache: vendor + +script: + - ./bin/phpunit --configuration tests/phpunit.xml.dist + - ./bin/sabre-cs-fixer fix . --dry-run --diff + +before_script: composer install diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/CHANGELOG.md b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/CHANGELOG.md new file mode 100644 index 0000000..3d8eb0f --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/CHANGELOG.md @@ -0,0 +1,208 @@ +ChangeLog +========= + +1.4.1 (2016-03-12) +----------------- + +* Parsing clark-notation is now cached. This can speed up parsing large + documents with lots of repeating elements a fair bit. (@icewind1991). + + +1.4.0 (2016-02-14) +------------------ + +* Any array thrown into the serializer with numeric keys is now simply + traversed and each individual item is serialized. This fixes an issue + related to serializing value objects with array children. +* When serializing value objects, properties that have a null value or an + empty array are now skipped. We believe this to be the saner default, but + does constitute a BC break for those depending on this. +* Serializing array properties in value objects was broken. + + +1.3.0 (2015-12-29) +------------------ + +* The `Service` class adds a new `mapValueObject` method which provides basic + capabilities to map between ValueObjects and XML. +* #61: You can now specify serializers for specific classes, allowing you + separate the object you want to serialize from the serializer. This uses the + `$classMap` property which is defined on both the `Service` and `Writer`. +* It's now possible to pass an array of possible root elements to + `Sabre\Xml\Service::expect()`. +* Moved some parsing logic to `Reader::getDeserializerForElementName()`, + so people with more advanced use-cases can implement their own logic there. +* #63: When serializing elements using arrays, the `value` key in the array is + now optional. +* #62: Added a `keyValue` deserializer function. This can be used instead of + the `Element\KeyValue` class and is a lot more flexible. (@staabm) +* Also added an `enum` deserializer function to replace + `Element\Elements`. +* Using an empty string for a namespace prefix now has the same effect as + `null`. + + +1.2.0 (2015-08-30) +------------------ + +* #53: Added `parseGetElements`, a function like `parseInnerTree`, except + that it always returns an array of elements, or an empty array. + + +1.1.0 (2015-06-29) +------------------ + +* #44, #45: Catching broken and invalid XML better and throwing + `Sabre\Xml\LibXMLException` whenever we encounter errors. (@stefanmajoor, + @DaanBiesterbos) + + +1.0.0 (2015-05-25) +------------------ + +* No functional changes since 0.4.3. Marking it as 1.0.0 as a promise for + API stability. +* Using php-cs-fixer for automated CS enforcement. + + +0.4.3 (2015-04-01) +----------------- + +* Minor tweaks for the public release. + + +0.4.2 (2015-03-20) +------------------ + +* Removed `constants.php` again. They messed with PHPUnit and don't really + provide a great benefit. +* #41: Correctly handle self-closing xml elements. + + +0.4.1 (2015-03-19) +------------------ + +* #40: An element with an empty namespace (xmlns="") is not allowed to have a + prefix. This is now fixed. + + +0.4.0 (2015-03-18) +------------------ + +* Added `Sabre\Xml\Service`. This is intended as a simple way to centrally + configure xml applications and easily parse/write things from there. #35, #38. +* Renamed 'baseUri' to 'contextUri' everywhere. +* #36: Added a few convenience constants to `lib/constants.php`. +* `Sabre\Xml\Util::parseClarkNotation` is now in the `Sabre\Xml\Service` class. + + +0.3.1 (2015-02-08) +------------------ + +* Added `XmlDeserializable` to match `XmlSerializable`. + + +0.3.0 (2015-02-06) +------------------ + +* Added `$elementMap` argument to parseInnerTree, for quickly overriding + parsing rules within an element. + + +0.2.2 (2015-02-05) +------------------ + +* Now depends on sabre/uri 1.0. + + +0.2.1 (2014-12-17) +------------------ + +* LibXMLException now inherits from ParseException, so it's easy for users to + catch any exception thrown by the parser. + + +0.2.0 (2014-12-05) +------------------ + +* Major BC Break: method names for the Element interface have been renamed + from `serializeXml` and `deserializeXml` to `xmlSerialize` and + `xmlDeserialize`. This is so that it matches PHP's `JsonSerializable` + interface. +* #25: Added `XmlSerializable` to allow people to write serializers without + having to implement a deserializer in the same class. +* #26: Renamed the `Sabre\XML` namespace to `Sabre\Xml`. Due to composer magic + and the fact that PHP namespace are case-insensitive, this should not affect + anyone, unless you are doing exact string matches on class names. +* #23: It's not possible to automatically extract or serialize Xml fragments + from documents using `Sabre\Xml\Element\XmlFragment`. + + +0.1.0 (2014-11-24) +------------------ + +* #16: Added ability to override `elementMap`, `namespaceMap` and `baseUri` for + a fragment of a document during reading an writing using `pushContext` and + `popContext`. +* Removed: `Writer::$context` and `Reader::$context`. +* #15: Added `Reader::$baseUri` to match `Writer::$baseUri`. +* #20: Allow callbacks to be used instead of `Element` classes in the `Reader`. +* #25: Added `readText` to quickly grab all text from a node and advance the + reader to the next node. +* #15: Added `Sabre\XML\Element\Uri`. + + +0.0.6 (2014-09-26) +------------------ + +* Added: `CData` element. +* #13: Better support for xml with no namespaces. (@kalmas) +* Switched to PSR-4 directory structure. + + +0.0.5 (2013-03-27) +------------------ + +* Added: baseUri property to the Writer class. +* Added: The writeElement method can now write complex elements. +* Added: Throwing exception when invalid objects are written. + + +0.0.4 (2013-03-14) +------------------ + +* Fixed: The KeyValue parser was skipping over elements when there was no + whitespace between them. +* Fixed: Clearing libxml errors after parsing. +* Added: Support for CDATA. +* Added: Context properties. + + +0.0.3 (2013-02-22) +------------------ + +* Changed: Reader::parse returns an array with 1 level less depth. +* Added: A LibXMLException is now thrown if the XMLReader comes across an error. +* Fixed: Both the Elements and KeyValue parsers had severe issues with + nesting. +* Fixed: The reader now detects when the end of the document is hit before it + should (because we're still parsing an element). + + +0.0.2 (2013-02-17) +------------------ + +* Added: Elements parser. +* Added: KeyValue parser. +* Change: Reader::parseSubTree is now named parseInnerTree, and returns either + a string (in case of a text-node), or an array (in case there were child + elements). +* Added: Reader::parseCurrentElement is now public. + + +0.0.1 (2013-02-07) +------------------ + +* First alpha release + +Project started: 2012-11-13. First experiments in June 2009. diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/LICENSE b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/LICENSE new file mode 100644 index 0000000..c9faf40 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/LICENSE @@ -0,0 +1,27 @@ +Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/) + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name Sabre nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/README.md b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/README.md new file mode 100644 index 0000000..e6fc4db --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/README.md @@ -0,0 +1,25 @@ +sabre/xml +========= + +[![Build Status](https://secure.travis-ci.org/fruux/sabre-xml.svg?branch=master)](http://travis-ci.org/fruux/sabre-xml) + +The sabre/xml library is a specialized XML reader and writer. + +Documentation +------------- + +* [Introduction](http://sabre.io/xml/). +* [Installation](http://sabre.io/xml/install/). +* [Reading XML](http://sabre.io/xml/reading/). +* [Writing XML](http://sabre.io/xml/writing/). + + +Support +------- + +Head over to the [SabreDAV mailing list](http://groups.google.com/group/sabredav-discuss) for any questions. + +Made at fruux +------------- + +This library is being developed by [fruux](https://fruux.com/). Drop us a line for commercial services or enterprise support. diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/bin/.empty b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/bin/.empty new file mode 100644 index 0000000..e69de29 diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/composer.json b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/composer.json new file mode 100644 index 0000000..2b8097f --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/composer.json @@ -0,0 +1,50 @@ +{ + "name": "sabre/xml", + "description" : "sabre/xml is an XML library that you may not hate.", + "keywords" : [ "XML", "XMLReader", "XMLWriter", "DOM" ], + "homepage" : "https://sabre.io/xml/", + "license" : "BSD-3-Clause", + "require" : { + "php" : ">=5.4.1", + "ext-xmlwriter" : "*", + "ext-xmlreader" : "*", + "ext-dom" : "*", + "lib-libxml" : ">=2.6.20", + "sabre/uri" : "~1.0" + }, + "authors" : [ + { + "name" : "Evert Pot", + "email" : "me@evertpot.com", + "homepage" : "http://evertpot.com/", + "role" : "Developer" + }, + { + "name": "Markus Staab", + "email": "markus.staab@redaxo.de", + "role" : "Developer" + } + ], + "support" : { + "forum" : "https://groups.google.com/group/sabredav-discuss", + "source" : "https://github.com/fruux/sabre-xml" + }, + "autoload" : { + "psr-4" : { + "Sabre\\Xml\\" : "lib/" + }, + "files": [ + "lib/Deserializer/functions.php", + "lib/Serializer/functions.php" + ] + }, + "bin" : [ + ], + "require-dev": { + "sabre/cs": "~0.0.2", + "phpunit/phpunit" : "*" + }, + "config" : { + "bin-dir" : "bin/" + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/ContextStackTrait.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/ContextStackTrait.php new file mode 100644 index 0000000..ee3a3ba --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/ContextStackTrait.php @@ -0,0 +1,123 @@ +<?php + +namespace Sabre\Xml; + +/** + * Context Stack + * + * The Context maintains information about a document during either reading or + * writing. + * + * During this process, it may be neccesary to override this context + * information. + * + * This trait allows easy access to the context, and allows the end-user to + * override its settings for document fragments, and easily restore it again + * later. + * + * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +trait ContextStackTrait { + + /** + * This is the element map. It contains a list of XML elements (in clark + * notation) as keys and PHP class names as values. + * + * The PHP class names must implement Sabre\Xml\Element. + * + * Values may also be a callable. In that case the function will be called + * directly. + * + * @var array + */ + public $elementMap = []; + + /** + * A contextUri pointing to the document being parsed / written. + * This uri may be used to resolve relative urls that may appear in the + * document. + * + * The reader and writer don't use this property, but as it's an extremely + * common use-case for parsing XML documents, it's added here as a + * convenience. + * + * @var string + */ + public $contextUri; + + /** + * This is a list of namespaces that you want to give default prefixes. + * + * You must make sure you create this entire list before starting to write. + * They should be registered on the root element. + * + * @var array + */ + public $namespaceMap = []; + + /** + * This is a list of custom serializers for specific classes. + * + * The writer may use this if you attempt to serialize an object with a + * class that does not implement XmlSerializable. + * + * Instead it will look at this classmap to see if there is a custom + * serializer here. This is useful if you don't want your value objects + * to be responsible for serializing themselves. + * + * The keys in this classmap need to be fully qualified PHP class names, + * the values must be callbacks. The callbacks take two arguments. The + * writer class, and the value that must be written. + * + * function (Writer $writer, object $value) + * + * @var array + */ + public $classMap = []; + + /** + * Backups of previous contexts. + * + * @var array + */ + protected $contextStack = []; + + /** + * Create a new "context". + * + * This allows you to safely modify the elementMap, contextUri or + * namespaceMap. After you're done, you can restore the old data again + * with popContext. + * + * @return null + */ + function pushContext() { + + $this->contextStack[] = [ + $this->elementMap, + $this->contextUri, + $this->namespaceMap, + $this->classMap + ]; + + } + + /** + * Restore the previous "context". + * + * @return null + */ + function popContext() { + + list( + $this->elementMap, + $this->contextUri, + $this->namespaceMap, + $this->classMap + ) = array_pop($this->contextStack); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Deserializer/functions.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Deserializer/functions.php new file mode 100644 index 0000000..fe88a6d --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Deserializer/functions.php @@ -0,0 +1,255 @@ +<?php + +namespace Sabre\Xml\Deserializer; + +use Sabre\Xml\Reader; + +/** + * This class provides a number of 'deserializer' helper functions. + * These can be used to easily specify custom deserializers for specific + * XML elements. + * + * You can either use these functions from within the $elementMap in the + * Service or Reader class, or you can call them from within your own + * deserializer functions. + */ + +/* + * The 'keyValue' deserializer parses all child elements, and outputs them as + * a "key=>value" array. + * + * For example, keyvalue will parse: + * + * <?xml version="1.0"?> + * <s:root xmlns:s="http://sabredav.org/ns"> + * <s:elem1>value1</s:elem1> + * <s:elem2>value2</s:elem2> + * <s:elem3 /> + * </s:root> + * + * Into: + * + * [ + * "{http://sabredav.org/ns}elem1" => "value1", + * "{http://sabredav.org/ns}elem2" => "value2", + * "{http://sabredav.org/ns}elem3" => null, + * ]; + * + * If you specify the 'namespace' argument, the deserializer will remove + * the namespaces of the keys that match that namespace. + * + * For example, if you call keyValue like this: + * + * keyValue($reader, 'http://sabredav.org/ns') + * + * it's output will instead be: + * + * [ + * "elem1" => "value1", + * "elem2" => "value2", + * "elem3" => null, + * ]; + * + * Attributes will be removed from the top-level elements. If elements with + * the same name appear twice in the list, only the last one will be kept. + * + * + * @param Reader $reader + * @param string $namespace + * @return array + */ +function keyValue(Reader $reader, $namespace = null) { + + // 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) { + if ($namespace !== null && $reader->namespaceURI === $namespace) { + $values[$reader->localName] = $reader->parseCurrentElement()['value']; + } else { + $clark = $reader->getClark(); + $values[$clark] = $reader->parseCurrentElement()['value']; + } + } else { + $reader->read(); + } + } while ($reader->nodeType !== Reader::END_ELEMENT); + + $reader->read(); + + return $values; + +} + +/** + * The 'enum' deserializer parses elements into a simple list + * without values or attributes. + * + * For example, Elements will parse: + * + * <?xml version="1.0"? > + * <s:root xmlns:s="http://sabredav.org/ns"> + * <s:elem1 /> + * <s:elem2 /> + * <s:elem3 /> + * <s:elem4>content</s:elem4> + * <s:elem5 attr="val" /> + * </s:root> + * + * Into: + * + * [ + * "{http://sabredav.org/ns}elem1", + * "{http://sabredav.org/ns}elem2", + * "{http://sabredav.org/ns}elem3", + * "{http://sabredav.org/ns}elem4", + * "{http://sabredav.org/ns}elem5", + * ]; + * + * This is useful for 'enum'-like structures. + * + * If the $namespace argument is specified, it will strip the namespace + * for all elements that match that. + * + * For example, + * + * enum($reader, 'http://sabredav.org/ns') + * + * would return: + * + * [ + * "elem1", + * "elem2", + * "elem3", + * "elem4", + * "elem5", + * ]; + * + * @param Reader $reader + * @param string $namespace + * @return string[] + */ +function enum(Reader $reader, $namespace = null) { + + // If there's no children, we don't do anything. + if ($reader->isEmptyElement) { + $reader->next(); + return []; + } + $reader->read(); + $currentDepth = $reader->depth; + + $values = []; + do { + + if ($reader->nodeType !== Reader::ELEMENT) { + continue; + } + if (!is_null($namespace) && $namespace === $reader->namespaceURI) { + $values[] = $reader->localName; + } else { + $values[] = $reader->getClark(); + } + + } while ($reader->depth >= $currentDepth && $reader->next()); + + $reader->next(); + return $values; + +} + +/** + * The valueObject deserializer turns an xml element into a PHP object of + * a specific class. + * + * This is primarily used by the mapValueObject function from the Service + * class, but it can also easily be used for more specific situations. + * + * @param Reader $reader + * @param string $className + * @param string $namespace + * @return object + */ +function valueObject(Reader $reader, $className, $namespace) { + + $valueObject = new $className(); + if ($reader->isEmptyElement) { + $reader->next(); + return $valueObject; + } + + $defaultProperties = get_class_vars($className); + + $reader->read(); + do { + + if ($reader->nodeType === Reader::ELEMENT && $reader->namespaceURI == $namespace) { + + if (property_exists($valueObject, $reader->localName)) { + if (is_array($defaultProperties[$reader->localName])) { + $valueObject->{$reader->localName}[] = $reader->parseCurrentElement()['value']; + } else { + $valueObject->{$reader->localName} = $reader->parseCurrentElement()['value']; + } + } else { + // Ignore property + $reader->next(); + } + } else { + $reader->read(); + } + } while ($reader->nodeType !== Reader::END_ELEMENT); + + $reader->read(); + return $valueObject; + +} + +/* + * This deserializer helps you deserialize xml structures that look like + * this: + * + * <collection> + * <item>...</item> + * <item>...</item> + * <item>...</item> + * </collection> + * + * Many XML documents use patterns like that, and this deserializer + * allow you to get all the 'items' as an array. + * + * In that previous example, you would register the deserializer as such: + * + * $reader->elementMap['{}collection'] = function($reader) { + * return repeatingElements($reader, '{}item'); + * } + * + * The repeatingElements deserializer simply returns everything as an array. + * + * @param Reader $reader + * @param string $childElementName Element name in clark-notation + * @return array + */ +function repeatingElements(Reader $reader, $childElementName) { + + $result = []; + + foreach ($reader->parseGetElements() as $element) { + + if ($element['name'] === $childElementName) { + $result[] = $element['value']; + } + + } + + return $result; + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Element.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Element.php new file mode 100644 index 0000000..dd89c58 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Element.php @@ -0,0 +1,20 @@ +<?php + +namespace Sabre\Xml; + +/** + * This is the XML element interface. + * + * Elements are responsible for serializing and deserializing part of an XML + * document into PHP values. + * + * It combines XmlSerializable and XmlDeserializable into one logical class + * that does both. + * + * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +interface Element extends XmlSerializable, XmlDeserializable { + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Element/Base.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Element/Base.php new file mode 100644 index 0000000..f59ba49 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Element/Base.php @@ -0,0 +1,91 @@ +<?php + +namespace Sabre\Xml\Element; + +use Sabre\Xml; + +/** + * The Base XML element is the standard parser & generator that's used by the + * XML reader and writer. + * + * It spits out a simple PHP array structure during deserialization, that can + * also be directly injected back into Writer::write. + * + * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Base implements Xml\Element { + + /** + * PHP value to serialize. + * + * @var mixed + */ + protected $value; + + /** + * Constructor + * + * @param mixed $value + */ + function __construct($value = null) { + + $this->value = $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 XmlSerializable 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(Xml\Writer $writer) { + + $writer->write($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 Xml\Reader $reader + * @return mixed + */ + static function xmlDeserialize(Xml\Reader $reader) { + + $subTree = $reader->parseInnerTree(); + return $subTree; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Element/Cdata.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Element/Cdata.php new file mode 100644 index 0000000..5f42c4c --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Element/Cdata.php @@ -0,0 +1,64 @@ +<?php + +namespace Sabre\Xml\Element; + +use Sabre\Xml; + +/** + * CDATA element. + * + * This element allows you to easily inject CDATA. + * + * Note that we strongly recommend avoiding CDATA nodes, unless you definitely + * know what you're doing, or you're working with unchangable systems that + * require CDATA. + * + * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Cdata implements Xml\XmlSerializable +{ + /** + * CDATA element value. + * + * @var string + */ + protected $value; + + /** + * Constructor + * + * @param string $value + */ + function __construct($value) + { + $this->value = $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(Xml\Writer $writer) { + + $writer->writeCData($this->value); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Element/Elements.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Element/Elements.php new file mode 100644 index 0000000..9eefd1b --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Element/Elements.php @@ -0,0 +1,108 @@ +<?php + +namespace Sabre\Xml\Element; + +use Sabre\Xml; +use Sabre\Xml\Deserializer; +use Sabre\Xml\Serializer; + +/** + * 'Elements' is a simple list of elements, without values or attributes. + * For example, Elements will parse: + * + * <?xml version="1.0"?> + * <s:root xmlns:s="http://sabredav.org/ns"> + * <s:elem1 /> + * <s:elem2 /> + * <s:elem3 /> + * <s:elem4>content</s:elem4> + * <s:elem5 attr="val" /> + * </s:root> + * + * Into: + * + * [ + * "{http://sabredav.org/ns}elem1", + * "{http://sabredav.org/ns}elem2", + * "{http://sabredav.org/ns}elem3", + * "{http://sabredav.org/ns}elem4", + * "{http://sabredav.org/ns}elem5", + * ]; + * + * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Elements implements Xml\Element { + + /** + * Value to serialize + * + * @var array + */ + protected $value; + + /** + * Constructor + * + * @param array $value + */ + function __construct(array $value = []) { + + $this->value = $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(Xml\Writer $writer) { + + Serializer\enum($writer, $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->parseSubTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Xml\Reader $reader + * @return mixed + */ + static function xmlDeserialize(Xml\Reader $reader) { + + return Deserializer\enum($reader); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Element/KeyValue.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Element/KeyValue.php new file mode 100644 index 0000000..7ce53bf --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Element/KeyValue.php @@ -0,0 +1,108 @@ +<?php + +namespace Sabre\Xml\Element; + +use Sabre\Xml; +use Sabre\Xml\Deserializer; + +/** + * 'KeyValue' parses out all child elements from a single node, and outputs a + * key=>value struct. + * + * Attributes will be removed, and duplicate child elements are discarded. + * Complex values within the elements will be parsed by the 'standard' parser. + * + * For example, KeyValue will parse: + * + * <?xml version="1.0"?> + * <s:root xmlns:s="http://sabredav.org/ns"> + * <s:elem1>value1</s:elem1> + * <s:elem2>value2</s:elem2> + * <s:elem3 /> + * </s:root> + * + * Into: + * + * [ + * "{http://sabredav.org/ns}elem1" => "value1", + * "{http://sabredav.org/ns}elem2" => "value2", + * "{http://sabredav.org/ns}elem3" => null, + * ]; + * + * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class KeyValue implements Xml\Element { + + /** + * Value to serialize + * + * @var array + */ + protected $value; + + /** + * Constructor + * + * @param array $value + */ + function __construct(array $value = []) { + + $this->value = $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(Xml\Writer $writer) { + + $writer->write($this->value); + + } + + /** + * 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 Xml\Reader $reader + * @return mixed + */ + static function xmlDeserialize(Xml\Reader $reader) { + + return Deserializer\keyValue($reader); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Element/Uri.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Element/Uri.php new file mode 100644 index 0000000..8f45c00 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Element/Uri.php @@ -0,0 +1,104 @@ +<?php + +namespace Sabre\Xml\Element; + +use Sabre\Xml; + +/** + * Uri element. + * + * This represents a single uri. An example of how this may be encoded: + * + * <link>/foo/bar</link> + * <d:href xmlns:d="DAV:">http://example.org/hi</d:href> + * + * If the uri is relative, it will be automatically expanded to an absolute + * url during writing and reading, if the contextUri property is set on the + * reader and/or writer. + * + * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Uri implements Xml\Element { + + /** + * Uri element value. + * + * @var string + */ + protected $value; + + /** + * Constructor + * + * @param string $value + */ + function __construct($value) + { + $this->value = $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(Xml\Writer $writer) { + + $writer->text( + \Sabre\Uri\resolve( + $writer->contextUri, + $this->value + ) + ); + + } + + /** + * This method is called during xml parsing. + * + * This method is called statically, 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->parseSubTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Xml\Reader $reader + * @return mixed + */ + static function xmlDeserialize(Xml\Reader $reader) { + + return new self( + \Sabre\Uri\resolve( + $reader->contextUri, + $reader->readText() + ) + ); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Element/XmlFragment.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Element/XmlFragment.php new file mode 100644 index 0000000..0abfac1 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Element/XmlFragment.php @@ -0,0 +1,147 @@ +<?php + +namespace Sabre\Xml\Element; + +use Sabre\Xml\Reader; +use Sabre\Xml\Writer; +use Sabre\Xml\Element; + +/** + * The XmlFragment element allows you to extract a portion of your xml tree, + * and get a well-formed xml string. + * + * This goes a bit beyond `innerXml` and friends, as we'll also match all the + * correct namespaces. + * + * Please note that the XML fragment: + * + * 1. Will not have an <?xml declaration. + * 2. Or a DTD + * 3. It will have all the relevant xmlns attributes. + * 4. It may not have a root element. + */ +class XmlFragment implements Element { + + protected $xml; + + function __construct($xml) { + + $this->xml = $xml; + + } + + function getXml() { + + return $this->xml; + + } + + /** + * 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) { + + $reader = new Reader(); + + // Wrapping the xml in a container, so root-less values can still be + // parsed. + $xml = <<<XML +<?xml version="1.0"?> +<xml-fragment xmlns="http://sabre.io/ns">{$this->getXml()}</xml-fragment> +XML; + + $reader->xml($xml); + + while ($reader->read()) { + + if ($reader->depth < 1) { + // Skipping the root node. + continue; + } + + switch ($reader->nodeType) { + + case Reader::ELEMENT : + $writer->startElement( + $reader->getClark() + ); + $empty = $reader->isEmptyElement; + while ($reader->moveToNextAttribute()) { + switch ($reader->namespaceURI) { + case '' : + $writer->writeAttribute($reader->localName, $reader->value); + break; + case 'http://www.w3.org/2000/xmlns/' : + // Skip namespace declarations + break; + default : + $writer->writeAttribute($reader->getClark(), $reader->value); + break; + } + } + if ($empty) { + $writer->endElement(); + } + break; + case Reader::CDATA : + case Reader::TEXT : + $writer->text( + $reader->value + ); + break; + case Reader::END_ELEMENT : + $writer->endElement(); + 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) { + + $result = new self($reader->readInnerXml()); + $reader->next(); + return $result; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/LibXMLException.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/LibXMLException.php new file mode 100644 index 0000000..f0190eb --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/LibXMLException.php @@ -0,0 +1,53 @@ +<?php + +namespace Sabre\Xml; + +use + LibXMLError; + +/** + * This exception is thrown when the Readers runs into a parsing error. + * + * This exception effectively wraps 1 or more LibXMLError objects. + * + * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class LibXMLException extends ParseException { + + /** + * The error list. + * + * @var LibXMLError[] + */ + protected $errors; + + /** + * Creates the exception. + * + * You should pass a list of LibXMLError objects in its constructor. + * + * @param LibXMLError[] $errors + * @param int $code + * @param Exception $previousException + */ + function __construct(array $errors, $code = null, Exception $previousException = null) { + + $this->errors = $errors; + parent::__construct($errors[0]->message . ' on line ' . $errors[0]->line . ', column ' . $errors[0]->column, $code, $previousException); + + } + + /** + * Returns the LibXML errors + * + * @return void + */ + function getErrors() { + + return $this->errors; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/ParseException.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/ParseException.php new file mode 100644 index 0000000..3a6883b --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/ParseException.php @@ -0,0 +1,17 @@ +<?php + +namespace Sabre\Xml; + +use + Exception; + +/** + * This is a base exception for any exception related to parsing xml files. + * + * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class ParseException extends Exception { + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Reader.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Reader.php new file mode 100644 index 0000000..7cba76c --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Reader.php @@ -0,0 +1,308 @@ +<?php + +namespace Sabre\Xml; + +use XMLReader; + +/** + * The Reader class expands upon PHP's built-in XMLReader. + * + * The intended usage, is to assign certain XML elements to PHP classes. These + * need to be registered using the $elementMap public property. + * + * After this is done, a single call to parse() will parse the entire document, + * and delegate sub-sections of the document to element classes. + * + * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Reader extends XMLReader { + + use ContextStackTrait; + + /** + * Returns the current nodename in clark-notation. + * + * For example: "{http://www.w3.org/2005/Atom}feed". + * Or if no namespace is defined: "{}feed". + * + * This method returns null if we're not currently on an element. + * + * @return string|null + */ + function getClark() { + + if (! $this->localName) { + return null; + } + + return '{' . $this->namespaceURI . '}' . $this->localName; + + } + + /** + * Reads the entire document. + * + * This function returns an array with the following three elements: + * * name - The root element name. + * * value - The value for the root element. + * * attributes - An array of attributes. + * + * This function will also disable the standard libxml error handler (which + * usually just results in PHP errors), and throw exceptions instead. + * + * @return array + */ + function parse() { + + $previousEntityState = libxml_disable_entity_loader(true); + $previousSetting = libxml_use_internal_errors(true); + + // Really sorry about the silence operator, seems like I have no + // choice. See: + // + // https://bugs.php.net/bug.php?id=64230 + while ($this->nodeType !== self::ELEMENT && @$this->read()) { + // noop + } + $result = $this->parseCurrentElement(); + + $errors = libxml_get_errors(); + libxml_clear_errors(); + libxml_use_internal_errors($previousSetting); + libxml_disable_entity_loader($previousEntityState); + + if ($errors) { + throw new LibXMLException($errors); + } + + return $result; + } + + + + /** + * parseGetElements parses everything in the current sub-tree, + * and returns a an array of elements. + * + * Each element has a 'name', 'value' and 'attributes' key. + * + * If the the element didn't contain sub-elements, an empty array is always + * returned. If there was any text inside the element, it will be + * discarded. + * + * If the $elementMap argument is specified, the existing elementMap will + * be overridden while parsing the tree, and restored after this process. + * + * @param array $elementMap + * @return array + */ + function parseGetElements(array $elementMap = null) { + + $result = $this->parseInnerTree($elementMap); + if (!is_array($result)) { + return []; + } + return $result; + + } + + /** + * Parses all elements below the current element. + * + * This method will return a string if this was a text-node, or an array if + * there were sub-elements. + * + * If there's both text and sub-elements, the text will be discarded. + * + * If the $elementMap argument is specified, the existing elementMap will + * be overridden while parsing the tree, and restored after this process. + * + * @param array $elementMap + * @return array|string + */ + function parseInnerTree(array $elementMap = null) { + + $text = null; + $elements = []; + + if ($this->nodeType === self::ELEMENT && $this->isEmptyElement) { + // Easy! + $this->next(); + return null; + } + + if (!is_null($elementMap)) { + $this->pushContext(); + $this->elementMap = $elementMap; + } + + // Really sorry about the silence operator, seems like I have no + // choice. See: + // + // https://bugs.php.net/bug.php?id=64230 + if (!@$this->read()) return false; + + while (true) { + + if (!$this->isValid()) { + + $errors = libxml_get_errors(); + + if ($errors) { + libxml_clear_errors(); + throw new LibXMLException($errors); + } + } + + switch ($this->nodeType) { + case self::ELEMENT : + $elements[] = $this->parseCurrentElement(); + break; + case self::TEXT : + case self::CDATA : + $text .= $this->value; + $this->read(); + break; + case self::END_ELEMENT : + // Ensuring we are moving the cursor after the end element. + $this->read(); + break 2; + case self::NONE : + throw new ParseException('We hit the end of the document prematurely. This likely means that some parser "eats" too many elements. Do not attempt to continue parsing.'); + default : + // Advance to the next element + $this->read(); + break; + } + + } + + if (!is_null($elementMap)) { + $this->popContext(); + } + return ($elements ? $elements : $text); + + } + + /** + * Reads all text below the current element, and returns this as a string. + * + * @return string + */ + function readText() { + + $result = ''; + $previousDepth = $this->depth; + + while ($this->read() && $this->depth != $previousDepth) { + if (in_array($this->nodeType, [XMLReader::TEXT, XMLReader::CDATA, XMLReader::WHITESPACE])) { + $result .= $this->value; + } + } + return $result; + + } + + /** + * Parses the current XML element. + * + * This method returns arn array with 3 properties: + * * name - A clark-notation XML element name. + * * value - The parsed value. + * * attributes - A key-value list of attributes. + * + * @return array + */ + function parseCurrentElement() { + + $name = $this->getClark(); + + $attributes = []; + + if ($this->hasAttributes) { + $attributes = $this->parseAttributes(); + } + + $value = call_user_func( + $this->getDeserializerForElementName($name), + $this + ); + + return [ + 'name' => $name, + 'value' => $value, + 'attributes' => $attributes, + ]; + } + + + /** + * Grabs all the attributes from the current element, and returns them as a + * key-value array. + * + * If the attributes are part of the same namespace, they will simply be + * short keys. If they are defined on a different namespace, the attribute + * name will be retured in clark-notation. + * + * @return array + */ + function parseAttributes() { + + $attributes = []; + + while ($this->moveToNextAttribute()) { + if ($this->namespaceURI) { + + // Ignoring 'xmlns', it doesn't make any sense. + if ($this->namespaceURI === 'http://www.w3.org/2000/xmlns/') { + continue; + } + + $name = $this->getClark(); + $attributes[$name] = $this->value; + + } else { + $attributes[$this->localName] = $this->value; + } + } + $this->moveToElement(); + + return $attributes; + + } + + /** + * Returns the function that should be used to parse the element identified + * by it's clark-notation name. + * + * @param string $name + * @return callable + */ + function getDeserializerForElementName($name) { + + if (!array_key_exists($name, $this->elementMap)) { + return ['Sabre\\Xml\\Element\\Base', 'xmlDeserialize']; + } + + $deserializer = $this->elementMap[$name]; + if (is_subclass_of($deserializer, 'Sabre\\Xml\\XmlDeserializable')) { + return [ $deserializer, 'xmlDeserialize' ]; + } + + if (is_callable($deserializer)) { + return $deserializer; + } + + $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 . ' for element: ' . $name); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Serializer/functions.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Serializer/functions.php new file mode 100644 index 0000000..2144801 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Serializer/functions.php @@ -0,0 +1,249 @@ +<?php + +namespace Sabre\Xml\Serializer; + +use InvalidArgumentException; +use Sabre\Xml\Writer; +use Sabre\Xml\XmlSerializable; + +/** + * This file provides a number of 'serializer' helper functions. + * + * These helper functions can be used to easily xml-encode common PHP + * data structures, or can be placed in the $classMap. + */ + +/** + * The 'enum' serializer writes simple list of elements. + * + * For example, calling: + * + * enum($writer, [ + * "{http://sabredav.org/ns}elem1", + * "{http://sabredav.org/ns}elem2", + * "{http://sabredav.org/ns}elem3", + * "{http://sabredav.org/ns}elem4", + * "{http://sabredav.org/ns}elem5", + * ]); + * + * Will generate something like this (if the correct namespace is declared): + * + * <s:elem1 /> + * <s:elem2 /> + * <s:elem3 /> + * <s:elem4>content</s:elem4> + * <s:elem5 attr="val" /> + * + * @param Writer $writer + * @param string[] $values + * @return void + */ +function enum(Writer $writer, array $values) { + + foreach ($values as $value) { + $writer->writeElement($value); + } +} + +/** + * The valueObject serializer turns a simple PHP object into a classname. + * + * Every public property will be encoded as an xml element with the same + * name, in the XML namespace as specified. + * + * Values that are set to null or an empty array are not serialized. To + * serialize empty properties, you must specify them as an empty string. + * + * @param Writer $writer + * @param object $valueObject + * @param string $namespace + */ +function valueObject(Writer $writer, $valueObject, $namespace) { + foreach (get_object_vars($valueObject) as $key => $val) { + if (is_array($val)) { + // If $val is an array, it has a special meaning. We need to + // generate one child element for each item in $val + foreach ($val as $child) { + $writer->writeElement('{' . $namespace . '}' . $key, $child); + } + + } elseif ($val !== null) { + $writer->writeElement('{' . $namespace . '}' . $key, $val); + } + } +} + + +/** + * This serializer helps you serialize xml structures that look like + * this: + * + * <collection> + * <item>...</item> + * <item>...</item> + * <item>...</item> + * </collection> + * + * In that previous example, this serializer just serializes the item element, + * and this could be called like this: + * + * repeatingElements($writer, $items, '{}item'); + * + * @param Writer $writer + * @param array $items A list of items sabre/xml can serialize. + * @param string $childElementName Element name in clark-notation + * @return void + */ +function repeatingElements(Writer $writer, array $items, $childElementName) { + + foreach ($items as $item) { + $writer->writeElement($childElementName, $item); + } + +} + +/** + * This function is the 'default' serializer that is able to serialize most + * things, and delegates to other serializers if needed. + * + * The standardSerializer supports a wide-array of values. + * + * $value may be a string or integer, it will just write out the string as text. + * $value may be an instance of XmlSerializable or Element, in which case it + * calls it's xmlSerialize() method. + * $value may be a PHP callback/function/closure, in case we call the callback + * and give it the Writer as an argument. + * $value may be a an object, and if it's in the classMap we automatically call + * the correct serializer for it. + * $value may be null, in which case we do nothing. + * + * If $value is an array, the array must look like this: + * + * [ + * [ + * 'name' => '{namespaceUri}element-name', + * 'value' => '...', + * 'attributes' => [ 'attName' => 'attValue' ] + * ] + * [, + * 'name' => '{namespaceUri}element-name2', + * 'value' => '...', + * ] + * ] + * + * This would result in xml like: + * + * <element-name xmlns="namespaceUri" attName="attValue"> + * ... + * </element-name> + * <element-name2> + * ... + * </element-name2> + * + * The value property may be any value standardSerializer supports, so you can + * nest data-structures this way. Both value and attributes are optional. + * + * Alternatively, you can also specify the array using this syntax: + * + * [ + * [ + * '{namespaceUri}element-name' => '...', + * '{namespaceUri}element-name2' => '...', + * ] + * ] + * + * This is excellent for simple key->value structures, and here you can also + * specify anything for the value. + * + * You can even mix the two array syntaxes. + * + * @param Writer $writer + * @param string|int|float|bool|array|object + * @return void + */ +function standardSerializer(Writer $writer, $value) { + + if (is_scalar($value)) { + + // String, integer, float, boolean + $writer->text($value); + + } elseif ($value instanceof XmlSerializable) { + + // XmlSerializable classes or Element classes. + $value->xmlSerialize($writer); + + } elseif (is_object($value) && isset($writer->classMap[get_class($value)])) { + + // It's an object which class appears in the classmap. + $writer->classMap[get_class($value)]($writer, $value); + + } elseif (is_callable($value)) { + + // A callback + $value($writer); + + } elseif (is_null($value)) { + + // nothing! + + } elseif (is_array($value) && array_key_exists('name', $value)) { + + // if the array had a 'name' element, we assume that this array + // describes a 'name' and optionally 'attributes' and 'value'. + + $name = $value['name']; + $attributes = isset($value['attributes']) ? $value['attributes'] : []; + $value = isset($value['value']) ? $value['value'] : null; + + $writer->startElement($name); + $writer->writeAttributes($attributes); + $writer->write($value); + $writer->endElement(); + + } elseif (is_array($value)) { + + foreach ($value as $name => $item) { + + if (is_int($name)) { + + // This item has a numeric index. We just loop through the + // array and throw it back in the writer. + standardSerializer($writer, $item); + + } elseif (is_string($name) && is_array($item) && isset($item['attributes'])) { + + // The key is used for a name, but $item has 'attributes' and + // possibly 'value' + $writer->startElement($name); + $writer->writeAttributes($item['attributes']); + if (isset($item['value'])) { + $writer->write($item['value']); + } + $writer->endElement(); + + } elseif (is_string($name)) { + + // This was a plain key-value array. + $writer->startElement($name); + $writer->write($item); + $writer->endElement(); + + } else { + + throw new InvalidArgumentException('The writer does not know how to serialize arrays with keys of type: ' . gettype($name)); + + } + } + + } elseif (is_object($value)) { + + throw new InvalidArgumentException('The writer cannot serialize objects of class: ' . get_class($value)); + + } else { + + throw new InvalidArgumentException('The writer cannot serialize values of type: ' . gettype($value)); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Service.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Service.php new file mode 100644 index 0000000..b2603a4 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Service.php @@ -0,0 +1,291 @@ +<?php + +namespace Sabre\Xml; + +/** + * XML parsing and writing service. + * + * You are encouraged to make a instance of this for your application and + * potentially extend it, as a central API point for dealing with xml and + * configuring the reader and writer. + * + * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Service { + + /** + * This is the element map. It contains a list of XML elements (in clark + * notation) as keys and PHP class names as values. + * + * The PHP class names must implement Sabre\Xml\Element. + * + * Values may also be a callable. In that case the function will be called + * directly. + * + * @var array + */ + public $elementMap = []; + + /** + * This is a list of namespaces that you want to give default prefixes. + * + * You must make sure you create this entire list before starting to write. + * They should be registered on the root element. + * + * @var array + */ + public $namespaceMap = []; + + /** + * This is a list of custom serializers for specific classes. + * + * The writer may use this if you attempt to serialize an object with a + * class that does not implement XmlSerializable. + * + * Instead it will look at this classmap to see if there is a custom + * serializer here. This is useful if you don't want your value objects + * to be responsible for serializing themselves. + * + * The keys in this classmap need to be fully qualified PHP class names, + * the values must be callbacks. The callbacks take two arguments. The + * writer class, and the value that must be written. + * + * function (Writer $writer, object $value) + * + * @var array + */ + public $classMap = []; + + /** + * Returns a fresh XML Reader + * + * @return Reader + */ + function getReader() { + + $r = new Reader(); + $r->elementMap = $this->elementMap; + return $r; + + } + + /** + * Returns a fresh xml writer + * + * @return Writer + */ + function getWriter() { + + $w = new Writer(); + $w->namespaceMap = $this->namespaceMap; + $w->classMap = $this->classMap; + return $w; + + } + + /** + * Parses a document in full. + * + * Input may be specified as a string or readable stream resource. + * The returned value is the value of the root document. + * + * Specifying the $contextUri allows the parser to figure out what the URI + * of the document was. This allows relative URIs within the document to be + * expanded easily. + * + * The $rootElementName is specified by reference and will be populated + * with the root element name of the document. + * + * @param string|resource $input + * @param string|null $contextUri + * @param string|null $rootElementName + * @throws ParseException + * @return array|object|string + */ + function parse($input, $contextUri = null, &$rootElementName = null) { + + if (is_resource($input)) { + // Unfortunately the XMLReader doesn't support streams. When it + // does, we can optimize this. + $input = stream_get_contents($input); + } + $r = $this->getReader(); + $r->contextUri = $contextUri; + $r->xml($input); + + $result = $r->parse(); + $rootElementName = $result['name']; + return $result['value']; + + } + + /** + * Parses a document in full, and specify what the expected root element + * name is. + * + * This function works similar to parse, but the difference is that the + * user can specify what the expected name of the root element should be, + * in clark notation. + * + * This is useful in cases where you expected a specific document to be + * passed, and reduces the amount of if statements. + * + * It's also possible to pass an array of expected rootElements if your + * code may expect more than one document type. + * + * @param string|string[] $rootElementName + * @param string|resource $input + * @param string|null $contextUri + * @return void + */ + function expect($rootElementName, $input, $contextUri = null) { + + if (is_resource($input)) { + // Unfortunately the XMLReader doesn't support streams. When it + // does, we can optimize this. + $input = stream_get_contents($input); + } + $r = $this->getReader(); + $r->contextUri = $contextUri; + $r->xml($input); + + $result = $r->parse(); + if (!in_array($result['name'], (array)$rootElementName, true)) { + throw new ParseException('Expected ' . implode(' or ', (array)$rootElementName) . ' but received ' . $result['name'] . ' as the root element'); + } + return $result['value']; + + } + + /** + * Generates an XML document in one go. + * + * The $rootElement must be specified in clark notation. + * The value must be a string, an array or an object implementing + * XmlSerializable. Basically, anything that's supported by the Writer + * object. + * + * $contextUri can be used to specify a sort of 'root' of the PHP application, + * in case the xml document is used as a http response. + * + * This allows an implementor to easily create URI's relative to the root + * of the domain. + * + * @param string $rootElementName + * @param string|array|XmlSerializable $value + * @param string|null $contextUri + */ + function write($rootElementName, $value, $contextUri = null) { + + $w = $this->getWriter(); + $w->openMemory(); + $w->contextUri = $contextUri; + $w->setIndent(true); + $w->startDocument(); + $w->writeElement($rootElementName, $value); + return $w->outputMemory(); + + } + + /** + * Map an xml element to a PHP class. + * + * Calling this function will automatically setup the Reader and Writer + * classes to turn a specific XML element to a PHP class. + * + * For example, given a class such as : + * + * class Author { + * public $firstName; + * public $lastName; + * } + * + * and an XML element such as: + * + * <author xmlns="http://example.org/ns"> + * <firstName>...</firstName> + * <lastName>...</lastName> + * </author> + * + * These can easily be mapped by calling: + * + * $service->mapValueObject('{http://example.org}author', 'Author'); + * + * @param string $elementName + * @param object $className + * @return void + */ + function mapValueObject($elementName, $className) { + list($namespace) = self::parseClarkNotation($elementName); + + $this->elementMap[$elementName] = function(Reader $reader) use ($className, $namespace) { + return \Sabre\Xml\Deserializer\valueObject($reader, $className, $namespace); + }; + $this->classMap[$className] = function(Writer $writer, $valueObject) use ($namespace) { + return \Sabre\Xml\Serializer\valueObject($writer, $valueObject, $namespace); + }; + $this->valueObjectMap[$className] = $elementName; + } + + /** + * Writes a value object. + * + * This function largely behaves similar to write(), except that it's + * intended specifically to serialize a Value Object into an XML document. + * + * The ValueObject must have been previously registered using + * mapValueObject(). + * + * @param object $object + * @param string $contextUri + * @return void + */ + function writeValueObject($object, $contextUri = null) { + + if (!isset($this->valueObjectMap[get_class($object)])) { + throw new \InvalidArgumentException('"' . get_class($object) . '" is not a registered value object class. Register your class with mapValueObject.'); + } + return $this->write( + $this->valueObjectMap[get_class($object)], + $object, + $contextUri + ); + + } + + /** + * 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) { + static $cache = []; + + if (!isset($cache[$str])) { + + if (!preg_match('/^{([^}]*)}(.*)$/', $str, $matches)) { + throw new \InvalidArgumentException('\'' . $str . '\' is not a valid clark-notation formatted string'); + } + + $cache[$str] = [ + $matches[1], + $matches[2] + ]; + } + + return $cache[$str]; + } + + /** + * A list of classes and which XML elements they map to. + */ + protected $valueObjectMap = []; + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Version.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Version.php new file mode 100644 index 0000000..f199e71 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Version.php @@ -0,0 +1,19 @@ +<?php + +namespace Sabre\Xml; + +/** + * This class contains the version number for this package. + * + * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ + */ +class Version { + + /** + * Full version number + */ + const VERSION = '1.4.1'; + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Writer.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Writer.php new file mode 100644 index 0000000..adfbe0c --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/Writer.php @@ -0,0 +1,266 @@ +<?php + +namespace Sabre\Xml; + +use XMLWriter; + +/** + * The XML Writer class. + * + * This class works exactly as PHP's built-in XMLWriter, with a few additions. + * + * Namespaces can be registered beforehand, globally. When the first element is + * written, namespaces will automatically be declared. + * + * The writeAttribute, startElement and writeElement can now take a + * clark-notation element name (example: {http://www.w3.org/2005/Atom}link). + * + * If, when writing the namespace is a known one a prefix will automatically be + * selected, otherwise a random prefix will be generated. + * + * Instead of standard string values, the writer can take Element classes (as + * defined by this library) to delegate the serialization. + * + * The write() method can take array structures to quickly write out simple xml + * trees. + * + * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Writer extends XMLWriter { + + use ContextStackTrait; + + /** + * Any namespace that the writer is asked to write, will be added here. + * + * Any of these elements will get a new namespace definition *every single + * time* they are used, but this array allows the writer to make sure that + * the prefixes are consistent anyway. + * + * @var array + */ + protected $adhocNamespaces = []; + + /** + * When the first element is written, this flag is set to true. + * + * This ensures that the namespaces in the namespaces map are only written + * once. + * + * @var bool + */ + protected $namespacesWritten = false; + + /** + * Writes a value to the output stream. + * + * The following values are supported: + * 1. Scalar values will be written as-is, as text. + * 2. Null values will be skipped (resulting in a short xml tag). + * 3. If a value is an instance of an Element class, writing will be + * delegated to the object. + * 4. If a value is an array, two formats are supported. + * + * Array format 1: + * [ + * "{namespace}name1" => "..", + * "{namespace}name2" => "..", + * ] + * + * One element will be created for each key in this array. The values of + * this array support any format this method supports (this method is + * called recursively). + * + * Array format 2: + * + * [ + * [ + * "name" => "{namespace}name1" + * "value" => "..", + * "attributes" => [ + * "attr" => "attribute value", + * ] + * ], + * [ + * "name" => "{namespace}name1" + * "value" => "..", + * "attributes" => [ + * "attr" => "attribute value", + * ] + * ] + * ] + * + * @param mixed $value + * @return void + */ + function write($value) { + + Serializer\standardSerializer($this, $value); + + } + + /** + * Opens a new element. + * + * You can either just use a local elementname, or you can use clark- + * notation to start a new element. + * + * Example: + * + * $writer->startElement('{http://www.w3.org/2005/Atom}entry'); + * + * Would result in something like: + * + * <entry xmlns="http://w3.org/2005/Atom"> + * + * @param string $name + * @return bool + */ + function startElement($name) { + + if ($name[0] === '{') { + + list($namespace, $localName) = + Service::parseClarkNotation($name); + + if (array_key_exists($namespace, $this->namespaceMap)) { + $result = $this->startElementNS( + $this->namespaceMap[$namespace] === '' ? null : $this->namespaceMap[$namespace], + $localName, + null + ); + } else { + + // An empty namespace means it's the global namespace. This is + // allowed, but it mustn't get a prefix. + if ($namespace === "" || $namespace === null) { + $result = $this->startElement($localName); + $this->writeAttribute('xmlns', ''); + } else { + if (!isset($this->adhocNamespaces[$namespace])) { + $this->adhocNamespaces[$namespace] = 'x' . (count($this->adhocNamespaces) + 1); + } + $result = $this->startElementNS($this->adhocNamespaces[$namespace], $localName, $namespace); + } + } + + } else { + $result = parent::startElement($name); + } + + if (!$this->namespacesWritten) { + + foreach ($this->namespaceMap as $namespace => $prefix) { + $this->writeAttribute(($prefix ? 'xmlns:' . $prefix : 'xmlns'), $namespace); + } + $this->namespacesWritten = true; + + } + + return $result; + + } + + /** + * Write a full element tag and it's contents. + * + * This method automatically closes the element as well. + * + * The element name may be specified in clark-notation. + * + * Examples: + * + * $writer->writeElement('{http://www.w3.org/2005/Atom}author',null); + * becomes: + * <author xmlns="http://www.w3.org/2005" /> + * + * $writer->writeElement('{http://www.w3.org/2005/Atom}author', [ + * '{http://www.w3.org/2005/Atom}name' => 'Evert Pot', + * ]); + * becomes: + * <author xmlns="http://www.w3.org/2005" /><name>Evert Pot</name></author> + * + * @param string $name + * @param string $content + * @return bool + */ + function writeElement($name, $content = null) { + + $this->startElement($name); + if (!is_null($content)) { + $this->write($content); + } + $this->endElement(); + + } + + /** + * Writes a list of attributes. + * + * Attributes are specified as a key->value array. + * + * The key is an attribute name. If the key is a 'localName', the current + * xml namespace is assumed. If it's a 'clark notation key', this namespace + * will be used instead. + * + * @param array $attributes + * @return void + */ + function writeAttributes(array $attributes) { + + foreach ($attributes as $name => $value) { + $this->writeAttribute($name, $value); + } + + } + + /** + * Writes a new attribute. + * + * The name may be specified in clark-notation. + * + * Returns true when successful. + * + * @param string $name + * @param string $value + * @return bool + */ + function writeAttribute($name, $value) { + + if ($name[0] === '{') { + + list( + $namespace, + $localName + ) = Service::parseClarkNotation($name); + + if (array_key_exists($namespace, $this->namespaceMap)) { + // It's an attribute with a namespace we know + $this->writeAttribute( + $this->namespaceMap[$namespace] . ':' . $localName, + $value + ); + } else { + + // We don't know the namespace, we must add it in-line + if (!isset($this->adhocNamespaces[$namespace])) { + $this->adhocNamespaces[$namespace] = 'x' . (count($this->adhocNamespaces) + 1); + } + $this->writeAttributeNS( + $this->adhocNamespaces[$namespace], + $localName, + $namespace, + $value + ); + + } + + } else { + return parent::writeAttribute($name, $value); + } + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/XmlDeserializable.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/XmlDeserializable.php new file mode 100644 index 0000000..fa857e8 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/XmlDeserializable.php @@ -0,0 +1,38 @@ +<?php + +namespace Sabre\Xml; + +/** + * Implementing the XmlDeserializable interface allows you to use a class as a + * deserializer for a specific element. + * + * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +interface XmlDeserializable { + + /** + * The deserialize method is called during xml parsing. + * + * This method is called statically, 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); + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/XmlSerializable.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/XmlSerializable.php new file mode 100644 index 0000000..3e2c528 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/lib/XmlSerializable.php @@ -0,0 +1,36 @@ +<?php + +namespace Sabre\Xml; + +/** + * Objects implementing XmlSerializable can control how they are represented in + * Xml. + * + * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +interface XmlSerializable { + + /** + * The xmlSerialize method 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); + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/ContextStackTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/ContextStackTest.php new file mode 100644 index 0000000..71dfd3f --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/ContextStackTest.php @@ -0,0 +1,44 @@ +<?php + +namespace Sabre\Xml; + +/** + * Test for the ContextStackTrait + * + * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class ContextStackTest extends \PHPUnit_Framework_TestCase { + + function setUp() { + + $this->stack = $this->getMockForTrait('Sabre\\Xml\\ContextStackTrait'); + + } + + function testPushAndPull() { + + $this->stack->contextUri = '/foo/bar'; + $this->stack->elementMap['{DAV:}foo'] = 'Bar'; + $this->stack->namespaceMap['DAV:'] = 'd'; + + $this->stack->pushContext(); + + $this->assertEquals('/foo/bar', $this->stack->contextUri); + $this->assertEquals('Bar', $this->stack->elementMap['{DAV:}foo']); + $this->assertEquals('d', $this->stack->namespaceMap['DAV:']); + + $this->stack->contextUri = '/gir/zim'; + $this->stack->elementMap['{DAV:}foo'] = 'newBar'; + $this->stack->namespaceMap['DAV:'] = 'dd'; + + $this->stack->popContext(); + + $this->assertEquals('/foo/bar', $this->stack->contextUri); + $this->assertEquals('Bar', $this->stack->elementMap['{DAV:}foo']); + $this->assertEquals('d', $this->stack->namespaceMap['DAV:']); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Deserializer/EnumTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Deserializer/EnumTest.php new file mode 100644 index 0000000..2eea9bb --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Deserializer/EnumTest.php @@ -0,0 +1,62 @@ +<?php + +namespace Sabre\Xml\Deserializer; + +use Sabre\Xml\Service; + +class EnumTest extends \PHPUnit_Framework_TestCase { + + function testDeserialize() { + + $service = new Service(); + $service->elementMap['{urn:test}root'] = 'Sabre\Xml\Deserializer\enum'; + + $xml = <<<XML +<?xml version="1.0"?> +<root xmlns="urn:test"> + <foo1/> + <foo2/> +</root> +XML; + + $result = $service->parse($xml); + + $expected = [ + '{urn:test}foo1', + '{urn:test}foo2', + ]; + + + $this->assertEquals($expected, $result); + + + } + + function testDeserializeDefaultNamespace() { + + $service = new Service(); + $service->elementMap['{urn:test}root'] = function($reader) { + return enum($reader, 'urn:test'); + }; + + $xml = <<<XML +<?xml version="1.0"?> +<root xmlns="urn:test"> + <foo1/> + <foo2/> +</root> +XML; + + $result = $service->parse($xml); + + $expected = [ + 'foo1', + 'foo2', + ]; + + + $this->assertEquals($expected, $result); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Deserializer/KeyValueTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Deserializer/KeyValueTest.php new file mode 100755 index 0000000..e0b074e --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Deserializer/KeyValueTest.php @@ -0,0 +1,63 @@ +<?php + +namespace Sabre\Xml\Deserializer; + +use + Sabre\Xml\Reader; + +class KeyValueTest extends \PHPUnit_Framework_TestCase { + + function testKeyValue() { + + $input = <<<BLA +<?xml version="1.0"?> +<root xmlns="http://sabredav.org/ns"> + <struct> + <elem1 /> + <elem2>hi</elem2> + <elem3 xmlns="http://sabredav.org/another-ns"> + <elem4>foo</elem4> + <elem5>foo & bar</elem5> + </elem3> + </struct> +</root> +BLA; + + $reader = new Reader(); + $reader->elementMap = [ + '{http://sabredav.org/ns}struct' => function(Reader $reader) { + return keyValue($reader, 'http://sabredav.org/ns'); + } + ]; + $reader->xml($input); + $output = $reader->parse(); + + $this->assertEquals([ + 'name' => '{http://sabredav.org/ns}root', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}struct', + 'value' => [ + 'elem1' => null, + 'elem2' => 'hi', + '{http://sabredav.org/another-ns}elem3' => [ + [ + 'name' => '{http://sabredav.org/another-ns}elem4', + 'value' => 'foo', + 'attributes' => [], + ], + [ + 'name' => '{http://sabredav.org/another-ns}elem5', + 'value' => 'foo & bar', + 'attributes' => [], + ], + ] + ], + 'attributes' => [], + ] + ], + 'attributes' => [], + ], $output); + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Deserializer/RepeatingElementsTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Deserializer/RepeatingElementsTest.php new file mode 100644 index 0000000..025d997 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Deserializer/RepeatingElementsTest.php @@ -0,0 +1,35 @@ +<?php + +namespace Sabre\Xml\Deserializer; + +use Sabre\Xml\Service; + +class RepeatingElementsTest extends \PHPUnit_Framework_TestCase { + + function testRead() { + + $service = new Service(); + $service->elementMap['{urn:test}collection'] = function($reader) { + return repeatingElements($reader, '{urn:test}item'); + }; + + $xml = <<<XML +<?xml version="1.0"?> +<collection xmlns="urn:test"> + <item>foo</item> + <item>bar</item> +</collection> +XML; + + $result = $service->parse($xml); + + $expected = [ + 'foo', + 'bar', + ]; + + $this->assertEquals($expected, $result); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Deserializer/ValueObjectTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Deserializer/ValueObjectTest.php new file mode 100644 index 0000000..2d6ce98 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Deserializer/ValueObjectTest.php @@ -0,0 +1,169 @@ +<?php + +namespace Sabre\XML\Deserializer; + +use + Sabre\Xml\Reader; + +class ValueObjectTest extends \PHPUnit_Framework_TestCase { + + function testDeserializeValueObject() { + + $input = <<<XML +<?xml version="1.0"?> +<foo xmlns="urn:foo"> + <firstName>Harry</firstName> + <lastName>Turtle</lastName> +</foo> +XML; + + $reader = new Reader(); + $reader->xml($input); + $reader->elementMap = [ + '{urn:foo}foo' => function(Reader $reader) { + return valueObject($reader, 'Sabre\\Xml\\Deserializer\\TestVo', 'urn:foo'); + } + ]; + + $output = $reader->parse(); + + $vo = new TestVo(); + $vo->firstName = 'Harry'; + $vo->lastName = 'Turtle'; + + $expected = [ + 'name' => '{urn:foo}foo', + 'value' => $vo, + 'attributes' => [] + ]; + + $this->assertEquals( + $expected, + $output + ); + + } + + function testDeserializeValueObjectIgnoredElement() { + + $input = <<<XML +<?xml version="1.0"?> +<foo xmlns="urn:foo"> + <firstName>Harry</firstName> + <lastName>Turtle</lastName> + <email>harry@example.org</email> +</foo> +XML; + + $reader = new Reader(); + $reader->xml($input); + $reader->elementMap = [ + '{urn:foo}foo' => function(Reader $reader) { + return valueObject($reader, 'Sabre\\Xml\\Deserializer\\TestVo', 'urn:foo'); + } + ]; + + $output = $reader->parse(); + + $vo = new TestVo(); + $vo->firstName = 'Harry'; + $vo->lastName = 'Turtle'; + + $expected = [ + 'name' => '{urn:foo}foo', + 'value' => $vo, + 'attributes' => [] + ]; + + $this->assertEquals( + $expected, + $output + ); + + } + + function testDeserializeValueObjectAutoArray() { + + $input = <<<XML +<?xml version="1.0"?> +<foo xmlns="urn:foo"> + <firstName>Harry</firstName> + <lastName>Turtle</lastName> + <link>http://example.org/</link> + <link>http://example.net/</link> +</foo> +XML; + + $reader = new Reader(); + $reader->xml($input); + $reader->elementMap = [ + '{urn:foo}foo' => function(Reader $reader) { + return valueObject($reader, 'Sabre\\Xml\\Deserializer\\TestVo', 'urn:foo'); + } + ]; + + $output = $reader->parse(); + + $vo = new TestVo(); + $vo->firstName = 'Harry'; + $vo->lastName = 'Turtle'; + $vo->link = [ + 'http://example.org/', + 'http://example.net/', + ]; + + + $expected = [ + 'name' => '{urn:foo}foo', + 'value' => $vo, + 'attributes' => [] + ]; + + $this->assertEquals( + $expected, + $output + ); + + } + function testDeserializeValueObjectEmpty() { + + $input = <<<XML +<?xml version="1.0"?> +<foo xmlns="urn:foo" /> +XML; + + $reader = new Reader(); + $reader->xml($input); + $reader->elementMap = [ + '{urn:foo}foo' => function(Reader $reader) { + return valueObject($reader, 'Sabre\\Xml\\Deserializer\\TestVo', 'urn:foo'); + } + ]; + + $output = $reader->parse(); + + $vo = new TestVo(); + + $expected = [ + 'name' => '{urn:foo}foo', + 'value' => $vo, + 'attributes' => [] + ]; + + $this->assertEquals( + $expected, + $output + ); + + } + +} + +class TestVo { + + public $firstName; + public $lastName; + + public $link = []; + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Element/CDataTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Element/CDataTest.php new file mode 100644 index 0000000..2d12d7b --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Element/CDataTest.php @@ -0,0 +1,58 @@ +<?php + +namespace Sabre\Xml\Element; + +use Sabre\Xml\Reader; +use Sabre\Xml\Writer; + +class CDataTest extends \PHPUnit_Framework_TestCase { + + /** + * @expectedException \LogicException + */ + function testDeserialize() { + + $input = <<<BLA +<?xml version="1.0"?> +<root xmlns="http://sabredav.org/ns"> + <blabla /> +</root> +BLA; + + $reader = new Reader(); + $reader->elementMap = [ + '{http://sabredav.org/ns}blabla' => 'Sabre\\Xml\\Element\\Cdata', + ]; + $reader->xml($input); + + $output = $reader->parse(); + + } + + function testSerialize() { + + $writer = new Writer(); + $writer->namespaceMap = [ + 'http://sabredav.org/ns' => null + ]; + $writer->openMemory(); + $writer->startDocument('1.0'); + $writer->setIndent(true); + $writer->write([ + '{http://sabredav.org/ns}root' => new Cdata('<foo&bar>'), + ]); + + $output = $writer->outputMemory(); + + $expected = <<<XML +<?xml version="1.0"?> +<root xmlns="http://sabredav.org/ns"><![CDATA[<foo&bar>]]></root> + +XML; + + $this->assertEquals($expected, $output); + + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Element/Eater.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Element/Eater.php new file mode 100644 index 0000000..aaba2a0 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Element/Eater.php @@ -0,0 +1,78 @@ +<?php + +namespace Sabre\Xml\Element; + +use Sabre\Xml; + +/** + * The intention for this reader class, is to read past the end element. This + * should trigger a ParseException + * + * @copyright Copyright (C) 2009-2015 fruux GmbH (https://fruux.com/). + * @author Evert Pot (http://evertpot.com/) + * @license http://sabre.io/license/ Modified BSD License + */ +class Eater implements Xml\Element { + + /** + * 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 Xml\Writer $writer + * @return void + */ + function xmlSerialize(Xml\Writer $writer) { + + $writer->startElement('{http://sabredav.org/ns}elem1'); + $writer->write('hiiii!'); + $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. + * + * 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->parseSubTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Xml\Reader $reader + * @return mixed + */ + static function xmlDeserialize(Xml\Reader $reader) { + + $reader->next(); + + $count = 1; + while ($count) { + + $reader->read(); + if ($reader->nodeType === $reader::END_ELEMENT) { + $count--; + } + + } + $reader->read(); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Element/ElementsTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Element/ElementsTest.php new file mode 100644 index 0000000..f17f209 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Element/ElementsTest.php @@ -0,0 +1,129 @@ +<?php + +namespace Sabre\Xml\Element; + +use Sabre\Xml\Reader; +use Sabre\Xml\Writer; + +class ElementsTest extends \PHPUnit_Framework_TestCase { + + function testDeserialize() { + + $input = <<<BLA +<?xml version="1.0"?> +<root xmlns="http://sabredav.org/ns"> + <listThingy> + <elem1 /> + <elem2 /> + <elem3 /> + <elem4 attr="val" /> + <elem5>content</elem5> + <elem6><subnode /></elem6> + </listThingy> + <listThingy /> + <otherThing> + <elem1 /> + <elem2 /> + <elem3 /> + </otherThing> +</root> +BLA; + + $reader = new Reader(); + $reader->elementMap = [ + '{http://sabredav.org/ns}listThingy' => 'Sabre\\Xml\\Element\\Elements', + ]; + $reader->xml($input); + + $output = $reader->parse(); + + $this->assertEquals([ + 'name' => '{http://sabredav.org/ns}root', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}listThingy', + 'value' => [ + '{http://sabredav.org/ns}elem1', + '{http://sabredav.org/ns}elem2', + '{http://sabredav.org/ns}elem3', + '{http://sabredav.org/ns}elem4', + '{http://sabredav.org/ns}elem5', + '{http://sabredav.org/ns}elem6', + ], + 'attributes' => [], + ], + [ + 'name' => '{http://sabredav.org/ns}listThingy', + 'value' => [], + 'attributes' => [], + ], + [ + 'name' => '{http://sabredav.org/ns}otherThing', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}elem1', + 'value' => null, + 'attributes' => [], + ], + [ + 'name' => '{http://sabredav.org/ns}elem2', + 'value' => null, + 'attributes' => [], + ], + [ + 'name' => '{http://sabredav.org/ns}elem3', + 'value' => null, + 'attributes' => [], + ], + ], + 'attributes' => [], + ], + ], + 'attributes' => [], + ], $output); + + } + + function testSerialize() { + + $value = [ + '{http://sabredav.org/ns}elem1', + '{http://sabredav.org/ns}elem2', + '{http://sabredav.org/ns}elem3', + '{http://sabredav.org/ns}elem4', + '{http://sabredav.org/ns}elem5', + '{http://sabredav.org/ns}elem6', + ]; + + $writer = new Writer(); + $writer->namespaceMap = [ + 'http://sabredav.org/ns' => null + ]; + $writer->openMemory(); + $writer->startDocument('1.0'); + $writer->setIndent(true); + $writer->write([ + '{http://sabredav.org/ns}root' => new Elements($value), + ]); + + $output = $writer->outputMemory(); + + $expected = <<<XML +<?xml version="1.0"?> +<root xmlns="http://sabredav.org/ns"> + <elem1/> + <elem2/> + <elem3/> + <elem4/> + <elem5/> + <elem6/> +</root> + +XML; + + $this->assertEquals($expected, $output); + + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Element/KeyValueTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Element/KeyValueTest.php new file mode 100644 index 0000000..51c87b5 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Element/KeyValueTest.php @@ -0,0 +1,210 @@ +<?php + +namespace Sabre\Xml\Element; + +use Sabre\Xml\Reader; +use Sabre\Xml\Writer; + +class KeyValueTest extends \PHPUnit_Framework_TestCase { + + function testDeserialize() { + + $input = <<<BLA +<?xml version="1.0"?> +<root xmlns="http://sabredav.org/ns"> + <struct> + <elem1 /> + <elem2>hi</elem2> + <elem3> + <elem4>foo</elem4> + <elem5>foo & bar</elem5> + </elem3> + <elem6>Hi<!-- ignore me -->there</elem6> + </struct> + <struct /> + <otherThing> + <elem1 /> + </otherThing> +</root> +BLA; + + $reader = new Reader(); + $reader->elementMap = [ + '{http://sabredav.org/ns}struct' => 'Sabre\\Xml\\Element\\KeyValue', + ]; + $reader->xml($input); + + $output = $reader->parse(); + + $this->assertEquals([ + 'name' => '{http://sabredav.org/ns}root', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}struct', + 'value' => [ + '{http://sabredav.org/ns}elem1' => null, + '{http://sabredav.org/ns}elem2' => 'hi', + '{http://sabredav.org/ns}elem3' => [ + [ + 'name' => '{http://sabredav.org/ns}elem4', + 'value' => 'foo', + 'attributes' => [], + ], + [ + 'name' => '{http://sabredav.org/ns}elem5', + 'value' => 'foo & bar', + 'attributes' => [], + ], + ], + '{http://sabredav.org/ns}elem6' => 'Hithere', + ], + 'attributes' => [], + ], + [ + 'name' => '{http://sabredav.org/ns}struct', + 'value' => [], + 'attributes' => [], + ], + [ + 'name' => '{http://sabredav.org/ns}otherThing', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}elem1', + 'value' => null, + 'attributes' => [], + ], + ], + 'attributes' => [], + ], + ], + 'attributes' => [], + ], $output); + + } + + /** + * This test was added to find out why an element gets eaten by the + * SabreDAV MKCOL parser. + */ + function testElementEater() { + + $input = <<<BLA +<?xml version="1.0"?> +<mkcol xmlns="DAV:"> + <set> + <prop> + <resourcetype><collection /></resourcetype> + <displayname>bla</displayname> + </prop> + </set> +</mkcol> +BLA; + + $reader = new Reader(); + $reader->elementMap = [ + '{DAV:}set' => 'Sabre\\Xml\\Element\\KeyValue', + '{DAV:}prop' => 'Sabre\\Xml\\Element\\KeyValue', + '{DAV:}resourcetype' => 'Sabre\\Xml\\Element\\Elements', + ]; + $reader->xml($input); + + $expected = [ + 'name' => '{DAV:}mkcol', + 'value' => [ + [ + 'name' => '{DAV:}set', + 'value' => [ + '{DAV:}prop' => [ + '{DAV:}resourcetype' => [ + '{DAV:}collection', + ], + '{DAV:}displayname' => 'bla', + ], + ], + 'attributes' => [], + ], + ], + 'attributes' => [], + ]; + + $this->assertEquals($expected, $reader->parse()); + + } + + + function testSerialize() { + + $value = [ + '{http://sabredav.org/ns}elem1' => null, + '{http://sabredav.org/ns}elem2' => 'textValue', + '{http://sabredav.org/ns}elem3' => [ + '{http://sabredav.org/ns}elem4' => 'text2', + '{http://sabredav.org/ns}elem5' => null, + ], + '{http://sabredav.org/ns}elem6' => 'text3', + ]; + + $writer = new Writer(); + $writer->namespaceMap = [ + 'http://sabredav.org/ns' => null + ]; + $writer->openMemory(); + $writer->startDocument('1.0'); + $writer->setIndent(true); + $writer->write([ + '{http://sabredav.org/ns}root' => new KeyValue($value), + ]); + + $output = $writer->outputMemory(); + + $expected = <<<XML +<?xml version="1.0"?> +<root xmlns="http://sabredav.org/ns"> + <elem1/> + <elem2>textValue</elem2> + <elem3> + <elem4>text2</elem4> + <elem5/> + </elem3> + <elem6>text3</elem6> +</root> + +XML; + + $this->assertEquals($expected, $output); + + } + + /** + * I discovered that when there's no whitespace between elements, elements + * can get skipped. + */ + function testElementSkipProblem() { + + $input = <<<BLA +<?xml version="1.0" encoding="utf-8"?> +<root xmlns="http://sabredav.org/ns"> +<elem3>val3</elem3><elem4>val4</elem4><elem5>val5</elem5></root> +BLA; + + $reader = new Reader(); + $reader->elementMap = [ + '{http://sabredav.org/ns}root' => 'Sabre\\Xml\\Element\\KeyValue', + ]; + $reader->xml($input); + + $output = $reader->parse(); + + $this->assertEquals([ + 'name' => '{http://sabredav.org/ns}root', + 'value' => [ + '{http://sabredav.org/ns}elem3' => 'val3', + '{http://sabredav.org/ns}elem4' => 'val4', + '{http://sabredav.org/ns}elem5' => 'val5', + ], + 'attributes' => [], + ], $output); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Element/Mock.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Element/Mock.php new file mode 100644 index 0000000..f96684c --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Element/Mock.php @@ -0,0 +1,60 @@ +<?php + +namespace Sabre\Xml\Element; + +use Sabre\Xml; + +class Mock implements Xml\Element { + + /** + * 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 Xml\Writer $writer + * @return void + */ + function xmlSerialize(Xml\Writer $writer) { + + $writer->startElement('{http://sabredav.org/ns}elem1'); + $writer->write('hiiii!'); + $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. + * + * 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->parseSubTree() will parse the entire sub-tree, and advance to + * the next element. + * + * @param Xml\Reader $reader + * @return mixed + */ + static function xmlDeserialize(Xml\Reader $reader) { + + $reader->next(); + return 'foobar'; + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Element/UriTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Element/UriTest.php new file mode 100644 index 0000000..53f89ed --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Element/UriTest.php @@ -0,0 +1,76 @@ +<?php + +namespace Sabre\Xml\Element; + +use Sabre\Xml\Reader; +use Sabre\Xml\Writer; + +class UriTest extends \PHPUnit_Framework_TestCase { + + function testDeserialize() { + + $input = <<<BLA +<?xml version="1.0"?> +<root xmlns="http://sabredav.org/ns"> + <uri>/foo/bar</uri> +</root> +BLA; + + $reader = new Reader(); + $reader->contextUri = 'http://example.org/'; + $reader->elementMap = [ + '{http://sabredav.org/ns}uri' => 'Sabre\\Xml\\Element\\Uri', + ]; + $reader->xml($input); + + $output = $reader->parse(); + + $this->assertEquals( + [ + 'name' => '{http://sabredav.org/ns}root', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}uri', + 'value' => new Uri('http://example.org/foo/bar'), + 'attributes' => [], + ] + ], + 'attributes' => [], + ], + $output + ); + + } + + function testSerialize() { + + $writer = new Writer(); + $writer->namespaceMap = [ + 'http://sabredav.org/ns' => null + ]; + $writer->openMemory(); + $writer->startDocument('1.0'); + $writer->setIndent(true); + $writer->contextUri = 'http://example.org/'; + $writer->write([ + '{http://sabredav.org/ns}root' => [ + '{http://sabredav.org/ns}uri' => new Uri('/foo/bar'), + ] + ]); + + $output = $writer->outputMemory(); + + $expected = <<<XML +<?xml version="1.0"?> +<root xmlns="http://sabredav.org/ns"> + <uri>http://example.org/foo/bar</uri> +</root> + +XML; + + $this->assertEquals($expected, $output); + + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Element/XmlFragmentTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Element/XmlFragmentTest.php new file mode 100644 index 0000000..461cc15 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Element/XmlFragmentTest.php @@ -0,0 +1,143 @@ +<?php + +namespace Sabre\Xml\Element; + +use Sabre\Xml\Reader; +use Sabre\Xml\Writer; + +class XmlFragmentTest extends \PHPUnit_Framework_TestCase { + + /** + * @dataProvider xmlProvider + */ + function testDeserialize($input, $expected) { + + $input = <<<BLA +<?xml version="1.0"?> +<root xmlns="http://sabredav.org/ns"> + <fragment>$input</fragment> +</root> +BLA; + + $reader = new Reader(); + $reader->elementMap = [ + '{http://sabredav.org/ns}fragment' => 'Sabre\\Xml\\Element\\XmlFragment', + ]; + $reader->xml($input); + + $output = $reader->parse(); + + $this->assertEquals([ + 'name' => '{http://sabredav.org/ns}root', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}fragment', + 'value' => new XmlFragment($expected), + 'attributes' => [], + ], + ], + 'attributes' => [], + ], $output); + + } + + /** + * Data provider for serialize and deserialize tests. + * + * Returns three items per test: + * + * 1. Input data for the reader. + * 2. Expected output for XmlFragment deserializer + * 3. Expected output after serializing that value again. + * + * If 3 is not set, use 1 for 3. + * + * @return void + */ + function xmlProvider() { + + return [ + [ + 'hello', + 'hello', + ], + [ + '<element>hello</element>', + '<element xmlns="http://sabredav.org/ns">hello</element>' + ], + [ + '<element foo="bar">hello</element>', + '<element xmlns="http://sabredav.org/ns" foo="bar">hello</element>' + ], + [ + '<element x1:foo="bar" xmlns:x1="http://example.org/ns">hello</element>', + '<element xmlns:x1="http://example.org/ns" xmlns="http://sabredav.org/ns" x1:foo="bar">hello</element>' + ], + [ + '<element xmlns="http://example.org/ns">hello</element>', + '<element xmlns="http://example.org/ns">hello</element>', + '<x1:element xmlns:x1="http://example.org/ns">hello</x1:element>', + ], + [ + '<element xmlns:foo="http://example.org/ns">hello</element>', + '<element xmlns:foo="http://example.org/ns" xmlns="http://sabredav.org/ns">hello</element>', + '<element>hello</element>', + ], + [ + '<foo:element xmlns:foo="http://example.org/ns">hello</foo:element>', + '<foo:element xmlns:foo="http://example.org/ns">hello</foo:element>', + '<x1:element xmlns:x1="http://example.org/ns">hello</x1:element>', + ], + [ + '<foo:element xmlns:foo="http://example.org/ns"><child>hello</child></foo:element>', + '<foo:element xmlns:foo="http://example.org/ns" xmlns="http://sabredav.org/ns"><child>hello</child></foo:element>', + '<x1:element xmlns:x1="http://example.org/ns"><child>hello</child></x1:element>', + ], + [ + '<foo:element xmlns:foo="http://example.org/ns"><child/></foo:element>', + '<foo:element xmlns:foo="http://example.org/ns" xmlns="http://sabredav.org/ns"><child/></foo:element>', + '<x1:element xmlns:x1="http://example.org/ns"><child/></x1:element>', + ], + [ + '<foo:element xmlns:foo="http://example.org/ns"><child a="b"/></foo:element>', + '<foo:element xmlns:foo="http://example.org/ns" xmlns="http://sabredav.org/ns"><child a="b"/></foo:element>', + '<x1:element xmlns:x1="http://example.org/ns"><child a="b"/></x1:element>', + ], + ]; + + } + + /** + * @dataProvider xmlProvider + */ + function testSerialize($expectedFallback, $input, $expected = null) { + + if (is_null($expected)) { + $expected = $expectedFallback; + } + + $writer = new Writer(); + $writer->namespaceMap = [ + 'http://sabredav.org/ns' => null + ]; + $writer->openMemory(); + $writer->startDocument('1.0'); + //$writer->setIndent(true); + $writer->write([ + '{http://sabredav.org/ns}root' => [ + '{http://sabredav.org/ns}fragment' => new XmlFragment($input), + ], + ]); + + $output = $writer->outputMemory(); + + $expected = <<<XML +<?xml version="1.0"?> +<root xmlns="http://sabredav.org/ns"><fragment>$expected</fragment></root> +XML; + + $this->assertEquals($expected, $output); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/InfiteLoopTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/InfiteLoopTest.php new file mode 100644 index 0000000..ec8a136 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/InfiteLoopTest.php @@ -0,0 +1,50 @@ +<?php + +namespace Sabre\Xml; + +class InfiteLoopTest extends \PHPUnit_Framework_TestCase { + + /** + * This particular xml body caused the parser to go into an infinite loop. + * Need to know why. + */ + function testDeserialize() { + + $body = '<?xml version="1.0"?> +<d:propertyupdate xmlns:d="DAV:" xmlns:s="http://sabredav.org/NS/test"> + <d:set><d:prop></d:prop></d:set> + <d:set><d:prop></d:prop></d:set> +</d:propertyupdate>'; + + $reader = new Reader(); + $reader->elementMap = [ + '{DAV:}set' => 'Sabre\\Xml\\Element\\KeyValue', + ]; + $reader->xml($body); + + $output = $reader->parse(); + + $this->assertEquals([ + 'name' => '{DAV:}propertyupdate', + 'value' => [ + [ + 'name' => '{DAV:}set', + 'value' => [ + '{DAV:}prop' => null, + ], + 'attributes' => [], + ], + [ + 'name' => '{DAV:}set', + 'value' => [ + '{DAV:}prop' => null, + ], + 'attributes' => [], + ], + ], + 'attributes' => [], + ], $output); + + } + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/ReaderTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/ReaderTest.php new file mode 100644 index 0000000..ec3e01e --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/ReaderTest.php @@ -0,0 +1,545 @@ +<?php + +namespace Sabre\Xml; + +class ReaderTest extends \PHPUnit_Framework_TestCase { + + function testGetClark() { + + $input = <<<BLA +<?xml version="1.0"?> +<root xmlns="http://sabredav.org/ns" /> +BLA; + $reader = new Reader(); + $reader->xml($input); + + $reader->next(); + + $this->assertEquals('{http://sabredav.org/ns}root', $reader->getClark()); + + } + + function testGetClarkNoNS() { + + $input = <<<BLA +<?xml version="1.0"?> +<root /> +BLA; + $reader = new Reader(); + $reader->xml($input); + + $reader->next(); + + $this->assertEquals('{}root', $reader->getClark()); + + } + + function testGetClarkNotOnAnElement() { + + $input = <<<BLA +<?xml version="1.0"?> +<root /> +BLA; + $reader = new Reader(); + $reader->xml($input); + + $this->assertNull($reader->getClark()); + } + + function testSimple() { + + $input = <<<BLA +<?xml version="1.0"?> +<root xmlns="http://sabredav.org/ns"> + <elem1 attr="val" /> + <elem2> + <elem3>Hi!</elem3> + </elem2> +</root> +BLA; + + $reader = new Reader(); + $reader->xml($input); + + $output = $reader->parse(); + + $expected = [ + 'name' => '{http://sabredav.org/ns}root', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}elem1', + 'value' => null, + 'attributes' => [ + 'attr' => 'val', + ], + ], + [ + 'name' => '{http://sabredav.org/ns}elem2', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}elem3', + 'value' => 'Hi!', + 'attributes' => [], + ], + ], + 'attributes' => [], + ], + + ], + 'attributes' => [], + + ]; + + $this->assertEquals($expected, $output); + + } + + function testCDATA() { + + $input = <<<BLA +<?xml version="1.0"?> +<root xmlns="http://sabredav.org/ns"> + <foo><![CDATA[bar]]></foo> +</root> +BLA; + + $reader = new Reader(); + $reader->xml($input); + + $output = $reader->parse(); + + $expected = [ + 'name' => '{http://sabredav.org/ns}root', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}foo', + 'value' => 'bar', + 'attributes' => [], + ], + + ], + 'attributes' => [], + + ]; + + $this->assertEquals($expected, $output); + + } + + function testSimpleNamespacedAttribute() { + + $input = <<<BLA +<?xml version="1.0"?> +<root xmlns="http://sabredav.org/ns" xmlns:foo="urn:foo"> + <elem1 foo:attr="val" /> +</root> +BLA; + + $reader = new Reader(); + $reader->xml($input); + + $output = $reader->parse(); + + $expected = [ + 'name' => '{http://sabredav.org/ns}root', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}elem1', + 'value' => null, + 'attributes' => [ + '{urn:foo}attr' => 'val', + ], + ], + ], + 'attributes' => [], + ]; + + $this->assertEquals($expected, $output); + + } + + function testMappedElement() { + + $input = <<<BLA +<?xml version="1.0"?> +<root xmlns="http://sabredav.org/ns"> + <elem1 /> +</root> +BLA; + + $reader = new Reader(); + $reader->elementMap = [ + '{http://sabredav.org/ns}elem1' => 'Sabre\\Xml\\Element\\Mock' + ]; + $reader->xml($input); + + $output = $reader->parse(); + + $expected = [ + 'name' => '{http://sabredav.org/ns}root', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}elem1', + 'value' => 'foobar', + 'attributes' => [], + ], + ], + 'attributes' => [], + + ]; + + $this->assertEquals($expected, $output); + + } + + /** + * @expectedException \LogicException + */ + function testMappedElementBadClass() { + + $input = <<<BLA +<?xml version="1.0"?> +<root xmlns="http://sabredav.org/ns"> + <elem1 /> +</root> +BLA; + + $reader = new Reader(); + $reader->elementMap = [ + '{http://sabredav.org/ns}elem1' => new \StdClass() + ]; + $reader->xml($input); + + $reader->parse(); + } + + /** + * @depends testMappedElement + */ + function testMappedElementCallBack() { + + $input = <<<BLA +<?xml version="1.0"?> +<root xmlns="http://sabredav.org/ns"> + <elem1 /> +</root> +BLA; + + $reader = new Reader(); + $reader->elementMap = [ + '{http://sabredav.org/ns}elem1' => function(Reader $reader) { + $reader->next(); + return 'foobar'; + } + ]; + $reader->xml($input); + + $output = $reader->parse(); + + $expected = [ + 'name' => '{http://sabredav.org/ns}root', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}elem1', + 'value' => 'foobar', + 'attributes' => [], + ], + ], + 'attributes' => [], + + ]; + + $this->assertEquals($expected, $output); + + } + + /** + * @depends testMappedElementCallBack + */ + function testReadText() { + + $input = <<<BLA +<?xml version="1.0"?> +<root xmlns="http://sabredav.org/ns"> + <elem1> + <elem2>hello </elem2> + <elem2>world</elem2> + </elem1> +</root> +BLA; + + $reader = new Reader(); + $reader->elementMap = [ + '{http://sabredav.org/ns}elem1' => function(Reader $reader) { + return $reader->readText(); + } + ]; + $reader->xml($input); + + $output = $reader->parse(); + + $expected = [ + 'name' => '{http://sabredav.org/ns}root', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}elem1', + 'value' => 'hello world', + 'attributes' => [], + ], + ], + 'attributes' => [], + + ]; + + $this->assertEquals($expected, $output); + + } + + function testParseProblem() { + + $input = <<<BLA +<?xml version="1.0"?> +<root xmlns="http://sabredav.org/ns"> +BLA; + + $reader = new Reader(); + $reader->elementMap = [ + '{http://sabredav.org/ns}elem1' => 'Sabre\\Xml\\Element\\Mock' + ]; + $reader->xml($input); + + try { + $output = $reader->parse(); + $this->fail('We expected a ParseException to be thrown'); + } catch (LibXMLException $e) { + + $this->assertInternalType('array', $e->getErrors()); + + } + + } + + /** + * @expectedException \Sabre\Xml\ParseException + */ + function testBrokenParserClass() { + + $input = <<<BLA +<?xml version="1.0"?> +<root xmlns="http://sabredav.org/ns"> +<elem1 /> +</root> +BLA; + + $reader = new Reader(); + $reader->elementMap = [ + '{http://sabredav.org/ns}elem1' => 'Sabre\\Xml\\Element\\Eater' + ]; + $reader->xml($input); + $reader->parse(); + + + } + + /** + * Test was added for Issue #10. + * + * @expectedException Sabre\Xml\LibXMLException + */ + function testBrokenXml() { + + $input = <<<BLA +<test> +<hello> +</hello> +</sffsdf> +BLA; + + $reader = new Reader(); + $reader->xml($input); + $reader->parse(); + + } + + /** + * Test was added for Issue #45. + * + * @expectedException Sabre\Xml\LibXMLException + */ + function testBrokenXml2() { + + $input = <<<XML +<?xml version="1.0" encoding="UTF-8"?> +<definitions> + <collaboration> + <participant id="sid-A33D08EB-A2DE-448F-86FE-A2B62E98818" name="Company" processRef="sid-A0A6A196-3C9A-4C69-88F6-7ED7DDFDD264"> + <extensionElements> + <signavio:signavioMetaData metaKey="bgcolor" /> + ""Administrative w"> + <extensionElements> + <signavio:signavioMetaData metaKey="bgcolor" metaValue=""/> + </extensionElements> + </lan +XML; + $reader = new Reader(); + $reader->xml($input); + $reader->parse(); + + } + + + /** + * @depends testMappedElement + */ + function testParseInnerTree() { + + $input = <<<BLA +<?xml version="1.0"?> +<root xmlns="http://sabredav.org/ns"> + <elem1> + <elem1 /> + </elem1> +</root> +BLA; + + $reader = new Reader(); + $reader->elementMap = [ + '{http://sabredav.org/ns}elem1' => function(Reader $reader) { + + $innerTree = $reader->parseInnerTree(['{http://sabredav.org/ns}elem1' => function(Reader $reader) { + $reader->next(); + return "foobar"; + }]); + + return $innerTree; + } + ]; + $reader->xml($input); + + $output = $reader->parse(); + + $expected = [ + 'name' => '{http://sabredav.org/ns}root', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}elem1', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}elem1', + 'value' => 'foobar', + 'attributes' => [], + ] + ], + 'attributes' => [], + ], + ], + 'attributes' => [], + + ]; + + $this->assertEquals($expected, $output); + + } + + /** + * @depends testParseInnerTree + */ + function testParseGetElements() { + + $input = <<<BLA +<?xml version="1.0"?> +<root xmlns="http://sabredav.org/ns"> + <elem1> + <elem1 /> + </elem1> +</root> +BLA; + + $reader = new Reader(); + $reader->elementMap = [ + '{http://sabredav.org/ns}elem1' => function(Reader $reader) { + + $innerTree = $reader->parseGetElements(['{http://sabredav.org/ns}elem1' => function(Reader $reader) { + $reader->next(); + return "foobar"; + }]); + + return $innerTree; + } + ]; + $reader->xml($input); + + $output = $reader->parse(); + + $expected = [ + 'name' => '{http://sabredav.org/ns}root', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}elem1', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}elem1', + 'value' => 'foobar', + 'attributes' => [], + ] + ], + 'attributes' => [], + ], + ], + 'attributes' => [], + + ]; + + $this->assertEquals($expected, $output); + + } + + /** + * @depends testParseInnerTree + */ + function testParseGetElementsNoElements() { + + $input = <<<BLA +<?xml version="1.0"?> +<root xmlns="http://sabredav.org/ns"> + <elem1> + hi + </elem1> +</root> +BLA; + + $reader = new Reader(); + $reader->elementMap = [ + '{http://sabredav.org/ns}elem1' => function(Reader $reader) { + + $innerTree = $reader->parseGetElements(['{http://sabredav.org/ns}elem1' => function(Reader $reader) { + $reader->next(); + return "foobar"; + }]); + + return $innerTree; + } + ]; + $reader->xml($input); + + $output = $reader->parse(); + + $expected = [ + 'name' => '{http://sabredav.org/ns}root', + 'value' => [ + [ + 'name' => '{http://sabredav.org/ns}elem1', + 'value' => [], + 'attributes' => [], + ], + ], + 'attributes' => [], + + ]; + + $this->assertEquals($expected, $output); + + } + + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Serializer/EnumTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Serializer/EnumTest.php new file mode 100644 index 0000000..2d26e66 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Serializer/EnumTest.php @@ -0,0 +1,36 @@ +<?php + +namespace Sabre\Xml\Serializer; + +use Sabre\Xml\Service; + +class EnumTest extends \PHPUnit_Framework_TestCase { + + function testSerialize() { + + $service = new Service(); + $service->namespaceMap['urn:test'] = null; + + $xml = $service->write('{urn:test}root', function($writer) { + enum($writer, [ + '{urn:test}foo1', + '{urn:test}foo2', + ]); + }); + + $expected = <<<XML +<?xml version="1.0"?> +<root xmlns="urn:test"> + <foo1/> + <foo2/> +</root> +XML; + + + $this->assertXmlStringEqualsXmlString($expected, $xml); + + + } + + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Serializer/RepeatingElementsTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Serializer/RepeatingElementsTest.php new file mode 100644 index 0000000..dbca65a --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/Serializer/RepeatingElementsTest.php @@ -0,0 +1,35 @@ +<?php + +namespace Sabre\Xml\Serializer; + +use Sabre\Xml\Service; + +class RepeatingElementsTest extends \PHPUnit_Framework_TestCase { + + function testSerialize() { + + $service = new Service(); + $service->namespaceMap['urn:test'] = null; + $xml = $service->write('{urn:test}collection', function($writer) { + repeatingElements($writer, [ + 'foo', + 'bar', + ], '{urn:test}item'); + }); + + $expected = <<<XML +<?xml version="1.0"?> +<collection xmlns="urn:test"> + <item>foo</item> + <item>bar</item> +</collection> +XML; + + + $this->assertXmlStringEqualsXmlString($expected, $xml); + + + } + + +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/ServiceTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/ServiceTest.php new file mode 100644 index 0000000..e6fcf14 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/ServiceTest.php @@ -0,0 +1,328 @@ +<?php + +namespace Sabre\Xml; + +class ServiceTest extends \PHPUnit_Framework_TestCase { + + function testGetReader() { + + $elems = [ + '{http://sabre.io/ns}test' => 'Test!', + ]; + + $util = new Service(); + $util->elementMap = $elems; + + $reader = $util->getReader(); + $this->assertInstanceOf('Sabre\\Xml\\Reader', $reader); + $this->assertEquals($elems, $reader->elementMap); + + } + + function testGetWriter() { + + $ns = [ + 'http://sabre.io/ns' => 's', + ]; + + $util = new Service(); + $util->namespaceMap = $ns; + + $writer = $util->getWriter(); + $this->assertInstanceOf('Sabre\\Xml\\Writer', $writer); + $this->assertEquals($ns, $writer->namespaceMap); + + } + + /** + * @depends testGetReader + */ + function testParse() { + + $xml = <<<XML +<root xmlns="http://sabre.io/ns"> + <child>value</child> +</root> +XML; + $util = new Service(); + $result = $util->parse($xml, null, $rootElement); + $this->assertEquals('{http://sabre.io/ns}root', $rootElement); + + $expected = [ + [ + 'name' => '{http://sabre.io/ns}child', + 'value' => 'value', + 'attributes' => [], + ] + ]; + + $this->assertEquals( + $expected, + $result + ); + + } + + /** + * @depends testGetReader + */ + function testParseStream() { + + $xml = <<<XML +<root xmlns="http://sabre.io/ns"> + <child>value</child> +</root> +XML; + $stream = fopen('php://memory', 'r+'); + fwrite($stream, $xml); + rewind($stream); + + $util = new Service(); + $result = $util->parse($stream, null, $rootElement); + $this->assertEquals('{http://sabre.io/ns}root', $rootElement); + + $expected = [ + [ + 'name' => '{http://sabre.io/ns}child', + 'value' => 'value', + 'attributes' => [], + ] + ]; + + $this->assertEquals( + $expected, + $result + ); + + } + + /** + * @depends testGetReader + */ + function testExpect() { + + $xml = <<<XML +<root xmlns="http://sabre.io/ns"> + <child>value</child> +</root> +XML; + $util = new Service(); + $result = $util->expect('{http://sabre.io/ns}root', $xml); + + $expected = [ + [ + 'name' => '{http://sabre.io/ns}child', + 'value' => 'value', + 'attributes' => [], + ] + ]; + + $this->assertEquals( + $expected, + $result + ); + } + + /** + * @depends testGetReader + */ + function testExpectStream() { + + $xml = <<<XML +<root xmlns="http://sabre.io/ns"> + <child>value</child> +</root> +XML; + + $stream = fopen('php://memory', 'r+'); + fwrite($stream, $xml); + rewind($stream); + + $util = new Service(); + $result = $util->expect('{http://sabre.io/ns}root', $stream); + + $expected = [ + [ + 'name' => '{http://sabre.io/ns}child', + 'value' => 'value', + 'attributes' => [], + ] + ]; + + $this->assertEquals( + $expected, + $result + ); + } + + /** + * @depends testGetReader + * @expectedException \Sabre\Xml\ParseException + */ + function testExpectWrong() { + + $xml = <<<XML +<root xmlns="http://sabre.io/ns"> + <child>value</child> +</root> +XML; + $util = new Service(); + $util->expect('{http://sabre.io/ns}error', $xml); + + } + + /** + * @depends testGetWriter + */ + function testWrite() { + + $util = new Service(); + $util->namespaceMap = [ + 'http://sabre.io/ns' => 's', + ]; + $result = $util->write('{http://sabre.io/ns}root', [ + '{http://sabre.io/ns}child' => 'value', + ]); + + $expected = <<<XML +<?xml version="1.0"?> +<s:root xmlns:s="http://sabre.io/ns"> + <s:child>value</s:child> +</s:root> + +XML; + $this->assertEquals( + $expected, + $result + ); + + } + + function testMapValueObject() { + + $input = <<<XML +<?xml version="1.0"?> +<order xmlns="http://sabredav.org/ns"> + <id>1234</id> + <amount>99.99</amount> + <description>black friday deal</description> + <status> + <id>5</id> + <label>processed</label> + </status> +</order> + +XML; + + $ns = 'http://sabredav.org/ns'; + $orderService = new \Sabre\Xml\Service(); + $orderService->mapValueObject('{' . $ns . '}order', 'Sabre\Xml\Order'); + $orderService->mapValueObject('{' . $ns . '}status', 'Sabre\Xml\OrderStatus'); + $orderService->namespaceMap[$ns] = null; + + $order = $orderService->parse($input); + $expected = new Order(); + $expected->id = 1234; + $expected->amount = 99.99; + $expected->description = 'black friday deal'; + $expected->status = new OrderStatus(); + $expected->status->id = 5; + $expected->status->label = 'processed'; + + $this->assertEquals($expected, $order); + + $writtenXml = $orderService->writeValueObject($order); + $this->assertEquals($input, $writtenXml); + } + + function testMapValueObjectArrayProperty() { + + $input = <<<XML +<?xml version="1.0"?> +<order xmlns="http://sabredav.org/ns"> + <id>1234</id> + <amount>99.99</amount> + <description>black friday deal</description> + <status> + <id>5</id> + <label>processed</label> + </status> + <link>http://example.org/</link> + <link>http://example.com/</link> +</order> + +XML; + + $ns = 'http://sabredav.org/ns'; + $orderService = new \Sabre\Xml\Service(); + $orderService->mapValueObject('{' . $ns . '}order', 'Sabre\Xml\Order'); + $orderService->mapValueObject('{' . $ns . '}status', 'Sabre\Xml\OrderStatus'); + $orderService->namespaceMap[$ns] = null; + + $order = $orderService->parse($input); + $expected = new Order(); + $expected->id = 1234; + $expected->amount = 99.99; + $expected->description = 'black friday deal'; + $expected->status = new OrderStatus(); + $expected->status->id = 5; + $expected->status->label = 'processed'; + $expected->link = ['http://example.org/', 'http://example.com/']; + + $this->assertEquals($expected, $order); + + $writtenXml = $orderService->writeValueObject($order); + $this->assertEquals($input, $writtenXml); + } + + /** + * @expectedException \InvalidArgumentException + */ + function testWriteVoNotFound() { + + $service = new Service(); + $service->writeValueObject(new \StdClass()); + + } + + function testParseClarkNotation() { + + $this->assertEquals([ + 'http://sabredav.org/ns', + 'elem', + ], Service::parseClarkNotation('{http://sabredav.org/ns}elem')); + + } + + /** + * @expectedException \InvalidArgumentException + */ + function testParseClarkNotationFail() { + + Service::parseClarkNotation('http://sabredav.org/ns}elem'); + + } + +} + +/** + * asset for testMapValueObject() + * @internal + */ +class Order { + public $id; + public $amount; + public $description; + public $status; + public $empty; + public $link = []; +} + +/** + * asset for testMapValueObject() + * @internal + */ +class OrderStatus { + public $id; + public $label; +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/WriterTest.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/WriterTest.php new file mode 100644 index 0000000..4fb5f68 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/Sabre/Xml/WriterTest.php @@ -0,0 +1,439 @@ +<?php + +namespace Sabre\Xml; + +class WriterTest extends \PHPUnit_Framework_TestCase { + + protected $writer; + + function setUp() { + + $this->writer = new Writer(); + $this->writer->namespaceMap = [ + 'http://sabredav.org/ns' => 's', + ]; + $this->writer->openMemory(); + $this->writer->setIndent(true); + $this->writer->startDocument(); + + } + + function compare($input, $output) { + + $this->writer->write($input); + $this->assertEquals($output, $this->writer->outputMemory()); + + } + + + function testSimple() { + + $this->compare([ + '{http://sabredav.org/ns}root' => 'text', + ], <<<HI +<?xml version="1.0"?> +<s:root xmlns:s="http://sabredav.org/ns">text</s:root> + +HI + ); + + } + + /** + * @depends testSimple + */ + function testSimpleQuotes() { + + $this->compare([ + '{http://sabredav.org/ns}root' => '"text"', + ], <<<HI +<?xml version="1.0"?> +<s:root xmlns:s="http://sabredav.org/ns">"text"</s:root> + +HI + ); + + } + + function testSimpleAttributes() { + + $this->compare([ + '{http://sabredav.org/ns}root' => [ + 'value' => 'text', + 'attributes' => [ + 'attr1' => 'attribute value', + ], + ], + ], <<<HI +<?xml version="1.0"?> +<s:root xmlns:s="http://sabredav.org/ns" attr1="attribute value">text</s:root> + +HI + ); + + } + function testMixedSyntax() { + $this->compare([ + '{http://sabredav.org/ns}root' => [ + '{http://sabredav.org/ns}single' => 'value', + '{http://sabredav.org/ns}multiple' => [ + [ + 'name' => '{http://sabredav.org/ns}foo', + 'value' => 'bar', + ], + [ + 'name' => '{http://sabredav.org/ns}foo', + 'value' => 'foobar', + ], + ], + [ + 'name' => '{http://sabredav.org/ns}attributes', + 'value' => null, + 'attributes' => [ + 'foo' => 'bar', + ], + ], + [ + 'name' => '{http://sabredav.org/ns}verbose', + 'value' => 'syntax', + 'attributes' => [ + 'foo' => 'bar', + ], + ], + ], + ], <<<HI +<?xml version="1.0"?> +<s:root xmlns:s="http://sabredav.org/ns"> + <s:single>value</s:single> + <s:multiple> + <s:foo>bar</s:foo> + <s:foo>foobar</s:foo> + </s:multiple> + <s:attributes foo="bar"/> + <s:verbose foo="bar">syntax</s:verbose> +</s:root> + +HI + ); + } + + function testNull() { + + $this->compare([ + '{http://sabredav.org/ns}root' => null, + ], <<<HI +<?xml version="1.0"?> +<s:root xmlns:s="http://sabredav.org/ns"/> + +HI + ); + + } + + function testArrayFormat2() { + + $this->compare([ + '{http://sabredav.org/ns}root' => [ + [ + 'name' => '{http://sabredav.org/ns}elem1', + 'value' => 'text', + 'attributes' => [ + 'attr1' => 'attribute value', + ], + ], + ], + ], <<<HI +<?xml version="1.0"?> +<s:root xmlns:s="http://sabredav.org/ns"> + <s:elem1 attr1="attribute value">text</s:elem1> +</s:root> + +HI + ); + + } + + function testArrayOfValues() { + + $this->compare([ + '{http://sabredav.org/ns}root' => [ + [ + 'name' => '{http://sabredav.org/ns}elem1', + 'value' => [ + 'foo', + 'bar', + 'baz', + ], + ], + ], + ], <<<HI +<?xml version="1.0"?> +<s:root xmlns:s="http://sabredav.org/ns"> + <s:elem1>foobarbaz</s:elem1> +</s:root> + +HI + ); + + } + + /** + * @depends testArrayFormat2 + */ + function testArrayFormat2NoValue() { + + $this->compare([ + '{http://sabredav.org/ns}root' => [ + [ + 'name' => '{http://sabredav.org/ns}elem1', + 'attributes' => [ + 'attr1' => 'attribute value', + ], + ], + ], + ], <<<HI +<?xml version="1.0"?> +<s:root xmlns:s="http://sabredav.org/ns"> + <s:elem1 attr1="attribute value"/> +</s:root> + +HI + ); + + } + + function testCustomNamespace() { + + $this->compare([ + '{http://sabredav.org/ns}root' => [ + '{urn:foo}elem1' => 'bar', + ], + ], <<<HI +<?xml version="1.0"?> +<s:root xmlns:s="http://sabredav.org/ns"> + <x1:elem1 xmlns:x1="urn:foo">bar</x1:elem1> +</s:root> + +HI + ); + + } + + function testEmptyNamespace() { + + // Empty namespaces are allowed, so we should support this. + $this->compare([ + '{http://sabredav.org/ns}root' => [ + '{}elem1' => 'bar', + ], + ], <<<HI +<?xml version="1.0"?> +<s:root xmlns:s="http://sabredav.org/ns"> + <elem1 xmlns="">bar</elem1> +</s:root> + +HI + ); + + } + + function testAttributes() { + + $this->compare([ + '{http://sabredav.org/ns}root' => [ + [ + 'name' => '{http://sabredav.org/ns}elem1', + 'value' => 'text', + 'attributes' => [ + 'attr1' => 'val1', + '{http://sabredav.org/ns}attr2' => 'val2', + '{urn:foo}attr3' => 'val3', + ], + ], + ], + ], <<<HI +<?xml version="1.0"?> +<s:root xmlns:s="http://sabredav.org/ns"> + <s:elem1 attr1="val1" s:attr2="val2" x1:attr3="val3" xmlns:x1="urn:foo">text</s:elem1> +</s:root> + +HI + ); + + } + + function testBaseElement() { + + $this->compare([ + '{http://sabredav.org/ns}root' => new Element\Base('hello') + ], <<<HI +<?xml version="1.0"?> +<s:root xmlns:s="http://sabredav.org/ns">hello</s:root> + +HI + ); + + } + + function testElementObj() { + + $this->compare([ + '{http://sabredav.org/ns}root' => new Element\Mock() + ], <<<HI +<?xml version="1.0"?> +<s:root xmlns:s="http://sabredav.org/ns"> + <s:elem1>hiiii!</s:elem1> +</s:root> + +HI + ); + + } + + function testEmptyNamespacePrefix() { + + $this->writer->namespaceMap['http://sabredav.org/ns'] = null; + $this->compare([ + '{http://sabredav.org/ns}root' => new Element\Mock() + ], <<<HI +<?xml version="1.0"?> +<root xmlns="http://sabredav.org/ns"> + <elem1>hiiii!</elem1> +</root> + +HI + ); + + } + + function testEmptyNamespacePrefixEmptyString() { + + $this->writer->namespaceMap['http://sabredav.org/ns'] = ''; + $this->compare([ + '{http://sabredav.org/ns}root' => new Element\Mock() + ], <<<HI +<?xml version="1.0"?> +<root xmlns="http://sabredav.org/ns"> + <elem1>hiiii!</elem1> +</root> + +HI + ); + + } + + function testWriteElement() { + + $this->writer->writeElement("{http://sabredav.org/ns}foo", 'content'); + + $output = <<<HI +<?xml version="1.0"?> +<s:foo xmlns:s="http://sabredav.org/ns">content</s:foo> + +HI; + + $this->assertEquals($output, $this->writer->outputMemory()); + + + } + + function testWriteElementComplex() { + + $this->writer->writeElement("{http://sabredav.org/ns}foo", new Element\KeyValue(['{http://sabredav.org/ns}bar' => 'test'])); + + $output = <<<HI +<?xml version="1.0"?> +<s:foo xmlns:s="http://sabredav.org/ns"> + <s:bar>test</s:bar> +</s:foo> + +HI; + + $this->assertEquals($output, $this->writer->outputMemory()); + + } + + /** + * @expectedException \InvalidArgumentException + */ + function testWriteBadObject() { + + $this->writer->write(new \StdClass()); + + } + + function testStartElementSimple() { + + $this->writer->startElement("foo"); + $this->writer->endElement(); + + $output = <<<HI +<?xml version="1.0"?> +<foo xmlns:s="http://sabredav.org/ns"/> + +HI; + + $this->assertEquals($output, $this->writer->outputMemory()); + + } + + function testCallback() { + + $this->compare([ + '{http://sabredav.org/ns}root' => function(Writer $writer) { + $writer->text('deferred writer'); + }, + ], <<<HI +<?xml version="1.0"?> +<s:root xmlns:s="http://sabredav.org/ns">deferred writer</s:root> + +HI + ); + + } + + /** + * @expectedException \InvalidArgumentException + */ + function testResource() { + + $this->compare([ + '{http://sabredav.org/ns}root' => fopen('php://memory', 'r'), + ], <<<HI +<?xml version="1.0"?> +<s:root xmlns:s="http://sabredav.org/ns">deferred writer</s:root> + +HI + ); + + } + + function testClassMap() { + + $obj = (object)[ + 'key1' => 'value1', + 'key2' => 'value2', + ]; + + $this->writer->classMap['stdClass'] = function(Writer $writer, $value) { + + foreach (get_object_vars($value) as $key => $val) { + $writer->writeElement('{http://sabredav.org/ns}' . $key, $val); + } + + }; + + $this->compare([ + '{http://sabredav.org/ns}root' => $obj + ], <<<HI +<?xml version="1.0"?> +<s:root xmlns:s="http://sabredav.org/ns"> + <s:key1>value1</s:key1> + <s:key2>value2</s:key2> +</s:root> + +HI + ); + + } +} diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/bootstrap.php b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/bootstrap.php new file mode 100644 index 0000000..c979834 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/bootstrap.php @@ -0,0 +1,17 @@ +<?php + +$try = [ + __DIR__ . '/../vendor/autoload.php', + __DIR__ . '/../../../autoload.php', +]; + +foreach ($try as $path) { + if (file_exists($path)) { + include $path; + break; + } +} + +// Some extra classes +include __DIR__ . '/Sabre/Xml/Element/Mock.php'; +include __DIR__ . '/Sabre/Xml/Element/Eater.php'; diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/phpcs/ruleset.xml b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/phpcs/ruleset.xml new file mode 100644 index 0000000..07acb89 --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/phpcs/ruleset.xml @@ -0,0 +1,56 @@ +<?xml version="1.0"?> +<ruleset name="sabre.php"> + <description>sabre.io codesniffer ruleset</description> + + <!-- Include the whole PSR-1 standard --> + <rule ref="PSR1" /> + + <!-- All PHP files MUST use the Unix LF (linefeed) line ending. --> + <rule ref="Generic.Files.LineEndings"> + <properties> + <property name="eolChar" value="\n"/> + </properties> + </rule> + + <!-- The closing ?> tag MUST be omitted from files containing only PHP. --> + <rule ref="Zend.Files.ClosingTag"/> + + <!-- There MUST NOT be trailing whitespace at the end of non-blank lines. --> + <rule ref="Squiz.WhiteSpace.SuperfluousWhitespace"> + <properties> + <property name="ignoreBlankLines" value="true"/> + </properties> + </rule> + + <!-- There MUST NOT be more than one statement per line. --> + <rule ref="Generic.Formatting.DisallowMultipleStatements"/> + + <rule ref="Generic.WhiteSpace.ScopeIndent"> + <properties> + <property name="ignoreIndentationTokens" type="array" value="T_COMMENT,T_DOC_COMMENT"/> + </properties> + </rule> + <rule ref="Generic.WhiteSpace.DisallowTabIndent"/> + + <!-- PHP keywords MUST be in lower case. --> + <rule ref="Generic.PHP.LowerCaseKeyword"/> + + <!-- The PHP constants true, false, and null MUST be in lower case. --> + <rule ref="Generic.PHP.LowerCaseConstant"/> + + <rule ref="Squiz.WhiteSpace.ScopeKeywordSpacing"/> + + <!-- In the argument list, there MUST NOT be a space before each comma, and there MUST be one space after each comma. --> + <!-- + <rule ref="Squiz.Functions.FunctionDeclarationArgumentSpacing"> + <properties> + <property name="equalsSpacing" value="1"/> + </properties> + </rule> + <rule ref="Squiz.Functions.FunctionDeclarationArgumentSpacing.SpacingAfterHint"> + <severity>0</severity> + </rule> + --> + <rule ref="PEAR.WhiteSpace.ScopeClosingBrace"/> + +</ruleset> diff --git a/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/phpunit.xml.dist b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/phpunit.xml.dist new file mode 100644 index 0000000..da3d10a --- /dev/null +++ b/src/serverlib/3rdparty/SabreDAV/vendor/sabre/xml/tests/phpunit.xml.dist @@ -0,0 +1,17 @@ +<phpunit + colors="true" + bootstrap="bootstrap.php" + convertErrorsToExceptions="true" + convertNoticesToExceptions="true" + convertWarningsToExceptions="true" + > + <testsuite name="Sabre_XML"> + <directory>Sabre/</directory> + </testsuite> + + <filter> + <whitelist addUncoveredFilesFromWhitelist="true"> + <directory suffix=".php">../lib/</directory> + </whitelist> + </filter> +</phpunit> diff --git a/src/serverlib/3rdparty/sabre/CalDAV/Backend/AbstractBackend.php b/src/serverlib/3rdparty/sabre/CalDAV/Backend/AbstractBackend.php deleted file mode 100644 index 0b29720..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/Backend/AbstractBackend.php +++ /dev/null @@ -1,221 +0,0 @@ -<?php - -namespace Sabre\CalDAV\Backend; - -use Sabre\VObject; -use Sabre\CalDAV; - -/** - * Abstract Calendaring backend. Extend this class to create your own backends. - * - * Checkout the BackendInterface for all the methods that must be implemented. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -abstract class AbstractBackend implements BackendInterface { - - /** - * 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 $path - * @param \Sabre\DAV\PropPatch $propPatch - * @return void - */ - function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch) { - - } - - /** - * 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) { - - return array_map(function($uri) use ($calendarId) { - return $this->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 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']); - } - - $vObject = VObject\Reader::read($object['calendardata']); - - $validator = new CalDAV\CalendarQueryValidator(); - return $validator->validate($vObject, $filters); - - } - - /** - * 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/src/serverlib/3rdparty/sabre/CalDAV/Backend/BackendInterface.php b/src/serverlib/3rdparty/sabre/CalDAV/Backend/BackendInterface.php deleted file mode 100644 index ef7b9a3..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/Backend/BackendInterface.php +++ /dev/null @@ -1,268 +0,0 @@ -<?php - -namespace Sabre\CalDAV\Backend; - -/** - * Every CalDAV backend must at least implement this interface. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -interface BackendInterface { - - /** - * 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'. - * - * 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\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); - - /** - * 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 void - */ - function createCalendar($principalUri,$calendarUri,array $properties); - - /** - * 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 $path - * @param \Sabre\DAV\PropPatch $propPatch - * @return void - */ - function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch); - - /** - * Delete a calendar and all it's objects - * - * @param mixed $calendarId - * @return void - */ - function deleteCalendar($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 mixed $calendarId - * @return array - */ - function getCalendarObjects($calendarId); - - /** - * 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 mixed $calendarId - * @param string $objectUri - * @return array|null - */ - function getCalendarObject($calendarId,$objectUri); - - /** - * 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); - - /** - * 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); - - /** - * 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); - - /** - * Deletes an existing calendar object. - * - * The object uri is only the basename, or filename and not a full path. - * - * @param mixed $calendarId - * @param string $objectUri - * @return void - */ - 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 - */ - function calendarQuery($calendarId, array $filters); - - /** - * 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); - -} diff --git a/src/serverlib/3rdparty/sabre/CalDAV/Backend/NotificationSupport.php b/src/serverlib/3rdparty/sabre/CalDAV/Backend/NotificationSupport.php deleted file mode 100644 index 931a780..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/Backend/NotificationSupport.php +++ /dev/null @@ -1,47 +0,0 @@ -<?php - -namespace Sabre\CalDAV\Backend; - -/** - * Adds caldav notification support to a backend. - * - * Note: This feature is experimental, and may change in between different - * SabreDAV versions. - * - * Notifications are defined at: - * http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-notifications.txt - * - * These notifications are basically a list of server-generated notifications - * displayed to the user. Users can dismiss notifications by deleting them. - * - * The primary usecase is to allow for calendar-sharing. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -interface NotificationSupport extends BackendInterface { - - /** - * Returns a list of notifications for a given principal url. - * - * The returned array should only consist of implementations of - * \Sabre\CalDAV\Notifications\INotificationType. - * - * @param string $principalUri - * @return array - */ - function getNotificationsForPrincipal($principalUri); - - /** - * This deletes a specific notifcation. - * - * This may be called by a client once it deems a notification handled. - * - * @param string $principalUri - * @param \Sabre\CalDAV\Notifications\INotificationType $notification - * @return void - */ - function deleteNotification($principalUri, \Sabre\CalDAV\Notifications\INotificationType $notification); - -} diff --git a/src/serverlib/3rdparty/sabre/CalDAV/Backend/PDO.php b/src/serverlib/3rdparty/sabre/CalDAV/Backend/PDO.php deleted file mode 100644 index 81292c8..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/Backend/PDO.php +++ /dev/null @@ -1,1220 +0,0 @@ -<?php - -namespace Sabre\CalDAV\Backend; - -use - Sabre\VObject, - Sabre\CalDAV, - Sabre\DAV, - Sabre\DAV\Exception\Forbidden; - -/** - * PDO CalDAV backend - * - * This backend is used to store calendar-data in a PDO database, such as - * sqlite or MySQL - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class PDO extends AbstractBackend implements SyncSupport, SubscriptionSupport, SchedulingSupport { - - /** - * We need to specify a max date, because we need to stop *somewhere* - * - * On 32 bit system the maximum for a signed integer is 2147483647, so - * MAX_DATE cannot be higher than date('Y-m-d', 2147483647) which results - * in 2038-01-19 to avoid problems when the date is converted - * to a unix timestamp. - */ - const MAX_DATE = '2038-01-01'; - - /** - * pdo - * - * @var \PDO - */ - protected $pdo; - - /** - * The table name that will be used for calendars - * - * @var string - */ - public $calendarTableName = 'calendars'; - - /** - * The table name that will be used for calendar objects - * - * @var string - */ - public $calendarObjectTableName = 'calendarobjects'; - - /** - * The table name that will be used for tracking changes in calendars. - * - * @var string - */ - public $calendarChangesTableName = 'calendarchanges'; - - /** - * The table name that will be used inbox items. - * - * @var string - */ - public $schedulingObjectTableName = 'schedulingobjects'; - - /** - * The table name that will be used for calendar subscriptions. - * - * @var string - */ - public $calendarSubscriptionsTableName = 'calendarsubscriptions'; - - /** - * List of CalDAV properties, and how they map to database fieldnames - * Add your own properties by simply adding on to this array. - * - * Note that only string-based properties are supported here. - * - * @var array - */ - public $propertyMap = [ - '{DAV:}displayname' => '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 - * @param string $calendarTableName - * @param string $calendarObjectTableName - * @param string $calendarChangesTable - * @param string $schedulingObjectTable - * @param string $calendarSubscriptionsTableName - * @deprecated We are going to remove all the 'tableName' arguments and - * move to public properties for those. Stop relying on them! - */ - function __construct(\PDO $pdo, $calendarTableName = 'calendars', $calendarObjectTableName = 'calendarobjects', $calendarChangesTableName = 'calendarchanges', $calendarSubscriptionsTableName = "calendarsubscriptions", $schedulingObjectTableName = "schedulingobjects") { - - $this->pdo = $pdo; - $this->calendarTableName = $calendarTableName; - $this->calendarObjectTableName = $calendarObjectTableName; - $this->calendarChangesTableName = $calendarChangesTableName; - $this->schedulingObjectTableName = $schedulingObjectTableName; - $this->calendarSubscriptionsTableName = $calendarSubscriptionsTableName; - - } - - /** - * 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\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\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 - */ - 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\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 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->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 [ - '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 = <<<SQL -SELECT - calendars.uri AS calendaruri, calendarobjects.uri as objecturi -FROM - $this->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\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/src/serverlib/3rdparty/sabre/CalDAV/Backend/SchedulingSupport.php b/src/serverlib/3rdparty/sabre/CalDAV/Backend/SchedulingSupport.php deleted file mode 100644 index ba3c369..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/Backend/SchedulingSupport.php +++ /dev/null @@ -1,65 +0,0 @@ -<?php - -namespace Sabre\CalDAV\Backend; - -/** - * Implementing this interface adds CalDAV Scheduling support to your caldav - * server, as defined in rfc6638. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License - */ -interface SchedulingSupport extends BackendInterface { - - /** - * Returns a single scheduling object for the inbox collection. - * - * 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); - - /** - * 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); - - /** - * Deletes a scheduling object from the inbox collection. - * - * @param string $principalUri - * @param string $objectUri - * @return void - */ - function deleteSchedulingObject($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); - -} diff --git a/src/serverlib/3rdparty/sabre/CalDAV/Calendar.php b/src/serverlib/3rdparty/sabre/CalDAV/Calendar.php deleted file mode 100644 index aa64db5..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/Calendar.php +++ /dev/null @@ -1,527 +0,0 @@ -<?php - -namespace Sabre\CalDAV; - -use - Sabre\DAV, - Sabre\DAVACL, - Sabre\DAV\PropPatch; - -/** - * This object represents a CalDAV calendar. - * - * A calendar can contain multiple TODO and or Events. These are represented - * as \Sabre\CalDAV\CalendarObject objects. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class Calendar implements ICalendar, DAV\IProperties, DAV\Sync\ISyncCollection, DAV\IMultiGet { - - /** - * This is an array with calendar information - * - * @var array - */ - protected $calendarInfo; - - /** - * CalDAV backend - * - * @var Backend\BackendInterface - */ - protected $caldavBackend; - - /** - * Constructor - * - * @param Backend\BackendInterface $caldavBackend - * @param array $calendarInfo - */ - function __construct(Backend\BackendInterface $caldavBackend, $calendarInfo) { - - $this->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($requestedProperties as $prop) { - - if (isset($this->calendarInfo[$prop])) - $response[$prop] = $this->calendarInfo[$prop]; - - } - 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. - * - * @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/src/serverlib/3rdparty/sabre/CalDAV/CalendarHome.php b/src/serverlib/3rdparty/sabre/CalDAV/CalendarHome.php deleted file mode 100644 index 6c3cd2b..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/CalendarHome.php +++ /dev/null @@ -1,426 +0,0 @@ -<?php - -namespace Sabre\CalDAV; - -use - Sabre\DAV, - Sabre\DAV\Exception\NotFound, - Sabre\DAVACL, - Sabre\HTTP\URLUtil; - -/** - * The CalendarHome represents a node that is usually in a users' - * calendar-homeset. - * - * It contains all the users' calendars, and can optionally contain a - * notifications collection, calendar subscriptions, a users' inbox, and a - * users' outbox. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class CalendarHome implements DAV\IExtendedCollection, DAVACL\IACL { - - /** - * CalDAV backend - * - * @var Sabre\CalDAV\Backend\BackendInterface - */ - protected $caldavBackend; - - /** - * Principal information - * - * @var array - */ - protected $principalInfo; - - /** - * Constructor - * - * @param Backend\BackendInterface $caldavBackend - * @param mixed $userUri - */ - function __construct(Backend\BackendInterface $caldavBackend, $principalInfo) { - - $this->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 - * - * @param string $name - * @param array $resourceType - * @param array $properties - * @return void - */ - function createExtendedCollection($name, array $resourceType, array $properties) { - - $isCalendar = false; - $isSubscription = 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; - case '{http://calendarserver.org/ns/}subscribed' : - $isSubscription = true; - break; - default : - throw new DAV\Exception\InvalidResourceType('Unknown resourceType: ' . $rt); - } - } - 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/src/serverlib/3rdparty/sabre/CalDAV/CalendarObject.php b/src/serverlib/3rdparty/sabre/CalDAV/CalendarObject.php deleted file mode 100644 index 7156c65..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/CalendarObject.php +++ /dev/null @@ -1,291 +0,0 @@ -<?php - -namespace Sabre\CalDAV; - -/** - * The CalendarObject represents a single VEVENT or VTODO within a Calendar. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class CalendarObject extends \Sabre\DAV\File implements ICalendarObject, \Sabre\DAVACL\IACL { - - /** - * Sabre\CalDAV\Backend\BackendInterface - * - * @var Sabre\CalDAV\Backend\AbstractBackend - */ - protected $caldavBackend; - - /** - * Array with information about this CalendarObject - * - * @var array - */ - protected $objectData; - - /** - * Array with information about the containing calendar - * - * @var array - */ - protected $calendarInfo; - - /** - * Constructor - * - * The following properties may be passed within $objectData: - * - * * calendarid - This must refer to a calendarid from a caldavBackend - * * uri - A unique uri. Only the 'basename' must be passed. - * * calendardata (optional) - The iCalendar data - * * etag - (optional) The etag for this object, MUST be encloded with - * double-quotes. - * * size - (optional) The size of the data in bytes. - * * lastmodified - (optional) format as a unix timestamp. - * * acl - (optional) Use this to override the default ACL for the node. - * - * @param Backend\BackendInterface $caldavBackend - * @param array $calendarInfo - * @param array $objectData - */ - function __construct(Backend\BackendInterface $caldavBackend,array $calendarInfo,array $objectData) { - - $this->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/src/serverlib/3rdparty/sabre/CalDAV/CalendarQueryParser.php b/src/serverlib/3rdparty/sabre/CalDAV/CalendarQueryParser.php deleted file mode 100644 index 490cf04..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/CalendarQueryParser.php +++ /dev/null @@ -1,299 +0,0 @@ -<?php - -namespace Sabre\CalDAV; - -use Sabre\VObject; - -/** - * Parses the calendar-query report request body. - * - * Whoever designed this format, and the CalDAV equivalent even more so, - * has no feel for design. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class CalendarQueryParser { - - /** - * List of requested properties the client wanted - * - * @var array - */ - public $requestedProperties; - - /** - * List of property/component filters. - * - * @var array - */ - public $filters; - - /** - * This property will contain null if CALDAV:expand was not specified, - * otherwise it will contain an array with 2 elements (start, end). Each - * contain a DateTime object. - * - * If expand is specified, recurring calendar objects are to be expanded - * into their individual components, and only the components that fall - * within the specified time-range are to be returned. - * - * For more details, see rfc4791, section 9.6.5. - * - * @var null|array - */ - public $expand; - - /** - * DOM Document - * - * @var DOMDocument - */ - protected $dom; - - /** - * DOM XPath object - * - * @var DOMXPath - */ - protected $xpath; - - /** - * Creates the parser - * - * @param \DOMDocument $dom - */ - function __construct(\DOMDocument $dom) { - - $this->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 - */ - function parse() { - - $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 = []; - - for($ii=0; $ii < $compFilterNodes->length; $ii++) { - - $compFilterNode = $compFilterNodes->item($ii); - - $compFilter = []; - $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'], [ - '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 = []; - - for ($ii=0; $ii < $propFilterNodes->length; $ii++) { - - $propFilterNode = $propFilterNodes->item($ii); - $propFilter = []; - $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 = []; - - for($ii=0;$ii<$paramFilterNodes->length;$ii++) { - - $paramFilterNode = $paramFilterNodes->item($ii); - $paramFilter = []; - $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 [ - '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 [ - 'start' => $start, - 'end' => $end, - ]; - - } - - /** - * Parses the CALDAV:expand element. - * - * This method returns an array with two elements: start and end. Each are - * a PHP DateTime object. - * - * @param \DOMElement $parentNode - * @return array - */ - 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 [ - 'start' => $start, - 'end' => $end, - ]; - - } - -} diff --git a/src/serverlib/3rdparty/sabre/CalDAV/CalendarQueryValidator.php b/src/serverlib/3rdparty/sabre/CalDAV/CalendarQueryValidator.php deleted file mode 100644 index 202d38d..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/CalendarQueryValidator.php +++ /dev/null @@ -1,375 +0,0 @@ -<?php - -namespace Sabre\CalDAV; - -use Sabre\VObject; -use DateTime; - -/** - * CalendarQuery Validator - * - * This class is responsible for checking if an iCalendar object matches a set - * of filters. The main function to do this is 'validate'. - * - * This is used to determine which icalendar objects should be returned for a - * calendar-query REPORT request. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class CalendarQueryValidator { - - /** - * Verify if a list of filters applies to the calendar data object - * - * The list of filters must be formatted as parsed by \Sabre\CalDAV\CalendarQueryParser - * - * @param VObject\Component $vObject - * @param array $filters - * @return bool - */ - function validate(VObject\Component\VCalendar $vObject,array $filters) { - - // The top level object is always a component filter. - // We'll parse it manually, as it's pretty simple. - if ($vObject->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\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/src/serverlib/3rdparty/sabre/CalDAV/CalendarRoot.php b/src/serverlib/3rdparty/sabre/CalDAV/CalendarRoot.php deleted file mode 100644 index b43f705..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/CalendarRoot.php +++ /dev/null @@ -1,80 +0,0 @@ -<?php - -namespace Sabre\CalDAV; - -use Sabre\DAVACL\PrincipalBackend; - -/** - * Calendars collection - * - * This object is responsible for generating a list of calendar-homes for each - * user. - * - * This is the top-most node for the calendars tree. In most servers this class - * represents the "/calendars" path. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class CalendarRoot extends \Sabre\DAVACL\AbstractPrincipalCollection { - - /** - * CalDAV backend - * - * @var Sabre\CalDAV\Backend\BackendInterface - */ - protected $caldavBackend; - - /** - * Constructor - * - * This constructor needs both an authentication and a caldav backend. - * - * By default this class will show a list of calendar collections for - * principals in the 'principals' collection. If your main principals are - * actually located in a different path, use the $principalPrefix argument - * to override this. - * - * @param PrincipalBackend\BackendInterface $principalBackend - * @param Backend\BackendInterface $caldavBackend - * @param string $principalPrefix - */ - function __construct(PrincipalBackend\BackendInterface $principalBackend,Backend\BackendInterface $caldavBackend, $principalPrefix = 'principals') { - - parent::__construct($principalBackend, $principalPrefix); - $this->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/src/serverlib/3rdparty/sabre/CalDAV/CalendarRootNode.php b/src/serverlib/3rdparty/sabre/CalDAV/CalendarRootNode.php deleted file mode 100644 index 11b890e..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/CalendarRootNode.php +++ /dev/null @@ -1,18 +0,0 @@ -<?php - -namespace Sabre\CalDAV; - -/** - * Deprecated! This class has been renamed to CalendarRoot. - * - * This stub class will be removed in a future version of sabredav! - * - * @deprecated - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - * @deprecated This class will be removed in a future version of sabredav. - * Use CalendarRoot instead of this class. - */ -class CalendarRootNode extends CalendarRoot { -} diff --git a/src/serverlib/3rdparty/sabre/CalDAV/Exception/InvalidComponentType.php b/src/serverlib/3rdparty/sabre/CalDAV/Exception/InvalidComponentType.php deleted file mode 100644 index 0341b9c..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/Exception/InvalidComponentType.php +++ /dev/null @@ -1,35 +0,0 @@ -<?php - -namespace Sabre\CalDAV\Exception; - -use Sabre\DAV; -use Sabre\CalDAV; - -/** - * InvalidComponentType - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class InvalidComponentType extends DAV\Exception\Forbidden { - - /** - * Adds in extra information in the xml response. - * - * This method adds the {CALDAV:}supported-calendar-component as defined in rfc4791 - * - * @param DAV\Server $server - * @param \DOMElement $errorNode - * @return void - */ - function serialize(DAV\Server $server, \DOMElement $errorNode) { - - $doc = $errorNode->ownerDocument; - - $np = $doc->createElementNS(CalDAV\Plugin::NS_CALDAV,'cal:supported-calendar-component'); - $errorNode->appendChild($np); - - } - -} diff --git a/src/serverlib/3rdparty/sabre/CalDAV/ICSExportPlugin.php b/src/serverlib/3rdparty/sabre/CalDAV/ICSExportPlugin.php deleted file mode 100644 index 7fc81d3..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/ICSExportPlugin.php +++ /dev/null @@ -1,313 +0,0 @@ -<?php - -namespace Sabre\CalDAV; - -use - DateTimeZone, - Sabre\DAV, - Sabre\VObject, - Sabre\HTTP\RequestInterface, - Sabre\HTTP\ResponseInterface, - Sabre\DAV\Exception\BadRequest, - DateTime; - -/** - * ICS Exporter - * - * This plugin adds the ability to export entire calendars as .ics files. - * This is useful for clients that don't support CalDAV yet. They often do - * support ics files. - * - * To use this, point a http client to a caldav calendar, and add ?expand to - * the url. - * - * Further options that can be added to the url: - * start=123456789 - Only return events after the given unix timestamp - * end=123245679 - Only return events from before the given unix timestamp - * expand=1 - Strip timezone information and expand recurring events. - * If you'd like to expand, you _must_ also specify start - * and end. - * - * By default this plugin returns data in the text/calendar format (iCalendar - * 2.0). If you'd like to receive jCal data instead, you can use an Accept - * header: - * - * Accept: application/calendar+json - * - * Alternatively, you can also specify this in the url using - * accept=application/calendar+json, or accept=jcal for short. If the url - * parameter and Accept header is specified, the url parameter wins. - * - * Note that specifying a start or end data implies that only events will be - * returned. VTODO and VJOURNAL will be stripped. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class ICSExportPlugin extends DAV\ServerPlugin { - - /** - * Reference to Server class - * - * @var \Sabre\DAV\Server - */ - protected $server; - - /** - * Initializes the plugin and registers event handlers - * - * @param \Sabre\DAV\Server $server - * @return void - */ - function initialize(DAV\Server $server) { - - $this->server = $server; - $this->server->on('method:GET', [$this,'httpGet'], 90); - - } - - /** - * 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; - 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; - } - - $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, $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 $format - * @param array $properties - * @param ResponseInterface $response - */ - protected function generateResponse($path, $start, $end, $expand, $format, $properties, ResponseInterface $response) { - - $calDataProp = '{' . Plugin::NS_CALDAV . '}calendar-data'; - - $blobs = []; - if ($start || $end) { - - // 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' => 'VEVENT', - '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[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(); - unset($vtimezoneObj); - } else { - // Defaulting to UTC. - $calendarTimeZone = new DateTimeZone('UTC'); - } - - $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 $inputObject) { - - $nodeComp = VObject\Reader::read($inputObject); - - 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; - - } - -} diff --git a/src/serverlib/3rdparty/sabre/CalDAV/ICalendar.php b/src/serverlib/3rdparty/sabre/CalDAV/ICalendar.php deleted file mode 100644 index 9fffc83..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/ICalendar.php +++ /dev/null @@ -1,19 +0,0 @@ -<?php - -namespace Sabre\CalDAV; - -use Sabre\DAV; -use Sabre\DAVACL; - -/** - * Calendar interface - * - * Implement this interface to allow a node to be recognized as an calendar. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -interface ICalendar extends ICalendarObjectContainer, DAVACL\IACL { - -} diff --git a/src/serverlib/3rdparty/sabre/CalDAV/ICalendarObject.php b/src/serverlib/3rdparty/sabre/CalDAV/ICalendarObject.php deleted file mode 100644 index 0698640..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/ICalendarObject.php +++ /dev/null @@ -1,20 +0,0 @@ -<?php - -namespace Sabre\CalDAV; -use Sabre\DAV; - -/** - * CalendarObject interface - * - * Extend the ICalendarObject interface to allow your custom nodes to be picked up as - * CalendarObjects. - * - * Calendar objects are resources such as Events, Todo's or Journals. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -interface ICalendarObject extends DAV\IFile { - -} diff --git a/src/serverlib/3rdparty/sabre/CalDAV/ICalendarObjectContainer.php b/src/serverlib/3rdparty/sabre/CalDAV/ICalendarObjectContainer.php deleted file mode 100644 index 669d39b..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/ICalendarObjectContainer.php +++ /dev/null @@ -1,39 +0,0 @@ -<?php - -namespace Sabre\CalDAV; - -/** - * This interface represents a node that may contain calendar objects. - * - * This is the shared parent for both the Inbox collection and calendars - * resources. - * - * In most cases you will likely want to look at ICalendar instead of this - * interface. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License - */ -interface ICalendarObjectContainer extends \Sabre\DAV\ICollection { - - /** - * 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); - -} diff --git a/src/serverlib/3rdparty/sabre/CalDAV/Notifications/Collection.php b/src/serverlib/3rdparty/sabre/CalDAV/Notifications/Collection.php deleted file mode 100644 index faa587e..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/Notifications/Collection.php +++ /dev/null @@ -1,173 +0,0 @@ -<?php - -namespace Sabre\CalDAV\Notifications; - -use Sabre\DAV; -use Sabre\CalDAV; -use Sabre\DAVACL; - -/** - * This node represents a list of notifications. - * - * It provides no additional functionality, but you must implement this - * interface to allow the Notifications plugin to mark the collection - * as a notifications collection. - * - * This collection should only return Sabre\CalDAV\Notifications\INode nodes as - * its children. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class Collection extends DAV\Collection implements ICollection, DAVACL\IACL { - - /** - * The notification backend - * - * @var Sabre\CalDAV\Backend\NotificationSupport - */ - protected $caldavBackend; - - /** - * Principal uri - * - * @var string - */ - protected $principalUri; - - /** - * Constructor - * - * @param CalDAV\Backend\NotificationSupport $caldavBackend - * @param string $principalUri - */ - function __construct(CalDAV\Backend\NotificationSupport $caldavBackend, $principalUri) { - - $this->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/src/serverlib/3rdparty/sabre/CalDAV/Notifications/INotificationType.php b/src/serverlib/3rdparty/sabre/CalDAV/Notifications/INotificationType.php deleted file mode 100644 index 0f3d979..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/Notifications/INotificationType.php +++ /dev/null @@ -1,44 +0,0 @@ -<?php - -namespace Sabre\CalDAV\Notifications; -use Sabre\DAV; - -/** - * This interface reflects a single notification type. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -interface INotificationType extends DAV\PropertyInterface { - - /** - * This method serializes the entire notification, as it is used in the - * response body. - * - * @param DAV\Server $server - * @param \DOMElement $node - * @return void - */ - function serializeBody(DAV\Server $server, \DOMElement $node); - - /** - * 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(); - - /** - * Returns the ETag for this notification. - * - * The ETag must be surrounded by literal double-quotes. - * - * @return string - */ - function getETag(); - -} diff --git a/src/serverlib/3rdparty/sabre/CalDAV/Notifications/Node.php b/src/serverlib/3rdparty/sabre/CalDAV/Notifications/Node.php deleted file mode 100644 index 648ae91..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/Notifications/Node.php +++ /dev/null @@ -1,192 +0,0 @@ -<?php - -namespace Sabre\CalDAV\Notifications; - -use Sabre\DAV; -use Sabre\CalDAV; -use Sabre\DAVACL; - -/** - * This node represents a single notification. - * - * The signature is mostly identical to that of Sabre\DAV\IFile, but the get() method - * MUST return an xml document that matches the requirements of the - * 'caldav-notifications.txt' spec. - - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class Node extends DAV\File implements INode, DAVACL\IACL { - - /** - * The notification backend - * - * @var Sabre\CalDAV\Backend\NotificationSupport - */ - protected $caldavBackend; - - /** - * The actual notification - * - * @var Sabre\CalDAV\Notifications\INotificationType - */ - protected $notification; - - /** - * Owner principal of the notification - * - * @var string - */ - protected $principalUri; - - /** - * Constructor - * - * @param CalDAV\Backend\NotificationSupport $caldavBackend - * @param string $principalUri - * @param CalDAV\Notifications\INotificationType $notification - */ - function __construct(CalDAV\Backend\NotificationSupport $caldavBackend, $principalUri, INotificationType $notification) { - - $this->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/src/serverlib/3rdparty/sabre/CalDAV/Notifications/Notification/Invite.php b/src/serverlib/3rdparty/sabre/CalDAV/Notifications/Notification/Invite.php deleted file mode 100644 index a677535..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/Notifications/Notification/Invite.php +++ /dev/null @@ -1,324 +0,0 @@ -<?php - -namespace Sabre\CalDAV\Notifications\Notification; - -use Sabre\CalDAV\SharingPlugin as SharingPlugin; -use Sabre\DAV; -use Sabre\CalDAV; - -/** - * This class represents the cs:invite-notification notification element. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class Invite extends DAV\Property implements CalDAV\Notifications\INotificationType { - - /** - * A unique id for the message - * - * @var string - */ - protected $id; - - /** - * Timestamp of the notification - * - * @var DateTime - */ - protected $dtStamp; - - /** - * A url to the recipient of the notification. This can be an email - * address (mailto:), or a principal url. - * - * @var string - */ - protected $href; - - /** - * The type of message, see the SharingPlugin::STATUS_* constants. - * - * @var int - */ - protected $type; - - /** - * True if access to a calendar is read-only. - * - * @var bool - */ - protected $readOnly; - - /** - * A url to the shared calendar. - * - * @var string - */ - protected $hostUrl; - - /** - * Url to the sharer of the calendar - * - * @var string - */ - protected $organizer; - - /** - * The name of the sharer. - * - * @var string - */ - protected $commonName; - - /** - * The name of the sharer. - * - * @var string - */ - protected $firstName; - - /** - * The name of the sharer. - * - * @var string - */ - protected $lastName; - - /** - * A description of the share request - * - * @var string - */ - protected $summary; - - /** - * The Etag for the notification - * - * @var string - */ - protected $etag; - - /** - * The list of supported components - * - * @var Sabre\CalDAV\Property\SupportedCalendarComponentSet - */ - protected $supportedComponents; - - /** - * Creates the Invite notification. - * - * This constructor receives an array with the following elements: - * - * * id - A unique id - * * etag - The etag - * * dtStamp - A DateTime object with a timestamp for the notification. - * * type - The type of notification, see SharingPlugin::STATUS_* - * constants for details. - * * readOnly - This must be set to true, if this is an invite for - * read-only access to a calendar. - * * hostUrl - A url to the shared calendar. - * * organizer - Url to the sharer principal. - * * commonName - The real name of the sharer (optional). - * * firstName - The first name of the sharer (optional). - * * lastName - The last name of the sharer (optional). - * * summary - Description of the share, can be the same as the - * calendar, but may also be modified (optional). - * * supportedComponents - An instance of - * Sabre\CalDAV\Property\SupportedCalendarComponentSet. - * This allows the client to determine which components - * will be supported in the shared calendar. This is - * also optional. - * - * @param array $values All the options - */ - function __construct(array $values) { - - $required = [ - 'id', - 'etag', - 'href', - 'dtStamp', - 'type', - 'readOnly', - 'hostUrl', - 'organizer', - ]; - foreach($required as $item) { - if (!isset($values[$item])) { - throw new \InvalidArgumentException($item . ' is a required constructor option'); - } - } - - foreach($values as $key=>$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 - */ - 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 - */ - 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 - */ - 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/src/serverlib/3rdparty/sabre/CalDAV/Notifications/Notification/InviteReply.php b/src/serverlib/3rdparty/sabre/CalDAV/Notifications/Notification/InviteReply.php deleted file mode 100644 index 232021a..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/Notifications/Notification/InviteReply.php +++ /dev/null @@ -1,218 +0,0 @@ -<?php - -namespace Sabre\CalDAV\Notifications\Notification; - -use Sabre\CalDAV\SharingPlugin as SharingPlugin; -use Sabre\DAV; -use Sabre\CalDAV; - -/** - * This class represents the cs:invite-reply notification element. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class InviteReply extends DAV\Property implements CalDAV\Notifications\INotificationType { - - /** - * A unique id for the message - * - * @var string - */ - protected $id; - - /** - * Timestamp of the notification - * - * @var DateTime - */ - protected $dtStamp; - - /** - * The unique id of the notification this was a reply to. - * - * @var string - */ - protected $inReplyTo; - - /** - * A url to the recipient of the original (!) notification. - * - * @var string - */ - protected $href; - - /** - * The type of message, see the SharingPlugin::STATUS_ constants. - * - * @var int - */ - protected $type; - - /** - * A url to the shared calendar. - * - * @var string - */ - protected $hostUrl; - - /** - * A description of the share request - * - * @var string - */ - protected $summary; - - /** - * Notification Etag - * - * @var string - */ - protected $etag; - - /** - * Creates the Invite Reply Notification. - * - * This constructor receives an array with the following elements: - * - * * id - A unique id - * * etag - The etag - * * dtStamp - A DateTime object with a timestamp for the notification. - * * inReplyTo - This should refer to the 'id' of the notification - * this is a reply to. - * * type - The type of notification, see SharingPlugin::STATUS_* - * constants for details. - * * hostUrl - A url to the shared calendar. - * * summary - Description of the share, can be the same as the - * calendar, but may also be modified (optional). - */ - function __construct(array $values) { - - $required = [ - 'id', - 'etag', - 'href', - 'dtStamp', - 'inReplyTo', - 'type', - 'hostUrl', - ]; - foreach($required as $item) { - if (!isset($values[$item])) { - throw new \InvalidArgumentException($item . ' is a required constructor option'); - } - } - - foreach($values as $key=>$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 - */ - 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 - */ - 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 - */ - 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/src/serverlib/3rdparty/sabre/CalDAV/Notifications/Notification/SystemStatus.php b/src/serverlib/3rdparty/sabre/CalDAV/Notifications/Notification/SystemStatus.php deleted file mode 100644 index 5ae0c7f..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/Notifications/Notification/SystemStatus.php +++ /dev/null @@ -1,182 +0,0 @@ -<?php - -namespace Sabre\CalDAV\Notifications\Notification; - -use Sabre\DAV; -use Sabre\CalDAV; - -/** - * SystemStatus notification - * - * This notification can be used to indicate to the user that the system is - * down. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class SystemStatus extends DAV\Property implements CalDAV\Notifications\INotificationType { - - const TYPE_LOW = 1; - const TYPE_MEDIUM = 2; - const TYPE_HIGH = 3; - - /** - * A unique id - * - * @var string - */ - protected $id; - - /** - * The type of alert. This should be one of the TYPE_ constants. - * - * @var int - */ - protected $type; - - /** - * A human-readable description of the problem. - * - * @var string - */ - protected $description; - - /** - * A url to a website with more information for the user. - * - * @var string - */ - protected $href; - - /** - * Notification Etag - * - * @var string - */ - protected $etag; - - /** - * Creates the notification. - * - * Some kind of unique id should be provided. This is used to generate a - * url. - * - * @param string $id - * @param string $etag - * @param int $type - * @param string $description - * @param string $href - */ - function __construct($id, $etag, $type = self::TYPE_HIGH, $description = null, $href = null) { - - $this->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 - */ - 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 - */ - 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 - */ - 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/src/serverlib/3rdparty/sabre/CalDAV/Notifications/Plugin.php b/src/serverlib/3rdparty/sabre/CalDAV/Notifications/Plugin.php deleted file mode 100644 index 8408d4a..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/Notifications/Plugin.php +++ /dev/null @@ -1,162 +0,0 @@ -<?php - -namespace Sabre\CalDAV\Notifications; - -use Sabre\DAV; -use Sabre\DAV\PropFind; -use Sabre\DAV\INode as BaseINode; -use Sabre\DAV\ServerPlugin; -use Sabre\DAV\Server; -use Sabre\DAVACL; -use Sabre\HTTP\RequestInterface; -use Sabre\HTTP\ResponseInterface; - - -/** - * Notifications plugin - * - * This plugin implements several features required by the caldav-notification - * draft specification. - * - * Before version 2.1.0 this functionality was part of Sabre\CalDAV\Plugin but - * this has since been split up. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License - */ -class Plugin extends ServerPlugin { - - /** - * This is the namespace for the proprietary calendarserver extensions - */ - const NS_CALENDARSERVER = 'http://calendarserver.org/ns/'; - - /** - * @var Server - */ - protected $server; - - /** - * 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 'notifications'; - - } - - /** - * 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) { - - $this->server = $server; - $server->on('method:GET', [$this,'httpGet'], 90); - $server->on('propFind', [$this,'propFind']); - - $server->xmlNamespaces[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\Property\Href($notificationPath); - - }); - - } // instanceof IPrincipal - - if ($node instanceof INode) { - - $propFind->handle( - '{' . self::NS_CALENDARSERVER . '}notificationtype', - [$node, 'getNotificationType'] - ); - - } // instanceof Notifications_INode - - } - - /** - * 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; - - $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); - - $response->setHeader('Content-Type','application/xml'); - $response->setHeader('ETag',$node->getETag()); - $response->setStatus(200); - $response->setBody($dom->saveXML()); - - // Return false to break the event chain. - return false; - - } - -} diff --git a/src/serverlib/3rdparty/sabre/CalDAV/Plugin.php b/src/serverlib/3rdparty/sabre/CalDAV/Plugin.php deleted file mode 100644 index 7d0e88c..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/Plugin.php +++ /dev/null @@ -1,1054 +0,0 @@ -<?php - -namespace Sabre\CalDAV; - -use DateTimeZone; -use Sabre\DAV; -use Sabre\DAV\Exception\BadRequest; -use Sabre\DAV\Property\HrefList; -use Sabre\DAVACL; -use Sabre\VObject; -use Sabre\HTTP; -use Sabre\HTTP\URLUtil; -use Sabre\HTTP\RequestInterface; -use Sabre\HTTP\ResponseInterface; - -/** - * CalDAV plugin - * - * This plugin provides functionality added by CalDAV (RFC 4791) - * It implements new reports, and the MKCALENDAR method. - * - * @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 { - - /** - * This is the official CalDAV namespace - */ - const NS_CALDAV = 'urn:ietf:params:xml:ns:caldav'; - - /** - * This is the namespace for the proprietary calendarserver extensions - */ - const NS_CALENDARSERVER = 'http://calendarserver.org/ns/'; - - /** - * The hardcoded root for calendar objects. It is unfortunate - * that we're stuck with it, but it will have to do for now - */ - const CALENDAR_ROOT = 'calendars'; - - /** - * Reference to server object - * - * @var DAV\Server - */ - protected $server; - - /** - * The default PDO storage uses a MySQL MEDIUMBLOB for iCalendar data, - * which can hold up to 2^24 = 16777216 bytes. This is plenty. We're - * capping it to 10M here. - */ - protected $maxResourceSize = 10000000; - - /** - * 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) { - - // The MKCALENDAR is only available on unmapped uri's, whose - // parents extend IExtendedCollection - list($parent, $name) = URLUtil::splitPath($uri); - - $node = $this->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. - * - * @param string $principalUrl - * @return string - */ - function getCalendarHomeForPrincipal($principalUrl) { - - // The default is a bit naive, but it can be overwritten. - list(, $nodeName) = URLUtil::splitPath($principalUrl); - - return self::CALENDAR_ROOT . '/' . $nodeName; - - } - - /** - * 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('onBrowserPostAction', [$this,'browserPostAction']); - $server->on('beforeCreateFile', [$this,'beforeCreateFile']); - $server->on('beforeWriteContent', [$this,'beforeWriteContent']); - $server->on('afterMethod:GET', [$this,'httpAfterGET']); - - $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\\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 \DOMNode $dom - * @return bool - */ - function report($reportName,$dom) { - - switch($reportName) { - case '{' . self::NS_CALDAV . '}calendar-multiget' : - $this->server->transactionType = 'report-calendar-multiget'; - $this->calendarMultiGetReport($dom); - return false; - case '{' . self::NS_CALDAV . '}calendar-query' : - $this->server->transactionType = 'report-calendar-query'; - $this->calendarQueryReport($dom); - return false; - case '{' . self::NS_CALDAV . '}free-busy-query' : - $this->server->transactionType = 'report-free-busy-query'; - $this->freeBusyQueryReport($dom); - 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) { - - // 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 = $request->getBodyAsString(); - $path = $request->getPath(); - - $properties = []; - - 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; - } - - } - } - - // 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,$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 Property\SupportedCalendarData(); - }); - $propFind->handle($ns . 'supported-collation-set', function() { - return new 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) . '/'; - return new DAV\Property\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 HrefList($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 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); - - // 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[]) = URLUtil::splitPath($group); - } - if ($groupNode instanceof Principal\IProxyWrite) { - list($writeList[]) = URLUtil::splitPath($group); - } - - } - - $propFind->set($propRead, new HrefList($readList)); - $propFind->set($propWrite, new HrefList($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( '{' . Plugin::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 \DOMNode $dom - * @return void - */ - 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; - - } - - $needsJson = $xpath->evaluate("boolean(/cal:calendar-multiget/dav:prop/cal:calendar-data[@content-type='application/calendar+json'])"); - - $uris = []; - foreach($hrefElems as $elem) { - $uris[] = $this->server->calculateUri($elem->nodeValue); - } - - $tz = null; - - $timeZones = []; - - foreach($this->server->getPropertiesForMultiplePaths($uris, $properties) as $uri=>$objProps) { - - if (($needsJson || $expand) && isset($objProps[200]['{' . self::NS_CALDAV . '}calendar-data'])) { - $vObject = VObject\Reader::read($objProps[200]['{' . self::NS_CALDAV . '}calendar-data']); - - if ($expand) { - // We're expanding, and for that we need to figure out the - // calendar's timezone. - list($calendarPath) = URLUtil::splitPath($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->expand($start, $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(); - } - } - - $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 \DOMNode $dom - * @return void - */ - function calendarQueryReport($dom) { - - $parser = new CalendarQueryParser($dom); - $parser->parse(); - - $path = $this->server->getRequestUri(); - - // TODO: move this into CalendarQueryParser - $xpath = new \DOMXPath($dom); - $xpath->registerNameSpace('cal',Plugin::NS_CALDAV); - $xpath->registerNameSpace('dav','urn:DAV'); - $needsJson = $xpath->evaluate("boolean(/cal:calendar-query/dav:prop/cal:calendar-data[@content-type='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 ($parser->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(); - unset($vtimezoneObj); - } 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 = $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( - $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,$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'], $calendarTimeZone); - } - if ($needsJson) { - $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = json_encode($vObject->jsonSerialize()); - } elseif ($parser->expand) { - $properties[200]['{' . self::NS_CALDAV . '}calendar-data'] = $vObject->serialize(); - } - } - - $result = [$properties]; - - } - - } - - } - - if ($node instanceof ICalendarObjectContainer && $depth === 0) { - - if(strpos($this->server->httpRequest->getHeader('User-Agent'), 'MSFT-WP/') === 0) { - // Windows phone incorrectly supplied depth as 0, when it actually - // should have set depth to 1. We're implementing a workaround here - // to deal with this. - $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($parser->filters); - - $timeZones = []; - - foreach($nodePaths as $path) { - - list($properties) = - $this->server->getPropertiesForPath($this->server->getRequestUri() . '/' . $path, $parser->requestedProperties); - - if (($needsJson || $parser->expand)) { - $vObject = VObject\Reader::read($properties[200]['{' . self::NS_CALDAV . '}calendar-data']); - - if ($parser->expand) { - $vObject->expand($parser->expand['start'], $parser->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(); - } - } - $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 \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); - } - - $uri = $this->server->getRequestUri(); - if (!$start && !$end) { - throw new DAV\Exception\BadRequest('The freebusy report must have a time-range filter'); - } - - $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(); - } 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' => $start, - 'end' => $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($start, $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) = URLUtil::splitPath($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) = URLUtil::splitPath($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; - } - - } - - } - - - /** - * 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.= '<tr><td colspan="2"><form method="post" action=""> - <h3>Create new calendar</h3> - <input type="hidden" name="sabreAction" value="mkcalendar" /> - <label>Name (uri):</label> <input type="text" name="name" /><br /> - <label>Display name:</label> <input type="text" name="{DAV:}displayname" /><br /> - <input type="submit" value="create" /> - </form> - </td></tr>'; - - 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 - */ - function browserPostAction($uri, $action, array $postVars) { - - if ($action!=='mkcalendar') - return; - - $resourceType = ['{DAV:}collection','{urn:ietf:params:xml:ns:caldav}calendar']; - $properties = []; - if (isset($postVars['{DAV:}displayname'])) { - $properties['{DAV:}displayname'] = $postVars['{DAV:}displayname']; - } - $this->server->createCollection($uri . '/' . $postVars['name'],$resourceType,$properties); - 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); - - $response->setHeader('Content-Type', 'application/calendar+json'); - $response->setHeader('Content-Length', strlen($jsonBody)); - - } - -} diff --git a/src/serverlib/3rdparty/sabre/CalDAV/Principal/Collection.php b/src/serverlib/3rdparty/sabre/CalDAV/Principal/Collection.php deleted file mode 100644 index 1c4b4c9..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/Principal/Collection.php +++ /dev/null @@ -1,32 +0,0 @@ -<?php - -namespace Sabre\CalDAV\Principal; -use Sabre\DAVACL; - -/** - * Principal collection - * - * This is an alternative collection to the standard ACL principal collection. - * This collection adds support for the calendar-proxy-read and - * calendar-proxy-write sub-principals, as defined by the caldav-proxy - * specification. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class Collection extends DAVACL\AbstractPrincipalCollection { - - /** - * Returns a child object based on principal information - * - * @param array $principalInfo - * @return User - */ - function getChildForPrincipal(array $principalInfo) { - - return new User($this->principalBackend, $principalInfo); - - } - -} diff --git a/src/serverlib/3rdparty/sabre/CalDAV/Principal/ProxyRead.php b/src/serverlib/3rdparty/sabre/CalDAV/Principal/ProxyRead.php deleted file mode 100644 index e11fdf7..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/Principal/ProxyRead.php +++ /dev/null @@ -1,180 +0,0 @@ -<?php - -namespace Sabre\CalDAV\Principal; -use Sabre\DAVACL; -use Sabre\DAV; - -/** - * ProxyRead principal - * - * This class represents a principal group, hosted under the main principal. - * This is needed to implement 'Calendar delegation' support. This class is - * instantiated by User. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class ProxyRead implements IProxyRead { - - /** - * Principal information from the parent principal. - * - * @var array - */ - protected $principalInfo; - - /** - * Principal backend - * - * @var DAVACL\PrincipalBackend\BackendInterface - */ - protected $principalBackend; - - /** - * Creates the object. - * - * Note that you MUST supply the parent principal information. - * - * @param DAVACL\PrincipalBackend\BackendInterface $principalBackend - * @param array $principalInfo - */ - function __construct(DAVACL\PrincipalBackend\BackendInterface $principalBackend, array $principalInfo) { - - $this->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/src/serverlib/3rdparty/sabre/CalDAV/Principal/ProxyWrite.php b/src/serverlib/3rdparty/sabre/CalDAV/Principal/ProxyWrite.php deleted file mode 100644 index 9b832a9..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/Principal/ProxyWrite.php +++ /dev/null @@ -1,180 +0,0 @@ -<?php - -namespace Sabre\CalDAV\Principal; -use Sabre\DAVACL; -use Sabre\DAV; - -/** - * ProxyWrite principal - * - * This class represents a principal group, hosted under the main principal. - * This is needed to implement 'Calendar delegation' support. This class is - * instantiated by User. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class ProxyWrite implements IProxyWrite { - - /** - * Parent principal information - * - * @var array - */ - protected $principalInfo; - - /** - * Principal Backend - * - * @var DAVACL\PrincipalBackend\BackendInterface - */ - protected $principalBackend; - - /** - * Creates the object - * - * Note that you MUST supply the parent principal information. - * - * @param DAVACL\PrincipalBackend\BackendInterface $principalBackend - * @param array $principalInfo - */ - function __construct(DAVACL\PrincipalBackend\BackendInterface $principalBackend, array $principalInfo) { - - $this->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 array(); - - } - - /** - * 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/src/serverlib/3rdparty/sabre/CalDAV/Principal/User.php b/src/serverlib/3rdparty/sabre/CalDAV/Principal/User.php deleted file mode 100644 index a0aacb3..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/Principal/User.php +++ /dev/null @@ -1,134 +0,0 @@ -<?php - -namespace Sabre\CalDAV\Principal; -use Sabre\DAV; -use Sabre\DAVACL; - -/** - * CalDAV principal - * - * This is a standard user-principal for CalDAV. This principal is also a - * collection and returns the caldav-proxy-read and caldav-proxy-write child - * principals. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class User extends DAVACL\Principal implements DAV\ICollection { - - /** - * Creates a new file in the directory - * - * @param string $name Name of the file - * @param resource $data Initial payload, passed as a readable stream resource. - * @throws DAV\Exception\Forbidden - * @return void - */ - function createFile($name, $data = null) { - - throw new DAV\Exception\Forbidden('Permission denied to create file (filename ' . $name . ')'); - - } - - /** - * Creates a new subdirectory - * - * @param string $name - * @throws DAV\Exception\Forbidden - * @return void - */ - function createDirectory($name) { - - throw new DAV\Exception\Forbidden('Permission denied to create directory'); - - } - - /** - * Returns a specific child node, referenced by its name - * - * @param string $name - * @return DAV\INode - */ - function getChild($name) { - - $principal = $this->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/src/serverlib/3rdparty/sabre/CalDAV/Property/AllowedSharingModes.php b/src/serverlib/3rdparty/sabre/CalDAV/Property/AllowedSharingModes.php deleted file mode 100644 index faf140b..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/Property/AllowedSharingModes.php +++ /dev/null @@ -1,74 +0,0 @@ -<?php - -namespace Sabre\CalDAV\Property; - -use Sabre\DAV; - -/** - * AllowedSharingModes - * - * This property encodes the 'allowed-sharing-modes' property, as defined by - * the 'caldav-sharing-02' spec, in the http://calendarserver.org/ns/ - * namespace. - * - * This property is a representation of the supported-calendar_component-set - * property in the CalDAV namespace. It simply requires an array of components, - * such as VEVENT, VTODO - * - * @see https://trac.calendarserver.org/browser/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class AllowedSharingModes extends DAV\Property { - - /** - * Whether or not a calendar can be shared with another user - * - * @var bool - */ - protected $canBeShared; - - /** - * Whether or not the calendar can be placed on a public url. - * - * @var bool - */ - protected $canBePublished; - - /** - * Constructor - * - * @param bool $canBeShared - * @param bool $canBePublished - * @return void - */ - function __construct($canBeShared, $canBePublished) { - - $this->canBeShared = $canBeShared; - $this->canBePublished = $canBePublished; - - } - - /** - * Serializes the property in a DOMDocument - * - * @param DAV\Server $server - * @param \DOMElement $node - * @return void - */ - 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/src/serverlib/3rdparty/sabre/CalDAV/Property/EmailAddressSet.php b/src/serverlib/3rdparty/sabre/CalDAV/Property/EmailAddressSet.php deleted file mode 100644 index 57e39d0..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/Property/EmailAddressSet.php +++ /dev/null @@ -1,73 +0,0 @@ -<?php - -namespace Sabre\CalDAV\Property; - -use Sabre\DAV; - -/** - * email-address-set property - * - * This property represents the email-address-set property in the - * http://calendarserver.org/ns/ namespace. - * - * It's a list of email addresses associated with a user. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class EmailAddressSet extends DAV\Property { - - /** - * emails - * - * @var array - */ - private $emails; - - /** - * __construct - * - * @param array $emails - */ - function __construct(array $emails) { - - $this->emails = $emails; - - } - - /** - * Returns the email addresses - * - * @return array - */ - function getValue() { - - return $this->emails; - - } - - /** - * 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 - */ - function serialize(DAV\Server $server,\DOMElement $dom) { - - $prefix = $server->xmlNamespaces['http://calendarserver.org/ns/']; - - foreach($this->emails as $email) { - - $elem = $dom->ownerDocument->createElement($prefix . ':email-address'); - $elem->appendChild($dom->ownerDocument->createTextNode($email)); - $dom->appendChild($elem); - - } - - } - -} diff --git a/src/serverlib/3rdparty/sabre/CalDAV/Property/Invite.php b/src/serverlib/3rdparty/sabre/CalDAV/Property/Invite.php deleted file mode 100644 index 76cad75..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/Property/Invite.php +++ /dev/null @@ -1,228 +0,0 @@ -<?php - -namespace Sabre\CalDAV\Property; - -use Sabre\CalDAV\SharingPlugin as SharingPlugin; -use Sabre\DAV; -use Sabre\CalDAV; - -/** - * Invite property - * - * This property encodes the 'invite' property, as defined by - * the 'caldav-sharing-02' spec, in the http://calendarserver.org/ns/ - * namespace. - * - * @see https://trac.calendarserver.org/browser/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class Invite extends DAV\Property { - - /** - * The list of users a calendar has been shared to. - * - * @var array - */ - protected $users; - - /** - * The organizer contains information about the person who shared the - * object. - * - * @var array - */ - protected $organizer; - - /** - * Creates the property. - * - * Users is an array. Each element of the array has the following - * properties: - * - * * href - Often a mailto: address - * * commonName - Optional, for example a first and lastname for a user. - * * status - One of the SharingPlugin::STATUS_* constants. - * * readOnly - true or false - * * summary - Optional, description of the share - * - * The organizer key is optional to specify. It's only useful when a - * 'sharee' requests the sharing information. - * - * The organizer may have the following properties: - * * href - Often a mailto: address. - * * commonName - Optional human-readable name. - * * firstName - Optional first name. - * * lastName - Optional last name. - * - * If you wonder why these two structures are so different, I guess a - * valid answer is that the current spec is still a draft. - * - * @param array $users - */ - function __construct(array $users, array $organizer = null) { - - $this->users = $users; - $this->organizer = $organizer; - - } - - /** - * Returns the list of users, as it was passed to the constructor. - * - * @return array - */ - function getValue() { - - return $this->users; - - } - - /** - * Serializes the property in a DOMDocument - * - * @param DAV\Server $server - * @param \DOMElement $node - * @return void - */ - 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 - * @param array $propertyMap - * @return DAV\IProperty - */ - static function unserialize(\DOMElement $prop, array $propertyMap) { - - $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/src/serverlib/3rdparty/sabre/CalDAV/Property/ScheduleCalendarTransp.php b/src/serverlib/3rdparty/sabre/CalDAV/Property/ScheduleCalendarTransp.php deleted file mode 100644 index 329e1cc..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/Property/ScheduleCalendarTransp.php +++ /dev/null @@ -1,103 +0,0 @@ -<?php - -namespace Sabre\CalDAV\Property; - -use Sabre\DAV; -use Sabre\CalDAV; - -/** - * schedule-calendar-transp property. - * - * This property is a representation of the schedule-calendar-transp property. - * This property is defined in RFC6638 (caldav scheduling). - * - * Its values are either 'transparent' or 'opaque'. If it's transparent, it - * means that this calendar will not be taken into consideration when a - * different user queries for free-busy information. If it's 'opaque', it will. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class ScheduleCalendarTransp extends DAV\Property { - - const TRANSPARENT = 'transparent'; - const OPAQUE = 'opaque'; - - protected $value; - - /** - * Creates the property - * - * @param string $value - */ - function __construct($value) { - - if ($value !== self::TRANSPARENT && $value !== self::OPAQUE) { - throw new \InvalidArgumentException('The value must either be specified as "transparent" or "opaque"'); - } - $this->value = $value; - - } - - /** - * Returns the current value - * - * @return string - */ - function getValue() { - - return $this->value; - - } - - /** - * Serializes the property in a DOMDocument - * - * @param DAV\Server $server - * @param \DOMElement $node - * @return void - */ - 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 - * @param array $propertyMap - * @return ScheduleCalendarTransp - */ - static function unserialize(\DOMElement $node, array $propertyMap) { - - $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/src/serverlib/3rdparty/sabre/CalDAV/Property/SupportedCalendarComponentSet.php b/src/serverlib/3rdparty/sabre/CalDAV/Property/SupportedCalendarComponentSet.php deleted file mode 100644 index 50e5474..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/Property/SupportedCalendarComponentSet.php +++ /dev/null @@ -1,89 +0,0 @@ -<?php - -namespace Sabre\CalDAV\Property; - -use Sabre\DAV; -use Sabre\CalDAV; - -/** - * Supported component set property - * - * This property is a representation of the supported-calendar_component-set - * property in the CalDAV namespace. It simply requires an array of components, - * such as VEVENT, VTODO - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class SupportedCalendarComponentSet extends DAV\Property { - - /** - * List of supported components, such as "VEVENT, VTODO" - * - * @var array - */ - private $components; - - /** - * Creates the property - * - * @param array $components - */ - function __construct(array $components) { - - $this->components = $components; - - } - - /** - * Returns the list of supported components - * - * @return array - */ - function getValue() { - - return $this->components; - - } - - /** - * Serializes the property in a DOMDocument - * - * @param DAV\Server $server - * @param \DOMElement $node - * @return void - */ - 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 - * @param array $propertyMap - * @return Property_SupportedCalendarComponentSet - */ - static function unserialize(\DOMElement $node, array $propertyMap) { - - $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/src/serverlib/3rdparty/sabre/CalDAV/Property/SupportedCalendarData.php b/src/serverlib/3rdparty/sabre/CalDAV/Property/SupportedCalendarData.php deleted file mode 100644 index 7f44c2c..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/Property/SupportedCalendarData.php +++ /dev/null @@ -1,44 +0,0 @@ -<?php - -namespace Sabre\CalDAV\Property; -use Sabre\DAV; -use Sabre\CalDAV\Plugin; - -/** - * Supported-calendar-data property - * - * This property is a representation of the supported-calendar-data property - * in the CalDAV namespace. SabreDAV only has support for text/calendar;2.0 - * so the value is currently hardcoded. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class SupportedCalendarData extends DAV\Property { - - /** - * Serializes the property in a DOMDocument - * - * @param DAV\Server $server - * @param \DOMElement $node - * @return void - */ - function serialize(DAV\Server $server,\DOMElement $node) { - - $doc = $node->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); - - $caldata = $doc->createElement($prefix . ':calendar-data'); - $caldata->setAttribute('content-type','application/calendar+json'); - $node->appendChild($caldata); - - } - -} diff --git a/src/serverlib/3rdparty/sabre/CalDAV/Property/SupportedCollationSet.php b/src/serverlib/3rdparty/sabre/CalDAV/Property/SupportedCollationSet.php deleted file mode 100644 index 91b4572..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/Property/SupportedCollationSet.php +++ /dev/null @@ -1,45 +0,0 @@ -<?php - -namespace Sabre\CalDAV\Property; -use Sabre\DAV; - -/** - * supported-collation-set property - * - * This property is a representation of the supported-collation-set property - * in the CalDAV namespace. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class SupportedCollationSet extends DAV\Property { - - /** - * Serializes the property in a DOM document - * - * @param DAV\Server $server - * @param \DOMElement $node - * @return void - */ - function serialize(DAV\Server $server,\DOMElement $node) { - - $doc = $node->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/src/serverlib/3rdparty/sabre/CalDAV/Schedule/IInbox.php b/src/serverlib/3rdparty/sabre/CalDAV/Schedule/IInbox.php deleted file mode 100644 index b49b8a9..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/Schedule/IInbox.php +++ /dev/null @@ -1,15 +0,0 @@ -<?php - -namespace Sabre\CalDAV\Schedule; - -/** - * Implement this interface to have a node be recognized as a CalDAV scheduling - * inbox. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License - */ -interface IInbox extends \Sabre\CalDAV\ICalendarObjectContainer, \Sabre\DAVACL\IACL { - -} diff --git a/src/serverlib/3rdparty/sabre/CalDAV/Schedule/IMipPlugin.php b/src/serverlib/3rdparty/sabre/CalDAV/Schedule/IMipPlugin.php deleted file mode 100644 index f7b6f58..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/Schedule/IMipPlugin.php +++ /dev/null @@ -1,177 +0,0 @@ -<?php - -namespace Sabre\CalDAV\Schedule; - -use Sabre\DAV; -use Sabre\VObject; -use Sabre\VObject\ITip; - -/** - * iMIP handler. - * - * This class is responsible for sending out iMIP messages. iMIP is the - * email-based transport for iTIP. iTIP deals with scheduling operations for - * iCalendar objects. - * - * If you want to customize the email that gets sent out, you can do so by - * extending this class and overriding the sendMessage method. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class IMipPlugin extends DAV\ServerPlugin { - - /** - * Email address used in From: header. - * - * @var string - */ - protected $senderEmail; - - /** - * Server class - * - * @var DAV\Server - */ - protected $server; - - /** - * ITipMessage - * - * @var ITip\Message - */ - protected $itipMessage; - - /** - * Creates the email handler. - * - * @param string $senderEmail. The 'senderEmail' is the email that shows up - * in the 'From:' address. This should - * generally be some kind of no-reply email - * address you own. - */ - function __construct($senderEmail) { - - $this->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 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 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/src/serverlib/3rdparty/sabre/CalDAV/Schedule/ISchedulingObject.php b/src/serverlib/3rdparty/sabre/CalDAV/Schedule/ISchedulingObject.php deleted file mode 100644 index 2455be6..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/Schedule/ISchedulingObject.php +++ /dev/null @@ -1,13 +0,0 @@ -<?php - -namespace Sabre\CalDAV\Schedule; - -/** - * The SchedulingObject represents a scheduling object in the Inbox collection - * - * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - */ -interface ISchedulingObject extends \Sabre\CalDAV\ICalendarObject { - -} diff --git a/src/serverlib/3rdparty/sabre/CalDAV/Schedule/Inbox.php b/src/serverlib/3rdparty/sabre/CalDAV/Schedule/Inbox.php deleted file mode 100644 index ce75cdf..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/Schedule/Inbox.php +++ /dev/null @@ -1,268 +0,0 @@ -<?php - -namespace Sabre\CalDAV\Schedule; - -use - Sabre\DAV, - Sabre\CalDAV, - Sabre\DAVACL, - Sabre\CalDAV\Backend, - Sabre\VObject; - -/** - * The CalDAV scheduling inbox - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License - */ -class Inbox extends DAV\Collection implements IInbox { - - /** - * CalDAV backend - * - * @var Backend\BackendInterface - */ - protected $caldavBackend; - - /** - * The principal Uri - * - * @var string - */ - protected $principalUri; - - /** - * Constructor - * - * @param string $principalUri - */ - function __construct(Backend\SchedulingSupport $caldavBackend, $principalUri) { - - $this->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' => $this->getOwner(), - 'protected' => true, - ], - [ - 'privilege' => '{DAV:}write-properties', - 'principal' => $this->getOwner(), - 'protected' => true, - ], - [ - 'privilege' => '{DAV:}unbind', - 'principal' => $this->getOwner(), - 'protected' => true, - ], - [ - 'privilege' => '{DAV:}read', - 'principal' => $this->getOwner() . '/calendar-proxy-read', - 'protected' => true, - ], - [ - 'privilege' => '{DAV:}read', - 'principal' => $this->getOwner() . '/calendar-proxy-write', - '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']; - } - } - return $result; - - } - -} diff --git a/src/serverlib/3rdparty/sabre/CalDAV/Schedule/Outbox.php b/src/serverlib/3rdparty/sabre/CalDAV/Schedule/Outbox.php deleted file mode 100644 index ca0c6d2..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/Schedule/Outbox.php +++ /dev/null @@ -1,183 +0,0 @@ -<?php - -namespace Sabre\CalDAV\Schedule; -use Sabre\DAV; -use Sabre\CalDAV; -use Sabre\DAVACL; - -/** - * The CalDAV scheduling outbox - * - * The outbox is mainly used as an endpoint in the tree for a client to do - * free-busy requests. This functionality is completely handled by the - * Scheduling plugin, so this object is actually mostly static. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class Outbox extends DAV\Collection implements IOutbox { - - /** - * The principal Uri - * - * @var string - */ - protected $principalUri; - - /** - * Constructor - * - * @param string $principalUri - */ - function __construct($principalUri) { - - $this->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/src/serverlib/3rdparty/sabre/CalDAV/Schedule/Plugin.php b/src/serverlib/3rdparty/sabre/CalDAV/Schedule/Plugin.php deleted file mode 100644 index 3ef0841..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/Schedule/Plugin.php +++ /dev/null @@ -1,881 +0,0 @@ -<?php - -namespace Sabre\CalDAV\Schedule; - -use - DateTimeZone, - Sabre\DAV\Server, - Sabre\DAV\ServerPlugin, - Sabre\DAV\Property\Href, - Sabre\DAV\PropFind, - Sabre\DAV\INode, - Sabre\DAV\IFile, - Sabre\HTTP\RequestInterface, - Sabre\HTTP\ResponseInterface, - Sabre\VObject, - Sabre\VObject\Reader, - Sabre\VObject\Component\VCalendar, - Sabre\VObject\ITip, - Sabre\VObject\ITip\Message, - Sabre\DAVACL, - Sabre\CalDAV\ICalendar, - Sabre\CalDAV\ICalendarObject, - Sabre\CalDAV\Property\ScheduleCalendarTransp, - Sabre\DAV\Exception\NotFound, - Sabre\DAV\Exception\Forbidden, - Sabre\DAV\Exception\BadRequest, - Sabre\DAV\Exception\NotImplemented; - -/** - * CalDAV scheduling plugin. - * ========================= - * - * This plugin provides the functionality added by the "Scheduling Extensions - * to CalDAV" standard, as defined in RFC6638. - * - * calendar-auto-schedule largely works by intercepting a users request to - * update their local calendar. If a user creates a new event with attendees, - * this plugin is supposed to grab the information from that event, and notify - * the attendees of this. - * - * There's 3 possible transports for this: - * * local delivery - * * delivery through email (iMip) - * * server-to-server delivery (iSchedule) - * - * iMip is simply, because we just need to add the iTip message as an email - * attachment. Local delivery is harder, because we both need to add this same - * message to a local DAV inbox, as well as live-update the relevant events. - * - * iSchedule is something for later. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License - */ -class Plugin extends ServerPlugin { - - /** - * This is the official CalDAV namespace - */ - const NS_CALDAV = 'urn:ietf:params:xml:ns:caldav'; - - /** - * Reference to main Server object. - * - * @var Server - */ - protected $server; - - /** - * Returns a list of features for the DAV: HTTP header. - * - * @return array - */ - function getFeatures() { - - return ['calendar-auto-schedule']; - - } - - /** - * Returns the name of the plugin. - * - * Using this name other plugins will be able to access other plugins - * using Server::getPlugin - * - * @return string - */ - function getPluginName() { - - return 'caldav-schedule'; - - } - - /** - * Initializes the plugin - * - * @param Server $server - * @return void - */ - function initialize(Server $server) { - - $this->server = $server; - $server->on('method:POST', [$this, 'httpPost']); - $server->on('propFind', [$this, 'propFind']); - $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) return; - - $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); - $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); - $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); - - $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'; - - }); - - } - - /** - * 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() - ); - - $broker = new ITip\Broker(); - - if (!$isNew) { - $node = $this->server->tree->getNodeForPath($request->getPath()); - $oldObj = Reader::read($node->get()); - } else { - $oldObj = null; - } - - $this->processICalendarChange($oldObj, $vCal, $addresses, [], $modified); - - } - - /** - * 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 = '{' . Plugin::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 boolean $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); - - } 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->xmlNamespaces 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 \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( - ['{http://sabredav.org/ns}email-address' => $email], - [ - '{DAV:}principal-URL', $caldavNS . 'calendar-home-set', - '{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, - ]; - } - $homeSet = $result[0][200][$caldavNS . 'calendar-home-set']->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(); - } - - // 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); - - } - - $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); - - $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'; - - } - -} diff --git a/src/serverlib/3rdparty/sabre/CalDAV/Schedule/SchedulingObject.php b/src/serverlib/3rdparty/sabre/CalDAV/Schedule/SchedulingObject.php deleted file mode 100644 index ab5d47c..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/Schedule/SchedulingObject.php +++ /dev/null @@ -1,165 +0,0 @@ -<?php - -namespace Sabre\CalDAV\Schedule; - -use Sabre\CalDAV\Backend; -use Sabre\DAV\Exception\MethodNotAllowed; - -/** - * The SchedulingObject represents a scheduling object in the Inbox collection - * - * @author Brett (https://github.com/bretten) - * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - */ -class SchedulingObject extends \Sabre\CalDAV\CalendarObject implements ISchedulingObject { - - /** - /* The CalDAV backend - * - * @var Backend\SchedulingSupport - */ - protected $caldavBackend; - - /** - * Array with information about this SchedulingObject - * - * @var array - */ - protected $objectData; - - /** - * Constructor - * - * The following properties may be passed within $objectData: - * - * * uri - A unique uri. Only the 'basename' must be passed. - * * principaluri - the principal that owns the object. - * * calendardata (optional) - The iCalendar data - * * etag - (optional) The etag for this object, MUST be encloded with - * double-quotes. - * * size - (optional) The size of the data in bytes. - * * lastmodified - (optional) format as a unix timestamp. - * * acl - (optional) Use this to override the default ACL for the node. - * - * @param Backend\BackendInterface $caldavBackend - * @param array $objectData - */ - function __construct(Backend\SchedulingSupport $caldavBackend,array $objectData) { - - $this->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/src/serverlib/3rdparty/sabre/CalDAV/SharedCalendar.php b/src/serverlib/3rdparty/sabre/CalDAV/SharedCalendar.php deleted file mode 100644 index a3019aa..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/SharedCalendar.php +++ /dev/null @@ -1,148 +0,0 @@ -<?php - -namespace Sabre\CalDAV; - -/** - * This object represents a CalDAV calendar that is shared by a different user. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class SharedCalendar extends Calendar implements ISharedCalendar { - - /** - * Constructor - * - * @param Backend\BackendInterface $caldavBackend - * @param array $calendarInfo - */ - function __construct(Backend\BackendInterface $caldavBackend, $calendarInfo) { - - $required = [ - '{http://calendarserver.org/ns/}shared-url', - '{http://sabredav.org/ns}owner-principal', - '{http://sabredav.org/ns}read-only', - ]; - foreach($required as $r) { - if (!isset($calendarInfo[$r])) { - throw new \InvalidArgumentException('The ' . $r . ' property must be specified for SharedCalendar(s)'); - } - } - - parent::__construct($caldavBackend, $calendarInfo); - - } - - /** - * This method should return the url of the owners' copy of the shared - * calendar. - * - * @return string - */ - function getSharedUrl() { - - return $this->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/src/serverlib/3rdparty/sabre/CalDAV/SharingPlugin.php b/src/serverlib/3rdparty/sabre/CalDAV/SharingPlugin.php deleted file mode 100644 index ff6f390..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/SharingPlugin.php +++ /dev/null @@ -1,502 +0,0 @@ -<?php - -namespace Sabre\CalDAV; - -use - Sabre\DAV, - Sabre\HTTP\RequestInterface, - Sabre\HTTP\ResponseInterface; - -/** - * This plugin implements support for caldav sharing. - * - * This spec is defined at: - * http://svn.calendarserver.org/repository/calendarserver/CalendarServer/trunk/doc/Extensions/caldav-sharing.txt - * - * See: - * Sabre\CalDAV\Backend\SharingSupport for all the documentation. - * - * Note: This feature is experimental, and may change in between different - * SabreDAV versions. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class SharingPlugin extends DAV\ServerPlugin { - - /** - * These are the various status constants used by sharing-messages. - */ - const STATUS_ACCEPTED = 1; - const STATUS_DECLINED = 2; - const STATUS_DELETED = 3; - const STATUS_NORESPONSE = 4; - const STATUS_INVALID = 5; - - /** - * Reference to SabreDAV server object. - * - * @var Sabre\DAV\Server - */ - protected $server; - - /** - * 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-sharing']; - - } - - /** - * 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 'caldav-sharing'; - - } - - /** - * 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) { - - $this->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->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 Property\Invite( - $node->getShares() - ); - }); - - } - - if ($node instanceof ISharedCalendar) { - - $propFind->handle('{' . Plugin::NS_CALENDARSERVER . '}shared-url', function() use ($node) { - return new DAV\Property\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 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 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); - - $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; - } - - $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'); - } - - $mutations = $this->parseShareRequest($dom); - - $node->updateShares($mutations[0], $mutations[1]); - - $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'); - } - - $message = $this->parseInviteReplyRequest($dom); - - $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) { - $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); - $response->setHeader('Content-Type','application/xml'); - $response->setBody($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; - } - $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; - - } - - - - } - - /** - * 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 - */ - function parseShareRequest(\DOMDocument $dom) { - - $xpath = new \DOMXPath($dom); - $xpath->registerNamespace('cs', Plugin::NS_CALENDARSERVER); - $xpath->registerNamespace('d', 'urn:DAV'); - - $set = []; - $elems = $xpath->query('cs:set'); - - for($i=0; $i < $elems->length; $i++) { - - $xset = $elems->item($i); - $set[] = [ - '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 = []; - $elems = $xpath->query('cs:remove'); - - for($i=0; $i < $elems->length; $i++) { - - $xremove = $elems->item($i); - $remove[] = $xpath->evaluate('string(d:href)', $xremove); - - } - - return [$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 - */ - 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 [ - '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/src/serverlib/3rdparty/sabre/CalDAV/Subscriptions/Plugin.php b/src/serverlib/3rdparty/sabre/CalDAV/Subscriptions/Plugin.php deleted file mode 100644 index edb93b8..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/Subscriptions/Plugin.php +++ /dev/null @@ -1,85 +0,0 @@ -<?php - -namespace Sabre\CalDAV\Subscriptions; - -use - Sabre\DAV\INode, - Sabre\DAV\PropFind, - Sabre\DAV\ServerPlugin, - Sabre\DAV\Server; - -/** - * This plugin adds calendar-subscription support to your CalDAV server. - * - * Some clients support 'managed subscriptions' server-side. This is basically - * a list of subscription urls a user is using. - * - * @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 ServerPlugin { - - /** - * 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->resourceTypeMapping['Sabre\\CalDAV\\Subscriptions\\ISubscription'] = - '{http://calendarserver.org/ns/}subscribed'; - - $server->propertyMap['{http://calendarserver.org/ns/}source'] = - 'Sabre\\DAV\\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. - * - * @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); - } - - } - - } - -} diff --git a/src/serverlib/3rdparty/sabre/CalDAV/Subscriptions/Subscription.php b/src/serverlib/3rdparty/sabre/CalDAV/Subscriptions/Subscription.php deleted file mode 100644 index c2413b0..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/Subscriptions/Subscription.php +++ /dev/null @@ -1,277 +0,0 @@ -<?php - -namespace Sabre\CalDAV\Subscriptions; - -use - Sabre\DAV\Collection, - Sabre\DAV\Property\Href, - Sabre\DAV\PropPatch, - Sabre\DAV\Exception\MethodNotAllowed, - Sabre\DAVACL\IACL, - Sabre\CalDAV\Backend\SubscriptionSupport; - - - -/** - * Subscription Node - * - * This node represents a subscription. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class Subscription extends Collection implements ISubscription, IACL { - - /** - * caldavBackend - * - * @var SupportsSubscriptions - */ - protected $caldavBackend; - - /** - * subscriptionInfo - * - * @var array - */ - protected $subscriptionInfo; - - /** - * Constructor - * - * @param SubscriptionSupport $caldavBackend - * @param array $calendarInfo - */ - function __construct(SubscriptionSupport $caldavBackend, array $subscriptionInfo) { - - $this->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 (isset($this->subscriptionInfo[$prop])) { - $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/src/serverlib/3rdparty/sabre/CalDAV/UserCalendars.php b/src/serverlib/3rdparty/sabre/CalDAV/UserCalendars.php deleted file mode 100644 index 2e3c23a..0000000 --- a/src/serverlib/3rdparty/sabre/CalDAV/UserCalendars.php +++ /dev/null @@ -1,19 +0,0 @@ -<?php - -namespace Sabre\CalDAV; - -/** - * This class is deprecated! - * - * It has been renamed to Sabre\CalDAV\CalendarHome. - * - * This stub class will be removed in a future version of sabredav. - * - * @deprecated - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class UserCalendars extends CalendarHome { - -} diff --git a/src/serverlib/3rdparty/sabre/CardDAV/AddressBook.php b/src/serverlib/3rdparty/sabre/CardDAV/AddressBook.php deleted file mode 100644 index 6fa5779..0000000 --- a/src/serverlib/3rdparty/sabre/CardDAV/AddressBook.php +++ /dev/null @@ -1,432 +0,0 @@ -<?php - -namespace Sabre\CardDAV; - -use Sabre\DAV; -use Sabre\DAVACL; - -/** - * The AddressBook class represents a CardDAV addressbook, owned by a specific user - * - * The AddressBook can contain multiple vcards - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class AddressBook extends DAV\Collection implements IAddressBook, DAV\IProperties, DAVACL\IACL, DAV\Sync\ISyncCollection, DAV\IMultiGet { - - /** - * This is an array with addressbook information - * - * @var array - */ - protected $addressBookInfo; - - /** - * CardDAV backend - * - * @var Backend\BackendInterface - */ - protected $carddavBackend; - - /** - * Constructor - * - * @param Backend\BackendInterface $carddavBackend - * @param array $addressBookInfo - */ - function __construct(Backend\BackendInterface $carddavBackend, array $addressBookInfo) { - - $this->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. - * - * @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/src/serverlib/3rdparty/sabre/CardDAV/AddressBookQueryParser.php b/src/serverlib/3rdparty/sabre/CardDAV/AddressBookQueryParser.php deleted file mode 100644 index 7b2938a..0000000 --- a/src/serverlib/3rdparty/sabre/CardDAV/AddressBookQueryParser.php +++ /dev/null @@ -1,221 +0,0 @@ -<?php - -namespace Sabre\CardDAV; - -use Sabre\DAV; - -/** - * Parses the addressbook-query report request body. - * - * Whoever designed this format, and the CalDAV equivalent even more so, - * has no feel for design. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class AddressBookQueryParser { - - const TEST_ANYOF = 'anyof'; - const TEST_ALLOF = 'allof'; - - /** - * List of requested properties the client wanted - * - * @var array - */ - public $requestedProperties; - - /** - * The number of results the client wants - * - * null means it wasn't specified, which in most cases means 'all results'. - * - * @var int|null - */ - public $limit; - - /** - * List of property filters. - * - * @var array - */ - public $filters; - - /** - * Either TEST_ANYOF or TEST_ALLOF - * - * @var string - */ - public $test; - - /** - * DOM Document - * - * @var DOMDocument - */ - protected $dom; - - /** - * DOM XPath object - * - * @var DOMXPath - */ - protected $xpath; - - /** - * Creates the parser - * - * @param \DOMDocument $dom - */ - function __construct(\DOMDocument $dom) { - - $this->dom = $dom; - - $this->xpath = new \DOMXPath($dom); - $this->xpath->registerNameSpace('card',Plugin::NS_CARDDAV); - - } - - /** - * Parses the request. - * - * @return void - */ - function parse() { - - $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 = []; - - if (!is_null($filter)) { - $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 = []; - $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'] = []; - - - for($ii=0;$ii<$paramFilterNodes->length;$ii++) { - - $propFilter['param-filters'][] = $this->parseParamFilterNode($paramFilterNodes->item($ii)); - - } - $propFilter['text-matches'] = []; - $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 - */ - function parseParamFilterNode(\DOMElement $paramFilterNode) { - - $paramFilter = []; - $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 - */ - function parseTextMatchNode(\DOMElement $textMatchNode) { - - $matchType = $textMatchNode->getAttribute('match-type'); - if (!$matchType) $matchType = 'contains'; - - if (!in_array($matchType, ['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 [ - 'negate-condition' => $negateCondition, - 'collation' => $collation, - 'match-type' => $matchType, - 'value' => $textMatchNode->nodeValue - ]; - - - } - -} diff --git a/src/serverlib/3rdparty/sabre/CardDAV/AddressBookRoot.php b/src/serverlib/3rdparty/sabre/CardDAV/AddressBookRoot.php deleted file mode 100644 index 9f8780b..0000000 --- a/src/serverlib/3rdparty/sabre/CardDAV/AddressBookRoot.php +++ /dev/null @@ -1,80 +0,0 @@ -<?php - -namespace Sabre\CardDAV; - -use Sabre\DAVACL; - -/** - * AddressBook rootnode - * - * This object lists a collection of users, which can contain addressbooks. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class AddressBookRoot extends DAVACL\AbstractPrincipalCollection { - - /** - * Principal Backend - * - * @var Sabre\DAVACL\PrincipalBackend\BackendInteface - */ - protected $principalBackend; - - /** - * CardDAV backend - * - * @var Backend\BackendInterface - */ - protected $carddavBackend; - - /** - * Constructor - * - * This constructor needs both a principal and a carddav backend. - * - * By default this class will show a list of addressbook collections for - * principals in the 'principals' collection. If your main principals are - * actually located in a different path, use the $principalPrefix argument - * to override this. - * - * @param DAVACL\PrincipalBackend\BackendInterface $principalBackend - * @param Backend\BackendInterface $carddavBackend - * @param string $principalPrefix - */ - function __construct(DAVACL\PrincipalBackend\BackendInterface $principalBackend,Backend\BackendInterface $carddavBackend, $principalPrefix = 'principals') { - - $this->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 UserAddressBooks($this->carddavBackend, $principal['uri']); - - } - -} diff --git a/src/serverlib/3rdparty/sabre/CardDAV/Backend/PDO.php b/src/serverlib/3rdparty/sabre/CardDAV/Backend/PDO.php deleted file mode 100644 index a836510..0000000 --- a/src/serverlib/3rdparty/sabre/CardDAV/Backend/PDO.php +++ /dev/null @@ -1,557 +0,0 @@ -<?php - -namespace Sabre\CardDAV\Backend; - -use Sabre\CardDAV; -use Sabre\DAV; - -/** - * PDO CardDAV backend - * - * This CardDAV backend uses PDO to store addressbooks - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class PDO extends AbstractBackend implements SyncSupport { - - /** - * PDO connection - * - * @var PDO - */ - protected $pdo; - - /** - * The PDO table name used to store addressbooks - */ - public $addressBooksTableName = 'addressbooks'; - - /** - * The PDO table name used to store cards - */ - public $cardsTableName = 'cards'; - - /** - * The table name that will be used for tracking changes in address books. - * - * @var string - */ - public $addressBookChangesTableName = 'addressbookchanges'; - - /** - * Sets up the object - * - * @param \PDO $pdo - * @param string $addressBooksTableName - * @param string $cardsTableName - * @deprecated We are going to remove all the tableName arguments in a - * future version, and rely on the public properties instead. - * Stop relying on them! - */ - function __construct(\PDO $pdo, $addressBooksTableName = 'addressbooks', $cardsTableName = 'cards', $addressBookChangesTableName = 'addressbookchanges') { - - $this->pdo = $pdo; - $this->addressBooksTableName = $addressBooksTableName; - $this->cardsTableName = $cardsTableName; - $this->addressBookChangesTableName = $addressBookChangesTableName; - - } - - /** - * 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 void - */ - 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) { - - return array_map(function($uri) use ($addressBookId) { - return $this->getCard($addressBookId, $uri); - }, $uris); - - $query = 'SELECT id, uri, lastmodified, etag, size 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/src/serverlib/3rdparty/sabre/CardDAV/Card.php b/src/serverlib/3rdparty/sabre/CardDAV/Card.php deleted file mode 100644 index 5c56415..0000000 --- a/src/serverlib/3rdparty/sabre/CardDAV/Card.php +++ /dev/null @@ -1,265 +0,0 @@ -<?php - -namespace Sabre\CardDAV; - -use Sabre\DAVACL; -use Sabre\DAV; - - -/** - * The Card object represents a single Card from an addressbook - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class Card extends DAV\File implements ICard, DAVACL\IACL { - - /** - * CardDAV backend - * - * @var Backend\BackendInterface - */ - protected $carddavBackend; - - /** - * Array with information about this Card - * - * @var array - */ - protected $cardData; - - /** - * Array with information about the containing addressbook - * - * @var array - */ - protected $addressBookInfo; - - /** - * Constructor - * - * @param Backend\BackendInterface $carddavBackend - * @param array $addressBookInfo - * @param array $cardData - */ - function __construct(Backend\BackendInterface $carddavBackend,array $addressBookInfo,array $cardData) { - - $this->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/src/serverlib/3rdparty/sabre/CardDAV/ICard.php b/src/serverlib/3rdparty/sabre/CardDAV/ICard.php deleted file mode 100644 index b14cce1..0000000 --- a/src/serverlib/3rdparty/sabre/CardDAV/ICard.php +++ /dev/null @@ -1,20 +0,0 @@ -<?php - -namespace Sabre\CardDAV; - -use Sabre\DAV; - -/** - * Card interface - * - * Extend the ICard interface to allow your custom nodes to be picked up as - * 'Cards'. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -interface ICard extends DAV\IFile { - -} - diff --git a/src/serverlib/3rdparty/sabre/CardDAV/Plugin.php b/src/serverlib/3rdparty/sabre/CardDAV/Plugin.php deleted file mode 100644 index a9d31b9..0000000 --- a/src/serverlib/3rdparty/sabre/CardDAV/Plugin.php +++ /dev/null @@ -1,913 +0,0 @@ -<?php - -namespace Sabre\CardDAV; - -use Sabre\DAV; -use Sabre\DAVACL; -use Sabre\VObject; - -use Sabre\DAV\Exception\ReportNotSupported; - -use Sabre\HTTP; -use Sabre\HTTP\RequestInterface; -use Sabre\HTTP\ResponseInterface; - - -/** - * CardDAV plugin - * - * The CardDAV plugin adds CardDAV functionality to the WebDAV server - * - * @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 { - - /** - * Url to the addressbooks - */ - const ADDRESSBOOK_ROOT = 'addressbooks'; - - /** - * xml namespace for CardDAV elements - */ - const NS_CARDDAV = 'urn:ietf:params:xml:ns:carddav'; - - /** - * Add urls to this property to have them automatically exposed as - * 'directories' to the user. - * - * @var array - */ - public $directories = []; - - /** - * Server class - * - * @var Sabre\DAV\Server - */ - protected $server; - - /** - * The default PDO storage uses a MySQL MEDIUMBLOB for iCalendar data, - * which can hold up to 2^24 = 16777216 bytes. This is plenty. We're - * capping it to 10M here. - */ - protected $maxResourceSize = 10000000; - - /** - * Initializes the plugin - * - * @param DAV\Server $server - * @return void - */ - function initialize(DAV\Server $server) { - - /* Events */ - $server->on('propFind', [$this, 'propFindEarly']); - $server->on('propFind', [$this, 'propFindLate'],150); - $server->on('propPatch', [$this, 'propPatch']); - $server->on('report', [$this, 'report']); - $server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']); - $server->on('onBrowserPostAction', [$this, 'browserPostAction']); - $server->on('beforeWriteContent', [$this, 'beforeWriteContent']); - $server->on('beforeCreateFile', [$this, 'beforeCreateFile']); - $server->on('afterMethod:GET', [$this, 'httpAfterGet']); - - /* 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 - */ - 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 Property\SupportedAddressData(); - }); - $propFind->handle($ns . 'supported-collation-set', function() { - return new Property\SupportedCollationSet(); - }); - - } - if ($node instanceof DAVACL\IPrincipal) { - - $path = $propFind->getPath(); - - $propFind->handle('{' . self::NS_CARDDAV . '}addressbook-home-set', function() use ($path) { - return new DAV\Property\Href($this->getAddressBookHomeForPrincipal($path) . '/'); - }); - - if ($this->directories) $propFind->handle('{' . self::NS_CARDDAV . '}directory-gateway', function() { - return 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. - $propFind->handle('{' . self::NS_CARDDAV . '}address-data', function() use ($node) { - $val = $node->get(); - if (is_resource($val)) - $val = stream_get_contents($val); - - return $val; - - }); - - } - - if ($node instanceof UserAddressBooks) { - - $propFind->handle('{http://calendarserver.org/ns/}me-card', function() use ($node) { - - $props = $this->server->getProperties($node->getOwner(), ['{http://sabredav.org/ns}vcard-url']); - if (isset($props['{http://sabredav.org/ns}vcard-url'])) { - - return new DAV\Property\Href( - $props['{http://sabredav.org/ns}vcard-url'] - ); - - } - - }); - - } - - } - - /** - * This event is triggered when a PROPPATCH method is executed - * - * @param string $path - * @param DAV\PropPatch $propPatch - * @return bool - */ - function propPatch($path, DAV\PropPatch $propPatch) { - - $node = $this->server->tree->getNodeForPath($path); - if (!$node instanceof UserAddressBooks) { - return true; - } - - $meCard = '{http://calendarserver.org/ns/}me-card'; - - $propPatch->handle($meCard, function($value) use ($node) { - - if ($value instanceof DAV\Property\IHref) { - $value = $value->getHref(); - $value = $this->server->calculateUri($value); - } elseif (!is_null($value)) { - return 400; - } - - $innerResult = $this->server->updateProperties( - $node->getOwner(), - [ - '{http://sabredav.org/ns}vcard-url' => $value, - ] - ); - - return $innerResult['{http://sabredav.org/ns}vcard-url']; - - }); - - } - - /** - * This functions handles REPORT requests specific to CardDAV - * - * @param string $reportName - * @param \DOMNode $dom - * @return bool - */ - function report($reportName,$dom) { - - 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 \DOMNode $dom - * @return void - */ - function addressbookMultiGetReport($dom) { - - $properties = array_keys(DAV\XMLUtil::parseProperties($dom->firstChild)); - - $hrefElems = $dom->getElementsByTagNameNS('urn:DAV','href'); - $propertyList = []; - - $uris = []; - foreach($hrefElems as $elem) { - - $uris[] = $this->server->calculateUri($elem->nodeValue); - - } - - $xpath = new \DOMXPath($dom); - $xpath->registerNameSpace('card',Plugin::NS_CARDDAV); - $xpath->registerNameSpace('dav','urn:DAV'); - - $contentType = $xpath->evaluate("string(/card:addressbook-multiget/dav:prop/card:address-data/@content-type)"); - $version = $xpath->evaluate("string(/card:addressbook-multiget/dav:prop/card:address-data/@version)"); - if ($version) { - $contentType.='; version=' . $version; - } - - $vcardType = $this->negotiateVCard( - $contentType - ); - - $propertyList = []; - foreach($this->server->getPropertiesForMultiplePaths($uris, $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; - } - - } - - - /** - * 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 = [ - $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()); - } - - $xpath = new \DOMXPath($dom); - $xpath->registerNameSpace('card',Plugin::NS_CARDDAV); - $xpath->registerNameSpace('dav','urn:DAV'); - - $contentType = $xpath->evaluate("string(/card:addressbook-query/dav:prop/card:address-data/@content-type)"); - $version = $xpath->evaluate("string(/card:addressbook-query/dav:prop/card:address-data/@version)"); - if ($version) { - $contentType.='; version=' . $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, $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 = []; - foreach($validNodes as $validNode) { - - if ($depth==0) { - $href = $this->server->getRequestUri(); - } else { - $href = $this->server->getRequestUri() . '/' . $validNode->getName(); - } - - list($props) = $this->server->getPropertiesForPath($href, $query->requestedProperties, 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) { - - $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 = []; - 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) { - 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 when fetching properties. - * - * This event is scheduled late in the process, after most work for - * propfind has been done. - */ - 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 UserAddressBooks) - return; - - $output.= '<tr><td colspan="2"><form method="post" action=""> - <h3>Create new address book</h3> - <input type="hidden" name="sabreAction" value="mkaddressbook" /> - <label>Name (uri):</label> <input type="text" name="name" /><br /> - <label>Display name:</label> <input type="text" name="{DAV:}displayname" /><br /> - <input type="submit" value="create" /> - </form> - </td></tr>'; - - 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 method allows us to intercept the 'mkaddressbook' sabreAction. This - * action enables the user to create new addressbooks from the browser plugin. - * - * @param string $uri - * @param string $action - * @param array $postVars - * @return bool - */ - function browserPostAction($uri, $action, array $postVars) { - - if ($action!=='mkaddressbook') - return; - - $resourceType = ['{DAV:}collection','{urn:ietf:params:xml:ns:carddav}addressbook']; - $properties = []; - if (isset($postVars['{DAV:}displayname'])) { - $properties['{DAV:}displayname'] = $postVars['{DAV:}displayname']; - } - $this->server->createCollection($uri . '/' . $postVars['name'],$resourceType,$properties); - return false; - - } - - /** - * 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); - return $data->serialize(); - case 'vcard4' : - $data = $data->convert(VObject\Document::VCARD40); - return $data->serialize(); - case 'jcard' : - $data = $data->convert(VObject\Document::VCARD40); - return json_encode($data->jsonSerialize()); - - // @codeCoverageIgnoreStart - } - // @codeCoverageIgnoreEnd - - } - -} diff --git a/src/serverlib/3rdparty/sabre/CardDAV/Property/SupportedAddressData.php b/src/serverlib/3rdparty/sabre/CardDAV/Property/SupportedAddressData.php deleted file mode 100644 index 9bec20e..0000000 --- a/src/serverlib/3rdparty/sabre/CardDAV/Property/SupportedAddressData.php +++ /dev/null @@ -1,73 +0,0 @@ -<?php - -namespace Sabre\CardDAV\Property; - -use Sabre\DAV; -use Sabre\CardDAV; - -/** - * Supported-address-data property - * - * This property is a representation of the supported-address-data property - * in the CardDAV namespace. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class SupportedAddressData extends DAV\Property { - - /** - * supported versions - * - * @var array - */ - protected $supportedData = []; - - /** - * Creates the property - * - * @param array|null $supportedData - */ - function __construct(array $supportedData = null) { - - if (is_null($supportedData)) { - $supportedData = [ - ['contentType' => 'text/vcard', 'version' => '3.0'], - ['contentType' => 'text/vcard', 'version' => '4.0'], - ['contentType' => 'application/vcard+json', 'version' => '4.0'], - ]; - } - - $this->supportedData = $supportedData; - - } - - /** - * Serializes the property in a DOMDocument - * - * @param DAV\Server $server - * @param \DOMElement $node - * @return void - */ - 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/src/serverlib/3rdparty/sabre/CardDAV/Property/SupportedCollationSet.php b/src/serverlib/3rdparty/sabre/CardDAV/Property/SupportedCollationSet.php deleted file mode 100644 index 3061d94..0000000 --- a/src/serverlib/3rdparty/sabre/CardDAV/Property/SupportedCollationSet.php +++ /dev/null @@ -1,45 +0,0 @@ -<?php - -namespace Sabre\CardDAV\Property; -use Sabre\DAV; - -/** - * supported-collation-set property - * - * This property is a representation of the supported-collation-set property - * in the CardDAV namespace. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class SupportedCollationSet extends DAV\Property { - - /** - * Serializes the property in a DOM document - * - * @param DAV\Server $server - * @param \DOMElement $node - * @return void - */ - function serialize(DAV\Server $server,\DOMElement $node) { - - $doc = $node->ownerDocument; - - $prefix = $node->lookupPrefix('urn:ietf:params:xml:ns:carddav'); - if (!$prefix) $prefix = 'card'; - - $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/src/serverlib/3rdparty/sabre/CardDAV/UserAddressBooks.php b/src/serverlib/3rdparty/sabre/CardDAV/UserAddressBooks.php deleted file mode 100644 index e0e8761..0000000 --- a/src/serverlib/3rdparty/sabre/CardDAV/UserAddressBooks.php +++ /dev/null @@ -1,261 +0,0 @@ -<?php - -namespace Sabre\CardDAV; - -use - Sabre\DAV, - Sabre\DAVACL, - Sabre\HTTP\URLUtil; - -/** - * UserAddressBooks class - * - * The UserAddressBooks collection contains a list of addressbooks associated with a user - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class UserAddressBooks extends DAV\Collection implements DAV\IExtendedCollection, DAVACL\IACL { - - /** - * Principal uri - * - * @var array - */ - protected $principalUri; - - /** - * carddavBackend - * - * @var Backend\BackendInterface - */ - protected $carddavBackend; - - /** - * Constructor - * - * @param Backend\BackendInterface $carddavBackend - * @param string $principalUri - */ - function __construct(Backend\BackendInterface $carddavBackend, $principalUri) { - - $this->carddavBackend = $carddavBackend; - $this->principalUri = $principalUri; - - } - - /** - * Returns the name of this object - * - * @return string - */ - function getName() { - - list(,$name) = URLUtil::splitPath($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 addressbook - * - * @param string $name - * @param array $resourceType - * @param array $properties - * @return void - */ - 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 - */ - 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/src/serverlib/3rdparty/sabre/CardDAV/VCFExportPlugin.php b/src/serverlib/3rdparty/sabre/CardDAV/VCFExportPlugin.php deleted file mode 100644 index f99d887..0000000 --- a/src/serverlib/3rdparty/sabre/CardDAV/VCFExportPlugin.php +++ /dev/null @@ -1,111 +0,0 @@ -<?php - -namespace Sabre\CardDAV; - -use - Sabre\DAV, - Sabre\VObject, - Sabre\HTTP\RequestInterface, - Sabre\HTTP\ResponseInterface; - -/** - * VCF Exporter - * - * This plugin adds the ability to export entire address books as .vcf files. - * This is useful for clients that don't support CardDAV yet. They often do - * support vcf files. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @author Thomas Tanghus (http://tanghus.net/) - * @license http://sabre.io/license/ Modified BSD License - */ -class VCFExportPlugin extends DAV\ServerPlugin { - - /** - * Reference to Server class - * - * @var Sabre\DAV\Server - */ - protected $server; - - /** - * Initializes the plugin and registers event handlers - * - * @param DAV\Server $server - * @return void - */ - function initialize(DAV\Server $server) { - - $this->server = $server; - $this->server->on('method:GET', [$this,'httpGet'], 90); - - } - - /** - * 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. - $output .= - VObject\Reader::read($nodeData)->serialize(); - - } - - return $output; - - } - -} diff --git a/src/serverlib/3rdparty/sabre/DAV/Auth/Backend/AbstractBasic.php b/src/serverlib/3rdparty/sabre/DAV/Auth/Backend/AbstractBasic.php deleted file mode 100644 index e9210c1..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Auth/Backend/AbstractBasic.php +++ /dev/null @@ -1,84 +0,0 @@ -<?php - -namespace Sabre\DAV\Auth\Backend; - -use Sabre\DAV; -use Sabre\HTTP; - -/** - * HTTP Basic authentication backend class - * - * This class can be used by authentication objects wishing to use HTTP Basic - * Most of the digest logic is handled, implementors just need to worry about - * the validateUserPass method. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author James David Low (http://jameslow.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -abstract class AbstractBasic implements BackendInterface { - - /** - * This variable holds the currently logged in username. - * - * @var string|null - */ - protected $currentUser; - - /** - * 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 - */ - abstract protected function validateUserPass($username, $password); - - /** - * Returns information about the currently logged in username. - * - * If nobody is currently logged in, this method should return null. - * - * @return string|null - */ - function getCurrentUser() { - return $this->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 - */ - function authenticate(DAV\Server $server, $realm) { - - $auth = new HTTP\Auth\Basic($realm, $server->httpRequest, $server->httpResponse); - $userpass = $auth->getCredentials($server->httpRequest); - 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/src/serverlib/3rdparty/sabre/DAV/Auth/Backend/AbstractDigest.php b/src/serverlib/3rdparty/sabre/DAV/Auth/Backend/AbstractDigest.php deleted file mode 100644 index f64c7d5..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Auth/Backend/AbstractDigest.php +++ /dev/null @@ -1,96 +0,0 @@ -<?php - -namespace Sabre\DAV\Auth\Backend; - -use - Sabre\HTTP, - Sabre\DAV; - -/** - * HTTP Digest authentication backend class - * - * This class can be used by authentication objects wishing to use HTTP Digest - * Most of the digest logic is handled, implementors just need to worry about - * the getDigestHash method - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -abstract class AbstractDigest implements BackendInterface { - - /** - * This variable holds the currently logged in username. - * - * @var array|null - */ - protected $currentUser; - - /** - * 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); - - /** - * 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 - */ - function authenticate(DAV\Server $server, $realm) { - - $digest = new HTTP\Auth\Digest($realm, $server->httpRequest, $server->httpResponse); - $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 - */ - function getCurrentUser() { - - return $this->currentUser; - - } - -} diff --git a/src/serverlib/3rdparty/sabre/DAV/Auth/Backend/Apache.php b/src/serverlib/3rdparty/sabre/DAV/Auth/Backend/Apache.php deleted file mode 100644 index 8d725d9..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Auth/Backend/Apache.php +++ /dev/null @@ -1,66 +0,0 @@ -<?php - -namespace Sabre\DAV\Auth\Backend; -use Sabre\DAV; - -/** - * Apache authenticator - * - * This authentication backend assumes that authentication has been - * configured in apache, rather than within SabreDAV. - * - * Make sure apache is properly configured for this to work. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class Apache implements BackendInterface { - - /** - * Current apache user - * - * @var string - */ - protected $remoteUser; - - /** - * 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 - * @return bool - */ - function authenticate(DAV\Server $server, $realm) { - - $remoteUser = $server->httpRequest->getRawServerValue('REMOTE_USER'); - if (is_null($remoteUser)) { - $remoteUser = $server->httpRequest->getRawServerValue('REDIRECT_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 - */ - function getCurrentUser() { - - return $this->remoteUser; - - } - -} - diff --git a/src/serverlib/3rdparty/sabre/DAV/Auth/Backend/BackendInterface.php b/src/serverlib/3rdparty/sabre/DAV/Auth/Backend/BackendInterface.php deleted file mode 100644 index 8c15085..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Auth/Backend/BackendInterface.php +++ /dev/null @@ -1,36 +0,0 @@ -<?php - -namespace Sabre\DAV\Auth\Backend; - -/** - * This is the base class for any authentication object. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -interface BackendInterface { - - /** - * 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 \Sabre\DAV\Server $server - * @param string $realm - * @return bool - */ - function authenticate(\Sabre\DAV\Server $server,$realm); - - /** - * Returns information about the currently logged in username. - * - * If nobody is currently logged in, this method should return null. - * - * @return string|null - */ - function getCurrentUser(); - -} - diff --git a/src/serverlib/3rdparty/sabre/DAV/Auth/Backend/BasicCallBack.php b/src/serverlib/3rdparty/sabre/DAV/Auth/Backend/BasicCallBack.php deleted file mode 100644 index 4bb6999..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Auth/Backend/BasicCallBack.php +++ /dev/null @@ -1,62 +0,0 @@ -<?php - -namespace Sabre\DAV\Auth\Backend; - -use Sabre\DAV; - -/** - * Extremely simply HTTP Basic auth backend. - * - * This backend basically works by calling a callback, which receives a - * - * username and password. - * The callback must return true or false depending on if authentication was - * correct. - - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class BasicCallBack extends AbstractBasic { - - /** - * Callback - * - * @var callable - */ - protected $callBack; - - /** - * Creates the backend. - * - * A callback must be provided to handle checking the username and - * password. - * - * @param callable $callBack - * @return void - */ - function __construct(callable $callBack) { - - $this->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/src/serverlib/3rdparty/sabre/DAV/Auth/Backend/File.php b/src/serverlib/3rdparty/sabre/DAV/Auth/Backend/File.php deleted file mode 100644 index 05e9041..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Auth/Backend/File.php +++ /dev/null @@ -1,77 +0,0 @@ -<?php - -namespace Sabre\DAV\Auth\Backend; - -use Sabre\DAV; - -/** - * This is an authentication backend that uses a file to manage passwords. - * - * The backend file must conform to Apache's htdigest format - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class File extends AbstractDigest { - - /** - * List of users - * - * @var array - */ - protected $users = []; - - /** - * Creates the backend object. - * - * If the filename argument is passed in, it will parse out the specified file fist. - * - * @param string|null $filename - */ - function __construct($filename=null) { - - if (!is_null($filename)) - $this->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/src/serverlib/3rdparty/sabre/DAV/Auth/Backend/PDO.php b/src/serverlib/3rdparty/sabre/DAV/Auth/Backend/PDO.php deleted file mode 100644 index 786b903..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Auth/Backend/PDO.php +++ /dev/null @@ -1,61 +0,0 @@ -<?php - -namespace Sabre\DAV\Auth\Backend; - -/** - * This is an authentication backend that uses a database to manage passwords. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class PDO extends AbstractDigest { - - /** - * Reference to PDO connection - * - * @var PDO - */ - protected $pdo; - - /** - * PDO table name we'll be using - * - * @var string - */ - public $tableName = 'users'; - - - /** - * Creates the backend object. - * - * If the filename argument is passed in, it will parse out the specified file fist. - * - * @param PDO $pdo - * @param string $tableName The PDO table name to use - * @deprecated The tableName argument will be removed from a future version - * of sabredav. Use the public property instead. - */ - function __construct(\PDO $pdo, $tableName = 'users') { - - $this->pdo = $pdo; - $this->tableName = $tableName; - - } - - /** - * 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/src/serverlib/3rdparty/sabre/DAV/Auth/Plugin.php b/src/serverlib/3rdparty/sabre/DAV/Auth/Plugin.php deleted file mode 100644 index 43a6886..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Auth/Plugin.php +++ /dev/null @@ -1,122 +0,0 @@ -<?php - -namespace Sabre\DAV\Auth; - -use - Sabre\DAV, - Sabre\HTTP\RequestInterface, - Sabre\HTTP\ResponseInterface; - -/** - * This plugin provides Authentication for a WebDAV server. - * - * It relies on a Backend object, which provides user information. - * - * Additionally, it provides support for: - * * {DAV:}current-user-principal property from RFC5397 - * * {DAV:}principal-collection-set property from RFC3744 - * - * @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 { - - /** - * Reference to main server object - * - * @var Sabre\DAV\Server - */ - protected $server; - - /** - * Authentication backend - * - * @var Backend\BackendInterface - */ - protected $authBackend; - - /** - * The authentication realm. - * - * @var string - */ - private $realm; - - /** - * @return string - */ - function getRealm() { - return $this->realm; - } - - /** - * __construct - * - * @param Backend\BackendInterface $authBackend - * @param string $realm - */ - function __construct(Backend\BackendInterface $authBackend, $realm) { - - $this->authBackend = $authBackend; - $this->realm = $realm; - - } - - /** - * Initializes the plugin. This function is automatically called by the server - * - * @param DAV\Server $server - * @return void - */ - function initialize(DAV\Server $server) { - - $this->server = $server; - $this->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 current users' principal uri. - * - * If nobody is logged in, this will return null. - * - * @return string|null - */ - 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 RequestInterface $request - * @param ResponseInterface $response - * @return bool - */ - function beforeMethod(RequestInterface $request, ResponseInterface $response) { - - $this->authBackend->authenticate($this->server,$this->getRealm()); - - } - -} diff --git a/src/serverlib/3rdparty/sabre/DAV/Browser/GuessContentType.php b/src/serverlib/3rdparty/sabre/DAV/Browser/GuessContentType.php deleted file mode 100644 index 3993cfe..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Browser/GuessContentType.php +++ /dev/null @@ -1,100 +0,0 @@ -<?php - -namespace Sabre\DAV\Browser; - -use - Sabre\HTTP\URLUtil, - Sabre\DAV, - Sabre\DAV\PropFind, - Sabre\DAV\Inode; - -/** - * GuessContentType plugin - * - * A lot of the built-in File objects just return application/octet-stream - * as a content-type by default. This is a problem for some clients, because - * they expect a correct contenttype. - * - * There's really no accurate, fast and portable way to determine the contenttype - * so this extension does what the rest of the world does, and guesses it based - * on the file extension. - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class GuessContentType extends DAV\ServerPlugin { - - /** - * List of recognized file extensions - * - * Feel free to add more - * - * @var array - */ - public $extensionMap = [ - - // images - 'jpg' => '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 - */ - 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]; - - } - -} diff --git a/src/serverlib/3rdparty/sabre/DAV/Browser/MapGetToPropFind.php b/src/serverlib/3rdparty/sabre/DAV/Browser/MapGetToPropFind.php deleted file mode 100644 index 08e2dcf..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Browser/MapGetToPropFind.php +++ /dev/null @@ -1,61 +0,0 @@ -<?php - -namespace Sabre\DAV\Browser; - -use - Sabre\DAV, - Sabre\HTTP\RequestInterface, - Sabre\HTTP\ResponseInterface; - -/** - * This is a simple plugin that will map any GET request for non-files to - * PROPFIND allprops-requests. - * - * This should allow easy debugging of PROPFIND - * - * @copyright Copyright (C) fruux GmbH (https://fruux.com/) - * @author Evert Pot (http://evertpot.com/) - * @license http://sabre.io/license/ Modified BSD License - */ -class MapGetToPropFind extends DAV\ServerPlugin { - - /** - * reference to server class - * - * @var Sabre\DAV\Server - */ - protected $server; - - /** - * 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,'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/src/serverlib/3rdparty/sabre/DAV/Browser/Plugin.php b/src/serverlib/3rdparty/sabre/DAV/Browser/Plugin.php deleted file mode 100644 index 88a9377..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Browser/Plugin.php +++ /dev/null @@ -1,675 +0,0 @@ -<?php - -namespace Sabre\DAV\Browser; - -use - Sabre\DAV, - Sabre\HTTP\URLUtil, - Sabre\HTTP\RequestInterface, - Sabre\HTTP\ResponseInterface; - -/** - * Browser Plugin - * - * This plugin provides a html representation, so that a WebDAV server may be accessed - * using a browser. - * - * The class intercepts GET requests to collection resources and generates a simple - * html index. - * - * @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 { - - /** - * 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; - - /** - * Creates the object. - * - * By default it will allow file creation and uploads. - * Specify the first argument as false to disable this - * - * @param bool $enablePost - */ - function __construct($enablePost=true) { - - $this->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,'httpGet'], 200); - $this->server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel'],200); - if ($this->enablePost) $this->server->on('method:POST', [$this,'httpPOST']); - } - - /** - * 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(); - - $sabreAction = isset($getVars['sabreAction'])?$getVars['sabreAction']:null; - - // Asset handling, such as images - if ($sabreAction === 'asset' && isset($getVars['assetName'])) { - $this->serveAsset($getVars['assetName']); - return false; - } - - 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; - - } - - /** - * 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'])); - $this->server->createDirectory($uri . '/' . $folderName); - } - 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) { - - $version = ''; - if (DAV\Server::$exposeVersion) { - $version = DAV\Version::VERSION; - } - - $vars = [ - 'path' => $this->escapeHTML($path), - '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 = <<<HTML -<!DOCTYPE html> -<html> -<head> - <title>$vars[path]/ - sabre/dav $version - - - - - - -
    - -
    - - "; - - $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.= ''; - } - - $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.= '
    '; - - } - - $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) { - $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->server->httpResponse->setHeader('Content-Security-Policy', "img-src 'self'; style-src 'self';"); - - 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 - */ - 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) { - - $view = 'unknown'; - if (is_scalar($value)) { - $view = 'string'; - } elseif($value instanceof DAV\Property) { - - $mapping = [ - 'Sabre\\DAV\\Property\\IHref' => 'href', - 'Sabre\\DAV\\Property\\HrefList' => 'hreflist', - 'Sabre\\DAV\\Property\\SupportedMethodSet' => 'valuelist', - 'Sabre\\DAV\\Property\\ResourceType' => 'xmlvaluelist', - 'Sabre\\DAV\\Property\\SupportedReportSet' => 'xmlvaluelist', - 'Sabre\\DAVACL\\Property\\CurrentUserPrivilegeSet' => 'xmlvaluelist', - 'Sabre\\DAVACL\\Property\\SupportedPrivilegeSet' => 'supported-privilege-set', - ]; - - $view = 'complex'; - foreach($mapping as $class=>$val) { - if ($value instanceof $class) { - $view = $val; - break; - } - } - } - - list($ns, $localName) = DAV\XMLUtil::parseClarkNotation($name); - - $realName = $name; - if (isset($this->server->xmlNamespaces[$ns])) { - $name = $this->server->xmlNamespaces[$ns] . ':' . $localName; - } - - ob_start(); - - $xmlValueDisplay = function($propName) { - $realPropName = $propName; - list($ns, $localName) = DAV\XMLUtil::parseClarkNotation($propName); - if (isset($this->server->xmlNamespaces[$ns])) { - $propName = $this->server->xmlNamespaces[$ns] . ':' . $localName; - } - return "escapeHTML($realPropName) . "\">" . $this->escapeHTML($propName) . ""; - }; - - echo "escapeHTML($realName), "\">", $this->escapeHTML($name), ""; - - switch($view) { - - case 'href' : - echo "server->getBaseUri() . $value->getHref() . '">' . $this->server->getBaseUri() . $value->getHref() . ''; - break; - case 'hreflist' : - echo implode('
    ', array_map(function($href) { - if (stripos($href,'mailto:')===0 || stripos($href,'/')===0 || stripos($href,'http:')===0 || stripos($href,'https:') === 0) { - return "escapeHTML($href) . '">' . $this->escapeHTML($href) . ''; - } else { - return "escapeHTML($this->server->getBaseUri() . $href) . '">' . $this->escapeHTML($this->server->getBaseUri() . $href) . ''; - } - }, $value->getHrefs())); - break; - case 'xmlvaluelist' : - echo implode(', ', array_map($xmlValueDisplay, $value->getValue())); - break; - case 'valuelist' : - echo $this->escapeHTML(implode(', ', $value->getValue())); - break; - case 'supported-privilege-set' : - $traverse = function($priv) use (&$traverse, $xmlValueDisplay) { - echo "
  • "; - echo $xmlValueDisplay($priv['privilege']); - if (isset($priv['abstract']) && $priv['abstract']) { - echo " (abstract)"; - } - if (isset($priv['description'])) { - echo " " . $this->escapeHTML($priv['description']); - } - if (isset($priv['aggregates'])) { - echo "\n
      \n"; - foreach($priv['aggregates'] as $subPriv) { - $traverse($subPriv); - } - echo "
    "; - } - echo "
  • \n"; - }; - echo "
      "; - $traverse($value->getValue(), ''); - echo "
    \n"; - break; - case 'string' : - echo $this->escapeHTML($value); - break; - case 'complex' : - echo 'complex'; - break; - default : - echo 'unknown'; - break; - - } - - return ob_get_clean(); - - } - -} diff --git a/src/serverlib/3rdparty/sabre/DAV/Browser/PropFindAll.php b/src/serverlib/3rdparty/sabre/DAV/Browser/PropFindAll.php deleted file mode 100644 index 3aa29b6..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Browser/PropFindAll.php +++ /dev/null @@ -1,132 +0,0 @@ -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/src/serverlib/3rdparty/sabre/DAV/Browser/assets/sabredav.css b/src/serverlib/3rdparty/sabre/DAV/Browser/assets/sabredav.css deleted file mode 100644 index f758e5a..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Browser/assets/sabredav.css +++ /dev/null @@ -1,222 +0,0 @@ -/* 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; -} -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; -} - -.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/src/serverlib/3rdparty/sabre/DAV/Client.php b/src/serverlib/3rdparty/sabre/DAV/Client.php deleted file mode 100644 index b9e650c..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Client.php +++ /dev/null @@ -1,474 +0,0 @@ -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; - } - } 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->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 - * - * @deprecated This method will be removed in the future, use - * addCurlSetting instead. - * @param string $certificates - * @return void - */ - function addTrustedCertificates($certificates) { - $this->addCurlSetting(CURLOPT_CAINFO, $certificates); - } - - /** - * Enables/disables SSL peer verification - * - * @deprecated This method will be removed in the future, use - * addCurlSetting instead. - * @param bool $value - * @return void - */ - function setVerifyPeer($value) { - $this->addCurlSetting(CURLOPT_SSL_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 - */ - 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 - ) = XMLUtil::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 Exception('HTTP error: ' . $response->getStatus()); - } - - $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 void - */ - function propPatch($url, array $properties) { - - $dom = new \DOMDocument('1.0', 'UTF-8'); - $dom->formatOutput = true; - $root = $dom->createElementNS('DAV:', 'd:propertyupdate'); - - foreach($properties as $propName => $propValue) { - - list( - $namespace, - $elementName - ) = XMLUtil::parseClarkNotation($propName); - - if ($propValue === null) { - - $remove = $dom->createElement('d:remove'); - $prop = $dom->createElement('d:prop'); - - if ($namespace === 'DAV:') { - $element = $dom->createElement('d:'.$elementName); - } else { - $element = $dom->createElementNS($namespace, 'x:'.$elementName); - } - - $root->appendChild( $remove )->appendChild( $prop )->appendChild( $element ); - - } else { - - $set = $dom->createElement('d:set'); - $prop = $dom->createElement('d:prop'); - - if ($namespace === 'DAV:') { - $element = $dom->createElement('d:'.$elementName); - } else { - $element = $dom->createElementNS($namespace, 'x:'.$elementName); - } - - if ( $propValue instanceof Property ) { - $propValue->serialize( new Server, $element ); - } else { - $element->nodeValue = htmlspecialchars($propValue, ENT_NOQUOTES, 'UTF-8'); - } - - $root->appendChild( $set )->appendChild( $prop )->appendChild( $element ); - - } - - } - - $dom->appendChild($root); - $body = $dom->saveXML(); - - $url = $this->getAbsoluteUrl($url); - $request = new HTTP\Request('PROPPATCH', $url, [ - 'Content-Type' => 'application/xml', - ], $body); - $this->send($request); - } - - /** - * 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) { - - try { - $dom = XMLUtil::loadDOMDocument($body); - } catch (Exception\BadRequest $e) { - throw new \InvalidArgumentException('The body passed to parseMultiStatus could not be parsed. Is it really xml?'); - } - - $responses = Property\ResponseList::unserialize( - $dom->documentElement, - $this->propertyMap - ); - - $result = []; - - foreach($responses->getResponses() as $response) { - - $result[$response->getHref()] = $response->getResponseProperties(); - - } - - return $result; - - } - -} diff --git a/src/serverlib/3rdparty/sabre/DAV/Collection.php b/src/serverlib/3rdparty/sabre/DAV/Collection.php deleted file mode 100644 index 86df413..0000000 --- a/src/serverlib/3rdparty/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 - */ - 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/src/serverlib/3rdparty/sabre/DAV/CorePlugin.php b/src/serverlib/3rdparty/sabre/DAV/CorePlugin.php deleted file mode 100644 index c23ae70..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/CorePlugin.php +++ /dev/null @@ -1,899 +0,0 @@ -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,0); - - 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; - - } - - // New read/write stream - $newStream = fopen('php://temp','r+'); - - // fseek will return 0 only if $streem is seekable (and -1 otherwise) - // for a seekable $body stream we set the pointer write before copying it - // for a non-seekable $body stream we set the pointer on the copy - if ((fseek($body, $start, SEEK_SET)) === 0) { - stream_copy_to_stream($body, $newStream, $end - $start + 1, $start); - rewind($newStream); - } else { - stream_copy_to_stream($body, $newStream, $end + 1); - fseek($newStream,$start, SEEK_SET); - } - - $response->setHeader('Content-Length', $end - $start + 1); - $response->setHeader('Content-Range','bytes ' . $start . '-' . $end . '/' . $nodeSize); - $response->setStatus(206); - $response->setBody($newStream); - - } 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); - $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(); - - $requestedProperties = $this->server->parsePropFindRequest( - $request->getBodyAsString() - ); - - $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, $requestedProperties, $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(); - - $newProperties = $this->server->parsePropPatchRequest( - $request->getBodyAsString() - ); - - $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'); - - } - - $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 = []; - foreach($dom->firstChild->childNodes as $childNode) { - - if (XMLUtil::toClarkNotation($childNode)!=='{DAV:}set') continue; - $properties = array_merge($properties, XMLUtil::parseProperties($childNode, $this->server->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 = []; - $resourceType = ['{DAV:}collection']; - - } - - $result = $this->server->createCollection($path, $resourceType, $properties); - - 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 ($copyInfo['destinationExists']) { - if (!$this->server->emit('beforeUnbind', [$copyInfo['destination']])) return false; - $this->server->tree->delete($copyInfo['destination']); - - } - if (!$this->server->emit('beforeBind', [$copyInfo['destination']])) return false; - $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(); - - $body = $request->getBodyAsString(); - $dom = XMLUtil::loadDOMDocument($body); - - $reportName = XMLUtil::toClarkNotation($dom->firstChild); - - if ($this->server->emit('report', [$reportName, $dom, $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 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 Property\SupportedReportSet($reports); - }); - $propFind->handle('{DAV:}resourcetype', function() use ($node) { - return new Property\ResourceType($this->server->getResourceTypeForNode($node)); - }); - $propFind->handle('{DAV:}supported-method-set', function() use ($propFind) { - return new 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($nodeProperties as $propertyName=>$value) { - $propFind->set($propertyName, $value, 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 Property\IHref) { - 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 Property\IHref) { - return substr($val->getHref(), strlen(Sync\Plugin::SYNCTOKEN_PREFIX)); - } - } - - }); - - } -} diff --git a/src/serverlib/3rdparty/sabre/DAV/Exception.php b/src/serverlib/3rdparty/sabre/DAV/Exception.php deleted file mode 100644 index 54c69b3..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Exception.php +++ /dev/null @@ -1,64 +0,0 @@ -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/src/serverlib/3rdparty/sabre/DAV/Exception/FileNotFound.php b/src/serverlib/3rdparty/sabre/DAV/Exception/FileNotFound.php deleted file mode 100644 index 07c3937..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Exception/FileNotFound.php +++ /dev/null @@ -1,19 +0,0 @@ -ownerDocument->createElementNS('DAV:','d:valid-resourcetype'); - $errorNode->appendChild($error); - - } - -} diff --git a/src/serverlib/3rdparty/sabre/DAV/Exception/InvalidSyncToken.php b/src/serverlib/3rdparty/sabre/DAV/Exception/InvalidSyncToken.php deleted file mode 100644 index c184335..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Exception/InvalidSyncToken.php +++ /dev/null @@ -1,38 +0,0 @@ -ownerDocument->createElementNS('DAV:','d:valid-sync-token'); - $errorNode->appendChild($error); - - } - -} diff --git a/src/serverlib/3rdparty/sabre/DAV/Exception/LockTokenMatchesRequestUri.php b/src/serverlib/3rdparty/sabre/DAV/Exception/LockTokenMatchesRequestUri.php deleted file mode 100644 index 2996d92..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Exception/LockTokenMatchesRequestUri.php +++ /dev/null @@ -1,41 +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 - */ - function serialize(DAV\Server $server,\DOMElement $errorNode) { - - $error = $errorNode->ownerDocument->createElementNS('DAV:','d:lock-token-matches-request-uri'); - $errorNode->appendChild($error); - - } - -} diff --git a/src/serverlib/3rdparty/sabre/DAV/Exception/Locked.php b/src/serverlib/3rdparty/sabre/DAV/Exception/Locked.php deleted file mode 100644 index 8016d14..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Exception/Locked.php +++ /dev/null @@ -1,73 +0,0 @@ -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/src/serverlib/3rdparty/sabre/DAV/Exception/MethodNotAllowed.php b/src/serverlib/3rdparty/sabre/DAV/Exception/MethodNotAllowed.php deleted file mode 100644 index 5c7d807..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Exception/MethodNotAllowed.php +++ /dev/null @@ -1,45 +0,0 @@ -getAllowedMethods($server->getRequestUri()); - - return [ - 'Allow' => strtoupper(implode(', ',$methods)), - ]; - - } - -} diff --git a/src/serverlib/3rdparty/sabre/DAV/Exception/NotFound.php b/src/serverlib/3rdparty/sabre/DAV/Exception/NotFound.php deleted file mode 100644 index f79533e..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Exception/NotFound.php +++ /dev/null @@ -1,28 +0,0 @@ -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/src/serverlib/3rdparty/sabre/DAV/Exception/ReportNotSupported.php b/src/serverlib/3rdparty/sabre/DAV/Exception/ReportNotSupported.php deleted file mode 100644 index 8d31a08..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Exception/ReportNotSupported.php +++ /dev/null @@ -1,32 +0,0 @@ -ownerDocument->createElementNS('DAV:','d:supported-report'); - $errorNode->appendChild($error); - - } - -} diff --git a/src/serverlib/3rdparty/sabre/DAV/Exception/RequestedRangeNotSatisfiable.php b/src/serverlib/3rdparty/sabre/DAV/Exception/RequestedRangeNotSatisfiable.php deleted file mode 100644 index 5ba9493..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Exception/RequestedRangeNotSatisfiable.php +++ /dev/null @@ -1,31 +0,0 @@ -ownerDocument->createElementNS('DAV:','d:number-of-matches-within-limits'); - $errorNode->appendChild($error); - - } - -} diff --git a/src/serverlib/3rdparty/sabre/DAV/Exception/UnsupportedMediaType.php b/src/serverlib/3rdparty/sabre/DAV/Exception/UnsupportedMediaType.php deleted file mode 100644 index ce07435..0000000 --- a/src/serverlib/3rdparty/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 - */ - 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 - */ - 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[] - */ - function getChildren() { - - $nodes = []; - 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 - */ - 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/src/serverlib/3rdparty/sabre/DAV/FS/File.php b/src/serverlib/3rdparty/sabre/DAV/FS/File.php deleted file mode 100644 index 87f838d..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/FS/File.php +++ /dev/null @@ -1,91 +0,0 @@ -path,$data); - - } - - /** - * Returns the data - * - * @return string - */ - 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 null; - - } - - /** - * 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/src/serverlib/3rdparty/sabre/DAV/FS/Node.php b/src/serverlib/3rdparty/sabre/DAV/FS/Node.php deleted file mode 100644 index bebbb82..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/FS/Node.php +++ /dev/null @@ -1,84 +0,0 @@ -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/src/serverlib/3rdparty/sabre/DAV/FSExt/Directory.php b/src/serverlib/3rdparty/sabre/DAV/FSExt/Directory.php deleted file mode 100644 index 5f84f58..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/FSExt/Directory.php +++ /dev/null @@ -1,196 +0,0 @@ -path . '/' . $name; - file_put_contents($newPath,$data); - - return '"' . md5_file($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); - - } - - /** - * 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 Directory($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 = []; - 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 - */ - 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 - */ - function getQuotaInfo() { - - return [ - disk_total_space($this->path)-disk_free_space($this->path), - disk_free_space($this->path) - ]; - - } - - /** - * 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 Node) { - 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/src/serverlib/3rdparty/sabre/DAV/FSExt/File.php b/src/serverlib/3rdparty/sabre/DAV/FSExt/File.php deleted file mode 100644 index ef280d3..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/FSExt/File.php +++ /dev/null @@ -1,143 +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 - */ - 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 - */ - function get() { - - return fopen($this->path,'r'); - - } - - /** - * Delete the current file - * - * @return bool - */ - function delete() { - return unlink($this->path) && 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 - */ - 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 - */ - function getContentType() { - - return null; - - } - - /** - * Returns the size of the file, in bytes - * - * @return int - */ - function getSize() { - - return filesize($this->path); - - } - -} diff --git a/src/serverlib/3rdparty/sabre/DAV/FSExt/Node.php b/src/serverlib/3rdparty/sabre/DAV/FSExt/Node.php deleted file mode 100644 index 30aac82..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/FSExt/Node.php +++ /dev/null @@ -1,226 +0,0 @@ -handleRemaining(function(array $properties) { - - $resourceData = $this->getResourceData(); - foreach($properties as $propertyName=>$propertyValue) { - - // If it was null, we need to delete the property - if (is_null($propertyValue)) { - 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 = []; - 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) = 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 ['properties' => []]; - - // 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 - flock($handle,LOCK_UN); - fclose($handle); - - // Unserializing and checking if the resource file contains data for this file - $data = unserialize($data); - if (!isset($data[$this->getName()])) { - return ['properties' => []]; - } - - $data = $data[$this->getName()]; - if (!isset($data['properties'])) $data['properties'] = []; - 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)); - flock($handle,LOCK_UN); - fclose($handle); - - } - - /** - * 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; - - // 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); - - - } - - /** - * Deletes the resource information. - * @return bool - */ - protected 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); - unset($data[$this->getName()]); - ftruncate($handle,0); - rewind($handle); - fwrite($handle,serialize($data)); - flock($handle,LOCK_UN); - fclose($handle); - - return true; - } - - function delete() { - - return $this->deleteResourceData(); - - } - -} diff --git a/src/serverlib/3rdparty/sabre/DAV/File.php b/src/serverlib/3rdparty/sabre/DAV/File.php deleted file mode 100644 index 74ae4a5..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/File.php +++ /dev/null @@ -1,85 +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 - */ - function getLocks($uri, $returnChildLocks) { - - $lockList = []; - $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 - */ - 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 - */ - 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 []; - - // 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 - 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; - - } - - /** - * 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)); - flock($handle,LOCK_UN); - fclose($handle); - - } - -} diff --git a/src/serverlib/3rdparty/sabre/DAV/Locks/Backend/File.php b/src/serverlib/3rdparty/sabre/DAV/Locks/Backend/File.php deleted file mode 100644 index daaa802..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Locks/Backend/File.php +++ /dev/null @@ -1,185 +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 - */ - 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/src/serverlib/3rdparty/sabre/DAV/Locks/Backend/PDO.php b/src/serverlib/3rdparty/sabre/DAV/Locks/Backend/PDO.php deleted file mode 100644 index 7e51b5a..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Locks/Backend/PDO.php +++ /dev/null @@ -1,185 +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 - */ - 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 = [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/src/serverlib/3rdparty/sabre/DAV/Locks/LockInfo.php b/src/serverlib/3rdparty/sabre/DAV/Locks/LockInfo.php deleted file mode 100644 index 5424022..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Locks/LockInfo.php +++ /dev/null @@ -1,81 +0,0 @@ -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 = null) { - - $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; - $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\Property\SupportedLock(!!$this->locksBackend); - }); - $propFind->handle('{DAV:}lockdiscovery', function() use ($propFind) { - return new DAV\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) { - - if ($this->locksBackend) - return ['LOCK','UNLOCK']; - - return []; - - } - - /** - * 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) { - - $lockList = []; - - 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 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; - - 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 - */ - function unlockNode($uri, LockInfo $lockInfo) { - - if (!$this->server->emit('beforeUnlock', [$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 - */ - function getTimeoutHeader() { - - $header = $this->server->httpRequest->getHeader('Timeout'); - - if ($header) { - - if (stripos($header,'second-')===0) $header = (int)(substr($header,7)); - else if (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) { - - $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([$lockInfo]); - $lockObj->serialize($this->server,$lockDiscovery); - - return $dom->saveXML(); - - } - - /** - * 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 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) { - - // 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/src/serverlib/3rdparty/sabre/DAV/Mount/Plugin.php b/src/serverlib/3rdparty/sabre/DAV/Mount/Plugin.php deleted file mode 100644 index 6fdf949..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Mount/Plugin.php +++ /dev/null @@ -1,86 +0,0 @@ -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 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/src/serverlib/3rdparty/sabre/DAV/Node.php b/src/serverlib/3rdparty/sabre/DAV/Node.php deleted file mode 100644 index 165b4f3..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Node.php +++ /dev/null @@ -1,55 +0,0 @@ -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 IFile || $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 IFile && !$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(); - - - 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->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]; - } elseif ($matches[4]) { - return [self::RANGE_END, $matches[4]]; - } else { - return null; - } - - } -} diff --git a/src/serverlib/3rdparty/sabre/DAV/PropFind.php b/src/serverlib/3rdparty/sabre/DAV/PropFind.php deleted file mode 100644 index 9f9dae2..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/PropFind.php +++ /dev/null @@ -1,336 +0,0 @@ -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 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/src/serverlib/3rdparty/sabre/DAV/PropPatch.php b/src/serverlib/3rdparty/sabre/DAV/PropPatch.php deleted file mode 100644 index 9a1faf7..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/PropPatch.php +++ /dev/null @@ -1,344 +0,0 @@ -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. - * - * @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. - * - * @return array - */ - function getRemainingMutations() { - - $remaining = []; - foreach($this->mutations as $propertyName => $propValue) { - if (!isset($this->result[$propertyName])) { - $remaining[] = $propertyName; - } - } - - 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/src/serverlib/3rdparty/sabre/DAV/Property.php b/src/serverlib/3rdparty/sabre/DAV/Property.php deleted file mode 100644 index ce28066..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Property.php +++ /dev/null @@ -1,32 +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 - */ - function serialize(DAV\Server $server, \DOMElement $prop) { - - //$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 - */ - function getTime() { - - return $this->time; - - } - -} - diff --git a/src/serverlib/3rdparty/sabre/DAV/Property/Href.php b/src/serverlib/3rdparty/sabre/DAV/Property/Href.php deleted file mode 100644 index 84fb818..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Property/Href.php +++ /dev/null @@ -1,100 +0,0 @@ -href = $href; - $this->autoPrefix = $autoPrefix; - - } - - /** - * Returns the uri - * - * @return string - */ - 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 - */ - 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 - * @param array $propertyMap - * @return DAV\Property\Href - */ - static function unserialize(\DOMElement $dom, array $propertyMap) { - - if ($dom->firstChild && DAV\XMLUtil::toClarkNotation($dom->firstChild)==='{DAV:}href') { - return new self($dom->firstChild->textContent,false); - } - - } - -} diff --git a/src/serverlib/3rdparty/sabre/DAV/Property/HrefList.php b/src/serverlib/3rdparty/sabre/DAV/Property/HrefList.php deleted file mode 100644 index d8c5ae2..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Property/HrefList.php +++ /dev/null @@ -1,106 +0,0 @@ -hrefs = $hrefs; - $this->autoPrefix = $autoPrefix; - - } - - /** - * Returns the uris - * - * @return array - */ - 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 - */ - 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 - * @param array $propertyMap - * @return DAV\Property\HrefList - */ - static function unserialize(\DOMElement $dom, array $propertyMap) { - - $hrefs = []; - foreach($dom->childNodes as $child) { - if (DAV\XMLUtil::toClarkNotation($child)==='{DAV:}href') { - $hrefs[] = $child->textContent; - } - } - return new self($hrefs, false); - - } - -} diff --git a/src/serverlib/3rdparty/sabre/DAV/Property/IHref.php b/src/serverlib/3rdparty/sabre/DAV/Property/IHref.php deleted file mode 100644 index 476e404..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Property/IHref.php +++ /dev/null @@ -1,25 +0,0 @@ -locks = $locks; - - } - - /** - * serialize - * - * @param DAV\Server $server - * @param \DOMElement $prop - * @return void - */ - 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)); - - $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/src/serverlib/3rdparty/sabre/DAV/Property/ResourceType.php b/src/serverlib/3rdparty/sabre/DAV/Property/ResourceType.php deleted file mode 100644 index 3b8db2d..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Property/ResourceType.php +++ /dev/null @@ -1,124 +0,0 @@ -resourceType = $resourceType; - else - $this->resourceType = [$resourceType]; - - } - - /** - * serialize - * - * @param DAV\Server $server - * @param \DOMElement $prop - * @return void - */ - 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 ['{DAV:}collection'] - * - * @return array - */ - function getValue() { - - return $this->resourceType; - - } - - /** - * Checks if the principal contains a certain value - * - * @param string $type - * @return bool - */ - function is($type) { - - return in_array($type, $this->resourceType); - - } - - /** - * Adds a resourcetype value to this property - * - * @param string $type - * @return void - */ - function add($type) { - - $this->resourceType[] = $type; - $this->resourceType = array_unique($this->resourceType); - - } - - /** - * Unserializes a DOM element into a ResourceType property. - * - * @param \DOMElement $dom - * @param array $propertyMap - * @return DAV\Property\ResourceType - */ - static function unserialize(\DOMElement $dom, array $propertyMap) { - - $value = []; - foreach($dom->childNodes as $child) { - - $value[] = DAV\XMLUtil::toClarkNotation($child); - - } - - return new self($value); - - } - -} diff --git a/src/serverlib/3rdparty/sabre/DAV/Property/Response.php b/src/serverlib/3rdparty/sabre/DAV/Property/Response.php deleted file mode 100644 index 1003bd1..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Property/Response.php +++ /dev/null @@ -1,222 +0,0 @@ -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; - - } - - /** - * serialize - * - * @param DAV\Server $server - * @param \DOMElement $dom - * @return void - */ - function serialize(DAV\Server $server, \DOMElement $dom) { - - $document = $dom->ownerDocument; - $properties = $this->responseProperties; - - $xresponse = $document->createElement('d:response'); - $dom->appendChild($xresponse); - - $uri = URLUtil::encodePath($this->href); - - if ($uri==='/') $uri = ''; - - // Adding the baseurl to the beginning of the url - $uri = $server->getBaseUri() . $uri; - - $xresponse->appendChild($document->createElement('d:href',$uri)); - - if ($this->httpStatus) { - $statusString = "HTTP/1.1 " . $this->httpStatus . " " . HTTP\Response::$statusCodes[$this->httpStatus]; - $xresponse->appendChild($document->createElement('d:status', $statusString)); - } - - // 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)) { - if ($propertyValue!=='') { // we want a self-closing xml element for empty strings. - $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','HTTP/1.1 ' . $httpStatus . ' ' . HTTP\Response::$statusCodes[$httpStatus])); - - } - - } - - /** - * Unserializes the property. - * - * This static method should return a an instance of this object. - * - * @param \DOMElement $prop - * @param array $propertyMap - * @return DAV\IProperty - */ - static function unserialize(\DOMElement $prop, array $propertyMap) { - - // Delegating this to the ResponseList property. It does make more - // sense there. - - $result = ResponseList::unserialize($prop, $propertyMap); - $result = $result->getResponses(); - - return $result[0]; - - } - -} diff --git a/src/serverlib/3rdparty/sabre/DAV/Property/ResponseList.php b/src/serverlib/3rdparty/sabre/DAV/Property/ResponseList.php deleted file mode 100644 index 4c6ee07..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Property/ResponseList.php +++ /dev/null @@ -1,134 +0,0 @@ -responses = $responses; - - } - - /** - * Returns the list of Response properties. - * - * @return Response[] - */ - function getResponses() { - - return $this->responses; - - } - - /** - * serialize - * - * @param DAV\Server $server - * @param \DOMElement $dom - * @return void - */ - function serialize(DAV\Server $server,\DOMElement $dom) { - - foreach($this->responses as $response) { - $response->serialize($server, $dom); - } - - } - - /** - * Unserializes the property. - * - * This static method should return a an instance of this object. - * - * @param \DOMElement $prop - * @param array $propertyMap - * @return DAV\IProperty - */ - static function unserialize(\DOMElement $prop, array $propertyMap) { - - $xpath = new \DOMXPath( $prop->ownerDocument ); - $xpath->registerNamespace('d','urn:DAV'); - - // Finding the 'response' element - $xResponses = $xpath->evaluate( - 'd:response', - $prop - ); - - $result = []; - - for($jj=0; $jj < $xResponses->length; $jj++) { - - $xResponse = $xResponses->item($jj); - - // Parsing 'href' - $href = Href::unserialize($xResponse, $propertyMap); - - $properties = []; - - // Parsing 'status' in 'd:response' - $responseStatus = $xpath->evaluate('string(d:status)', $xResponse); - if ($responseStatus) { - list(, $responseStatus,) = explode(' ', $responseStatus, 3); - } - - - // Parsing 'propstat' - $xPropstat = $xpath->query('d:propstat', $xResponse); - - for($ii=0; $ii < $xPropstat->length; $ii++) { - - // Parsing 'status' - $status = $xpath->evaluate('string(d:status)', $xPropstat->item($ii)); - - list(,$statusCode,) = explode(' ', $status, 3); - - $usedPropertyMap = $statusCode == '200' ? $propertyMap : []; - - // Parsing 'prop' - $properties[$statusCode] = DAV\XMLUtil::parseProperties($xPropstat->item($ii), $usedPropertyMap); - - } - - $result[] = new Response($href->getHref(), $properties, $responseStatus?$responseStatus:null); - - } - - return new self($result); - - } - - -} diff --git a/src/serverlib/3rdparty/sabre/DAV/Property/SupportedLock.php b/src/serverlib/3rdparty/sabre/DAV/Property/SupportedLock.php deleted file mode 100644 index 9eb9dd0..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Property/SupportedLock.php +++ /dev/null @@ -1,78 +0,0 @@ -supportsLocks = $supportsLocks; - - } - - /** - * serialize - * - * @param DAV\Server $server - * @param \DOMElement $prop - * @return void - */ - 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/src/serverlib/3rdparty/sabre/DAV/Property/SupportedMethodSet.php b/src/serverlib/3rdparty/sabre/DAV/Property/SupportedMethodSet.php deleted file mode 100644 index fbf9bbe..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Property/SupportedMethodSet.php +++ /dev/null @@ -1,68 +0,0 @@ -methods = $method; - - } - - /** - * Returns the list of supported methods. - * - * @return string[] - */ - function getValue() { - - return $this->methods; - - } - - /** - * Serializes the node - * - * @param DAV\Server $server - * @param \DOMElement $prop - * @return void - */ - function serialize(DAV\Server $server, \DOMElement $prop) { - - foreach($this->methods as $method) { - - $supportedMethod = $prop->ownerDocument->createElement('d:supported-method'); - $supportedMethod->setAttribute('name', $method); - $prop->appendChild($supportedMethod); - - } - - } - -} diff --git a/src/serverlib/3rdparty/sabre/DAV/Property/SupportedReportSet.php b/src/serverlib/3rdparty/sabre/DAV/Property/SupportedReportSet.php deleted file mode 100644 index dfa5c4b..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Property/SupportedReportSet.php +++ /dev/null @@ -1,126 +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 - */ - function addReport($report) { - - if (!is_array($report)) $report = [$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 - */ - 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 - ); - - } - - /** - * Serializes the node - * - * @param DAV\Server $server - * @param \DOMElement $prop - * @return void - */ - 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/src/serverlib/3rdparty/sabre/DAV/PropertyInterface.php b/src/serverlib/3rdparty/sabre/DAV/PropertyInterface.php deleted file mode 100644 index 727ac13..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/PropertyInterface.php +++ /dev/null @@ -1,39 +0,0 @@ -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. - * - * @param string $path - * @param PropFind $propFind - * @return void - */ - function propFind($path, PropFind $propFind) { - - $propertyNames = $propFind->get404Properties(); - if (!$propertyNames) { - return; - } - - $query = 'SELECT name, value FROM propertystorage WHERE path = ?'; - $stmt = $this->pdo->prepare($query); - $stmt->execute([$path]); - - while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { - $propFind->set($row['name'], $row['value']); - } - - } - - /** - * 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 propertystorage (path, name, value) VALUES (?, ?, ?)"); - $deleteStmt = $this->pdo->prepare("DELETE FROM propertystorage WHERE path = ? AND name = ?"); - - foreach($properties as $name=>$value) { - - if (!is_null($value)) { - $updateStmt->execute([$path, $name, $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 propertystorage 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 propertystorage WHERE path = ? OR path LIKE ?'); - $select->execute([$source, $source . '/%']); - - $update = $this->pdo->prepare('UPDATE propertystorage 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/src/serverlib/3rdparty/sabre/DAV/PropertyStorage/Plugin.php b/src/serverlib/3rdparty/sabre/DAV/PropertyStorage/Plugin.php deleted file mode 100644 index 1b77e89..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/PropertyStorage/Plugin.php +++ /dev/null @@ -1,145 +0,0 @@ -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); - - } -} diff --git a/src/serverlib/3rdparty/sabre/DAV/Server.php b/src/serverlib/3rdparty/sabre/DAV/Server.php deleted file mode 100644 index 4b4b86f..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Server.php +++ /dev/null @@ -1,1741 +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 = [ - '{DAV:}resourcetype' => 'Sabre\\DAV\\Property\\ResourceType', - ]; - - public $protectedProperties = [ - // 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', - - // RFC3253 - '{DAV:}supported-method-set', - '{DAV:}supported-report-set', - - // RFC6578 - '{DAV:}sync-token', - - // calendarserver.org extensions - '{http://calendarserver.org/ns/}ctag', - - // sabredav extensions - '{http://sabredav.org/ns}sync-token', - - ]; - - /** - * 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 automatically added for nodes - * implementing Sabre\DAV\ICollection. - * - * @var array - */ - public $resourceTypeMapping = [ - 'Sabre\\DAV\\ICollection' => '{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; - - /** - * 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\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->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]; - - // 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 - */ - function getPlugins() { - - return $this->plugins; - - } - - /** - * Handles a http request, and execute a method based on its name - * - * @param RequestInterface $request - * @param ResponseInterface $response - * @return void - */ - function invokeMethod(RequestInterface $request, ResponseInterface $response) { - - $method = $request->getMethod(); - - if (!$this->emit('beforeMethod:' . $method, [$request, $response])) return; - if (!$this->emit('beforeMethod', [$request, $response])) return; - - if (Server::$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])) { - // Unsupported method - throw new Exception\NotImplemented('There was no handler found for this "' . $method . '" method'); - } - } - - if (!$this->emit('afterMethod:' . $method, [$request, $response])) return; - if (!$this->emit('afterMethod', [$request, $response])) return; - - $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()); - - } - - /** - * 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 - */ - 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 - */ - 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 = [ - '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; - - } - - } - - } elseif ($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) - * - * @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. - * - * @param string $path - * @param array $propertyNames - */ - 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 - */ - 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 Property\GetLastModified) { - $headers[$header] = HTTP\Util::toHTTPDate($properties[$property]->getTime()); - } - - } - - return $headers; - - } - - /** - * Small helper to support PROPFIND with DEPTH_INFINITY. - */ - 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); - $subPath = $path !== ''? $path . '/' . $childNode->getName() : $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, $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,['{DAV:}collection'], []); - - } - - /** - * 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 - */ - 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->emit('beforeBind',[$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->emit('beforeUnbind',[$uri])) return; - $this->tree->delete($uri); - - // Re-throwing exception - if ($exception) throw $exception; - - // Re-arranging the result so it makes sense for - // generateMultiStatus. - $newResult = [ - 'href' => $uri, - ]; - foreach($errorResult as $property=>$code) { - if (!isset($newResult[$code])) { - $newResult[$code] = [$property => null]; - } else { - $newResult[$code][$property] = null; - } - } - return $newResult; - } - - } - $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->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) { - 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->getETag(); - - 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"' - * ] - * ] - * ], - * ], - * ] - * - * @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) { - - $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 - */ - 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 = []; - - 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 - */ - function parsePropFindRequest($body) { - - // If the propfind body was empty, it means IE is requesting 'all' properties - if (!$body) return []; - - $dom = XMLUtil::loadDOMDocument($body); - $elem = $dom->getElementsByTagNameNS('urn:DAV','propfind')->item(0); - if (is_null($elem)) throw new Exception\UnsupportedMediaType('We could not find a {DAV:}propfind element in the xml request body'); - - return array_keys(XMLUtil::parseProperties($elem)); - - } - - // }}} - -} diff --git a/src/serverlib/3rdparty/sabre/DAV/ServerPlugin.php b/src/serverlib/3rdparty/sabre/DAV/ServerPlugin.php deleted file mode 100644 index b9fe481..0000000 --- a/src/serverlib/3rdparty/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 - */ - 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/src/serverlib/3rdparty/sabre/DAV/SimpleFile.php b/src/serverlib/3rdparty/sabre/DAV/SimpleFile.php deleted file mode 100644 index 54180e2..0000000 --- a/src/serverlib/3rdparty/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 - */ - 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 '"' . md5($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/src/serverlib/3rdparty/sabre/DAV/StringUtil.php b/src/serverlib/3rdparty/sabre/DAV/StringUtil.php deleted file mode 100644 index c8ad896..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/StringUtil.php +++ /dev/null @@ -1,91 +0,0 @@ - '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/src/serverlib/3rdparty/sabre/DAV/Sync/Plugin.php b/src/serverlib/3rdparty/sabre/DAV/Sync/Plugin.php deleted file mode 100644 index 8f162e8..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Sync/Plugin.php +++ /dev/null @@ -1,337 +0,0 @@ -server = $server; - - $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 \DOMDocument $dom - * @return void - */ - function syncCollection($uri, \DOMDocument $dom) { - - // rfc3253 specifies 0 is the default value for Depth: - $depth = $this->server->getHTTPDepth(0); - - list( - $syncToken, - $syncLevel, - $limit, - $properties - ) = $this->parseSyncCollectionRequest($dom, $depth); - - // 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'); - } - - 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, $syncLevel, $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'], - $properties - ); - - } - - /** - * Parses the {DAV:}sync-collection REPORT request body. - * - * This method returns an array with 3 values: - * 0 - the value of the {DAV:}sync-token element - * 1 - the value of the {DAV:}sync-level element - * 2 - The value of the {DAV:}limit element - * 3 - A list of requested properties - * - * @param \DOMDocument $dom - * @param int $depth - * @return void - */ - protected function parseSyncCollectionRequest(\DOMDocument $dom, $depth) { - - $xpath = new \DOMXPath($dom); - $xpath->registerNamespace('d','urn:DAV'); - - $syncToken = $xpath->query("//d:sync-token"); - if ($syncToken->length !== 1) { - throw new DAV\Exception\BadRequest('You must specify a {DAV:}sync-token element, and it must appear exactly once'); - } - $syncToken = $syncToken->item(0)->nodeValue; - // Initial sync - if (!$syncToken) $syncToken = null; - - $syncLevel = $xpath->query("//d:sync-level"); - if ($syncLevel->length === 0) { - // In case there was no sync-level, it could mean that we're dealing - // with an old client. For these we must use the depth header - // instead. - $syncLevel = $depth; - } else { - $syncLevel = $syncLevel->item(0)->nodeValue; - if ($syncLevel === 'infinite') { - $syncLevel = DAV\Server::DEPTH_INFINITY; - } - - } - $limit = $xpath->query("//d:limit/d:nresults"); - if ($limit->length === 0) { - $limit = null; - } else { - $limit = $limit->item(0)->nodeValue; - } - - $prop = $xpath->query('d:prop'); - if ($prop->length !== 1) { - throw new DAV\Exception\BadRequest('The {DAV:}sync-collection must contain extactly 1 {DAV:}prop'); - } - - $properties = array_keys( - DAV\XMLUtil::parseProperties($dom->documentElement) - ); - - return [ - $syncToken, - $syncLevel, - $limit, - $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) { - - $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); - - } - - $fullPaths = []; - - // Pre-fetching children, if this is possible. - foreach(array_merge($added, $modified) as $item) { - $fullPath = $collectionUrl . '/' . $item; - $fullPaths[] = $fullPath; - } - - foreach($this->server->getPropertiesForMultiplePaths($fullPaths, $properties) as $fullPath => $props) { - - // The 'Property_Response' class is responsible for generating a - // single {DAV:}response xml element. - $response = new DAV\Property\Response($fullPath, $props); - $response->serialize($this->server, $multiStatus); - - } - - // 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; - $response = new DAV\Property\Response($fullPath, [], 404); - $response->serialize($this->server, $multiStatus); - - } - - $syncToken = $dom->createElement('d:sync-token', self::SYNCTOKEN_PREFIX . $syncToken); - $multiStatus->appendChild($syncToken); - - $this->server->httpResponse->setStatus(207); - $this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8'); - $this->server->httpResponse->setBody($dom->saveXML()); - - } - - /** - * 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 mixed $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; - } - - } - - } - - } - -} - diff --git a/src/serverlib/3rdparty/sabre/DAV/TemporaryFileFilterPlugin.php b/src/serverlib/3rdparty/sabre/DAV/TemporaryFileFilterPlugin.php deleted file mode 100644 index ae3d527..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/TemporaryFileFilterPlugin.php +++ /dev/null @@ -1,296 +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 - */ - 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 true; - - 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 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 - */ - 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 RequestInterface $request - * @param ResponseInterface $hR - * @param string $tempLocation - * @return bool - */ - function httpGet(RequestInterface $request, ResponseInterface $hR, $tempLocation) { - - if (!file_exists($tempLocation)) return true; - - $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 true; - - 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 true; - - $hR->setHeader('X-Sabre-Temp','true'); - $hR->setStatus(207); - $hR->setHeader('Content-Type','application/xml; charset=utf-8'); - - $this->server->parsePropFindRequest($request->getBodyAsString()); - - $properties = [ - 'href' => $request->getPath(), - 200 => [ - '{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([$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/src/serverlib/3rdparty/sabre/DAV/Tree.php b/src/serverlib/3rdparty/sabre/DAV/Tree.php deleted file mode 100644 index 059945d..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/Tree.php +++ /dev/null @@ -1,341 +0,0 @@ -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/src/serverlib/3rdparty/sabre/DAV/URLUtil.php b/src/serverlib/3rdparty/sabre/DAV/URLUtil.php deleted file mode 100644 index 43b17c0..0000000 --- a/src/serverlib/3rdparty/sabre/DAV/URLUtil.php +++ /dev/null @@ -1,22 +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 [ - $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 = []) { - - $propList = []; - 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([$propertyMap[$propertyName],'unserialize'],$propNodeData, $propertyMap); - } else { - $propList[$propertyName] = $propNodeData->textContent; - } - } - - - } - return $propList; - - } - -} diff --git a/src/serverlib/3rdparty/sabre/DAVACL/AbstractPrincipalCollection.php b/src/serverlib/3rdparty/sabre/DAVACL/AbstractPrincipalCollection.php deleted file mode 100644 index f6e01f8..0000000 --- a/src/serverlib/3rdparty/sabre/DAVACL/AbstractPrincipalCollection.php +++ /dev/null @@ -1,181 +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 - */ - 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 - * @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/src/serverlib/3rdparty/sabre/DAVACL/Exception/AceConflict.php b/src/serverlib/3rdparty/sabre/DAVACL/Exception/AceConflict.php deleted file mode 100644 index 9ff99a9..0000000 --- a/src/serverlib/3rdparty/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/src/serverlib/3rdparty/sabre/DAVACL/Exception/NeedPrivileges.php b/src/serverlib/3rdparty/sabre/DAVACL/Exception/NeedPrivileges.php deleted file mode 100644 index b460e56..0000000 --- a/src/serverlib/3rdparty/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 - */ - 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/src/serverlib/3rdparty/sabre/DAVACL/Exception/NoAbstract.php b/src/serverlib/3rdparty/sabre/DAVACL/Exception/NoAbstract.php deleted file mode 100644 index 005f124..0000000 --- a/src/serverlib/3rdparty/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/src/serverlib/3rdparty/sabre/DAVACL/Exception/NotRecognizedPrincipal.php b/src/serverlib/3rdparty/sabre/DAVACL/Exception/NotRecognizedPrincipal.php deleted file mode 100644 index 67adfc3..0000000 --- a/src/serverlib/3rdparty/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/src/serverlib/3rdparty/sabre/DAVACL/Exception/NotSupportedPrivilege.php b/src/serverlib/3rdparty/sabre/DAVACL/Exception/NotSupportedPrivilege.php deleted file mode 100644 index 49ae560..0000000 --- a/src/serverlib/3rdparty/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/src/serverlib/3rdparty/sabre/DAVACL/IACL.php b/src/serverlib/3rdparty/sabre/DAVACL/IACL.php deleted file mode 100644 index f34bc5b..0000000 --- a/src/serverlib/3rdparty/sabre/DAVACL/IACL.php +++ /dev/null @@ -1,74 +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 = []; - - /** - * 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 */ - - $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 - */ - 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); - - $flat = []; - $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 = [ - '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) { - - $this->getFPSTraverse($subPriv, $myPriv['concrete'], $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 url based on an email address. - * - * Note that wether or not this works may depend on wether a search - * facility is built into the server. - * - * This method returns false if the principal could not be found. - * - * @deprecated use getPrincipalByUri instead. - * @return string|bool - */ - function getPrincipalByEmail($email) { - - $result = $this->getPrincipalByUri('mailto:' . $email); - return $result?:false; - - } - - /** - * 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']); - - 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 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) = 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 - */ - function beforeUnbind($uri) { - - list($parentUri) = 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 - */ - 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\Property\HrefList($node->getAlternateUriSet()); - }); - $propFind->handle('{DAV:}principal-URL', function() use ($node) { - return new DAV\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\Property\HrefList($members); - }); - $propFind->handle('{DAV:}group-membership', function() use ($node) { - $members = $node->getGroupMembership(); - foreach($members as $k=>$member) { - $members[$k] = rtrim($member,'/') . '/'; - } - return new DAV\Property\HrefList($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\Property\HrefList($val); - - }); - $propFind->handle('{DAV:}current-user-principal', function() { - if ($url = $this->getCurrentUserPrincipal()) { - return new Property\Principal(Property\Principal::HREF, $url . '/'); - } else { - return new Property\Principal(Property\Principal::UNAUTHENTICATED); - } - }); - $propFind->handle('{DAV:}supported-privilege-set', function() use ($node) { - return new 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 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 Property\Acl($this->getACL($node)); - } - } - }); - $propFind->handle('{DAV:}acl-restrictions', function() { - return new Property\AclRestrictions(); - }); - - /* Adding ACL properties */ - if ($node instanceof IACL) { - $propFind->handle('{DAV:}owner', function() use ($node) { - return new DAV\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\Property\HrefList) { - $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 \DOMNode $dom - * @return bool - */ - function report($reportName, $dom) { - - switch($reportName) { - - case '{DAV:}principal-property-search' : - $this->server->transactionType = 'report-principal-property-search'; - $this->principalPropertySearchReport($dom); - return false; - case '{DAV:}principal-search-property-set' : - $this->server->transactionType = 'report-principal-search-property-set'; - $this->principalSearchPropertySetReport($dom); - return false; - case '{DAV:}expand-property' : - $this->server->transactionType = 'report-expand-property'; - $this->expandPropertyReport($dom); - 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(); - $dom = DAV\XMLUtil::loadDOMDocument($body); - - $newAcl = - Property\Acl::unserialize($dom->firstChild, $this->server->propertyMap) - ->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 \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->setStatus(207); - $this->server->httpResponse->setBody($xml); - - } - - /** - * This method is used by expandPropertyReport to parse - * out the entire HTTP request. - * - * @param \DOMElement $node - * @return array - */ - protected function parseExpandPropertyReportRequest($node) { - - $requestedProperties = []; - do { - - if (DAV\XMLUtil::toClarkNotation($node)!=='{DAV:}property') continue; - - if ($node->firstChild) { - - $children = $this->parseExpandPropertyReportRequest($node->firstChild); - - } else { - - $children = []; - - } - - $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 = []; - - 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 = [$node[200][$propertyName]->getHref()]; - } elseif ($node[200][$propertyName] instanceof DAV\Property\HrefList) { - $hrefs = $node[200][$propertyName]->getHrefs(); - } - - $childProps = []; - 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->setStatus(200); - $this->server->httpResponse->setBody($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, - $test - ) = $this->parsePrincipalPropertySearchReportRequest($dom); - - $uri = null; - if (!$applyToPrincipalCollectionSet) { - $uri = $this->server->getRequestUri(); - } - $result = $this->principalSearch($searchProperties, $requestedProperties, $uri, $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'])); - - } - - /** - * 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 = []; - - $applyToPrincipalCollectionSet = false; - - $test = $dom->firstChild->getAttribute('test') === 'anyof' ? 'anyof' : 'allof'; - - // 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 [ - $searchProperties, - array_keys(DAV\XMLUtil::parseProperties($dom->firstChild)), - $applyToPrincipalCollectionSet, - $test - ]; - - } - - - /* }}} */ - -} diff --git a/src/serverlib/3rdparty/sabre/DAVACL/Principal.php b/src/serverlib/3rdparty/sabre/DAVACL/Principal.php deleted file mode 100644 index 6db9ba5..0000000 --- a/src/serverlib/3rdparty/sabre/DAVACL/Principal.php +++ /dev/null @@ -1,291 +0,0 @@ -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/src/serverlib/3rdparty/sabre/DAVACL/PrincipalBackend/AbstractBackend.php b/src/serverlib/3rdparty/sabre/DAVACL/PrincipalBackend/AbstractBackend.php deleted file mode 100644 index 2e77ce6..0000000 --- a/src/serverlib/3rdparty/sabre/DAVACL/PrincipalBackend/AbstractBackend.php +++ /dev/null @@ -1,53 +0,0 @@ -searchPrincipals( - $principalPrefix, - ['{http://sabredav.org/ns}email-address' => substr($uri,7)] - ); - - if ($result) { - return $result[0]; - } - - } - -} diff --git a/src/serverlib/3rdparty/sabre/DAVACL/PrincipalBackend/PDO.php b/src/serverlib/3rdparty/sabre/DAVACL/PrincipalBackend/PDO.php deleted file mode 100644 index 64d8af2..0000000 --- a/src/serverlib/3rdparty/sabre/DAVACL/PrincipalBackend/PDO.php +++ /dev/null @@ -1,386 +0,0 @@ - [ - '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' => [ - 'dbField' => 'vcardurl', - ], - /** - * This is the users' primary email-address. - */ - '{http://sabredav.org/ns}email-address' =>[ - 'dbField' => 'email', - ], - ]; - - /** - * Sets up the backend. - * - * @param PDO $pdo - * @param string $tableName - * @param string $groupMembersTableName - * @deprecated We are removing the tableName arguments in a future version - * of sabredav. Use the public properties instead. - */ - 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 - */ - 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 \Sabre\DAV\PropPatch $propPatch - */ - function updatePrincipal($path, \Sabre\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') { - - $query = 'SELECT uri FROM ' . $this->tableName . ' WHERE 1=1 '; - $values = []; - 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 []; - - } - - } - $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; - - } - - /** - * 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]); - - } - - } - -} diff --git a/src/serverlib/3rdparty/sabre/DAVACL/PrincipalCollection.php b/src/serverlib/3rdparty/sabre/DAVACL/PrincipalCollection.php deleted file mode 100644 index d270865..0000000 --- a/src/serverlib/3rdparty/sabre/DAVACL/PrincipalCollection.php +++ /dev/null @@ -1,33 +0,0 @@ -principalBackend, $principal); - - } - -} diff --git a/src/serverlib/3rdparty/sabre/DAVACL/Property/Acl.php b/src/serverlib/3rdparty/sabre/DAVACL/Property/Acl.php deleted file mode 100644 index 1dfd4b0..0000000 --- a/src/serverlib/3rdparty/sabre/DAVACL/Property/Acl.php +++ /dev/null @@ -1,212 +0,0 @@ -privileges = $privileges; - $this->prefixBaseUrl = $prefixBaseUrl; - - } - - /** - * Returns the list of privileges for this property - * - * @return array - */ - function getPrivileges() { - - return $this->privileges; - - } - - /** - * Serializes the property into a DOMElement - * - * @param DAV\Server $server - * @param \DOMElement $node - * @return void - */ - 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 - * @param array $propertyMap - * @return Acl - */ - static function unserialize(\DOMElement $dom, array $propertyMap) { - - $privileges = []; - $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), $propertyMap); - - 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[] = [ - '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/src/serverlib/3rdparty/sabre/DAVACL/Property/AclRestrictions.php b/src/serverlib/3rdparty/sabre/DAVACL/Property/AclRestrictions.php deleted file mode 100644 index 156f606..0000000 --- a/src/serverlib/3rdparty/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/src/serverlib/3rdparty/sabre/DAVACL/Property/CurrentUserPrivilegeSet.php b/src/serverlib/3rdparty/sabre/DAVACL/Property/CurrentUserPrivilegeSet.php deleted file mode 100644 index 33bfabe..0000000 --- a/src/serverlib/3rdparty/sabre/DAVACL/Property/CurrentUserPrivilegeSet.php +++ /dev/null @@ -1,136 +0,0 @@ -privileges = $privileges; - - } - - /** - * Serializes the property in the DOM - * - * @param DAV\Server $server - * @param \DOMElement $node - * @return void - */ - 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 - */ - function has($privilegeName) { - - return in_array($privilegeName, $this->privileges); - - } - - /** - * Returns the list of privileges. - * - * @return array - */ - function getValue() { - - return $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 - * @param array $propertyMap - * @return CurrentUserPrivilegeSet - */ - static function unserialize(\DOMElement $node, array $propertyMap) { - - $result = []; - - $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/src/serverlib/3rdparty/sabre/DAVACL/Property/Principal.php b/src/serverlib/3rdparty/sabre/DAVACL/Property/Principal.php deleted file mode 100644 index 88a6680..0000000 --- a/src/serverlib/3rdparty/sabre/DAVACL/Property/Principal.php +++ /dev/null @@ -1,162 +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 - */ - function getType() { - - return $this->type; - - } - - /** - * Returns the principal uri. - * - * @return string - */ - function getHref() { - - return $this->href; - - } - - /** - * Serializes the property into a DOMElement. - * - * @param DAV\Server $server - * @param \DOMElement $node - * @return void - */ - 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 - * @param array $propertyMap - * @return Principal - */ - static function unserialize(\DOMElement $dom, array $propertyMap) { - - $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/src/serverlib/3rdparty/sabre/DAVACL/Property/SupportedPrivilegeSet.php b/src/serverlib/3rdparty/sabre/DAVACL/Property/SupportedPrivilegeSet.php deleted file mode 100644 index c42efda..0000000 --- a/src/serverlib/3rdparty/sabre/DAVACL/Property/SupportedPrivilegeSet.php +++ /dev/null @@ -1,105 +0,0 @@ -privileges = $privileges; - - } - - /** - * Returns the privilege value. - * - * @return array - */ - function getValue() { - - return $this->privileges; - - } - - /** - * Serializes the property into a domdocument. - * - * @param DAV\Server $server - * @param \DOMElement $node - * @return void - */ - 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/src/serverlib/3rdparty/sabre/HTTP/Auth/AWS.php b/src/serverlib/3rdparty/sabre/HTTP/Auth/AWS.php deleted file mode 100644 index 34c11c8..0000000 --- a/src/serverlib/3rdparty/sabre/HTTP/Auth/AWS.php +++ /dev/null @@ -1,234 +0,0 @@ -request->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 - */ - function getAccessKey() { - - return $this->accessKey; - - } - - /** - * Validates the signature based on the secretKey - * - * @param string $secretKey - * @return bool - */ - function validate($secretKey) { - - $contentMD5 = $this->request->getHeader('Content-MD5'); - - if ($contentMD5) { - // We need to validate the integrity of the request - $body = $this->request->getBody(true); - $this->request->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->request->getHeader('x-amz-date')) - $requestDate = $this->request->getHeader('Date'); - - if (!$this->validateRFC2616Date($requestDate)) - return false; - - $amzHeaders = $this->getAmzHeaders(); - - $signature = base64_encode( - $this->hmacsha1($secretKey, - $this->request->getMethod() . "\n" . - $contentMD5 . "\n" . - $this->request->getHeader('Content-type') . "\n" . - $requestDate . "\n" . - $amzHeaders . - $this->request->getUrl() - ) - ); - - 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 - */ - function requireLogin() { - - $this->response->addHeader('WWW-Authenticate','AWS'); - $this->response->setStatus(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 = []; - $headers = $this->request->getHeaders(); - foreach($headers as $headerName => $headerValue) { - if (strpos(strtolower($headerName),'x-amz-')===0) { - $amzHeaders[strtolower($headerName)] = str_replace( ["\r\n"], [' '],$headerValue[0]) . "\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) { - - if (function_exists('hash_hmac')) { - return hash_hmac('sha1', $message, $key, true); - } - - $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/src/serverlib/3rdparty/sabre/HTTP/Auth/AbstractAuth.php b/src/serverlib/3rdparty/sabre/HTTP/Auth/AbstractAuth.php deleted file mode 100644 index 914d638..0000000 --- a/src/serverlib/3rdparty/sabre/HTTP/Auth/AbstractAuth.php +++ /dev/null @@ -1,74 +0,0 @@ -realm = $realm; - $this->request = $request; - $this->response = $response; - - } - - /** - * This method sends the needed HTTP header and statuscode (401) to force - * the user to login. - * - * @return void - */ - abstract function requireLogin(); - - /** - * Returns the HTTP realm - * - * @return string - */ - function getRealm() { - - return $this->realm; - - } - -} diff --git a/src/serverlib/3rdparty/sabre/HTTP/Auth/Basic.php b/src/serverlib/3rdparty/sabre/HTTP/Auth/Basic.php deleted file mode 100644 index 0b23db0..0000000 --- a/src/serverlib/3rdparty/sabre/HTTP/Auth/Basic.php +++ /dev/null @@ -1,57 +0,0 @@ -request->getHeader('Authorization'); - - if (!$auth) { - return null; - } - - if (strtolower(substr($auth,0,6))!=='basic ') { - return null; - } - - return explode(':',base64_decode(substr($auth, 6)), 2); - - } - - /** - * This method sends the needed HTTP header and statuscode (401) to force - * the user to login. - * - * @return void - */ - function requireLogin() { - - $this->response->addHeader('WWW-Authenticate','Basic realm="' . $this->realm . '"'); - $this->response->setStatus(401); - - } - -} diff --git a/src/serverlib/3rdparty/sabre/HTTP/Auth/Digest.php b/src/serverlib/3rdparty/sabre/HTTP/Auth/Digest.php deleted file mode 100644 index 3701d55..0000000 --- a/src/serverlib/3rdparty/sabre/HTTP/Auth/Digest.php +++ /dev/null @@ -1,232 +0,0 @@ -nonce = uniqid(); - $this->opaque = md5($realm); - parent::__construct($realm, $request, $response); - - } - - /** - * Gathers all information from the headers - * - * This method needs to be called prior to anything else. - * - * @return void - */ - 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 - */ - function setQOP($qop) { - - $this->qop = $qop; - - } - - /** - * Validates the user. - * - * The A1 parameter should be md5($username . ':' . $realm . ':' . $password); - * - * @param string $A1 - * @return bool - */ - 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 - */ - function validatePassword($password) { - - $this->A1 = md5($this->digestParts['username'] . ':' . $this->realm . ':' . $password); - return $this->validate(); - - } - - /** - * Returns the username for the request - * - * @return string - */ - function getUsername() { - - return $this->digestParts['username']; - - } - - /** - * Validates the digest challenge - * - * @return bool - */ - protected function validate() { - - $A2 = $this->request->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->request->getBody($asString = true); - $this->request->setBody($body); - $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 - */ - 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->response->addHeader('WWW-Authenticate','Digest realm="' . $this->realm . '",qop="'.$qop.'",nonce="' . $this->nonce . '",opaque="' . $this->opaque . '"'); - $this->response->setStatus(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 - */ - function getDigest() { - - return $this->request->getHeader('Authorization'); - - } - - - /** - * 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 = ['nonce'=>1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, 'uri'=>1, 'response'=>1]; - $data = []; - - 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/src/serverlib/3rdparty/sabre/HTTP/Client.php b/src/serverlib/3rdparty/sabre/HTTP/Client.php deleted file mode 100644 index 57fd23c..0000000 --- a/src/serverlib/3rdparty/sabre/HTTP/Client.php +++ /dev/null @@ -1,589 +0,0 @@ -curlSettings = [ - CURLOPT_RETURNTRANSFER => true, - CURLOPT_HEADER => true, - CURLOPT_NOBODY => false, - ]; - - } - - /** - * Sends a request to a HTTP server, and returns a response. - * - * @param RequestInterface $request - * @return ResponseInterface - */ - function send(RequestInterface $request) { - - $this->emit('beforeRequest', [$request]); - - $retryCount = 0; - $redirects = 0; - - do { - - $doRedirect = false; - $retry = false; - - try { - - $response = $this->doRequest($request); - - $code = (int)$response->getStatus(); - - // We are doing in-PHP redirects, because curl's - // FOLLOW_LOCATION throws errors when PHP is configured with - // open_basedir. - // - // https://github.com/fruux/sabre-http/issues/12 - if (in_array($code, [301, 302, 307, 308]) && $redirects < $this->maxRedirects) { - - $oldLocation = $request->getUrl(); - - // Creating a new instance of the request object. - $request = clone $request; - - // Setting the new location - $request->setUrl(URLUtil::resolve( - $oldLocation, - $response->getHeader('Location') - )); - - $doRedirect = true; - $redirects++; - - } - - // This was a HTTP error - if ($code >= 400) { - - $this->emit('error', [$request, $response, &$retry, $retryCount]); - $this->emit('error:' . $code, [$request, $response, &$retry, $retryCount]); - - } - - } catch (ClientException $e) { - $this->emit('exception', [$request, $e, &$retry, $retryCount]); - - // If retry was still set to false, it means no event handler - // dealt with the problem. In this case we just re-throw the - // exception. - if (!$retry) { - throw $e; - } - } - if ($retry) { - $retryCount++; - } - - } while ($retry || $doRedirect); - - $this->emit('afterRequest', [$request, $response]); - - if ($this->throwExceptions && $code >= 400) { - throw new ClientHttpException($response); - } - - return $response; - - } - - /** - * Sends a HTTP request asynchronously. - * - * Due to the nature of PHP, you must from time to time poll to see if any - * new responses came in. - * - * After calling sendAsync, you must therefore occasionally call the poll() - * method, or wait(). - * - * @param RequestInterface $request - * @param callable $success - * @param callable $error - * @return void - */ - function sendAsync(RequestInterface $request, callable $success = null, callable $error = null) { - - $this->emit('beforeRequest', [$request]); - $this->sendAsyncInternal($request, $success, $error); - $this->poll(); - - } - - - /** - * This method checks if any http requests have gotten results, and if so, - * call the appropriate success or error handlers. - * - * This method will return true if there are still requests waiting to - * return, and false if all the work is done. - * - * @return bool - */ - function poll() { - - // nothing to do? - if(!$this->curlMultiMap) { - return false; - } - - do { - $r = curl_multi_exec( - $this->curlMultiHandle, - $stillRunning - ); - } while ($r === CURLM_CALL_MULTI_PERFORM); - - do { - - messageQueue: - - $status = curl_multi_info_read( - $this->curlMultiHandle, - $messagesInQueue - ); - if ($status && $status['msg'] === CURLMSG_DONE) { - - $resourceId = intval($status['handle']); - list( - $request, - $successCallback, - $errorCallback, - $retryCount, - ) = $this->curlMultiMap[$resourceId]; - - unset($this->curlMultiMap[$resourceId]); - - $curlResult = $this->parseCurlResult(curl_multi_getcontent($status['handle']), $status['handle']); - - $retry = false; - - if ($curlResult['status'] === self::STATUS_CURLERROR) { - - $e = new ClientException($curlResult['curl_errmsg'], $curlResult['curl_errno']); - $this->emit('exception', [$request, $e, &$retry, $retryCount]); - if ($retry) { - $retryCount++; - $this->sendASyncInternal($request, $successCallback, $errorCallback, $retryCount); - goto messageQueue; - } - $curlResult['request'] = $request; - if ($errorCallback) { - $errorCallback($curlResult); - } - - } elseif ($curlResult['status'] === self::STATUS_HTTPERROR) { - - $this->emit('error', [$request, $curlResult['response'], &$retry, $retryCount]); - $this->emit('error:' . $curlResult['http_code'], [$request, $curlResult['response'], &$retry, $retryCount]); - - if ($retry) { - $retryCount++; - $this->sendASyncInternal($request, $successCallback, $errorCallback, $retryCount); - goto messageQueue; - } - $curlResult['request'] = $request; - if ($errorCallback) { - $errorCallback($curlResult); - } - } else { - $this->emit('afterRequest', [$request, $curlResult['response']]); - if ($successCallback) { - $successCallback($curlResult['response']); - } - } - } - } while ($messagesInQueue > 0); - - return $stillRunning; - - } - - /** - * Processes every HTTP request in the queue, and waits till they are all - * completed. - * - * @return void - */ - function wait() { - - do { - curl_multi_select($this->curlMultiHandle); - $stillRunning = $this->poll(); - } while ($stillRunning); - - } - - /** - * If this is set to true, the Client will automatically throw exceptions - * upon HTTP errors. - * - * This means that if a response came back with a status code greater than - * or equal to 400, we will throw a ClientHttpException. - * - * This only works for the send() method. Throwing exceptions for - * sendAsync() is not supported. - * - * @param bool $throwExceptions - * @return void - */ - function setThrowExceptions($throwExceptions) { - - $this->throwExceptions = $throwExceptions; - - } - - /** - * Adds a CURL setting. - * - * These settings will be included in every HTTP request. - * - * @param int $name - * @param mixed $value - * @return void - */ - function addCurlSetting($name, $value) { - - $this->curlSettings[$name] = $value; - - } - - /** - * This method is responsible for performing a single request. - * - * @param RequestInterface $request - * @return ResponseInterface - */ - protected function doRequest(RequestInterface $request) { - - $settings = $this->createCurlSettingsArray($request); - - if (!$this->curlHandle) { - $this->curlHandle = curl_init(); - } - - curl_setopt_array($this->curlHandle, $settings); - $response = $this->curlExec($this->curlHandle); - - $response = $this->parseCurlResult($response, $this->curlHandle); - - if ($response['status'] === self::STATUS_CURLERROR) { - throw new ClientException($response['curl_errmsg'], $response['curl_errno']); - } - - return $response['response']; - - } - - /** - * Cached curl handle. - * - * By keeping this resource around for the lifetime of this object, things - * like persistent connections are possible. - * - * @var resource - */ - private $curlHandle; - - /** - * Handler for curl_multi requests. - * - * The first time sendAsync is used, this will be created. - * - * @var resource - */ - private $curlMultiHandle; - - /** - * Has a list of curl handles, as well as their associated success and - * error callbacks. - * - * @var array - */ - private $curlMultiMap = []; - - /** - * Turns a RequestInterface object into an array with settings that can be - * fed to curl_setopt - * - * @param RequestInterface $request - * @return array - */ - protected function createCurlSettingsArray(RequestInterface $request) { - - $settings = $this->curlSettings; - - switch($request->getMethod()) { - case 'HEAD' : - $settings[CURLOPT_NOBODY] = true; - $settings[CURLOPT_CUSTOMREQUEST] = 'HEAD'; - $settings[CURLOPT_POSTFIELDS] = ''; - $settings[CURLOPT_PUT] = false; - break; - case 'GET' : - $settings[CURLOPT_CUSTOMREQUEST] = 'GET'; - $settings[CURLOPT_POSTFIELDS] = ''; - $settings[CURLOPT_PUT] = false; - break; - default : - $body = $request->getBody(); - if (is_resource($body)) { - // This needs to be set to PUT, regardless of the actual - // method used. Without it, INFILE will be ignored for some - // reason. - $settings[CURLOPT_PUT] = true; - $settings[CURLOPT_INFILE] = $request->getBody(); - } else { - // 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. - $settings[CURLOPT_POSTFIELDS] = (string)$body; - } - $settings[CURLOPT_CUSTOMREQUEST] = $request->getMethod(); - break; - - } - - $nHeaders = []; - foreach($request->getHeaders() as $key=>$values) { - - foreach($values as $value) { - $nHeaders[] = $key . ': ' . $value; - } - - } - $settings[CURLOPT_HTTPHEADER] = $nHeaders; - $settings[CURLOPT_URL] = $request->getUrl(); - // FIXME: CURLOPT_PROTOCOLS is currently unsupported by HHVM - if(defined('CURLOPT_PROTOCOLS')) { - $settings[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; - } - // FIXME: CURLOPT_REDIR_PROTOCOLS is currently unsupported by HHVM - if(defined('CURLOPT_REDIR_PROTOCOLS')) { - $settings[CURLOPT_REDIR_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; - } - - return $settings; - - } - - const STATUS_SUCCESS = 0; - const STATUS_CURLERROR = 1; - const STATUS_HTTPERROR = 2; - - /** - * Parses the result of a curl call in a format that's a bit more - * convenient to work with. - * - * The method returns an array with the following elements: - * * status - one of the 3 STATUS constants. - * * curl_errno - A curl error number. Only set if status is - * STATUS_CURLERROR. - * * curl_errmsg - A current error message. Only set if status is - * STATUS_CURLERROR. - * * response - Response object. Only set if status is STATUS_SUCCESS, or - * STATUS_HTTPERROR. - * * http_code - HTTP status code, as an int. Only set if Only set if - * status is STATUS_SUCCESS, or STATUS_HTTPERROR - * - * @param string $response - * @param resource $curlHandle - * @return Response - */ - protected function parseCurlResult($response, $curlHandle) { - - list( - $curlInfo, - $curlErrNo, - $curlErrMsg - ) = $this->curlStuff($curlHandle); - - if ($curlErrNo) { - return [ - 'status' => self::STATUS_CURLERROR, - 'curl_errno' => $curlErrNo, - 'curl_errmsg' => $curlErrMsg, - ]; - } - - $headerBlob = substr($response, 0, $curlInfo['header_size']); - // In the case of 204 No Content, strlen($response) == $curlInfo['header_size]. - // This will cause substr($response, $curlInfo['header_size']) return FALSE instead of NULL - // An exception will be thrown when calling getBodyAsString then - $responseBody = substr($response, $curlInfo['header_size']) ?: null; - - unset($response); - - // 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); - - $response = new Response(); - $response->setStatus($curlInfo['http_code']); - - foreach($headerBlob as $header) { - $parts = explode(':', $header, 2); - if (count($parts)==2) { - $response->addHeader(trim($parts[0]), trim($parts[1])); - } - } - - $response->setBody($responseBody); - - $httpCode = intval($response->getStatus()); - - return [ - 'status' => $httpCode >= 400 ? self::STATUS_HTTPERROR : self::STATUS_SUCCESS, - 'response' => $response, - 'http_code' => $httpCode, - ]; - - } - - /** - * Sends a asynchrous http request. - * - * We keep this in a separate method, so we can call it without triggering - * the beforeRequest event and don't do the poll(). - * - * @param RequestInterface $request - * @param callable $success - * @param callable $error - * @param int $retryCount - */ - protected function sendAsyncInternal(RequestInterface $request, callable $success, callable $error, $retryCount = 0) { - - if (!$this->curlMultiHandle) { - $this->curlMultiHandle = curl_multi_init(); - } - $curl = curl_init(); - curl_setopt_array( - $curl, - $this->createCurlSettingsArray($request) - ); - curl_multi_add_handle($this->curlMultiHandle, $curl); - $this->curlMultiMap[intval($curl)] = [ - $request, - $success, - $error, - $retryCount - ]; - - } - - // @codeCoverageIgnoreStart - - /** - * Calls curl_exec - * - * This method exists so it can easily be overridden and mocked. - * - * @param resource $curlHandle - * @return string - - */ - protected function curlExec($curlHandle) { - - return curl_exec($curlHandle); - - } - - /** - * Returns a bunch of information about a curl request. - * - * This method exists so it can easily be overridden and mocked. - * - * @param resource $curlHandle - * @return array - */ - protected function curlStuff($curlHandle) { - - return [ - curl_getinfo($curlHandle), - curl_errno($curlHandle), - curl_error($curlHandle), - ]; - - } - // @codeCoverageIgnoreEnd - -} diff --git a/src/serverlib/3rdparty/sabre/HTTP/ClientException.php b/src/serverlib/3rdparty/sabre/HTTP/ClientException.php deleted file mode 100644 index cff9e1d..0000000 --- a/src/serverlib/3rdparty/sabre/HTTP/ClientException.php +++ /dev/null @@ -1,15 +0,0 @@ -response = $response; - parent::__construct($response->getStatusText(), $response->getStatus()); - - } - - /** - * The http status code for the error. - * - * @return int - */ - function getHttpStatus() { - - return $this->response->getStatus(); - - } - - /** - * Returns the full response object. - * - * @return ResponseInterface - */ - function getResponse() { - - return $this->response; - - } - -} diff --git a/src/serverlib/3rdparty/sabre/HTTP/HttpException.php b/src/serverlib/3rdparty/sabre/HTTP/HttpException.php deleted file mode 100644 index 9106ce4..0000000 --- a/src/serverlib/3rdparty/sabre/HTTP/HttpException.php +++ /dev/null @@ -1,30 +0,0 @@ -getBody(); - if (is_string($body) || is_null($body)) { - $stream = fopen('php://temp', 'r+'); - fwrite($stream, $body); - rewind($stream); - return $stream; - } - return $body; - - } - - /** - * Returns the body as a string. - * - * Note that because the underlying data may be based on a stream, this - * method could only work correctly the first time. - * - * @return string - */ - function getBodyAsString() { - - $body = $this->getBody(); - if (is_string($body)) { - return $body; - } - if (is_null($body)) { - return ''; - } - return stream_get_contents($body); - - } - - /** - * Returns the message body, as it's internal representation. - * - * This could be either a string or a stream. - * - * @return resource|string - */ - function getBody() { - - return $this->body; - - } - - /** - * Replaces the body resource with a new stream or string. - * - * @param resource $body - */ - function setBody($body) { - - $this->body = $body; - - } - - /** - * Returns all the HTTP headers as an array. - * - * Every header is returned as an array, with one or more values. - * - * @return array - */ - function getHeaders() { - - $result = []; - foreach($this->headers as $headerInfo) { - $result[$headerInfo[0]] = $headerInfo[1]; - } - return $result; - - } - - /** - * Will return true or false, depending on if a HTTP header exists. - * - * @param string $name - * @return bool - */ - function hasHeader($name) { - - return isset($this->headers[strtolower($name)]); - - } - - /** - * Returns a specific HTTP header, based on it's name. - * - * The name must be treated as case-insensitive. - * If the header does not exist, this method must return null. - * - * If a header appeared more than once in a HTTP request, this method will - * concatenate all the values with a comma. - * - * Note that this not make sense for all headers. Some, such as - * `Set-Cookie` cannot be logically combined with a comma. In those cases - * you *should* use getHeaderAsArray(). - * - * @param string $name - * @return string|null - */ - function getHeader($name) { - - $name = strtolower($name); - - if (isset($this->headers[$name])) { - return implode(',', $this->headers[$name][1]); - } - return null; - - } - - /** - * Returns a HTTP header as an array. - * - * For every time the HTTP header appeared in the request or response, an - * item will appear in the array. - * - * If the header did not exists, this method will return an empty array. - * - * @param string $name - * @return string[] - */ - function getHeaderAsArray($name) { - - $name = strtolower($name); - - if (isset($this->headers[$name])) { - return $this->headers[$name][1]; - } - - return []; - - } - - /** - * Updates a HTTP header. - * - * The case-sensitity of the name value must be retained as-is. - * - * If the header already existed, it will be overwritten. - * - * @param string $name - * @param string|string[] $value - * @return void - */ - function setHeader($name, $value) { - - $this->headers[strtolower($name)] = [$name, (array)$value]; - - } - - /** - * Sets a new set of HTTP headers. - * - * The headers array should contain headernames for keys, and their value - * should be specified as either a string or an array. - * - * Any header that already existed will be overwritten. - * - * @param array $headers - * @return void - */ - function setHeaders(array $headers) { - - foreach($headers as $name => $value) { - $this->setHeader($name, $value); - } - - } - - /** - * Adds a HTTP header. - * - * This method will not overwrite any existing HTTP header, but instead add - * another value. Individual values can be retrieved with - * getHeadersAsArray. - * - * @param string $name - * @param string $value - * @return void - */ - function addHeader($name, $value) { - - $lName = strtolower($name); - if (isset($this->headers[$lName])) { - $this->headers[$lName][1] = array_merge( - $this->headers[$lName][1], - (array)$value - ); - } else { - $this->headers[$lName] = [ - $name, - (array)$value - ]; - } - - } - - /** - * Adds a new set of HTTP headers. - * - * Any existing headers will not be overwritten. - * - * @param array $headers - * @return void - */ - function addHeaders(array $headers) { - - foreach($headers as $name => $value) { - $this->addHeader($name, $value); - } - - } - - - /** - * Removes a HTTP header. - * - * The specified header name must be treated as case-insenstive. - * This method should return true if the header was successfully deleted, - * and false if the header did not exist. - * - * @return bool - */ - function removeHeader($name) { - - $name = strtolower($name); - if (!isset($this->headers[$name])) { - return false; - } - unset($this->headers[$name]); - return true; - - } - - /** - * Sets the HTTP version. - * - * Should be 1.0 or 1.1. - * - * @param string $version - * @return void - */ - function setHttpVersion($version) { - - $this->httpVersion = $version; - - } - - /** - * Returns the HTTP version. - * - * @return string - */ - function getHttpVersion() { - - return $this->httpVersion; - - } -} diff --git a/src/serverlib/3rdparty/sabre/HTTP/MessageDecoratorTrait.php b/src/serverlib/3rdparty/sabre/HTTP/MessageDecoratorTrait.php deleted file mode 100644 index e976a95..0000000 --- a/src/serverlib/3rdparty/sabre/HTTP/MessageDecoratorTrait.php +++ /dev/null @@ -1,250 +0,0 @@ -inner->getBodyAsStream(); - - } - - /** - * Returns the body as a string. - * - * Note that because the underlying data may be based on a stream, this - * method could only work correctly the first time. - * - * @return string - */ - function getBodyAsString() { - - return $this->inner->getBodyAsString(); - - } - - /** - * Returns the message body, as it's internal representation. - * - * This could be either a string or a stream. - * - * @return resource|string - */ - function getBody() { - - return $this->inner->getBody(); - - } - - /** - * Updates the body resource with a new stream. - * - * @param resource $body - * @return void - */ - function setBody($body) { - - $this->inner->setBody($body); - - } - - /** - * Returns all the HTTP headers as an array. - * - * Every header is returned as an array, with one or more values. - * - * @return array - */ - function getHeaders() { - - return $this->inner->getHeaders(); - - } - - /** - * Will return true or false, depending on if a HTTP header exists. - * - * @param string $name - * @return bool - */ - function hasHeader($name) { - - return $this->inner->hasHeader($name); - - } - - /** - * Returns a specific HTTP header, based on it's name. - * - * The name must be treated as case-insensitive. - * If the header does not exist, this method must return null. - * - * If a header appeared more than once in a HTTP request, this method will - * concatenate all the values with a comma. - * - * Note that this not make sense for all headers. Some, such as - * `Set-Cookie` cannot be logically combined with a comma. In those cases - * you *should* use getHeaderAsArray(). - * - * @param string $name - * @return string|null - */ - function getHeader($name) { - - return $this->inner->getHeader($name); - - } - - /** - * Returns a HTTP header as an array. - * - * For every time the HTTP header appeared in the request or response, an - * item will appear in the array. - * - * If the header did not exists, this method will return an empty array. - * - * @param string $name - * @return string[] - */ - function getHeaderAsArray($name) { - - return $this->inner->getHeaderAsArray($name); - - } - - /** - * Updates a HTTP header. - * - * The case-sensitity of the name value must be retained as-is. - * - * If the header already existed, it will be overwritten. - * - * @param string $name - * @param string|string[] $value - * @return void - */ - function setHeader($name, $value) { - - $this->inner->setHeader($name, $value); - - } - - /** - * Sets a new set of HTTP headers. - * - * The headers array should contain headernames for keys, and their value - * should be specified as either a string or an array. - * - * Any header that already existed will be overwritten. - * - * @param array $headers - * @return void - */ - function setHeaders(array $headers) { - - $this->inner->setHeaders($headers); - - } - - /** - * Adds a HTTP header. - * - * This method will not overwrite any existing HTTP header, but instead add - * another value. Individual values can be retrieved with - * getHeadersAsArray. - * - * @param string $name - * @param string $value - * @return void - */ - function addHeader($name, $value) { - - $this->inner->addHeader($name, $value); - - } - - /** - * Adds a new set of HTTP headers. - * - * Any existing headers will not be overwritten. - * - * @param array $headers - * @return void - */ - function addHeaders(array $headers) { - - $this->inner->addHeaders($headers); - - } - - - /** - * Removes a HTTP header. - * - * The specified header name must be treated as case-insenstive. - * This method should return true if the header was successfully deleted, - * and false if the header did not exist. - * - * @return bool - */ - function removeHeader($name) { - - $this->inner->removeHeader($name); - - } - - /** - * Sets the HTTP version. - * - * Should be 1.0 or 1.1. - * - * @param string $version - * @return void - */ - function setHttpVersion($version) { - - $this->inner->setHttpVersion($version); - - } - - /** - * Returns the HTTP version. - * - * @return string - */ - function getHttpVersion() { - - return $this->inner->getHttpVersion(); - - } - -} diff --git a/src/serverlib/3rdparty/sabre/HTTP/MessageInterface.php b/src/serverlib/3rdparty/sabre/HTTP/MessageInterface.php deleted file mode 100644 index 82f5c6a..0000000 --- a/src/serverlib/3rdparty/sabre/HTTP/MessageInterface.php +++ /dev/null @@ -1,177 +0,0 @@ -setMethod($method); - if (!is_null($url)) $this->setUrl($url); - if (!is_null($headers)) $this->setHeaders($headers); - if (!is_null($body)) $this->setBody($body); - - } - - /** - * Returns the current HTTP method - * - * @return string - */ - function getMethod() { - - return $this->method; - - } - - /** - * Sets the HTTP method - * - * @param string $method - * @return void - */ - function setMethod($method) { - - $this->method = $method; - - } - - /** - * Returns the request url. - * - * @return string - */ - function getUrl() { - - return $this->url; - - } - - /** - * Sets the request url. - * - * @param string $url - * @return void - */ - function setUrl($url) { - - $this->url = $url; - - } - - /** - * Returns the list of query parameters. - * - * This is equivalent to PHP's $_GET superglobal. - * - * @return array - */ - function getQueryParameters() { - - $url = $this->getUrl(); - if (($index = strpos($url,'?'))===false) { - return []; - } else { - parse_str(substr($url, $index+1), $queryParams); - return $queryParams; - } - - } - - /** - * Sets the absolute url. - * - * @param string $url - * @return void - */ - function setAbsoluteUrl($url) { - - $this->absoluteUrl = $url; - - } - - /** - * Returns the absolute url. - * - * @return string - */ - function getAbsoluteUrl() { - - return $this->absoluteUrl; - - } - - /** - * Base url - * - * @var string - */ - protected $baseUrl = '/'; - - /** - * Sets a base url. - * - * This url is used for relative path calculations. - * - * @param string $url - * @return void - */ - function setBaseUrl($url) { - - $this->baseUrl = $url; - - } - - /** - * Returns the current base url. - * - * @return string - */ - function getBaseUrl() { - - return $this->baseUrl; - - } - - /** - * Returns the relative path. - * - * This is being calculated using the base url. This path will not start - * with a slash, so it will always return something like - * 'example/path.html'. - * - * If the full path is equal to the base url, this method will return an - * empty string. - * - * This method will also urldecode the path, and if the url was incoded as - * ISO-8859-1, it will convert it to UTF-8. - * - * If the path is outside of the base url, a LogicException will be thrown. - * - * @return string - */ - function getPath() { - - // Removing duplicated slashes. - $uri = str_replace('//','/',$this->getUrl()); - - if (strpos($uri,$this->getBaseUrl())===0) { - - // We're not interested in the query part (everything after the ?). - list($uri) = explode('?', $uri); - return trim(URLUtil::decodePath(substr($uri,strlen($this->getBaseUrl()))),'/'); - - } - // A special case, if the baseUri was accessed without a trailing - // slash, we'll accept it as well. - elseif ($uri.'/' === $this->getBaseUrl()) { - - return ''; - - } - - throw new \LogicException('Requested uri (' . $this->getUrl() . ') is out of base uri (' . $this->getBaseUrl() . ')'); - } - - /** - * Equivalent of PHP's $_POST. - * - * @var array - */ - protected $postData = []; - - /** - * Sets the post data. - * - * This is equivalent to PHP's $_POST superglobal. - * - * This would not have been needed, if POST data was accessible as - * php://input, but unfortunately we need to special case it. - * - * @param array $postData - * @return void - */ - function setPostData(array $postData) { - - $this->postData = $postData; - - } - - /** - * Returns the POST data. - * - * This is equivalent to PHP's $_POST superglobal. - * - * @return array - */ - function getPostData() { - - return $this->postData; - - } - - /** - * An array containing the raw _SERVER array. - * - * @var array - */ - protected $rawServerData; - - /** - * Returns an item from the _SERVER array. - * - * If the value does not exist in the array, null is returned. - * - * @param string $valueName - * @return string|null - */ - function getRawServerValue($valueName) { - - if (isset($this->rawServerData[$valueName])) { - return $this->rawServerData[$valueName]; - } - - } - - /** - * Sets the _SERVER array. - * - * @param array $data - * @return void - */ - function setRawServerData(array $data) { - - $this->rawServerData = $data; - - } - - /** - * Serializes the request object as a string. - * - * This is useful for debugging purposes. - * - * @return string - */ - function __toString() { - - $out = $this->getMethod() . ' ' . $this->getUrl() . ' HTTP/' . $this->getHTTPVersion() . "\r\n"; - - foreach($this->getHeaders() as $key=>$value) { - foreach($value as $v) { - if ($key==='Authorization') { - list($v) = explode(' ', $v,2); - $v .= ' REDACTED'; - } - $out .= $key . ": " . $v . "\r\n"; - } - } - $out .= "\r\n"; - $out .= $this->getBodyAsString(); - - return $out; - - } - -} diff --git a/src/serverlib/3rdparty/sabre/HTTP/RequestDecorator.php b/src/serverlib/3rdparty/sabre/HTTP/RequestDecorator.php deleted file mode 100644 index 322ddbc..0000000 --- a/src/serverlib/3rdparty/sabre/HTTP/RequestDecorator.php +++ /dev/null @@ -1,231 +0,0 @@ -inner = $inner; - - } - - /** - * Returns the current HTTP method - * - * @return string - */ - function getMethod() { - - return $this->inner->getMethod(); - - } - - /** - * Sets the HTTP method - * - * @param string $method - * @return void - */ - function setMethod($method) { - - $this->inner->setMethod($method); - - } - - /** - * Returns the request url. - * - * @return string - */ - function getUrl() { - - return $this->inner->getUrl(); - - } - - /** - * Sets the request url. - * - * @param string $url - * @return void - */ - function setUrl($url) { - - $this->inner->setUrl($url); - - } - - /** - * Returns the absolute url. - * - * @return string - */ - function getAbsoluteUrl() { - - return $this->inner->getAbsoluteUrl(); - - } - - /** - * Sets the absolute url. - * - * @param string $url - * @return void - */ - function setAbsoluteUrl($url) { - - $this->inner->setAbsoluteUrl($url); - - } - - /** - * Returns the current base url. - * - * @return string - */ - function getBaseUrl() { - - return $this->inner->getBaseUrl(); - - } - - /** - * Sets a base url. - * - * This url is used for relative path calculations. - * - * The base url should default to / - * - * @param string $url - * @return void - */ - function setBaseUrl($url) { - - $this->inner->setBaseUrl($url); - - } - - /** - * Returns the relative path. - * - * This is being calculated using the base url. This path will not start - * with a slash, so it will always return something like - * 'example/path.html'. - * - * If the full path is equal to the base url, this method will return an - * empty string. - * - * This method will also urldecode the path, and if the url was incoded as - * ISO-8859-1, it will convert it to UTF-8. - * - * If the path is outside of the base url, a LogicException will be thrown. - * - * @return string - */ - function getPath() { - - return $this->inner->getPath(); - - } - - /** - * Returns the list of query parameters. - * - * This is equivalent to PHP's $_GET superglobal. - * - * @return array - */ - function getQueryParameters() { - - return $this->inner->getQueryParameters(); - - } - - /** - * Returns the POST data. - * - * This is equivalent to PHP's $_POST superglobal. - * - * @return array - */ - function getPostData() { - - return $this->inner->getPostData(); - - } - - /** - * Sets the post data. - * - * This is equivalent to PHP's $_POST superglobal. - * - * This would not have been needed, if POST data was accessible as - * php://input, but unfortunately we need to special case it. - * - * @param array $postData - * @return void - */ - function setPostData(array $postData) { - - $this->inner->setPostData($postData); - - } - - - /** - * Returns an item from the _SERVER array. - * - * If the value does not exist in the array, null is returned. - * - * @param string $valueName - * @return string|null - */ - function getRawServerValue($valueName) { - - return $this->inner->getRawServerValue($valueName); - - } - - /** - * Sets the _SERVER array. - * - * @param array $data - * @return void - */ - function setRawServerData(array $data) { - - $this->inner->setRawServerData($data); - - } - - /** - * Serializes the request object as a string. - * - * This is useful for debugging purposes. - * - * @return string - */ - function __toString() { - - return $this->inner->__toString(); - - } -} diff --git a/src/serverlib/3rdparty/sabre/HTTP/RequestInterface.php b/src/serverlib/3rdparty/sabre/HTTP/RequestInterface.php deleted file mode 100644 index 1c262d4..0000000 --- a/src/serverlib/3rdparty/sabre/HTTP/RequestInterface.php +++ /dev/null @@ -1,147 +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', - 307 => 'Temporary Redirect', - 308 => 'Permanent 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', // RFC 6585 - 429 => 'Too Many Requests', // RFC 6585 - 431 => 'Request Header Fields Too Large', // RFC 6585 - 451 => 'Unavailable For Legal Reasons', // draft-tbray-http-legally-restricted-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', // RFC 6585 - ]; - - /** - * HTTP status code - * - * @var int - */ - protected $status; - - /** - * HTTP status text - * - * @var string - */ - protected $statusText; - - /** - * Creates the response object - * - * @param string|int $status - * @param array $headers - * @param resource $body - * @return void - */ - function __construct($status = null, array $headers = null, $body = null) { - - if (!is_null($status)) $this->setStatus($status); - if (!is_null($headers)) $this->setHeaders($headers); - if (!is_null($body)) $this->setBody($body); - - } - - - /** - * Returns the current HTTP status code. - * - * @return int - */ - function getStatus() { - - return $this->status; - - } - - /** - * Returns the human-readable status string. - * - * In the case of a 200, this may for example be 'OK'. - * - * @return string - */ - function getStatusText() { - - return $this->statusText; - - } - - /** - * Sets the HTTP status code. - * - * This can be either the full HTTP status code with human readable string, - * for example: "403 I can't let you do that, Dave". - * - * Or just the code, in which case the appropriate default message will be - * added. - * - * @param string|int $status - * @throws \InvalidArgumentExeption - * @return void - */ - function setStatus($status) { - - if (ctype_digit($status) || is_int($status)) { - - $statusCode = $status; - $statusText = isset(self::$statusCodes[$status])?self::$statusCodes[$status]:'Unknown'; - - } else { - list( - $statusCode, - $statusText - ) = explode(' ', $status, 2); - } - if ($statusCode < 100 || $statusCode > 999) { - throw new \InvalidArgumentException('The HTTP status code must be exactly 3 digits'); - } - - $this->status = $statusCode; - $this->statusText = $statusText; - - } - - /** - * Serializes the response object as a string. - * - * This is useful for debugging purposes. - * - * @return string - */ - function __toString() { - - $str = 'HTTP/' . $this->httpVersion . ' ' . $this->getStatus() . ' ' . $this->getStatusText() . "\r\n"; - foreach($this->getHeaders() as $key=>$value) { - foreach($value as $v) { - $str.= $key . ": " . $v . "\r\n"; - } - } - $str.="\r\n"; - $str.=$this->getBodyAsString(); - return $str; - - } - -} diff --git a/src/serverlib/3rdparty/sabre/HTTP/ResponseDecorator.php b/src/serverlib/3rdparty/sabre/HTTP/ResponseDecorator.php deleted file mode 100644 index 3fa0620..0000000 --- a/src/serverlib/3rdparty/sabre/HTTP/ResponseDecorator.php +++ /dev/null @@ -1,84 +0,0 @@ -inner = $inner; - - } - - /** - * Returns the current HTTP status code. - * - * @return int - */ - function getStatus() { - - return $this->inner->getStatus(); - - } - - - /** - * Returns the human-readable status string. - * - * In the case of a 200, this may for example be 'OK'. - * - * @return string - */ - function getStatusText() { - - return $this->inner->getStatusText(); - - } - /** - * Sets the HTTP status code. - * - * This can be either the full HTTP status code with human readable string, - * for example: "403 I can't let you do that, Dave". - * - * Or just the code, in which case the appropriate default message will be - * added. - * - * @param string|int $status - * @return void - */ - function setStatus($status) { - - $this->inner->setStatus($status); - - } - - /** - * Serializes the request object as a string. - * - * This is useful for debugging purposes. - * - * @return string - */ - function __toString() { - - return $this->inner->__toString(); - - } -} diff --git a/src/serverlib/3rdparty/sabre/HTTP/ResponseInterface.php b/src/serverlib/3rdparty/sabre/HTTP/ResponseInterface.php deleted file mode 100644 index 757d54d..0000000 --- a/src/serverlib/3rdparty/sabre/HTTP/ResponseInterface.php +++ /dev/null @@ -1,45 +0,0 @@ -setBody(fopen('php://input','r')); - $r->setPostData($_POST); - return $r; - - } - - /** - * Sends the HTTP response back to a HTTP client. - * - * This calls php's header() function and streams the body to php://output. - * - * @param ResponseInterface $response - * @return void - */ - static function sendResponse(ResponseInterface $response) { - - header('HTTP/' . $response->getHttpVersion() . ' ' . $response->getStatus() . ' ' . $response->getStatusText()); - foreach($response->getHeaders() as $key=>$value) { - - foreach($value as $k=>$v) { - if ($k === 0) { - header($key . ': ' . $v); - } else { - header($key . ': ' . $v, false); - } - } - - } - file_put_contents('php://output', $response->getBody()); - - } - - /** - * This static method will create a new Request object, based on a PHP - * $_SERVER array. - * - * @param array $serverArray - * @return Request - */ - static function createFromServerArray(array $serverArray) { - - $headers = []; - $method = null; - $url = null; - $httpVersion = '1.1'; - - $protocol = 'http'; - $hostName = 'localhost'; - - foreach($serverArray as $key=>$value) { - - switch($key) { - - case 'SERVER_PROTOCOL' : - if ($value==='HTTP/1.0') { - $httpVersion = '1.0'; - } - break; - case 'REQUEST_METHOD' : - $method = $value; - break; - case 'REQUEST_URI' : - $url = $value; - break; - - // These sometimes should up without a HTTP_ prefix - case 'CONTENT_TYPE' : - $headers['Content-Type'] = $value; - break; - case 'CONTENT_LENGTH' : - $headers['Content-Length'] = $value; - break; - - // mod_php on apache will put credentials in these variables. - // (fast)cgi does not usually do this, however. - case 'PHP_AUTH_USER' : - if (isset($serverArray['PHP_AUTH_PW'])) { - $headers['Authorization'] = 'Basic ' . base64_encode($value . ':' . $serverArray['PHP_AUTH_PW']); - } - break; - - // Similarly, mod_php may also screw around with digest auth. - case 'PHP_AUTH_DIGEST' : - $headers['Authorization'] = 'Digest ' . $value; - break; - - // Apache may prefix the HTTP_AUTHORIZATION header with - // REDIRECT_, if mod_rewrite was used. - case 'REDIRECT_HTTP_AUTHORIZATION' : - $headers['Authorization'] = $value; - break; - - case 'HTTP_HOST' : - $hostName = $value; - $headers['Host'] = $value; - break; - - case 'HTTPS' : - if (!empty($value) && $value!=='off') { - $protocol = 'https'; - } - break; - - default : - if (substr($key,0,5)==='HTTP_') { - // It's a HTTP header - - // Normalizing it to be prettier - $header = strtolower(substr($key,5)); - - // Transforming dashes into spaces, and uppercasing - // every first letter. - $header = ucwords(str_replace('_', ' ', $header)); - - // Turning spaces into dashes. - $header = str_replace(' ', '-', $header); - $headers[$header] = $value; - - } - break; - - - } - - } - - $r = new Request($method, $url, $headers); - $r->setHttpVersion($httpVersion); - $r->setRawServerData($serverArray); - $r->setAbsoluteUrl($protocol . '://' . $hostName . $url); - return $r; - - } - -} diff --git a/src/serverlib/3rdparty/sabre/HTTP/URLUtil.php b/src/serverlib/3rdparty/sabre/HTTP/URLUtil.php deleted file mode 100644 index 0b49c8b..0000000 --- a/src/serverlib/3rdparty/sabre/HTTP/URLUtil.php +++ /dev/null @@ -1,218 +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'); - - } - - /** - * This method can be used to aid with content negotiation. - * - * It takes 2 arguments, the $acceptHeaderValue, which usually comes from - * an Accept header, and $availableOptions, which contains an array of - * items that the server can support. - * - * The result of this function will be the 'best possible option'. If no - * best possible option could be found, null is returned. - * - * When it's null you can according to the spec either return a default, or - * you can choose to emit 406 Not Acceptable. - * - * The method also accepts sending 'null' for the $acceptHeaderValue, - * implying that no accept header was sent. - * - * @param string|null $acceptHeaderValue - * @param array $availableOptions - * @return string|null - */ - static function negotiateContentType($acceptHeaderValue, array $availableOptions) { - - if (!$acceptHeaderValue) { - // Grabbing the first in the list. - return reset($availableOptions); - } - - $proposals = array_map( - ['self', 'parseMimeType'], - explode(',', $acceptHeaderValue) - ); - - // Ensuring array keys are reset. - $availableOptions = array_values($availableOptions); - - $options = array_map( - ['self', 'parseMimeType'], - $availableOptions - ); - - $lastQuality = 0; - $lastSpecificity = 0; - $lastOptionIndex = 0; - $lastChoice = null; - - foreach($proposals as $proposal) { - - // Ignoring broken values. - if (is_null($proposal)) continue; - - // If the quality is lower we don't have to bother comparing. - if ($proposal['quality'] < $lastQuality) { - continue; - } - - foreach($options as $optionIndex => $option) { - - if ($proposal['type'] !== '*' && $proposal['type'] !== $option['type']) { - // no match on type. - continue; - } - if ($proposal['subType'] !== '*' && $proposal['subType'] !== $option['subType']) { - // no match on subtype. - continue; - } - - // Any parameters appearing on the options must appear on - // proposals. - foreach($option['parameters'] as $paramName => $paramValue) { - if (!array_key_exists($paramName, $proposal['parameters'])) { - continue 2; - } - if ($paramValue !== $proposal['parameters'][$paramName]) { - continue 2; - } - } - - // If we got here, we have a match on parameters, type and - // subtype. We need to calculate a score for how specific the - // match was. - $specificity = - ($proposal['type']!=='*'?20:0) + - ($proposal['subType']!=='*'?10:0) + - count($option['parameters']); - - - // Does this entry win? - if ( - ($proposal['quality'] > $lastQuality) || - ($proposal['quality'] === $lastQuality && $specificity > $lastSpecificity) || - ($proposal['quality'] === $lastQuality && $specificity === $lastSpecificity && $optionIndex < $lastOptionIndex) - ) { - - $lastQuality = $proposal['quality']; - $lastSpecificity = $specificity; - $lastOptionIndex = $optionIndex; - $lastChoice = $availableOptions[$optionIndex]; - - } - - } - - } - - return $lastChoice; - - } - - /** - * Parses a mime-type and splits it into: - * - * 1. type - * 2. subtype - * 3. quality - * 4. parameters - * - * @param string $str - * @return array - */ - private static function parseMimeType($str) { - - $parameters = []; - // If no q= parameter appears, then quality = 1. - $quality = 1; - - $parts = explode(';', $str); - - // The first part is the mime-type. - $mimeType = array_shift($parts); - - $mimeType = explode('/', trim($mimeType)); - if (count($mimeType)!==2) { - // Illegal value - return null; - } - list($type, $subType) = $mimeType; - - foreach($parts as $part) { - - $part = trim($part); - if (strpos($part, '=')) { - list($partName, $partValue) = - explode('=', $part, 2); - } else { - $partName = $part; - $partValue = null; - } - - // The quality parameter, if it appears, also marks the end of - // the parameter list. Anything after the q= counts as an - // 'accept extension' and could introduce new semantics in - // content-negotation. - if ($partName!=='q') { - $parameters[$partName] = $part; - } else { - $quality = (float)$partValue; - break; // Stop parsing parts - } - - } - - return [ - 'type' => $type, - 'subType' => $subType, - 'quality' => $quality, - 'parameters' => $parameters, - ]; - - } - - /** - * Deprecated! Use negotiateContentType. - * - * @deprecated - * @param string|null $acceptHeader - * @param array $availableOptions - * @return string|null - */ - static function negotiate($acceptHeaderValue, array $availableOptions) { - - return self::negotiateContentType($acceptHeaderValue, $availableOptions); - - } - -} diff --git a/src/serverlib/3rdparty/sabre/HTTP/Version.php b/src/serverlib/3rdparty/sabre/HTTP/Version.php deleted file mode 100644 index 5c66d44..0000000 --- a/src/serverlib/3rdparty/sabre/HTTP/Version.php +++ /dev/null @@ -1,19 +0,0 @@ -component !== 'VEVENT') { - return false; - } - - switch($itipMessage->method) { - - case 'REQUEST' : - return $this->processMessageRequest($itipMessage, $existingObject); - - case 'CANCEL' : - return $this->processMessageCancel($itipMessage, $existingObject); - - case 'REPLY' : - return $this->processMessageReply($itipMessage, $existingObject); - - default : - // Unsupported iTip message - return null; - - } - - return $existingObject; - - } - - /** - * This function parses a VCALENDAR object and figure out if any messages - * need to be sent. - * - * A VCALENDAR object will be created from the perspective of either an - * attendee, or an organizer. You must pass a string identifying the - * current user, so we can figure out who in the list of attendees or the - * organizer we are sending this message on behalf of. - * - * It's possible to specify the current user as an array, in case the user - * has more than one identifying href (such as multiple emails). - * - * It $oldCalendar is specified, it is assumed that the operation is - * updating an existing event, which means that we need to look at the - * differences between events, and potentially send old attendees - * cancellations, and current attendees updates. - * - * If $calendar is null, but $oldCalendar is specified, we treat the - * operation as if the user has deleted an event. If the user was an - * organizer, this means that we need to send cancellation notices to - * people. If the user was an attendee, we need to make sure that the - * organizer gets the 'declined' message. - * - * @param VCalendar|string $calendar - * @param string|array $userHref - * @param VCalendar|string $oldCalendar - * @return array - */ - public function parseEvent($calendar = null, $userHref, $oldCalendar = null) { - - if ($oldCalendar) { - if (is_string($oldCalendar)) { - $oldCalendar = Reader::read($oldCalendar); - } - if (!isset($oldCalendar->VEVENT)) { - // We only support events at the moment - return array(); - } - - $oldEventInfo = $this->parseEventInfo($oldCalendar); - } else { - $oldEventInfo = array( - 'organizer' => null, - 'significantChangeHash' => '', - 'attendees' => array(), - ); - } - - $userHref = (array)$userHref; - - if (!is_null($calendar)) { - - if (is_string($calendar)) { - $calendar = Reader::read($calendar); - } - if (!isset($calendar->VEVENT)) { - // We only support events at the moment - return array(); - } - $eventInfo = $this->parseEventInfo($calendar); - if (!$eventInfo['attendees'] && !$oldEventInfo['attendees']) { - // If there were no attendees on either side of the equation, - // we don't need to do anything. - return array(); - } - if (!$eventInfo['organizer'] && !$oldEventInfo['organizer']) { - // There was no organizer before or after the change. - return array(); - } - - $baseCalendar = $calendar; - - // If the new object didn't have an organizer, the organizer - // changed the object from a scheduling object to a non-scheduling - // object. We just copy the info from the old object. - if (!$eventInfo['organizer'] && $oldEventInfo['organizer']) { - $eventInfo['organizer'] = $oldEventInfo['organizer']; - $eventInfo['organizerName'] = $oldEventInfo['organizerName']; - } - - } else { - // The calendar object got deleted, we need to process this as a - // cancellation / decline. - if (!$oldCalendar) { - // No old and no new calendar, there's no thing to do. - return array(); - } - - $eventInfo = $oldEventInfo; - - if (in_array($eventInfo['organizer'], $userHref)) { - // This is an organizer deleting the event. - $eventInfo['attendees'] = array(); - // Increasing the sequence, but only if the organizer deleted - // the event. - $eventInfo['sequence']++; - } else { - // This is an attendee deleting the event. - foreach($eventInfo['attendees'] as $key=>$attendee) { - if (in_array($attendee['href'], $userHref)) { - $eventInfo['attendees'][$key]['instances'] = array('master' => - array('id'=>'master', 'partstat' => 'DECLINED') - ); - } - } - } - $baseCalendar = $oldCalendar; - - } - - if (in_array($eventInfo['organizer'], $userHref)) { - return $this->parseEventForOrganizer($baseCalendar, $eventInfo, $oldEventInfo); - } elseif ($oldCalendar) { - // We need to figure out if the user is an attendee, but we're only - // doing so if there's an oldCalendar, because we only want to - // process updates, not creation of new events. - foreach($eventInfo['attendees'] as $attendee) { - if (in_array($attendee['href'], $userHref)) { - return $this->parseEventForAttendee($baseCalendar, $eventInfo, $oldEventInfo, $attendee['href']); - } - } - } - return array(); - - } - - /** - * Processes incoming REQUEST messages. - * - * This is message from an organizer, and is either a new event - * invite, or an update to an existing one. - * - * - * @param Message $itipMessage - * @param VCalendar $existingObject - * @return VCalendar|null - */ - protected function processMessageRequest(Message $itipMessage, VCalendar $existingObject = null) { - - if (!$existingObject) { - // This is a new invite, and we're just going to copy over - // all the components from the invite. - $existingObject = new VCalendar(); - foreach($itipMessage->message->getComponents() as $component) { - $existingObject->add(clone $component); - } - } else { - // We need to update an existing object with all the new - // information. We can just remove all existing components - // and create new ones. - foreach($existingObject->getComponents() as $component) { - $existingObject->remove($component); - } - foreach($itipMessage->message->getComponents() as $component) { - $existingObject->add(clone $component); - } - } - return $existingObject; - - } - - /** - * Processes incoming CANCEL messages. - * - * This is a message from an organizer, and means that either an - * attendee got removed from an event, or an event got cancelled - * altogether. - * - * @param Message $itipMessage - * @param VCalendar $existingObject - * @return VCalendar|null - */ - protected function processMessageCancel(Message $itipMessage, VCalendar $existingObject = null) { - - if (!$existingObject) { - // The event didn't exist in the first place, so we're just - // ignoring this message. - } else { - foreach($existingObject->VEVENT as $vevent) { - $vevent->STATUS = 'CANCELLED'; - $vevent->SEQUENCE = $itipMessage->sequence; - } - } - return $existingObject; - - } - - /** - * Processes incoming REPLY messages. - * - * The message is a reply. This is for example an attendee telling - * an organizer he accepted the invite, or declined it. - * - * @param Message $itipMessage - * @param VCalendar $existingObject - * @return VCalendar|null - */ - protected function processMessageReply(Message $itipMessage, VCalendar $existingObject = null) { - - // A reply can only be processed based on an existing object. - // If the object is not available, the reply is ignored. - if (!$existingObject) { - return null; - } - $instances = array(); - $requestStatus = '2.0'; - - // Finding all the instances the attendee replied to. - foreach($itipMessage->message->VEVENT as $vevent) { - $recurId = isset($vevent->{'RECURRENCE-ID'})?$vevent->{'RECURRENCE-ID'}->getValue():'master'; - $attendee = $vevent->ATTENDEE; - $instances[$recurId] = $attendee['PARTSTAT']->getValue(); - if (isset($vevent->{'REQUEST-STATUS'})) { - $requestStatus = $vevent->{'REQUEST-STATUS'}->getValue(); - list($requestStatus) = explode(';', $requestStatus); - } - } - - // Now we need to loop through the original organizer event, to find - // all the instances where we have a reply for. - $masterObject = null; - foreach($existingObject->VEVENT as $vevent) { - $recurId = isset($vevent->{'RECURRENCE-ID'})?$vevent->{'RECURRENCE-ID'}->getValue():'master'; - if ($recurId==='master') { - $masterObject = $vevent; - } - if (isset($instances[$recurId])) { - $attendeeFound = false; - if (isset($vevent->ATTENDEE)) { - foreach($vevent->ATTENDEE as $attendee) { - if ($attendee->getValue() === $itipMessage->sender) { - $attendeeFound = true; - $attendee['PARTSTAT'] = $instances[$recurId]; - $attendee['SCHEDULE-STATUS'] = $requestStatus; - // Un-setting the RSVP status, because we now know - // that the attende already replied. - unset($attendee['RSVP']); - break; - } - } - } - if (!$attendeeFound) { - // Adding a new attendee. The iTip documentation calls this - // a party crasher. - $attendee = $vevent->add('ATTENDEE', $itipMessage->sender, array( - 'PARTSTAT' => $instances[$recurId] - )); - if ($itipMessage->senderName) $attendee['CN'] = $itipMessage->senderName; - } - unset($instances[$recurId]); - } - } - - if(!$masterObject) { - // No master object, we can't add new instances. - return null; - } - // If we got replies to instances that did not exist in the - // original list, it means that new exceptions must be created. - foreach($instances as $recurId=>$partstat) { - - $recurrenceIterator = new EventIterator($existingObject, $itipMessage->uid); - $found = false; - $iterations = 1000; - do { - - $newObject = $recurrenceIterator->getEventObject(); - $recurrenceIterator->next(); - - if (isset($newObject->{'RECURRENCE-ID'}) && $newObject->{'RECURRENCE-ID'}->getValue()===$recurId) { - $found = true; - } - $iterations--; - - } while($recurrenceIterator->valid() && !$found && $iterations); - - // Invalid recurrence id. Skipping this object. - if (!$found) continue; - - unset( - $newObject->RRULE, - $newObject->EXDATE, - $newObject->RDATE - ); - $attendeeFound = false; - if (isset($newObject->ATTENDEE)) { - foreach($newObject->ATTENDEE as $attendee) { - if ($attendee->getValue() === $itipMessage->sender) { - $attendeeFound = true; - $attendee['PARTSTAT'] = $partstat; - break; - } - } - } - if (!$attendeeFound) { - // Adding a new attendee - $attendee = $newObject->add('ATTENDEE', $itipMessage->sender, array( - 'PARTSTAT' => $partstat - )); - if ($itipMessage->senderName) { - $attendee['CN'] = $itipMessage->senderName; - } - } - $existingObject->add($newObject); - - } - return $existingObject; - - } - - /** - * This method is used in cases where an event got updated, and we - * potentially need to send emails to attendees to let them know of updates - * in the events. - * - * We will detect which attendees got added, which got removed and create - * specific messages for these situations. - * - * @param VCalendar $calendar - * @param array $eventInfo - * @param array $oldEventInfo - * @return array - */ - protected function parseEventForOrganizer(VCalendar $calendar, array $eventInfo, array $oldEventInfo) { - - // Merging attendee lists. - $attendees = array(); - foreach($oldEventInfo['attendees'] as $attendee) { - $attendees[$attendee['href']] = array( - 'href' => $attendee['href'], - 'oldInstances' => $attendee['instances'], - 'newInstances' => array(), - 'name' => $attendee['name'], - 'forceSend' => null, - ); - } - foreach($eventInfo['attendees'] as $attendee) { - if (isset($attendees[$attendee['href']])) { - $attendees[$attendee['href']]['name'] = $attendee['name']; - $attendees[$attendee['href']]['newInstances'] = $attendee['instances']; - $attendees[$attendee['href']]['forceSend'] = $attendee['forceSend']; - } else { - $attendees[$attendee['href']] = array( - 'href' => $attendee['href'], - 'oldInstances' => array(), - 'newInstances' => $attendee['instances'], - 'name' => $attendee['name'], - 'forceSend' => $attendee['forceSend'], - ); - } - } - - $messages = array(); - - foreach($attendees as $attendee) { - - // An organizer can also be an attendee. We should not generate any - // messages for those. - if ($attendee['href']===$eventInfo['organizer']) { - continue; - } - - $message = new Message(); - $message->uid = $eventInfo['uid']; - $message->component = 'VEVENT'; - $message->sequence = $eventInfo['sequence']; - $message->sender = $eventInfo['organizer']; - $message->senderName = $eventInfo['organizerName']; - $message->recipient = $attendee['href']; - $message->recipientName = $attendee['name']; - - if (!$attendee['newInstances']) { - - // If there are no instances the attendee is a part of, it - // means the attendee was removed and we need to send him a - // CANCEL. - $message->method = 'CANCEL'; - - // Creating the new iCalendar body. - $icalMsg = new VCalendar(); - $icalMsg->METHOD = $message->method; - $event = $icalMsg->add('VEVENT', array( - 'UID' => $message->uid, - 'SEQUENCE' => $message->sequence, - )); - if (isset($calendar->VEVENT->SUMMARY)) { - $event->add('SUMMARY', $calendar->VEVENT->SUMMARY->getValue()); - } - $event->add(clone $calendar->VEVENT->DTSTART); - if (isset($calendar->VEVENT->DTEND)) { - $event->add(clone $calendar->VEVENT->DTEND); - } elseif (isset($calendar->VEVENT->DURATION)) { - $event->add(clone $calendar->VEVENT->DURATION); - } - $org = $event->add('ORGANIZER', $eventInfo['organizer']); - if ($eventInfo['organizerName']) $org['CN'] = $eventInfo['organizerName']; - $event->add('ATTENDEE', $attendee['href'], array( - 'CN' => $attendee['name'], - )); - $message->significantChange = true; - - } else { - - // The attendee gets the updated event body - $message->method = 'REQUEST'; - - // Creating the new iCalendar body. - $icalMsg = new VCalendar(); - $icalMsg->METHOD = $message->method; - - foreach($calendar->select('VTIMEZONE') as $timezone) { - $icalMsg->add(clone $timezone); - } - - // We need to find out that this change is significant. If it's - // not, systems may opt to not send messages. - // - // We do this based on the 'significantChangeHash' which is - // some value that changes if there's a certain set of - // properties changed in the event, or simply if there's a - // difference in instances that the attendee is invited to. - - $message->significantChange = - $attendee['forceSend'] === 'REQUEST' || - array_keys($attendee['oldInstances']) != array_keys($attendee['newInstances']) || - $oldEventInfo['significantChangeHash']!==$eventInfo['significantChangeHash']; - - foreach($attendee['newInstances'] as $instanceId => $instanceInfo) { - - $currentEvent = clone $eventInfo['instances'][$instanceId]; - if ($instanceId === 'master') { - - // We need to find a list of events that the attendee - // is not a part of to add to the list of exceptions. - $exceptions = array(); - foreach($eventInfo['instances'] as $instanceId=>$vevent) { - if (!isset($attendee['newInstances'][$instanceId])) { - $exceptions[] = $instanceId; - } - } - - // If there were exceptions, we need to add it to an - // existing EXDATE property, if it exists. - if ($exceptions) { - if (isset($currentEvent->EXDATE)) { - $currentEvent->EXDATE->setParts(array_merge( - $currentEvent->EXDATE->getParts(), - $exceptions - )); - } else { - $currentEvent->EXDATE = $exceptions; - } - } - - // Cleaning up any scheduling information that - // shouldn't be sent along. - unset($currentEvent->ORGANIZER['SCHEDULE-FORCE-SEND']); - unset($currentEvent->ORGANIZER['SCHEDULE-STATUS']); - - foreach($currentEvent->ATTENDEE as $attendee) { - unset($attendee['SCHEDULE-FORCE-SEND']); - unset($attendee['SCHEDULE-STATUS']); - - // We're adding PARTSTAT=NEEDS-ACTION to ensure that - // iOS shows an "Inbox Item" - if (!isset($attendee['PARTSTAT'])) { - $attendee['PARTSTAT'] = 'NEEDS-ACTION'; - } - - } - - } - - $icalMsg->add($currentEvent); - - } - - } - - $message->message = $icalMsg; - $messages[] = $message; - - } - - return $messages; - - } - - /** - * Parse an event update for an attendee. - * - * This function figures out if we need to send a reply to an organizer. - * - * @param VCalendar $calendar - * @param array $eventInfo - * @param array $oldEventInfo - * @param string $attendee - * @return Message[] - */ - protected function parseEventForAttendee(VCalendar $calendar, array $eventInfo, array $oldEventInfo, $attendee) { - - if ($this->scheduleAgentServerRules && $eventInfo['organizerScheduleAgent']==='CLIENT') { - return array(); - } - - // Don't bother generating messages for events that have already been - // cancelled. - if ($eventInfo['status']==='CANCELLED') { - return array(); - } - - $oldInstances = !empty($oldEventInfo['attendees'][$attendee]['instances']) ? - $oldEventInfo['attendees'][$attendee]['instances'] : - array(); - - $instances = array(); - foreach($oldInstances as $instance) { - - $instances[$instance['id']] = array( - 'id' => $instance['id'], - 'oldstatus' => $instance['partstat'], - 'newstatus' => null, - ); - - } - foreach($eventInfo['attendees'][$attendee]['instances'] as $instance) { - - if (isset($instances[$instance['id']])) { - $instances[$instance['id']]['newstatus'] = $instance['partstat']; - } else { - $instances[$instance['id']] = array( - 'id' => $instance['id'], - 'oldstatus' => null, - 'newstatus' => $instance['partstat'], - ); - } - - } - - // We need to also look for differences in EXDATE. If there are new - // items in EXDATE, it means that an attendee deleted instances of an - // event, which means we need to send DECLINED specifically for those - // instances. - // We only need to do that though, if the master event is not declined. - if (isset($instances['master']) && $instances['master']['newstatus'] !== 'DECLINED') { - foreach($eventInfo['exdate'] as $exDate) { - - if (!in_array($exDate, $oldEventInfo['exdate'])) { - if (isset($instances[$exDate])) { - $instances[$exDate]['newstatus'] = 'DECLINED'; - } else { - $instances[$exDate] = array( - 'id' => $exDate, - 'oldstatus' => null, - 'newstatus' => 'DECLINED', - ); - } - } - - } - } - - // Gathering a few extra properties for each instance. - foreach($instances as $recurId=>$instanceInfo) { - - if (isset($eventInfo['instances'][$recurId])) { - $instances[$recurId]['dtstart'] = clone $eventInfo['instances'][$recurId]->DTSTART; - } else { - $instances[$recurId]['dtstart'] = $recurId; - } - - } - - $message = new Message(); - $message->uid = $eventInfo['uid']; - $message->method = 'REPLY'; - $message->component = 'VEVENT'; - $message->sequence = $eventInfo['sequence']; - $message->sender = $attendee; - $message->senderName = $eventInfo['attendees'][$attendee]['name']; - $message->recipient = $eventInfo['organizer']; - $message->recipientName = $eventInfo['organizerName']; - - $icalMsg = new VCalendar(); - $icalMsg->METHOD = 'REPLY'; - - $hasReply = false; - - foreach($instances as $instance) { - - if ($instance['oldstatus']==$instance['newstatus'] && $eventInfo['organizerForceSend'] !== 'REPLY') { - // Skip - continue; - } - - $event = $icalMsg->add('VEVENT', array( - 'UID' => $message->uid, - 'SEQUENCE' => $message->sequence, - )); - $summary = isset($calendar->VEVENT->SUMMARY)?$calendar->VEVENT->SUMMARY->getValue():''; - // Adding properties from the correct source instance - if (isset($eventInfo['instances'][$instance['id']])) { - $instanceObj = $eventInfo['instances'][$instance['id']]; - $event->add(clone $instanceObj->DTSTART); - if (isset($instanceObj->DTEND)) { - $event->add(clone $instanceObj->DTEND); - } elseif (isset($instanceObj->DURATION)) { - $event->add(clone $instanceObj->DURATION); - } - if (isset($instanceObj->SUMMARY)) { - $event->add('SUMMARY', $instanceObj->SUMMARY->getValue()); - } elseif ($summary) { - $event->add('SUMMARY', $summary); - } - } else { - // This branch of the code is reached, when a reply is - // generated for an instance of a recurring event, through the - // fact that the instance has disappeared by showing up in - // EXDATE - $dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']); - // Treat is as a DATE field - if (strlen($instance['id']) <= 8) { - $recur = $event->add('DTSTART', $dt, array('VALUE' => 'DATE')); - } else { - $recur = $event->add('DTSTART', $dt); - } - if ($summary) { - $event->add('SUMMARY', $summary); - } - } - if ($instance['id'] !== 'master') { - $dt = DateTimeParser::parse($instance['id'], $eventInfo['timezone']); - // Treat is as a DATE field - if (strlen($instance['id']) <= 8) { - $recur = $event->add('RECURRENCE-ID', $dt, array('VALUE' => 'DATE')); - } else { - $recur = $event->add('RECURRENCE-ID', $dt); - } - } - $organizer = $event->add('ORGANIZER', $message->recipient); - if ($message->recipientName) { - $organizer['CN'] = $message->recipientName; - } - $attendee = $event->add('ATTENDEE', $message->sender, array( - 'PARTSTAT' => $instance['newstatus'] - )); - if ($message->senderName) { - $attendee['CN'] = $message->senderName; - } - $hasReply = true; - - } - - if ($hasReply) { - $message->message = $icalMsg; - return array($message); - } else { - return array(); - } - - } - - /** - * Returns attendee information and information about instances of an - * event. - * - * Returns an array with the following keys: - * - * 1. uid - * 2. organizer - * 3. organizerName - * 4. organizerScheduleAgent - * 5. organizerForceSend - * 6. instances - * 7. attendees - * 8. sequence - * 9. exdate - * 10. timezone - strictly the timezone on which the recurrence rule is - * based on. - * 11. significantChangeHash - * 12. status - * @param VCalendar $calendar - * @return array - */ - protected function parseEventInfo(VCalendar $calendar = null) { - - $uid = null; - $organizer = null; - $organizerName = null; - $organizerForceSend = null; - $sequence = null; - $timezone = null; - $status = null; - $organizerScheduleAgent = 'SERVER'; - - $significantChangeHash = ''; - - // Now we need to collect a list of attendees, and which instances they - // are a part of. - $attendees = array(); - - $instances = array(); - $exdate = array(); - - foreach($calendar->VEVENT as $vevent) { - - if (is_null($uid)) { - $uid = $vevent->UID->getValue(); - } else { - if ($uid !== $vevent->UID->getValue()) { - throw new ITipException('If a calendar contained more than one event, they must have the same UID.'); - } - } - - if (!isset($vevent->DTSTART)) { - throw new ITipException('An event MUST have a DTSTART property.'); - } - - if (isset($vevent->ORGANIZER)) { - if (is_null($organizer)) { - $organizer = $vevent->ORGANIZER->getNormalizedValue(); - $organizerName = isset($vevent->ORGANIZER['CN'])?$vevent->ORGANIZER['CN']:null; - } else { - if ($organizer !== $vevent->ORGANIZER->getNormalizedValue()) { - throw new SameOrganizerForAllComponentsException('Every instance of the event must have the same organizer.'); - } - } - $organizerForceSend = - isset($vevent->ORGANIZER['SCHEDULE-FORCE-SEND']) ? - strtoupper($vevent->ORGANIZER['SCHEDULE-FORCE-SEND']) : - null; - $organizerScheduleAgent = - isset($vevent->ORGANIZER['SCHEDULE-AGENT']) ? - strtoupper((string)$vevent->ORGANIZER['SCHEDULE-AGENT']) : - 'SERVER'; - } - if (is_null($sequence) && isset($vevent->SEQUENCE)) { - $sequence = $vevent->SEQUENCE->getValue(); - } - if (isset($vevent->EXDATE)) { - foreach ($vevent->select('EXDATE') as $val) { - $exdate = array_merge($exdate, $val->getParts()); - } - sort($exdate); - } - if (isset($vevent->STATUS)) { - $status = strtoupper($vevent->STATUS->getValue()); - } - - $recurId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getValue() : 'master'; - if (is_null($timezone)) { - if ($recurId === 'master') { - $timezone = $vevent->DTSTART->getDateTime()->getTimeZone(); - } else { - $timezone = $vevent->{'RECURRENCE-ID'}->getDateTime()->getTimeZone(); - } - } - if(isset($vevent->ATTENDEE)) { - foreach($vevent->ATTENDEE as $attendee) { - - if ($this->scheduleAgentServerRules && - isset($attendee['SCHEDULE-AGENT']) && - strtoupper($attendee['SCHEDULE-AGENT']->getValue()) === 'CLIENT' - ) { - continue; - } - $partStat = - isset($attendee['PARTSTAT']) ? - strtoupper($attendee['PARTSTAT']) : - 'NEEDS-ACTION'; - - $forceSend = - isset($attendee['SCHEDULE-FORCE-SEND']) ? - strtoupper($attendee['SCHEDULE-FORCE-SEND']) : - null; - - - if (isset($attendees[$attendee->getNormalizedValue()])) { - $attendees[$attendee->getNormalizedValue()]['instances'][$recurId] = array( - 'id' => $recurId, - 'partstat' => $partStat, - 'force-send' => $forceSend, - ); - } else { - $attendees[$attendee->getNormalizedValue()] = array( - 'href' => $attendee->getNormalizedValue(), - 'instances' => array( - $recurId => array( - 'id' => $recurId, - 'partstat' => $partStat, - ), - ), - 'name' => isset($attendee['CN'])?(string)$attendee['CN']:null, - 'forceSend' => $forceSend, - ); - } - - } - $instances[$recurId] = $vevent; - - } - - foreach($this->significantChangeProperties as $prop) { - if (isset($vevent->$prop)) { - $propertyValues = $vevent->select($prop); - - $significantChangeHash.=$prop.':'; - - if ($prop === 'EXDATE') { - - $significantChangeHash.= implode(',', $exdate).';'; - - } else { - - foreach($propertyValues as $val) { - $significantChangeHash.= $val->getValue().';'; - } - - } - } - } - - } - $significantChangeHash = md5($significantChangeHash); - - return compact( - 'uid', - 'organizer', - 'organizerName', - 'organizerScheduleAgent', - 'organizerForceSend', - 'instances', - 'attendees', - 'sequence', - 'exdate', - 'timezone', - 'significantChangeHash', - 'status' - ); - - } - -} diff --git a/src/serverlib/3rdparty/sabre/VObject/Recur/EventIterator.php b/src/serverlib/3rdparty/sabre/VObject/Recur/EventIterator.php deleted file mode 100644 index 506a121..0000000 --- a/src/serverlib/3rdparty/sabre/VObject/Recur/EventIterator.php +++ /dev/null @@ -1,500 +0,0 @@ -timeZone)) { - $timeZone = new DateTimeZone('UTC'); - } - $this->timeZone = $timeZone; - - if (is_array($input)) { - $events = $input; - } elseif ($input instanceof VEvent) { - // Single instance mode. - $events = array($input); - } else { - // Calendar + UID mode. - $uid = (string)$uid; - if (!$uid) { - throw new InvalidArgumentException('The UID argument is required when a VCALENDAR is passed to this constructor'); - } - if (!isset($input->VEVENT)) { - throw new InvalidArgumentException('No events found in this calendar'); - } - $events = $input->getByUID($uid); - - } - - foreach($events as $vevent) { - - if (!isset($vevent->{'RECURRENCE-ID'})) { - - $this->masterEvent = $vevent; - - } else { - - $this->exceptions[ - $vevent->{'RECURRENCE-ID'}->getDateTime($this->timeZone)->getTimeStamp() - ] = true; - $this->overriddenEvents[] = $vevent; - - } - - } - - if (!$this->masterEvent) { - // No base event was found. CalDAV does allow cases where only - // overridden instances are stored. - // - // In this particular case, we're just going to grab the first - // event and use that instead. This may not always give the - // desired result. - if (!count($this->overriddenEvents)) { - throw new InvalidArgumentException('This VCALENDAR did not have an event with UID: ' . $uid); - } - $this->masterEvent = array_shift($this->overriddenEvents); - } - - $this->startDate = $this->masterEvent->DTSTART->getDateTime($this->timeZone); - $this->allDay = !$this->masterEvent->DTSTART->hasTime(); - - if (isset($this->masterEvent->EXDATE)) { - - foreach($this->masterEvent->EXDATE as $exDate) { - - foreach($exDate->getDateTimes($this->timeZone) as $dt) { - $this->exceptions[$dt->getTimeStamp()] = true; - } - - } - - } - - if (isset($this->masterEvent->DTEND)) { - $this->eventDuration = - $this->masterEvent->DTEND->getDateTime($this->timeZone)->getTimeStamp() - - $this->startDate->getTimeStamp(); - } elseif (isset($this->masterEvent->DURATION)) { - $duration = $this->masterEvent->DURATION->getDateInterval(); - $end = clone $this->startDate; - $end->add($duration); - $this->eventDuration = $end->getTimeStamp() - $this->startDate->getTimeStamp(); - } elseif ($this->allDay) { - $this->eventDuration = 3600 * 24; - } else { - $this->eventDuration = 0; - } - - if (isset($this->masterEvent->RDATE)) { - $this->recurIterator = new RDateIterator( - $this->masterEvent->RDATE->getParts(), - $this->startDate - ); - } elseif (isset($this->masterEvent->RRULE)) { - $this->recurIterator = new RRuleIterator( - $this->masterEvent->RRULE->getParts(), - $this->startDate - ); - } else { - $this->recurIterator = new RRuleIterator( - array( - 'FREQ' => 'DAILY', - 'COUNT' => 1, - ), - $this->startDate - ); - } - - $this->rewind(); - if (!$this->valid()) { - throw new NoInstancesException('This recurrence rule does not generate any valid instances'); - } - - } - - /** - * Returns the date for the current position of the iterator. - * - * @return DateTime - */ - public function current() { - - if ($this->currentDate) { - return clone $this->currentDate; - } - - } - - /** - * This method returns the start date for the current iteration of the - * event. - * - * @return DateTime - */ - public function getDtStart() { - - if ($this->currentDate) { - return clone $this->currentDate; - } - - } - - /** - * This method returns the end date for the current iteration of the - * event. - * - * @return DateTime - */ - public function getDtEnd() { - - if (!$this->valid()) { - return null; - } - $end = clone $this->currentDate; - $end->modify('+' . $this->eventDuration . ' seconds'); - return $end; - - } - - /** - * Returns a VEVENT for the current iterations of the event. - * - * This VEVENT will have a recurrence id, and it's DTSTART and DTEND - * altered. - * - * @return VEvent - */ - public function getEventObject() { - - if ($this->currentOverriddenEvent) { - return $this->currentOverriddenEvent; - } - - $event = clone $this->masterEvent; - - // Ignoring the following block, because PHPUnit's code coverage - // ignores most of these lines, and this messes with our stats. - // - // @codeCoverageIgnoreStart - unset( - $event->RRULE, - $event->EXDATE, - $event->RDATE, - $event->EXRULE, - $event->{'RECURRENCE-ID'} - ); - // @codeCoverageIgnoreEnd - - $event->DTSTART->setDateTime($this->getDtStart(), $event->DTSTART->isFloating()); - if (isset($event->DTEND)) { - $event->DTEND->setDateTime($this->getDtEnd(), $event->DTEND->isFloating()); - } - $recurid = clone $event->DTSTART; - $recurid->name = 'RECURRENCE-ID'; - $event->add($recurid); - return $event; - - } - - /** - * Returns the current position of the iterator. - * - * This is for us simply a 0-based index. - * - * @return int - */ - public function key() { - - // The counter is always 1 ahead. - return $this->counter - 1; - - } - - /** - * This is called after next, to see if the iterator is still at a valid - * position, or if it's at the end. - * - * @return bool - */ - public function valid() { - - return !!$this->currentDate; - - } - - /** - * Sets the iterator back to the starting point. - */ - public function rewind() { - - $this->recurIterator->rewind(); - // re-creating overridden event index. - $index = array(); - foreach($this->overriddenEvents as $key=>$event) { - $stamp = $event->DTSTART->getDateTime($this->timeZone)->getTimeStamp(); - $index[$stamp][] = $key; - } - krsort($index); - $this->counter = 0; - $this->overriddenEventsIndex = $index; - $this->currentOverriddenEvent = null; - - $this->nextDate = null; - $this->currentDate = clone $this->startDate; - - $this->next(); - - } - - /** - * Advances the iterator with one step. - * - * @return void - */ - public function next() { - - $this->currentOverriddenEvent = null; - $this->counter++; - if ($this->nextDate) { - // We had a stored value. - $nextDate = $this->nextDate; - $this->nextDate = null; - } else { - // We need to ask rruleparser for the next date. - // We need to do this until we find a date that's not in the - // exception list. - do { - if (!$this->recurIterator->valid()) { - $nextDate = null; - break; - } - $nextDate = $this->recurIterator->current(); - $this->recurIterator->next(); - } while(isset($this->exceptions[$nextDate->getTimeStamp()])); - - } - - - // $nextDate now contains what rrule thinks is the next one, but an - // overridden event may cut ahead. - if ($this->overriddenEventsIndex) { - - $offsets = end($this->overriddenEventsIndex); - $timestamp = key($this->overriddenEventsIndex); - $offset = end($offsets); - if (!$nextDate || $timestamp < $nextDate->getTimeStamp()) { - // Overridden event comes first. - $this->currentOverriddenEvent = $this->overriddenEvents[$offset]; - - // Putting the rrule next date aside. - $this->nextDate = $nextDate; - $this->currentDate = $this->currentOverriddenEvent->DTSTART->getDateTime($this->timeZone); - - // Ensuring that this item will only be used once. - array_pop($this->overriddenEventsIndex[$timestamp]); - if (!$this->overriddenEventsIndex[$timestamp]) { - array_pop($this->overriddenEventsIndex); - } - - // Exit point! - return; - - } - - } - - $this->currentDate = $nextDate; - - } - - /** - * Quickly jump to a date in the future. - * - * @param DateTime $dateTime - */ - public function fastForward(DateTime $dateTime) { - - while($this->valid() && $this->getDtEnd() < $dateTime ) { - $this->next(); - } - - } - - /** - * Returns true if this recurring event never ends. - * - * @return bool - */ - public function isInfinite() { - - return $this->recurIterator->isInfinite(); - - } - - /** - * RRULE parser - * - * @var RRuleIterator - */ - protected $recurIterator; - - /** - * The duration, in seconds, of the master event. - * - * We use this to calculate the DTEND for subsequent events. - */ - protected $eventDuration; - - /** - * A reference to the main (master) event. - * - * @var VEVENT - */ - protected $masterEvent; - - /** - * List of overridden events. - * - * @var array - */ - protected $overriddenEvents = array(); - - /** - * Overridden event index. - * - * Key is timestamp, value is the list of indexes of the item in the $overriddenEvent - * property. - * - * @var array - */ - protected $overriddenEventsIndex; - - /** - * A list of recurrence-id's that are either part of EXDATE, or are - * overridden. - * - * @var array - */ - protected $exceptions = array(); - - /** - * Internal event counter - * - * @var int - */ - protected $counter; - - /** - * The very start of the iteration process. - * - * @var DateTime - */ - protected $startDate; - - /** - * Where we are currently in the iteration process - * - * @var DateTime - */ - protected $currentDate; - - /** - * The next date from the rrule parser. - * - * Sometimes we need to temporary store the next date, because an - * overridden event came before. - * - * @var DateTime - */ - protected $nextDate; - -} diff --git a/src/serverlib/3rdparty/sabre/VObject/Version.php b/src/serverlib/3rdparty/sabre/VObject/Version.php deleted file mode 100644 index e891587..0000000 --- a/src/serverlib/3rdparty/sabre/VObject/Version.php +++ /dev/null @@ -1,19 +0,0 @@ -groupObject = $this->userObject->GetGroup(); $this->userRow = $this->userObject->Fetch(); $this->groupRow = $this->groupObject->Fetch(); - + // check privileges if($this->checkPermissions()) { $this->setupState(); return(true); } - else + else { // log PutLog(sprintf('DAV login as <%s> failed (permission denied)', @@ -112,7 +96,7 @@ abstract class BMAuthBackend extends Sabre\DAV\Auth\Backend\AbstractBasic __FILE__, __LINE__); } - + return(false); } }