Completed
Push — master ( 88aa80...c06d85 )
by John
24:06 queued 14s
created
apps/dav/tests/unit/Connector/Sabre/FilesPluginTest.php 1 patch
Indentation   +716 added lines, -716 removed lines patch added patch discarded remove patch
@@ -39,721 +39,721 @@
 block discarded – undo
39 39
  */
40 40
 class FilesPluginTest extends TestCase {
41 41
 
42
-	private Tree&MockObject $tree;
43
-	private Server&MockObject $server;
44
-	private IConfig&MockObject $config;
45
-	private IRequest&MockObject $request;
46
-	private IPreview&MockObject $previewManager;
47
-	private IUserSession&MockObject $userSession;
48
-	private IFilenameValidator&MockObject $filenameValidator;
49
-	private IAccountManager&MockObject $accountManager;
50
-	private FilesPlugin $plugin;
51
-
52
-	protected function setUp(): void {
53
-		parent::setUp();
54
-		$this->server = $this->createMock(Server::class);
55
-		$this->tree = $this->createMock(Tree::class);
56
-		$this->config = $this->createMock(IConfig::class);
57
-		$this->config->expects($this->any())->method('getSystemValue')
58
-			->with($this->equalTo('data-fingerprint'), $this->equalTo(''))
59
-			->willReturn('my_fingerprint');
60
-		$this->request = $this->createMock(IRequest::class);
61
-		$this->previewManager = $this->createMock(IPreview::class);
62
-		$this->userSession = $this->createMock(IUserSession::class);
63
-		$this->filenameValidator = $this->createMock(IFilenameValidator::class);
64
-		$this->accountManager = $this->createMock(IAccountManager::class);
65
-
66
-		$this->plugin = new FilesPlugin(
67
-			$this->tree,
68
-			$this->config,
69
-			$this->request,
70
-			$this->previewManager,
71
-			$this->userSession,
72
-			$this->filenameValidator,
73
-			$this->accountManager,
74
-		);
75
-
76
-		$response = $this->getMockBuilder(ResponseInterface::class)
77
-			->disableOriginalConstructor()
78
-			->getMock();
79
-		$this->server->httpResponse = $response;
80
-		$this->server->xml = new Service();
81
-
82
-		$this->plugin->initialize($this->server);
83
-	}
84
-
85
-	/**
86
-	 * @param string $class
87
-	 * @return \PHPUnit\Framework\MockObject\MockObject
88
-	 */
89
-	private function createTestNode($class, $path = '/dummypath') {
90
-		$node = $this->getMockBuilder($class)
91
-			->disableOriginalConstructor()
92
-			->getMock();
93
-
94
-		$node->expects($this->any())
95
-			->method('getId')
96
-			->willReturn(123);
97
-
98
-		$this->tree->expects($this->any())
99
-			->method('getNodeForPath')
100
-			->with($path)
101
-			->willReturn($node);
102
-
103
-		$node->expects($this->any())
104
-			->method('getFileId')
105
-			->willReturn('00000123instanceid');
106
-		$node->expects($this->any())
107
-			->method('getInternalFileId')
108
-			->willReturn('123');
109
-		$node->expects($this->any())
110
-			->method('getEtag')
111
-			->willReturn('"abc"');
112
-		$node->expects($this->any())
113
-			->method('getDavPermissions')
114
-			->willReturn('DWCKMSR');
115
-
116
-		$fileInfo = $this->createMock(FileInfo::class);
117
-		$fileInfo->expects($this->any())
118
-			->method('isReadable')
119
-			->willReturn(true);
120
-		$fileInfo->expects($this->any())
121
-			->method('getCreationTime')
122
-			->willReturn(123456789);
123
-
124
-		$node->expects($this->any())
125
-			->method('getFileInfo')
126
-			->willReturn($fileInfo);
127
-
128
-		return $node;
129
-	}
130
-
131
-	public function testGetPropertiesForFile(): void {
132
-		/** @var File|\PHPUnit\Framework\MockObject\MockObject $node */
133
-		$node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File');
134
-
135
-		$propFind = new PropFind(
136
-			'/dummyPath',
137
-			[
138
-				FilesPlugin::GETETAG_PROPERTYNAME,
139
-				FilesPlugin::FILEID_PROPERTYNAME,
140
-				FilesPlugin::INTERNAL_FILEID_PROPERTYNAME,
141
-				FilesPlugin::SIZE_PROPERTYNAME,
142
-				FilesPlugin::PERMISSIONS_PROPERTYNAME,
143
-				FilesPlugin::DOWNLOADURL_PROPERTYNAME,
144
-				FilesPlugin::OWNER_ID_PROPERTYNAME,
145
-				FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME,
146
-				FilesPlugin::DATA_FINGERPRINT_PROPERTYNAME,
147
-				FilesPlugin::CREATIONDATE_PROPERTYNAME,
148
-			],
149
-			0
150
-		);
151
-
152
-		$user = $this->getMockBuilder(User::class)
153
-			->disableOriginalConstructor()->getMock();
154
-		$user
155
-			->expects($this->once())
156
-			->method('getUID')
157
-			->willReturn('foo');
158
-		$user
159
-			->expects($this->once())
160
-			->method('getDisplayName')
161
-			->willReturn('M. Foo');
162
-
163
-		$owner = $this->getMockBuilder(Account::class)
164
-			->disableOriginalConstructor()->getMock();
165
-		$this->accountManager->expects($this->once())
166
-			->method('getAccount')
167
-			->with($user)
168
-			->willReturn($owner);
169
-
170
-		$node->expects($this->once())
171
-			->method('getDirectDownload')
172
-			->willReturn(['url' => 'http://example.com/']);
173
-		$node->expects($this->exactly(2))
174
-			->method('getOwner')
175
-			->willReturn($user);
176
-
177
-		$displayNameProp = $this->getMockBuilder(AccountProperty::class)
178
-			->disableOriginalConstructor()->getMock();
179
-		$owner
180
-			->expects($this->once())
181
-			->method('getProperty')
182
-			->with(IAccountManager::PROPERTY_DISPLAYNAME)
183
-			->willReturn($displayNameProp);
184
-		$displayNameProp
185
-			->expects($this->once())
186
-			->method('getScope')
187
-			->willReturn(IAccountManager::SCOPE_PUBLISHED);
188
-
189
-		$this->plugin->handleGetProperties(
190
-			$propFind,
191
-			$node
192
-		);
193
-
194
-		$this->assertEquals('"abc"', $propFind->get(FilesPlugin::GETETAG_PROPERTYNAME));
195
-		$this->assertEquals('00000123instanceid', $propFind->get(FilesPlugin::FILEID_PROPERTYNAME));
196
-		$this->assertEquals('123', $propFind->get(FilesPlugin::INTERNAL_FILEID_PROPERTYNAME));
197
-		$this->assertEquals('1973-11-29T21:33:09+00:00', $propFind->get(FilesPlugin::CREATIONDATE_PROPERTYNAME));
198
-		$this->assertEquals(0, $propFind->get(FilesPlugin::SIZE_PROPERTYNAME));
199
-		$this->assertEquals('DWCKMSR', $propFind->get(FilesPlugin::PERMISSIONS_PROPERTYNAME));
200
-		$this->assertEquals('http://example.com/', $propFind->get(FilesPlugin::DOWNLOADURL_PROPERTYNAME));
201
-		$this->assertEquals('foo', $propFind->get(FilesPlugin::OWNER_ID_PROPERTYNAME));
202
-		$this->assertEquals('M. Foo', $propFind->get(FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME));
203
-		$this->assertEquals('my_fingerprint', $propFind->get(FilesPlugin::DATA_FINGERPRINT_PROPERTYNAME));
204
-		$this->assertEquals([], $propFind->get404Properties());
205
-	}
206
-
207
-	public function testGetDisplayNamePropertyWhenNotPublished(): void {
208
-		/** @var File|\PHPUnit\Framework\MockObject\MockObject $node */
209
-		$node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File');
210
-
211
-		$propFind = new PropFind(
212
-			'/dummyPath',
213
-			[
214
-				FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME,
215
-			],
216
-			0
217
-		);
218
-
219
-		$this->userSession->expects($this->once())
220
-			->method('getUser')
221
-			->willReturn(null);
222
-
223
-		$user = $this->getMockBuilder(User::class)
224
-			->disableOriginalConstructor()->getMock();
225
-
226
-		$user
227
-			->expects($this->never())
228
-			->method('getDisplayName');
229
-
230
-		$owner = $this->getMockBuilder(Account::class)
231
-			->disableOriginalConstructor()->getMock();
232
-		$this->accountManager->expects($this->once())
233
-			->method('getAccount')
234
-			->with($user)
235
-			->willReturn($owner);
236
-
237
-		$node->expects($this->once())
238
-			->method('getOwner')
239
-			->willReturn($user);
240
-
241
-		$displayNameProp = $this->getMockBuilder(AccountProperty::class)
242
-			->disableOriginalConstructor()->getMock();
243
-		$owner
244
-			->expects($this->once())
245
-			->method('getProperty')
246
-			->with(IAccountManager::PROPERTY_DISPLAYNAME)
247
-			->willReturn($displayNameProp);
248
-		$displayNameProp
249
-			->expects($this->once())
250
-			->method('getScope')
251
-			->willReturn(IAccountManager::SCOPE_PRIVATE);
252
-
253
-		$this->plugin->handleGetProperties(
254
-			$propFind,
255
-			$node
256
-		);
257
-
258
-		$this->assertEquals(null, $propFind->get(FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME));
259
-	}
42
+    private Tree&MockObject $tree;
43
+    private Server&MockObject $server;
44
+    private IConfig&MockObject $config;
45
+    private IRequest&MockObject $request;
46
+    private IPreview&MockObject $previewManager;
47
+    private IUserSession&MockObject $userSession;
48
+    private IFilenameValidator&MockObject $filenameValidator;
49
+    private IAccountManager&MockObject $accountManager;
50
+    private FilesPlugin $plugin;
51
+
52
+    protected function setUp(): void {
53
+        parent::setUp();
54
+        $this->server = $this->createMock(Server::class);
55
+        $this->tree = $this->createMock(Tree::class);
56
+        $this->config = $this->createMock(IConfig::class);
57
+        $this->config->expects($this->any())->method('getSystemValue')
58
+            ->with($this->equalTo('data-fingerprint'), $this->equalTo(''))
59
+            ->willReturn('my_fingerprint');
60
+        $this->request = $this->createMock(IRequest::class);
61
+        $this->previewManager = $this->createMock(IPreview::class);
62
+        $this->userSession = $this->createMock(IUserSession::class);
63
+        $this->filenameValidator = $this->createMock(IFilenameValidator::class);
64
+        $this->accountManager = $this->createMock(IAccountManager::class);
65
+
66
+        $this->plugin = new FilesPlugin(
67
+            $this->tree,
68
+            $this->config,
69
+            $this->request,
70
+            $this->previewManager,
71
+            $this->userSession,
72
+            $this->filenameValidator,
73
+            $this->accountManager,
74
+        );
75
+
76
+        $response = $this->getMockBuilder(ResponseInterface::class)
77
+            ->disableOriginalConstructor()
78
+            ->getMock();
79
+        $this->server->httpResponse = $response;
80
+        $this->server->xml = new Service();
81
+
82
+        $this->plugin->initialize($this->server);
83
+    }
84
+
85
+    /**
86
+     * @param string $class
87
+     * @return \PHPUnit\Framework\MockObject\MockObject
88
+     */
89
+    private function createTestNode($class, $path = '/dummypath') {
90
+        $node = $this->getMockBuilder($class)
91
+            ->disableOriginalConstructor()
92
+            ->getMock();
93
+
94
+        $node->expects($this->any())
95
+            ->method('getId')
96
+            ->willReturn(123);
97
+
98
+        $this->tree->expects($this->any())
99
+            ->method('getNodeForPath')
100
+            ->with($path)
101
+            ->willReturn($node);
102
+
103
+        $node->expects($this->any())
104
+            ->method('getFileId')
105
+            ->willReturn('00000123instanceid');
106
+        $node->expects($this->any())
107
+            ->method('getInternalFileId')
108
+            ->willReturn('123');
109
+        $node->expects($this->any())
110
+            ->method('getEtag')
111
+            ->willReturn('"abc"');
112
+        $node->expects($this->any())
113
+            ->method('getDavPermissions')
114
+            ->willReturn('DWCKMSR');
115
+
116
+        $fileInfo = $this->createMock(FileInfo::class);
117
+        $fileInfo->expects($this->any())
118
+            ->method('isReadable')
119
+            ->willReturn(true);
120
+        $fileInfo->expects($this->any())
121
+            ->method('getCreationTime')
122
+            ->willReturn(123456789);
123
+
124
+        $node->expects($this->any())
125
+            ->method('getFileInfo')
126
+            ->willReturn($fileInfo);
127
+
128
+        return $node;
129
+    }
130
+
131
+    public function testGetPropertiesForFile(): void {
132
+        /** @var File|\PHPUnit\Framework\MockObject\MockObject $node */
133
+        $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File');
134
+
135
+        $propFind = new PropFind(
136
+            '/dummyPath',
137
+            [
138
+                FilesPlugin::GETETAG_PROPERTYNAME,
139
+                FilesPlugin::FILEID_PROPERTYNAME,
140
+                FilesPlugin::INTERNAL_FILEID_PROPERTYNAME,
141
+                FilesPlugin::SIZE_PROPERTYNAME,
142
+                FilesPlugin::PERMISSIONS_PROPERTYNAME,
143
+                FilesPlugin::DOWNLOADURL_PROPERTYNAME,
144
+                FilesPlugin::OWNER_ID_PROPERTYNAME,
145
+                FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME,
146
+                FilesPlugin::DATA_FINGERPRINT_PROPERTYNAME,
147
+                FilesPlugin::CREATIONDATE_PROPERTYNAME,
148
+            ],
149
+            0
150
+        );
151
+
152
+        $user = $this->getMockBuilder(User::class)
153
+            ->disableOriginalConstructor()->getMock();
154
+        $user
155
+            ->expects($this->once())
156
+            ->method('getUID')
157
+            ->willReturn('foo');
158
+        $user
159
+            ->expects($this->once())
160
+            ->method('getDisplayName')
161
+            ->willReturn('M. Foo');
162
+
163
+        $owner = $this->getMockBuilder(Account::class)
164
+            ->disableOriginalConstructor()->getMock();
165
+        $this->accountManager->expects($this->once())
166
+            ->method('getAccount')
167
+            ->with($user)
168
+            ->willReturn($owner);
169
+
170
+        $node->expects($this->once())
171
+            ->method('getDirectDownload')
172
+            ->willReturn(['url' => 'http://example.com/']);
173
+        $node->expects($this->exactly(2))
174
+            ->method('getOwner')
175
+            ->willReturn($user);
176
+
177
+        $displayNameProp = $this->getMockBuilder(AccountProperty::class)
178
+            ->disableOriginalConstructor()->getMock();
179
+        $owner
180
+            ->expects($this->once())
181
+            ->method('getProperty')
182
+            ->with(IAccountManager::PROPERTY_DISPLAYNAME)
183
+            ->willReturn($displayNameProp);
184
+        $displayNameProp
185
+            ->expects($this->once())
186
+            ->method('getScope')
187
+            ->willReturn(IAccountManager::SCOPE_PUBLISHED);
188
+
189
+        $this->plugin->handleGetProperties(
190
+            $propFind,
191
+            $node
192
+        );
193
+
194
+        $this->assertEquals('"abc"', $propFind->get(FilesPlugin::GETETAG_PROPERTYNAME));
195
+        $this->assertEquals('00000123instanceid', $propFind->get(FilesPlugin::FILEID_PROPERTYNAME));
196
+        $this->assertEquals('123', $propFind->get(FilesPlugin::INTERNAL_FILEID_PROPERTYNAME));
197
+        $this->assertEquals('1973-11-29T21:33:09+00:00', $propFind->get(FilesPlugin::CREATIONDATE_PROPERTYNAME));
198
+        $this->assertEquals(0, $propFind->get(FilesPlugin::SIZE_PROPERTYNAME));
199
+        $this->assertEquals('DWCKMSR', $propFind->get(FilesPlugin::PERMISSIONS_PROPERTYNAME));
200
+        $this->assertEquals('http://example.com/', $propFind->get(FilesPlugin::DOWNLOADURL_PROPERTYNAME));
201
+        $this->assertEquals('foo', $propFind->get(FilesPlugin::OWNER_ID_PROPERTYNAME));
202
+        $this->assertEquals('M. Foo', $propFind->get(FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME));
203
+        $this->assertEquals('my_fingerprint', $propFind->get(FilesPlugin::DATA_FINGERPRINT_PROPERTYNAME));
204
+        $this->assertEquals([], $propFind->get404Properties());
205
+    }
206
+
207
+    public function testGetDisplayNamePropertyWhenNotPublished(): void {
208
+        /** @var File|\PHPUnit\Framework\MockObject\MockObject $node */
209
+        $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File');
210
+
211
+        $propFind = new PropFind(
212
+            '/dummyPath',
213
+            [
214
+                FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME,
215
+            ],
216
+            0
217
+        );
218
+
219
+        $this->userSession->expects($this->once())
220
+            ->method('getUser')
221
+            ->willReturn(null);
222
+
223
+        $user = $this->getMockBuilder(User::class)
224
+            ->disableOriginalConstructor()->getMock();
225
+
226
+        $user
227
+            ->expects($this->never())
228
+            ->method('getDisplayName');
229
+
230
+        $owner = $this->getMockBuilder(Account::class)
231
+            ->disableOriginalConstructor()->getMock();
232
+        $this->accountManager->expects($this->once())
233
+            ->method('getAccount')
234
+            ->with($user)
235
+            ->willReturn($owner);
236
+
237
+        $node->expects($this->once())
238
+            ->method('getOwner')
239
+            ->willReturn($user);
240
+
241
+        $displayNameProp = $this->getMockBuilder(AccountProperty::class)
242
+            ->disableOriginalConstructor()->getMock();
243
+        $owner
244
+            ->expects($this->once())
245
+            ->method('getProperty')
246
+            ->with(IAccountManager::PROPERTY_DISPLAYNAME)
247
+            ->willReturn($displayNameProp);
248
+        $displayNameProp
249
+            ->expects($this->once())
250
+            ->method('getScope')
251
+            ->willReturn(IAccountManager::SCOPE_PRIVATE);
252
+
253
+        $this->plugin->handleGetProperties(
254
+            $propFind,
255
+            $node
256
+        );
257
+
258
+        $this->assertEquals(null, $propFind->get(FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME));
259
+    }
260 260
 	
261
-	public function testGetDisplayNamePropertyWhenNotPublishedButLoggedIn(): void {
262
-		/** @var File|\PHPUnit\Framework\MockObject\MockObject $node */
263
-		$node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File');
264
-
265
-		$propFind = new PropFind(
266
-			'/dummyPath',
267
-			[
268
-				FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME,
269
-			],
270
-			0
271
-		);
272
-
273
-		$user = $this->getMockBuilder(User::class)
274
-			->disableOriginalConstructor()->getMock();
275
-
276
-		$node->expects($this->once())
277
-			->method('getOwner')
278
-			->willReturn($user);
279
-
280
-		$loggedInUser = $this->getMockBuilder(User::class)
281
-			->disableOriginalConstructor()->getMock();
282
-		$this->userSession->expects($this->once())
283
-			->method('getUser')
284
-			->willReturn($loggedInUser);
285
-
286
-		$user
287
-			->expects($this->once())
288
-			->method('getDisplayName')
289
-			->willReturn('M. Foo');
290
-
291
-		$this->accountManager->expects($this->never())
292
-			->method('getAccount');
293
-
294
-		$this->plugin->handleGetProperties(
295
-			$propFind,
296
-			$node
297
-		);
298
-
299
-		$this->assertEquals('M. Foo', $propFind->get(FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME));
300
-	}
301
-
302
-	public function testGetPropertiesStorageNotAvailable(): void {
303
-		/** @var File|\PHPUnit\Framework\MockObject\MockObject $node */
304
-		$node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File');
305
-
306
-		$propFind = new PropFind(
307
-			'/dummyPath',
308
-			[
309
-				FilesPlugin::DOWNLOADURL_PROPERTYNAME,
310
-			],
311
-			0
312
-		);
313
-
314
-		$node->expects($this->once())
315
-			->method('getDirectDownload')
316
-			->will($this->throwException(new StorageNotAvailableException()));
317
-
318
-		$this->plugin->handleGetProperties(
319
-			$propFind,
320
-			$node
321
-		);
322
-
323
-		$this->assertEquals(null, $propFind->get(FilesPlugin::DOWNLOADURL_PROPERTYNAME));
324
-	}
325
-
326
-	public function testGetPublicPermissions(): void {
327
-		/** @var IRequest&MockObject */
328
-		$request = $this->getMockBuilder(IRequest::class)
329
-			->disableOriginalConstructor()
330
-			->getMock();
331
-		$this->plugin = new FilesPlugin(
332
-			$this->tree,
333
-			$this->config,
334
-			$request,
335
-			$this->previewManager,
336
-			$this->userSession,
337
-			$this->filenameValidator,
338
-			$this->accountManager,
339
-			true,
340
-		);
341
-		$this->plugin->initialize($this->server);
342
-
343
-		$propFind = new PropFind(
344
-			'/dummyPath',
345
-			[
346
-				FilesPlugin::PERMISSIONS_PROPERTYNAME,
347
-			],
348
-			0
349
-		);
350
-
351
-		/** @var File|\PHPUnit\Framework\MockObject\MockObject $node */
352
-		$node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File');
353
-		$node->expects($this->any())
354
-			->method('getDavPermissions')
355
-			->willReturn('DWCKMSR');
356
-
357
-		$this->plugin->handleGetProperties(
358
-			$propFind,
359
-			$node
360
-		);
361
-
362
-		$this->assertEquals('DWCKR', $propFind->get(FilesPlugin::PERMISSIONS_PROPERTYNAME));
363
-	}
364
-
365
-	public function testGetPropertiesForDirectory(): void {
366
-		/** @var Directory|\PHPUnit\Framework\MockObject\MockObject $node */
367
-		$node = $this->createTestNode('\OCA\DAV\Connector\Sabre\Directory');
368
-
369
-		$propFind = new PropFind(
370
-			'/dummyPath',
371
-			[
372
-				FilesPlugin::GETETAG_PROPERTYNAME,
373
-				FilesPlugin::FILEID_PROPERTYNAME,
374
-				FilesPlugin::SIZE_PROPERTYNAME,
375
-				FilesPlugin::PERMISSIONS_PROPERTYNAME,
376
-				FilesPlugin::DOWNLOADURL_PROPERTYNAME,
377
-				FilesPlugin::DATA_FINGERPRINT_PROPERTYNAME,
378
-			],
379
-			0
380
-		);
381
-
382
-		$node->expects($this->once())
383
-			->method('getSize')
384
-			->willReturn(1025);
385
-
386
-		$this->plugin->handleGetProperties(
387
-			$propFind,
388
-			$node
389
-		);
390
-
391
-		$this->assertEquals('"abc"', $propFind->get(FilesPlugin::GETETAG_PROPERTYNAME));
392
-		$this->assertEquals('00000123instanceid', $propFind->get(FilesPlugin::FILEID_PROPERTYNAME));
393
-		$this->assertEquals(1025, $propFind->get(FilesPlugin::SIZE_PROPERTYNAME));
394
-		$this->assertEquals('DWCKMSR', $propFind->get(FilesPlugin::PERMISSIONS_PROPERTYNAME));
395
-		$this->assertEquals(null, $propFind->get(FilesPlugin::DOWNLOADURL_PROPERTYNAME));
396
-		$this->assertEquals('my_fingerprint', $propFind->get(FilesPlugin::DATA_FINGERPRINT_PROPERTYNAME));
397
-		$this->assertEquals([FilesPlugin::DOWNLOADURL_PROPERTYNAME], $propFind->get404Properties());
398
-	}
399
-
400
-	public function testGetPropertiesForRootDirectory(): void {
401
-		/** @var Directory|\PHPUnit\Framework\MockObject\MockObject $node */
402
-		$node = $this->getMockBuilder(Directory::class)
403
-			->disableOriginalConstructor()
404
-			->getMock();
405
-		$node->expects($this->any())->method('getPath')->willReturn('/');
406
-
407
-		$fileInfo = $this->createMock(FileInfo::class);
408
-		$fileInfo->expects($this->any())
409
-			->method('isReadable')
410
-			->willReturn(true);
411
-
412
-		$node->expects($this->any())
413
-			->method('getFileInfo')
414
-			->willReturn($fileInfo);
415
-
416
-		$propFind = new PropFind(
417
-			'/',
418
-			[
419
-				FilesPlugin::DATA_FINGERPRINT_PROPERTYNAME,
420
-			],
421
-			0
422
-		);
423
-
424
-		$this->plugin->handleGetProperties(
425
-			$propFind,
426
-			$node
427
-		);
428
-
429
-		$this->assertEquals('my_fingerprint', $propFind->get(FilesPlugin::DATA_FINGERPRINT_PROPERTYNAME));
430
-	}
431
-
432
-	public function testGetPropertiesWhenNoPermission(): void {
433
-		// No read permissions can be caused by files access control.
434
-		// But we still want to load the directory list, so this is okay for us.
435
-		// $this->expectException(\Sabre\DAV\Exception\NotFound::class);
436
-		/** @var Directory|\PHPUnit\Framework\MockObject\MockObject $node */
437
-		$node = $this->getMockBuilder(Directory::class)
438
-			->disableOriginalConstructor()
439
-			->getMock();
440
-		$node->expects($this->any())->method('getPath')->willReturn('/');
441
-
442
-		$fileInfo = $this->createMock(FileInfo::class);
443
-		$fileInfo->expects($this->any())
444
-			->method('isReadable')
445
-			->willReturn(false);
446
-
447
-		$node->expects($this->any())
448
-			->method('getFileInfo')
449
-			->willReturn($fileInfo);
450
-
451
-		$propFind = new PropFind(
452
-			'/test',
453
-			[
454
-				FilesPlugin::DATA_FINGERPRINT_PROPERTYNAME,
455
-			],
456
-			0
457
-		);
458
-
459
-		$this->plugin->handleGetProperties(
460
-			$propFind,
461
-			$node
462
-		);
463
-
464
-		$this->addToAssertionCount(1);
465
-	}
466
-
467
-	public function testUpdateProps(): void {
468
-		$node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File');
469
-
470
-		$testDate = 'Fri, 13 Feb 2015 00:01:02 GMT';
471
-		$testCreationDate = '2007-08-31T16:47+00:00';
472
-
473
-		$node->expects($this->once())
474
-			->method('touch')
475
-			->with($testDate);
476
-
477
-		$node->expects($this->once())
478
-			->method('setEtag')
479
-			->with('newetag')
480
-			->willReturn(true);
481
-
482
-		$node->expects($this->once())
483
-			->method('setCreationTime')
484
-			->with('1188578820');
485
-
486
-		// properties to set
487
-		$propPatch = new PropPatch([
488
-			FilesPlugin::GETETAG_PROPERTYNAME => 'newetag',
489
-			FilesPlugin::LASTMODIFIED_PROPERTYNAME => $testDate,
490
-			FilesPlugin::CREATIONDATE_PROPERTYNAME => $testCreationDate,
491
-		]);
492
-
493
-
494
-		$this->plugin->handleUpdateProperties(
495
-			'/dummypath',
496
-			$propPatch
497
-		);
498
-
499
-		$propPatch->commit();
500
-
501
-		$this->assertEmpty($propPatch->getRemainingMutations());
502
-
503
-		$result = $propPatch->getResult();
504
-		$this->assertEquals(200, $result[FilesPlugin::LASTMODIFIED_PROPERTYNAME]);
505
-		$this->assertEquals(200, $result[FilesPlugin::GETETAG_PROPERTYNAME]);
506
-		$this->assertEquals(200, $result[FilesPlugin::CREATIONDATE_PROPERTYNAME]);
507
-	}
508
-
509
-	public function testUpdatePropsForbidden(): void {
510
-		$propPatch = new PropPatch([
511
-			FilesPlugin::OWNER_ID_PROPERTYNAME => 'user2',
512
-			FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME => 'User Two',
513
-			FilesPlugin::FILEID_PROPERTYNAME => 12345,
514
-			FilesPlugin::PERMISSIONS_PROPERTYNAME => 'C',
515
-			FilesPlugin::SIZE_PROPERTYNAME => 123,
516
-			FilesPlugin::DOWNLOADURL_PROPERTYNAME => 'http://example.com/',
517
-		]);
518
-
519
-		$this->plugin->handleUpdateProperties(
520
-			'/dummypath',
521
-			$propPatch
522
-		);
523
-
524
-		$propPatch->commit();
525
-
526
-		$this->assertEmpty($propPatch->getRemainingMutations());
527
-
528
-		$result = $propPatch->getResult();
529
-		$this->assertEquals(403, $result[FilesPlugin::OWNER_ID_PROPERTYNAME]);
530
-		$this->assertEquals(403, $result[FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME]);
531
-		$this->assertEquals(403, $result[FilesPlugin::FILEID_PROPERTYNAME]);
532
-		$this->assertEquals(403, $result[FilesPlugin::PERMISSIONS_PROPERTYNAME]);
533
-		$this->assertEquals(403, $result[FilesPlugin::SIZE_PROPERTYNAME]);
534
-		$this->assertEquals(403, $result[FilesPlugin::DOWNLOADURL_PROPERTYNAME]);
535
-	}
536
-
537
-	/**
538
-	 * Test case from https://github.com/owncloud/core/issues/5251
539
-	 *
540
-	 * |-FolderA
541
-	 *  |-text.txt
542
-	 * |-test.txt
543
-	 *
544
-	 * FolderA is an incoming shared folder and there are no delete permissions.
545
-	 * Thus moving /FolderA/test.txt to /test.txt should fail already on that check
546
-	 *
547
-	 */
548
-	public function testMoveSrcNotDeletable(): void {
549
-		$this->expectException(\Sabre\DAV\Exception\Forbidden::class);
550
-		$this->expectExceptionMessage('FolderA/test.txt cannot be deleted');
551
-
552
-		$fileInfoFolderATestTXT = $this->getMockBuilder(FileInfo::class)
553
-			->disableOriginalConstructor()
554
-			->getMock();
555
-		$fileInfoFolderATestTXT->expects($this->once())
556
-			->method('isDeletable')
557
-			->willReturn(false);
558
-
559
-		$node = $this->getMockBuilder(Node::class)
560
-			->disableOriginalConstructor()
561
-			->getMock();
562
-		$node->expects($this->atLeastOnce())
563
-			->method('getFileInfo')
564
-			->willReturn($fileInfoFolderATestTXT);
565
-
566
-		$this->tree->expects($this->atLeastOnce())
567
-			->method('getNodeForPath')
568
-			->willReturn($node);
569
-
570
-		$this->plugin->checkMove('FolderA/test.txt', 'test.txt');
571
-	}
572
-
573
-	public function testMoveSrcDeletable(): void {
574
-		$fileInfoFolderATestTXT = $this->getMockBuilder(FileInfo::class)
575
-			->disableOriginalConstructor()
576
-			->getMock();
577
-		$fileInfoFolderATestTXT->expects($this->once())
578
-			->method('isDeletable')
579
-			->willReturn(true);
580
-
581
-		$node = $this->getMockBuilder(Node::class)
582
-			->disableOriginalConstructor()
583
-			->getMock();
584
-		$node->expects($this->atLeastOnce())
585
-			->method('getFileInfo')
586
-			->willReturn($fileInfoFolderATestTXT);
587
-
588
-		$this->tree->expects($this->atLeastOnce())
589
-			->method('getNodeForPath')
590
-			->willReturn($node);
591
-
592
-		$this->plugin->checkMove('FolderA/test.txt', 'test.txt');
593
-	}
594
-
595
-	public function testMoveSrcNotExist(): void {
596
-		$this->expectException(\Sabre\DAV\Exception\NotFound::class);
597
-		$this->expectExceptionMessage('FolderA/test.txt does not exist');
598
-
599
-		$node = $this->getMockBuilder(Node::class)
600
-			->disableOriginalConstructor()
601
-			->getMock();
602
-		$node->expects($this->atLeastOnce())
603
-			->method('getFileInfo')
604
-			->willReturn(null);
605
-
606
-		$this->tree->expects($this->atLeastOnce())
607
-			->method('getNodeForPath')
608
-			->willReturn($node);
609
-
610
-		$this->plugin->checkMove('FolderA/test.txt', 'test.txt');
611
-	}
612
-
613
-	public function testMoveDestinationInvalid(): void {
614
-		$this->expectException(InvalidPath::class);
615
-		$this->expectExceptionMessage('Mocked exception');
616
-
617
-		$fileInfoFolderATestTXT = $this->createMock(FileInfo::class);
618
-		$fileInfoFolderATestTXT->expects(self::any())
619
-			->method('isDeletable')
620
-			->willReturn(true);
621
-
622
-		$node = $this->createMock(Node::class);
623
-		$node->expects($this->atLeastOnce())
624
-			->method('getFileInfo')
625
-			->willReturn($fileInfoFolderATestTXT);
626
-
627
-		$this->tree->expects($this->atLeastOnce())
628
-			->method('getNodeForPath')
629
-			->willReturn($node);
630
-
631
-		$this->filenameValidator->expects(self::once())
632
-			->method('validateFilename')
633
-			->with('invalid\\path.txt')
634
-			->willThrowException(new InvalidPathException('Mocked exception'));
635
-
636
-		$this->plugin->checkMove('FolderA/test.txt', 'invalid\\path.txt');
637
-	}
638
-
639
-	public function testCopySrcNotExist(): void {
640
-		$this->expectException(\Sabre\DAV\Exception\NotFound::class);
641
-		$this->expectExceptionMessage('FolderA/test.txt does not exist');
642
-
643
-		$node = $this->createMock(Node::class);
644
-		$node->expects($this->atLeastOnce())
645
-			->method('getFileInfo')
646
-			->willReturn(null);
647
-
648
-		$this->tree->expects($this->atLeastOnce())
649
-			->method('getNodeForPath')
650
-			->willReturn($node);
651
-
652
-		$this->plugin->checkCopy('FolderA/test.txt', 'test.txt');
653
-	}
654
-
655
-	public function testCopyDestinationInvalid(): void {
656
-		$this->expectException(InvalidPath::class);
657
-		$this->expectExceptionMessage('Mocked exception');
658
-
659
-		$fileInfoFolderATestTXT = $this->createMock(FileInfo::class);
660
-		$node = $this->createMock(Node::class);
661
-		$node->expects($this->atLeastOnce())
662
-			->method('getFileInfo')
663
-			->willReturn($fileInfoFolderATestTXT);
664
-
665
-		$this->tree->expects($this->atLeastOnce())
666
-			->method('getNodeForPath')
667
-			->willReturn($node);
668
-
669
-		$this->filenameValidator->expects(self::once())
670
-			->method('validateFilename')
671
-			->with('invalid\\path.txt')
672
-			->willThrowException(new InvalidPathException('Mocked exception'));
673
-
674
-		$this->plugin->checkCopy('FolderA/test.txt', 'invalid\\path.txt');
675
-	}
676
-
677
-	public function downloadHeadersProvider() {
678
-		return [
679
-			[
680
-				false,
681
-				'attachment; filename*=UTF-8\'\'somefile.xml; filename="somefile.xml"'
682
-			],
683
-			[
684
-				true,
685
-				'attachment; filename="somefile.xml"'
686
-			],
687
-		];
688
-	}
689
-
690
-	/**
691
-	 * @dataProvider downloadHeadersProvider
692
-	 */
693
-	public function testDownloadHeaders($isClumsyAgent, $contentDispositionHeader): void {
694
-		$request = $this->getMockBuilder(RequestInterface::class)
695
-			->disableOriginalConstructor()
696
-			->getMock();
697
-		$response = $this->getMockBuilder(ResponseInterface::class)
698
-			->disableOriginalConstructor()
699
-			->getMock();
700
-
701
-		$request
702
-			->expects($this->once())
703
-			->method('getPath')
704
-			->willReturn('test/somefile.xml');
705
-
706
-		$node = $this->getMockBuilder(File::class)
707
-			->disableOriginalConstructor()
708
-			->getMock();
709
-		$node
710
-			->expects($this->once())
711
-			->method('getName')
712
-			->willReturn('somefile.xml');
713
-
714
-		$this->tree
715
-			->expects($this->once())
716
-			->method('getNodeForPath')
717
-			->with('test/somefile.xml')
718
-			->willReturn($node);
719
-
720
-		$this->request
721
-			->expects($this->once())
722
-			->method('isUserAgent')
723
-			->willReturn($isClumsyAgent);
724
-
725
-		$response
726
-			->expects($this->exactly(2))
727
-			->method('addHeader')
728
-			->withConsecutive(
729
-				['Content-Disposition', $contentDispositionHeader],
730
-				['X-Accel-Buffering', 'no']
731
-			);
732
-
733
-		$this->plugin->httpGet($request, $response);
734
-	}
735
-
736
-	public function testHasPreview(): void {
737
-		/** @var Directory|\PHPUnit\Framework\MockObject\MockObject $node */
738
-		$node = $this->createTestNode('\OCA\DAV\Connector\Sabre\Directory');
739
-
740
-		$propFind = new PropFind(
741
-			'/dummyPath',
742
-			[
743
-				FilesPlugin::HAS_PREVIEW_PROPERTYNAME
744
-			],
745
-			0
746
-		);
747
-
748
-		$this->previewManager->expects($this->once())
749
-			->method('isAvailable')
750
-			->willReturn(false);
751
-
752
-		$this->plugin->handleGetProperties(
753
-			$propFind,
754
-			$node
755
-		);
756
-
757
-		$this->assertEquals('false', $propFind->get(FilesPlugin::HAS_PREVIEW_PROPERTYNAME));
758
-	}
261
+    public function testGetDisplayNamePropertyWhenNotPublishedButLoggedIn(): void {
262
+        /** @var File|\PHPUnit\Framework\MockObject\MockObject $node */
263
+        $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File');
264
+
265
+        $propFind = new PropFind(
266
+            '/dummyPath',
267
+            [
268
+                FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME,
269
+            ],
270
+            0
271
+        );
272
+
273
+        $user = $this->getMockBuilder(User::class)
274
+            ->disableOriginalConstructor()->getMock();
275
+
276
+        $node->expects($this->once())
277
+            ->method('getOwner')
278
+            ->willReturn($user);
279
+
280
+        $loggedInUser = $this->getMockBuilder(User::class)
281
+            ->disableOriginalConstructor()->getMock();
282
+        $this->userSession->expects($this->once())
283
+            ->method('getUser')
284
+            ->willReturn($loggedInUser);
285
+
286
+        $user
287
+            ->expects($this->once())
288
+            ->method('getDisplayName')
289
+            ->willReturn('M. Foo');
290
+
291
+        $this->accountManager->expects($this->never())
292
+            ->method('getAccount');
293
+
294
+        $this->plugin->handleGetProperties(
295
+            $propFind,
296
+            $node
297
+        );
298
+
299
+        $this->assertEquals('M. Foo', $propFind->get(FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME));
300
+    }
301
+
302
+    public function testGetPropertiesStorageNotAvailable(): void {
303
+        /** @var File|\PHPUnit\Framework\MockObject\MockObject $node */
304
+        $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File');
305
+
306
+        $propFind = new PropFind(
307
+            '/dummyPath',
308
+            [
309
+                FilesPlugin::DOWNLOADURL_PROPERTYNAME,
310
+            ],
311
+            0
312
+        );
313
+
314
+        $node->expects($this->once())
315
+            ->method('getDirectDownload')
316
+            ->will($this->throwException(new StorageNotAvailableException()));
317
+
318
+        $this->plugin->handleGetProperties(
319
+            $propFind,
320
+            $node
321
+        );
322
+
323
+        $this->assertEquals(null, $propFind->get(FilesPlugin::DOWNLOADURL_PROPERTYNAME));
324
+    }
325
+
326
+    public function testGetPublicPermissions(): void {
327
+        /** @var IRequest&MockObject */
328
+        $request = $this->getMockBuilder(IRequest::class)
329
+            ->disableOriginalConstructor()
330
+            ->getMock();
331
+        $this->plugin = new FilesPlugin(
332
+            $this->tree,
333
+            $this->config,
334
+            $request,
335
+            $this->previewManager,
336
+            $this->userSession,
337
+            $this->filenameValidator,
338
+            $this->accountManager,
339
+            true,
340
+        );
341
+        $this->plugin->initialize($this->server);
342
+
343
+        $propFind = new PropFind(
344
+            '/dummyPath',
345
+            [
346
+                FilesPlugin::PERMISSIONS_PROPERTYNAME,
347
+            ],
348
+            0
349
+        );
350
+
351
+        /** @var File|\PHPUnit\Framework\MockObject\MockObject $node */
352
+        $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File');
353
+        $node->expects($this->any())
354
+            ->method('getDavPermissions')
355
+            ->willReturn('DWCKMSR');
356
+
357
+        $this->plugin->handleGetProperties(
358
+            $propFind,
359
+            $node
360
+        );
361
+
362
+        $this->assertEquals('DWCKR', $propFind->get(FilesPlugin::PERMISSIONS_PROPERTYNAME));
363
+    }
364
+
365
+    public function testGetPropertiesForDirectory(): void {
366
+        /** @var Directory|\PHPUnit\Framework\MockObject\MockObject $node */
367
+        $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\Directory');
368
+
369
+        $propFind = new PropFind(
370
+            '/dummyPath',
371
+            [
372
+                FilesPlugin::GETETAG_PROPERTYNAME,
373
+                FilesPlugin::FILEID_PROPERTYNAME,
374
+                FilesPlugin::SIZE_PROPERTYNAME,
375
+                FilesPlugin::PERMISSIONS_PROPERTYNAME,
376
+                FilesPlugin::DOWNLOADURL_PROPERTYNAME,
377
+                FilesPlugin::DATA_FINGERPRINT_PROPERTYNAME,
378
+            ],
379
+            0
380
+        );
381
+
382
+        $node->expects($this->once())
383
+            ->method('getSize')
384
+            ->willReturn(1025);
385
+
386
+        $this->plugin->handleGetProperties(
387
+            $propFind,
388
+            $node
389
+        );
390
+
391
+        $this->assertEquals('"abc"', $propFind->get(FilesPlugin::GETETAG_PROPERTYNAME));
392
+        $this->assertEquals('00000123instanceid', $propFind->get(FilesPlugin::FILEID_PROPERTYNAME));
393
+        $this->assertEquals(1025, $propFind->get(FilesPlugin::SIZE_PROPERTYNAME));
394
+        $this->assertEquals('DWCKMSR', $propFind->get(FilesPlugin::PERMISSIONS_PROPERTYNAME));
395
+        $this->assertEquals(null, $propFind->get(FilesPlugin::DOWNLOADURL_PROPERTYNAME));
396
+        $this->assertEquals('my_fingerprint', $propFind->get(FilesPlugin::DATA_FINGERPRINT_PROPERTYNAME));
397
+        $this->assertEquals([FilesPlugin::DOWNLOADURL_PROPERTYNAME], $propFind->get404Properties());
398
+    }
399
+
400
+    public function testGetPropertiesForRootDirectory(): void {
401
+        /** @var Directory|\PHPUnit\Framework\MockObject\MockObject $node */
402
+        $node = $this->getMockBuilder(Directory::class)
403
+            ->disableOriginalConstructor()
404
+            ->getMock();
405
+        $node->expects($this->any())->method('getPath')->willReturn('/');
406
+
407
+        $fileInfo = $this->createMock(FileInfo::class);
408
+        $fileInfo->expects($this->any())
409
+            ->method('isReadable')
410
+            ->willReturn(true);
411
+
412
+        $node->expects($this->any())
413
+            ->method('getFileInfo')
414
+            ->willReturn($fileInfo);
415
+
416
+        $propFind = new PropFind(
417
+            '/',
418
+            [
419
+                FilesPlugin::DATA_FINGERPRINT_PROPERTYNAME,
420
+            ],
421
+            0
422
+        );
423
+
424
+        $this->plugin->handleGetProperties(
425
+            $propFind,
426
+            $node
427
+        );
428
+
429
+        $this->assertEquals('my_fingerprint', $propFind->get(FilesPlugin::DATA_FINGERPRINT_PROPERTYNAME));
430
+    }
431
+
432
+    public function testGetPropertiesWhenNoPermission(): void {
433
+        // No read permissions can be caused by files access control.
434
+        // But we still want to load the directory list, so this is okay for us.
435
+        // $this->expectException(\Sabre\DAV\Exception\NotFound::class);
436
+        /** @var Directory|\PHPUnit\Framework\MockObject\MockObject $node */
437
+        $node = $this->getMockBuilder(Directory::class)
438
+            ->disableOriginalConstructor()
439
+            ->getMock();
440
+        $node->expects($this->any())->method('getPath')->willReturn('/');
441
+
442
+        $fileInfo = $this->createMock(FileInfo::class);
443
+        $fileInfo->expects($this->any())
444
+            ->method('isReadable')
445
+            ->willReturn(false);
446
+
447
+        $node->expects($this->any())
448
+            ->method('getFileInfo')
449
+            ->willReturn($fileInfo);
450
+
451
+        $propFind = new PropFind(
452
+            '/test',
453
+            [
454
+                FilesPlugin::DATA_FINGERPRINT_PROPERTYNAME,
455
+            ],
456
+            0
457
+        );
458
+
459
+        $this->plugin->handleGetProperties(
460
+            $propFind,
461
+            $node
462
+        );
463
+
464
+        $this->addToAssertionCount(1);
465
+    }
466
+
467
+    public function testUpdateProps(): void {
468
+        $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File');
469
+
470
+        $testDate = 'Fri, 13 Feb 2015 00:01:02 GMT';
471
+        $testCreationDate = '2007-08-31T16:47+00:00';
472
+
473
+        $node->expects($this->once())
474
+            ->method('touch')
475
+            ->with($testDate);
476
+
477
+        $node->expects($this->once())
478
+            ->method('setEtag')
479
+            ->with('newetag')
480
+            ->willReturn(true);
481
+
482
+        $node->expects($this->once())
483
+            ->method('setCreationTime')
484
+            ->with('1188578820');
485
+
486
+        // properties to set
487
+        $propPatch = new PropPatch([
488
+            FilesPlugin::GETETAG_PROPERTYNAME => 'newetag',
489
+            FilesPlugin::LASTMODIFIED_PROPERTYNAME => $testDate,
490
+            FilesPlugin::CREATIONDATE_PROPERTYNAME => $testCreationDate,
491
+        ]);
492
+
493
+
494
+        $this->plugin->handleUpdateProperties(
495
+            '/dummypath',
496
+            $propPatch
497
+        );
498
+
499
+        $propPatch->commit();
500
+
501
+        $this->assertEmpty($propPatch->getRemainingMutations());
502
+
503
+        $result = $propPatch->getResult();
504
+        $this->assertEquals(200, $result[FilesPlugin::LASTMODIFIED_PROPERTYNAME]);
505
+        $this->assertEquals(200, $result[FilesPlugin::GETETAG_PROPERTYNAME]);
506
+        $this->assertEquals(200, $result[FilesPlugin::CREATIONDATE_PROPERTYNAME]);
507
+    }
508
+
509
+    public function testUpdatePropsForbidden(): void {
510
+        $propPatch = new PropPatch([
511
+            FilesPlugin::OWNER_ID_PROPERTYNAME => 'user2',
512
+            FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME => 'User Two',
513
+            FilesPlugin::FILEID_PROPERTYNAME => 12345,
514
+            FilesPlugin::PERMISSIONS_PROPERTYNAME => 'C',
515
+            FilesPlugin::SIZE_PROPERTYNAME => 123,
516
+            FilesPlugin::DOWNLOADURL_PROPERTYNAME => 'http://example.com/',
517
+        ]);
518
+
519
+        $this->plugin->handleUpdateProperties(
520
+            '/dummypath',
521
+            $propPatch
522
+        );
523
+
524
+        $propPatch->commit();
525
+
526
+        $this->assertEmpty($propPatch->getRemainingMutations());
527
+
528
+        $result = $propPatch->getResult();
529
+        $this->assertEquals(403, $result[FilesPlugin::OWNER_ID_PROPERTYNAME]);
530
+        $this->assertEquals(403, $result[FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME]);
531
+        $this->assertEquals(403, $result[FilesPlugin::FILEID_PROPERTYNAME]);
532
+        $this->assertEquals(403, $result[FilesPlugin::PERMISSIONS_PROPERTYNAME]);
533
+        $this->assertEquals(403, $result[FilesPlugin::SIZE_PROPERTYNAME]);
534
+        $this->assertEquals(403, $result[FilesPlugin::DOWNLOADURL_PROPERTYNAME]);
535
+    }
536
+
537
+    /**
538
+     * Test case from https://github.com/owncloud/core/issues/5251
539
+     *
540
+     * |-FolderA
541
+     *  |-text.txt
542
+     * |-test.txt
543
+     *
544
+     * FolderA is an incoming shared folder and there are no delete permissions.
545
+     * Thus moving /FolderA/test.txt to /test.txt should fail already on that check
546
+     *
547
+     */
548
+    public function testMoveSrcNotDeletable(): void {
549
+        $this->expectException(\Sabre\DAV\Exception\Forbidden::class);
550
+        $this->expectExceptionMessage('FolderA/test.txt cannot be deleted');
551
+
552
+        $fileInfoFolderATestTXT = $this->getMockBuilder(FileInfo::class)
553
+            ->disableOriginalConstructor()
554
+            ->getMock();
555
+        $fileInfoFolderATestTXT->expects($this->once())
556
+            ->method('isDeletable')
557
+            ->willReturn(false);
558
+
559
+        $node = $this->getMockBuilder(Node::class)
560
+            ->disableOriginalConstructor()
561
+            ->getMock();
562
+        $node->expects($this->atLeastOnce())
563
+            ->method('getFileInfo')
564
+            ->willReturn($fileInfoFolderATestTXT);
565
+
566
+        $this->tree->expects($this->atLeastOnce())
567
+            ->method('getNodeForPath')
568
+            ->willReturn($node);
569
+
570
+        $this->plugin->checkMove('FolderA/test.txt', 'test.txt');
571
+    }
572
+
573
+    public function testMoveSrcDeletable(): void {
574
+        $fileInfoFolderATestTXT = $this->getMockBuilder(FileInfo::class)
575
+            ->disableOriginalConstructor()
576
+            ->getMock();
577
+        $fileInfoFolderATestTXT->expects($this->once())
578
+            ->method('isDeletable')
579
+            ->willReturn(true);
580
+
581
+        $node = $this->getMockBuilder(Node::class)
582
+            ->disableOriginalConstructor()
583
+            ->getMock();
584
+        $node->expects($this->atLeastOnce())
585
+            ->method('getFileInfo')
586
+            ->willReturn($fileInfoFolderATestTXT);
587
+
588
+        $this->tree->expects($this->atLeastOnce())
589
+            ->method('getNodeForPath')
590
+            ->willReturn($node);
591
+
592
+        $this->plugin->checkMove('FolderA/test.txt', 'test.txt');
593
+    }
594
+
595
+    public function testMoveSrcNotExist(): void {
596
+        $this->expectException(\Sabre\DAV\Exception\NotFound::class);
597
+        $this->expectExceptionMessage('FolderA/test.txt does not exist');
598
+
599
+        $node = $this->getMockBuilder(Node::class)
600
+            ->disableOriginalConstructor()
601
+            ->getMock();
602
+        $node->expects($this->atLeastOnce())
603
+            ->method('getFileInfo')
604
+            ->willReturn(null);
605
+
606
+        $this->tree->expects($this->atLeastOnce())
607
+            ->method('getNodeForPath')
608
+            ->willReturn($node);
609
+
610
+        $this->plugin->checkMove('FolderA/test.txt', 'test.txt');
611
+    }
612
+
613
+    public function testMoveDestinationInvalid(): void {
614
+        $this->expectException(InvalidPath::class);
615
+        $this->expectExceptionMessage('Mocked exception');
616
+
617
+        $fileInfoFolderATestTXT = $this->createMock(FileInfo::class);
618
+        $fileInfoFolderATestTXT->expects(self::any())
619
+            ->method('isDeletable')
620
+            ->willReturn(true);
621
+
622
+        $node = $this->createMock(Node::class);
623
+        $node->expects($this->atLeastOnce())
624
+            ->method('getFileInfo')
625
+            ->willReturn($fileInfoFolderATestTXT);
626
+
627
+        $this->tree->expects($this->atLeastOnce())
628
+            ->method('getNodeForPath')
629
+            ->willReturn($node);
630
+
631
+        $this->filenameValidator->expects(self::once())
632
+            ->method('validateFilename')
633
+            ->with('invalid\\path.txt')
634
+            ->willThrowException(new InvalidPathException('Mocked exception'));
635
+
636
+        $this->plugin->checkMove('FolderA/test.txt', 'invalid\\path.txt');
637
+    }
638
+
639
+    public function testCopySrcNotExist(): void {
640
+        $this->expectException(\Sabre\DAV\Exception\NotFound::class);
641
+        $this->expectExceptionMessage('FolderA/test.txt does not exist');
642
+
643
+        $node = $this->createMock(Node::class);
644
+        $node->expects($this->atLeastOnce())
645
+            ->method('getFileInfo')
646
+            ->willReturn(null);
647
+
648
+        $this->tree->expects($this->atLeastOnce())
649
+            ->method('getNodeForPath')
650
+            ->willReturn($node);
651
+
652
+        $this->plugin->checkCopy('FolderA/test.txt', 'test.txt');
653
+    }
654
+
655
+    public function testCopyDestinationInvalid(): void {
656
+        $this->expectException(InvalidPath::class);
657
+        $this->expectExceptionMessage('Mocked exception');
658
+
659
+        $fileInfoFolderATestTXT = $this->createMock(FileInfo::class);
660
+        $node = $this->createMock(Node::class);
661
+        $node->expects($this->atLeastOnce())
662
+            ->method('getFileInfo')
663
+            ->willReturn($fileInfoFolderATestTXT);
664
+
665
+        $this->tree->expects($this->atLeastOnce())
666
+            ->method('getNodeForPath')
667
+            ->willReturn($node);
668
+
669
+        $this->filenameValidator->expects(self::once())
670
+            ->method('validateFilename')
671
+            ->with('invalid\\path.txt')
672
+            ->willThrowException(new InvalidPathException('Mocked exception'));
673
+
674
+        $this->plugin->checkCopy('FolderA/test.txt', 'invalid\\path.txt');
675
+    }
676
+
677
+    public function downloadHeadersProvider() {
678
+        return [
679
+            [
680
+                false,
681
+                'attachment; filename*=UTF-8\'\'somefile.xml; filename="somefile.xml"'
682
+            ],
683
+            [
684
+                true,
685
+                'attachment; filename="somefile.xml"'
686
+            ],
687
+        ];
688
+    }
689
+
690
+    /**
691
+     * @dataProvider downloadHeadersProvider
692
+     */
693
+    public function testDownloadHeaders($isClumsyAgent, $contentDispositionHeader): void {
694
+        $request = $this->getMockBuilder(RequestInterface::class)
695
+            ->disableOriginalConstructor()
696
+            ->getMock();
697
+        $response = $this->getMockBuilder(ResponseInterface::class)
698
+            ->disableOriginalConstructor()
699
+            ->getMock();
700
+
701
+        $request
702
+            ->expects($this->once())
703
+            ->method('getPath')
704
+            ->willReturn('test/somefile.xml');
705
+
706
+        $node = $this->getMockBuilder(File::class)
707
+            ->disableOriginalConstructor()
708
+            ->getMock();
709
+        $node
710
+            ->expects($this->once())
711
+            ->method('getName')
712
+            ->willReturn('somefile.xml');
713
+
714
+        $this->tree
715
+            ->expects($this->once())
716
+            ->method('getNodeForPath')
717
+            ->with('test/somefile.xml')
718
+            ->willReturn($node);
719
+
720
+        $this->request
721
+            ->expects($this->once())
722
+            ->method('isUserAgent')
723
+            ->willReturn($isClumsyAgent);
724
+
725
+        $response
726
+            ->expects($this->exactly(2))
727
+            ->method('addHeader')
728
+            ->withConsecutive(
729
+                ['Content-Disposition', $contentDispositionHeader],
730
+                ['X-Accel-Buffering', 'no']
731
+            );
732
+
733
+        $this->plugin->httpGet($request, $response);
734
+    }
735
+
736
+    public function testHasPreview(): void {
737
+        /** @var Directory|\PHPUnit\Framework\MockObject\MockObject $node */
738
+        $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\Directory');
739
+
740
+        $propFind = new PropFind(
741
+            '/dummyPath',
742
+            [
743
+                FilesPlugin::HAS_PREVIEW_PROPERTYNAME
744
+            ],
745
+            0
746
+        );
747
+
748
+        $this->previewManager->expects($this->once())
749
+            ->method('isAvailable')
750
+            ->willReturn(false);
751
+
752
+        $this->plugin->handleGetProperties(
753
+            $propFind,
754
+            $node
755
+        );
756
+
757
+        $this->assertEquals('false', $propFind->get(FilesPlugin::HAS_PREVIEW_PROPERTYNAME));
758
+    }
759 759
 }
