diff options
author | John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> | 2020-07-02 17:49:42 +0200 |
---|---|---|
committer | John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> | 2020-08-21 09:56:08 +0200 |
commit | f44028131344636e45c5158cc13ccbe4edd19097 (patch) | |
tree | 12023f8c4d62c6ba173e572f89e15c1565799446 /lib | |
parent | 63b2aff43903d51fc382a7c6cb0019845363c183 (diff) |
Add PatchPlugin
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/AppInfo/Application.php | 28 | ||||
-rw-r--r-- | lib/Dav/PatchPlugin.php | 186 |
2 files changed, 210 insertions, 4 deletions
diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 565fe5b7..a2f3a396 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -22,16 +22,36 @@ */ namespace OCA\Contacts\AppInfo; +use OCA\Contacts\Dav\PatchPlugin; use OCP\AppFramework\App; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\SabrePluginEvent; class Application extends App { public const APP_ID = 'contacts'; - - public function __construct() { - parent::__construct(self::APP_ID); - } public const AVAIL_SETTINGS = [ 'allowSocialSync' => 'yes', ]; + + public function __construct() { + parent::__construct(self::APP_ID); + } + + public function register() { + $server = $this->getContainer()->getServer(); + + /** @var IEventDispatcher $eventDispatcher */ + $eventDispatcher = $server->query(IEventDispatcher::class); + $eventDispatcher->addListener('OCA\DAV\Connector\Sabre::addPlugin', function (SabrePluginEvent $event) { + $server = $event->getServer(); + + if ($server !== null) { + // We have to register the LockPlugin here and not info.xml, + // because info.xml plugins are loaded, after the + // beforeMethod:* hook has already been emitted. + $server->addPlugin($this->getContainer()->query(PatchPlugin::class)); + } + }); + } } diff --git a/lib/Dav/PatchPlugin.php b/lib/Dav/PatchPlugin.php new file mode 100644 index 00000000..61f36b7a --- /dev/null +++ b/lib/Dav/PatchPlugin.php @@ -0,0 +1,186 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com> + * + * @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> + * + * @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 <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Contacts\Dav; + +use Sabre\CardDAV\Card; +use Sabre\DAV; +use Sabre\DAV\INode; +use Sabre\DAV\PropPatch; +use Sabre\DAV\Server; +use Sabre\DAV\ServerPlugin; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; +use Sabre\VObject\Component\VCard; +use Sabre\VObject\Reader; + +class PatchPlugin extends ServerPlugin { + public const METHOD_REPLACE = 0; + public const METHOD_APPEND = 1; + + /** @var Server */ + protected $server; + + /** + * Initializes the plugin and registers event handlers + * + * @param Server $server + * @return void + */ + public function initialize(Server $server) { + $this->server = $server; + $server->on('method:PATCH', [$this, 'httpPatch']); + } + + /** + * Use this method to tell the server this plugin defines additional + * HTTP methods. + * + * This method is passed a uri. It should only return HTTP methods that are + * available for the specified uri. + * + * We claim to support PATCH method (partirl update) if and only if + * - the node exist + * - the node implements our partial update interface + * + * @param string $uri + * + * @return array + */ + public function getHTTPMethods($uri) { + $tree = $this->server->tree; + + if ($tree->nodeExists($uri)) { + $node = $tree->getNodeForPath($uri); + if ($node instanceof Card) { + return ['PATCH']; + } + } + + return []; + } + + /** + * Adds all CardDAV-specific properties + * + * @param PropPatch $propPatch + * @param INode $node + * @return void + */ + public function httpPatch(RequestInterface $request, ResponseInterface $response) { + $path = $request->getPath(); + $node = $this->server->tree->getNodeForPath($path); + + if (!($node instanceof Card)) { + return true; + } + + // Checking ACL, if available. + if ($aclPlugin = $this->server->getPlugin('acl')) { + /** @var \Sabre\DAVACL\Plugin $aclPlugin */ + $aclPlugin->checkPrivileges($path, '{DAV:}write'); + } + + // Init property name & value + $propertyName = $request->getHeader('X-Property'); + if (is_null($propertyName)) { + throw new DAV\Exception\BadRequest('No valid "X-Property" found in the headers'); + } + + $propertyData = $request->getHeader('X-Property-Replace'); + $method = self::METHOD_REPLACE; + if (is_null($propertyData)) { + $propertyData = $request->getHeader('X-Property-Append'); + $method = self::METHOD_APPEND; + if (is_null($propertyData)) { + throw new DAV\Exception\BadRequest('No valid "X-Property-Append" or "X-Property-Replace" found in the headers'); + } + } + + // Init contact + $vCard = Reader::read($node->get()); + $properties = $vCard->select($propertyName); + + // We cannot know which one to update in that case + if (count($properties) > 1) { + throw new DAV\Exception\BadRequest('The specified property appear more than once'); + } + + // Init if not in the vcard + if (count($properties) === 0) { + $vCard->add($propertyName, $propertyData); + $properties = $vCard->select($propertyName); + } + + // Replace existing value + if ($method === self::METHOD_REPLACE) { + $properties[0]->setRawMimeDirValue($propertyData); + } + + // Append to existing value + if ($method === self::METHOD_APPEND) { + $oldData = $properties[0]->getValue(); + $properties[0]->setRawMimeDirValue($oldData.$propertyData); + } + + // Validate & write + $vCard->validate(); + $node->put($vCard->serialize()); + $response->setStatus(200); + + return false; + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins + * using \Sabre\DAV\Server::getPlugin + * + * @return string + */ + public function getPluginName() { + return 'vcard-patch'; + } + + /** + * Returns a bunch of meta-data about the plugin. + * + * Providing this information is optional, and is mainly displayed by the + * Browser plugin. + * + * The description key in the returned array may contain html and will not + * be sanitized. + * + * @return array + */ + public function getPluginInfo() { + return [ + 'name' => $this->getPluginName(), + 'description' => 'Allow to patch unique properties.' + ]; + } +} |