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();
+ }
+}