summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorJohn Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>2020-07-02 17:49:42 +0200
committerJohn Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>2020-08-21 09:56:08 +0200
commitf44028131344636e45c5158cc13ccbe4edd19097 (patch)
tree12023f8c4d62c6ba173e572f89e15c1565799446 /lib
parent63b2aff43903d51fc382a7c6cb0019845363c183 (diff)
Add PatchPlugin
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
Diffstat (limited to 'lib')
-rw-r--r--lib/AppInfo/Application.php28
-rw-r--r--lib/Dav/PatchPlugin.php186
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.'
+ ];
+ }
+}