diff --git a/.gitignore b/.gitignore index f475acc55..fba4789f8 100644 --- a/.gitignore +++ b/.gitignore @@ -96,6 +96,9 @@ icewind/streams/tests icewind/streams/README.md icewind/streams/LICENCE icewind/streams/composer.json +icewind/searchdav/tests +icewind/searchdav/README.md +icewind/searchdav/composer.json swiftmailer/swiftmailer/.travis.yml swiftmailer/swiftmailer/phpunit.xml.dist diff --git a/composer.json b/composer.json index 1e9db8a8e..1265adfb1 100644 --- a/composer.json +++ b/composer.json @@ -36,6 +36,7 @@ "symfony/polyfill-php70": "^1.0", "lukasreschke/id3parser": "^0.0.1", "stecman/symfony-console-completion": "^0.7.0", - "leafo/scssphp": "^0.6.6" + "leafo/scssphp": "^0.6.6", + "icewind/searchdav": "0.2.0" } } diff --git a/composer.lock b/composer.lock index 3fe023e96..860cc777c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "4388bb420f4cbfdefd856a6c0007df11", + "content-hash": "20290e8ec883e5cbfcbe14dc9bc3cfe8", "packages": [ { "name": "bantu/ini-get-wrapper", @@ -902,6 +902,47 @@ ], "time": "2014-10-12T19:18:40+00:00" }, + { + "name": "icewind/searchdav", + "version": "v0.2.0", + "source": { + "type": "git", + "url": "https://github.com/icewind1991/SearchDAV.git", + "reference": "5c2a3f1b021ccd18a12f8aa6b196c07fefac67ee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/icewind1991/SearchDAV/zipball/5c2a3f1b021ccd18a12f8aa6b196c07fefac67ee", + "reference": "5c2a3f1b021ccd18a12f8aa6b196c07fefac67ee", + "shasum": "" + }, + "require": { + "php": ">=5.6", + "sabre/dav": "^3.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "SearchDAV\\": "src/", + "SearchDAV\\Test\\": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "AGPL" + ], + "authors": [ + { + "name": "Robin Appelman", + "email": "robin@icewind.nl" + } + ], + "description": "sabre/dav plugin to implement rfc5323 SEARCH", + "time": "2017-02-22T17:14:08+00:00" + }, { "name": "icewind/streams", "version": "0.5.2", @@ -1317,7 +1358,7 @@ "css", "minify" ], - "time": "2014-09-10T14:34:00+00:00" + "time": "2014-09-10 14:34:00" }, { "name": "nikic/php-parser", diff --git a/composer/autoload_classmap.php b/composer/autoload_classmap.php index 89ed46d39..1eabd5e30 100644 --- a/composer/autoload_classmap.php +++ b/composer/autoload_classmap.php @@ -615,6 +615,20 @@ 'Icewind\\Streams\\PathWrapper' => $vendorDir . '/icewind/streams/src/PathWrapper.php', 'Icewind\\Streams\\RetryWrapper' => $vendorDir . '/icewind/streams/src/RetryWrapper.php', 'Icewind\\Streams\\SeekableWrapper' => $vendorDir . '/icewind/streams/src/SeekableWrapper.php', + 'Icewind\\Streams\\Tests\\CallbackWrapper' => $vendorDir . '/icewind/streams/tests/CallbackWrapper.php', + 'Icewind\\Streams\\Tests\\DirectoryFilter' => $vendorDir . '/icewind/streams/tests/DirectoryFilter.php', + 'Icewind\\Streams\\Tests\\DirectoryWrapper' => $vendorDir . '/icewind/streams/tests/DirectoryWrapper.php', + 'Icewind\\Streams\\Tests\\DirectoryWrapperDummy' => $vendorDir . '/icewind/streams/tests/DirectoryWrapper.php', + 'Icewind\\Streams\\Tests\\DirectoryWrapperNull' => $vendorDir . '/icewind/streams/tests/DirectoryWrapper.php', + 'Icewind\\Streams\\Tests\\FailWrapper' => $vendorDir . '/icewind/streams/tests/RetryWrapper.php', + 'Icewind\\Streams\\Tests\\IteratorDirectory' => $vendorDir . '/icewind/streams/tests/IteratorDirectory.php', + 'Icewind\\Streams\\Tests\\NullWrapper' => $vendorDir . '/icewind/streams/tests/NullWrapper.php', + 'Icewind\\Streams\\Tests\\PartialWrapper' => $vendorDir . '/icewind/streams/tests/RetryWrapper.php', + 'Icewind\\Streams\\Tests\\PathWrapper' => $vendorDir . '/icewind/streams/tests/PathWrapper.php', + 'Icewind\\Streams\\Tests\\RetryWrapper' => $vendorDir . '/icewind/streams/tests/RetryWrapper.php', + 'Icewind\\Streams\\Tests\\SeekableWrapper' => $vendorDir . '/icewind/streams/tests/SeekableWrapper.php', + 'Icewind\\Streams\\Tests\\UrlCallBack' => $vendorDir . '/icewind/streams/tests/UrlCallBack.php', + 'Icewind\\Streams\\Tests\\Wrapper' => $vendorDir . '/icewind/streams/tests/Wrapper.php', 'Icewind\\Streams\\Url' => $vendorDir . '/icewind/streams/src/Url.php', 'Icewind\\Streams\\UrlCallback' => $vendorDir . '/icewind/streams/src/UrlCallBack.php', 'Icewind\\Streams\\Wrapper' => $vendorDir . '/icewind/streams/src/Wrapper.php', @@ -1350,6 +1364,27 @@ 'Sabre\\Xml\\Writer' => $vendorDir . '/sabre/xml/lib/Writer.php', 'Sabre\\Xml\\XmlDeserializable' => $vendorDir . '/sabre/xml/lib/XmlDeserializable.php', 'Sabre\\Xml\\XmlSerializable' => $vendorDir . '/sabre/xml/lib/XmlSerializable.php', + 'SearchDAV\\Backend\\ISearchBackend' => $vendorDir . '/icewind/searchdav/src/Backend/ISearchBackend.php', + 'SearchDAV\\Backend\\SearchPropertyDefinition' => $vendorDir . '/icewind/searchdav/src/Backend/SearchPropertyDefinition.php', + 'SearchDAV\\Backend\\SearchResult' => $vendorDir . '/icewind/searchdav/src/Backend/SearchResult.php', + 'SearchDAV\\DAV\\DiscoverHandler' => $vendorDir . '/icewind/searchdav/src/DAV/DiscoverHandler.php', + 'SearchDAV\\DAV\\PathHelper' => $vendorDir . '/icewind/searchdav/src/DAV/PathHelper.php', + 'SearchDAV\\DAV\\QueryParser' => $vendorDir . '/icewind/searchdav/src/DAV/QueryParser.php', + 'SearchDAV\\DAV\\SearchHandler' => $vendorDir . '/icewind/searchdav/src/DAV/SearchHandler.php', + 'SearchDAV\\DAV\\SearchPlugin' => $vendorDir . '/icewind/searchdav/src/DAV/SearchPlugin.php', + 'SearchDAV\\Test\\DummyBackend' => $vendorDir . '/icewind/searchdav/tests/DummyBackend.php', + 'SearchDAV\\Test\\PathHelperTest' => $vendorDir . '/icewind/searchdav/tests/PathHelperTest.php', + 'SearchDAV\\Test\\QueryParserTest' => $vendorDir . '/icewind/searchdav/tests/QueryParserTest.php', + 'SearchDAV\\Test\\SearchPluginTest' => $vendorDir . '/icewind/searchdav/tests/SearchPluginTest.php', + 'SearchDAV\\XML\\BasicSearch' => $vendorDir . '/icewind/searchdav/src/XML/BasicSearch.php', + 'SearchDAV\\XML\\BasicSearchSchema' => $vendorDir . '/icewind/searchdav/src/XML/BasicSearchSchema.php', + 'SearchDAV\\XML\\Literal' => $vendorDir . '/icewind/searchdav/src/XML/Literal.php', + 'SearchDAV\\XML\\Operator' => $vendorDir . '/icewind/searchdav/src/XML/Operator.php', + 'SearchDAV\\XML\\Order' => $vendorDir . '/icewind/searchdav/src/XML/Order.php', + 'SearchDAV\\XML\\PropDesc' => $vendorDir . '/icewind/searchdav/src/XML/PropDesc.php', + 'SearchDAV\\XML\\QueryDiscoverResponse' => $vendorDir . '/icewind/searchdav/src/XML/QueryDiscoverResponse.php', + 'SearchDAV\\XML\\Scope' => $vendorDir . '/icewind/searchdav/src/XML/Scope.php', + 'SearchDAV\\XML\\SupportedQueryGrammar' => $vendorDir . '/icewind/searchdav/src/XML/SupportedQueryGrammar.php', 'Stecman\\Component\\Symfony\\Console\\BashCompletion\\Completion' => $vendorDir . '/stecman/symfony-console-completion/src/Completion.php', 'Stecman\\Component\\Symfony\\Console\\BashCompletion\\CompletionCommand' => $vendorDir . '/stecman/symfony-console-completion/src/CompletionCommand.php', 'Stecman\\Component\\Symfony\\Console\\BashCompletion\\CompletionContext' => $vendorDir . '/stecman/symfony-console-completion/src/CompletionContext.php', diff --git a/composer/autoload_files.php b/composer/autoload_files.php index dd1cda08a..c6394740b 100644 --- a/composer/autoload_files.php +++ b/composer/autoload_files.php @@ -7,15 +7,15 @@ return array( '383eaff206634a77a1be54e64e6459c7' => $vendorDir . '/sabre/uri/lib/functions.php', - '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', '3569eecfeed3bcf0bad3c998a494ecb8' => $vendorDir . '/sabre/xml/lib/Deserializer/functions.php', '93aa591bc4ca510c520999e34229ee79' => $vendorDir . '/sabre/xml/lib/Serializer/functions.php', '2b9d0f43f9552984cfa82fee95491826' => $vendorDir . '/sabre/event/lib/coroutine.php', 'd81bab31d3feb45bfe2f283ea3c8fdf7' => $vendorDir . '/sabre/event/lib/Loop/functions.php', 'a1cce3d26cc15c00fcd0b3354bd72c88' => $vendorDir . '/sabre/event/lib/Promise/functions.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', 'ad155f8f1cf0d418fe49e248db8c661b' => $vendorDir . '/react/promise/src/functions_include.php', - 'fe1bcd0336136e435eaf197895daf81a' => $vendorDir . '/nikic/php-parser/lib/bootstrap.php', 'ebdb698ed4152ae445614b69b5e4bb6a' => $vendorDir . '/sabre/http/lib/functions.php', + 'fe1bcd0336136e435eaf197895daf81a' => $vendorDir . '/nikic/php-parser/lib/bootstrap.php', '5255c38a0faeba867671b61dfda6d864' => $vendorDir . '/paragonie/random_compat/lib/random.php', '023d27dca8066ef29e6739335ea73bad' => $vendorDir . '/symfony/polyfill-php70/bootstrap.php', 'decc78cc4436b1292c6c0d151b19445c' => $vendorDir . '/phpseclib/phpseclib/phpseclib/bootstrap.php', diff --git a/composer/autoload_psr4.php b/composer/autoload_psr4.php index c723f2ec9..30faee423 100644 --- a/composer/autoload_psr4.php +++ b/composer/autoload_psr4.php @@ -18,6 +18,8 @@ 'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'), 'SuperClosure\\' => array($vendorDir . '/jeremeamia/SuperClosure/src'), 'Stecman\\Component\\Symfony\\Console\\BashCompletion\\' => array($vendorDir . '/stecman/symfony-console-completion/src'), + 'SearchDAV\\Test\\' => array($vendorDir . '/icewind/searchdav/tests'), + 'SearchDAV\\' => array($vendorDir . '/icewind/searchdav/src'), 'Sabre\\Xml\\' => array($vendorDir . '/sabre/xml/lib'), 'Sabre\\VObject\\' => array($vendorDir . '/sabre/vobject/lib'), 'Sabre\\Uri\\' => array($vendorDir . '/sabre/uri/lib'), diff --git a/composer/autoload_static.php b/composer/autoload_static.php index e36ae6ddb..5dae3fd9b 100644 --- a/composer/autoload_static.php +++ b/composer/autoload_static.php @@ -8,15 +8,15 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 { public static $files = array ( '383eaff206634a77a1be54e64e6459c7' => __DIR__ . '/..' . '/sabre/uri/lib/functions.php', - '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', '3569eecfeed3bcf0bad3c998a494ecb8' => __DIR__ . '/..' . '/sabre/xml/lib/Deserializer/functions.php', '93aa591bc4ca510c520999e34229ee79' => __DIR__ . '/..' . '/sabre/xml/lib/Serializer/functions.php', '2b9d0f43f9552984cfa82fee95491826' => __DIR__ . '/..' . '/sabre/event/lib/coroutine.php', 'd81bab31d3feb45bfe2f283ea3c8fdf7' => __DIR__ . '/..' . '/sabre/event/lib/Loop/functions.php', 'a1cce3d26cc15c00fcd0b3354bd72c88' => __DIR__ . '/..' . '/sabre/event/lib/Promise/functions.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', 'ad155f8f1cf0d418fe49e248db8c661b' => __DIR__ . '/..' . '/react/promise/src/functions_include.php', - 'fe1bcd0336136e435eaf197895daf81a' => __DIR__ . '/..' . '/nikic/php-parser/lib/bootstrap.php', 'ebdb698ed4152ae445614b69b5e4bb6a' => __DIR__ . '/..' . '/sabre/http/lib/functions.php', + 'fe1bcd0336136e435eaf197895daf81a' => __DIR__ . '/..' . '/nikic/php-parser/lib/bootstrap.php', '5255c38a0faeba867671b61dfda6d864' => __DIR__ . '/..' . '/paragonie/random_compat/lib/random.php', '023d27dca8066ef29e6739335ea73bad' => __DIR__ . '/..' . '/symfony/polyfill-php70/bootstrap.php', 'decc78cc4436b1292c6c0d151b19445c' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/bootstrap.php', @@ -50,6 +50,8 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 'Symfony\\Component\\Console\\' => 26, 'SuperClosure\\' => 13, 'Stecman\\Component\\Symfony\\Console\\BashCompletion\\' => 49, + 'SearchDAV\\Test\\' => 15, + 'SearchDAV\\' => 10, 'Sabre\\Xml\\' => 10, 'Sabre\\VObject\\' => 14, 'Sabre\\Uri\\' => 10, @@ -144,6 +146,14 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 array ( 0 => __DIR__ . '/..' . '/stecman/symfony-console-completion/src', ), + 'SearchDAV\\Test\\' => + array ( + 0 => __DIR__ . '/..' . '/icewind/searchdav/tests', + ), + 'SearchDAV\\' => + array ( + 0 => __DIR__ . '/..' . '/icewind/searchdav/src', + ), 'Sabre\\Xml\\' => array ( 0 => __DIR__ . '/..' . '/sabre/xml/lib', @@ -935,6 +945,20 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 'Icewind\\Streams\\PathWrapper' => __DIR__ . '/..' . '/icewind/streams/src/PathWrapper.php', 'Icewind\\Streams\\RetryWrapper' => __DIR__ . '/..' . '/icewind/streams/src/RetryWrapper.php', 'Icewind\\Streams\\SeekableWrapper' => __DIR__ . '/..' . '/icewind/streams/src/SeekableWrapper.php', + 'Icewind\\Streams\\Tests\\CallbackWrapper' => __DIR__ . '/..' . '/icewind/streams/tests/CallbackWrapper.php', + 'Icewind\\Streams\\Tests\\DirectoryFilter' => __DIR__ . '/..' . '/icewind/streams/tests/DirectoryFilter.php', + 'Icewind\\Streams\\Tests\\DirectoryWrapper' => __DIR__ . '/..' . '/icewind/streams/tests/DirectoryWrapper.php', + 'Icewind\\Streams\\Tests\\DirectoryWrapperDummy' => __DIR__ . '/..' . '/icewind/streams/tests/DirectoryWrapper.php', + 'Icewind\\Streams\\Tests\\DirectoryWrapperNull' => __DIR__ . '/..' . '/icewind/streams/tests/DirectoryWrapper.php', + 'Icewind\\Streams\\Tests\\FailWrapper' => __DIR__ . '/..' . '/icewind/streams/tests/RetryWrapper.php', + 'Icewind\\Streams\\Tests\\IteratorDirectory' => __DIR__ . '/..' . '/icewind/streams/tests/IteratorDirectory.php', + 'Icewind\\Streams\\Tests\\NullWrapper' => __DIR__ . '/..' . '/icewind/streams/tests/NullWrapper.php', + 'Icewind\\Streams\\Tests\\PartialWrapper' => __DIR__ . '/..' . '/icewind/streams/tests/RetryWrapper.php', + 'Icewind\\Streams\\Tests\\PathWrapper' => __DIR__ . '/..' . '/icewind/streams/tests/PathWrapper.php', + 'Icewind\\Streams\\Tests\\RetryWrapper' => __DIR__ . '/..' . '/icewind/streams/tests/RetryWrapper.php', + 'Icewind\\Streams\\Tests\\SeekableWrapper' => __DIR__ . '/..' . '/icewind/streams/tests/SeekableWrapper.php', + 'Icewind\\Streams\\Tests\\UrlCallBack' => __DIR__ . '/..' . '/icewind/streams/tests/UrlCallBack.php', + 'Icewind\\Streams\\Tests\\Wrapper' => __DIR__ . '/..' . '/icewind/streams/tests/Wrapper.php', 'Icewind\\Streams\\Url' => __DIR__ . '/..' . '/icewind/streams/src/Url.php', 'Icewind\\Streams\\UrlCallback' => __DIR__ . '/..' . '/icewind/streams/src/UrlCallBack.php', 'Icewind\\Streams\\Wrapper' => __DIR__ . '/..' . '/icewind/streams/src/Wrapper.php', @@ -1670,6 +1694,27 @@ class ComposerStaticInit2f23f73bc0cc116b4b1eee1521aa8652 'Sabre\\Xml\\Writer' => __DIR__ . '/..' . '/sabre/xml/lib/Writer.php', 'Sabre\\Xml\\XmlDeserializable' => __DIR__ . '/..' . '/sabre/xml/lib/XmlDeserializable.php', 'Sabre\\Xml\\XmlSerializable' => __DIR__ . '/..' . '/sabre/xml/lib/XmlSerializable.php', + 'SearchDAV\\Backend\\ISearchBackend' => __DIR__ . '/..' . '/icewind/searchdav/src/Backend/ISearchBackend.php', + 'SearchDAV\\Backend\\SearchPropertyDefinition' => __DIR__ . '/..' . '/icewind/searchdav/src/Backend/SearchPropertyDefinition.php', + 'SearchDAV\\Backend\\SearchResult' => __DIR__ . '/..' . '/icewind/searchdav/src/Backend/SearchResult.php', + 'SearchDAV\\DAV\\DiscoverHandler' => __DIR__ . '/..' . '/icewind/searchdav/src/DAV/DiscoverHandler.php', + 'SearchDAV\\DAV\\PathHelper' => __DIR__ . '/..' . '/icewind/searchdav/src/DAV/PathHelper.php', + 'SearchDAV\\DAV\\QueryParser' => __DIR__ . '/..' . '/icewind/searchdav/src/DAV/QueryParser.php', + 'SearchDAV\\DAV\\SearchHandler' => __DIR__ . '/..' . '/icewind/searchdav/src/DAV/SearchHandler.php', + 'SearchDAV\\DAV\\SearchPlugin' => __DIR__ . '/..' . '/icewind/searchdav/src/DAV/SearchPlugin.php', + 'SearchDAV\\Test\\DummyBackend' => __DIR__ . '/..' . '/icewind/searchdav/tests/DummyBackend.php', + 'SearchDAV\\Test\\PathHelperTest' => __DIR__ . '/..' . '/icewind/searchdav/tests/PathHelperTest.php', + 'SearchDAV\\Test\\QueryParserTest' => __DIR__ . '/..' . '/icewind/searchdav/tests/QueryParserTest.php', + 'SearchDAV\\Test\\SearchPluginTest' => __DIR__ . '/..' . '/icewind/searchdav/tests/SearchPluginTest.php', + 'SearchDAV\\XML\\BasicSearch' => __DIR__ . '/..' . '/icewind/searchdav/src/XML/BasicSearch.php', + 'SearchDAV\\XML\\BasicSearchSchema' => __DIR__ . '/..' . '/icewind/searchdav/src/XML/BasicSearchSchema.php', + 'SearchDAV\\XML\\Literal' => __DIR__ . '/..' . '/icewind/searchdav/src/XML/Literal.php', + 'SearchDAV\\XML\\Operator' => __DIR__ . '/..' . '/icewind/searchdav/src/XML/Operator.php', + 'SearchDAV\\XML\\Order' => __DIR__ . '/..' . '/icewind/searchdav/src/XML/Order.php', + 'SearchDAV\\XML\\PropDesc' => __DIR__ . '/..' . '/icewind/searchdav/src/XML/PropDesc.php', + 'SearchDAV\\XML\\QueryDiscoverResponse' => __DIR__ . '/..' . '/icewind/searchdav/src/XML/QueryDiscoverResponse.php', + 'SearchDAV\\XML\\Scope' => __DIR__ . '/..' . '/icewind/searchdav/src/XML/Scope.php', + 'SearchDAV\\XML\\SupportedQueryGrammar' => __DIR__ . '/..' . '/icewind/searchdav/src/XML/SupportedQueryGrammar.php', 'Stecman\\Component\\Symfony\\Console\\BashCompletion\\Completion' => __DIR__ . '/..' . '/stecman/symfony-console-completion/src/Completion.php', 'Stecman\\Component\\Symfony\\Console\\BashCompletion\\CompletionCommand' => __DIR__ . '/..' . '/stecman/symfony-console-completion/src/CompletionCommand.php', 'Stecman\\Component\\Symfony\\Console\\BashCompletion\\CompletionContext' => __DIR__ . '/..' . '/stecman/symfony-console-completion/src/CompletionContext.php', diff --git a/composer/installed.json b/composer/installed.json index a8e239954..27dc09739 100644 --- a/composer/installed.json +++ b/composer/installed.json @@ -3044,5 +3044,48 @@ "persistence", "queryobject" ] + }, + { + "name": "icewind/searchdav", + "version": "v0.2.0", + "version_normalized": "0.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/icewind1991/SearchDAV.git", + "reference": "5c2a3f1b021ccd18a12f8aa6b196c07fefac67ee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/icewind1991/SearchDAV/zipball/5c2a3f1b021ccd18a12f8aa6b196c07fefac67ee", + "reference": "5c2a3f1b021ccd18a12f8aa6b196c07fefac67ee", + "shasum": "" + }, + "require": { + "php": ">=5.6", + "sabre/dav": "^3.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8" + }, + "time": "2017-02-22T17:14:08+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "SearchDAV\\": "src/", + "SearchDAV\\Test\\": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "AGPL" + ], + "authors": [ + { + "name": "Robin Appelman", + "email": "robin@icewind.nl" + } + ], + "description": "sabre/dav plugin to implement rfc5323 SEARCH" } ] diff --git a/icewind/searchdav/.gitignore b/icewind/searchdav/.gitignore new file mode 100644 index 000000000..987e2a253 --- /dev/null +++ b/icewind/searchdav/.gitignore @@ -0,0 +1,2 @@ +composer.lock +vendor diff --git a/icewind/searchdav/src/Backend/ISearchBackend.php b/icewind/searchdav/src/Backend/ISearchBackend.php new file mode 100644 index 000000000..8db8db632 --- /dev/null +++ b/icewind/searchdav/src/Backend/ISearchBackend.php @@ -0,0 +1,86 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace SearchDAV\Backend; + +use Sabre\DAV\INode; +use SearchDAV\XML\BasicSearch; +use SearchDAV\XML\Scope; + +interface ISearchBackend { + /** + * Get the path of the search arbiter of this backend + * + * The search arbiter is the URI that the client will send it's SEARCH requests to + * Note that this is not required to be the same as the search scopes which determine what to search in + * + * The returned value should be a path relative the root of the dav server. + * + * For example, if you want to support SEARCH requests on `https://example.com/dav.php/search` + * with the sabre/dav server listening on `/dav.php` you should return `search` as arbiter path. + * + * @return string + */ + public function getArbiterPath(); + + /** + * Whether or not the search backend supports search requests on this scope + * + * The scope defines the resource that it being searched, such as a folder or address book. + * + * Note that a search arbiter has no inherit limitations on which scopes it can support and scopes + * that reside on a different dav server entirely might be considered valid by an implementation. + * + * One example use case for this would be a service that provides additional indexing on a 3rd party service. + * + * @param string $href an absolute uri of the search scope + * @param string|integer $depth 0, 1 or 'infinite' + * @param string|null $path the path of the search scope relative to the dav server, or null if the scope is outside the dav server + * @return bool + */ + public function isValidScope($href, $depth, $path); + + /** + * List the available properties that can be used in search + * + * This is used to tell the search client what properties can be queried, used to filter and used to sort. + * + * Since sabre's PropFind handling mechanism is used to return the properties to the client, it's required that all + * properties which are listed as selectable have a PropFind handler set. + * + * @param string $href an absolute uri of the search scope + * @param string|null $path the path of the search scope relative to the dav server, or null if the scope is outside the dav server + * @return SearchPropertyDefinition[] + */ + public function getPropertyDefinitionsForScope($href, $path); + + /** + * Preform the search request + * + * The search results consist of the uri for the found resource and an INode describing the resource + * To return the properties requested by the query sabre's existing PropFind method is used, thus the search implementation + * is not required to collect these properties and is free to ignore the `select` part of the query + * + * @param BasicSearch $query + * @return SearchResult[] + */ + public function search(BasicSearch $query); +} diff --git a/icewind/searchdav/src/Backend/SearchPropertyDefinition.php b/icewind/searchdav/src/Backend/SearchPropertyDefinition.php new file mode 100644 index 000000000..d7de46a05 --- /dev/null +++ b/icewind/searchdav/src/Backend/SearchPropertyDefinition.php @@ -0,0 +1,65 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace SearchDAV\Backend; + +class SearchPropertyDefinition { + const XS = '{http://www.w3.org/2001/XMLSchema}'; + const DATATYPE_STRING = self::XS . 'string'; + const DATATYPE_INTEGER = self::XS . 'integer'; + const DATATYPE_NONNEGATIVE_INTEGER = self::XS . 'nonNegativeInteger'; + const DATATYPE_DECIMAL = self::XS . 'decimal'; + const DATATYPE_DATETIME = self::XS . 'dateTime'; + const DATATYPE_BOOLEAN = self::XS . 'boolean'; + + + /** @var boolean */ + public $searchable; + /** @var boolean */ + public $selectable; + /** @var boolean */ + public $sortable; + /** @var boolean */ + public $caseSensitive; + /** @var string */ + public $dataType; + /** @var string */ + public $name; + + /** + * SearchProperty constructor. + * + * @param string $name the name and namespace of the property in clark notation + * @param bool $searchable whether or not this property can be used as part of a search query + * @param bool $selectable whether or not this property can be returned as part of a search result + * @param bool $sortable whether or not this property can be used to sort the search result + * @param string $dataType the datatype of the property, one of the SearchProperty::DATATYPE_ constants or any XSD datatype in clark notation + * @param bool $caseSensitive whether or not comparisons on the property are case sensitive, only applies to string propertries + */ + public function __construct($name, $searchable, $selectable, $sortable, $dataType = self::DATATYPE_STRING, $caseSensitive = true) { + $this->searchable = $searchable; + $this->selectable = $selectable; + $this->sortable = $sortable; + $this->dataType = $dataType; + $this->name = $name; + $this->caseSensitive = $caseSensitive; + } +} diff --git a/icewind/searchdav/src/Backend/SearchResult.php b/icewind/searchdav/src/Backend/SearchResult.php new file mode 100644 index 000000000..32f99a7ec --- /dev/null +++ b/icewind/searchdav/src/Backend/SearchResult.php @@ -0,0 +1,43 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace SearchDAV\Backend; + + +use Sabre\DAV\INode; + +class SearchResult { + /** @var INode */ + public $node; + /** @var string */ + public $href; + + /** + * SearchResult constructor. + * + * @param INode $node + * @param string $href + */ + public function __construct(INode $node, $href) { + $this->node = $node; + $this->href = $href; + } +} diff --git a/icewind/searchdav/src/DAV/DiscoverHandler.php b/icewind/searchdav/src/DAV/DiscoverHandler.php new file mode 100644 index 000000000..675fd6a73 --- /dev/null +++ b/icewind/searchdav/src/DAV/DiscoverHandler.php @@ -0,0 +1,112 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace SearchDAV\DAV; + +use Sabre\DAV\Exception\BadRequest; +use Sabre\DAV\Xml\Response\MultiStatus; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; +use SearchDAV\Backend\ISearchBackend; +use SearchDAV\Backend\SearchPropertyDefinition; +use SearchDAV\XML\BasicSearch; +use SearchDAV\XML\BasicSearchSchema; +use SearchDAV\XML\PropDesc; +use SearchDAV\XML\QueryDiscoverResponse; +use SearchDAV\XML\Scope; + +class DiscoverHandler { + /** @var ISearchBackend */ + private $searchBackend; + + /** @var PathHelper */ + private $pathHelper; + + /** @var QueryParser */ + private $queryParser; + + /** + * @param ISearchBackend $searchBackend + * @param PathHelper $pathHelper + * @param QueryParser $queryParser + */ + public function __construct(ISearchBackend $searchBackend, PathHelper $pathHelper, QueryParser $queryParser) { + $this->searchBackend = $searchBackend; + $this->pathHelper = $pathHelper; + $this->queryParser = $queryParser; + } + + public function handelDiscoverRequest($xml, RequestInterface $request, ResponseInterface $response) { + if (!isset($xml['{DAV:}basicsearch'])) { + $response->setStatus(400); + $response->setBody('Unexpected xml content for query-schema-discovery, expected basicsearch'); + return false; + } + /** @var BasicSearch $query */ + $query = $xml['{DAV:}basicsearch']; + $scopes = $query->from; + $results = array_map(function (Scope $scope) { + $scope->path = $this->pathHelper->getPathFromUri($scope->href); + if ($this->searchBackend->isValidScope($scope->href, $scope->depth, $scope->path)) { + $searchProperties = $this->searchBackend->getPropertyDefinitionsForScope($scope->href, $scope->path); + $searchSchema = $this->getBasicSearchForProperties($searchProperties); + return new QueryDiscoverResponse($scope->href, $searchSchema, 200); + } else { + return new QueryDiscoverResponse($scope->href, null, 404); // TODO something other than 404? 403 maybe + } + }, $scopes); + $multiStatus = new MultiStatus($results); + $response->setStatus(207); + $response->setHeader('Content-Type', 'application/xml; charset="utf-8"'); + $response->setBody($this->queryParser->write('{DAV:}multistatus', $multiStatus, $request->getUrl())); + return false; + } + + private function hashDefinition(SearchPropertyDefinition $definition) { + return $definition->dataType + . (($definition->searchable) ? '1' : '0') + . (($definition->sortable) ? '1' : '0') + . (($definition->selectable) ? '1' : '0'); + } + + /** + * @param SearchPropertyDefinition[] $propertyDefinitions + * @return BasicSearchSchema + */ + private function getBasicSearchForProperties(array $propertyDefinitions) { + /** @var PropDesc[] $groups */ + $groups = []; + foreach ($propertyDefinitions as $propertyDefinition) { + $key = $this->hashDefinition($propertyDefinition); + if (!isset($groups[$key])) { + $desc = new PropDesc(); + $desc->dataType = $propertyDefinition->dataType; + $desc->sortable = $propertyDefinition->sortable; + $desc->selectable = $propertyDefinition->selectable; + $desc->searchable = $propertyDefinition->searchable; + $groups[$key] = $desc; + } + $groups[$key]->properties[] = $propertyDefinition->name; + } + + return new BasicSearchSchema(array_values($groups)); + } +} diff --git a/icewind/searchdav/src/DAV/PathHelper.php b/icewind/searchdav/src/DAV/PathHelper.php new file mode 100644 index 000000000..de7ea6a03 --- /dev/null +++ b/icewind/searchdav/src/DAV/PathHelper.php @@ -0,0 +1,50 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace SearchDAV\DAV; + +use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\Server; + +class PathHelper { + /** @var Server */ + private $server; + + /** + * PathHelper constructor. + * + * @param Server $server + */ + public function __construct(Server $server) { + $this->server = $server; + } + + public function getPathFromUri($uri) { + if (strpos($uri, '://') === false) { + return $uri; + } + try { + return ($uri === '' && $this->server->getBaseUri() === '/') ? '' : $this->server->calculateUri($uri); + } catch (Forbidden $e) { + return null; + } + } +} diff --git a/icewind/searchdav/src/DAV/QueryParser.php b/icewind/searchdav/src/DAV/QueryParser.php new file mode 100644 index 000000000..175be07cd --- /dev/null +++ b/icewind/searchdav/src/DAV/QueryParser.php @@ -0,0 +1,77 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace SearchDAV\DAV; + +use Sabre\Xml\Element; +use Sabre\Xml\Reader; +use Sabre\Xml\Service; +use SearchDAV\XML\BasicSearch; +use SearchDAV\XML\Literal; +use SearchDAV\XML\Operator; +use SearchDAV\XML\Order; +use SearchDAV\XML\Scope; + +class QueryParser extends Service { + public $namespaceMap = [ + 'DAV:' => 'd', + 'http://sabredav.org/ns' => 's', + 'http://www.w3.org/2001/XMLSchema' => 'xs' + ]; + + public function __construct() { + $this->elementMap = [ + '{DAV:}literal' => Literal::class, + '{DAV:}searchrequest' => Element\KeyValue::class, + '{DAV:}query-schema-discovery' => Element\KeyValue::class, + '{DAV:}basicsearch' => BasicSearch::class, + '{DAV:}select' => function (Reader $reader) { + return \Sabre\Xml\Deserializer\keyValue($reader, '{DAV:}scope')['{DAV:}prop']; + }, + '{DAV:}from' => function (Reader $reader) { + return \Sabre\Xml\Deserializer\repeatingElements($reader, '{DAV:}scope'); + }, + '{DAV:}orderby' => function (Reader $reader) { + return \Sabre\Xml\Deserializer\repeatingElements($reader, '{DAV:}order'); + }, + '{DAV:}scope' => Scope::class, + '{DAV:}where' => function (Reader $reader) { + $operators = array_map(function ($element) { + return $element['value']; + }, $reader->parseGetElements()); + return (isset($operators[0])) ? $operators[0] : null; + }, + '{DAV:}prop' => Element\Elements::class, + '{DAV:}order' => Order::class, + '{DAV:}eq' => Operator::class, + '{DAV:}gt' => Operator::class, + '{DAV:}gte' => Operator::class, + '{DAV:}lt' => Operator::class, + '{DAV:}lte' => Operator::class, + '{DAV:}and' => Operator::class, + '{DAV:}or' => Operator::class, + '{DAV:}like' => Operator::class, + '{DAV:}contains' => Operator::class, + '{DAV:}not' => Operator::class, + '{DAV:}is-collection' => Operator::class, + ]; + } +} diff --git a/icewind/searchdav/src/DAV/SearchHandler.php b/icewind/searchdav/src/DAV/SearchHandler.php new file mode 100644 index 000000000..b71778f70 --- /dev/null +++ b/icewind/searchdav/src/DAV/SearchHandler.php @@ -0,0 +1,119 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace SearchDAV\DAV; + +use Sabre\DAV\Exception\BadRequest; +use Sabre\DAV\PropFind; +use Sabre\DAV\Server; +use Sabre\HTTP\ResponseInterface; +use SearchDAV\Backend\ISearchBackend; +use SearchDAV\Backend\SearchResult; +use SearchDAV\XML\BasicSearch; + +class SearchHandler { + /** @var ISearchBackend */ + private $searchBackend; + + /** @var PathHelper */ + private $pathHelper; + + /** @var Server */ + private $server; + + /** + * @param ISearchBackend $searchBackend + * @param PathHelper $pathHelper + * @param Server $server + */ + public function __construct(ISearchBackend $searchBackend, PathHelper $pathHelper, Server $server) { + $this->searchBackend = $searchBackend; + $this->pathHelper = $pathHelper; + $this->server = $server; + } + + public function handleSearchRequest($xml, ResponseInterface $response) { + if (!isset($xml['{DAV:}basicsearch'])) { + $response->setStatus(400); + $response->setBody('Unexpected xml content for searchrequest, expected basicsearch'); + return false; + } + /** @var BasicSearch $query */ + $query = $xml['{DAV:}basicsearch']; + if (!$query->where) { + $response->setStatus(400); + $response->setBody('Parse error: Missing {DAV:}where from {DAV:}basicsearch'); + return false; + } + if (!$query->select) { + $response->setStatus(400); + $response->setBody('Parse error: Missing {DAV:}select from {DAV:}basicsearch'); + return false; + } + $response->setStatus(207); + $response->setHeader('Content-Type', 'application/xml; charset="utf-8"'); + foreach ($query->from as $scope) { + $scope->path = $this->pathHelper->getPathFromUri($scope->href); + } + $results = $this->searchBackend->search($query); + $data = $this->server->generateMultiStatus(iterator_to_array($this->getPropertiesIteratorResults($results, $query->select)), false); + $response->setBody($data); + return false; + } + + /** + * 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 SearchResult[] $results + * @param array $propertyNames + * @param int $depth + * @return \Iterator + */ + private function getPropertiesIteratorResults($results, $propertyNames = [], $depth = 0) { + $propFindType = $propertyNames ? PropFind::NORMAL : PropFind::ALLPROPS; + + foreach ($results as $result) { + $node = $result->node; + $propFind = new PropFind($result->href, (array)$propertyNames, $depth, $propFindType); + $r = $this->server->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->server->getResourceTypeForNode($node); + if (in_array('{DAV:}collection', $resourceType) || in_array('{DAV:}principal', $resourceType)) { + $result['href'] .= '/'; + } + yield $result; + } + } + } +} diff --git a/icewind/searchdav/src/DAV/SearchPlugin.php b/icewind/searchdav/src/DAV/SearchPlugin.php new file mode 100644 index 000000000..6e2f0e7a1 --- /dev/null +++ b/icewind/searchdav/src/DAV/SearchPlugin.php @@ -0,0 +1,143 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace SearchDAV\DAV; + +use Sabre\DAV\Exception\BadRequest; +use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\INode; +use Sabre\DAV\Node; +use Sabre\DAV\PropFind; +use Sabre\DAV\Server; +use Sabre\DAV\ServerPlugin; +use Sabre\DAV\Xml\Element\Response; +use Sabre\DAV\Xml\Response\MultiStatus; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; +use Sabre\Xml\ParseException; +use Sabre\Xml\Writer; +use SearchDAV\Backend\ISearchBackend; +use SearchDAV\Backend\SearchPropertyDefinition; +use SearchDAV\Backend\SearchResult; +use SearchDAV\XML\BasicSearch; +use SearchDAV\XML\BasicSearchSchema; +use SearchDAV\XML\PropDesc; +use SearchDAV\XML\QueryDiscoverResponse; +use SearchDAV\XML\Scope; +use SearchDAV\XML\SupportedQueryGrammar; + +class SearchPlugin extends ServerPlugin { + /** @var Server */ + private $server; + + /** @var ISearchBackend */ + private $searchBackend; + + /** @var QueryParser */ + private $queryParser; + + /** @var PathHelper */ + private $pathHelper; + + /** @var SearchHandler */ + private $search; + + /** @var DiscoverHandler */ + private $discover; + + public function __construct(ISearchBackend $searchBackend) { + $this->searchBackend = $searchBackend; + $this->queryParser = new QueryParser(); + } + + public function initialize(Server $server) { + $this->server = $server; + $this->pathHelper = new PathHelper($server); + $this->search = new SearchHandler($this->searchBackend, $this->pathHelper, $server); + $this->discover = new DiscoverHandler($this->searchBackend, $this->pathHelper, $this->queryParser); + $server->on('method:SEARCH', [$this, 'searchHandler']); + $server->on('afterMethod:OPTIONS', [$this, 'optionHandler']); + $server->on('propFind', [$this, 'propFindHandler']); + } + + public function propFindHandler(PropFind $propFind, INode $node) { + if ($propFind->getPath() === $this->searchBackend->getArbiterPath()) { + $propFind->handle('{DAV:}supported-query-grammar-set', new SupportedQueryGrammar()); + } + } + + /** + * SEARCH is allowed for users files + * + * @param string $uri + * @return array + */ + public function getHTTPMethods($uri) { + $path = $this->pathHelper->getPathFromUri($uri); + if ($this->searchBackend->getArbiterPath() === $path) { + return ['SEARCH']; + } else { + return []; + } + } + + public function optionHandler(RequestInterface $request, ResponseInterface $response) { + if ($request->getPath() === $this->searchBackend->getArbiterPath()) { + $response->addHeader('DASL', ''); + } + } + + public function searchHandler(RequestInterface $request, ResponseInterface $response) { + $contentType = $request->getHeader('Content-Type'); + + // Currently we only support xml search queries + if ((strpos($contentType, 'text/xml') === false) && (strpos($contentType, 'application/xml') === false)) { + return true; + } + + if ($request->getPath() !== $this->searchBackend->getArbiterPath()) { + return true; + } + + try { + $xml = $this->queryParser->parse( + $request->getBody(), + $request->getUrl(), + $documentType + ); + } catch (ParseException $e) { + $response->setStatus(400); + $response->setBody('Parse error: ' . $e->getMessage()); + return false; + } + + switch ($documentType) { + case '{DAV:}searchrequest': + return $this->search->handleSearchRequest($xml, $response); + case '{DAV:}query-schema-discovery': + return $this->discover->handelDiscoverRequest($xml, $request, $response); + default: + $response->setStatus(400); + $response->setBody('Unexpected document type: ' . $documentType . ' for this Content-Type, expected {DAV:}searchrequest or {DAV:}query-schema-discovery'); + return false; + } + } +} diff --git a/icewind/searchdav/src/XML/BasicSearch.php b/icewind/searchdav/src/XML/BasicSearch.php new file mode 100644 index 000000000..f3e914576 --- /dev/null +++ b/icewind/searchdav/src/XML/BasicSearch.php @@ -0,0 +1,78 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace SearchDAV\XML; + +use Sabre\Xml\ParseException; +use Sabre\Xml\Reader; +use Sabre\Xml\XmlDeserializable; + +/** + * The object representation of a search query made by the client + */ +class BasicSearch implements XmlDeserializable { + /** + * @var string[] + * + * The list of properties to be selected, specified in clark notation + */ + public $select; + /** + * @var Scope[] + * + * The collections to perform the search in + */ + public $from; + /** + * @var Operator + * + * The search operator, either a comparison ('gt', 'eq', ...) or a boolean operator ('and', 'or', 'not') + */ + public $where; + /** + * @var Order[] + * + * The list of order operations that should be used to order the results. + * + * Each order operations consists of a property to sort on and a sort direction. + * If more then one order operations are specified, the comparisons for ordering should + * be applied in the order that the order operations are defined in with the earlier comparisons being + * more significant. + */ + public $orderBy; + + static function xmlDeserialize(Reader $reader) { + $search = new self(); + + $elements = \Sabre\Xml\Deserializer\keyValue($reader); + + if (!isset($elements['{DAV:}from'])) { + throw new ParseException('Missing {DAV:}from when parsing {DAV:}basicsearch'); + } + + $search->select = isset($elements['{DAV:}select']) ? $elements['{DAV:}select'] : []; + $search->from = $elements['{DAV:}from']; + $search->where = isset($elements['{DAV:}where']) ? $elements['{DAV:}where'] : null; + $search->orderBy = isset($elements['{DAV:}orderby']) ? $elements['{DAV:}orderby'] : []; + + return $search; + } +} diff --git a/icewind/searchdav/src/XML/BasicSearchSchema.php b/icewind/searchdav/src/XML/BasicSearchSchema.php new file mode 100644 index 000000000..0e4c30d40 --- /dev/null +++ b/icewind/searchdav/src/XML/BasicSearchSchema.php @@ -0,0 +1,50 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace SearchDAV\XML; + + +use Sabre\Xml\Writer; +use Sabre\Xml\XmlSerializable; + +class BasicSearchSchema implements XmlSerializable { + /** @var PropDesc[] */ + public $properties; + + /** + * BasicSearchSchema constructor. + * + * @param PropDesc[] $properties + */ + public function __construct(array $properties) { + $this->properties = $properties; + } + + function xmlSerialize(Writer $writer) { + $childs = array_map(function(PropDesc $propDesc) { + return [ + 'name' => '{DAV:}propdesc', + 'value' => $propDesc + ]; + }, $this->properties); + $writer->writeElement('{DAV:}properties', $childs); + } +} diff --git a/icewind/searchdav/src/XML/Literal.php b/icewind/searchdav/src/XML/Literal.php new file mode 100644 index 000000000..5564c3dd4 --- /dev/null +++ b/icewind/searchdav/src/XML/Literal.php @@ -0,0 +1,54 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace SearchDAV\XML; + + +use Sabre\Xml\Reader; +use Sabre\Xml\XmlDeserializable; + +class Literal implements XmlDeserializable { + /** + * @var string|boolean|\DateTime|integer + * + * The value of the literal + */ + public $value; + + /** + * Literal constructor. + * + * @param bool|\DateTime|int|string $value + */ + public function __construct($value = '') { + $this->value = $value; + } + + + static function xmlDeserialize(Reader $reader) { + $literal = new self(); + + $literal->value = $reader->readText(); + $reader->read(); + + return $literal; + } +} diff --git a/icewind/searchdav/src/XML/Operator.php b/icewind/searchdav/src/XML/Operator.php new file mode 100644 index 000000000..a9343f59b --- /dev/null +++ b/icewind/searchdav/src/XML/Operator.php @@ -0,0 +1,99 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace SearchDAV\XML; + + +use Sabre\Xml\Reader; +use Sabre\Xml\XmlDeserializable; + +class Operator implements XmlDeserializable { + const OPERATION_AND = '{DAV:}and'; + const OPERATION_OR = '{DAV:}or'; + const OPERATION_NOT = '{DAV:}not'; + const OPERATION_EQUAL = '{DAV:}eq'; + const OPERATION_LESS_THAN = '{DAV:}lt'; + const OPERATION_LESS_OR_EQUAL_THAN = '{DAV:}lte'; + const OPERATION_GREATER_THAN = '{DAV:}gt'; + const OPERATION_GREATER_OR_EQUAL_THAN = '{DAV:}gte'; + const OPERATION_IS_COLLECTION = '{DAV:}is-collection'; + const OPERATION_IS_DEFINED = '{DAV:}is-defined'; + const OPERATION_IS_LIKE = '{DAV:}like'; + const OPERATION_CONTAINS = '{DAV:}contains'; + + /** + * @var string + * + * The type of operation, one of the Operation::OPERATION_* constants + */ + public $type; + /** + * @var (Literal|string|Operation)[] + * + * The list of arguments for the operation + * + * - string: property name for comparison + * - Literal: literal value for comparison + * - Operation: nested operation for and/or/not operations + * + * Which type and what number of argument an Operator takes depends on the operator type. + */ + public $arguments; + + /** + * Operator constructor. + * + * @param string $type + * @param array $arguments + */ + public function __construct($type = '', array $arguments = []) { + $this->type = $type; + $this->arguments = $arguments; + } + + + static function xmlDeserialize(Reader $reader) { + $operator = new self(); + + $operator->type = $reader->getClark(); + if ($reader->isEmptyElement) { + $reader->next(); + return $operator; + } + $reader->read(); + do { + if ($reader->nodeType === Reader::ELEMENT) { + $argument = $reader->parseCurrentElement(); + if ($argument['name'] === '{DAV:}prop') { + $operator->arguments[] = $argument['value'][0]; + } else { + $operator->arguments[] = $argument['value']; + } + } else { + $reader->read(); + } + } while ($reader->nodeType !== Reader::END_ELEMENT); + + $reader->read(); + + return $operator; + } +} diff --git a/icewind/searchdav/src/XML/Order.php b/icewind/searchdav/src/XML/Order.php new file mode 100644 index 000000000..e1285d3f5 --- /dev/null +++ b/icewind/searchdav/src/XML/Order.php @@ -0,0 +1,66 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace SearchDAV\XML; + + +use Sabre\Xml\Reader; +use Sabre\Xml\XmlDeserializable; + +class Order implements XmlDeserializable { + const ASC = 'ascending'; + const DESC = 'descending'; + + /** + * @var string + * + * The property that should be sorted on. + */ + public $property; + /** + * @var string 'ascending' or 'descending' + * + * The sort direction + */ + public $order; + + /** + * Order constructor. + * + * @param string $property + * @param string $order + */ + public function __construct($property = '', $order = self::ASC) { + $this->property = $property; + $this->order = $order; + } + + static function xmlDeserialize(Reader $reader) { + $order = new self(); + + $childs = \Sabre\Xml\Deserializer\keyValue($reader); + + $order->order = isset($childs['{DAV:}descending']) ? self::DESC : self::ASC; + $order->property = $childs['{DAV:}prop'][0]; + + return $order; + } +} diff --git a/icewind/searchdav/src/XML/PropDesc.php b/icewind/searchdav/src/XML/PropDesc.php new file mode 100644 index 000000000..8d62fcf41 --- /dev/null +++ b/icewind/searchdav/src/XML/PropDesc.php @@ -0,0 +1,59 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace SearchDAV\XML; + + +use Sabre\Xml\Writer; +use Sabre\Xml\XmlSerializable; + +class PropDesc implements XmlSerializable { + /** + * @var string[] + */ + public $properties = []; + public $dataType; + public $searchable; + public $selectable; + public $sortable; + + function xmlSerialize(Writer $writer) { + $data = [ + '{DAV:}dataType' => [$this->dataType => null] + ]; + if ($this->searchable) { + $data['{DAV:}searchable'] = null; + } + if ($this->sortable) { + $data['{DAV:}sortable'] = null; + } + if ($this->selectable) { + $data['{DAV:}selectable'] = null; + } + $writer->write(array_map(function ($propName) { + return [ + 'name' => '{DAV:}prop', + 'value' => $propName + ]; + }, $this->properties)); + $writer->write($data); + } +} diff --git a/icewind/searchdav/src/XML/QueryDiscoverResponse.php b/icewind/searchdav/src/XML/QueryDiscoverResponse.php new file mode 100644 index 000000000..7c5c4db8e --- /dev/null +++ b/icewind/searchdav/src/XML/QueryDiscoverResponse.php @@ -0,0 +1,56 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace SearchDAV\XML; + + +use Sabre\DAV\Xml\Element\Response; +use Sabre\Xml\Writer; + +class QueryDiscoverResponse extends Response { + protected $schema; + + /** + * QueryDiscoverResponse constructor. + * + * @param string $href + * @param BasicSearchSchema|null $schema + * @param null|int|string $httpStatus + */ + function __construct($href, BasicSearchSchema $schema = null, $httpStatus = null) { + parent::__construct($href, [], $httpStatus); + $this->schema = $schema; + + } + + 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', \Sabre\HTTP\encodePath($this->getHref())); + + if ($this->schema) { + $writer->writeElement('{DAV:}query-schema', [ + '{DAV:}basicsearchschema' => $this->schema + ]); + } + } +} diff --git a/icewind/searchdav/src/XML/Scope.php b/icewind/searchdav/src/XML/Scope.php new file mode 100644 index 000000000..9bc830365 --- /dev/null +++ b/icewind/searchdav/src/XML/Scope.php @@ -0,0 +1,73 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace SearchDAV\XML; + + +use Sabre\Xml\Reader; +use Sabre\Xml\XmlDeserializable; + +class Scope implements XmlDeserializable { + /** + * @var string + * + * The scope of the search, either as absolute uri or as a path relative to the + * search arbiter. + */ + public $href; + + /** + * @var string|int 0, 1 or 'infinite' + * + * How deep the search query should be with 0 being only the scope itself, + * 1 being all direct child entries of the scope and infinite being all entries + * in the scope collection at any depth. + */ + public $depth; + + /** + * @var string|null + * + * the path of the search scope relative to the dav server, or null if the scope is outside the dav server + */ + public $path; + + /** + * @param string $href + * @param int|string $depth + * @param string|null $path + */ + public function __construct($href = '', $depth = 1, $path = null) { + $this->href = $href; + $this->depth = $depth; + $this->path = $path; + } + + static function xmlDeserialize(Reader $reader) { + $scope = new self(); + + $values = \Sabre\Xml\Deserializer\keyValue($reader); + $scope->href = $values['{DAV:}href']; + $scope->depth = $values['{DAV:}depth']; + + return $scope; + } +} diff --git a/icewind/searchdav/src/XML/SupportedQueryGrammar.php b/icewind/searchdav/src/XML/SupportedQueryGrammar.php new file mode 100644 index 000000000..9b17a9ba0 --- /dev/null +++ b/icewind/searchdav/src/XML/SupportedQueryGrammar.php @@ -0,0 +1,41 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace SearchDAV\XML; + + +use Sabre\Xml\Writer; +use Sabre\Xml\XmlSerializable; + +class SupportedQueryGrammar implements XmlSerializable { + const GRAMMAR_BASICSEARCH = '{DAV:}basicsearch'; + + public $grammar = self::GRAMMAR_BASICSEARCH; + + function xmlSerialize(Writer $writer) { + $writer->startElement('{DAV:}supported-query-grammar'); + $writer->startElement('{DAV:}grammar'); + $writer->startElement($this->grammar); + $writer->endElement(); + $writer->endElement(); + $writer->endElement(); + } +}