Please login to merge, or discard this patch.
apps/dav/tests/unit/Connector/Sabre/FilesReportPluginTest.php 1 patch
Indentation   +887 added lines, -887 removed lines patch added patch discarded remove patch
@@ -36,891 +36,891 @@
 block discarded – undo
36 36
 
37 37
 class FilesReportPluginTest extends \Test\TestCase {
38 38
 
39
-	private \Sabre\DAV\Server&MockObject $server;
40
-	private Tree&MockObject $tree;
41
-	private ISystemTagObjectMapper&MockObject $tagMapper;
42
-	private ISystemTagManager&MockObject $tagManager;
43
-	private ITags&MockObject $privateTags;
44
-	private ITagManager&MockObject $privateTagManager;
45
-	private IUserSession&MockObject $userSession;
46
-	private FilesReportPluginImplementation $plugin;
47
-	private View&MockObject $view;
48
-	private IGroupManager&MockObject $groupManager;
49
-	private Folder&MockObject $userFolder;
50
-	private IPreview&MockObject $previewManager;
51
-	private IAppManager&MockObject $appManager;
52
-
53
-	protected function setUp(): void {
54
-		parent::setUp();
55
-		$this->tree = $this->getMockBuilder(Tree::class)
56
-			->disableOriginalConstructor()
57
-			->getMock();
58
-
59
-		$this->view = $this->getMockBuilder(View::class)
60
-			->disableOriginalConstructor()
61
-			->getMock();
62
-
63
-		$this->server = $this->getMockBuilder('\Sabre\DAV\Server')
64
-			->setConstructorArgs([$this->tree])
65
-			->onlyMethods(['getRequestUri', 'getBaseUri'])
66
-			->getMock();
67
-
68
-		$this->server->expects($this->any())
69
-			->method('getBaseUri')
70
-			->willReturn('http://example.com/owncloud/remote.php/dav');
71
-
72
-		$this->groupManager = $this->getMockBuilder(IGroupManager::class)
73
-			->disableOriginalConstructor()
74
-			->getMock();
75
-
76
-		$this->userFolder = $this->getMockBuilder(Folder::class)
77
-			->disableOriginalConstructor()
78
-			->getMock();
79
-
80
-		$this->previewManager = $this->getMockBuilder(IPreview::class)
81
-			->disableOriginalConstructor()
82
-			->getMock();
83
-
84
-		$this->appManager = $this->getMockBuilder(IAppManager::class)
85
-			->disableOriginalConstructor()
86
-			->getMock();
87
-
88
-		$this->tagManager = $this->createMock(ISystemTagManager::class);
89
-		$this->tagMapper = $this->createMock(ISystemTagObjectMapper::class);
90
-		$this->userSession = $this->createMock(IUserSession::class);
91
-		$this->privateTags = $this->createMock(ITags::class);
92
-		$this->privateTagManager = $this->createMock(ITagManager::class);
93
-		$this->privateTagManager->expects($this->any())
94
-			->method('load')
95
-			->with('files')
96
-			->willReturn($this->privateTags);
97
-
98
-		$user = $this->getMockBuilder(IUser::class)
99
-			->disableOriginalConstructor()
100
-			->getMock();
101
-		$user->expects($this->any())
102
-			->method('getUID')
103
-			->willReturn('testuser');
104
-		$this->userSession->expects($this->any())
105
-			->method('getUser')
106
-			->willReturn($user);
107
-
108
-		$this->plugin = new FilesReportPluginImplementation(
109
-			$this->tree,
110
-			$this->view,
111
-			$this->tagManager,
112
-			$this->tagMapper,
113
-			$this->privateTagManager,
114
-			$this->userSession,
115
-			$this->groupManager,
116
-			$this->userFolder,
117
-			$this->appManager
118
-		);
119
-	}
120
-
121
-	public function testOnReportInvalidNode(): void {
122
-		$path = 'totally/unrelated/13';
123
-
124
-		$this->tree->expects($this->any())
125
-			->method('getNodeForPath')
126
-			->with('/' . $path)
127
-			->willReturn(
128
-				$this->getMockBuilder(INode::class)
129
-					->disableOriginalConstructor()
130
-					->getMock()
131
-			);
132
-
133
-		$this->server->expects($this->any())
134
-			->method('getRequestUri')
135
-			->willReturn($path);
136
-		$this->plugin->initialize($this->server);
137
-
138
-		$this->assertNull($this->plugin->onReport(FilesReportPluginImplementation::REPORT_NAME, [], '/' . $path));
139
-	}
140
-
141
-	public function testOnReportInvalidReportName(): void {
142
-		$path = 'test';
143
-
144
-		$this->tree->expects($this->any())
145
-			->method('getNodeForPath')
146
-			->with('/' . $path)
147
-			->willReturn(
148
-				$this->getMockBuilder(INode::class)
149
-					->disableOriginalConstructor()
150
-					->getMock()
151
-			);
152
-
153
-		$this->server->expects($this->any())
154
-			->method('getRequestUri')
155
-			->willReturn($path);
156
-		$this->plugin->initialize($this->server);
157
-
158
-		$this->assertNull($this->plugin->onReport('{whoever}whatever', [], '/' . $path));
159
-	}
160
-
161
-	public function testOnReport(): void {
162
-		$path = 'test';
163
-
164
-		$parameters = [
165
-			[
166
-				'name' => '{DAV:}prop',
167
-				'value' => [
168
-					['name' => '{DAV:}getcontentlength', 'value' => ''],
169
-					['name' => '{http://owncloud.org/ns}size', 'value' => ''],
170
-				],
171
-			],
172
-			[
173
-				'name' => '{http://owncloud.org/ns}filter-rules',
174
-				'value' => [
175
-					['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
176
-					['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'],
177
-				],
178
-			],
179
-		];
180
-
181
-		$this->groupManager->expects($this->any())
182
-			->method('isAdmin')
183
-			->willReturn(true);
184
-
185
-		$reportTargetNode = $this->getMockBuilder(Directory::class)
186
-			->disableOriginalConstructor()
187
-			->getMock();
188
-		$reportTargetNode->expects($this->any())
189
-			->method('getPath')
190
-			->willReturn('');
191
-
192
-		$response = $this->getMockBuilder(ResponseInterface::class)
193
-			->disableOriginalConstructor()
194
-			->getMock();
195
-
196
-		$response->expects($this->once())
197
-			->method('setHeader')
198
-			->with('Content-Type', 'application/xml; charset=utf-8');
199
-
200
-		$response->expects($this->once())
201
-			->method('setStatus')
202
-			->with(207);
203
-
204
-		$response->expects($this->once())
205
-			->method('setBody');
206
-
207
-		$this->tree->expects($this->any())
208
-			->method('getNodeForPath')
209
-			->with('/' . $path)
210
-			->willReturn($reportTargetNode);
211
-
212
-		$filesNode1 = $this->createMock(File::class);
213
-		$filesNode1->expects($this->any())
214
-			->method('getSize')
215
-			->willReturn(12);
216
-		$filesNode2 = $this->createMock(Folder::class);
217
-		$filesNode2->expects($this->any())
218
-			->method('getSize')
219
-			->willReturn(10);
220
-
221
-		$tag123 = $this->createMock(ISystemTag::class);
222
-		$tag123->expects($this->any())
223
-			->method('getName')
224
-			->willReturn('OneTwoThree');
225
-		$tag123->expects($this->any())
226
-			->method('isUserVisible')
227
-			->willReturn(true);
228
-		$tag456 = $this->createMock(ISystemTag::class);
229
-		$tag456->expects($this->any())
230
-			->method('getName')
231
-			->willReturn('FourFiveSix');
232
-		$tag456->expects($this->any())
233
-			->method('isUserVisible')
234
-			->willReturn(true);
235
-
236
-		$this->tagManager->expects($this->once())
237
-			->method('getTagsByIds')
238
-			->with(['123', '456'])
239
-			->willReturn([$tag123, $tag456]);
240
-
241
-		$this->userFolder->expects($this->exactly(2))
242
-			->method('searchBySystemTag')
243
-			->withConsecutive(
244
-				['OneTwoThree'],
245
-				['FourFiveSix'],
246
-			)
247
-			->willReturnOnConsecutiveCalls(
248
-				[$filesNode1],
249
-				[$filesNode2],
250
-			);
251
-
252
-		$this->server->expects($this->any())
253
-			->method('getRequestUri')
254
-			->willReturn($path);
255
-		$this->server->httpResponse = $response;
256
-		$this->plugin->initialize($this->server);
257
-
258
-		$this->assertFalse($this->plugin->onReport(FilesReportPluginImplementation::REPORT_NAME, $parameters, '/' . $path));
259
-	}
260
-
261
-	public function testFindNodesByFileIdsRoot(): void {
262
-		$filesNode1 = $this->getMockBuilder(Folder::class)
263
-			->disableOriginalConstructor()
264
-			->getMock();
265
-		$filesNode1->expects($this->once())
266
-			->method('getName')
267
-			->willReturn('first node');
268
-
269
-		$filesNode2 = $this->getMockBuilder(File::class)
270
-			->disableOriginalConstructor()
271
-			->getMock();
272
-		$filesNode2->expects($this->once())
273
-			->method('getName')
274
-			->willReturn('second node');
275
-
276
-		$reportTargetNode = $this->getMockBuilder(Directory::class)
277
-			->disableOriginalConstructor()
278
-			->getMock();
279
-		$reportTargetNode->expects($this->any())
280
-			->method('getPath')
281
-			->willReturn('/');
282
-
283
-		$this->userFolder->expects($this->exactly(2))
284
-			->method('getFirstNodeById')
285
-			->withConsecutive(
286
-				['111'],
287
-				['222'],
288
-			)
289
-			->willReturnOnConsecutiveCalls(
290
-				$filesNode1,
291
-				$filesNode2,
292
-			);
293
-
294
-		/** @var Directory&MockObject $reportTargetNode */
295
-		$result = $this->plugin->findNodesByFileIds($reportTargetNode, ['111', '222']);
296
-
297
-		$this->assertCount(2, $result);
298
-		$this->assertInstanceOf('\OCA\DAV\Connector\Sabre\Directory', $result[0]);
299
-		$this->assertEquals('first node', $result[0]->getName());
300
-		$this->assertInstanceOf('\OCA\DAV\Connector\Sabre\File', $result[1]);
301
-		$this->assertEquals('second node', $result[1]->getName());
302
-	}
303
-
304
-	public function testFindNodesByFileIdsSubDir(): void {
305
-		$filesNode1 = $this->getMockBuilder(Folder::class)
306
-			->disableOriginalConstructor()
307
-			->getMock();
308
-		$filesNode1->expects($this->once())
309
-			->method('getName')
310
-			->willReturn('first node');
311
-
312
-		$filesNode2 = $this->getMockBuilder(File::class)
313
-			->disableOriginalConstructor()
314
-			->getMock();
315
-		$filesNode2->expects($this->once())
316
-			->method('getName')
317
-			->willReturn('second node');
318
-
319
-		$reportTargetNode = $this->getMockBuilder(Directory::class)
320
-			->disableOriginalConstructor()
321
-			->getMock();
322
-		$reportTargetNode->expects($this->any())
323
-			->method('getPath')
324
-			->willReturn('/sub1/sub2');
325
-
326
-
327
-		$subNode = $this->getMockBuilder(Folder::class)
328
-			->disableOriginalConstructor()
329
-			->getMock();
330
-
331
-		$this->userFolder->expects($this->once())
332
-			->method('get')
333
-			->with('/sub1/sub2')
334
-			->willReturn($subNode);
335
-
336
-		$subNode->expects($this->exactly(2))
337
-			->method('getFirstNodeById')
338
-			->withConsecutive(
339
-				['111'],
340
-				['222'],
341
-			)
342
-			->willReturnOnConsecutiveCalls(
343
-				$filesNode1,
344
-				$filesNode2,
345
-			);
346
-
347
-		/** @var Directory&MockObject $reportTargetNode */
348
-		$result = $this->plugin->findNodesByFileIds($reportTargetNode, ['111', '222']);
349
-
350
-		$this->assertCount(2, $result);
351
-		$this->assertInstanceOf('\OCA\DAV\Connector\Sabre\Directory', $result[0]);
352
-		$this->assertEquals('first node', $result[0]->getName());
353
-		$this->assertInstanceOf('\OCA\DAV\Connector\Sabre\File', $result[1]);
354
-		$this->assertEquals('second node', $result[1]->getName());
355
-	}
356
-
357
-	public function testPrepareResponses(): void {
358
-		$requestedProps = ['{DAV:}getcontentlength', '{http://owncloud.org/ns}fileid', '{DAV:}resourcetype'];
359
-
360
-		$fileInfo = $this->createMock(FileInfo::class);
361
-		$fileInfo->method('isReadable')->willReturn(true);
362
-
363
-		$node1 = $this->getMockBuilder(Directory::class)
364
-			->disableOriginalConstructor()
365
-			->getMock();
366
-		$node2 = $this->getMockBuilder(\OCA\DAV\Connector\Sabre\File::class)
367
-			->disableOriginalConstructor()
368
-			->getMock();
369
-
370
-		$node1->expects($this->once())
371
-			->method('getInternalFileId')
372
-			->willReturn('111');
373
-		$node1->expects($this->any())
374
-			->method('getPath')
375
-			->willReturn('/node1');
376
-		$node1->method('getFileInfo')->willReturn($fileInfo);
377
-		$node2->expects($this->once())
378
-			->method('getInternalFileId')
379
-			->willReturn('222');
380
-		$node2->expects($this->once())
381
-			->method('getSize')
382
-			->willReturn(1024);
383
-		$node2->expects($this->any())
384
-			->method('getPath')
385
-			->willReturn('/sub/node2');
386
-		$node2->method('getFileInfo')->willReturn($fileInfo);
387
-
388
-		$config = $this->getMockBuilder(IConfig::class)
389
-			->disableOriginalConstructor()
390
-			->getMock();
391
-
392
-		$validator = $this->createMock(IFilenameValidator::class);
393
-		$accountManager = $this->createMock(IAccountManager::class);
394
-
395
-		$this->server->addPlugin(
396
-			new FilesPlugin(
397
-				$this->tree,
398
-				$config,
399
-				$this->createMock(IRequest::class),
400
-				$this->previewManager,
401
-				$this->createMock(IUserSession::class),
402
-				$validator,
403
-				$accountManager,
404
-			)
405
-		);
406
-		$this->plugin->initialize($this->server);
407
-		$responses = $this->plugin->prepareResponses('/files/username', $requestedProps, [$node1, $node2]);
408
-
409
-		$this->assertCount(2, $responses);
410
-
411
-		$this->assertEquals('http://example.com/owncloud/remote.php/dav/files/username/node1', $responses[0]->getHref());
412
-		$this->assertEquals('http://example.com/owncloud/remote.php/dav/files/username/sub/node2', $responses[1]->getHref());
413
-
414
-		$props1 = $responses[0]->getResponseProperties();
415
-		$this->assertEquals('111', $props1[200]['{http://owncloud.org/ns}fileid']);
416
-		$this->assertNull($props1[404]['{DAV:}getcontentlength']);
417
-		$this->assertInstanceOf('\Sabre\DAV\Xml\Property\ResourceType', $props1[200]['{DAV:}resourcetype']);
418
-		$resourceType1 = $props1[200]['{DAV:}resourcetype']->getValue();
419
-		$this->assertEquals('{DAV:}collection', $resourceType1[0]);
420
-
421
-		$props2 = $responses[1]->getResponseProperties();
422
-		$this->assertEquals('1024', $props2[200]['{DAV:}getcontentlength']);
423
-		$this->assertEquals('222', $props2[200]['{http://owncloud.org/ns}fileid']);
424
-		$this->assertInstanceOf('\Sabre\DAV\Xml\Property\ResourceType', $props2[200]['{DAV:}resourcetype']);
425
-		$this->assertCount(0, $props2[200]['{DAV:}resourcetype']->getValue());
426
-	}
427
-
428
-	public function testProcessFilterRulesSingle(): void {
429
-		$this->groupManager->expects($this->any())
430
-			->method('isAdmin')
431
-			->willReturn(true);
432
-
433
-		$rules = [
434
-			['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
435
-		];
436
-
437
-		$filesNode1 = $this->createMock(File::class);
438
-		$filesNode1->expects($this->any())
439
-			->method('getSize')
440
-			->willReturn(12);
441
-		$filesNode2 = $this->createMock(Folder::class);
442
-		$filesNode2->expects($this->any())
443
-			->method('getSize')
444
-			->willReturn(10);
445
-
446
-		$tag123 = $this->createMock(ISystemTag::class);
447
-		$tag123->expects($this->any())
448
-			->method('getName')
449
-			->willReturn('OneTwoThree');
450
-		$tag123->expects($this->any())
451
-			->method('isUserVisible')
452
-			->willReturn(true);
453
-
454
-		$this->tagManager->expects($this->once())
455
-			->method('getTagsByIds')
456
-			->with(['123'])
457
-			->willReturn([$tag123]);
458
-
459
-		$this->userFolder->expects($this->once())
460
-			->method('searchBySystemTag')
461
-			->with('OneTwoThree')
462
-			->willReturn([$filesNode1, $filesNode2]);
463
-
464
-		$this->assertEquals([$filesNode1, $filesNode2], $this->invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, 0, 0]));
465
-	}
466
-
467
-	public function testProcessFilterRulesAndCondition(): void {
468
-		$this->groupManager->expects($this->any())
469
-			->method('isAdmin')
470
-			->willReturn(true);
471
-
472
-		$filesNode1 = $this->createMock(File::class);
473
-		$filesNode1->expects($this->any())
474
-			->method('getSize')
475
-			->willReturn(12);
476
-		$filesNode1->expects($this->any())
477
-			->method('getId')
478
-			->willReturn(111);
479
-		$filesNode2 = $this->createMock(Folder::class);
480
-		$filesNode2->expects($this->any())
481
-			->method('getSize')
482
-			->willReturn(10);
483
-		$filesNode2->expects($this->any())
484
-			->method('getId')
485
-			->willReturn(222);
486
-		$filesNode3 = $this->createMock(File::class);
487
-		$filesNode3->expects($this->any())
488
-			->method('getSize')
489
-			->willReturn(14);
490
-		$filesNode3->expects($this->any())
491
-			->method('getId')
492
-			->willReturn(333);
493
-
494
-		$tag123 = $this->createMock(ISystemTag::class);
495
-		$tag123->expects($this->any())
496
-			->method('getName')
497
-			->willReturn('OneTwoThree');
498
-		$tag123->expects($this->any())
499
-			->method('isUserVisible')
500
-			->willReturn(true);
501
-		$tag456 = $this->createMock(ISystemTag::class);
502
-		$tag456->expects($this->any())
503
-			->method('getName')
504
-			->willReturn('FourFiveSix');
505
-		$tag456->expects($this->any())
506
-			->method('isUserVisible')
507
-			->willReturn(true);
508
-
509
-		$this->tagManager->expects($this->once())
510
-			->method('getTagsByIds')
511
-			->with(['123', '456'])
512
-			->willReturn([$tag123, $tag456]);
513
-
514
-		$this->userFolder->expects($this->exactly(2))
515
-			->method('searchBySystemTag')
516
-			->withConsecutive(
517
-				['OneTwoThree'],
518
-				['FourFiveSix'],
519
-			)
520
-			->willReturnOnConsecutiveCalls(
521
-				[$filesNode1, $filesNode2],
522
-				[$filesNode2, $filesNode3],
523
-			);
524
-
525
-		$rules = [
526
-			['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
527
-			['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'],
528
-		];
529
-
530
-		$this->assertEquals([$filesNode2], array_values($this->invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null])));
531
-	}
532
-
533
-	public function testProcessFilterRulesAndConditionWithOneEmptyResult(): void {
534
-		$this->groupManager->expects($this->any())
535
-			->method('isAdmin')
536
-			->willReturn(true);
537
-
538
-		$filesNode1 = $this->createMock(File::class);
539
-		$filesNode1->expects($this->any())
540
-			->method('getSize')
541
-			->willReturn(12);
542
-		$filesNode1->expects($this->any())
543
-			->method('getId')
544
-			->willReturn(111);
545
-		$filesNode2 = $this->createMock(Folder::class);
546
-		$filesNode2->expects($this->any())
547
-			->method('getSize')
548
-			->willReturn(10);
549
-		$filesNode2->expects($this->any())
550
-			->method('getId')
551
-			->willReturn(222);
552
-
553
-		$tag123 = $this->createMock(ISystemTag::class);
554
-		$tag123->expects($this->any())
555
-			->method('getName')
556
-			->willReturn('OneTwoThree');
557
-		$tag123->expects($this->any())
558
-			->method('isUserVisible')
559
-			->willReturn(true);
560
-		$tag456 = $this->createMock(ISystemTag::class);
561
-		$tag456->expects($this->any())
562
-			->method('getName')
563
-			->willReturn('FourFiveSix');
564
-		$tag456->expects($this->any())
565
-			->method('isUserVisible')
566
-			->willReturn(true);
567
-
568
-		$this->tagManager->expects($this->once())
569
-			->method('getTagsByIds')
570
-			->with(['123', '456'])
571
-			->willReturn([$tag123, $tag456]);
572
-
573
-		$this->userFolder->expects($this->exactly(2))
574
-			->method('searchBySystemTag')
575
-			->withConsecutive(
576
-				['OneTwoThree'],
577
-				['FourFiveSix'],
578
-			)
579
-			->willReturnOnConsecutiveCalls(
580
-				[$filesNode1, $filesNode2],
581
-				[],
582
-			);
583
-
584
-		$rules = [
585
-			['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
586
-			['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'],
587
-		];
588
-
589
-		$this->assertEquals([], $this->invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null]));
590
-	}
591
-
592
-	public function testProcessFilterRulesAndConditionWithFirstEmptyResult(): void {
593
-		$this->groupManager->expects($this->any())
594
-			->method('isAdmin')
595
-			->willReturn(true);
596
-
597
-		$filesNode1 = $this->createMock(File::class);
598
-		$filesNode1->expects($this->any())
599
-			->method('getSize')
600
-			->willReturn(12);
601
-		$filesNode1->expects($this->any())
602
-			->method('getId')
603
-			->willReturn(111);
604
-		$filesNode2 = $this->createMock(Folder::class);
605
-		$filesNode2->expects($this->any())
606
-			->method('getSize')
607
-			->willReturn(10);
608
-		$filesNode2->expects($this->any())
609
-			->method('getId')
610
-			->willReturn(222);
611
-
612
-		$tag123 = $this->createMock(ISystemTag::class);
613
-		$tag123->expects($this->any())
614
-			->method('getName')
615
-			->willReturn('OneTwoThree');
616
-		$tag123->expects($this->any())
617
-			->method('isUserVisible')
618
-			->willReturn(true);
619
-		$tag456 = $this->createMock(ISystemTag::class);
620
-		$tag456->expects($this->any())
621
-			->method('getName')
622
-			->willReturn('FourFiveSix');
623
-		$tag456->expects($this->any())
624
-			->method('isUserVisible')
625
-			->willReturn(true);
626
-
627
-		$this->tagManager->expects($this->once())
628
-			->method('getTagsByIds')
629
-			->with(['123', '456'])
630
-			->willReturn([$tag123, $tag456]);
631
-
632
-		$this->userFolder->expects($this->once())
633
-			->method('searchBySystemTag')
634
-			->with('OneTwoThree')
635
-			->willReturnOnConsecutiveCalls(
636
-				[],
637
-				[$filesNode1, $filesNode2],
638
-			);
639
-
640
-		$rules = [
641
-			['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
642
-			['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'],
643
-		];
644
-
645
-		$this->assertEquals([], $this->invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null]));
646
-	}
647
-
648
-	public function testProcessFilterRulesAndConditionWithEmptyMidResult(): void {
649
-		$this->groupManager->expects($this->any())
650
-			->method('isAdmin')
651
-			->willReturn(true);
652
-
653
-		$filesNode1 = $this->createMock(File::class);
654
-		$filesNode1->expects($this->any())
655
-			->method('getSize')
656
-			->willReturn(12);
657
-		$filesNode1->expects($this->any())
658
-			->method('getId')
659
-			->willReturn(111);
660
-		$filesNode2 = $this->createMock(Folder::class);
661
-		$filesNode2->expects($this->any())
662
-			->method('getSize')
663
-			->willReturn(10);
664
-		$filesNode2->expects($this->any())
665
-			->method('getId')
666
-			->willReturn(222);
667
-		$filesNode3 = $this->createMock(Folder::class);
668
-		$filesNode3->expects($this->any())
669
-			->method('getSize')
670
-			->willReturn(13);
671
-		$filesNode3->expects($this->any())
672
-			->method('getId')
673
-			->willReturn(333);
674
-
675
-		$tag123 = $this->createMock(ISystemTag::class);
676
-		$tag123->expects($this->any())
677
-			->method('getName')
678
-			->willReturn('OneTwoThree');
679
-		$tag123->expects($this->any())
680
-			->method('isUserVisible')
681
-			->willReturn(true);
682
-		$tag456 = $this->createMock(ISystemTag::class);
683
-		$tag456->expects($this->any())
684
-			->method('getName')
685
-			->willReturn('FourFiveSix');
686
-		$tag456->expects($this->any())
687
-			->method('isUserVisible')
688
-			->willReturn(true);
689
-		$tag789 = $this->createMock(ISystemTag::class);
690
-		$tag789->expects($this->any())
691
-			->method('getName')
692
-			->willReturn('SevenEightNein');
693
-		$tag789->expects($this->any())
694
-			->method('isUserVisible')
695
-			->willReturn(true);
696
-
697
-		$this->tagManager->expects($this->once())
698
-			->method('getTagsByIds')
699
-			->with(['123', '456', '789'])
700
-			->willReturn([$tag123, $tag456, $tag789]);
701
-
702
-		$this->userFolder->expects($this->exactly(2))
703
-			->method('searchBySystemTag')
704
-			->withConsecutive(['OneTwoThree'], ['FourFiveSix'], ['SevenEightNein'])
705
-			->willReturnOnConsecutiveCalls(
706
-				[$filesNode1, $filesNode2],
707
-				[$filesNode3],
708
-				[$filesNode1, $filesNode2],
709
-			);
710
-
711
-		$rules = [
712
-			['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
713
-			['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'],
714
-			['name' => '{http://owncloud.org/ns}systemtag', 'value' => '789'],
715
-		];
716
-
717
-		$this->assertEquals([], array_values($this->invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null])));
718
-	}
719
-
720
-	public function testProcessFilterRulesInvisibleTagAsAdmin(): void {
721
-		$this->groupManager->expects($this->any())
722
-			->method('isAdmin')
723
-			->willReturn(true);
724
-
725
-		$filesNode1 = $this->createMock(File::class);
726
-		$filesNode1->expects($this->any())
727
-			->method('getSize')
728
-			->willReturn(12);
729
-		$filesNode1->expects($this->any())
730
-			->method('getId')
731
-			->willReturn(111);
732
-		$filesNode2 = $this->createMock(Folder::class);
733
-		$filesNode2->expects($this->any())
734
-			->method('getSize')
735
-			->willReturn(10);
736
-		$filesNode2->expects($this->any())
737
-			->method('getId')
738
-			->willReturn(222);
739
-		$filesNode3 = $this->createMock(Folder::class);
740
-		$filesNode3->expects($this->any())
741
-			->method('getSize')
742
-			->willReturn(13);
743
-		$filesNode3->expects($this->any())
744
-			->method('getId')
745
-			->willReturn(333);
746
-
747
-		$tag123 = $this->createMock(ISystemTag::class);
748
-		$tag123->expects($this->any())
749
-			->method('getName')
750
-			->willReturn('OneTwoThree');
751
-		$tag123->expects($this->any())
752
-			->method('isUserVisible')
753
-			->willReturn(true);
754
-		$tag456 = $this->createMock(ISystemTag::class);
755
-		$tag456->expects($this->any())
756
-			->method('getName')
757
-			->willReturn('FourFiveSix');
758
-		$tag456->expects($this->any())
759
-			->method('isUserVisible')
760
-			->willReturn(false);
761
-
762
-		$this->tagManager->expects($this->once())
763
-			->method('getTagsByIds')
764
-			->with(['123', '456'])
765
-			->willReturn([$tag123, $tag456]);
766
-
767
-		$this->userFolder->expects($this->exactly(2))
768
-			->method('searchBySystemTag')
769
-			->withConsecutive(['OneTwoThree'], ['FourFiveSix'])
770
-			->willReturnOnConsecutiveCalls(
771
-				[$filesNode1, $filesNode2],
772
-				[$filesNode2, $filesNode3],
773
-			);
774
-
775
-		$rules = [
776
-			['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
777
-			['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'],
778
-		];
779
-
780
-		$this->assertEquals([$filesNode2], array_values($this->invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null])));
781
-	}
782
-
783
-
784
-	public function testProcessFilterRulesInvisibleTagAsUser(): void {
785
-		$this->expectException(TagNotFoundException::class);
786
-
787
-		$this->groupManager->expects($this->any())
788
-			->method('isAdmin')
789
-			->willReturn(false);
790
-
791
-		$tag123 = $this->createMock(ISystemTag::class);
792
-		$tag123->expects($this->any())
793
-			->method('getName')
794
-			->willReturn('OneTwoThree');
795
-		$tag123->expects($this->any())
796
-			->method('isUserVisible')
797
-			->willReturn(true);
798
-		$tag456 = $this->createMock(ISystemTag::class);
799
-		$tag456->expects($this->any())
800
-			->method('getName')
801
-			->willReturn('FourFiveSix');
802
-		$tag456->expects($this->any())
803
-			->method('isUserVisible')
804
-			->willReturn(false);
805
-
806
-		$this->tagManager->expects($this->once())
807
-			->method('getTagsByIds')
808
-			->with(['123', '456'])
809
-			->willThrowException(new TagNotFoundException());
810
-
811
-		$this->userFolder->expects($this->never())
812
-			->method('searchBySystemTag');
813
-
814
-		$rules = [
815
-			['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
816
-			['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'],
817
-		];
818
-
819
-		$this->invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null]);
820
-	}
821
-
822
-	public function testProcessFilterRulesVisibleTagAsUser(): void {
823
-		$this->groupManager->expects($this->any())
824
-			->method('isAdmin')
825
-			->willReturn(false);
826
-
827
-		$tag1 = $this->createMock(ISystemTag::class);
828
-		$tag1->expects($this->any())
829
-			->method('getId')
830
-			->willReturn('123');
831
-		$tag1->expects($this->any())
832
-			->method('isUserVisible')
833
-			->willReturn(true);
834
-		$tag1->expects($this->any())
835
-			->method('getName')
836
-			->willReturn('OneTwoThree');
837
-
838
-		$tag2 = $this->createMock(ISystemTag::class);
839
-		$tag2->expects($this->any())
840
-			->method('getId')
841
-			->willReturn('123');
842
-		$tag2->expects($this->any())
843
-			->method('isUserVisible')
844
-			->willReturn(true);
845
-		$tag2->expects($this->any())
846
-			->method('getName')
847
-			->willReturn('FourFiveSix');
848
-
849
-		$this->tagManager->expects($this->once())
850
-			->method('getTagsByIds')
851
-			->with(['123', '456'])
852
-			->willReturn([$tag1, $tag2]);
853
-
854
-		$filesNode1 = $this->createMock(File::class);
855
-		$filesNode1->expects($this->any())
856
-			->method('getId')
857
-			->willReturn(111);
858
-		$filesNode1->expects($this->any())
859
-			->method('getSize')
860
-			->willReturn(12);
861
-		$filesNode2 = $this->createMock(Folder::class);
862
-		$filesNode2->expects($this->any())
863
-			->method('getId')
864
-			->willReturn(222);
865
-		$filesNode2->expects($this->any())
866
-			->method('getSize')
867
-			->willReturn(10);
868
-		$filesNode3 = $this->createMock(Folder::class);
869
-		$filesNode3->expects($this->any())
870
-			->method('getId')
871
-			->willReturn(333);
872
-		$filesNode3->expects($this->any())
873
-			->method('getSize')
874
-			->willReturn(33);
875
-
876
-		$this->tagManager->expects($this->once())
877
-			->method('getTagsByIds')
878
-			->with(['123', '456'])
879
-			->willReturn([$tag1, $tag2]);
880
-
881
-		// main assertion: only user visible tags are being passed through.
882
-		$this->userFolder->expects($this->exactly(2))
883
-			->method('searchBySystemTag')
884
-			->withConsecutive(['OneTwoThree'], ['FourFiveSix'])
885
-			->willReturnOnConsecutiveCalls(
886
-				[$filesNode1, $filesNode2],
887
-				[$filesNode2, $filesNode3],
888
-			);
889
-
890
-		$rules = [
891
-			['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
892
-			['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'],
893
-		];
894
-
895
-		$this->assertEquals([$filesNode2], array_values($this->invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null])));
896
-	}
897
-
898
-	public function testProcessFavoriteFilter(): void {
899
-		$rules = [
900
-			['name' => '{http://owncloud.org/ns}favorite', 'value' => '1'],
901
-		];
902
-
903
-		$this->privateTags->expects($this->once())
904
-			->method('getFavorites')
905
-			->willReturn(['456', '789']);
906
-
907
-		$this->assertEquals(['456', '789'], array_values($this->invokePrivate($this->plugin, 'processFilterRulesForFileIDs', [$rules])));
908
-	}
909
-
910
-	public function filesBaseUriProvider() {
911
-		return [
912
-			['', '', ''],
913
-			['files/username', '', '/files/username'],
914
-			['files/username/test', '/test', '/files/username'],
915
-			['files/username/test/sub', '/test/sub', '/files/username'],
916
-			['test', '/test', ''],
917
-		];
918
-	}
919
-
920
-	/**
921
-	 * @dataProvider filesBaseUriProvider
922
-	 */
923
-	public function testFilesBaseUri($uri, $reportPath, $expectedUri): void {
924
-		$this->assertEquals($expectedUri, $this->invokePrivate($this->plugin, 'getFilesBaseUri', [$uri, $reportPath]));
925
-	}
39
+    private \Sabre\DAV\Server&MockObject $server;
40
+    private Tree&MockObject $tree;
41
+    private ISystemTagObjectMapper&MockObject $tagMapper;
42
+    private ISystemTagManager&MockObject $tagManager;
43
+    private ITags&MockObject $privateTags;
44
+    private ITagManager&MockObject $privateTagManager;
45
+    private IUserSession&MockObject $userSession;
46
+    private FilesReportPluginImplementation $plugin;
47
+    private View&MockObject $view;
48
+    private IGroupManager&MockObject $groupManager;
49
+    private Folder&MockObject $userFolder;
50
+    private IPreview&MockObject $previewManager;
51
+    private IAppManager&MockObject $appManager;
52
+
53
+    protected function setUp(): void {
54
+        parent::setUp();
55
+        $this->tree = $this->getMockBuilder(Tree::class)
56
+            ->disableOriginalConstructor()
57
+            ->getMock();
58
+
59
+        $this->view = $this->getMockBuilder(View::class)
60
+            ->disableOriginalConstructor()
61
+            ->getMock();
62
+
63
+        $this->server = $this->getMockBuilder('\Sabre\DAV\Server')
64
+            ->setConstructorArgs([$this->tree])
65
+            ->onlyMethods(['getRequestUri', 'getBaseUri'])
66
+            ->getMock();
67
+
68
+        $this->server->expects($this->any())
69
+            ->method('getBaseUri')
70
+            ->willReturn('http://example.com/owncloud/remote.php/dav');
71
+
72
+        $this->groupManager = $this->getMockBuilder(IGroupManager::class)
73
+            ->disableOriginalConstructor()
74
+            ->getMock();
75
+
76
+        $this->userFolder = $this->getMockBuilder(Folder::class)
77
+            ->disableOriginalConstructor()
78
+            ->getMock();
79
+
80
+        $this->previewManager = $this->getMockBuilder(IPreview::class)
81
+            ->disableOriginalConstructor()
82
+            ->getMock();
83
+
84
+        $this->appManager = $this->getMockBuilder(IAppManager::class)
85
+            ->disableOriginalConstructor()
86
+            ->getMock();
87
+
88
+        $this->tagManager = $this->createMock(ISystemTagManager::class);
89
+        $this->tagMapper = $this->createMock(ISystemTagObjectMapper::class);
90
+        $this->userSession = $this->createMock(IUserSession::class);
91
+        $this->privateTags = $this->createMock(ITags::class);
92
+        $this->privateTagManager = $this->createMock(ITagManager::class);
93
+        $this->privateTagManager->expects($this->any())
94
+            ->method('load')
95
+            ->with('files')
96
+            ->willReturn($this->privateTags);
97
+
98
+        $user = $this->getMockBuilder(IUser::class)
99
+            ->disableOriginalConstructor()
100
+            ->getMock();
101
+        $user->expects($this->any())
102
+            ->method('getUID')
103
+            ->willReturn('testuser');
104
+        $this->userSession->expects($this->any())
105
+            ->method('getUser')
106
+            ->willReturn($user);
107
+
108
+        $this->plugin = new FilesReportPluginImplementation(
109
+            $this->tree,
110
+            $this->view,
111
+            $this->tagManager,
112
+            $this->tagMapper,
113
+            $this->privateTagManager,
114
+            $this->userSession,
115
+            $this->groupManager,
116
+            $this->userFolder,
117
+            $this->appManager
118
+        );
119
+    }
120
+
121
+    public function testOnReportInvalidNode(): void {
122
+        $path = 'totally/unrelated/13';
123
+
124
+        $this->tree->expects($this->any())
125
+            ->method('getNodeForPath')
126
+            ->with('/' . $path)
127
+            ->willReturn(
128
+                $this->getMockBuilder(INode::class)
129
+                    ->disableOriginalConstructor()
130
+                    ->getMock()
131
+            );
132
+
133
+        $this->server->expects($this->any())
134
+            ->method('getRequestUri')
135
+            ->willReturn($path);
136
+        $this->plugin->initialize($this->server);
137
+
138
+        $this->assertNull($this->plugin->onReport(FilesReportPluginImplementation::REPORT_NAME, [], '/' . $path));
139
+    }
140
+
141
+    public function testOnReportInvalidReportName(): void {
142
+        $path = 'test';
143
+
144
+        $this->tree->expects($this->any())
145
+            ->method('getNodeForPath')
146
+            ->with('/' . $path)
147
+            ->willReturn(
148
+                $this->getMockBuilder(INode::class)
149
+                    ->disableOriginalConstructor()
150
+                    ->getMock()
151
+            );
152
+
153
+        $this->server->expects($this->any())
154
+            ->method('getRequestUri')
155
+            ->willReturn($path);
156
+        $this->plugin->initialize($this->server);
157
+
158
+        $this->assertNull($this->plugin->onReport('{whoever}whatever', [], '/' . $path));
159
+    }
160
+
161
+    public function testOnReport(): void {
162
+        $path = 'test';
163
+
164
+        $parameters = [
165
+            [
166
+                'name' => '{DAV:}prop',
167
+                'value' => [
168
+                    ['name' => '{DAV:}getcontentlength', 'value' => ''],
169
+                    ['name' => '{http://owncloud.org/ns}size', 'value' => ''],
170
+                ],
171
+            ],
172
+            [
173
+                'name' => '{http://owncloud.org/ns}filter-rules',
174
+                'value' => [
175
+                    ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
176
+                    ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'],
177
+                ],
178
+            ],
179
+        ];
180
+
181
+        $this->groupManager->expects($this->any())
182
+            ->method('isAdmin')
183
+            ->willReturn(true);
184
+
185
+        $reportTargetNode = $this->getMockBuilder(Directory::class)
186
+            ->disableOriginalConstructor()
187
+            ->getMock();
188
+        $reportTargetNode->expects($this->any())
189
+            ->method('getPath')
190
+            ->willReturn('');
191
+
192
+        $response = $this->getMockBuilder(ResponseInterface::class)
193
+            ->disableOriginalConstructor()
194
+            ->getMock();
195
+
196
+        $response->expects($this->once())
197
+            ->method('setHeader')
198
+            ->with('Content-Type', 'application/xml; charset=utf-8');
199
+
200
+        $response->expects($this->once())
201
+            ->method('setStatus')
202
+            ->with(207);
203
+
204
+        $response->expects($this->once())
205
+            ->method('setBody');
206
+
207
+        $this->tree->expects($this->any())
208
+            ->method('getNodeForPath')
209
+            ->with('/' . $path)
210
+            ->willReturn($reportTargetNode);
211
+
212
+        $filesNode1 = $this->createMock(File::class);
213
+        $filesNode1->expects($this->any())
214
+            ->method('getSize')
215
+            ->willReturn(12);
216
+        $filesNode2 = $this->createMock(Folder::class);
217
+        $filesNode2->expects($this->any())
218
+            ->method('getSize')
219
+            ->willReturn(10);
220
+
221
+        $tag123 = $this->createMock(ISystemTag::class);
222
+        $tag123->expects($this->any())
223
+            ->method('getName')
224
+            ->willReturn('OneTwoThree');
225
+        $tag123->expects($this->any())
226
+            ->method('isUserVisible')
227
+            ->willReturn(true);
228
+        $tag456 = $this->createMock(ISystemTag::class);
229
+        $tag456->expects($this->any())
230
+            ->method('getName')
231
+            ->willReturn('FourFiveSix');
232
+        $tag456->expects($this->any())
233
+            ->method('isUserVisible')
234
+            ->willReturn(true);
235
+
236
+        $this->tagManager->expects($this->once())
237
+            ->method('getTagsByIds')
238
+            ->with(['123', '456'])
239
+            ->willReturn([$tag123, $tag456]);
240
+
241
+        $this->userFolder->expects($this->exactly(2))
242
+            ->method('searchBySystemTag')
243
+            ->withConsecutive(
244
+                ['OneTwoThree'],
245
+                ['FourFiveSix'],
246
+            )
247
+            ->willReturnOnConsecutiveCalls(
248
+                [$filesNode1],
249
+                [$filesNode2],
250
+            );
251
+
252
+        $this->server->expects($this->any())
253
+            ->method('getRequestUri')
254
+            ->willReturn($path);
255
+        $this->server->httpResponse = $response;
256
+        $this->plugin->initialize($this->server);
257
+
258
+        $this->assertFalse($this->plugin->onReport(FilesReportPluginImplementation::REPORT_NAME, $parameters, '/' . $path));
259
+    }
260
+
261
+    public function testFindNodesByFileIdsRoot(): void {
262
+        $filesNode1 = $this->getMockBuilder(Folder::class)
263
+            ->disableOriginalConstructor()
264
+            ->getMock();
265
+        $filesNode1->expects($this->once())
266
+            ->method('getName')
267
+            ->willReturn('first node');
268
+
269
+        $filesNode2 = $this->getMockBuilder(File::class)
270
+            ->disableOriginalConstructor()
271
+            ->getMock();
272
+        $filesNode2->expects($this->once())
273
+            ->method('getName')
274
+            ->willReturn('second node');
275
+
276
+        $reportTargetNode = $this->getMockBuilder(Directory::class)
277
+            ->disableOriginalConstructor()
278
+            ->getMock();
279
+        $reportTargetNode->expects($this->any())
280
+            ->method('getPath')
281
+            ->willReturn('/');
282
+
283
+        $this->userFolder->expects($this->exactly(2))
284
+            ->method('getFirstNodeById')
285
+            ->withConsecutive(
286
+                ['111'],
287
+                ['222'],
288
+            )
289
+            ->willReturnOnConsecutiveCalls(
290
+                $filesNode1,
291
+                $filesNode2,
292
+            );
293
+
294
+        /** @var Directory&MockObject $reportTargetNode */
295
+        $result = $this->plugin->findNodesByFileIds($reportTargetNode, ['111', '222']);
296
+
297
+        $this->assertCount(2, $result);
298
+        $this->assertInstanceOf('\OCA\DAV\Connector\Sabre\Directory', $result[0]);
299
+        $this->assertEquals('first node', $result[0]->getName());
300
+        $this->assertInstanceOf('\OCA\DAV\Connector\Sabre\File', $result[1]);
301
+        $this->assertEquals('second node', $result[1]->getName());
302
+    }
303
+
304
+    public function testFindNodesByFileIdsSubDir(): void {
305
+        $filesNode1 = $this->getMockBuilder(Folder::class)
306
+            ->disableOriginalConstructor()
307
+            ->getMock();
308
+        $filesNode1->expects($this->once())
309
+            ->method('getName')
310
+            ->willReturn('first node');
311
+
312
+        $filesNode2 = $this->getMockBuilder(File::class)
313
+            ->disableOriginalConstructor()
314
+            ->getMock();
315
+        $filesNode2->expects($this->once())
316
+            ->method('getName')
317
+            ->willReturn('second node');
318
+
319
+        $reportTargetNode = $this->getMockBuilder(Directory::class)
320
+            ->disableOriginalConstructor()
321
+            ->getMock();
322
+        $reportTargetNode->expects($this->any())
323
+            ->method('getPath')
324
+            ->willReturn('/sub1/sub2');
325
+
326
+
327
+        $subNode = $this->getMockBuilder(Folder::class)
328
+            ->disableOriginalConstructor()
329
+            ->getMock();
330
+
331
+        $this->userFolder->expects($this->once())
332
+            ->method('get')
333
+            ->with('/sub1/sub2')
334
+            ->willReturn($subNode);
335
+
336
+        $subNode->expects($this->exactly(2))
337
+            ->method('getFirstNodeById')
338
+            ->withConsecutive(
339
+                ['111'],
340
+                ['222'],
341
+            )
342
+            ->willReturnOnConsecutiveCalls(
343
+                $filesNode1,
344
+                $filesNode2,
345
+            );
346
+
347
+        /** @var Directory&MockObject $reportTargetNode */
348
+        $result = $this->plugin->findNodesByFileIds($reportTargetNode, ['111', '222']);
349
+
350
+        $this->assertCount(2, $result);
351
+        $this->assertInstanceOf('\OCA\DAV\Connector\Sabre\Directory', $result[0]);
352
+        $this->assertEquals('first node', $result[0]->getName());
353
+        $this->assertInstanceOf('\OCA\DAV\Connector\Sabre\File', $result[1]);
354
+        $this->assertEquals('second node', $result[1]->getName());
355
+    }
356
+
357
+    public function testPrepareResponses(): void {
358
+        $requestedProps = ['{DAV:}getcontentlength', '{http://owncloud.org/ns}fileid', '{DAV:}resourcetype'];
359
+
360
+        $fileInfo = $this->createMock(FileInfo::class);
361
+        $fileInfo->method('isReadable')->willReturn(true);
362
+
363
+        $node1 = $this->getMockBuilder(Directory::class)
364
+            ->disableOriginalConstructor()
365
+            ->getMock();
366
+        $node2 = $this->getMockBuilder(\OCA\DAV\Connector\Sabre\File::class)
367
+            ->disableOriginalConstructor()
368
+            ->getMock();
369
+
370
+        $node1->expects($this->once())
371
+            ->method('getInternalFileId')
372
+            ->willReturn('111');
373
+        $node1->expects($this->any())
374
+            ->method('getPath')
375
+            ->willReturn('/node1');
376
+        $node1->method('getFileInfo')->willReturn($fileInfo);
377
+        $node2->expects($this->once())
378
+            ->method('getInternalFileId')
379
+            ->willReturn('222');
380
+        $node2->expects($this->once())
381
+            ->method('getSize')
382
+            ->willReturn(1024);
383
+        $node2->expects($this->any())
384
+            ->method('getPath')
385
+            ->willReturn('/sub/node2');
386
+        $node2->method('getFileInfo')->willReturn($fileInfo);
387
+
388
+        $config = $this->getMockBuilder(IConfig::class)
389
+            ->disableOriginalConstructor()
390
+            ->getMock();
391
+
392
+        $validator = $this->createMock(IFilenameValidator::class);
393
+        $accountManager = $this->createMock(IAccountManager::class);
394
+
395
+        $this->server->addPlugin(
396
+            new FilesPlugin(
397
+                $this->tree,
398
+                $config,
399
+                $this->createMock(IRequest::class),
400
+                $this->previewManager,
401
+                $this->createMock(IUserSession::class),
402
+                $validator,
403
+                $accountManager,
404
+            )
405
+        );
406
+        $this->plugin->initialize($this->server);
407
+        $responses = $this->plugin->prepareResponses('/files/username', $requestedProps, [$node1, $node2]);
408
+
409
+        $this->assertCount(2, $responses);
410
+
411
+        $this->assertEquals('http://example.com/owncloud/remote.php/dav/files/username/node1', $responses[0]->getHref());
412
+        $this->assertEquals('http://example.com/owncloud/remote.php/dav/files/username/sub/node2', $responses[1]->getHref());
413
+
414
+        $props1 = $responses[0]->getResponseProperties();
415
+        $this->assertEquals('111', $props1[200]['{http://owncloud.org/ns}fileid']);
416
+        $this->assertNull($props1[404]['{DAV:}getcontentlength']);
417
+        $this->assertInstanceOf('\Sabre\DAV\Xml\Property\ResourceType', $props1[200]['{DAV:}resourcetype']);
418
+        $resourceType1 = $props1[200]['{DAV:}resourcetype']->getValue();
419
+        $this->assertEquals('{DAV:}collection', $resourceType1[0]);
420
+
421
+        $props2 = $responses[1]->getResponseProperties();
422
+        $this->assertEquals('1024', $props2[200]['{DAV:}getcontentlength']);
423
+        $this->assertEquals('222', $props2[200]['{http://owncloud.org/ns}fileid']);
424
+        $this->assertInstanceOf('\Sabre\DAV\Xml\Property\ResourceType', $props2[200]['{DAV:}resourcetype']);
425
+        $this->assertCount(0, $props2[200]['{DAV:}resourcetype']->getValue());
426
+    }
427
+
428
+    public function testProcessFilterRulesSingle(): void {
429
+        $this->groupManager->expects($this->any())
430
+            ->method('isAdmin')
431
+            ->willReturn(true);
432
+
433
+        $rules = [
434
+            ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
435
+        ];
436
+
437
+        $filesNode1 = $this->createMock(File::class);
438
+        $filesNode1->expects($this->any())
439
+            ->method('getSize')
440
+            ->willReturn(12);
441
+        $filesNode2 = $this->createMock(Folder::class);
442
+        $filesNode2->expects($this->any())
443
+            ->method('getSize')
444
+            ->willReturn(10);
445
+
446
+        $tag123 = $this->createMock(ISystemTag::class);
447
+        $tag123->expects($this->any())
448
+            ->method('getName')
449
+            ->willReturn('OneTwoThree');
450
+        $tag123->expects($this->any())
451
+            ->method('isUserVisible')
452
+            ->willReturn(true);
453
+
454
+        $this->tagManager->expects($this->once())
455
+            ->method('getTagsByIds')
456
+            ->with(['123'])
457
+            ->willReturn([$tag123]);
458
+
459
+        $this->userFolder->expects($this->once())
460
+            ->method('searchBySystemTag')
461
+            ->with('OneTwoThree')
462
+            ->willReturn([$filesNode1, $filesNode2]);
463
+
464
+        $this->assertEquals([$filesNode1, $filesNode2], $this->invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, 0, 0]));
465
+    }
466
+
467
+    public function testProcessFilterRulesAndCondition(): void {
468
+        $this->groupManager->expects($this->any())
469
+            ->method('isAdmin')
470
+            ->willReturn(true);
471
+
472
+        $filesNode1 = $this->createMock(File::class);
473
+        $filesNode1->expects($this->any())
474
+            ->method('getSize')
475
+            ->willReturn(12);
476
+        $filesNode1->expects($this->any())
477
+            ->method('getId')
478
+            ->willReturn(111);
479
+        $filesNode2 = $this->createMock(Folder::class);
480
+        $filesNode2->expects($this->any())
481
+            ->method('getSize')
482
+            ->willReturn(10);
483
+        $filesNode2->expects($this->any())
484
+            ->method('getId')
485
+            ->willReturn(222);
486
+        $filesNode3 = $this->createMock(File::class);
487
+        $filesNode3->expects($this->any())
488
+            ->method('getSize')
489
+            ->willReturn(14);
490
+        $filesNode3->expects($this->any())
491
+            ->method('getId')
492
+            ->willReturn(333);
493
+
494
+        $tag123 = $this->createMock(ISystemTag::class);
495
+        $tag123->expects($this->any())
496
+            ->method('getName')
497
+            ->willReturn('OneTwoThree');
498
+        $tag123->expects($this->any())
499
+            ->method('isUserVisible')
500
+            ->willReturn(true);
501
+        $tag456 = $this->createMock(ISystemTag::class);
502
+        $tag456->expects($this->any())
503
+            ->method('getName')
504
+            ->willReturn('FourFiveSix');
505
+        $tag456->expects($this->any())
506
+            ->method('isUserVisible')
507
+            ->willReturn(true);
508
+
509
+        $this->tagManager->expects($this->once())
510
+            ->method('getTagsByIds')
511
+            ->with(['123', '456'])
512
+            ->willReturn([$tag123, $tag456]);
513
+
514
+        $this->userFolder->expects($this->exactly(2))
515
+            ->method('searchBySystemTag')
516
+            ->withConsecutive(
517
+                ['OneTwoThree'],
518
+                ['FourFiveSix'],
519
+            )
520
+            ->willReturnOnConsecutiveCalls(
521
+                [$filesNode1, $filesNode2],
522
+                [$filesNode2, $filesNode3],
523
+            );
524
+
525
+        $rules = [
526
+            ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
527
+            ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'],
528
+        ];
529
+
530
+        $this->assertEquals([$filesNode2], array_values($this->invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null])));
531
+    }
532
+
533
+    public function testProcessFilterRulesAndConditionWithOneEmptyResult(): void {
534
+        $this->groupManager->expects($this->any())
535
+            ->method('isAdmin')
536
+            ->willReturn(true);
537
+
538
+        $filesNode1 = $this->createMock(File::class);
539
+        $filesNode1->expects($this->any())
540
+            ->method('getSize')
541
+            ->willReturn(12);
542
+        $filesNode1->expects($this->any())
543
+            ->method('getId')
544
+            ->willReturn(111);
545
+        $filesNode2 = $this->createMock(Folder::class);
546
+        $filesNode2->expects($this->any())
547
+            ->method('getSize')
548
+            ->willReturn(10);
549
+        $filesNode2->expects($this->any())
550
+            ->method('getId')
551
+            ->willReturn(222);
552
+
553
+        $tag123 = $this->createMock(ISystemTag::class);
554
+        $tag123->expects($this->any())
555
+            ->method('getName')
556
+            ->willReturn('OneTwoThree');
557
+        $tag123->expects($this->any())
558
+            ->method('isUserVisible')
559
+            ->willReturn(true);
560
+        $tag456 = $this->createMock(ISystemTag::class);
561
+        $tag456->expects($this->any())
562
+            ->method('getName')
563
+            ->willReturn('FourFiveSix');
564
+        $tag456->expects($this->any())
565
+            ->method('isUserVisible')
566
+            ->willReturn(true);
567
+
568
+        $this->tagManager->expects($this->once())
569
+            ->method('getTagsByIds')
570
+            ->with(['123', '456'])
571
+            ->willReturn([$tag123, $tag456]);
572
+
573
+        $this->userFolder->expects($this->exactly(2))
574
+            ->method('searchBySystemTag')
575
+            ->withConsecutive(
576
+                ['OneTwoThree'],
577
+                ['FourFiveSix'],
578
+            )
579
+            ->willReturnOnConsecutiveCalls(
580
+                [$filesNode1, $filesNode2],
581
+                [],
582
+            );
583
+
584
+        $rules = [
585
+            ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
586
+            ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'],
587
+        ];
588
+
589
+        $this->assertEquals([], $this->invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null]));
590
+    }
591
+
592
+    public function testProcessFilterRulesAndConditionWithFirstEmptyResult(): void {
593
+        $this->groupManager->expects($this->any())
594
+            ->method('isAdmin')
595
+            ->willReturn(true);
596
+
597
+        $filesNode1 = $this->createMock(File::class);
598
+        $filesNode1->expects($this->any())
599
+            ->method('getSize')
600
+            ->willReturn(12);
601
+        $filesNode1->expects($this->any())
602
+            ->method('getId')
603
+            ->willReturn(111);
604
+        $filesNode2 = $this->createMock(Folder::class);
605
+        $filesNode2->expects($this->any())
606
+            ->method('getSize')
607
+            ->willReturn(10);
608
+        $filesNode2->expects($this->any())
609
+            ->method('getId')
610
+            ->willReturn(222);
611
+
612
+        $tag123 = $this->createMock(ISystemTag::class);
613
+        $tag123->expects($this->any())
614
+            ->method('getName')
615
+            ->willReturn('OneTwoThree');
616
+        $tag123->expects($this->any())
617
+            ->method('isUserVisible')
618
+            ->willReturn(true);
619
+        $tag456 = $this->createMock(ISystemTag::class);
620
+        $tag456->expects($this->any())
621
+            ->method('getName')
622
+            ->willReturn('FourFiveSix');
623
+        $tag456->expects($this->any())
624
+            ->method('isUserVisible')
625
+            ->willReturn(true);
626
+
627
+        $this->tagManager->expects($this->once())
628
+            ->method('getTagsByIds')
629
+            ->with(['123', '456'])
630
+            ->willReturn([$tag123, $tag456]);
631
+
632
+        $this->userFolder->expects($this->once())
633
+            ->method('searchBySystemTag')
634
+            ->with('OneTwoThree')
635
+            ->willReturnOnConsecutiveCalls(
636
+                [],
637
+                [$filesNode1, $filesNode2],
638
+            );
639
+
640
+        $rules = [
641
+            ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
642
+            ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'],
643
+        ];
644
+
645
+        $this->assertEquals([], $this->invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null]));
646
+    }
647
+
648
+    public function testProcessFilterRulesAndConditionWithEmptyMidResult(): void {
649
+        $this->groupManager->expects($this->any())
650
+            ->method('isAdmin')
651
+            ->willReturn(true);
652
+
653
+        $filesNode1 = $this->createMock(File::class);
654
+        $filesNode1->expects($this->any())
655
+            ->method('getSize')
656
+            ->willReturn(12);
657
+        $filesNode1->expects($this->any())
658
+            ->method('getId')
659
+            ->willReturn(111);
660
+        $filesNode2 = $this->createMock(Folder::class);
661
+        $filesNode2->expects($this->any())
662
+            ->method('getSize')
663
+            ->willReturn(10);
664
+        $filesNode2->expects($this->any())
665
+            ->method('getId')
666
+            ->willReturn(222);
667
+        $filesNode3 = $this->createMock(Folder::class);
668
+        $filesNode3->expects($this->any())
669
+            ->method('getSize')
670
+            ->willReturn(13);
671
+        $filesNode3->expects($this->any())
672
+            ->method('getId')
673
+            ->willReturn(333);
674
+
675
+        $tag123 = $this->createMock(ISystemTag::class);
676
+        $tag123->expects($this->any())
677
+            ->method('getName')
678
+            ->willReturn('OneTwoThree');
679
+        $tag123->expects($this->any())
680
+            ->method('isUserVisible')
681
+            ->willReturn(true);
682
+        $tag456 = $this->createMock(ISystemTag::class);
683
+        $tag456->expects($this->any())
684
+            ->method('getName')
685
+            ->willReturn('FourFiveSix');
686
+        $tag456->expects($this->any())
687
+            ->method('isUserVisible')
688
+            ->willReturn(true);
689
+        $tag789 = $this->createMock(ISystemTag::class);
690
+        $tag789->expects($this->any())
691
+            ->method('getName')
692
+            ->willReturn('SevenEightNein');
693
+        $tag789->expects($this->any())
694
+            ->method('isUserVisible')
695
+            ->willReturn(true);
696
+
697
+        $this->tagManager->expects($this->once())
698
+            ->method('getTagsByIds')
699
+            ->with(['123', '456', '789'])
700
+            ->willReturn([$tag123, $tag456, $tag789]);
701
+
702
+        $this->userFolder->expects($this->exactly(2))
703
+            ->method('searchBySystemTag')
704
+            ->withConsecutive(['OneTwoThree'], ['FourFiveSix'], ['SevenEightNein'])
705
+            ->willReturnOnConsecutiveCalls(
706
+                [$filesNode1, $filesNode2],
707
+                [$filesNode3],
708
+                [$filesNode1, $filesNode2],
709
+            );
710
+
711
+        $rules = [
712
+            ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
713
+            ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'],
714
+            ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '789'],
715
+        ];
716
+
717
+        $this->assertEquals([], array_values($this->invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null])));
718
+    }
719
+
720
+    public function testProcessFilterRulesInvisibleTagAsAdmin(): void {
721
+        $this->groupManager->expects($this->any())
722
+            ->method('isAdmin')
723
+            ->willReturn(true);
724
+
725
+        $filesNode1 = $this->createMock(File::class);
726
+        $filesNode1->expects($this->any())
727
+            ->method('getSize')
728
+            ->willReturn(12);
729
+        $filesNode1->expects($this->any())
730
+            ->method('getId')
731
+            ->willReturn(111);
732
+        $filesNode2 = $this->createMock(Folder::class);
733
+        $filesNode2->expects($this->any())
734
+            ->method('getSize')
735
+            ->willReturn(10);
736
+        $filesNode2->expects($this->any())
737
+            ->method('getId')
738
+            ->willReturn(222);
739
+        $filesNode3 = $this->createMock(Folder::class);
740
+        $filesNode3->expects($this->any())
741
+            ->method('getSize')
742
+            ->willReturn(13);
743
+        $filesNode3->expects($this->any())
744
+            ->method('getId')
745
+            ->willReturn(333);
746
+
747
+        $tag123 = $this->createMock(ISystemTag::class);
748
+        $tag123->expects($this->any())
749
+            ->method('getName')
750
+            ->willReturn('OneTwoThree');
751
+        $tag123->expects($this->any())
752
+            ->method('isUserVisible')
753
+            ->willReturn(true);
754
+        $tag456 = $this->createMock(ISystemTag::class);
755
+        $tag456->expects($this->any())
756
+            ->method('getName')
757
+            ->willReturn('FourFiveSix');
758
+        $tag456->expects($this->any())
759
+            ->method('isUserVisible')
760
+            ->willReturn(false);
761
+
762
+        $this->tagManager->expects($this->once())
763
+            ->method('getTagsByIds')
764
+            ->with(['123', '456'])
765
+            ->willReturn([$tag123, $tag456]);
766
+
767
+        $this->userFolder->expects($this->exactly(2))
768
+            ->method('searchBySystemTag')
769
+            ->withConsecutive(['OneTwoThree'], ['FourFiveSix'])
770
+            ->willReturnOnConsecutiveCalls(
771
+                [$filesNode1, $filesNode2],
772
+                [$filesNode2, $filesNode3],
773
+            );
774
+
775
+        $rules = [
776
+            ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
777
+            ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'],
778
+        ];
779
+
780
+        $this->assertEquals([$filesNode2], array_values($this->invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null])));
781
+    }
782
+
783
+
784
+    public function testProcessFilterRulesInvisibleTagAsUser(): void {
785
+        $this->expectException(TagNotFoundException::class);
786
+
787
+        $this->groupManager->expects($this->any())
788
+            ->method('isAdmin')
789
+            ->willReturn(false);
790
+
791
+        $tag123 = $this->createMock(ISystemTag::class);
792
+        $tag123->expects($this->any())
793
+            ->method('getName')
794
+            ->willReturn('OneTwoThree');
795
+        $tag123->expects($this->any())
796
+            ->method('isUserVisible')
797
+            ->willReturn(true);
798
+        $tag456 = $this->createMock(ISystemTag::class);
799
+        $tag456->expects($this->any())
800
+            ->method('getName')
801
+            ->willReturn('FourFiveSix');
802
+        $tag456->expects($this->any())
803
+            ->method('isUserVisible')
804
+            ->willReturn(false);
805
+
806
+        $this->tagManager->expects($this->once())
807
+            ->method('getTagsByIds')
808
+            ->with(['123', '456'])
809
+            ->willThrowException(new TagNotFoundException());
810
+
811
+        $this->userFolder->expects($this->never())
812
+            ->method('searchBySystemTag');
813
+
814
+        $rules = [
815
+            ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
816
+            ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'],
817
+        ];
818
+
819
+        $this->invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null]);
820
+    }
821
+
822
+    public function testProcessFilterRulesVisibleTagAsUser(): void {
823
+        $this->groupManager->expects($this->any())
824
+            ->method('isAdmin')
825
+            ->willReturn(false);
826
+
827
+        $tag1 = $this->createMock(ISystemTag::class);
828
+        $tag1->expects($this->any())
829
+            ->method('getId')
830
+            ->willReturn('123');
831
+        $tag1->expects($this->any())
832
+            ->method('isUserVisible')
833
+            ->willReturn(true);
834
+        $tag1->expects($this->any())
835
+            ->method('getName')
836
+            ->willReturn('OneTwoThree');
837
+
838
+        $tag2 = $this->createMock(ISystemTag::class);
839
+        $tag2->expects($this->any())
840
+            ->method('getId')
841
+            ->willReturn('123');
842
+        $tag2->expects($this->any())
843
+            ->method('isUserVisible')
844
+            ->willReturn(true);
845
+        $tag2->expects($this->any())
846
+            ->method('getName')
847
+            ->willReturn('FourFiveSix');
848
+
849
+        $this->tagManager->expects($this->once())
850
+            ->method('getTagsByIds')
851
+            ->with(['123', '456'])
852
+            ->willReturn([$tag1, $tag2]);
853
+
854
+        $filesNode1 = $this->createMock(File::class);
855
+        $filesNode1->expects($this->any())
856
+            ->method('getId')
857
+            ->willReturn(111);
858
+        $filesNode1->expects($this->any())
859
+            ->method('getSize')
860
+            ->willReturn(12);
861
+        $filesNode2 = $this->createMock(Folder::class);
862
+        $filesNode2->expects($this->any())
863
+            ->method('getId')
864
+            ->willReturn(222);
865
+        $filesNode2->expects($this->any())
866
+            ->method('getSize')
867
+            ->willReturn(10);
868
+        $filesNode3 = $this->createMock(Folder::class);
869
+        $filesNode3->expects($this->any())
870
+            ->method('getId')
871
+            ->willReturn(333);
872
+        $filesNode3->expects($this->any())
873
+            ->method('getSize')
874
+            ->willReturn(33);
875
+
876
+        $this->tagManager->expects($this->once())
877
+            ->method('getTagsByIds')
878
+            ->with(['123', '456'])
879
+            ->willReturn([$tag1, $tag2]);
880
+
881
+        // main assertion: only user visible tags are being passed through.
882
+        $this->userFolder->expects($this->exactly(2))
883
+            ->method('searchBySystemTag')
884
+            ->withConsecutive(['OneTwoThree'], ['FourFiveSix'])
885
+            ->willReturnOnConsecutiveCalls(
886
+                [$filesNode1, $filesNode2],
887
+                [$filesNode2, $filesNode3],
888
+            );
889
+
890
+        $rules = [
891
+            ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
892
+            ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'],
893
+        ];
894
+
895
+        $this->assertEquals([$filesNode2], array_values($this->invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null])));
896
+    }
897
+
898
+    public function testProcessFavoriteFilter(): void {
899
+        $rules = [
900
+            ['name' => '{http://owncloud.org/ns}favorite', 'value' => '1'],
901
+        ];
902
+
903
+        $this->privateTags->expects($this->once())
904
+            ->method('getFavorites')
905
+            ->willReturn(['456', '789']);
906
+
907
+        $this->assertEquals(['456', '789'], array_values($this->invokePrivate($this->plugin, 'processFilterRulesForFileIDs', [$rules])));
908
+    }
909
+
910
+    public function filesBaseUriProvider() {
911
+        return [
912
+            ['', '', ''],
913
+            ['files/username', '', '/files/username'],
914
+            ['files/username/test', '/test', '/files/username'],
915
+            ['files/username/test/sub', '/test/sub', '/files/username'],
916
+            ['test', '/test', ''],
917
+        ];
918
+    }
919
+
920
+    /**
921
+     * @dataProvider filesBaseUriProvider
922
+     */
923
+    public function testFilesBaseUri($uri, $reportPath, $expectedUri): void {
924
+        $this->assertEquals($expectedUri, $this->invokePrivate($this->plugin, 'getFilesBaseUri', [$uri, $reportPath]));
925
+    }
926 926
 }
