Skip to content

Commit c2f2f21

Browse files
committed
feat: Support deleting metadata from WebDAV
The `$value` will be `null` if the update is wrapped inside a `<d:remove>...</d:remove>` block. Signed-off-by: Louis Chemineau <[email protected]>
1 parent eab718a commit c2f2f21

File tree

4 files changed

+151
-0
lines changed

4 files changed

+151
-0
lines changed

apps/dav/lib/Connector/Sabre/FilesPlugin.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,12 @@ function (mixed $value) use ($accessRight, $knownMetadata, $node, $mutation, $fi
599599
throw new FilesMetadataException('you do not have enough rights to update \'' . $metadataKey . '\' on this node');
600600
}
601601

602+
if ($value === null) {
603+
$metadata->unset($metadataKey);
604+
$filesMetadataManager->saveMetadata($metadata);
605+
return true;
606+
}
607+
602608
// If the metadata is unknown, it defaults to string.
603609
try {
604610
$type = $knownMetadata->getType($metadataKey);

build/integration/config/behat.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,12 @@ default:
113113
- CommandLineContext:
114114
baseUrl: http://localhost:8080
115115
ocPath: ../../
116+
- MetadataContext:
117+
baseUrl: http://localhost:8080
118+
admin:
119+
- admin
120+
- admin
121+
regular_user_password: 123456
116122
files_conversion:
117123
paths:
118124
- "%paths.base%/../file_conversions"
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
<?php
2+
/**
3+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
4+
* SPDX-License-Identifier: AGPL-3.0-or-later
5+
*/
6+
7+
use Behat\Behat\Context\Context;
8+
use Behat\Step\Then;
9+
use Behat\Step\When;
10+
use PHPUnit\Framework\Assert;
11+
use Sabre\DAV\Client as SClient;
12+
13+
require __DIR__ . '/../../vendor/autoload.php';
14+
15+
class MetadataContext implements Context {
16+
private string $davPath = '/remote.php/dav';
17+
18+
public function __construct(
19+
private string $baseUrl,
20+
private array $admin,
21+
private string $regular_user_password,
22+
) {
23+
// in case of ci deployment we take the server url from the environment
24+
$testServerUrl = getenv('TEST_SERVER_URL');
25+
if ($testServerUrl !== false) {
26+
$this->baseUrl = substr($testServerUrl, 0, -5);
27+
}
28+
}
29+
30+
#[When('User :user sets the :metadataKey prop with value :metadataValue on :fileName')]
31+
public function userSetsProp(string $user, string $metadataKey, string $metadataValue, string $fileName) {
32+
$client = new SClient([
33+
'baseUri' => $this->baseUrl,
34+
'userName' => $user,
35+
'password' => '123456',
36+
'authType' => SClient::AUTH_BASIC,
37+
]);
38+
39+
$body = '<?xml version="1.0"?>
40+
<d:propertyupdate xmlns:d="DAV:" xmlns:nc="http://nextcloud.com/ns">
41+
<d:set>
42+
<d:prop>
43+
<nc:' . $metadataKey . '>' . $metadataValue . '</nc:' . $metadataKey . '>
44+
</d:prop>
45+
</d:set>
46+
</d:propertyupdate>';
47+
48+
$davUrl = $this->getDavUrl($user, $fileName);
49+
$client->request('PROPPATCH', $this->baseUrl . $davUrl, $body);
50+
}
51+
52+
#[When('User :user deletes the :metadataKey prop on :fileName')]
53+
public function userDeletesProp(string $user, string $metadataKey, string $fileName) {
54+
$client = new SClient([
55+
'baseUri' => $this->baseUrl,
56+
'userName' => $user,
57+
'password' => '123456',
58+
'authType' => SClient::AUTH_BASIC,
59+
]);
60+
61+
$body = '<?xml version="1.0"?>
62+
<d:propertyupdate xmlns:d="DAV:" xmlns:nc="http://nextcloud.com/ns">
63+
<d:remove>
64+
<d:prop>
65+
<nc:' . $metadataKey . '></nc:' . $metadataKey . '>
66+
</d:prop>
67+
</d:remove>
68+
</d:propertyupdate>';
69+
70+
$davUrl = $this->getDavUrl($user, $fileName);
71+
$client->request('PROPPATCH', $this->baseUrl . $davUrl, $body);
72+
}
73+
74+
#[Then('User :user should see the prop :metadataKey equal to :metadataValue for file :fileName')]
75+
public function checkPropForFile(string $user, string $metadataKey, string $metadataValue, string $fileName) {
76+
$client = new SClient([
77+
'baseUri' => $this->baseUrl,
78+
'userName' => $user,
79+
'password' => '123456',
80+
'authType' => SClient::AUTH_BASIC,
81+
]);
82+
83+
$body = '<?xml version="1.0"?>
84+
<d:propfind xmlns:d="DAV:" xmlns:nc="http://nextcloud.com/ns">
85+
<d:prop>
86+
<nc:' . $metadataKey . '></nc:' . $metadataKey . '>
87+
</d:prop>
88+
</d:propfind>';
89+
90+
$davUrl = $this->getDavUrl($user, $fileName);
91+
$response = $client->request('PROPFIND', $this->baseUrl . $davUrl, $body);
92+
$parsedResponse = $client->parseMultistatus($response['body']);
93+
94+
Assert::assertEquals($parsedResponse[$davUrl]['200']['{http://nextcloud.com/ns}' . $metadataKey], $metadataValue);
95+
}
96+
97+
#[Then('User :user should not see the prop :metadataKey for file :fileName')]
98+
public function checkPropDoesNotExistsForFile(string $user, string $metadataKey, string $fileName) {
99+
$client = new SClient([
100+
'baseUri' => $this->baseUrl,
101+
'userName' => $user,
102+
'password' => '123456',
103+
'authType' => SClient::AUTH_BASIC,
104+
]);
105+
106+
$body = '<?xml version="1.0"?>
107+
<d:propfind xmlns:d="DAV:" xmlns:nc="http://nextcloud.com/ns">
108+
<d:prop>
109+
<nc:' . $metadataKey . '></nc:' . $metadataKey . '>
110+
</d:prop>
111+
</d:propfind>';
112+
113+
$davUrl = $this->getDavUrl($user, $fileName);
114+
$response = $client->request('PROPFIND', $this->baseUrl . $davUrl, $body);
115+
$parsedResponse = $client->parseMultistatus($response['body']);
116+
117+
Assert::assertEquals($parsedResponse[$davUrl]['404']['{http://nextcloud.com/ns}' . $metadataKey], null);
118+
}
119+
120+
private function getDavUrl(string $user, string $fileName) {
121+
return $this->davPath . '/files/' . $user . $fileName;
122+
}
123+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
2+
# SPDX-License-Identifier: AGPL-3.0-only
3+
Feature: metadata
4+
5+
Scenario: Setting metadata works
6+
Given user "user0" exists
7+
When User "user0" uploads file with content "AAA" to "/test.txt"
8+
And User "user0" sets the "metadata-files-live-photo" prop with value "metadata-value" on "/test.txt"
9+
Then User "user0" should see the prop "metadata-files-live-photo" equal to "metadata-value" for file "/test.txt"
10+
11+
Scenario: Deleting metadata works
12+
Given user "user0" exists
13+
When User "user0" uploads file with content "AAA" to "/test.txt"
14+
And User "user0" sets the "metadata-files-live-photo" prop with value "metadata-value" on "/test.txt"
15+
And User "user0" deletes the "metadata-files-live-photo" prop on "/test.txt"
16+
Then User "user0" should not see the prop "metadata-files-live-photo" for file "/test.txt"

0 commit comments

Comments
 (0)