Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117530643
D5835.1774855294.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
10 KB
Referenced Files
None
Subscribers
None
D5835.1774855294.diff
View Options
diff --git a/src/app/Backends/Storage.php b/src/app/Backends/Storage.php
--- a/src/app/Backends/Storage.php
+++ b/src/app/Backends/Storage.php
@@ -213,6 +213,10 @@
if ($file->type & Item::TYPE_INCOMPLETE) {
$file->type -= Item::TYPE_INCOMPLETE;
$file->save();
+ } else {
+ // Bump last modification time (needed e.g. for proper WebDAV syncronization/ETag)
+ // Note: We don't use touch() directly on $file because it fails when the object has custom properties
+ Item::where('id', $file->id)->touch();
}
// Update the file type and size information
diff --git a/src/app/Http/Controllers/DAVController.php b/src/app/Http/Controllers/DAVController.php
--- a/src/app/Http/Controllers/DAVController.php
+++ b/src/app/Http/Controllers/DAVController.php
@@ -69,6 +69,7 @@
// Register some plugins
$server->addPlugin(new \Sabre\DAV\Auth\Plugin($auth_backend));
+ $server->addPlugin(new DAV\ServerPlugin());
// Unauthenticated access doesn't work for us since we require credentials to get access to the data in the first place.
// $acl_plugin = new \Sabre\DAVACL\Plugin();
diff --git a/src/app/Http/DAV/Collection.php b/src/app/Http/DAV/Collection.php
--- a/src/app/Http/DAV/Collection.php
+++ b/src/app/Http/DAV/Collection.php
@@ -8,15 +8,18 @@
use Sabre\DAV\Exception;
use Sabre\DAV\ICollection;
use Sabre\DAV\ICopyTarget;
+use Sabre\DAV\IExtendedCollection;
use Sabre\DAV\IMoveTarget;
use Sabre\DAV\INode;
use Sabre\DAV\INodeByPath;
use Sabre\DAV\IProperties;
+use Sabre\DAV\MkCol;
+use Sabre\DAV\Xml\Property\ResourceType;
/**
* Sabre DAV Collection interface implemetation
*/
-class Collection extends Node implements ICollection, ICopyTarget, IMoveTarget, INodeByPath, IProperties
+class Collection extends Node implements ICollection, ICopyTarget, IExtendedCollection, IMoveTarget, INodeByPath, IProperties
{
/**
* Checks if a child-node exists.
@@ -76,6 +79,49 @@
return true;
}
+ /**
+ * Creates a new collection.
+ *
+ * This method will receive a MkCol object with all the information about
+ * the new collection that's being created.
+ *
+ * The MkCol object contains information about the resourceType of the new
+ * collection. If you don't support the specified resourceType, you should
+ * throw Exception\InvalidResourceType.
+ *
+ * The object also contains a list of WebDAV properties for the new
+ * collection.
+ *
+ * You should call the handle() method on this object to specify exactly
+ * which properties you are storing. This allows the system to figure out
+ * exactly which properties you didn't store, which in turn allows other
+ * plugins (such as the propertystorage plugin) to handle storing the
+ * property for you.
+ *
+ * @param string $name
+ *
+ * @throws Exception\InvalidResourceType
+ */
+ public function createExtendedCollection($name, MkCol $mkCol)
+ {
+ $types = $mkCol->getResourceType();
+
+ if (count($types) > 1) {
+ // For now we only support use of 'notebook' in the resourcetype (Kolab Notes)
+ if (in_array('{Kolab:}notebook', $types)) {
+ $type = 'notebook';
+ } else {
+ throw new Exception\InvalidResourceType('The {DAV:}resourcetype you specified is not supported here.');
+ }
+ }
+
+ $collection = $this->createDirectory($name);
+
+ if (!empty($type)) {
+ $collection->setProperty('dav:resourcetype', $type);
+ }
+ }
+
/**
* Creates a new file in the directory
*
@@ -160,6 +206,8 @@
}
DB::commit();
+
+ return $collection;
}
/**
@@ -207,9 +255,10 @@
->select('fs_items.*')
->whereNot('type', '&', Item::TYPE_INCOMPLETE);
- foreach (['name', 'size', 'mimetype'] as $key) {
+ foreach (['name', 'size', 'mimetype', 'dav:resourcetype', 'dav:displayname', 'dav:links', 'dav:categories'] as $key) {
+ $alias = str_replace('dav:', '', $key);
$query->selectRaw('(select value from fs_properties where fs_items.id = fs_properties.item_id'
- . " and fs_properties.key = '{$key}') as {$key}");
+ . " and fs_properties.key = '{$key}') as {$alias}");
}
if ($parent = $this->data) {
@@ -309,6 +358,10 @@
$result['{DAV:}creationdate'] = \Sabre\HTTP\toDate($this->data->created_at);
}
+ if (!empty($this->data->resourcetype)) {
+ $result['{DAV:}resourcetype'] = new ResourceType(['{DAV:}collection', "{Kolab:}{$this->data->resourcetype}"]);
+ }
+
return $result;
}
@@ -361,7 +414,6 @@
{
\Log::debug('[DAV] PROP-PATCH: ' . $this->path);
- // not supported
- // FIXME: Should we throw an exception?
+ // Not implemented
}
}
diff --git a/src/app/Http/DAV/File.php b/src/app/Http/DAV/File.php
--- a/src/app/Http/DAV/File.php
+++ b/src/app/Http/DAV/File.php
@@ -3,7 +3,7 @@
namespace App\Http\DAV;
use App\Backends\Storage;
-use Sabre\DAV\Exception;
+use App\Fs\Item;
use Sabre\DAV\IFile;
use Sabre\DAV\IProperties;
@@ -86,6 +86,18 @@
$result['{DAV:}creationdate'] = \Sabre\HTTP\toDate($this->data->created_at);
}
+ if (!empty($this->data->displayname)) {
+ $result['{DAV:}displayname'] = $this->data->displayname;
+ }
+
+ if (isset($this->data->links)) {
+ $result['{Kolab:}links'] = self::propListOutput(\json_decode($this->data->links), 'link');
+ }
+
+ if (isset($this->data->categories)) {
+ $result['{Kolab:}categories'] = self::propListOutput(\json_decode($this->data->categories), 'category');
+ }
+
return $result;
}
@@ -112,8 +124,62 @@
{
\Log::debug('[DAV] PROP-PATCH: ' . $this->path);
- // not supported
- // FIXME: Should we throw an exception?
+ // Note: Here we register handlers that are executed later by Sabre/DAV
+ $propPatch->handle(
+ // Properties used by Kolab Notes
+ ['{DAV:}displayname', '{Kolab:}links', '{Kolab:}categories'],
+ function ($properties) {
+ return $this->propPatchValidateAndSave($properties);
+ }
+ );
+ }
+
+ /**
+ * Validate PROPPATCH properties
+ */
+ protected function propPatchValidateAndSave($properties): array
+ {
+ $result = [];
+ $updated = false;
+
+ foreach ($properties as $key => $value) {
+ $status = true;
+ $prop_name = null;
+
+ switch ($key) {
+ case '{DAV:}displayname':
+ $prop_name = 'dav:displayname';
+ $status = is_string($value);
+ break;
+ case '{Kolab:}categories':
+ case '{Kolab:}links':
+ $prop_name = 'dav:' . str_replace('{Kolab:}', '', $key);
+ $status = is_array($value);
+ break;
+ }
+
+ if ($status && $prop_name) {
+ if ($value === '' || (is_array($value) && empty($value))) {
+ $value = null;
+ }
+ if (is_array($value)) {
+ $value = json_encode($value);
+ }
+
+ $updated = $updated || $value !== ($this->data->{$prop_name} ?? null);
+ $this->data->setProperty($prop_name, $value);
+ }
+
+ $result[$key] = $status ? 200 : 403; // result to SabreDAV
+ }
+
+ // Bump last modification time (needed e.g. for proper WebDAV syncronization/ETag)
+ // Note: We don't use touch() directly on $file because it fails when the object has custom properties
+ if ($updated) {
+ Item::where('id', $this->data->id)->touch();
+ }
+
+ return $result;
}
/**
diff --git a/src/app/Http/DAV/Locks.php b/src/app/Http/DAV/Locks.php
--- a/src/app/Http/DAV/Locks.php
+++ b/src/app/Http/DAV/Locks.php
@@ -33,6 +33,9 @@
{
\Log::debug('[DAV] GET-LOCKS: ' . $uri);
+ // TODO: On a node delete Sabre invokes this method twice (once before and once after)
+ // so there's a place for some optimization.
+
// Note: We're disabling exceptions here, otherwise it has unwanted effects
// in places where Sabre checks locks on non-existing paths
$ids = Node::resolvePath($uri, true);
diff --git a/src/app/Http/DAV/Node.php b/src/app/Http/DAV/Node.php
--- a/src/app/Http/DAV/Node.php
+++ b/src/app/Http/DAV/Node.php
@@ -268,4 +268,20 @@
// that's why we store all lookup results including `false`.
Context::addHidden('fs:' . $path, $item);
}
+
+ /**
+ * Convert an array into XML property understood by the Sabre XML writer
+ */
+ protected static function propListOutput(array $list, string $item_name): array
+ {
+ foreach ($list as $idx => $item) {
+ $list[$idx] = [
+ 'name' => "{Kolab:}{$item_name}",
+ 'value' => $item,
+ 'properties' => [],
+ ];
+ }
+
+ return $list;
+ }
}
diff --git a/src/app/Http/DAV/ServerPlugin.php b/src/app/Http/DAV/ServerPlugin.php
new file mode 100644
--- /dev/null
+++ b/src/app/Http/DAV/ServerPlugin.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace App\Http\DAV;
+
+use Sabre\DAV\Server;
+use Sabre\Xml\Deserializer;
+use Sabre\Xml\Reader;
+
+/**
+ * A plugin covering Kolab XML extensions.
+ *
+ * Plugins can modifies/extends the Sabre server behaviour.
+ */
+class ServerPlugin extends \Sabre\DAV\ServerPlugin
+{
+ /**
+ * This initializes the plugin.
+ *
+ * This function is called by Sabre\DAV\Server, after addPlugin is called.
+ */
+ public function initialize(Server $server)
+ {
+ // Tell the XML parser how to handle structured Kolab properties
+ $server->xml->elementMap['{Kolab:}links'] = function (Reader $reader) {
+ return Deserializer\repeatingElements($reader, '{Kolab:}link');
+ };
+
+ $server->xml->elementMap['{Kolab:}categories'] = function (Reader $reader) {
+ return Deserializer\repeatingElements($reader, '{Kolab:}category');
+ };
+ }
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, Mar 30, 7:21 AM (2 d, 17 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18796047
Default Alt Text
D5835.1774855294.diff (10 KB)
Attached To
Mode
D5835: WebDAV: Kolab Notes support
Attached
Detach File
Event Timeline