Please login to merge, or discard this patch.
apps/dav/lib/Server.php 1 patch
Indentation   +327 added lines, -327 removed lines patch added patch discarded remove patch
@@ -100,332 +100,332 @@
 block discarded – undo
100 100
 use SearchDAV\DAV\SearchPlugin;
101 101
 
102 102
 class Server {
103
-	public Connector\Sabre\Server $server;
104
-	private IProfiler $profiler;
105
-
106
-	public function __construct(
107
-		private IRequest $request,
108
-		private string $baseUri,
109
-	) {
110
-		$this->profiler = \OCP\Server::get(IProfiler::class);
111
-		if ($this->profiler->isEnabled()) {
112
-			/** @var IEventLogger $eventLogger */
113
-			$eventLogger = \OCP\Server::get(IEventLogger::class);
114
-			$eventLogger->start('runtime', 'DAV Runtime');
115
-		}
116
-
117
-		$logger = \OCP\Server::get(LoggerInterface::class);
118
-		$eventDispatcher = \OCP\Server::get(IEventDispatcher::class);
119
-
120
-		$root = new RootCollection();
121
-		$this->server = new \OCA\DAV\Connector\Sabre\Server(new CachingTree($root));
122
-
123
-		// Add maintenance plugin
124
-		$this->server->addPlugin(new MaintenancePlugin(\OCP\Server::get(IConfig::class), \OC::$server->getL10N('dav')));
125
-
126
-		$this->server->addPlugin(new AppleQuirksPlugin());
127
-
128
-		// Backends
129
-		$authBackend = new Auth(
130
-			\OCP\Server::get(ISession::class),
131
-			\OCP\Server::get(IUserSession::class),
132
-			\OCP\Server::get(IRequest::class),
133
-			\OCP\Server::get(\OC\Authentication\TwoFactorAuth\Manager::class),
134
-			\OCP\Server::get(IThrottler::class)
135
-		);
136
-
137
-		// Set URL explicitly due to reverse-proxy situations
138
-		$this->server->httpRequest->setUrl($this->request->getRequestUri());
139
-		$this->server->setBaseUri($this->baseUri);
140
-
141
-		$this->server->addPlugin(new ProfilerPlugin($this->request));
142
-		$this->server->addPlugin(new BlockLegacyClientPlugin(
143
-			\OCP\Server::get(IConfig::class),
144
-			\OCP\Server::get(ThemingDefaults::class),
145
-		));
146
-		$this->server->addPlugin(new AnonymousOptionsPlugin());
147
-		$authPlugin = new Plugin();
148
-		$authPlugin->addBackend(new PublicAuth());
149
-		$this->server->addPlugin($authPlugin);
150
-
151
-		// allow setup of additional auth backends
152
-		$event = new SabrePluginEvent($this->server);
153
-		$eventDispatcher->dispatch('OCA\DAV\Connector\Sabre::authInit', $event);
154
-
155
-		$newAuthEvent = new SabrePluginAuthInitEvent($this->server);
156
-		$eventDispatcher->dispatchTyped($newAuthEvent);
157
-
158
-		$bearerAuthBackend = new BearerAuth(
159
-			\OCP\Server::get(IUserSession::class),
160
-			\OCP\Server::get(ISession::class),
161
-			\OCP\Server::get(IRequest::class),
162
-			\OCP\Server::get(IConfig::class),
163
-		);
164
-		$authPlugin->addBackend($bearerAuthBackend);
165
-		// because we are throwing exceptions this plugin has to be the last one
166
-		$authPlugin->addBackend($authBackend);
167
-
168
-		// debugging
169
-		if (\OCP\Server::get(IConfig::class)->getSystemValue('debug', false)) {
170
-			$this->server->addPlugin(new \Sabre\DAV\Browser\Plugin());
171
-		} else {
172
-			$this->server->addPlugin(new DummyGetResponsePlugin());
173
-		}
174
-
175
-		$this->server->addPlugin(new ExceptionLoggerPlugin('webdav', $logger));
176
-		$this->server->addPlugin(new LockPlugin());
177
-		$this->server->addPlugin(new \Sabre\DAV\Sync\Plugin());
178
-
179
-		// acl
180
-		$acl = new DavAclPlugin();
181
-		$acl->principalCollectionSet = [
182
-			'principals/users',
183
-			'principals/groups',
184
-			'principals/calendar-resources',
185
-			'principals/calendar-rooms',
186
-		];
187
-		$this->server->addPlugin($acl);
188
-
189
-		// calendar plugins
190
-		if ($this->requestIsForSubtree(['calendars', 'public-calendars', 'system-calendars', 'principals'])) {
191
-			$this->server->addPlugin(new DAV\Sharing\Plugin($authBackend, \OCP\Server::get(IRequest::class), \OCP\Server::get(IConfig::class)));
192
-			$this->server->addPlugin(new \OCA\DAV\CalDAV\Plugin());
193
-			$this->server->addPlugin(new ICSExportPlugin(\OCP\Server::get(IConfig::class), $logger));
194
-			$this->server->addPlugin(new \OCA\DAV\CalDAV\Schedule\Plugin(\OCP\Server::get(IConfig::class), \OCP\Server::get(LoggerInterface::class), \OCP\Server::get(DefaultCalendarValidator::class)));
195
-
196
-			$this->server->addPlugin(\OCP\Server::get(\OCA\DAV\CalDAV\Trashbin\Plugin::class));
197
-			$this->server->addPlugin(new \OCA\DAV\CalDAV\WebcalCaching\Plugin($this->request));
198
-			if (\OCP\Server::get(IConfig::class)->getAppValue('dav', 'allow_calendar_link_subscriptions', 'yes') === 'yes') {
199
-				$this->server->addPlugin(new \Sabre\CalDAV\Subscriptions\Plugin());
200
-			}
201
-
202
-			$this->server->addPlugin(new \Sabre\CalDAV\Notifications\Plugin());
203
-			$this->server->addPlugin(new PublishPlugin(
204
-				\OCP\Server::get(IConfig::class),
205
-				\OCP\Server::get(IURLGenerator::class)
206
-			));
207
-
208
-			$this->server->addPlugin(\OCP\Server::get(RateLimitingPlugin::class));
209
-			$this->server->addPlugin(\OCP\Server::get(CalDavValidatePlugin::class));
210
-		}
211
-
212
-		// addressbook plugins
213
-		if ($this->requestIsForSubtree(['addressbooks', 'principals'])) {
214
-			$this->server->addPlugin(new DAV\Sharing\Plugin($authBackend, \OCP\Server::get(IRequest::class), \OCP\Server::get(IConfig::class)));
215
-			$this->server->addPlugin(new \OCA\DAV\CardDAV\Plugin());
216
-			$this->server->addPlugin(new VCFExportPlugin());
217
-			$this->server->addPlugin(new MultiGetExportPlugin());
218
-			$this->server->addPlugin(new HasPhotoPlugin());
219
-			$this->server->addPlugin(new ImageExportPlugin(new PhotoCache(
220
-				\OCP\Server::get(IAppDataFactory::class)->get('dav-photocache'),
221
-				$logger)
222
-			));
223
-
224
-			$this->server->addPlugin(\OCP\Server::get(CardDavRateLimitingPlugin::class));
225
-			$this->server->addPlugin(\OCP\Server::get(CardDavValidatePlugin::class));
226
-		}
227
-
228
-		// system tags plugins
229
-		$this->server->addPlugin(\OCP\Server::get(SystemTagPlugin::class));
230
-
231
-		// comments plugin
232
-		$this->server->addPlugin(new CommentsPlugin(
233
-			\OCP\Server::get(ICommentsManager::class),
234
-			\OCP\Server::get(IUserSession::class)
235
-		));
236
-
237
-		$this->server->addPlugin(new CopyEtagHeaderPlugin());
238
-		$this->server->addPlugin(new RequestIdHeaderPlugin(\OCP\Server::get(IRequest::class)));
239
-		$this->server->addPlugin(new ChunkingV2Plugin(\OCP\Server::get(ICacheFactory::class)));
240
-		$this->server->addPlugin(new ChunkingPlugin());
241
-		$this->server->addPlugin(new ZipFolderPlugin(
242
-			$this->server->tree,
243
-			$logger,
244
-			$eventDispatcher,
245
-		));
246
-		$this->server->addPlugin(\OCP\Server::get(PaginatePlugin::class));
247
-
248
-		// allow setup of additional plugins
249
-		$eventDispatcher->dispatch('OCA\DAV\Connector\Sabre::addPlugin', $event);
250
-		$typedEvent = new SabrePluginAddEvent($this->server);
251
-		$eventDispatcher->dispatchTyped($typedEvent);
252
-
253
-		// Some WebDAV clients do require Class 2 WebDAV support (locking), since
254
-		// we do not provide locking we emulate it using a fake locking plugin.
255
-		if ($this->request->isUserAgent([
256
-			'/WebDAVFS/',
257
-			'/OneNote/',
258
-			'/^Microsoft-WebDAV/',// Microsoft-WebDAV-MiniRedir/6.1.7601
259
-		])) {
260
-			$this->server->addPlugin(new FakeLockerPlugin());
261
-		}
262
-
263
-		if (BrowserErrorPagePlugin::isBrowserRequest($request)) {
264
-			$this->server->addPlugin(new BrowserErrorPagePlugin());
265
-		}
266
-
267
-		$lazySearchBackend = new LazySearchBackend();
268
-		$this->server->addPlugin(new SearchPlugin($lazySearchBackend));
269
-
270
-		// wait with registering these until auth is handled and the filesystem is setup
271
-		$this->server->on('beforeMethod:*', function () use ($root, $lazySearchBackend, $logger): void {
272
-			// Allow view-only plugin for webdav requests
273
-			$this->server->addPlugin(new ViewOnlyPlugin(
274
-				\OC::$server->getUserFolder(),
275
-			));
276
-
277
-			// custom properties plugin must be the last one
278
-			$userSession = \OCP\Server::get(IUserSession::class);
279
-			$user = $userSession->getUser();
280
-			if ($user !== null) {
281
-				$view = Filesystem::getView();
282
-				$config = \OCP\Server::get(IConfig::class);
283
-				$this->server->addPlugin(
284
-					new FilesPlugin(
285
-						$this->server->tree,
286
-						$config,
287
-						$this->request,
288
-						\OCP\Server::get(IPreview::class),
289
-						\OCP\Server::get(IUserSession::class),
290
-						\OCP\Server::get(IFilenameValidator::class),
291
-						\OCP\Server::get(IAccountManager::class),
292
-						false,
293
-						$config->getSystemValueBool('debug', false) === false,
294
-					)
295
-				);
296
-				$this->server->addPlugin(new ChecksumUpdatePlugin());
297
-
298
-				$this->server->addPlugin(
299
-					new \Sabre\DAV\PropertyStorage\Plugin(
300
-						new CustomPropertiesBackend(
301
-							$this->server,
302
-							$this->server->tree,
303
-							\OCP\Server::get(IDBConnection::class),
304
-							\OCP\Server::get(IUserSession::class)->getUser(),
305
-							\OCP\Server::get(DefaultCalendarValidator::class),
306
-						)
307
-					)
308
-				);
309
-				if ($view !== null) {
310
-					$this->server->addPlugin(
311
-						new QuotaPlugin($view));
312
-				}
313
-				$this->server->addPlugin(
314
-					new TagsPlugin(
315
-						$this->server->tree, \OCP\Server::get(ITagManager::class), \OCP\Server::get(IEventDispatcher::class), \OCP\Server::get(IUserSession::class)
316
-					)
317
-				);
318
-
319
-				// TODO: switch to LazyUserFolder
320
-				$userFolder = \OC::$server->getUserFolder();
321
-				$shareManager = \OCP\Server::get(\OCP\Share\IManager::class);
322
-				$this->server->addPlugin(new SharesPlugin(
323
-					$this->server->tree,
324
-					$userSession,
325
-					$userFolder,
326
-					$shareManager,
327
-				));
328
-				$this->server->addPlugin(new CommentPropertiesPlugin(
329
-					\OCP\Server::get(ICommentsManager::class),
330
-					$userSession
331
-				));
332
-				if (\OCP\Server::get(IConfig::class)->getAppValue('dav', 'sendInvitations', 'yes') === 'yes') {
333
-					$this->server->addPlugin(new IMipPlugin(
334
-						\OCP\Server::get(IAppConfig::class),
335
-						\OCP\Server::get(IMailer::class),
336
-						\OCP\Server::get(LoggerInterface::class),
337
-						\OCP\Server::get(ITimeFactory::class),
338
-						\OCP\Server::get(Defaults::class),
339
-						$userSession,
340
-						\OCP\Server::get(IMipService::class),
341
-						\OCP\Server::get(EventComparisonService::class),
342
-						\OCP\Server::get(\OCP\Mail\Provider\IManager::class)
343
-					));
344
-				}
345
-				$this->server->addPlugin(new \OCA\DAV\CalDAV\Search\SearchPlugin());
346
-				if ($view !== null) {
347
-					$this->server->addPlugin(new FilesReportPlugin(
348
-						$this->server->tree,
349
-						$view,
350
-						\OCP\Server::get(ISystemTagManager::class),
351
-						\OCP\Server::get(ISystemTagObjectMapper::class),
352
-						\OCP\Server::get(ITagManager::class),
353
-						$userSession,
354
-						\OCP\Server::get(IGroupManager::class),
355
-						$userFolder,
356
-						\OCP\Server::get(IAppManager::class)
357
-					));
358
-					$lazySearchBackend->setBackend(new FileSearchBackend(
359
-						$this->server->tree,
360
-						$user,
361
-						\OCP\Server::get(IRootFolder::class),
362
-						$shareManager,
363
-						$view,
364
-						\OCP\Server::get(IFilesMetadataManager::class)
365
-					));
366
-					$this->server->addPlugin(
367
-						new BulkUploadPlugin(
368
-							$userFolder,
369
-							$logger
370
-						)
371
-					);
372
-				}
373
-				$this->server->addPlugin(new EnablePlugin(
374
-					\OCP\Server::get(IConfig::class),
375
-					\OCP\Server::get(BirthdayService::class),
376
-					$user
377
-				));
378
-				$this->server->addPlugin(new AppleProvisioningPlugin(
379
-					\OCP\Server::get(IUserSession::class),
380
-					\OCP\Server::get(IURLGenerator::class),
381
-					\OCP\Server::get(ThemingDefaults::class),
382
-					\OCP\Server::get(IRequest::class),
383
-					\OC::$server->getL10N('dav'),
384
-					function () {
385
-						return UUIDUtil::getUUID();
386
-					}
387
-				));
388
-			}
389
-
390
-			// register plugins from apps
391
-			$pluginManager = new PluginManager(
392
-				\OC::$server,
393
-				\OCP\Server::get(IAppManager::class)
394
-			);
395
-			foreach ($pluginManager->getAppPlugins() as $appPlugin) {
396
-				$this->server->addPlugin($appPlugin);
397
-			}
398
-			foreach ($pluginManager->getAppCollections() as $appCollection) {
399
-				$root->addChild($appCollection);
400
-			}
401
-		});
402
-
403
-		$this->server->addPlugin(
404
-			new PropfindCompressionPlugin()
405
-		);
406
-	}
407
-
408
-	public function exec() {
409
-		/** @var IEventLogger $eventLogger */
410
-		$eventLogger = \OCP\Server::get(IEventLogger::class);
411
-		$eventLogger->start('dav_server_exec', '');
412
-		$this->server->start();
413
-		$eventLogger->end('dav_server_exec');
414
-		if ($this->profiler->isEnabled()) {
415
-			$eventLogger->end('runtime');
416
-			$profile = $this->profiler->collect(\OCP\Server::get(IRequest::class), new Response());
417
-			$this->profiler->saveProfile($profile);
418
-		}
419
-	}
420
-
421
-	private function requestIsForSubtree(array $subTrees): bool {
422
-		foreach ($subTrees as $subTree) {
423
-			$subTree = trim($subTree, ' /');
424
-			if (str_starts_with($this->server->getRequestUri(), $subTree . '/')) {
425
-				return true;
426
-			}
427
-		}
428
-		return false;
429
-	}
103
+    public Connector\Sabre\Server $server;
104
+    private IProfiler $profiler;
105
+
106
+    public function __construct(
107
+        private IRequest $request,
108
+        private string $baseUri,
109
+    ) {
110
+        $this->profiler = \OCP\Server::get(IProfiler::class);
111
+        if ($this->profiler->isEnabled()) {
112
+            /** @var IEventLogger $eventLogger */
113
+            $eventLogger = \OCP\Server::get(IEventLogger::class);
114
+            $eventLogger->start('runtime', 'DAV Runtime');
115
+        }
116
+
117
+        $logger = \OCP\Server::get(LoggerInterface::class);
118
+        $eventDispatcher = \OCP\Server::get(IEventDispatcher::class);
119
+
120
+        $root = new RootCollection();
121
+        $this->server = new \OCA\DAV\Connector\Sabre\Server(new CachingTree($root));
122
+
123
+        // Add maintenance plugin
124
+        $this->server->addPlugin(new MaintenancePlugin(\OCP\Server::get(IConfig::class), \OC::$server->getL10N('dav')));
125
+
126
+        $this->server->addPlugin(new AppleQuirksPlugin());
127
+
128
+        // Backends
129
+        $authBackend = new Auth(
130
+            \OCP\Server::get(ISession::class),
131
+            \OCP\Server::get(IUserSession::class),
132
+            \OCP\Server::get(IRequest::class),
133
+            \OCP\Server::get(\OC\Authentication\TwoFactorAuth\Manager::class),
134
+            \OCP\Server::get(IThrottler::class)
135
+        );
136
+
137
+        // Set URL explicitly due to reverse-proxy situations
138
+        $this->server->httpRequest->setUrl($this->request->getRequestUri());
139
+        $this->server->setBaseUri($this->baseUri);
140
+
141
+        $this->server->addPlugin(new ProfilerPlugin($this->request));
142
+        $this->server->addPlugin(new BlockLegacyClientPlugin(
143
+            \OCP\Server::get(IConfig::class),
144
+            \OCP\Server::get(ThemingDefaults::class),
145
+        ));
146
+        $this->server->addPlugin(new AnonymousOptionsPlugin());
147
+        $authPlugin = new Plugin();
148
+        $authPlugin->addBackend(new PublicAuth());
149
+        $this->server->addPlugin($authPlugin);
150
+
151
+        // allow setup of additional auth backends
152
+        $event = new SabrePluginEvent($this->server);
153
+        $eventDispatcher->dispatch('OCA\DAV\Connector\Sabre::authInit', $event);
154
+
155
+        $newAuthEvent = new SabrePluginAuthInitEvent($this->server);
156
+        $eventDispatcher->dispatchTyped($newAuthEvent);
157
+
158
+        $bearerAuthBackend = new BearerAuth(
159
+            \OCP\Server::get(IUserSession::class),
160
+            \OCP\Server::get(ISession::class),
161
+            \OCP\Server::get(IRequest::class),
162
+            \OCP\Server::get(IConfig::class),
163
+        );
164
+        $authPlugin->addBackend($bearerAuthBackend);
165
+        // because we are throwing exceptions this plugin has to be the last one
166
+        $authPlugin->addBackend($authBackend);
167
+
168
+        // debugging
169
+        if (\OCP\Server::get(IConfig::class)->getSystemValue('debug', false)) {
170
+            $this->server->addPlugin(new \Sabre\DAV\Browser\Plugin());
171
+        } else {
172
+            $this->server->addPlugin(new DummyGetResponsePlugin());
173
+        }
174
+
175
+        $this->server->addPlugin(new ExceptionLoggerPlugin('webdav', $logger));
176
+        $this->server->addPlugin(new LockPlugin());
177
+        $this->server->addPlugin(new \Sabre\DAV\Sync\Plugin());
178
+
179
+        // acl
180
+        $acl = new DavAclPlugin();
181
+        $acl->principalCollectionSet = [
182
+            'principals/users',
183
+            'principals/groups',
184
+            'principals/calendar-resources',
185
+            'principals/calendar-rooms',
186
+        ];
187
+        $this->server->addPlugin($acl);
188
+
189
+        // calendar plugins
190
+        if ($this->requestIsForSubtree(['calendars', 'public-calendars', 'system-calendars', 'principals'])) {
191
+            $this->server->addPlugin(new DAV\Sharing\Plugin($authBackend, \OCP\Server::get(IRequest::class), \OCP\Server::get(IConfig::class)));
192
+            $this->server->addPlugin(new \OCA\DAV\CalDAV\Plugin());
193
+            $this->server->addPlugin(new ICSExportPlugin(\OCP\Server::get(IConfig::class), $logger));
194
+            $this->server->addPlugin(new \OCA\DAV\CalDAV\Schedule\Plugin(\OCP\Server::get(IConfig::class), \OCP\Server::get(LoggerInterface::class), \OCP\Server::get(DefaultCalendarValidator::class)));
195
+
196
+            $this->server->addPlugin(\OCP\Server::get(\OCA\DAV\CalDAV\Trashbin\Plugin::class));
197
+            $this->server->addPlugin(new \OCA\DAV\CalDAV\WebcalCaching\Plugin($this->request));
198
+            if (\OCP\Server::get(IConfig::class)->getAppValue('dav', 'allow_calendar_link_subscriptions', 'yes') === 'yes') {
199
+                $this->server->addPlugin(new \Sabre\CalDAV\Subscriptions\Plugin());
200
+            }
201
+
202
+            $this->server->addPlugin(new \Sabre\CalDAV\Notifications\Plugin());
203
+            $this->server->addPlugin(new PublishPlugin(
204
+                \OCP\Server::get(IConfig::class),
205
+                \OCP\Server::get(IURLGenerator::class)
206
+            ));
207
+
208
+            $this->server->addPlugin(\OCP\Server::get(RateLimitingPlugin::class));
209
+            $this->server->addPlugin(\OCP\Server::get(CalDavValidatePlugin::class));
210
+        }
211
+
212
+        // addressbook plugins
213
+        if ($this->requestIsForSubtree(['addressbooks', 'principals'])) {
214
+            $this->server->addPlugin(new DAV\Sharing\Plugin($authBackend, \OCP\Server::get(IRequest::class), \OCP\Server::get(IConfig::class)));
215
+            $this->server->addPlugin(new \OCA\DAV\CardDAV\Plugin());
216
+            $this->server->addPlugin(new VCFExportPlugin());
217
+            $this->server->addPlugin(new MultiGetExportPlugin());
218
+            $this->server->addPlugin(new HasPhotoPlugin());
219
+            $this->server->addPlugin(new ImageExportPlugin(new PhotoCache(
220
+                \OCP\Server::get(IAppDataFactory::class)->get('dav-photocache'),
221
+                $logger)
222
+            ));
223
+
224
+            $this->server->addPlugin(\OCP\Server::get(CardDavRateLimitingPlugin::class));
225
+            $this->server->addPlugin(\OCP\Server::get(CardDavValidatePlugin::class));
226
+        }
227
+
228
+        // system tags plugins
229
+        $this->server->addPlugin(\OCP\Server::get(SystemTagPlugin::class));
230
+
231
+        // comments plugin
232
+        $this->server->addPlugin(new CommentsPlugin(
233
+            \OCP\Server::get(ICommentsManager::class),
234
+            \OCP\Server::get(IUserSession::class)
235
+        ));
236
+
237
+        $this->server->addPlugin(new CopyEtagHeaderPlugin());
238
+        $this->server->addPlugin(new RequestIdHeaderPlugin(\OCP\Server::get(IRequest::class)));
239
+        $this->server->addPlugin(new ChunkingV2Plugin(\OCP\Server::get(ICacheFactory::class)));
240
+        $this->server->addPlugin(new ChunkingPlugin());
241
+        $this->server->addPlugin(new ZipFolderPlugin(
242
+            $this->server->tree,
243
+            $logger,
244
+            $eventDispatcher,
245
+        ));
246
+        $this->server->addPlugin(\OCP\Server::get(PaginatePlugin::class));
247
+
248
+        // allow setup of additional plugins
249
+        $eventDispatcher->dispatch('OCA\DAV\Connector\Sabre::addPlugin', $event);
250
+        $typedEvent = new SabrePluginAddEvent($this->server);
251
+        $eventDispatcher->dispatchTyped($typedEvent);
252
+
253
+        // Some WebDAV clients do require Class 2 WebDAV support (locking), since
254
+        // we do not provide locking we emulate it using a fake locking plugin.
255
+        if ($this->request->isUserAgent([
256
+            '/WebDAVFS/',
257
+            '/OneNote/',
258
+            '/^Microsoft-WebDAV/',// Microsoft-WebDAV-MiniRedir/6.1.7601
259
+        ])) {
260
+            $this->server->addPlugin(new FakeLockerPlugin());
261
+        }
262
+
263
+        if (BrowserErrorPagePlugin::isBrowserRequest($request)) {
264
+            $this->server->addPlugin(new BrowserErrorPagePlugin());
265
+        }
266
+
267
+        $lazySearchBackend = new LazySearchBackend();
268
+        $this->server->addPlugin(new SearchPlugin($lazySearchBackend));
269
+
270
+        // wait with registering these until auth is handled and the filesystem is setup
271
+        $this->server->on('beforeMethod:*', function () use ($root, $lazySearchBackend, $logger): void {
272
+            // Allow view-only plugin for webdav requests
273
+            $this->server->addPlugin(new ViewOnlyPlugin(
274
+                \OC::$server->getUserFolder(),
275
+            ));
276
+
277
+            // custom properties plugin must be the last one
278
+            $userSession = \OCP\Server::get(IUserSession::class);
279
+            $user = $userSession->getUser();
280
+            if ($user !== null) {
281
+                $view = Filesystem::getView();
282
+                $config = \OCP\Server::get(IConfig::class);
283
+                $this->server->addPlugin(
284
+                    new FilesPlugin(
285
+                        $this->server->tree,
286
+                        $config,
287
+                        $this->request,
288
+                        \OCP\Server::get(IPreview::class),
289
+                        \OCP\Server::get(IUserSession::class),
290
+                        \OCP\Server::get(IFilenameValidator::class),
291
+                        \OCP\Server::get(IAccountManager::class),
292
+                        false,
293
+                        $config->getSystemValueBool('debug', false) === false,
294
+                    )
295
+                );
296
+                $this->server->addPlugin(new ChecksumUpdatePlugin());
297
+
298
+                $this->server->addPlugin(
299
+                    new \Sabre\DAV\PropertyStorage\Plugin(
300
+                        new CustomPropertiesBackend(
301
+                            $this->server,
302
+                            $this->server->tree,
303
+                            \OCP\Server::get(IDBConnection::class),
304
+                            \OCP\Server::get(IUserSession::class)->getUser(),
305
+                            \OCP\Server::get(DefaultCalendarValidator::class),
306
+                        )
307
+                    )
308
+                );
309
+                if ($view !== null) {
310
+                    $this->server->addPlugin(
311
+                        new QuotaPlugin($view));
312
+                }
313
+                $this->server->addPlugin(
314
+                    new TagsPlugin(
315
+                        $this->server->tree, \OCP\Server::get(ITagManager::class), \OCP\Server::get(IEventDispatcher::class), \OCP\Server::get(IUserSession::class)
316
+                    )
317
+                );
318
+
319
+                // TODO: switch to LazyUserFolder
320
+                $userFolder = \OC::$server->getUserFolder();
321
+                $shareManager = \OCP\Server::get(\OCP\Share\IManager::class);
322
+                $this->server->addPlugin(new SharesPlugin(
323
+                    $this->server->tree,
324
+                    $userSession,
325
+                    $userFolder,
326
+                    $shareManager,
327
+                ));
328
+                $this->server->addPlugin(new CommentPropertiesPlugin(
329
+                    \OCP\Server::get(ICommentsManager::class),
330
+                    $userSession
331
+                ));
332
+                if (\OCP\Server::get(IConfig::class)->getAppValue('dav', 'sendInvitations', 'yes') === 'yes') {
333
+                    $this->server->addPlugin(new IMipPlugin(
334
+                        \OCP\Server::get(IAppConfig::class),
335
+                        \OCP\Server::get(IMailer::class),
336
+                        \OCP\Server::get(LoggerInterface::class),
337
+                        \OCP\Server::get(ITimeFactory::class),
338
+                        \OCP\Server::get(Defaults::class),
339
+                        $userSession,
340
+                        \OCP\Server::get(IMipService::class),
341
+                        \OCP\Server::get(EventComparisonService::class),
342
+                        \OCP\Server::get(\OCP\Mail\Provider\IManager::class)
343
+                    ));
344
+                }
345
+                $this->server->addPlugin(new \OCA\DAV\CalDAV\Search\SearchPlugin());
346
+                if ($view !== null) {
347
+                    $this->server->addPlugin(new FilesReportPlugin(
348
+                        $this->server->tree,
349
+                        $view,
350
+                        \OCP\Server::get(ISystemTagManager::class),
351
+                        \OCP\Server::get(ISystemTagObjectMapper::class),
352
+                        \OCP\Server::get(ITagManager::class),
353
+                        $userSession,
354
+                        \OCP\Server::get(IGroupManager::class),
355
+                        $userFolder,
356
+                        \OCP\Server::get(IAppManager::class)
357
+                    ));
358
+                    $lazySearchBackend->setBackend(new FileSearchBackend(
359
+                        $this->server->tree,
360
+                        $user,
361
+                        \OCP\Server::get(IRootFolder::class),
362
+                        $shareManager,
363
+                        $view,
364
+                        \OCP\Server::get(IFilesMetadataManager::class)
365
+                    ));
366
+                    $this->server->addPlugin(
367
+                        new BulkUploadPlugin(
368
+                            $userFolder,
369
+                            $logger
370
+                        )
371
+                    );
372
+                }
373
+                $this->server->addPlugin(new EnablePlugin(
374
+                    \OCP\Server::get(IConfig::class),
375
+                    \OCP\Server::get(BirthdayService::class),
376
+                    $user
377
+                ));
378
+                $this->server->addPlugin(new AppleProvisioningPlugin(
379
+                    \OCP\Server::get(IUserSession::class),
380
+                    \OCP\Server::get(IURLGenerator::class),
381
+                    \OCP\Server::get(ThemingDefaults::class),
382
+                    \OCP\Server::get(IRequest::class),
383
+                    \OC::$server->getL10N('dav'),
384
+                    function () {
385
+                        return UUIDUtil::getUUID();
386
+                    }
387
+                ));
388
+            }
389
+
390
+            // register plugins from apps
391
+            $pluginManager = new PluginManager(
392
+                \OC::$server,
393
+                \OCP\Server::get(IAppManager::class)
394
+            );
395
+            foreach ($pluginManager->getAppPlugins() as $appPlugin) {
396
+                $this->server->addPlugin($appPlugin);
397
+            }
398
+            foreach ($pluginManager->getAppCollections() as $appCollection) {
399
+                $root->addChild($appCollection);
400
+            }
401
+        });
402
+
403
+        $this->server->addPlugin(
404
+            new PropfindCompressionPlugin()
405
+        );
406
+    }
407
+
408
+    public function exec() {
409
+        /** @var IEventLogger $eventLogger */
410
+        $eventLogger = \OCP\Server::get(IEventLogger::class);
411
+        $eventLogger->start('dav_server_exec', '');
412
+        $this->server->start();
413
+        $eventLogger->end('dav_server_exec');
414
+        if ($this->profiler->isEnabled()) {
415
+            $eventLogger->end('runtime');
416
+            $profile = $this->profiler->collect(\OCP\Server::get(IRequest::class), new Response());
417
+            $this->profiler->saveProfile($profile);
418
+        }
419
+    }
420
+
421
+    private function requestIsForSubtree(array $subTrees): bool {
422
+        foreach ($subTrees as $subTree) {
423
+            $subTree = trim($subTree, ' /');
424
+            if (str_starts_with($this->server->getRequestUri(), $subTree . '/')) {
425
+                return true;
426
+            }
427
+        }
428
+        return false;
429
+    }
430 430
 
431 431
 }
Please login to merge, or discard this patch.
apps/dav/lib/Connector/Sabre/ServerFactory.php 1 patch
Indentation   +153 added lines, -153 removed lines patch added patch discarded remove patch
@@ -37,157 +37,157 @@
 block discarded – undo
37 37
 
38 38
 class ServerFactory {
39 39
 
40
-	public function __construct(
41
-		private IConfig $config,
42
-		private LoggerInterface $logger,
43
-		private IDBConnection $databaseConnection,
44
-		private IUserSession $userSession,
45
-		private IMountManager $mountManager,
46
-		private ITagManager $tagManager,
47
-		private IRequest $request,
48
-		private IPreview $previewManager,
49
-		private IEventDispatcher $eventDispatcher,
50
-		private IL10N $l10n,
51
-	) {
52
-	}
53
-
54
-	/**
55
-	 * @param callable $viewCallBack callback that should return the view for the dav endpoint
56
-	 */
57
-	public function createServer(string $baseUri,
58
-		string $requestUri,
59
-		Plugin $authPlugin,
60
-		callable $viewCallBack): Server {
61
-		// Fire up server
62
-		$objectTree = new ObjectTree();
63
-		$server = new Server($objectTree);
64
-		// Set URL explicitly due to reverse-proxy situations
65
-		$server->httpRequest->setUrl($requestUri);
66
-		$server->setBaseUri($baseUri);
67
-
68
-		// Load plugins
69
-		$server->addPlugin(new MaintenancePlugin($this->config, $this->l10n));
70
-		$server->addPlugin(new BlockLegacyClientPlugin(
71
-			$this->config,
72
-			\OCP\Server::get(ThemingDefaults::class),
73
-		));
74
-		$server->addPlugin(new AnonymousOptionsPlugin());
75
-		$server->addPlugin($authPlugin);
76
-		// FIXME: The following line is a workaround for legacy components relying on being able to send a GET to /
77
-		$server->addPlugin(new DummyGetResponsePlugin());
78
-		$server->addPlugin(new ExceptionLoggerPlugin('webdav', $this->logger));
79
-		$server->addPlugin(new LockPlugin());
80
-
81
-		$server->addPlugin(new RequestIdHeaderPlugin($this->request));
82
-
83
-		$server->addPlugin(new ZipFolderPlugin(
84
-			$objectTree,
85
-			$this->logger,
86
-			$this->eventDispatcher,
87
-		));
88
-
89
-		// Some WebDAV clients do require Class 2 WebDAV support (locking), since
90
-		// we do not provide locking we emulate it using a fake locking plugin.
91
-		if ($this->request->isUserAgent([
92
-			'/WebDAVFS/',
93
-			'/OneNote/',
94
-			'/Microsoft-WebDAV-MiniRedir/',
95
-		])) {
96
-			$server->addPlugin(new FakeLockerPlugin());
97
-		}
98
-
99
-		if (BrowserErrorPagePlugin::isBrowserRequest($this->request)) {
100
-			$server->addPlugin(new BrowserErrorPagePlugin());
101
-		}
102
-
103
-		// wait with registering these until auth is handled and the filesystem is setup
104
-		$server->on('beforeMethod:*', function () use ($server, $objectTree, $viewCallBack): void {
105
-			// ensure the skeleton is copied
106
-			$userFolder = \OC::$server->getUserFolder();
107
-
108
-			/** @var View $view */
109
-			$view = $viewCallBack($server);
110
-			if ($userFolder instanceof Folder && $userFolder->getPath() === $view->getRoot()) {
111
-				$rootInfo = $userFolder;
112
-			} else {
113
-				$rootInfo = $view->getFileInfo('');
114
-			}
115
-
116
-			// Create Nextcloud Dir
117
-			if ($rootInfo->getType() === 'dir') {
118
-				$root = new Directory($view, $rootInfo, $objectTree);
119
-			} else {
120
-				$root = new File($view, $rootInfo);
121
-			}
122
-			$objectTree->init($root, $view, $this->mountManager);
123
-
124
-			$server->addPlugin(
125
-				new FilesPlugin(
126
-					$objectTree,
127
-					$this->config,
128
-					$this->request,
129
-					$this->previewManager,
130
-					$this->userSession,
131
-					\OCP\Server::get(IFilenameValidator::class),
132
-					\OCP\Server::get(IAccountManager::class),
133
-					false,
134
-					!$this->config->getSystemValue('debug', false)
135
-				)
136
-			);
137
-			$server->addPlugin(new QuotaPlugin($view, true));
138
-			$server->addPlugin(new ChecksumUpdatePlugin());
139
-
140
-			// Allow view-only plugin for webdav requests
141
-			$server->addPlugin(new ViewOnlyPlugin(
142
-				$userFolder,
143
-			));
144
-
145
-			if ($this->userSession->isLoggedIn()) {
146
-				$server->addPlugin(new TagsPlugin($objectTree, $this->tagManager, $this->eventDispatcher, $this->userSession));
147
-				$server->addPlugin(new SharesPlugin(
148
-					$objectTree,
149
-					$this->userSession,
150
-					$userFolder,
151
-					\OCP\Server::get(\OCP\Share\IManager::class)
152
-				));
153
-				$server->addPlugin(new CommentPropertiesPlugin(\OCP\Server::get(ICommentsManager::class), $this->userSession));
154
-				$server->addPlugin(new FilesReportPlugin(
155
-					$objectTree,
156
-					$view,
157
-					\OCP\Server::get(ISystemTagManager::class),
158
-					\OCP\Server::get(ISystemTagObjectMapper::class),
159
-					\OCP\Server::get(ITagManager::class),
160
-					$this->userSession,
161
-					\OCP\Server::get(IGroupManager::class),
162
-					$userFolder,
163
-					\OCP\Server::get(IAppManager::class)
164
-				));
165
-				// custom properties plugin must be the last one
166
-				$server->addPlugin(
167
-					new \Sabre\DAV\PropertyStorage\Plugin(
168
-						new CustomPropertiesBackend(
169
-							$server,
170
-							$objectTree,
171
-							$this->databaseConnection,
172
-							$this->userSession->getUser(),
173
-							\OCP\Server::get(DefaultCalendarValidator::class),
174
-						)
175
-					)
176
-				);
177
-			}
178
-			$server->addPlugin(new CopyEtagHeaderPlugin());
179
-
180
-			// Load dav plugins from apps
181
-			$event = new SabrePluginEvent($server);
182
-			$this->eventDispatcher->dispatchTyped($event);
183
-			$pluginManager = new PluginManager(
184
-				\OC::$server,
185
-				\OCP\Server::get(IAppManager::class)
186
-			);
187
-			foreach ($pluginManager->getAppPlugins() as $appPlugin) {
188
-				$server->addPlugin($appPlugin);
189
-			}
190
-		}, 30); // priority 30: after auth (10) and acl(20), before lock(50) and handling the request
191
-		return $server;
192
-	}
40
+    public function __construct(
41
+        private IConfig $config,
42
+        private LoggerInterface $logger,
43
+        private IDBConnection $databaseConnection,
44
+        private IUserSession $userSession,
45
+        private IMountManager $mountManager,
46
+        private ITagManager $tagManager,
47
+        private IRequest $request,
48
+        private IPreview $previewManager,
49
+        private IEventDispatcher $eventDispatcher,
50
+        private IL10N $l10n,
51
+    ) {
52
+    }
53
+
54
+    /**
55
+     * @param callable $viewCallBack callback that should return the view for the dav endpoint
56
+     */
57
+    public function createServer(string $baseUri,
58
+        string $requestUri,
59
+        Plugin $authPlugin,
60
+        callable $viewCallBack): Server {
61
+        // Fire up server
62
+        $objectTree = new ObjectTree();
63
+        $server = new Server($objectTree);
64
+        // Set URL explicitly due to reverse-proxy situations
65
+        $server->httpRequest->setUrl($requestUri);
66
+        $server->setBaseUri($baseUri);
67
+
68
+        // Load plugins
69
+        $server->addPlugin(new MaintenancePlugin($this->config, $this->l10n));
70
+        $server->addPlugin(new BlockLegacyClientPlugin(
71
+            $this->config,
72
+            \OCP\Server::get(ThemingDefaults::class),
73
+        ));
74
+        $server->addPlugin(new AnonymousOptionsPlugin());
75
+        $server->addPlugin($authPlugin);
76
+        // FIXME: The following line is a workaround for legacy components relying on being able to send a GET to /
77
+        $server->addPlugin(new DummyGetResponsePlugin());
78
+        $server->addPlugin(new ExceptionLoggerPlugin('webdav', $this->logger));
79
+        $server->addPlugin(new LockPlugin());
80
+
81
+        $server->addPlugin(new RequestIdHeaderPlugin($this->request));
82
+
83
+        $server->addPlugin(new ZipFolderPlugin(
84
+            $objectTree,
85
+            $this->logger,
86
+            $this->eventDispatcher,
87
+        ));
88
+
89
+        // Some WebDAV clients do require Class 2 WebDAV support (locking), since
90
+        // we do not provide locking we emulate it using a fake locking plugin.
91
+        if ($this->request->isUserAgent([
92
+            '/WebDAVFS/',
93
+            '/OneNote/',
94
+            '/Microsoft-WebDAV-MiniRedir/',
95
+        ])) {
96
+            $server->addPlugin(new FakeLockerPlugin());
97
+        }
98
+
99
+        if (BrowserErrorPagePlugin::isBrowserRequest($this->request)) {
100
+            $server->addPlugin(new BrowserErrorPagePlugin());
101
+        }
102
+
103
+        // wait with registering these until auth is handled and the filesystem is setup
104
+        $server->on('beforeMethod:*', function () use ($server, $objectTree, $viewCallBack): void {
105
+            // ensure the skeleton is copied
106
+            $userFolder = \OC::$server->getUserFolder();
107
+
108
+            /** @var View $view */
109
+            $view = $viewCallBack($server);
110
+            if ($userFolder instanceof Folder && $userFolder->getPath() === $view->getRoot()) {
111
+                $rootInfo = $userFolder;
112
+            } else {
113
+                $rootInfo = $view->getFileInfo('');
114
+            }
115
+
116
+            // Create Nextcloud Dir
117
+            if ($rootInfo->getType() === 'dir') {
118
+                $root = new Directory($view, $rootInfo, $objectTree);
119
+            } else {
120
+                $root = new File($view, $rootInfo);
121
+            }
122
+            $objectTree->init($root, $view, $this->mountManager);
123
+
124
+            $server->addPlugin(
125
+                new FilesPlugin(
126
+                    $objectTree,
127
+                    $this->config,
128
+                    $this->request,
129
+                    $this->previewManager,
130
+                    $this->userSession,
131
+                    \OCP\Server::get(IFilenameValidator::class),
132
+                    \OCP\Server::get(IAccountManager::class),
133
+                    false,
134
+                    !$this->config->getSystemValue('debug', false)
135
+                )
136
+            );
137
+            $server->addPlugin(new QuotaPlugin($view, true));
138
+            $server->addPlugin(new ChecksumUpdatePlugin());
139
+
140
+            // Allow view-only plugin for webdav requests
141
+            $server->addPlugin(new ViewOnlyPlugin(
142
+                $userFolder,
143
+            ));
144
+
145
+            if ($this->userSession->isLoggedIn()) {
146
+                $server->addPlugin(new TagsPlugin($objectTree, $this->tagManager, $this->eventDispatcher, $this->userSession));
147
+                $server->addPlugin(new SharesPlugin(
148
+                    $objectTree,
149
+                    $this->userSession,
150
+                    $userFolder,
151
+                    \OCP\Server::get(\OCP\Share\IManager::class)
152
+                ));
153
+                $server->addPlugin(new CommentPropertiesPlugin(\OCP\Server::get(ICommentsManager::class), $this->userSession));
154
+                $server->addPlugin(new FilesReportPlugin(
155
+                    $objectTree,
156
+                    $view,
157
+                    \OCP\Server::get(ISystemTagManager::class),
158
+                    \OCP\Server::get(ISystemTagObjectMapper::class),
159
+                    \OCP\Server::get(ITagManager::class),
160
+                    $this->userSession,
161
+                    \OCP\Server::get(IGroupManager::class),
162
+                    $userFolder,
163
+                    \OCP\Server::get(IAppManager::class)
164
+                ));
165
+                // custom properties plugin must be the last one
166
+                $server->addPlugin(
167
+                    new \Sabre\DAV\PropertyStorage\Plugin(
168
+                        new CustomPropertiesBackend(
169
+                            $server,
170
+                            $objectTree,
171
+                            $this->databaseConnection,
172
+                            $this->userSession->getUser(),
173
+                            \OCP\Server::get(DefaultCalendarValidator::class),
174
+                        )
175
+                    )
176
+                );
177
+            }
178
+            $server->addPlugin(new CopyEtagHeaderPlugin());
179
+
180
+            // Load dav plugins from apps
181
+            $event = new SabrePluginEvent($server);
182
+            $this->eventDispatcher->dispatchTyped($event);
183
+            $pluginManager = new PluginManager(
184
+                \OC::$server,
185
+                \OCP\Server::get(IAppManager::class)
186
+            );
187
+            foreach ($pluginManager->getAppPlugins() as $appPlugin) {
188
+                $server->addPlugin($appPlugin);
189
+            }
190
+        }, 30); // priority 30: after auth (10) and acl(20), before lock(50) and handling the request
191
+        return $server;
192
+    }
193 193
 }
Please login to merge, or discard this patch.
apps/dav/lib/Connector/Sabre/FilesPlugin.php 2 patches
Indentation   +692 added lines, -692 removed lines patch added patch discarded remove patch
@@ -39,696 +39,696 @@
 block discarded – undo
39 39
 use Sabre\HTTP\ResponseInterface;
40 40
 
41 41
 class FilesPlugin extends ServerPlugin {
42
-	// namespace
43
-	public const NS_OWNCLOUD = 'http://owncloud.org/ns';
44
-	public const NS_NEXTCLOUD = 'http://nextcloud.org/ns';
45
-	public const FILEID_PROPERTYNAME = '{http://owncloud.org/ns}id';
46
-	public const INTERNAL_FILEID_PROPERTYNAME = '{http://owncloud.org/ns}fileid';
47
-	public const PERMISSIONS_PROPERTYNAME = '{http://owncloud.org/ns}permissions';
48
-	public const SHARE_PERMISSIONS_PROPERTYNAME = '{http://open-collaboration-services.org/ns}share-permissions';
49
-	public const OCM_SHARE_PERMISSIONS_PROPERTYNAME = '{http://open-cloud-mesh.org/ns}share-permissions';
50
-	public const SHARE_ATTRIBUTES_PROPERTYNAME = '{http://nextcloud.org/ns}share-attributes';
51
-	public const DOWNLOADURL_PROPERTYNAME = '{http://owncloud.org/ns}downloadURL';
52
-	public const SIZE_PROPERTYNAME = '{http://owncloud.org/ns}size';
53
-	public const GETETAG_PROPERTYNAME = '{DAV:}getetag';
54
-	public const LASTMODIFIED_PROPERTYNAME = '{DAV:}lastmodified';
55
-	public const CREATIONDATE_PROPERTYNAME = '{DAV:}creationdate';
56
-	public const DISPLAYNAME_PROPERTYNAME = '{DAV:}displayname';
57
-	public const OWNER_ID_PROPERTYNAME = '{http://owncloud.org/ns}owner-id';
58
-	public const OWNER_DISPLAY_NAME_PROPERTYNAME = '{http://owncloud.org/ns}owner-display-name';
59
-	public const CHECKSUMS_PROPERTYNAME = '{http://owncloud.org/ns}checksums';
60
-	public const DATA_FINGERPRINT_PROPERTYNAME = '{http://owncloud.org/ns}data-fingerprint';
61
-	public const HAS_PREVIEW_PROPERTYNAME = '{http://nextcloud.org/ns}has-preview';
62
-	public const MOUNT_TYPE_PROPERTYNAME = '{http://nextcloud.org/ns}mount-type';
63
-	public const MOUNT_ROOT_PROPERTYNAME = '{http://nextcloud.org/ns}is-mount-root';
64
-	public const IS_FEDERATED_PROPERTYNAME = '{http://nextcloud.org/ns}is-federated';
65
-	public const METADATA_ETAG_PROPERTYNAME = '{http://nextcloud.org/ns}metadata_etag';
66
-	public const UPLOAD_TIME_PROPERTYNAME = '{http://nextcloud.org/ns}upload_time';
67
-	public const CREATION_TIME_PROPERTYNAME = '{http://nextcloud.org/ns}creation_time';
68
-	public const SHARE_NOTE = '{http://nextcloud.org/ns}note';
69
-	public const SHARE_HIDE_DOWNLOAD_PROPERTYNAME = '{http://nextcloud.org/ns}hide-download';
70
-	public const SUBFOLDER_COUNT_PROPERTYNAME = '{http://nextcloud.org/ns}contained-folder-count';
71
-	public const SUBFILE_COUNT_PROPERTYNAME = '{http://nextcloud.org/ns}contained-file-count';
72
-	public const FILE_METADATA_PREFIX = '{http://nextcloud.org/ns}metadata-';
73
-	public const HIDDEN_PROPERTYNAME = '{http://nextcloud.org/ns}hidden';
74
-
75
-	/** Reference to main server object */
76
-	private ?Server $server = null;
77
-
78
-	/**
79
-	 * @param Tree $tree
80
-	 * @param IConfig $config
81
-	 * @param IRequest $request
82
-	 * @param IPreview $previewManager
83
-	 * @param IUserSession $userSession
84
-	 * @param bool $isPublic Whether this is public WebDAV. If true, some returned information will be stripped off.
85
-	 * @param bool $downloadAttachment
86
-	 * @return void
87
-	 */
88
-	public function __construct(
89
-		private Tree $tree,
90
-		private IConfig $config,
91
-		private IRequest $request,
92
-		private IPreview $previewManager,
93
-		private IUserSession $userSession,
94
-		private IFilenameValidator $validator,
95
-		private IAccountManager $accountManager,
96
-		private bool $isPublic = false,
97
-		private bool $downloadAttachment = true,
98
-	) {
99
-	}
100
-
101
-	/**
102
-	 * This initializes the plugin.
103
-	 *
104
-	 * This function is called by \Sabre\DAV\Server, after
105
-	 * addPlugin is called.
106
-	 *
107
-	 * This method should set up the required event subscriptions.
108
-	 *
109
-	 * @return void
110
-	 */
111
-	public function initialize(Server $server) {
112
-		$server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
113
-		$server->xml->namespaceMap[self::NS_NEXTCLOUD] = 'nc';
114
-		$server->protectedProperties[] = self::FILEID_PROPERTYNAME;
115
-		$server->protectedProperties[] = self::INTERNAL_FILEID_PROPERTYNAME;
116
-		$server->protectedProperties[] = self::PERMISSIONS_PROPERTYNAME;
117
-		$server->protectedProperties[] = self::SHARE_PERMISSIONS_PROPERTYNAME;
118
-		$server->protectedProperties[] = self::OCM_SHARE_PERMISSIONS_PROPERTYNAME;
119
-		$server->protectedProperties[] = self::SHARE_ATTRIBUTES_PROPERTYNAME;
120
-		$server->protectedProperties[] = self::SIZE_PROPERTYNAME;
121
-		$server->protectedProperties[] = self::DOWNLOADURL_PROPERTYNAME;
122
-		$server->protectedProperties[] = self::OWNER_ID_PROPERTYNAME;
123
-		$server->protectedProperties[] = self::OWNER_DISPLAY_NAME_PROPERTYNAME;
124
-		$server->protectedProperties[] = self::CHECKSUMS_PROPERTYNAME;
125
-		$server->protectedProperties[] = self::DATA_FINGERPRINT_PROPERTYNAME;
126
-		$server->protectedProperties[] = self::HAS_PREVIEW_PROPERTYNAME;
127
-		$server->protectedProperties[] = self::MOUNT_TYPE_PROPERTYNAME;
128
-		$server->protectedProperties[] = self::IS_FEDERATED_PROPERTYNAME;
129
-		$server->protectedProperties[] = self::SHARE_NOTE;
130
-
131
-		// normally these cannot be changed (RFC4918), but we want them modifiable through PROPPATCH
132
-		$allowedProperties = ['{DAV:}getetag'];
133
-		$server->protectedProperties = array_diff($server->protectedProperties, $allowedProperties);
134
-
135
-		$this->server = $server;
136
-		$this->server->on('propFind', [$this, 'handleGetProperties']);
137
-		$this->server->on('propPatch', [$this, 'handleUpdateProperties']);
138
-		$this->server->on('afterBind', [$this, 'sendFileIdHeader']);
139
-		$this->server->on('afterWriteContent', [$this, 'sendFileIdHeader']);
140
-		$this->server->on('afterMethod:GET', [$this,'httpGet']);
141
-		$this->server->on('afterMethod:GET', [$this, 'handleDownloadToken']);
142
-		$this->server->on('afterResponse', function ($request, ResponseInterface $response): void {
143
-			$body = $response->getBody();
144
-			if (is_resource($body)) {
145
-				fclose($body);
146
-			}
147
-		});
148
-		$this->server->on('beforeMove', [$this, 'checkMove']);
149
-		$this->server->on('beforeCopy', [$this, 'checkCopy']);
150
-	}
151
-
152
-	/**
153
-	 * Plugin that checks if a copy can actually be performed.
154
-	 *
155
-	 * @param string $source source path
156
-	 * @param string $target target path
157
-	 * @throws NotFound If the source does not exist
158
-	 * @throws InvalidPath If the target is invalid
159
-	 */
160
-	public function checkCopy($source, $target): void {
161
-		$sourceNode = $this->tree->getNodeForPath($source);
162
-		if (!$sourceNode instanceof Node) {
163
-			return;
164
-		}
165
-
166
-		// Ensure source exists
167
-		$sourceNodeFileInfo = $sourceNode->getFileInfo();
168
-		if ($sourceNodeFileInfo === null) {
169
-			throw new NotFound($source . ' does not exist');
170
-		}
171
-		// Ensure the target name is valid
172
-		try {
173
-			[$targetPath, $targetName] = \Sabre\Uri\split($target);
174
-			$this->validator->validateFilename($targetName);
175
-		} catch (InvalidPathException $e) {
176
-			throw new InvalidPath($e->getMessage(), false);
177
-		}
178
-		// Ensure the target path is valid
179
-		$segments = array_slice(explode('/', $targetPath), 2);
180
-		foreach ($segments as $segment) {
181
-			if ($this->validator->isFilenameValid($segment) === false) {
182
-				$l = \OCP\Server::get(IFactory::class)->get('dav');
183
-				throw new InvalidPath($l->t('Invalid target path'));
184
-			}
185
-		}
186
-	}
187
-
188
-	/**
189
-	 * Plugin that checks if a move can actually be performed.
190
-	 *
191
-	 * @param string $source source path
192
-	 * @param string $target target path
193
-	 * @throws Forbidden If the source is not deletable
194
-	 * @throws NotFound If the source does not exist
195
-	 * @throws InvalidPath If the target name is invalid
196
-	 */
197
-	public function checkMove(string $source, string $target): void {
198
-		$sourceNode = $this->tree->getNodeForPath($source);
199
-		if (!$sourceNode instanceof Node) {
200
-			return;
201
-		}
202
-
203
-		// First check copyable (move only needs additional delete permission)
204
-		$this->checkCopy($source, $target);
205
-
206
-		// The source needs to be deletable for moving
207
-		$sourceNodeFileInfo = $sourceNode->getFileInfo();
208
-		if (!$sourceNodeFileInfo->isDeletable()) {
209
-			throw new Forbidden($source . ' cannot be deleted');
210
-		}
211
-
212
-		// The source is not allowed to be the parent of the target
213
-		if (str_starts_with($source, $target . '/')) {
214
-			throw new Forbidden($source . ' cannot be moved to it\'s parent');
215
-		}
216
-	}
217
-
218
-	/**
219
-	 * This sets a cookie to be able to recognize the start of the download
220
-	 * the content must not be longer than 32 characters and must only contain
221
-	 * alphanumeric characters
222
-	 *
223
-	 * @param RequestInterface $request
224
-	 * @param ResponseInterface $response
225
-	 */
226
-	public function handleDownloadToken(RequestInterface $request, ResponseInterface $response) {
227
-		$queryParams = $request->getQueryParameters();
228
-
229
-		/**
230
-		 * this sets a cookie to be able to recognize the start of the download
231
-		 * the content must not be longer than 32 characters and must only contain
232
-		 * alphanumeric characters
233
-		 */
234
-		if (isset($queryParams['downloadStartSecret'])) {
235
-			$token = $queryParams['downloadStartSecret'];
236
-			if (!isset($token[32])
237
-				&& preg_match('!^[a-zA-Z0-9]+$!', $token) === 1) {
238
-				// FIXME: use $response->setHeader() instead
239
-				setcookie('ocDownloadStarted', $token, time() + 20, '/');
240
-			}
241
-		}
242
-	}
243
-
244
-	/**
245
-	 * Add headers to file download
246
-	 *
247
-	 * @param RequestInterface $request
248
-	 * @param ResponseInterface $response
249
-	 */
250
-	public function httpGet(RequestInterface $request, ResponseInterface $response) {
251
-		// Only handle valid files
252
-		$node = $this->tree->getNodeForPath($request->getPath());
253
-		if (!($node instanceof IFile)) {
254
-			return;
255
-		}
256
-
257
-		// adds a 'Content-Disposition: attachment' header in case no disposition
258
-		// header has been set before
259
-		if ($this->downloadAttachment &&
260
-			$response->getHeader('Content-Disposition') === null) {
261
-			$filename = $node->getName();
262
-			if ($this->request->isUserAgent(
263
-				[
264
-					Request::USER_AGENT_IE,
265
-					Request::USER_AGENT_ANDROID_MOBILE_CHROME,
266
-					Request::USER_AGENT_FREEBOX,
267
-				])) {
268
-				$response->addHeader('Content-Disposition', 'attachment; filename="' . rawurlencode($filename) . '"');
269
-			} else {
270
-				$response->addHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . rawurlencode($filename)
271
-													 . '; filename="' . rawurlencode($filename) . '"');
272
-			}
273
-		}
274
-
275
-		if ($node instanceof File) {
276
-			//Add OC-Checksum header
277
-			$checksum = $node->getChecksum();
278
-			if ($checksum !== null && $checksum !== '') {
279
-				$response->addHeader('OC-Checksum', $checksum);
280
-			}
281
-		}
282
-		$response->addHeader('X-Accel-Buffering', 'no');
283
-	}
284
-
285
-	/**
286
-	 * Adds all ownCloud-specific properties
287
-	 *
288
-	 * @param PropFind $propFind
289
-	 * @param \Sabre\DAV\INode $node
290
-	 * @return void
291
-	 */
292
-	public function handleGetProperties(PropFind $propFind, \Sabre\DAV\INode $node) {
293
-		$httpRequest = $this->server->httpRequest;
294
-
295
-		if ($node instanceof Node) {
296
-			/**
297
-			 * This was disabled, because it made dir listing throw an exception,
298
-			 * so users were unable to navigate into folders where one subitem
299
-			 * is blocked by the files_accesscontrol app, see:
300
-			 * https://github.com/nextcloud/files_accesscontrol/issues/65
301
-			 * if (!$node->getFileInfo()->isReadable()) {
302
-			 *     // avoid detecting files through this means
303
-			 *     throw new NotFound();
304
-			 * }
305
-			 */
306
-
307
-			$propFind->handle(self::FILEID_PROPERTYNAME, function () use ($node) {
308
-				return $node->getFileId();
309
-			});
310
-
311
-			$propFind->handle(self::INTERNAL_FILEID_PROPERTYNAME, function () use ($node) {
312
-				return $node->getInternalFileId();
313
-			});
314
-
315
-			$propFind->handle(self::PERMISSIONS_PROPERTYNAME, function () use ($node) {
316
-				$perms = $node->getDavPermissions();
317
-				if ($this->isPublic) {
318
-					// remove mount information
319
-					$perms = str_replace(['S', 'M'], '', $perms);
320
-				}
321
-				return $perms;
322
-			});
323
-
324
-			$propFind->handle(self::SHARE_PERMISSIONS_PROPERTYNAME, function () use ($node, $httpRequest) {
325
-				$user = $this->userSession->getUser();
326
-				if ($user === null) {
327
-					return null;
328
-				}
329
-				return $node->getSharePermissions(
330
-					$user->getUID()
331
-				);
332
-			});
333
-
334
-			$propFind->handle(self::OCM_SHARE_PERMISSIONS_PROPERTYNAME, function () use ($node, $httpRequest): ?string {
335
-				$user = $this->userSession->getUser();
336
-				if ($user === null) {
337
-					return null;
338
-				}
339
-				$ncPermissions = $node->getSharePermissions(
340
-					$user->getUID()
341
-				);
342
-				$ocmPermissions = $this->ncPermissions2ocmPermissions($ncPermissions);
343
-				return json_encode($ocmPermissions, JSON_THROW_ON_ERROR);
344
-			});
345
-
346
-			$propFind->handle(self::SHARE_ATTRIBUTES_PROPERTYNAME, function () use ($node, $httpRequest) {
347
-				return json_encode($node->getShareAttributes(), JSON_THROW_ON_ERROR);
348
-			});
349
-
350
-			$propFind->handle(self::GETETAG_PROPERTYNAME, function () use ($node): string {
351
-				return $node->getETag();
352
-			});
353
-
354
-			$propFind->handle(self::OWNER_ID_PROPERTYNAME, function () use ($node): ?string {
355
-				$owner = $node->getOwner();
356
-				if (!$owner) {
357
-					return null;
358
-				} else {
359
-					return $owner->getUID();
360
-				}
361
-			});
362
-			$propFind->handle(self::OWNER_DISPLAY_NAME_PROPERTYNAME, function () use ($node): ?string {
363
-				$owner = $node->getOwner();
364
-				if (!$owner) {
365
-					return null;
366
-				}
367
-
368
-				// Get current user to see if we're in a public share or not
369
-				$user = $this->userSession->getUser();
370
-
371
-				// If the user is logged in, we can return the display name
372
-				if ($user !== null) {
373
-					return $owner->getDisplayName();
374
-				}
375
-
376
-				// Check if the user published their display name
377
-				$ownerAccount = $this->accountManager->getAccount($owner);
378
-				$ownerNameProperty = $ownerAccount->getProperty(IAccountManager::PROPERTY_DISPLAYNAME);
379
-
380
-				// Since we are not logged in, we need to have at least the published scope
381
-				if ($ownerNameProperty->getScope() === IAccountManager::SCOPE_PUBLISHED) {
382
-					return $owner->getDisplayName();
383
-				}
384
-
385
-				return null;
386
-			});
387
-
388
-			$propFind->handle(self::HAS_PREVIEW_PROPERTYNAME, function () use ($node) {
389
-				return json_encode($this->previewManager->isAvailable($node->getFileInfo()), JSON_THROW_ON_ERROR);
390
-			});
391
-			$propFind->handle(self::SIZE_PROPERTYNAME, function () use ($node): int|float {
392
-				return $node->getSize();
393
-			});
394
-			$propFind->handle(self::MOUNT_TYPE_PROPERTYNAME, function () use ($node) {
395
-				return $node->getFileInfo()->getMountPoint()->getMountType();
396
-			});
397
-
398
-			/**
399
-			 * This is a special property which is used to determine if a node
400
-			 * is a mount root or not, e.g. a shared folder.
401
-			 * If so, then the node can only be unshared and not deleted.
402
-			 * @see https://github.com/nextcloud/server/blob/cc75294eb6b16b916a342e69998935f89222619d/lib/private/Files/View.php#L696-L698
403
-			 */
404
-			$propFind->handle(self::MOUNT_ROOT_PROPERTYNAME, function () use ($node) {
405
-				return $node->getNode()->getInternalPath() === '' ? 'true' : 'false';
406
-			});
407
-
408
-			$propFind->handle(self::SHARE_NOTE, function () use ($node): ?string {
409
-				$user = $this->userSession->getUser();
410
-				return $node->getNoteFromShare(
411
-					$user?->getUID()
412
-				);
413
-			});
414
-
415
-			$propFind->handle(self::SHARE_HIDE_DOWNLOAD_PROPERTYNAME, function () use ($node) {
416
-				$storage = $node->getNode()->getStorage();
417
-				if ($storage->instanceOfStorage(ISharedStorage::class)) {
418
-					/** @var ISharedStorage $storage */
419
-					return match($storage->getShare()->getHideDownload()) {
420
-						true => 'true',
421
-						false => 'false',
422
-					};
423
-				} else {
424
-					return null;
425
-				}
426
-			});
427
-
428
-			$propFind->handle(self::DATA_FINGERPRINT_PROPERTYNAME, function () {
429
-				return $this->config->getSystemValue('data-fingerprint', '');
430
-			});
431
-			$propFind->handle(self::CREATIONDATE_PROPERTYNAME, function () use ($node) {
432
-				return (new \DateTimeImmutable())
433
-					->setTimestamp($node->getFileInfo()->getCreationTime())
434
-					->format(\DateTimeInterface::ATOM);
435
-			});
436
-			$propFind->handle(self::CREATION_TIME_PROPERTYNAME, function () use ($node) {
437
-				return $node->getFileInfo()->getCreationTime();
438
-			});
439
-
440
-			foreach ($node->getFileInfo()->getMetadata() as $metadataKey => $metadataValue) {
441
-				$propFind->handle(self::FILE_METADATA_PREFIX . $metadataKey, $metadataValue);
442
-			}
443
-
444
-			$propFind->handle(self::HIDDEN_PROPERTYNAME, function () use ($node) {
445
-				$isLivePhoto = isset($node->getFileInfo()->getMetadata()['files-live-photo']);
446
-				$isMovFile = $node->getFileInfo()->getMimetype() === 'video/quicktime';
447
-				return ($isLivePhoto && $isMovFile) ? 'true' : 'false';
448
-			});
449
-
450
-			/**
451
-			 * Return file/folder name as displayname. The primary reason to
452
-			 * implement it this way is to avoid costly fallback to
453
-			 * CustomPropertiesBackend (esp. visible when querying all files
454
-			 * in a folder).
455
-			 */
456
-			$propFind->handle(self::DISPLAYNAME_PROPERTYNAME, function () use ($node) {
457
-				return $node->getName();
458
-			});
459
-
460
-			$propFind->handle(self::IS_FEDERATED_PROPERTYNAME, function () use ($node) {
461
-				return $node->getFileInfo()->getMountPoint()
462
-					instanceof SharingExternalMount;
463
-			});
464
-		}
465
-
466
-		if ($node instanceof File) {
467
-			$propFind->handle(self::DOWNLOADURL_PROPERTYNAME, function () use ($node) {
468
-				try {
469
-					$directDownloadUrl = $node->getDirectDownload();
470
-					if (isset($directDownloadUrl['url'])) {
471
-						return $directDownloadUrl['url'];
472
-					}
473
-				} catch (StorageNotAvailableException $e) {
474
-					return false;
475
-				} catch (ForbiddenException $e) {
476
-					return false;
477
-				}
478
-				return false;
479
-			});
480
-
481
-			$propFind->handle(self::CHECKSUMS_PROPERTYNAME, function () use ($node) {
482
-				$checksum = $node->getChecksum();
483
-				if ($checksum === null || $checksum === '') {
484
-					return null;
485
-				}
486
-
487
-				return new ChecksumList($checksum);
488
-			});
489
-
490
-			$propFind->handle(self::UPLOAD_TIME_PROPERTYNAME, function () use ($node) {
491
-				return $node->getFileInfo()->getUploadTime();
492
-			});
493
-		}
494
-
495
-		if ($node instanceof Directory) {
496
-			$propFind->handle(self::SIZE_PROPERTYNAME, function () use ($node) {
497
-				return $node->getSize();
498
-			});
499
-
500
-			$requestProperties = $propFind->getRequestedProperties();
501
-
502
-			if (in_array(self::SUBFILE_COUNT_PROPERTYNAME, $requestProperties, true)
503
-				|| in_array(self::SUBFOLDER_COUNT_PROPERTYNAME, $requestProperties, true)) {
504
-				$nbFiles = 0;
505
-				$nbFolders = 0;
506
-				foreach ($node->getChildren() as $child) {
507
-					if ($child instanceof File) {
508
-						$nbFiles++;
509
-					} elseif ($child instanceof Directory) {
510
-						$nbFolders++;
511
-					}
512
-				}
513
-
514
-				$propFind->handle(self::SUBFILE_COUNT_PROPERTYNAME, $nbFiles);
515
-				$propFind->handle(self::SUBFOLDER_COUNT_PROPERTYNAME, $nbFolders);
516
-			}
517
-		}
518
-	}
519
-
520
-	/**
521
-	 * translate Nextcloud permissions to OCM Permissions
522
-	 *
523
-	 * @param $ncPermissions
524
-	 * @return array
525
-	 */
526
-	protected function ncPermissions2ocmPermissions($ncPermissions) {
527
-		$ocmPermissions = [];
528
-
529
-		if ($ncPermissions & Constants::PERMISSION_SHARE) {
530
-			$ocmPermissions[] = 'share';
531
-		}
532
-
533
-		if ($ncPermissions & Constants::PERMISSION_READ) {
534
-			$ocmPermissions[] = 'read';
535
-		}
536
-
537
-		if (($ncPermissions & Constants::PERMISSION_CREATE) ||
538
-			($ncPermissions & Constants::PERMISSION_UPDATE)) {
539
-			$ocmPermissions[] = 'write';
540
-		}
541
-
542
-		return $ocmPermissions;
543
-	}
544
-
545
-	/**
546
-	 * Update ownCloud-specific properties
547
-	 *
548
-	 * @param string $path
549
-	 * @param PropPatch $propPatch
550
-	 *
551
-	 * @return void
552
-	 */
553
-	public function handleUpdateProperties($path, PropPatch $propPatch) {
554
-		$node = $this->tree->getNodeForPath($path);
555
-		if (!($node instanceof Node)) {
556
-			return;
557
-		}
558
-
559
-		$propPatch->handle(self::LASTMODIFIED_PROPERTYNAME, function ($time) use ($node) {
560
-			if (empty($time)) {
561
-				return false;
562
-			}
563
-			$node->touch($time);
564
-			return true;
565
-		});
566
-		$propPatch->handle(self::GETETAG_PROPERTYNAME, function ($etag) use ($node) {
567
-			if (empty($etag)) {
568
-				return false;
569
-			}
570
-			return $node->setEtag($etag) !== -1;
571
-		});
572
-		$propPatch->handle(self::CREATIONDATE_PROPERTYNAME, function ($time) use ($node) {
573
-			if (empty($time)) {
574
-				return false;
575
-			}
576
-			$dateTime = new \DateTimeImmutable($time);
577
-			$node->setCreationTime($dateTime->getTimestamp());
578
-			return true;
579
-		});
580
-		$propPatch->handle(self::CREATION_TIME_PROPERTYNAME, function ($time) use ($node) {
581
-			if (empty($time)) {
582
-				return false;
583
-			}
584
-			$node->setCreationTime((int)$time);
585
-			return true;
586
-		});
587
-
588
-		$this->handleUpdatePropertiesMetadata($propPatch, $node);
589
-
590
-		/**
591
-		 * Disable modification of the displayname property for files and
592
-		 * folders via PROPPATCH. See PROPFIND for more information.
593
-		 */
594
-		$propPatch->handle(self::DISPLAYNAME_PROPERTYNAME, function ($displayName) {
595
-			return 403;
596
-		});
597
-	}
598
-
599
-
600
-	/**
601
-	 * handle the update of metadata from PROPPATCH requests
602
-	 *
603
-	 * @param PropPatch $propPatch
604
-	 * @param Node $node
605
-	 *
606
-	 * @throws FilesMetadataException
607
-	 */
608
-	private function handleUpdatePropertiesMetadata(PropPatch $propPatch, Node $node): void {
609
-		$userId = $this->userSession->getUser()?->getUID();
610
-		if ($userId === null) {
611
-			return;
612
-		}
613
-
614
-		$accessRight = $this->getMetadataFileAccessRight($node, $userId);
615
-		$filesMetadataManager = $this->initFilesMetadataManager();
616
-		$knownMetadata = $filesMetadataManager->getKnownMetadata();
617
-
618
-		foreach ($propPatch->getRemainingMutations() as $mutation) {
619
-			if (!str_starts_with($mutation, self::FILE_METADATA_PREFIX)) {
620
-				continue;
621
-			}
622
-
623
-			$propPatch->handle(
624
-				$mutation,
625
-				function (mixed $value) use ($accessRight, $knownMetadata, $node, $mutation, $filesMetadataManager): bool {
626
-					/** @var FilesMetadata $metadata */
627
-					$metadata = $filesMetadataManager->getMetadata((int)$node->getFileId(), true);
628
-					$metadata->setStorageId($node->getNode()->getStorage()->getCache()->getNumericStorageId());
629
-					$metadataKey = substr($mutation, strlen(self::FILE_METADATA_PREFIX));
630
-
631
-					// confirm metadata key is editable via PROPPATCH
632
-					if ($knownMetadata->getEditPermission($metadataKey) < $accessRight) {
633
-						throw new FilesMetadataException('you do not have enough rights to update \'' . $metadataKey . '\' on this node');
634
-					}
635
-
636
-					if ($value === null) {
637
-						$metadata->unset($metadataKey);
638
-						$filesMetadataManager->saveMetadata($metadata);
639
-						return true;
640
-					}
641
-
642
-					// If the metadata is unknown, it defaults to string.
643
-					try {
644
-						$type = $knownMetadata->getType($metadataKey);
645
-					} catch (FilesMetadataNotFoundException) {
646
-						$type = IMetadataValueWrapper::TYPE_STRING;
647
-					}
648
-
649
-					switch ($type) {
650
-						case IMetadataValueWrapper::TYPE_STRING:
651
-							$metadata->setString($metadataKey, $value, $knownMetadata->isIndex($metadataKey));
652
-							break;
653
-						case IMetadataValueWrapper::TYPE_INT:
654
-							$metadata->setInt($metadataKey, $value, $knownMetadata->isIndex($metadataKey));
655
-							break;
656
-						case IMetadataValueWrapper::TYPE_FLOAT:
657
-							$metadata->setFloat($metadataKey, $value);
658
-							break;
659
-						case IMetadataValueWrapper::TYPE_BOOL:
660
-							$metadata->setBool($metadataKey, $value, $knownMetadata->isIndex($metadataKey));
661
-							break;
662
-						case IMetadataValueWrapper::TYPE_ARRAY:
663
-							$metadata->setArray($metadataKey, $value);
664
-							break;
665
-						case IMetadataValueWrapper::TYPE_STRING_LIST:
666
-							$metadata->setStringList($metadataKey, $value, $knownMetadata->isIndex($metadataKey));
667
-							break;
668
-						case IMetadataValueWrapper::TYPE_INT_LIST:
669
-							$metadata->setIntList($metadataKey, $value, $knownMetadata->isIndex($metadataKey));
670
-							break;
671
-					}
672
-
673
-					$filesMetadataManager->saveMetadata($metadata);
674
-
675
-					return true;
676
-				}
677
-			);
678
-		}
679
-	}
680
-
681
-	/**
682
-	 * init default internal metadata
683
-	 *
684
-	 * @return IFilesMetadataManager
685
-	 */
686
-	private function initFilesMetadataManager(): IFilesMetadataManager {
687
-		/** @var IFilesMetadataManager $manager */
688
-		$manager = \OCP\Server::get(IFilesMetadataManager::class);
689
-		$manager->initMetadata('files-live-photo', IMetadataValueWrapper::TYPE_STRING, false, IMetadataValueWrapper::EDIT_REQ_OWNERSHIP);
690
-
691
-		return $manager;
692
-	}
693
-
694
-	/**
695
-	 * based on owner and shares, returns the bottom limit to update related metadata
696
-	 *
697
-	 * @param Node $node
698
-	 * @param string $userId
699
-	 *
700
-	 * @return int
701
-	 */
702
-	private function getMetadataFileAccessRight(Node $node, string $userId): int {
703
-		if ($node->getOwner()?->getUID() === $userId) {
704
-			return IMetadataValueWrapper::EDIT_REQ_OWNERSHIP;
705
-		} else {
706
-			$filePermissions = $node->getSharePermissions($userId);
707
-			if ($filePermissions & Constants::PERMISSION_UPDATE) {
708
-				return IMetadataValueWrapper::EDIT_REQ_WRITE_PERMISSION;
709
-			}
710
-		}
711
-
712
-		return IMetadataValueWrapper::EDIT_REQ_READ_PERMISSION;
713
-	}
714
-
715
-	/**
716
-	 * @param string $filePath
717
-	 * @param ?\Sabre\DAV\INode $node
718
-	 * @return void
719
-	 * @throws \Sabre\DAV\Exception\BadRequest
720
-	 */
721
-	public function sendFileIdHeader($filePath, ?\Sabre\DAV\INode $node = null) {
722
-		// we get the node for the given $filePath here because in case of afterCreateFile $node is the parent folder
723
-		if (!$this->server->tree->nodeExists($filePath)) {
724
-			return;
725
-		}
726
-		$node = $this->server->tree->getNodeForPath($filePath);
727
-		if ($node instanceof Node) {
728
-			$fileId = $node->getFileId();
729
-			if (!is_null($fileId)) {
730
-				$this->server->httpResponse->setHeader('OC-FileId', $fileId);
731
-			}
732
-		}
733
-	}
42
+    // namespace
43
+    public const NS_OWNCLOUD = 'http://owncloud.org/ns';
44
+    public const NS_NEXTCLOUD = 'http://nextcloud.org/ns';
45
+    public const FILEID_PROPERTYNAME = '{http://owncloud.org/ns}id';
46
+    public const INTERNAL_FILEID_PROPERTYNAME = '{http://owncloud.org/ns}fileid';
47
+    public const PERMISSIONS_PROPERTYNAME = '{http://owncloud.org/ns}permissions';
48
+    public const SHARE_PERMISSIONS_PROPERTYNAME = '{http://open-collaboration-services.org/ns}share-permissions';
49
+    public const OCM_SHARE_PERMISSIONS_PROPERTYNAME = '{http://open-cloud-mesh.org/ns}share-permissions';
50
+    public const SHARE_ATTRIBUTES_PROPERTYNAME = '{http://nextcloud.org/ns}share-attributes';
51
+    public const DOWNLOADURL_PROPERTYNAME = '{http://owncloud.org/ns}downloadURL';
52
+    public const SIZE_PROPERTYNAME = '{http://owncloud.org/ns}size';
53
+    public const GETETAG_PROPERTYNAME = '{DAV:}getetag';
54
+    public const LASTMODIFIED_PROPERTYNAME = '{DAV:}lastmodified';
55
+    public const CREATIONDATE_PROPERTYNAME = '{DAV:}creationdate';
56
+    public const DISPLAYNAME_PROPERTYNAME = '{DAV:}displayname';
57
+    public const OWNER_ID_PROPERTYNAME = '{http://owncloud.org/ns}owner-id';
58
+    public const OWNER_DISPLAY_NAME_PROPERTYNAME = '{http://owncloud.org/ns}owner-display-name';
59
+    public const CHECKSUMS_PROPERTYNAME = '{http://owncloud.org/ns}checksums';
60
+    public const DATA_FINGERPRINT_PROPERTYNAME = '{http://owncloud.org/ns}data-fingerprint';
61
+    public const HAS_PREVIEW_PROPERTYNAME = '{http://nextcloud.org/ns}has-preview';
62
+    public const MOUNT_TYPE_PROPERTYNAME = '{http://nextcloud.org/ns}mount-type';
63
+    public const MOUNT_ROOT_PROPERTYNAME = '{http://nextcloud.org/ns}is-mount-root';
64
+    public const IS_FEDERATED_PROPERTYNAME = '{http://nextcloud.org/ns}is-federated';
65
+    public const METADATA_ETAG_PROPERTYNAME = '{http://nextcloud.org/ns}metadata_etag';
66
+    public const UPLOAD_TIME_PROPERTYNAME = '{http://nextcloud.org/ns}upload_time';
67
+    public const CREATION_TIME_PROPERTYNAME = '{http://nextcloud.org/ns}creation_time';
68
+    public const SHARE_NOTE = '{http://nextcloud.org/ns}note';
69
+    public const SHARE_HIDE_DOWNLOAD_PROPERTYNAME = '{http://nextcloud.org/ns}hide-download';
70
+    public const SUBFOLDER_COUNT_PROPERTYNAME = '{http://nextcloud.org/ns}contained-folder-count';
71
+    public const SUBFILE_COUNT_PROPERTYNAME = '{http://nextcloud.org/ns}contained-file-count';
72
+    public const FILE_METADATA_PREFIX = '{http://nextcloud.org/ns}metadata-';
73
+    public const HIDDEN_PROPERTYNAME = '{http://nextcloud.org/ns}hidden';
74
+
75
+    /** Reference to main server object */
76
+    private ?Server $server = null;
77
+
78
+    /**
79
+     * @param Tree $tree
80
+     * @param IConfig $config
81
+     * @param IRequest $request
82
+     * @param IPreview $previewManager
83
+     * @param IUserSession $userSession
84
+     * @param bool $isPublic Whether this is public WebDAV. If true, some returned information will be stripped off.
85
+     * @param bool $downloadAttachment
86
+     * @return void
87
+     */
88
+    public function __construct(
89
+        private Tree $tree,
90
+        private IConfig $config,
91
+        private IRequest $request,
92
+        private IPreview $previewManager,
93
+        private IUserSession $userSession,
94
+        private IFilenameValidator $validator,
95
+        private IAccountManager $accountManager,
96
+        private bool $isPublic = false,
97
+        private bool $downloadAttachment = true,
98
+    ) {
99
+    }
100
+
101
+    /**
102
+     * This initializes the plugin.
103
+     *
104
+     * This function is called by \Sabre\DAV\Server, after
105
+     * addPlugin is called.
106
+     *
107
+     * This method should set up the required event subscriptions.
108
+     *
109
+     * @return void
110
+     */
111
+    public function initialize(Server $server) {
112
+        $server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
113
+        $server->xml->namespaceMap[self::NS_NEXTCLOUD] = 'nc';
114
+        $server->protectedProperties[] = self::FILEID_PROPERTYNAME;
115
+        $server->protectedProperties[] = self::INTERNAL_FILEID_PROPERTYNAME;
116
+        $server->protectedProperties[] = self::PERMISSIONS_PROPERTYNAME;
117
+        $server->protectedProperties[] = self::SHARE_PERMISSIONS_PROPERTYNAME;
118
+        $server->protectedProperties[] = self::OCM_SHARE_PERMISSIONS_PROPERTYNAME;
119
+        $server->protectedProperties[] = self::SHARE_ATTRIBUTES_PROPERTYNAME;
120
+        $server->protectedProperties[] = self::SIZE_PROPERTYNAME;
121
+        $server->protectedProperties[] = self::DOWNLOADURL_PROPERTYNAME;
122
+        $server->protectedProperties[] = self::OWNER_ID_PROPERTYNAME;
123
+        $server->protectedProperties[] = self::OWNER_DISPLAY_NAME_PROPERTYNAME;
124
+        $server->protectedProperties[] = self::CHECKSUMS_PROPERTYNAME;
125
+        $server->protectedProperties[] = self::DATA_FINGERPRINT_PROPERTYNAME;
126
+        $server->protectedProperties[] = self::HAS_PREVIEW_PROPERTYNAME;
127
+        $server->protectedProperties[] = self::MOUNT_TYPE_PROPERTYNAME;
128
+        $server->protectedProperties[] = self::IS_FEDERATED_PROPERTYNAME;
129
+        $server->protectedProperties[] = self::SHARE_NOTE;
130
+
131
+        // normally these cannot be changed (RFC4918), but we want them modifiable through PROPPATCH
132
+        $allowedProperties = ['{DAV:}getetag'];
133
+        $server->protectedProperties = array_diff($server->protectedProperties, $allowedProperties);
134
+
135
+        $this->server = $server;
136
+        $this->server->on('propFind', [$this, 'handleGetProperties']);
137
+        $this->server->on('propPatch', [$this, 'handleUpdateProperties']);
138
+        $this->server->on('afterBind', [$this, 'sendFileIdHeader']);
139
+        $this->server->on('afterWriteContent', [$this, 'sendFileIdHeader']);
140
+        $this->server->on('afterMethod:GET', [$this,'httpGet']);
141
+        $this->server->on('afterMethod:GET', [$this, 'handleDownloadToken']);
142
+        $this->server->on('afterResponse', function ($request, ResponseInterface $response): void {
143
+            $body = $response->getBody();
144
+            if (is_resource($body)) {
145
+                fclose($body);
146
+            }
147
+        });
148
+        $this->server->on('beforeMove', [$this, 'checkMove']);
149
+        $this->server->on('beforeCopy', [$this, 'checkCopy']);
150
+    }
151
+
152
+    /**
153
+     * Plugin that checks if a copy can actually be performed.
154
+     *
155
+     * @param string $source source path
156
+     * @param string $target target path
157
+     * @throws NotFound If the source does not exist
158
+     * @throws InvalidPath If the target is invalid
159
+     */
160
+    public function checkCopy($source, $target): void {
161
+        $sourceNode = $this->tree->getNodeForPath($source);
162
+        if (!$sourceNode instanceof Node) {
163
+            return;
164
+        }
165
+
166
+        // Ensure source exists
167
+        $sourceNodeFileInfo = $sourceNode->getFileInfo();
168
+        if ($sourceNodeFileInfo === null) {
169
+            throw new NotFound($source . ' does not exist');
170
+        }
171
+        // Ensure the target name is valid
172
+        try {
173
+            [$targetPath, $targetName] = \Sabre\Uri\split($target);
174
+            $this->validator->validateFilename($targetName);
175
+        } catch (InvalidPathException $e) {
176
+            throw new InvalidPath($e->getMessage(), false);
177
+        }
178
+        // Ensure the target path is valid
179
+        $segments = array_slice(explode('/', $targetPath), 2);
180
+        foreach ($segments as $segment) {
181
+            if ($this->validator->isFilenameValid($segment) === false) {
182
+                $l = \OCP\Server::get(IFactory::class)->get('dav');
183
+                throw new InvalidPath($l->t('Invalid target path'));
184
+            }
185
+        }
186
+    }
187
+
188
+    /**
189
+     * Plugin that checks if a move can actually be performed.
190
+     *
191
+     * @param string $source source path
192
+     * @param string $target target path
193
+     * @throws Forbidden If the source is not deletable
194
+     * @throws NotFound If the source does not exist
195
+     * @throws InvalidPath If the target name is invalid
196
+     */
197
+    public function checkMove(string $source, string $target): void {
198
+        $sourceNode = $this->tree->getNodeForPath($source);
199
+        if (!$sourceNode instanceof Node) {
200
+            return;
201
+        }
202
+
203
+        // First check copyable (move only needs additional delete permission)
204
+        $this->checkCopy($source, $target);
205
+
206
+        // The source needs to be deletable for moving
207
+        $sourceNodeFileInfo = $sourceNode->getFileInfo();
208
+        if (!$sourceNodeFileInfo->isDeletable()) {
209
+            throw new Forbidden($source . ' cannot be deleted');
210
+        }
211
+
212
+        // The source is not allowed to be the parent of the target
213
+        if (str_starts_with($source, $target . '/')) {
214
+            throw new Forbidden($source . ' cannot be moved to it\'s parent');
215
+        }
216
+    }
217
+
218
+    /**
219
+     * This sets a cookie to be able to recognize the start of the download
220
+     * the content must not be longer than 32 characters and must only contain
221
+     * alphanumeric characters
222
+     *
223
+     * @param RequestInterface $request
224
+     * @param ResponseInterface $response
225
+     */
226
+    public function handleDownloadToken(RequestInterface $request, ResponseInterface $response) {
227
+        $queryParams = $request->getQueryParameters();
228
+
229
+        /**
230
+         * this sets a cookie to be able to recognize the start of the download
231
+         * the content must not be longer than 32 characters and must only contain
232
+         * alphanumeric characters
233
+         */
234
+        if (isset($queryParams['downloadStartSecret'])) {
235
+            $token = $queryParams['downloadStartSecret'];
236
+            if (!isset($token[32])
237
+                && preg_match('!^[a-zA-Z0-9]+$!', $token) === 1) {
238
+                // FIXME: use $response->setHeader() instead
239
+                setcookie('ocDownloadStarted', $token, time() + 20, '/');
240
+            }
241
+        }
242
+    }
243
+
244
+    /**
245
+     * Add headers to file download
246
+     *
247
+     * @param RequestInterface $request
248
+     * @param ResponseInterface $response
249
+     */
250
+    public function httpGet(RequestInterface $request, ResponseInterface $response) {
251
+        // Only handle valid files
252
+        $node = $this->tree->getNodeForPath($request->getPath());
253
+        if (!($node instanceof IFile)) {
254
+            return;
255
+        }
256
+
257
+        // adds a 'Content-Disposition: attachment' header in case no disposition
258
+        // header has been set before
259
+        if ($this->downloadAttachment &&
260
+            $response->getHeader('Content-Disposition') === null) {
261
+            $filename = $node->getName();
262
+            if ($this->request->isUserAgent(
263
+                [
264
+                    Request::USER_AGENT_IE,
265
+                    Request::USER_AGENT_ANDROID_MOBILE_CHROME,
266
+                    Request::USER_AGENT_FREEBOX,
267
+                ])) {
268
+                $response->addHeader('Content-Disposition', 'attachment; filename="' . rawurlencode($filename) . '"');
269
+            } else {
270
+                $response->addHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . rawurlencode($filename)
271
+                                                        . '; filename="' . rawurlencode($filename) . '"');
272
+            }
273
+        }
274
+
275
+        if ($node instanceof File) {
276
+            //Add OC-Checksum header
277
+            $checksum = $node->getChecksum();
278
+            if ($checksum !== null && $checksum !== '') {
279
+                $response->addHeader('OC-Checksum', $checksum);
280
+            }
281
+        }
282
+        $response->addHeader('X-Accel-Buffering', 'no');
283
+    }
284
+
285
+    /**
286
+     * Adds all ownCloud-specific properties
287
+     *
288
+     * @param PropFind $propFind
289
+     * @param \Sabre\DAV\INode $node
290
+     * @return void
291
+     */
292
+    public function handleGetProperties(PropFind $propFind, \Sabre\DAV\INode $node) {
293
+        $httpRequest = $this->server->httpRequest;
294
+
295
+        if ($node instanceof Node) {
296
+            /**
297
+             * This was disabled, because it made dir listing throw an exception,
298
+             * so users were unable to navigate into folders where one subitem
299
+             * is blocked by the files_accesscontrol app, see:
300
+             * https://github.com/nextcloud/files_accesscontrol/issues/65
301
+             * if (!$node->getFileInfo()->isReadable()) {
302
+             *     // avoid detecting files through this means
303
+             *     throw new NotFound();
304
+             * }
305
+             */
306
+
307
+            $propFind->handle(self::FILEID_PROPERTYNAME, function () use ($node) {
308
+                return $node->getFileId();
309
+            });
310
+
311
+            $propFind->handle(self::INTERNAL_FILEID_PROPERTYNAME, function () use ($node) {
312
+                return $node->getInternalFileId();
313
+            });
314
+
315
+            $propFind->handle(self::PERMISSIONS_PROPERTYNAME, function () use ($node) {
316
+                $perms = $node->getDavPermissions();
317
+                if ($this->isPublic) {
318
+                    // remove mount information
319
+                    $perms = str_replace(['S', 'M'], '', $perms);
320
+                }
321
+                return $perms;
322
+            });
323
+
324
+            $propFind->handle(self::SHARE_PERMISSIONS_PROPERTYNAME, function () use ($node, $httpRequest) {
325
+                $user = $this->userSession->getUser();
326
+                if ($user === null) {
327
+                    return null;
328
+                }
329
+                return $node->getSharePermissions(
330
+                    $user->getUID()
331
+                );
332
+            });
333
+
334
+            $propFind->handle(self::OCM_SHARE_PERMISSIONS_PROPERTYNAME, function () use ($node, $httpRequest): ?string {
335
+                $user = $this->userSession->getUser();
336
+                if ($user === null) {
337
+                    return null;
338
+                }
339
+                $ncPermissions = $node->getSharePermissions(
340
+                    $user->getUID()
341
+                );
342
+                $ocmPermissions = $this->ncPermissions2ocmPermissions($ncPermissions);
343
+                return json_encode($ocmPermissions, JSON_THROW_ON_ERROR);
344
+            });
345
+
346
+            $propFind->handle(self::SHARE_ATTRIBUTES_PROPERTYNAME, function () use ($node, $httpRequest) {
347
+                return json_encode($node->getShareAttributes(), JSON_THROW_ON_ERROR);
348
+            });
349
+
350
+            $propFind->handle(self::GETETAG_PROPERTYNAME, function () use ($node): string {
351
+                return $node->getETag();
352
+            });
353
+
354
+            $propFind->handle(self::OWNER_ID_PROPERTYNAME, function () use ($node): ?string {
355
+                $owner = $node->getOwner();
356
+                if (!$owner) {
357
+                    return null;
358
+                } else {
359
+                    return $owner->getUID();
360
+                }
361
+            });
362
+            $propFind->handle(self::OWNER_DISPLAY_NAME_PROPERTYNAME, function () use ($node): ?string {
363
+                $owner = $node->getOwner();
364
+                if (!$owner) {
365
+                    return null;
366
+                }
367
+
368
+                // Get current user to see if we're in a public share or not
369
+                $user = $this->userSession->getUser();
370
+
371
+                // If the user is logged in, we can return the display name
372
+                if ($user !== null) {
373
+                    return $owner->getDisplayName();
374
+                }
375
+
376
+                // Check if the user published their display name
377
+                $ownerAccount = $this->accountManager->getAccount($owner);
378
+                $ownerNameProperty = $ownerAccount->getProperty(IAccountManager::PROPERTY_DISPLAYNAME);
379
+
380
+                // Since we are not logged in, we need to have at least the published scope
381
+                if ($ownerNameProperty->getScope() === IAccountManager::SCOPE_PUBLISHED) {
382
+                    return $owner->getDisplayName();
383
+                }
384
+
385
+                return null;
386
+            });
387
+
388
+            $propFind->handle(self::HAS_PREVIEW_PROPERTYNAME, function () use ($node) {
389
+                return json_encode($this->previewManager->isAvailable($node->getFileInfo()), JSON_THROW_ON_ERROR);
390
+            });
391
+            $propFind->handle(self::SIZE_PROPERTYNAME, function () use ($node): int|float {
392
+                return $node->getSize();
393
+            });
394
+            $propFind->handle(self::MOUNT_TYPE_PROPERTYNAME, function () use ($node) {
395
+                return $node->getFileInfo()->getMountPoint()->getMountType();
396
+            });
397
+
398
+            /**
399
+             * This is a special property which is used to determine if a node
400
+             * is a mount root or not, e.g. a shared folder.
401
+             * If so, then the node can only be unshared and not deleted.
402
+             * @see https://github.com/nextcloud/server/blob/cc75294eb6b16b916a342e69998935f89222619d/lib/private/Files/View.php#L696-L698
403
+             */
404
+            $propFind->handle(self::MOUNT_ROOT_PROPERTYNAME, function () use ($node) {
405
+                return $node->getNode()->getInternalPath() === '' ? 'true' : 'false';
406
+            });
407
+
408
+            $propFind->handle(self::SHARE_NOTE, function () use ($node): ?string {
409
+                $user = $this->userSession->getUser();
410
+                return $node->getNoteFromShare(
411
+                    $user?->getUID()
412
+                );
413
+            });
414
+
415
+            $propFind->handle(self::SHARE_HIDE_DOWNLOAD_PROPERTYNAME, function () use ($node) {
416
+                $storage = $node->getNode()->getStorage();
417
+                if ($storage->instanceOfStorage(ISharedStorage::class)) {
418
+                    /** @var ISharedStorage $storage */
419
+                    return match($storage->getShare()->getHideDownload()) {
420
+                        true => 'true',
421
+                        false => 'false',
422
+                    };
423
+                } else {
424
+                    return null;
425
+                }
426
+            });
427
+
428
+            $propFind->handle(self::DATA_FINGERPRINT_PROPERTYNAME, function () {
429
+                return $this->config->getSystemValue('data-fingerprint', '');
430
+            });
431
+            $propFind->handle(self::CREATIONDATE_PROPERTYNAME, function () use ($node) {
432
+                return (new \DateTimeImmutable())
433
+                    ->setTimestamp($node->getFileInfo()->getCreationTime())
434
+                    ->format(\DateTimeInterface::ATOM);
435
+            });
436
+            $propFind->handle(self::CREATION_TIME_PROPERTYNAME, function () use ($node) {
437
+                return $node->getFileInfo()->getCreationTime();
438
+            });
439
+
440
+            foreach ($node->getFileInfo()->getMetadata() as $metadataKey => $metadataValue) {
441
+                $propFind->handle(self::FILE_METADATA_PREFIX . $metadataKey, $metadataValue);
442
+            }
443
+
444
+            $propFind->handle(self::HIDDEN_PROPERTYNAME, function () use ($node) {
445
+                $isLivePhoto = isset($node->getFileInfo()->getMetadata()['files-live-photo']);
446
+                $isMovFile = $node->getFileInfo()->getMimetype() === 'video/quicktime';
447
+                return ($isLivePhoto && $isMovFile) ? 'true' : 'false';
448
+            });
449
+
450
+            /**
451
+             * Return file/folder name as displayname. The primary reason to
452
+             * implement it this way is to avoid costly fallback to
453
+             * CustomPropertiesBackend (esp. visible when querying all files
454
+             * in a folder).
455
+             */
456
+            $propFind->handle(self::DISPLAYNAME_PROPERTYNAME, function () use ($node) {
457
+                return $node->getName();
458
+            });
459
+
460
+            $propFind->handle(self::IS_FEDERATED_PROPERTYNAME, function () use ($node) {
461
+                return $node->getFileInfo()->getMountPoint()
462
+                    instanceof SharingExternalMount;
463
+            });
464
+        }
465
+
466
+        if ($node instanceof File) {
467
+            $propFind->handle(self::DOWNLOADURL_PROPERTYNAME, function () use ($node) {
468
+                try {
469
+                    $directDownloadUrl = $node->getDirectDownload();
470
+                    if (isset($directDownloadUrl['url'])) {
471
+                        return $directDownloadUrl['url'];
472
+                    }
473
+                } catch (StorageNotAvailableException $e) {
474
+                    return false;
475
+                } catch (ForbiddenException $e) {
476
+                    return false;
477
+                }
478
+                return false;
479
+            });
480
+
481
+            $propFind->handle(self::CHECKSUMS_PROPERTYNAME, function () use ($node) {
482
+                $checksum = $node->getChecksum();
483
+                if ($checksum === null || $checksum === '') {
484
+                    return null;
485
+                }
486
+
487
+                return new ChecksumList($checksum);
488
+            });
489
+
490
+            $propFind->handle(self::UPLOAD_TIME_PROPERTYNAME, function () use ($node) {
491
+                return $node->getFileInfo()->getUploadTime();
492
+            });
493
+        }
494
+
495
+        if ($node instanceof Directory) {
496
+            $propFind->handle(self::SIZE_PROPERTYNAME, function () use ($node) {
497
+                return $node->getSize();
498
+            });
499
+
500
+            $requestProperties = $propFind->getRequestedProperties();
501
+
502
+            if (in_array(self::SUBFILE_COUNT_PROPERTYNAME, $requestProperties, true)
503
+                || in_array(self::SUBFOLDER_COUNT_PROPERTYNAME, $requestProperties, true)) {
504
+                $nbFiles = 0;
505
+                $nbFolders = 0;
506
+                foreach ($node->getChildren() as $child) {
507
+                    if ($child instanceof File) {
508
+                        $nbFiles++;
509
+                    } elseif ($child instanceof Directory) {
510
+                        $nbFolders++;
511
+                    }
512
+                }
513
+
514
+                $propFind->handle(self::SUBFILE_COUNT_PROPERTYNAME, $nbFiles);
515
+                $propFind->handle(self::SUBFOLDER_COUNT_PROPERTYNAME, $nbFolders);
516
+            }
517
+        }
518
+    }
519
+
520
+    /**
521
+     * translate Nextcloud permissions to OCM Permissions
522
+     *
523
+     * @param $ncPermissions
524
+     * @return array
525
+     */
526
+    protected function ncPermissions2ocmPermissions($ncPermissions) {
527
+        $ocmPermissions = [];
528
+
529
+        if ($ncPermissions & Constants::PERMISSION_SHARE) {
530
+            $ocmPermissions[] = 'share';
531
+        }
532
+
533
+        if ($ncPermissions & Constants::PERMISSION_READ) {
534
+            $ocmPermissions[] = 'read';
535
+        }
536
+
537
+        if (($ncPermissions & Constants::PERMISSION_CREATE) ||
538
+            ($ncPermissions & Constants::PERMISSION_UPDATE)) {
539
+            $ocmPermissions[] = 'write';
540
+        }
541
+
542
+        return $ocmPermissions;
543
+    }
544
+
545
+    /**
546
+     * Update ownCloud-specific properties
547
+     *
548
+     * @param string $path
549
+     * @param PropPatch $propPatch
550
+     *
551
+     * @return void
552
+     */
553
+    public function handleUpdateProperties($path, PropPatch $propPatch) {
554
+        $node = $this->tree->getNodeForPath($path);
555
+        if (!($node instanceof Node)) {
556
+            return;
557
+        }
558
+
559
+        $propPatch->handle(self::LASTMODIFIED_PROPERTYNAME, function ($time) use ($node) {
560
+            if (empty($time)) {
561
+                return false;
562
+            }
563
+            $node->touch($time);
564
+            return true;
565
+        });
566
+        $propPatch->handle(self::GETETAG_PROPERTYNAME, function ($etag) use ($node) {
567
+            if (empty($etag)) {
568
+                return false;
569
+            }
570
+            return $node->setEtag($etag) !== -1;
571
+        });
572
+        $propPatch->handle(self::CREATIONDATE_PROPERTYNAME, function ($time) use ($node) {
573
+            if (empty($time)) {
574
+                return false;
575
+            }
576
+            $dateTime = new \DateTimeImmutable($time);
577
+            $node->setCreationTime($dateTime->getTimestamp());
578
+            return true;
579
+        });
580
+        $propPatch->handle(self::CREATION_TIME_PROPERTYNAME, function ($time) use ($node) {
581
+            if (empty($time)) {
582
+                return false;
583
+            }
584
+            $node->setCreationTime((int)$time);
585
+            return true;
586
+        });
587
+
588
+        $this->handleUpdatePropertiesMetadata($propPatch, $node);
589
+
590
+        /**
591
+         * Disable modification of the displayname property for files and
592
+         * folders via PROPPATCH. See PROPFIND for more information.
593
+         */
594
+        $propPatch->handle(self::DISPLAYNAME_PROPERTYNAME, function ($displayName) {
595
+            return 403;
596
+        });
597
+    }
598
+
599
+
600
+    /**
601
+     * handle the update of metadata from PROPPATCH requests
602
+     *
603
+     * @param PropPatch $propPatch
604
+     * @param Node $node
605
+     *
606
+     * @throws FilesMetadataException
607
+     */
608
+    private function handleUpdatePropertiesMetadata(PropPatch $propPatch, Node $node): void {
609
+        $userId = $this->userSession->getUser()?->getUID();
610
+        if ($userId === null) {
611
+            return;
612
+        }
613
+
614
+        $accessRight = $this->getMetadataFileAccessRight($node, $userId);
615
+        $filesMetadataManager = $this->initFilesMetadataManager();
616
+        $knownMetadata = $filesMetadataManager->getKnownMetadata();
617
+
618
+        foreach ($propPatch->getRemainingMutations() as $mutation) {
619
+            if (!str_starts_with($mutation, self::FILE_METADATA_PREFIX)) {
620
+                continue;
621
+            }
622
+
623
+            $propPatch->handle(
624
+                $mutation,
625
+                function (mixed $value) use ($accessRight, $knownMetadata, $node, $mutation, $filesMetadataManager): bool {
626
+                    /** @var FilesMetadata $metadata */
627
+                    $metadata = $filesMetadataManager->getMetadata((int)$node->getFileId(), true);
628
+                    $metadata->setStorageId($node->getNode()->getStorage()->getCache()->getNumericStorageId());
629
+                    $metadataKey = substr($mutation, strlen(self::FILE_METADATA_PREFIX));
630
+
631
+                    // confirm metadata key is editable via PROPPATCH
632
+                    if ($knownMetadata->getEditPermission($metadataKey) < $accessRight) {
633
+                        throw new FilesMetadataException('you do not have enough rights to update \'' . $metadataKey . '\' on this node');
634
+                    }
635
+
636
+                    if ($value === null) {
637
+                        $metadata->unset($metadataKey);
638
+                        $filesMetadataManager->saveMetadata($metadata);
639
+                        return true;
640
+                    }
641
+
642
+                    // If the metadata is unknown, it defaults to string.
643
+                    try {
644
+                        $type = $knownMetadata->getType($metadataKey);
645
+                    } catch (FilesMetadataNotFoundException) {
646
+                        $type = IMetadataValueWrapper::TYPE_STRING;
647
+                    }
648
+
649
+                    switch ($type) {
650
+                        case IMetadataValueWrapper::TYPE_STRING:
651
+                            $metadata->setString($metadataKey, $value, $knownMetadata->isIndex($metadataKey));
652
+                            break;
653
+                        case IMetadataValueWrapper::TYPE_INT:
654
+                            $metadata->setInt($metadataKey, $value, $knownMetadata->isIndex($metadataKey));
655
+                            break;
656
+                        case IMetadataValueWrapper::TYPE_FLOAT:
657
+                            $metadata->setFloat($metadataKey, $value);
658
+                            break;
659
+                        case IMetadataValueWrapper::TYPE_BOOL:
660
+                            $metadata->setBool($metadataKey, $value, $knownMetadata->isIndex($metadataKey));
661
+                            break;
662
+                        case IMetadataValueWrapper::TYPE_ARRAY:
663
+                            $metadata->setArray($metadataKey, $value);
664
+                            break;
665
+                        case IMetadataValueWrapper::TYPE_STRING_LIST:
666
+                            $metadata->setStringList($metadataKey, $value, $knownMetadata->isIndex($metadataKey));
667
+                            break;
668
+                        case IMetadataValueWrapper::TYPE_INT_LIST:
669
+                            $metadata->setIntList($metadataKey, $value, $knownMetadata->isIndex($metadataKey));
670
+                            break;
671
+                    }
672
+
673
+                    $filesMetadataManager->saveMetadata($metadata);
674
+
675
+                    return true;
676
+                }
677
+            );
678
+        }
679
+    }
680
+
681
+    /**
682
+     * init default internal metadata
683
+     *
684
+     * @return IFilesMetadataManager
685
+     */
686
+    private function initFilesMetadataManager(): IFilesMetadataManager {
687
+        /** @var IFilesMetadataManager $manager */
688
+        $manager = \OCP\Server::get(IFilesMetadataManager::class);
689
+        $manager->initMetadata('files-live-photo', IMetadataValueWrapper::TYPE_STRING, false, IMetadataValueWrapper::EDIT_REQ_OWNERSHIP);
690
+
691
+        return $manager;
692
+    }
693
+
694
+    /**
695
+     * based on owner and shares, returns the bottom limit to update related metadata
696
+     *
697
+     * @param Node $node
698
+     * @param string $userId
699
+     *
700
+     * @return int
701
+     */
702
+    private function getMetadataFileAccessRight(Node $node, string $userId): int {
703
+        if ($node->getOwner()?->getUID() === $userId) {
704
+            return IMetadataValueWrapper::EDIT_REQ_OWNERSHIP;
705
+        } else {
706
+            $filePermissions = $node->getSharePermissions($userId);
707
+            if ($filePermissions & Constants::PERMISSION_UPDATE) {
708
+                return IMetadataValueWrapper::EDIT_REQ_WRITE_PERMISSION;
709
+            }
710
+        }
711
+
712
+        return IMetadataValueWrapper::EDIT_REQ_READ_PERMISSION;
713
+    }
714
+
715
+    /**
716
+     * @param string $filePath
717
+     * @param ?\Sabre\DAV\INode $node
718
+     * @return void
719
+     * @throws \Sabre\DAV\Exception\BadRequest
720
+     */
721
+    public function sendFileIdHeader($filePath, ?\Sabre\DAV\INode $node = null) {
722
+        // we get the node for the given $filePath here because in case of afterCreateFile $node is the parent folder
723
+        if (!$this->server->tree->nodeExists($filePath)) {
724
+            return;
725
+        }
726
+        $node = $this->server->tree->getNodeForPath($filePath);
727
+        if ($node instanceof Node) {
728
+            $fileId = $node->getFileId();
729
+            if (!is_null($fileId)) {
730
+                $this->server->httpResponse->setHeader('OC-FileId', $fileId);
731
+            }
732
+        }
733
+    }
734 734
 }
Please login to merge, or discard this patch.
Spacing   +44 added lines, -44 removed lines patch added patch discarded remove patch
@@ -137,9 +137,9 @@  discard block
 block discarded – undo
137 137
 		$this->server->on('propPatch', [$this, 'handleUpdateProperties']);
138 138
 		$this->server->on('afterBind', [$this, 'sendFileIdHeader']);
139 139
 		$this->server->on('afterWriteContent', [$this, 'sendFileIdHeader']);
140
-		$this->server->on('afterMethod:GET', [$this,'httpGet']);
140
+		$this->server->on('afterMethod:GET', [$this, 'httpGet']);
141 141
 		$this->server->on('afterMethod:GET', [$this, 'handleDownloadToken']);
142
-		$this->server->on('afterResponse', function ($request, ResponseInterface $response): void {
142
+		$this->server->on('afterResponse', function($request, ResponseInterface $response): void {
143 143
 			$body = $response->getBody();
144 144
 			if (is_resource($body)) {
145 145
 				fclose($body);
@@ -166,7 +166,7 @@  discard block
 block discarded – undo
166 166
 		// Ensure source exists
167 167
 		$sourceNodeFileInfo = $sourceNode->getFileInfo();
168 168
 		if ($sourceNodeFileInfo === null) {
169
-			throw new NotFound($source . ' does not exist');
169
+			throw new NotFound($source.' does not exist');
170 170
 		}
171 171
 		// Ensure the target name is valid
172 172
 		try {
@@ -206,12 +206,12 @@  discard block
 block discarded – undo
206 206
 		// The source needs to be deletable for moving
207 207
 		$sourceNodeFileInfo = $sourceNode->getFileInfo();
208 208
 		if (!$sourceNodeFileInfo->isDeletable()) {
209
-			throw new Forbidden($source . ' cannot be deleted');
209
+			throw new Forbidden($source.' cannot be deleted');
210 210
 		}
211 211
 
212 212
 		// The source is not allowed to be the parent of the target
213
-		if (str_starts_with($source, $target . '/')) {
214
-			throw new Forbidden($source . ' cannot be moved to it\'s parent');
213
+		if (str_starts_with($source, $target.'/')) {
214
+			throw new Forbidden($source.' cannot be moved to it\'s parent');
215 215
 		}
216 216
 	}
217 217
 
@@ -265,10 +265,10 @@  discard block
 block discarded – undo
265 265
 					Request::USER_AGENT_ANDROID_MOBILE_CHROME,
266 266
 					Request::USER_AGENT_FREEBOX,
267 267
 				])) {
268
-				$response->addHeader('Content-Disposition', 'attachment; filename="' . rawurlencode($filename) . '"');
268
+				$response->addHeader('Content-Disposition', 'attachment; filename="'.rawurlencode($filename).'"');
269 269
 			} else {
270
-				$response->addHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . rawurlencode($filename)
271
-													 . '; filename="' . rawurlencode($filename) . '"');
270
+				$response->addHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\''.rawurlencode($filename)
271
+													 . '; filename="'.rawurlencode($filename).'"');
272 272
 			}
273 273
 		}
274 274
 
@@ -304,15 +304,15 @@  discard block
 block discarded – undo
304 304
 			 * }
305 305
 			 */
306 306
 
307
-			$propFind->handle(self::FILEID_PROPERTYNAME, function () use ($node) {
307
+			$propFind->handle(self::FILEID_PROPERTYNAME, function() use ($node) {
308 308
 				return $node->getFileId();
309 309
 			});
310 310
 
311
-			$propFind->handle(self::INTERNAL_FILEID_PROPERTYNAME, function () use ($node) {
311
+			$propFind->handle(self::INTERNAL_FILEID_PROPERTYNAME, function() use ($node) {
312 312
 				return $node->getInternalFileId();
313 313
 			});
314 314
 
315
-			$propFind->handle(self::PERMISSIONS_PROPERTYNAME, function () use ($node) {
315
+			$propFind->handle(self::PERMISSIONS_PROPERTYNAME, function() use ($node) {
316 316
 				$perms = $node->getDavPermissions();
317 317
 				if ($this->isPublic) {
318 318
 					// remove mount information
@@ -321,7 +321,7 @@  discard block
 block discarded – undo
321 321
 				return $perms;
322 322
 			});
323 323
 
324
-			$propFind->handle(self::SHARE_PERMISSIONS_PROPERTYNAME, function () use ($node, $httpRequest) {
324
+			$propFind->handle(self::SHARE_PERMISSIONS_PROPERTYNAME, function() use ($node, $httpRequest) {
325 325
 				$user = $this->userSession->getUser();
326 326
 				if ($user === null) {
327 327
 					return null;
@@ -331,7 +331,7 @@  discard block
 block discarded – undo
331 331
 				);
332 332
 			});
333 333
 
334
-			$propFind->handle(self::OCM_SHARE_PERMISSIONS_PROPERTYNAME, function () use ($node, $httpRequest): ?string {
334
+			$propFind->handle(self::OCM_SHARE_PERMISSIONS_PROPERTYNAME, function() use ($node, $httpRequest): ?string {
335 335
 				$user = $this->userSession->getUser();
336 336
 				if ($user === null) {
337 337
 					return null;
@@ -343,15 +343,15 @@  discard block
 block discarded – undo
343 343
 				return json_encode($ocmPermissions, JSON_THROW_ON_ERROR);
344 344
 			});
345 345
 
346
-			$propFind->handle(self::SHARE_ATTRIBUTES_PROPERTYNAME, function () use ($node, $httpRequest) {
346
+			$propFind->handle(self::SHARE_ATTRIBUTES_PROPERTYNAME, function() use ($node, $httpRequest) {
347 347
 				return json_encode($node->getShareAttributes(), JSON_THROW_ON_ERROR);
348 348
 			});
349 349
 
350
-			$propFind->handle(self::GETETAG_PROPERTYNAME, function () use ($node): string {
350
+			$propFind->handle(self::GETETAG_PROPERTYNAME, function() use ($node): string {
351 351
 				return $node->getETag();
352 352
 			});
353 353
 
354
-			$propFind->handle(self::OWNER_ID_PROPERTYNAME, function () use ($node): ?string {
354
+			$propFind->handle(self::OWNER_ID_PROPERTYNAME, function() use ($node): ?string {
355 355
 				$owner = $node->getOwner();
356 356
 				if (!$owner) {
357 357
 					return null;
@@ -359,7 +359,7 @@  discard block
 block discarded – undo
359 359
 					return $owner->getUID();
360 360
 				}
361 361
 			});
362
-			$propFind->handle(self::OWNER_DISPLAY_NAME_PROPERTYNAME, function () use ($node): ?string {
362
+			$propFind->handle(self::OWNER_DISPLAY_NAME_PROPERTYNAME, function() use ($node): ?string {
363 363
 				$owner = $node->getOwner();
364 364
 				if (!$owner) {
365 365
 					return null;
@@ -385,13 +385,13 @@  discard block
 block discarded – undo
385 385
 				return null;
386 386
 			});
387 387
 
388
-			$propFind->handle(self::HAS_PREVIEW_PROPERTYNAME, function () use ($node) {
388
+			$propFind->handle(self::HAS_PREVIEW_PROPERTYNAME, function() use ($node) {
389 389
 				return json_encode($this->previewManager->isAvailable($node->getFileInfo()), JSON_THROW_ON_ERROR);
390 390
 			});
391
-			$propFind->handle(self::SIZE_PROPERTYNAME, function () use ($node): int|float {
391
+			$propFind->handle(self::SIZE_PROPERTYNAME, function() use ($node): int | float {
392 392
 				return $node->getSize();
393 393
 			});
394
-			$propFind->handle(self::MOUNT_TYPE_PROPERTYNAME, function () use ($node) {
394
+			$propFind->handle(self::MOUNT_TYPE_PROPERTYNAME, function() use ($node) {
395 395
 				return $node->getFileInfo()->getMountPoint()->getMountType();
396 396
 			});
397 397
 
@@ -401,18 +401,18 @@  discard block
 block discarded – undo
401 401
 			 * If so, then the node can only be unshared and not deleted.
402 402
 			 * @see https://github.com/nextcloud/server/blob/cc75294eb6b16b916a342e69998935f89222619d/lib/private/Files/View.php#L696-L698
403 403
 			 */
404
-			$propFind->handle(self::MOUNT_ROOT_PROPERTYNAME, function () use ($node) {
404
+			$propFind->handle(self::MOUNT_ROOT_PROPERTYNAME, function() use ($node) {
405 405
 				return $node->getNode()->getInternalPath() === '' ? 'true' : 'false';
406 406
 			});
407 407
 
408
-			$propFind->handle(self::SHARE_NOTE, function () use ($node): ?string {
408
+			$propFind->handle(self::SHARE_NOTE, function() use ($node): ?string {
409 409
 				$user = $this->userSession->getUser();
410 410
 				return $node->getNoteFromShare(
411 411
 					$user?->getUID()
412 412
 				);
413 413
 			});
414 414
 
415
-			$propFind->handle(self::SHARE_HIDE_DOWNLOAD_PROPERTYNAME, function () use ($node) {
415
+			$propFind->handle(self::SHARE_HIDE_DOWNLOAD_PROPERTYNAME, function() use ($node) {
416 416
 				$storage = $node->getNode()->getStorage();
417 417
 				if ($storage->instanceOfStorage(ISharedStorage::class)) {
418 418
 					/** @var ISharedStorage $storage */
@@ -425,23 +425,23 @@  discard block
 block discarded – undo
425 425
 				}
426 426
 			});
427 427
 
428
-			$propFind->handle(self::DATA_FINGERPRINT_PROPERTYNAME, function () {
428
+			$propFind->handle(self::DATA_FINGERPRINT_PROPERTYNAME, function() {
429 429
 				return $this->config->getSystemValue('data-fingerprint', '');
430 430
 			});
431
-			$propFind->handle(self::CREATIONDATE_PROPERTYNAME, function () use ($node) {
431
+			$propFind->handle(self::CREATIONDATE_PROPERTYNAME, function() use ($node) {
432 432
 				return (new \DateTimeImmutable())
433 433
 					->setTimestamp($node->getFileInfo()->getCreationTime())
434 434
 					->format(\DateTimeInterface::ATOM);
435 435
 			});
436
-			$propFind->handle(self::CREATION_TIME_PROPERTYNAME, function () use ($node) {
436
+			$propFind->handle(self::CREATION_TIME_PROPERTYNAME, function() use ($node) {
437 437
 				return $node->getFileInfo()->getCreationTime();
438 438
 			});
439 439
 
440 440
 			foreach ($node->getFileInfo()->getMetadata() as $metadataKey => $metadataValue) {
441
-				$propFind->handle(self::FILE_METADATA_PREFIX . $metadataKey, $metadataValue);
441
+				$propFind->handle(self::FILE_METADATA_PREFIX.$metadataKey, $metadataValue);
442 442
 			}
443 443
 
444
-			$propFind->handle(self::HIDDEN_PROPERTYNAME, function () use ($node) {
444
+			$propFind->handle(self::HIDDEN_PROPERTYNAME, function() use ($node) {
445 445
 				$isLivePhoto = isset($node->getFileInfo()->getMetadata()['files-live-photo']);
446 446
 				$isMovFile = $node->getFileInfo()->getMimetype() === 'video/quicktime';
447 447
 				return ($isLivePhoto && $isMovFile) ? 'true' : 'false';
@@ -453,18 +453,18 @@  discard block
 block discarded – undo
453 453
 			 * CustomPropertiesBackend (esp. visible when querying all files
454 454
 			 * in a folder).
455 455
 			 */
456
-			$propFind->handle(self::DISPLAYNAME_PROPERTYNAME, function () use ($node) {
456
+			$propFind->handle(self::DISPLAYNAME_PROPERTYNAME, function() use ($node) {
457 457
 				return $node->getName();
458 458
 			});
459 459
 
460
-			$propFind->handle(self::IS_FEDERATED_PROPERTYNAME, function () use ($node) {
460
+			$propFind->handle(self::IS_FEDERATED_PROPERTYNAME, function() use ($node) {
461 461
 				return $node->getFileInfo()->getMountPoint()
462 462
 					instanceof SharingExternalMount;
463 463
 			});
464 464
 		}
465 465
 
466 466
 		if ($node instanceof File) {
467
-			$propFind->handle(self::DOWNLOADURL_PROPERTYNAME, function () use ($node) {
467
+			$propFind->handle(self::DOWNLOADURL_PROPERTYNAME, function() use ($node) {
468 468
 				try {
469 469
 					$directDownloadUrl = $node->getDirectDownload();
470 470
 					if (isset($directDownloadUrl['url'])) {
@@ -478,7 +478,7 @@  discard block
 block discarded – undo
478 478
 				return false;
479 479
 			});
480 480
 
481
-			$propFind->handle(self::CHECKSUMS_PROPERTYNAME, function () use ($node) {
481
+			$propFind->handle(self::CHECKSUMS_PROPERTYNAME, function() use ($node) {
482 482
 				$checksum = $node->getChecksum();
483 483
 				if ($checksum === null || $checksum === '') {
484 484
 					return null;
@@ -487,13 +487,13 @@  discard block
 block discarded – undo
487 487
 				return new ChecksumList($checksum);
488 488
 			});
489 489
 
490
-			$propFind->handle(self::UPLOAD_TIME_PROPERTYNAME, function () use ($node) {
490
+			$propFind->handle(self::UPLOAD_TIME_PROPERTYNAME, function() use ($node) {
491 491
 				return $node->getFileInfo()->getUploadTime();
492 492
 			});
493 493
 		}
494 494
 
495 495
 		if ($node instanceof Directory) {
496
-			$propFind->handle(self::SIZE_PROPERTYNAME, function () use ($node) {
496
+			$propFind->handle(self::SIZE_PROPERTYNAME, function() use ($node) {
497 497
 				return $node->getSize();
498 498
 			});
499 499
 
@@ -556,20 +556,20 @@  discard block
 block discarded – undo
556 556
 			return;
557 557
 		}
558 558
 
559
-		$propPatch->handle(self::LASTMODIFIED_PROPERTYNAME, function ($time) use ($node) {
559
+		$propPatch->handle(self::LASTMODIFIED_PROPERTYNAME, function($time) use ($node) {
560 560
 			if (empty($time)) {
561 561
 				return false;
562 562
 			}
563 563
 			$node->touch($time);
564 564
 			return true;
565 565
 		});
566
-		$propPatch->handle(self::GETETAG_PROPERTYNAME, function ($etag) use ($node) {
566
+		$propPatch->handle(self::GETETAG_PROPERTYNAME, function($etag) use ($node) {
567 567
 			if (empty($etag)) {
568 568
 				return false;
569 569
 			}
570 570
 			return $node->setEtag($etag) !== -1;
571 571
 		});
572
-		$propPatch->handle(self::CREATIONDATE_PROPERTYNAME, function ($time) use ($node) {
572
+		$propPatch->handle(self::CREATIONDATE_PROPERTYNAME, function($time) use ($node) {
573 573
 			if (empty($time)) {
574 574
 				return false;
575 575
 			}
@@ -577,11 +577,11 @@  discard block
 block discarded – undo
577 577
 			$node->setCreationTime($dateTime->getTimestamp());
578 578
 			return true;
579 579
 		});
580
-		$propPatch->handle(self::CREATION_TIME_PROPERTYNAME, function ($time) use ($node) {
580
+		$propPatch->handle(self::CREATION_TIME_PROPERTYNAME, function($time) use ($node) {
581 581
 			if (empty($time)) {
582 582
 				return false;
583 583
 			}
584
-			$node->setCreationTime((int)$time);
584
+			$node->setCreationTime((int) $time);
585 585
 			return true;
586 586
 		});
587 587
 
@@ -591,7 +591,7 @@  discard block
 block discarded – undo
591 591
 		 * Disable modification of the displayname property for files and
592 592
 		 * folders via PROPPATCH. See PROPFIND for more information.
593 593
 		 */
594
-		$propPatch->handle(self::DISPLAYNAME_PROPERTYNAME, function ($displayName) {
594
+		$propPatch->handle(self::DISPLAYNAME_PROPERTYNAME, function($displayName) {
595 595
 			return 403;
596 596
 		});
597 597
 	}
@@ -622,15 +622,15 @@  discard block
 block discarded – undo
622 622
 
623 623
 			$propPatch->handle(
624 624
 				$mutation,
625
-				function (mixed $value) use ($accessRight, $knownMetadata, $node, $mutation, $filesMetadataManager): bool {
625
+				function(mixed $value) use ($accessRight, $knownMetadata, $node, $mutation, $filesMetadataManager): bool {
626 626
 					/** @var FilesMetadata $metadata */
627
-					$metadata = $filesMetadataManager->getMetadata((int)$node->getFileId(), true);
627
+					$metadata = $filesMetadataManager->getMetadata((int) $node->getFileId(), true);
628 628
 					$metadata->setStorageId($node->getNode()->getStorage()->getCache()->getNumericStorageId());
629 629
 					$metadataKey = substr($mutation, strlen(self::FILE_METADATA_PREFIX));
630 630
 
631 631
 					// confirm metadata key is editable via PROPPATCH
632 632
 					if ($knownMetadata->getEditPermission($metadataKey) < $accessRight) {
633
-						throw new FilesMetadataException('you do not have enough rights to update \'' . $metadataKey . '\' on this node');
633
+						throw new FilesMetadataException('you do not have enough rights to update \''.$metadataKey.'\' on this node');
634 634
 					}
635 635
 
636 636
 					if ($value === null) {
Please login to merge, or discard this patch.