Completed
Push — master ( d15feb...a7a64d )
by
unknown
44:34
created
apps/dav/tests/unit/Connector/Sabre/DirectoryTest.php 2 patches
Indentation   +576 added lines, -576 removed lines patch added patch discarded remove patch
@@ -29,585 +29,585 @@
 block discarded – undo
29 29
 use Test\Traits\UserTrait;
30 30
 
31 31
 class TestViewDirectory extends View {
32
-	public function __construct(
33
-		private $updatables,
34
-		private $deletables,
35
-		private $canRename = true,
36
-	) {
37
-	}
38
-
39
-	public function isUpdatable($path) {
40
-		return $this->updatables[$path];
41
-	}
42
-
43
-	public function isCreatable($path) {
44
-		return $this->updatables[$path];
45
-	}
46
-
47
-	public function isDeletable($path) {
48
-		return $this->deletables[$path];
49
-	}
50
-
51
-	public function rename($source, $target, array $options = []) {
52
-		return $this->canRename;
53
-	}
54
-
55
-	public function getRelativePath($path): ?string {
56
-		return $path;
57
-	}
32
+    public function __construct(
33
+        private $updatables,
34
+        private $deletables,
35
+        private $canRename = true,
36
+    ) {
37
+    }
38
+
39
+    public function isUpdatable($path) {
40
+        return $this->updatables[$path];
41
+    }
42
+
43
+    public function isCreatable($path) {
44
+        return $this->updatables[$path];
45
+    }
46
+
47
+    public function isDeletable($path) {
48
+        return $this->deletables[$path];
49
+    }
50
+
51
+    public function rename($source, $target, array $options = []) {
52
+        return $this->canRename;
53
+    }
54
+
55
+    public function getRelativePath($path): ?string {
56
+        return $path;
57
+    }
58 58
 }
59 59
 
60 60
 
61 61
 #[\PHPUnit\Framework\Attributes\Group('DB')]
62 62
 class DirectoryTest extends \Test\TestCase {
63
-	use UserTrait;
64
-
65
-	private View&MockObject $view;
66
-	private FileInfo&MockObject $info;
67
-	private IStorage&MockObject $storage;
68
-
69
-	protected function setUp(): void {
70
-		parent::setUp();
71
-
72
-		$this->view = $this->createMock(View::class);
73
-		$this->info = $this->createMock(FileInfo::class);
74
-		$this->storage = $this->createMock(IStorage::class);
75
-		$this->info->method('getStorage')
76
-			->willReturn($this->storage);
77
-		$this->info->method('isReadable')
78
-			->willReturn(true);
79
-		$this->info->method('getType')
80
-			->willReturn(Node::TYPE_FOLDER);
81
-		$this->info->method('getName')
82
-			->willReturn('folder');
83
-		$this->info->method('getPath')
84
-			->willReturn('/admin/files/folder');
85
-		$this->info->method('getPermissions')
86
-			->willReturn(Constants::PERMISSION_READ);
87
-	}
88
-
89
-	private function getDir(string $path = '/'): Directory {
90
-		$this->view->expects($this->once())
91
-			->method('getRelativePath')
92
-			->willReturn($path);
93
-
94
-		$this->info->expects($this->once())
95
-			->method('getPath')
96
-			->willReturn($path);
97
-
98
-		return new Directory($this->view, $this->info);
99
-	}
100
-
101
-
102
-	public function testDeleteRootFolderFails(): void {
103
-		$this->expectException(\Sabre\DAV\Exception\Forbidden::class);
104
-
105
-		$this->info->expects($this->any())
106
-			->method('isDeletable')
107
-			->willReturn(true);
108
-		$this->view->expects($this->never())
109
-			->method('rmdir');
110
-		$dir = $this->getDir();
111
-		$dir->delete();
112
-	}
113
-
114
-
115
-	public function testDeleteForbidden(): void {
116
-		$this->expectException(Forbidden::class);
117
-
118
-		// deletion allowed
119
-		$this->info->expects($this->once())
120
-			->method('isDeletable')
121
-			->willReturn(true);
122
-
123
-		// but fails
124
-		$this->view->expects($this->once())
125
-			->method('rmdir')
126
-			->with('sub')
127
-			->willThrowException(new ForbiddenException('', true));
128
-
129
-		$dir = $this->getDir('sub');
130
-		$dir->delete();
131
-	}
132
-
133
-
134
-	public function testDeleteFolderWhenAllowed(): void {
135
-		// deletion allowed
136
-		$this->info->expects($this->once())
137
-			->method('isDeletable')
138
-			->willReturn(true);
139
-
140
-		// but fails
141
-		$this->view->expects($this->once())
142
-			->method('rmdir')
143
-			->with('sub')
144
-			->willReturn(true);
145
-
146
-		$dir = $this->getDir('sub');
147
-		$dir->delete();
148
-	}
149
-
150
-
151
-	public function testDeleteFolderFailsWhenNotAllowed(): void {
152
-		$this->expectException(\Sabre\DAV\Exception\Forbidden::class);
153
-
154
-		$this->info->expects($this->once())
155
-			->method('isDeletable')
156
-			->willReturn(false);
157
-
158
-		$dir = $this->getDir('sub');
159
-		$dir->delete();
160
-	}
161
-
162
-
163
-	public function testDeleteFolderThrowsWhenDeletionFailed(): void {
164
-		$this->expectException(\Sabre\DAV\Exception\Forbidden::class);
165
-
166
-		// deletion allowed
167
-		$this->info->expects($this->once())
168
-			->method('isDeletable')
169
-			->willReturn(true);
170
-
171
-		// but fails
172
-		$this->view->expects($this->once())
173
-			->method('rmdir')
174
-			->with('sub')
175
-			->willReturn(false);
176
-
177
-		$dir = $this->getDir('sub');
178
-		$dir->delete();
179
-	}
180
-
181
-	public function testGetChildren(): void {
182
-		$info1 = $this->createMock(FileInfo::class);
183
-		$info2 = $this->createMock(FileInfo::class);
184
-		$info1->method('getName')
185
-			->willReturn('first');
186
-		$info1->method('getPath')
187
-			->willReturn('folder/first');
188
-		$info1->method('getEtag')
189
-			->willReturn('abc');
190
-		$info2->method('getName')
191
-			->willReturn('second');
192
-		$info2->method('getPath')
193
-			->willReturn('folder/second');
194
-		$info2->method('getEtag')
195
-			->willReturn('def');
196
-
197
-		$this->view->expects($this->once())
198
-			->method('getDirectoryContent')
199
-			->willReturn([$info1, $info2]);
200
-
201
-		$this->view->expects($this->any())
202
-			->method('getRelativePath')
203
-			->willReturnCallback(function ($path) {
204
-				return str_replace('/admin/files/', '', $path);
205
-			});
206
-
207
-		$this->view->expects($this->any())
208
-			->method('getAbsolutePath')
209
-			->willReturnCallback(function ($path) {
210
-				return Filesystem::normalizePath('/admin/files' . $path);
211
-			});
212
-
213
-		$this->overwriteService(View::class, $this->view);
214
-
215
-		$dir = new Directory($this->view, $this->info);
216
-		$nodes = $dir->getChildren();
217
-
218
-		$this->assertCount(2, $nodes);
219
-
220
-		// calling a second time just returns the cached values,
221
-		// does not call getDirectoryContents again
222
-		$dir->getChildren();
223
-	}
224
-
225
-
226
-	public function testGetChildrenNoPermission(): void {
227
-		$this->expectException(\Sabre\DAV\Exception\Forbidden::class);
228
-
229
-		$info = $this->createMock(FileInfo::class);
230
-		$info->expects($this->any())
231
-			->method('isReadable')
232
-			->willReturn(false);
233
-
234
-		$dir = new Directory($this->view, $info);
235
-		$dir->getChildren();
236
-	}
237
-
238
-
239
-	public function testGetChildNoPermission(): void {
240
-		$this->expectException(\Sabre\DAV\Exception\NotFound::class);
241
-
242
-		$this->info->expects($this->any())
243
-			->method('isReadable')
244
-			->willReturn(false);
245
-
246
-		$dir = new Directory($this->view, $this->info);
247
-		$dir->getChild('test');
248
-	}
249
-
250
-
251
-	public function testGetChildThrowStorageNotAvailableException(): void {
252
-		$this->expectException(\Sabre\DAV\Exception\ServiceUnavailable::class);
253
-
254
-		$this->view->expects($this->once())
255
-			->method('getFileInfo')
256
-			->willThrowException(new StorageNotAvailableException());
257
-
258
-		$dir = new Directory($this->view, $this->info);
259
-		$dir->getChild('.');
260
-	}
261
-
262
-
263
-	public function testGetChildThrowInvalidPath(): void {
264
-		$this->expectException(InvalidPath::class);
265
-
266
-		$this->view->expects($this->once())
267
-			->method('verifyPath')
268
-			->willThrowException(new InvalidPathException());
269
-		$this->view->expects($this->never())
270
-			->method('getFileInfo');
271
-
272
-		$dir = new Directory($this->view, $this->info);
273
-		$dir->getChild('.');
274
-	}
275
-
276
-	public function testGetNodeForPath(): void {
277
-		$directoryNode = $this->createMock(Folder::class);
278
-		$pathNode = $this->createMock(Folder::class);
279
-		$pathParentNode = $this->createMock(Folder::class);
280
-		$storage = $this->createMock(IStorage::class);
281
-
282
-		$directoryNode->expects($this->once())
283
-			->method('getStorage')
284
-			->willReturn($storage);
285
-		$storage->expects($this->once())
286
-			->method('instanceOfStorage')
287
-			->willReturn(false);
288
-
289
-		$directoryNode->expects($this->once())
290
-			->method('isReadable')
291
-			->willReturn(true);
292
-		$directoryNode->expects($this->once())
293
-			->method('getPath')
294
-			->willReturn('/admin/files/');
295
-		$directoryNode->expects($this->once())
296
-			->method('get')
297
-			->willReturn($pathNode);
298
-
299
-		$pathNode->expects($this->once())
300
-			->method('getPath')
301
-			->willReturn('/admin/files/my/deep/folder/');
302
-		$pathNode->expects($this->once())
303
-			->method('isReadable')
304
-			->willReturn(true);
305
-		$pathNode->expects($this->once())
306
-			->method('getMimetype')
307
-			->willReturn(FileInfo::MIMETYPE_FOLDER);
308
-
309
-		$this->view->method('getRelativePath')
310
-			->willReturnCallback(function ($path) {
311
-				return str_replace('/admin/files/', '', $path);
312
-			});
313
-
314
-		$this->view->expects($this->exactly(2))
315
-			->method('getFileInfo')
316
-			->willReturn($pathParentNode);
317
-
318
-		$pathParentNode->expects($this->exactly(2))
319
-			->method('getPath')
320
-			->willReturnOnConsecutiveCalls('/my/deep', '/my');
321
-		$pathParentNode->expects($this->exactly(2))
322
-			->method('isReadable')
323
-			->willReturn(true);
324
-
325
-		$dir = new Directory($this->view, $directoryNode);
326
-		$dir->getNodeForPath('/my/deep/folder/');
327
-	}
328
-
329
-	public function testGetNodeForPathFailsWithNoReadPermissionsForParent(): void {
330
-		$directoryNode = $this->createMock(Folder::class);
331
-		$pathNode = $this->createMock(Folder::class);
332
-		$pathParentNode = $this->createMock(Folder::class);
333
-		$storage = $this->createMock(IStorage::class);
334
-
335
-		$directoryNode->expects($this->once())
336
-			->method('getStorage')
337
-			->willReturn($storage);
338
-		$storage->expects($this->once())
339
-			->method('instanceOfStorage')
340
-			->willReturn(false);
341
-
342
-		$directoryNode->expects($this->once())
343
-			->method('isReadable')
344
-			->willReturn(true);
345
-		$directoryNode->expects($this->once())
346
-			->method('getPath')
347
-			->willReturn('/admin/files/');
348
-		$directoryNode->expects($this->once())
349
-			->method('get')
350
-			->willReturn($pathNode);
351
-
352
-		$pathNode->expects($this->once())
353
-			->method('getPath')
354
-			->willReturn('/admin/files/my/deep/folder/');
355
-		$pathNode->expects($this->once())
356
-			->method('isReadable')
357
-			->willReturn(true);
358
-		$pathNode->expects($this->once())
359
-			->method('getMimetype')
360
-			->willReturn(FileInfo::MIMETYPE_FOLDER);
361
-
362
-		$this->view->method('getRelativePath')
363
-			->willReturnCallback(function ($path) {
364
-				return str_replace('/admin/files/', '', $path);
365
-			});
366
-
367
-		$this->view->expects($this->exactly(2))
368
-			->method('getFileInfo')
369
-			->willReturn($pathParentNode);
370
-
371
-		$pathParentNode->expects($this->exactly(2))
372
-			->method('getPath')
373
-			->willReturnOnConsecutiveCalls('/my/deep', '/my');
374
-		$pathParentNode->expects($this->exactly(2))
375
-			->method('isReadable')
376
-			->willReturnOnConsecutiveCalls(true, false);
377
-
378
-		$this->expectException(NotFound::class);
379
-
380
-		$dir = new Directory($this->view, $directoryNode);
381
-		$dir->getNodeForPath('/my/deep/folder/');
382
-	}
383
-
384
-	public function testGetNodeForPathFailsWithNoReadPermissionsForPath(): void {
385
-		$directoryNode = $this->createMock(Folder::class);
386
-		$pathNode = $this->createMock(Folder::class);
387
-		$storage = $this->createMock(IStorage::class);
388
-
389
-		$directoryNode->expects($this->once())
390
-			->method('getStorage')
391
-			->willReturn($storage);
392
-		$storage->expects($this->once())
393
-			->method('instanceOfStorage')
394
-			->willReturn(false);
395
-
396
-		$directoryNode->expects($this->once())
397
-			->method('isReadable')
398
-			->willReturn(true);
399
-		$directoryNode->expects($this->once())
400
-			->method('getPath')
401
-			->willReturn('/admin/files/');
402
-		$directoryNode->expects($this->once())
403
-			->method('get')
404
-			->willReturn($pathNode);
405
-
406
-		$pathNode->expects($this->once())
407
-			->method('isReadable')
408
-			->willReturn(false);
409
-
410
-		$this->expectException(\Sabre\DAV\Exception\Forbidden::class);
411
-
412
-		$dir = new Directory($this->view, $directoryNode);
413
-		$dir->getNodeForPath('/my/deep/folder/');
414
-	}
415
-
416
-	public function testGetQuotaInfoUnlimited(): void {
417
-		$this->createUser('user', 'password');
418
-		self::loginAsUser('user');
419
-		$mountPoint = $this->createMock(IMountPoint::class);
420
-		$storage = $this->createMock(Quota::class);
421
-		$mountPoint->method('getStorage')
422
-			->willReturn($storage);
423
-
424
-		$storage->expects($this->any())
425
-			->method('instanceOfStorage')
426
-			->willReturnMap([
427
-				['\OCA\Files_Sharing\SharedStorage', false],
428
-				['\OC\Files\Storage\Wrapper\Quota', false],
429
-				[Storage::class, false],
430
-			]);
431
-
432
-		$storage->expects($this->once())
433
-			->method('getOwner')
434
-			->willReturn('user');
435
-
436
-		$storage->expects($this->never())
437
-			->method('getQuota');
438
-
439
-		$storage->expects($this->once())
440
-			->method('free_space')
441
-			->willReturn(800);
442
-
443
-		$this->info->expects($this->any())
444
-			->method('getPath')
445
-			->willReturn('/admin/files/foo');
446
-
447
-		$this->info->expects($this->once())
448
-			->method('getSize')
449
-			->willReturn(200);
450
-
451
-		$this->info->expects($this->once())
452
-			->method('getMountPoint')
453
-			->willReturn($mountPoint);
454
-
455
-		$this->view->expects($this->any())
456
-			->method('getRelativePath')
457
-			->willReturn('/foo');
458
-
459
-		$this->info->expects($this->once())
460
-			->method('getInternalPath')
461
-			->willReturn('/foo');
462
-
463
-		$mountPoint->method('getMountPoint')
464
-			->willReturn('/user/files/mymountpoint');
465
-
466
-		$dir = new Directory($this->view, $this->info);
467
-		$this->assertEquals([200, -3], $dir->getQuotaInfo()); //200 used, unlimited
468
-	}
469
-
470
-	public function testGetQuotaInfoSpecific(): void {
471
-		$this->createUser('user', 'password');
472
-		self::loginAsUser('user');
473
-		$mountPoint = $this->createMock(IMountPoint::class);
474
-		$storage = $this->createMock(Quota::class);
475
-		$mountPoint->method('getStorage')
476
-			->willReturn($storage);
477
-
478
-		$storage->expects($this->any())
479
-			->method('instanceOfStorage')
480
-			->willReturnMap([
481
-				['\OCA\Files_Sharing\SharedStorage', false],
482
-				['\OC\Files\Storage\Wrapper\Quota', true],
483
-				[Storage::class, false],
484
-			]);
485
-
486
-		$storage->expects($this->once())
487
-			->method('getOwner')
488
-			->willReturn('user');
489
-
490
-		$storage->expects($this->once())
491
-			->method('getQuota')
492
-			->willReturn(1000);
493
-
494
-		$storage->expects($this->once())
495
-			->method('free_space')
496
-			->willReturn(800);
497
-
498
-		$this->info->expects($this->once())
499
-			->method('getSize')
500
-			->willReturn(200);
501
-
502
-		$this->info->expects($this->once())
503
-			->method('getMountPoint')
504
-			->willReturn($mountPoint);
505
-
506
-		$this->info->expects($this->once())
507
-			->method('getInternalPath')
508
-			->willReturn('/foo');
509
-
510
-		$mountPoint->method('getMountPoint')
511
-			->willReturn('/user/files/mymountpoint');
512
-
513
-		$this->view->expects($this->any())
514
-			->method('getRelativePath')
515
-			->willReturn('/foo');
516
-
517
-		$dir = new Directory($this->view, $this->info);
518
-		$this->assertEquals([200, 800], $dir->getQuotaInfo()); //200 used, 800 free
519
-	}
520
-
521
-	#[\PHPUnit\Framework\Attributes\DataProvider('moveFailedProvider')]
522
-	public function testMoveFailed(string $source, string $destination, array $updatables, array $deletables): void {
523
-		$this->expectException(\Sabre\DAV\Exception\Forbidden::class);
524
-
525
-		$this->moveTest($source, $destination, $updatables, $deletables);
526
-	}
527
-
528
-	#[\PHPUnit\Framework\Attributes\DataProvider('moveSuccessProvider')]
529
-	public function testMoveSuccess(string $source, string $destination, array $updatables, array $deletables): void {
530
-		$this->moveTest($source, $destination, $updatables, $deletables);
531
-		$this->addToAssertionCount(1);
532
-	}
533
-
534
-	#[\PHPUnit\Framework\Attributes\DataProvider('moveFailedInvalidCharsProvider')]
535
-	public function testMoveFailedInvalidChars(string $source, string $destination, array $updatables, array $deletables): void {
536
-		$this->expectException(InvalidPath::class);
537
-
538
-		$this->moveTest($source, $destination, $updatables, $deletables);
539
-	}
540
-
541
-	public static function moveFailedInvalidCharsProvider(): array {
542
-		return [
543
-			['a/valid', "a/i\nvalid", ['a' => true, 'a/valid' => true, 'a/c*' => false], []],
544
-		];
545
-	}
546
-
547
-	public static function moveFailedProvider(): array {
548
-		return [
549
-			['a/b', 'a/c', ['a' => false, 'a/b' => false, 'a/c' => false], []],
550
-			['a/b', 'b/b', ['a' => false, 'a/b' => false, 'b' => false, 'b/b' => false], []],
551
-			['a/b', 'b/b', ['a' => false, 'a/b' => true, 'b' => false, 'b/b' => false], []],
552
-			['a/b', 'b/b', ['a' => true, 'a/b' => true, 'b' => false, 'b/b' => false], []],
553
-			['a/b', 'b/b', ['a' => true, 'a/b' => true, 'b' => true, 'b/b' => false], ['a/b' => false]],
554
-			['a/b', 'a/c', ['a' => false, 'a/b' => true, 'a/c' => false], []],
555
-		];
556
-	}
557
-
558
-	public static function moveSuccessProvider(): array {
559
-		return [
560
-			['a/b', 'b/b', ['a' => true, 'a/b' => true, 'b' => true, 'b/b' => false], ['a/b' => true]],
561
-			// older files with special chars can still be renamed to valid names
562
-			['a/b*', 'b/b', ['a' => true, 'a/b*' => true, 'b' => true, 'b/b' => false], ['a/b*' => true]],
563
-		];
564
-	}
565
-
566
-	private function moveTest(string $source, string $destination, array $updatables, array $deletables): void {
567
-		$view = new TestViewDirectory($updatables, $deletables);
568
-
569
-		$sourceInfo = new FileInfo($source, null, null, [
570
-			'type' => FileInfo::TYPE_FOLDER,
571
-		], null);
572
-		$targetInfo = new FileInfo(dirname($destination), null, null, [
573
-			'type' => FileInfo::TYPE_FOLDER,
574
-		], null);
575
-
576
-		$sourceNode = new Directory($view, $sourceInfo);
577
-		$targetNode = $this->getMockBuilder(Directory::class)
578
-			->onlyMethods(['childExists'])
579
-			->setConstructorArgs([$view, $targetInfo])
580
-			->getMock();
581
-		$targetNode->expects($this->any())->method('childExists')
582
-			->with(basename($destination))
583
-			->willReturn(false);
584
-		$this->assertTrue($targetNode->moveInto(basename($destination), $source, $sourceNode));
585
-	}
586
-
587
-
588
-	public function testFailingMove(): void {
589
-		$this->expectException(\Sabre\DAV\Exception\Forbidden::class);
590
-		$this->expectExceptionMessage('Could not copy directory b, target exists');
591
-
592
-		$source = 'a/b';
593
-		$destination = 'c/b';
594
-		$updatables = ['a' => true, 'a/b' => true, 'b' => true, 'c/b' => false];
595
-		$deletables = ['a/b' => true];
596
-
597
-		$view = new TestViewDirectory($updatables, $deletables);
598
-
599
-		$sourceInfo = new FileInfo($source, null, null, ['type' => FileInfo::TYPE_FOLDER], null);
600
-		$targetInfo = new FileInfo(dirname($destination), null, null, ['type' => FileInfo::TYPE_FOLDER], null);
601
-
602
-		$sourceNode = new Directory($view, $sourceInfo);
603
-		$targetNode = $this->getMockBuilder(Directory::class)
604
-			->onlyMethods(['childExists'])
605
-			->setConstructorArgs([$view, $targetInfo])
606
-			->getMock();
607
-		$targetNode->expects($this->once())->method('childExists')
608
-			->with(basename($destination))
609
-			->willReturn(true);
610
-
611
-		$targetNode->moveInto(basename($destination), $source, $sourceNode);
612
-	}
63
+    use UserTrait;
64
+
65
+    private View&MockObject $view;
66
+    private FileInfo&MockObject $info;
67
+    private IStorage&MockObject $storage;
68
+
69
+    protected function setUp(): void {
70
+        parent::setUp();
71
+
72
+        $this->view = $this->createMock(View::class);
73
+        $this->info = $this->createMock(FileInfo::class);
74
+        $this->storage = $this->createMock(IStorage::class);
75
+        $this->info->method('getStorage')
76
+            ->willReturn($this->storage);
77
+        $this->info->method('isReadable')
78
+            ->willReturn(true);
79
+        $this->info->method('getType')
80
+            ->willReturn(Node::TYPE_FOLDER);
81
+        $this->info->method('getName')
82
+            ->willReturn('folder');
83
+        $this->info->method('getPath')
84
+            ->willReturn('/admin/files/folder');
85
+        $this->info->method('getPermissions')
86
+            ->willReturn(Constants::PERMISSION_READ);
87
+    }
88
+
89
+    private function getDir(string $path = '/'): Directory {
90
+        $this->view->expects($this->once())
91
+            ->method('getRelativePath')
92
+            ->willReturn($path);
93
+
94
+        $this->info->expects($this->once())
95
+            ->method('getPath')
96
+            ->willReturn($path);
97
+
98
+        return new Directory($this->view, $this->info);
99
+    }
100
+
101
+
102
+    public function testDeleteRootFolderFails(): void {
103
+        $this->expectException(\Sabre\DAV\Exception\Forbidden::class);
104
+
105
+        $this->info->expects($this->any())
106
+            ->method('isDeletable')
107
+            ->willReturn(true);
108
+        $this->view->expects($this->never())
109
+            ->method('rmdir');
110
+        $dir = $this->getDir();
111
+        $dir->delete();
112
+    }
113
+
114
+
115
+    public function testDeleteForbidden(): void {
116
+        $this->expectException(Forbidden::class);
117
+
118
+        // deletion allowed
119
+        $this->info->expects($this->once())
120
+            ->method('isDeletable')
121
+            ->willReturn(true);
122
+
123
+        // but fails
124
+        $this->view->expects($this->once())
125
+            ->method('rmdir')
126
+            ->with('sub')
127
+            ->willThrowException(new ForbiddenException('', true));
128
+
129
+        $dir = $this->getDir('sub');
130
+        $dir->delete();
131
+    }
132
+
133
+
134
+    public function testDeleteFolderWhenAllowed(): void {
135
+        // deletion allowed
136
+        $this->info->expects($this->once())
137
+            ->method('isDeletable')
138
+            ->willReturn(true);
139
+
140
+        // but fails
141
+        $this->view->expects($this->once())
142
+            ->method('rmdir')
143
+            ->with('sub')
144
+            ->willReturn(true);
145
+
146
+        $dir = $this->getDir('sub');
147
+        $dir->delete();
148
+    }
149
+
150
+
151
+    public function testDeleteFolderFailsWhenNotAllowed(): void {
152
+        $this->expectException(\Sabre\DAV\Exception\Forbidden::class);
153
+
154
+        $this->info->expects($this->once())
155
+            ->method('isDeletable')
156
+            ->willReturn(false);
157
+
158
+        $dir = $this->getDir('sub');
159
+        $dir->delete();
160
+    }
161
+
162
+
163
+    public function testDeleteFolderThrowsWhenDeletionFailed(): void {
164
+        $this->expectException(\Sabre\DAV\Exception\Forbidden::class);
165
+
166
+        // deletion allowed
167
+        $this->info->expects($this->once())
168
+            ->method('isDeletable')
169
+            ->willReturn(true);
170
+
171
+        // but fails
172
+        $this->view->expects($this->once())
173
+            ->method('rmdir')
174
+            ->with('sub')
175
+            ->willReturn(false);
176
+
177
+        $dir = $this->getDir('sub');
178
+        $dir->delete();
179
+    }
180
+
181
+    public function testGetChildren(): void {
182
+        $info1 = $this->createMock(FileInfo::class);
183
+        $info2 = $this->createMock(FileInfo::class);
184
+        $info1->method('getName')
185
+            ->willReturn('first');
186
+        $info1->method('getPath')
187
+            ->willReturn('folder/first');
188
+        $info1->method('getEtag')
189
+            ->willReturn('abc');
190
+        $info2->method('getName')
191
+            ->willReturn('second');
192
+        $info2->method('getPath')
193
+            ->willReturn('folder/second');
194
+        $info2->method('getEtag')
195
+            ->willReturn('def');
196
+
197
+        $this->view->expects($this->once())
198
+            ->method('getDirectoryContent')
199
+            ->willReturn([$info1, $info2]);
200
+
201
+        $this->view->expects($this->any())
202
+            ->method('getRelativePath')
203
+            ->willReturnCallback(function ($path) {
204
+                return str_replace('/admin/files/', '', $path);
205
+            });
206
+
207
+        $this->view->expects($this->any())
208
+            ->method('getAbsolutePath')
209
+            ->willReturnCallback(function ($path) {
210
+                return Filesystem::normalizePath('/admin/files' . $path);
211
+            });
212
+
213
+        $this->overwriteService(View::class, $this->view);
214
+
215
+        $dir = new Directory($this->view, $this->info);
216
+        $nodes = $dir->getChildren();
217
+
218
+        $this->assertCount(2, $nodes);
219
+
220
+        // calling a second time just returns the cached values,
221
+        // does not call getDirectoryContents again
222
+        $dir->getChildren();
223
+    }
224
+
225
+
226
+    public function testGetChildrenNoPermission(): void {
227
+        $this->expectException(\Sabre\DAV\Exception\Forbidden::class);
228
+
229
+        $info = $this->createMock(FileInfo::class);
230
+        $info->expects($this->any())
231
+            ->method('isReadable')
232
+            ->willReturn(false);
233
+
234
+        $dir = new Directory($this->view, $info);
235
+        $dir->getChildren();
236
+    }
237
+
238
+
239
+    public function testGetChildNoPermission(): void {
240
+        $this->expectException(\Sabre\DAV\Exception\NotFound::class);
241
+
242
+        $this->info->expects($this->any())
243
+            ->method('isReadable')
244
+            ->willReturn(false);
245
+
246
+        $dir = new Directory($this->view, $this->info);
247
+        $dir->getChild('test');
248
+    }
249
+
250
+
251
+    public function testGetChildThrowStorageNotAvailableException(): void {
252
+        $this->expectException(\Sabre\DAV\Exception\ServiceUnavailable::class);
253
+
254
+        $this->view->expects($this->once())
255
+            ->method('getFileInfo')
256
+            ->willThrowException(new StorageNotAvailableException());
257
+
258
+        $dir = new Directory($this->view, $this->info);
259
+        $dir->getChild('.');
260
+    }
261
+
262
+
263
+    public function testGetChildThrowInvalidPath(): void {
264
+        $this->expectException(InvalidPath::class);
265
+
266
+        $this->view->expects($this->once())
267
+            ->method('verifyPath')
268
+            ->willThrowException(new InvalidPathException());
269
+        $this->view->expects($this->never())
270
+            ->method('getFileInfo');
271
+
272
+        $dir = new Directory($this->view, $this->info);
273
+        $dir->getChild('.');
274
+    }
275
+
276
+    public function testGetNodeForPath(): void {
277
+        $directoryNode = $this->createMock(Folder::class);
278
+        $pathNode = $this->createMock(Folder::class);
279
+        $pathParentNode = $this->createMock(Folder::class);
280
+        $storage = $this->createMock(IStorage::class);
281
+
282
+        $directoryNode->expects($this->once())
283
+            ->method('getStorage')
284
+            ->willReturn($storage);
285
+        $storage->expects($this->once())
286
+            ->method('instanceOfStorage')
287
+            ->willReturn(false);
288
+
289
+        $directoryNode->expects($this->once())
290
+            ->method('isReadable')
291
+            ->willReturn(true);
292
+        $directoryNode->expects($this->once())
293
+            ->method('getPath')
294
+            ->willReturn('/admin/files/');
295
+        $directoryNode->expects($this->once())
296
+            ->method('get')
297
+            ->willReturn($pathNode);
298
+
299
+        $pathNode->expects($this->once())
300
+            ->method('getPath')
301
+            ->willReturn('/admin/files/my/deep/folder/');
302
+        $pathNode->expects($this->once())
303
+            ->method('isReadable')
304
+            ->willReturn(true);
305
+        $pathNode->expects($this->once())
306
+            ->method('getMimetype')
307
+            ->willReturn(FileInfo::MIMETYPE_FOLDER);
308
+
309
+        $this->view->method('getRelativePath')
310
+            ->willReturnCallback(function ($path) {
311
+                return str_replace('/admin/files/', '', $path);
312
+            });
313
+
314
+        $this->view->expects($this->exactly(2))
315
+            ->method('getFileInfo')
316
+            ->willReturn($pathParentNode);
317
+
318
+        $pathParentNode->expects($this->exactly(2))
319
+            ->method('getPath')
320
+            ->willReturnOnConsecutiveCalls('/my/deep', '/my');
321
+        $pathParentNode->expects($this->exactly(2))
322
+            ->method('isReadable')
323
+            ->willReturn(true);
324
+
325
+        $dir = new Directory($this->view, $directoryNode);
326
+        $dir->getNodeForPath('/my/deep/folder/');
327
+    }
328
+
329
+    public function testGetNodeForPathFailsWithNoReadPermissionsForParent(): void {
330
+        $directoryNode = $this->createMock(Folder::class);
331
+        $pathNode = $this->createMock(Folder::class);
332
+        $pathParentNode = $this->createMock(Folder::class);
333
+        $storage = $this->createMock(IStorage::class);
334
+
335
+        $directoryNode->expects($this->once())
336
+            ->method('getStorage')
337
+            ->willReturn($storage);
338
+        $storage->expects($this->once())
339
+            ->method('instanceOfStorage')
340
+            ->willReturn(false);
341
+
342
+        $directoryNode->expects($this->once())
343
+            ->method('isReadable')
344
+            ->willReturn(true);
345
+        $directoryNode->expects($this->once())
346
+            ->method('getPath')
347
+            ->willReturn('/admin/files/');
348
+        $directoryNode->expects($this->once())
349
+            ->method('get')
350
+            ->willReturn($pathNode);
351
+
352
+        $pathNode->expects($this->once())
353
+            ->method('getPath')
354
+            ->willReturn('/admin/files/my/deep/folder/');
355
+        $pathNode->expects($this->once())
356
+            ->method('isReadable')
357
+            ->willReturn(true);
358
+        $pathNode->expects($this->once())
359
+            ->method('getMimetype')
360
+            ->willReturn(FileInfo::MIMETYPE_FOLDER);
361
+
362
+        $this->view->method('getRelativePath')
363
+            ->willReturnCallback(function ($path) {
364
+                return str_replace('/admin/files/', '', $path);
365
+            });
366
+
367
+        $this->view->expects($this->exactly(2))
368
+            ->method('getFileInfo')
369
+            ->willReturn($pathParentNode);
370
+
371
+        $pathParentNode->expects($this->exactly(2))
372
+            ->method('getPath')
373
+            ->willReturnOnConsecutiveCalls('/my/deep', '/my');
374
+        $pathParentNode->expects($this->exactly(2))
375
+            ->method('isReadable')
376
+            ->willReturnOnConsecutiveCalls(true, false);
377
+
378
+        $this->expectException(NotFound::class);
379
+
380
+        $dir = new Directory($this->view, $directoryNode);
381
+        $dir->getNodeForPath('/my/deep/folder/');
382
+    }
383
+
384
+    public function testGetNodeForPathFailsWithNoReadPermissionsForPath(): void {
385
+        $directoryNode = $this->createMock(Folder::class);
386
+        $pathNode = $this->createMock(Folder::class);
387
+        $storage = $this->createMock(IStorage::class);
388
+
389
+        $directoryNode->expects($this->once())
390
+            ->method('getStorage')
391
+            ->willReturn($storage);
392
+        $storage->expects($this->once())
393
+            ->method('instanceOfStorage')
394
+            ->willReturn(false);
395
+
396
+        $directoryNode->expects($this->once())
397
+            ->method('isReadable')
398
+            ->willReturn(true);
399
+        $directoryNode->expects($this->once())
400
+            ->method('getPath')
401
+            ->willReturn('/admin/files/');
402
+        $directoryNode->expects($this->once())
403
+            ->method('get')
404
+            ->willReturn($pathNode);
405
+
406
+        $pathNode->expects($this->once())
407
+            ->method('isReadable')
408
+            ->willReturn(false);
409
+
410
+        $this->expectException(\Sabre\DAV\Exception\Forbidden::class);
411
+
412
+        $dir = new Directory($this->view, $directoryNode);
413
+        $dir->getNodeForPath('/my/deep/folder/');
414
+    }
415
+
416
+    public function testGetQuotaInfoUnlimited(): void {
417
+        $this->createUser('user', 'password');
418
+        self::loginAsUser('user');
419
+        $mountPoint = $this->createMock(IMountPoint::class);
420
+        $storage = $this->createMock(Quota::class);
421
+        $mountPoint->method('getStorage')
422
+            ->willReturn($storage);
423
+
424
+        $storage->expects($this->any())
425
+            ->method('instanceOfStorage')
426
+            ->willReturnMap([
427
+                ['\OCA\Files_Sharing\SharedStorage', false],
428
+                ['\OC\Files\Storage\Wrapper\Quota', false],
429
+                [Storage::class, false],
430
+            ]);
431
+
432
+        $storage->expects($this->once())
433
+            ->method('getOwner')
434
+            ->willReturn('user');
435
+
436
+        $storage->expects($this->never())
437
+            ->method('getQuota');
438
+
439
+        $storage->expects($this->once())
440
+            ->method('free_space')
441
+            ->willReturn(800);
442
+
443
+        $this->info->expects($this->any())
444
+            ->method('getPath')
445
+            ->willReturn('/admin/files/foo');
446
+
447
+        $this->info->expects($this->once())
448
+            ->method('getSize')
449
+            ->willReturn(200);
450
+
451
+        $this->info->expects($this->once())
452
+            ->method('getMountPoint')
453
+            ->willReturn($mountPoint);
454
+
455
+        $this->view->expects($this->any())
456
+            ->method('getRelativePath')
457
+            ->willReturn('/foo');
458
+
459
+        $this->info->expects($this->once())
460
+            ->method('getInternalPath')
461
+            ->willReturn('/foo');
462
+
463
+        $mountPoint->method('getMountPoint')
464
+            ->willReturn('/user/files/mymountpoint');
465
+
466
+        $dir = new Directory($this->view, $this->info);
467
+        $this->assertEquals([200, -3], $dir->getQuotaInfo()); //200 used, unlimited
468
+    }
469
+
470
+    public function testGetQuotaInfoSpecific(): void {
471
+        $this->createUser('user', 'password');
472
+        self::loginAsUser('user');
473
+        $mountPoint = $this->createMock(IMountPoint::class);
474
+        $storage = $this->createMock(Quota::class);
475
+        $mountPoint->method('getStorage')
476
+            ->willReturn($storage);
477
+
478
+        $storage->expects($this->any())
479
+            ->method('instanceOfStorage')
480
+            ->willReturnMap([
481
+                ['\OCA\Files_Sharing\SharedStorage', false],
482
+                ['\OC\Files\Storage\Wrapper\Quota', true],
483
+                [Storage::class, false],
484
+            ]);
485
+
486
+        $storage->expects($this->once())
487
+            ->method('getOwner')
488
+            ->willReturn('user');
489
+
490
+        $storage->expects($this->once())
491
+            ->method('getQuota')
492
+            ->willReturn(1000);
493
+
494
+        $storage->expects($this->once())
495
+            ->method('free_space')
496
+            ->willReturn(800);
497
+
498
+        $this->info->expects($this->once())
499
+            ->method('getSize')
500
+            ->willReturn(200);
501
+
502
+        $this->info->expects($this->once())
503
+            ->method('getMountPoint')
504
+            ->willReturn($mountPoint);
505
+
506
+        $this->info->expects($this->once())
507
+            ->method('getInternalPath')
508
+            ->willReturn('/foo');
509
+
510
+        $mountPoint->method('getMountPoint')
511
+            ->willReturn('/user/files/mymountpoint');
512
+
513
+        $this->view->expects($this->any())
514
+            ->method('getRelativePath')
515
+            ->willReturn('/foo');
516
+
517
+        $dir = new Directory($this->view, $this->info);
518
+        $this->assertEquals([200, 800], $dir->getQuotaInfo()); //200 used, 800 free
519
+    }
520
+
521
+    #[\PHPUnit\Framework\Attributes\DataProvider('moveFailedProvider')]
522
+    public function testMoveFailed(string $source, string $destination, array $updatables, array $deletables): void {
523
+        $this->expectException(\Sabre\DAV\Exception\Forbidden::class);
524
+
525
+        $this->moveTest($source, $destination, $updatables, $deletables);
526
+    }
527
+
528
+    #[\PHPUnit\Framework\Attributes\DataProvider('moveSuccessProvider')]
529
+    public function testMoveSuccess(string $source, string $destination, array $updatables, array $deletables): void {
530
+        $this->moveTest($source, $destination, $updatables, $deletables);
531
+        $this->addToAssertionCount(1);
532
+    }
533
+
534
+    #[\PHPUnit\Framework\Attributes\DataProvider('moveFailedInvalidCharsProvider')]
535
+    public function testMoveFailedInvalidChars(string $source, string $destination, array $updatables, array $deletables): void {
536
+        $this->expectException(InvalidPath::class);
537
+
538
+        $this->moveTest($source, $destination, $updatables, $deletables);
539
+    }
540
+
541
+    public static function moveFailedInvalidCharsProvider(): array {
542
+        return [
543
+            ['a/valid', "a/i\nvalid", ['a' => true, 'a/valid' => true, 'a/c*' => false], []],
544
+        ];
545
+    }
546
+
547
+    public static function moveFailedProvider(): array {
548
+        return [
549
+            ['a/b', 'a/c', ['a' => false, 'a/b' => false, 'a/c' => false], []],
550
+            ['a/b', 'b/b', ['a' => false, 'a/b' => false, 'b' => false, 'b/b' => false], []],
551
+            ['a/b', 'b/b', ['a' => false, 'a/b' => true, 'b' => false, 'b/b' => false], []],
552
+            ['a/b', 'b/b', ['a' => true, 'a/b' => true, 'b' => false, 'b/b' => false], []],
553
+            ['a/b', 'b/b', ['a' => true, 'a/b' => true, 'b' => true, 'b/b' => false], ['a/b' => false]],
554
+            ['a/b', 'a/c', ['a' => false, 'a/b' => true, 'a/c' => false], []],
555
+        ];
556
+    }
557
+
558
+    public static function moveSuccessProvider(): array {
559
+        return [
560
+            ['a/b', 'b/b', ['a' => true, 'a/b' => true, 'b' => true, 'b/b' => false], ['a/b' => true]],
561
+            // older files with special chars can still be renamed to valid names
562
+            ['a/b*', 'b/b', ['a' => true, 'a/b*' => true, 'b' => true, 'b/b' => false], ['a/b*' => true]],
563
+        ];
564
+    }
565
+
566
+    private function moveTest(string $source, string $destination, array $updatables, array $deletables): void {
567
+        $view = new TestViewDirectory($updatables, $deletables);
568
+
569
+        $sourceInfo = new FileInfo($source, null, null, [
570
+            'type' => FileInfo::TYPE_FOLDER,
571
+        ], null);
572
+        $targetInfo = new FileInfo(dirname($destination), null, null, [
573
+            'type' => FileInfo::TYPE_FOLDER,
574
+        ], null);
575
+
576
+        $sourceNode = new Directory($view, $sourceInfo);
577
+        $targetNode = $this->getMockBuilder(Directory::class)
578
+            ->onlyMethods(['childExists'])
579
+            ->setConstructorArgs([$view, $targetInfo])
580
+            ->getMock();
581
+        $targetNode->expects($this->any())->method('childExists')
582
+            ->with(basename($destination))
583
+            ->willReturn(false);
584
+        $this->assertTrue($targetNode->moveInto(basename($destination), $source, $sourceNode));
585
+    }
586
+
587
+
588
+    public function testFailingMove(): void {
589
+        $this->expectException(\Sabre\DAV\Exception\Forbidden::class);
590
+        $this->expectExceptionMessage('Could not copy directory b, target exists');
591
+
592
+        $source = 'a/b';
593
+        $destination = 'c/b';
594
+        $updatables = ['a' => true, 'a/b' => true, 'b' => true, 'c/b' => false];
595
+        $deletables = ['a/b' => true];
596
+
597
+        $view = new TestViewDirectory($updatables, $deletables);
598
+
599
+        $sourceInfo = new FileInfo($source, null, null, ['type' => FileInfo::TYPE_FOLDER], null);
600
+        $targetInfo = new FileInfo(dirname($destination), null, null, ['type' => FileInfo::TYPE_FOLDER], null);
601
+
602
+        $sourceNode = new Directory($view, $sourceInfo);
603
+        $targetNode = $this->getMockBuilder(Directory::class)
604
+            ->onlyMethods(['childExists'])
605
+            ->setConstructorArgs([$view, $targetInfo])
606
+            ->getMock();
607
+        $targetNode->expects($this->once())->method('childExists')
608
+            ->with(basename($destination))
609
+            ->willReturn(true);
610
+
611
+        $targetNode->moveInto(basename($destination), $source, $sourceNode);
612
+    }
613 613
 }
Please login to merge, or discard this patch.
Spacing   +5 added lines, -5 removed lines patch added patch discarded remove patch
@@ -200,14 +200,14 @@  discard block
 block discarded – undo
200 200
 
201 201
 		$this->view->expects($this->any())
202 202
 			->method('getRelativePath')
203
-			->willReturnCallback(function ($path) {
203
+			->willReturnCallback(function($path) {
204 204
 				return str_replace('/admin/files/', '', $path);
205 205
 			});
206 206
 
207 207
 		$this->view->expects($this->any())
208 208
 			->method('getAbsolutePath')
209
-			->willReturnCallback(function ($path) {
210
-				return Filesystem::normalizePath('/admin/files' . $path);
209
+			->willReturnCallback(function($path) {
210
+				return Filesystem::normalizePath('/admin/files'.$path);
211 211
 			});
212 212
 
213 213
 		$this->overwriteService(View::class, $this->view);
@@ -307,7 +307,7 @@  discard block
 block discarded – undo
307 307
 			->willReturn(FileInfo::MIMETYPE_FOLDER);
308 308
 
309 309
 		$this->view->method('getRelativePath')
310
-			->willReturnCallback(function ($path) {
310
+			->willReturnCallback(function($path) {
311 311
 				return str_replace('/admin/files/', '', $path);
312 312
 			});
313 313
 
@@ -360,7 +360,7 @@  discard block
 block discarded – undo
360 360
 			->willReturn(FileInfo::MIMETYPE_FOLDER);
361 361
 
362 362
 		$this->view->method('getRelativePath')
363
-			->willReturnCallback(function ($path) {
363
+			->willReturnCallback(function($path) {
364 364
 				return str_replace('/admin/files/', '', $path);
365 365
 			});
366 366
 
Please login to merge, or discard this patch.
apps/dav/lib/Connector/Sabre/Directory.php 2 patches
Indentation   +530 added lines, -530 removed lines patch added patch discarded remove patch
@@ -42,534 +42,534 @@
 block discarded – undo
42 42
 use Sabre\DAV\INodeByPath;
43 43
 
44 44
 class Directory extends Node implements
45
-	\Sabre\DAV\ICollection,
46
-	\Sabre\DAV\IQuota,
47
-	\Sabre\DAV\IMoveTarget,
48
-	\Sabre\DAV\ICopyTarget,
49
-	INodeByPath {
50
-	/**
51
-	 * Cached directory content
52
-	 * @var FileInfo[]
53
-	 */
54
-	private ?array $dirContent = null;
55
-
56
-	/** Cached quota info */
57
-	private ?array $quotaInfo = null;
58
-
59
-	/**
60
-	 * Sets up the node, expects a full path name
61
-	 */
62
-	public function __construct(
63
-		View $view,
64
-		FileInfo $info,
65
-		private ?CachingTree $tree = null,
66
-		?IShareManager $shareManager = null,
67
-	) {
68
-		parent::__construct($view, $info, $shareManager);
69
-	}
70
-
71
-	/**
72
-	 * Creates a new file in the directory
73
-	 *
74
-	 * Data will either be supplied as a stream resource, or in certain cases
75
-	 * as a string. Keep in mind that you may have to support either.
76
-	 *
77
-	 * After successful creation of the file, you may choose to return the ETag
78
-	 * of the new file here.
79
-	 *
80
-	 * The returned ETag must be surrounded by double-quotes (The quotes should
81
-	 * be part of the actual string).
82
-	 *
83
-	 * If you cannot accurately determine the ETag, you should not return it.
84
-	 * If you don't store the file exactly as-is (you're transforming it
85
-	 * somehow) you should also not return an ETag.
86
-	 *
87
-	 * This means that if a subsequent GET to this new file does not exactly
88
-	 * return the same contents of what was submitted here, you are strongly
89
-	 * recommended to omit the ETag.
90
-	 *
91
-	 * @param string $name Name of the file
92
-	 * @param resource|string $data Initial payload
93
-	 * @return null|string
94
-	 * @throws Exception\EntityTooLarge
95
-	 * @throws Exception\UnsupportedMediaType
96
-	 * @throws FileLocked
97
-	 * @throws InvalidPath
98
-	 * @throws \Sabre\DAV\Exception
99
-	 * @throws \Sabre\DAV\Exception\BadRequest
100
-	 * @throws \Sabre\DAV\Exception\Forbidden
101
-	 * @throws \Sabre\DAV\Exception\ServiceUnavailable
102
-	 */
103
-	public function createFile($name, $data = null) {
104
-		try {
105
-			if (!$this->fileView->isCreatable($this->path)) {
106
-				throw new \Sabre\DAV\Exception\Forbidden();
107
-			}
108
-
109
-			$this->fileView->verifyPath($this->path, $name);
110
-
111
-			$path = $this->fileView->getAbsolutePath($this->path) . '/' . $name;
112
-			// in case the file already exists/overwriting
113
-			$info = $this->fileView->getFileInfo($this->path . '/' . $name);
114
-			if (!$info) {
115
-				// use a dummy FileInfo which is acceptable here since it will be refreshed after the put is complete
116
-				$info = new \OC\Files\FileInfo($path, null, null, [
117
-					'type' => FileInfo::TYPE_FILE
118
-				], null);
119
-			}
120
-			$node = new File($this->fileView, $info);
121
-
122
-			// only allow 1 process to upload a file at once but still allow reading the file while writing the part file
123
-			$node->acquireLock(ILockingProvider::LOCK_SHARED);
124
-			$this->fileView->lockFile($this->path . '/' . $name . '.upload.part', ILockingProvider::LOCK_EXCLUSIVE);
125
-
126
-			$result = $node->put($data);
127
-
128
-			$this->fileView->unlockFile($this->path . '/' . $name . '.upload.part', ILockingProvider::LOCK_EXCLUSIVE);
129
-			$node->releaseLock(ILockingProvider::LOCK_SHARED);
130
-			return $result;
131
-		} catch (StorageNotAvailableException $e) {
132
-			throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage(), $e->getCode(), $e);
133
-		} catch (InvalidPathException $ex) {
134
-			throw new InvalidPath($ex->getMessage(), false, $ex);
135
-		} catch (ForbiddenException $ex) {
136
-			throw new Forbidden($ex->getMessage(), $ex->getRetry(), $ex);
137
-		} catch (LockedException $e) {
138
-			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
139
-		}
140
-	}
141
-
142
-	/**
143
-	 * Creates a new subdirectory
144
-	 *
145
-	 * @param string $name
146
-	 * @throws FileLocked
147
-	 * @throws InvalidPath
148
-	 * @throws \Sabre\DAV\Exception\Forbidden
149
-	 * @throws \Sabre\DAV\Exception\ServiceUnavailable
150
-	 */
151
-	public function createDirectory($name) {
152
-		try {
153
-			if (!$this->info->isCreatable()) {
154
-				throw new \Sabre\DAV\Exception\Forbidden();
155
-			}
156
-
157
-			$this->fileView->verifyPath($this->path, $name);
158
-			$newPath = $this->path . '/' . $name;
159
-			if (!$this->fileView->mkdir($newPath)) {
160
-				throw new \Sabre\DAV\Exception\Forbidden('Could not create directory ' . $newPath);
161
-			}
162
-		} catch (StorageNotAvailableException $e) {
163
-			throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage(), 0, $e);
164
-		} catch (InvalidPathException $ex) {
165
-			throw new InvalidPath($ex->getMessage(), false, $ex);
166
-		} catch (ForbiddenException $ex) {
167
-			throw new Forbidden($ex->getMessage(), $ex->getRetry(), $ex);
168
-		} catch (LockedException $e) {
169
-			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
170
-		}
171
-	}
172
-
173
-	/**
174
-	 * Returns a specific child node, referenced by its name
175
-	 *
176
-	 * @param string $name
177
-	 * @param FileInfo $info
178
-	 * @return \Sabre\DAV\INode
179
-	 * @throws InvalidPath
180
-	 * @throws \Sabre\DAV\Exception\NotFound
181
-	 * @throws \Sabre\DAV\Exception\ServiceUnavailable
182
-	 */
183
-	public function getChild($name, $info = null, ?IRequest $request = null, ?IL10N $l10n = null) {
184
-		$storage = $this->info->getStorage();
185
-		$allowDirectory = false;
186
-
187
-		// Checking if we're in a file drop
188
-		// If we are, then only PUT and MKCOL are allowed (see plugin)
189
-		// so we are safe to return the directory without a risk of
190
-		// leaking files and folders structure.
191
-		if ($storage->instanceOfStorage(PublicShareWrapper::class)) {
192
-			$share = $storage->getShare();
193
-			$allowDirectory = ($share->getPermissions() & Constants::PERMISSION_READ) !== Constants::PERMISSION_READ;
194
-		}
195
-
196
-		// For file drop we need to be allowed to read the directory with the nickname
197
-		if (!$allowDirectory && !$this->info->isReadable()) {
198
-			// avoid detecting files through this way
199
-			throw new NotFound();
200
-		}
201
-
202
-		$path = $this->path . '/' . $name;
203
-		if (is_null($info)) {
204
-			try {
205
-				$this->fileView->verifyPath($this->path, $name, true);
206
-				$info = $this->fileView->getFileInfo($path);
207
-			} catch (StorageNotAvailableException $e) {
208
-				throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage(), 0, $e);
209
-			} catch (InvalidPathException $ex) {
210
-				throw new InvalidPath($ex->getMessage(), false, $ex);
211
-			} catch (ForbiddenException $e) {
212
-				throw new \Sabre\DAV\Exception\Forbidden($e->getMessage(), $e->getCode(), $e);
213
-			}
214
-		}
215
-
216
-		if (!$info) {
217
-			throw new \Sabre\DAV\Exception\NotFound('File with name ' . $path . ' could not be located');
218
-		}
219
-
220
-		if ($info->getMimeType() === FileInfo::MIMETYPE_FOLDER) {
221
-			$node = new Directory($this->fileView, $info, $this->tree, $this->shareManager);
222
-		} else {
223
-			// In case reading a directory was allowed but it turns out the node was a not a directory, reject it now.
224
-			if (!$this->info->isReadable()) {
225
-				throw new NotFound();
226
-			}
227
-
228
-			$node = new File($this->fileView, $info, $this->shareManager, $request, $l10n);
229
-		}
230
-		if ($this->tree) {
231
-			$this->tree->cacheNode($node);
232
-		}
233
-		return $node;
234
-	}
235
-
236
-	/**
237
-	 * Returns an array with all the child nodes
238
-	 *
239
-	 * @return \Sabre\DAV\INode[]
240
-	 * @throws \Sabre\DAV\Exception\Locked
241
-	 * @throws Forbidden
242
-	 */
243
-	public function getChildren() {
244
-		if (!is_null($this->dirContent)) {
245
-			return $this->dirContent;
246
-		}
247
-		try {
248
-			if (!$this->info->isReadable()) {
249
-				// return 403 instead of 404 because a 404 would make
250
-				// the caller believe that the collection itself does not exist
251
-				if (Server::get(IAppManager::class)->isEnabledForAnyone('files_accesscontrol')) {
252
-					throw new Forbidden('No read permissions. This might be caused by files_accesscontrol, check your configured rules');
253
-				} else {
254
-					throw new Forbidden('No read permissions');
255
-				}
256
-			}
257
-			$folderContent = $this->getNode()->getDirectoryListing();
258
-		} catch (LockedException $e) {
259
-			throw new Locked();
260
-		}
261
-
262
-		$nodes = [];
263
-		$request = Server::get(IRequest::class);
264
-		$l10nFactory = Server::get(IFactory::class);
265
-		$l10n = $l10nFactory->get(Application::APP_ID);
266
-		foreach ($folderContent as $info) {
267
-			$node = $this->getChild($info->getName(), $info, $request, $l10n);
268
-			$nodes[] = $node;
269
-		}
270
-		$this->dirContent = $nodes;
271
-		return $this->dirContent;
272
-	}
273
-
274
-	/**
275
-	 * Checks if a child exists.
276
-	 *
277
-	 * @param string $name
278
-	 * @return bool
279
-	 */
280
-	public function childExists($name) {
281
-		// note: here we do NOT resolve the chunk file name to the real file name
282
-		// to make sure we return false when checking for file existence with a chunk
283
-		// file name.
284
-		// This is to make sure that "createFile" is still triggered
285
-		// (required old code) instead of "updateFile".
286
-		//
287
-		// TODO: resolve chunk file name here and implement "updateFile"
288
-		$path = $this->path . '/' . $name;
289
-		return $this->fileView->file_exists($path);
290
-	}
291
-
292
-	/**
293
-	 * Deletes all files in this directory, and then itself
294
-	 *
295
-	 * @return void
296
-	 * @throws FileLocked
297
-	 * @throws \Sabre\DAV\Exception\Forbidden
298
-	 */
299
-	public function delete() {
300
-		if ($this->path === '' || $this->path === '/' || !$this->info->isDeletable()) {
301
-			throw new \Sabre\DAV\Exception\Forbidden();
302
-		}
303
-
304
-		try {
305
-			if (!$this->fileView->rmdir($this->path)) {
306
-				// assume it wasn't possible to remove due to permission issue
307
-				throw new \Sabre\DAV\Exception\Forbidden();
308
-			}
309
-		} catch (ForbiddenException $ex) {
310
-			throw new Forbidden($ex->getMessage(), $ex->getRetry());
311
-		} catch (LockedException $e) {
312
-			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
313
-		}
314
-	}
315
-
316
-	private function getLogger(): LoggerInterface {
317
-		return Server::get(LoggerInterface::class);
318
-	}
319
-
320
-	/**
321
-	 * Returns available diskspace information
322
-	 *
323
-	 * @return array
324
-	 */
325
-	public function getQuotaInfo() {
326
-		if ($this->quotaInfo) {
327
-			return $this->quotaInfo;
328
-		}
329
-		$relativePath = $this->fileView->getRelativePath($this->info->getPath());
330
-		if ($relativePath === null) {
331
-			$this->getLogger()->warning('error while getting quota as the relative path cannot be found');
332
-			return [0, 0];
333
-		}
334
-
335
-		try {
336
-			$storageInfo = \OC_Helper::getStorageInfo($relativePath, $this->info, false);
337
-			if ($storageInfo['quota'] === FileInfo::SPACE_UNLIMITED) {
338
-				$free = FileInfo::SPACE_UNLIMITED;
339
-			} else {
340
-				$free = $storageInfo['free'];
341
-			}
342
-			$this->quotaInfo = [
343
-				$storageInfo['used'],
344
-				$free
345
-			];
346
-			return $this->quotaInfo;
347
-		} catch (NotFoundException $e) {
348
-			$this->getLogger()->warning('error while getting quota into', ['exception' => $e]);
349
-			return [0, 0];
350
-		} catch (StorageNotAvailableException $e) {
351
-			$this->getLogger()->warning('error while getting quota into', ['exception' => $e]);
352
-			return [0, 0];
353
-		} catch (NotPermittedException $e) {
354
-			$this->getLogger()->warning('error while getting quota into', ['exception' => $e]);
355
-			return [0, 0];
356
-		}
357
-	}
358
-
359
-	/**
360
-	 * Moves a node into this collection.
361
-	 *
362
-	 * It is up to the implementors to:
363
-	 *   1. Create the new resource.
364
-	 *   2. Remove the old resource.
365
-	 *   3. Transfer any properties or other data.
366
-	 *
367
-	 * Generally you should make very sure that your collection can easily move
368
-	 * the move.
369
-	 *
370
-	 * If you don't, just return false, which will trigger sabre/dav to handle
371
-	 * the move itself. If you return true from this function, the assumption
372
-	 * is that the move was successful.
373
-	 *
374
-	 * @param string $targetName New local file/collection name.
375
-	 * @param string $fullSourcePath Full path to source node
376
-	 * @param INode $sourceNode Source node itself
377
-	 * @return bool
378
-	 * @throws BadRequest
379
-	 * @throws ServiceUnavailable
380
-	 * @throws Forbidden
381
-	 * @throws FileLocked
382
-	 * @throws \Sabre\DAV\Exception\Forbidden
383
-	 */
384
-	public function moveInto($targetName, $fullSourcePath, INode $sourceNode) {
385
-		if (!$sourceNode instanceof Node) {
386
-			// it's a file of another kind, like FutureFile
387
-			if ($sourceNode instanceof IFile) {
388
-				// fallback to default copy+delete handling
389
-				return false;
390
-			}
391
-			throw new BadRequest('Incompatible node types');
392
-		}
393
-
394
-		$destinationPath = $this->getPath() . '/' . $targetName;
395
-
396
-
397
-		$targetNodeExists = $this->childExists($targetName);
398
-
399
-		// at getNodeForPath we also check the path for isForbiddenFileOrDir
400
-		// with that we have covered both source and destination
401
-		if ($sourceNode instanceof Directory && $targetNodeExists) {
402
-			throw new \Sabre\DAV\Exception\Forbidden('Could not copy directory ' . $sourceNode->getName() . ', target exists');
403
-		}
404
-
405
-		[$sourceDir,] = \Sabre\Uri\split($sourceNode->getPath());
406
-		$destinationDir = $this->getPath();
407
-
408
-		$sourcePath = $sourceNode->getPath();
409
-
410
-		$isMovableMount = false;
411
-		$sourceMount = Server::get(IMountManager::class)->find($this->fileView->getAbsolutePath($sourcePath));
412
-		$internalPath = $sourceMount->getInternalPath($this->fileView->getAbsolutePath($sourcePath));
413
-		if ($sourceMount instanceof MoveableMount && $internalPath === '') {
414
-			$isMovableMount = true;
415
-		}
416
-
417
-		try {
418
-			$sameFolder = ($sourceDir === $destinationDir);
419
-			// if we're overwriting or same folder
420
-			if ($targetNodeExists || $sameFolder) {
421
-				// note that renaming a share mount point is always allowed
422
-				if (!$this->fileView->isUpdatable($destinationDir) && !$isMovableMount) {
423
-					throw new \Sabre\DAV\Exception\Forbidden();
424
-				}
425
-			} else {
426
-				if (!$this->fileView->isCreatable($destinationDir)) {
427
-					throw new \Sabre\DAV\Exception\Forbidden();
428
-				}
429
-			}
430
-
431
-			if (!$sameFolder) {
432
-				// moving to a different folder, source will be gone, like a deletion
433
-				// note that moving a share mount point is always allowed
434
-				if (!$this->fileView->isDeletable($sourcePath) && !$isMovableMount) {
435
-					throw new \Sabre\DAV\Exception\Forbidden();
436
-				}
437
-			}
438
-
439
-			$fileName = basename($destinationPath);
440
-			try {
441
-				$this->fileView->verifyPath($destinationDir, $fileName);
442
-			} catch (InvalidPathException $ex) {
443
-				throw new InvalidPath($ex->getMessage());
444
-			}
445
-
446
-			$renameOkay = $this->fileView->rename($sourcePath, $destinationPath);
447
-			if (!$renameOkay) {
448
-				throw new \Sabre\DAV\Exception\Forbidden('');
449
-			}
450
-		} catch (StorageNotAvailableException $e) {
451
-			throw new ServiceUnavailable($e->getMessage(), $e->getCode(), $e);
452
-		} catch (ForbiddenException $ex) {
453
-			throw new Forbidden($ex->getMessage(), $ex->getRetry(), $ex);
454
-		} catch (LockedException $e) {
455
-			throw new FileLocked($e->getMessage(), $e->getCode(), $e);
456
-		}
457
-
458
-		return true;
459
-	}
460
-
461
-
462
-	public function copyInto($targetName, $sourcePath, INode $sourceNode) {
463
-		if ($sourceNode instanceof File || $sourceNode instanceof Directory) {
464
-			try {
465
-				$destinationPath = $this->getPath() . '/' . $targetName;
466
-				$sourcePath = $sourceNode->getPath();
467
-
468
-				if (!$this->fileView->isCreatable($this->getPath())) {
469
-					throw new \Sabre\DAV\Exception\Forbidden();
470
-				}
471
-
472
-				try {
473
-					$this->fileView->verifyPath($this->getPath(), $targetName);
474
-				} catch (InvalidPathException $ex) {
475
-					throw new InvalidPath($ex->getMessage());
476
-				}
477
-
478
-				$copyOkay = $this->fileView->copy($sourcePath, $destinationPath);
479
-
480
-				if (!$copyOkay) {
481
-					throw new \Sabre\DAV\Exception\Forbidden('Copy did not proceed');
482
-				}
483
-
484
-				return true;
485
-			} catch (StorageNotAvailableException $e) {
486
-				throw new ServiceUnavailable($e->getMessage(), $e->getCode(), $e);
487
-			} catch (ForbiddenException $ex) {
488
-				throw new Forbidden($ex->getMessage(), $ex->getRetry(), $ex);
489
-			} catch (LockedException $e) {
490
-				throw new FileLocked($e->getMessage(), $e->getCode(), $e);
491
-			}
492
-		}
493
-
494
-		return false;
495
-	}
496
-
497
-	public function getNode(): Folder {
498
-		return $this->node;
499
-	}
500
-
501
-	public function getNodeForPath($path): INode {
502
-		$storage = $this->info->getStorage();
503
-		$allowDirectory = false;
504
-
505
-		// Checking if we're in a file drop
506
-		// If we are, then only PUT and MKCOL are allowed (see plugin)
507
-		// so we are safe to return the directory without a risk of
508
-		// leaking files and folders structure.
509
-		if ($storage->instanceOfStorage(PublicShareWrapper::class)) {
510
-			$share = $storage->getShare();
511
-			$allowDirectory = ($share->getPermissions() & Constants::PERMISSION_READ) !== Constants::PERMISSION_READ;
512
-		}
513
-
514
-		// For file drop we need to be allowed to read the directory with the nickname
515
-		if (!$allowDirectory && !$this->info->isReadable()) {
516
-			// avoid detecting files through this way
517
-			throw new NotFound();
518
-		}
519
-
520
-		$destinationPath = PathHelper::normalizePath($this->getPath() . '/' . $path);
521
-		$destinationDir = dirname($destinationPath);
522
-
523
-		try {
524
-			$info = $this->getNode()->get($path);
525
-		} catch (NotFoundException $e) {
526
-			throw new \Sabre\DAV\Exception\NotFound('File with name ' . $destinationPath
527
-				. ' could not be located');
528
-		} catch (StorageNotAvailableException $e) {
529
-			throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage(), 0, $e);
530
-		} catch (NotPermittedException $ex) {
531
-			throw new InvalidPath($ex->getMessage(), false, $ex);
532
-		}
533
-
534
-		// if not in a public share with no read permissions, throw Forbidden
535
-		if (!$allowDirectory && !$info->isReadable()) {
536
-			if (Server::get(IAppManager::class)->isEnabledForAnyone('files_accesscontrol')) {
537
-				throw new Forbidden('No read permissions. This might be caused by files_accesscontrol, check your configured rules');
538
-			}
539
-
540
-			throw new Forbidden('No read permissions');
541
-		}
542
-
543
-		if ($info->getMimeType() === FileInfo::MIMETYPE_FOLDER) {
544
-			$node = new \OCA\DAV\Connector\Sabre\Directory($this->fileView, $info, $this->tree, $this->shareManager);
545
-		} else {
546
-			// In case reading a directory was allowed but it turns out the node was a not a directory, reject it now.
547
-			if (!$this->info->isReadable()) {
548
-				throw new NotFound();
549
-			}
550
-
551
-			$node = new File($this->fileView, $info, $this->shareManager);
552
-		}
553
-		$this->tree?->cacheNode($node);
554
-
555
-		// recurse upwards until the root and check for read permissions to keep
556
-		// ACL checks working in files_accesscontrol
557
-		if (!$allowDirectory && $destinationDir !== '') {
558
-			$scanPath = $destinationPath;
559
-			while (($scanPath = dirname($scanPath)) !== '/') {
560
-				// fileView can get the parent info in a cheaper way compared
561
-				// to the node API
562
-				/** @psalm-suppress InternalMethod */
563
-				$info = $this->fileView->getFileInfo($scanPath, false);
564
-				$directory = new Directory($this->fileView, $info, $this->tree, $this->shareManager);
565
-				$readable = $directory->getNode()->isReadable();
566
-				if (!$readable) {
567
-					throw new \Sabre\DAV\Exception\NotFound('File with name ' . $destinationPath
568
-						. ' could not be located');
569
-				}
570
-			}
571
-		}
572
-
573
-		return $node;
574
-	}
45
+    \Sabre\DAV\ICollection,
46
+    \Sabre\DAV\IQuota,
47
+    \Sabre\DAV\IMoveTarget,
48
+    \Sabre\DAV\ICopyTarget,
49
+    INodeByPath {
50
+    /**
51
+     * Cached directory content
52
+     * @var FileInfo[]
53
+     */
54
+    private ?array $dirContent = null;
55
+
56
+    /** Cached quota info */
57
+    private ?array $quotaInfo = null;
58
+
59
+    /**
60
+     * Sets up the node, expects a full path name
61
+     */
62
+    public function __construct(
63
+        View $view,
64
+        FileInfo $info,
65
+        private ?CachingTree $tree = null,
66
+        ?IShareManager $shareManager = null,
67
+    ) {
68
+        parent::__construct($view, $info, $shareManager);
69
+    }
70
+
71
+    /**
72
+     * Creates a new file in the directory
73
+     *
74
+     * Data will either be supplied as a stream resource, or in certain cases
75
+     * as a string. Keep in mind that you may have to support either.
76
+     *
77
+     * After successful creation of the file, you may choose to return the ETag
78
+     * of the new file here.
79
+     *
80
+     * The returned ETag must be surrounded by double-quotes (The quotes should
81
+     * be part of the actual string).
82
+     *
83
+     * If you cannot accurately determine the ETag, you should not return it.
84
+     * If you don't store the file exactly as-is (you're transforming it
85
+     * somehow) you should also not return an ETag.
86
+     *
87
+     * This means that if a subsequent GET to this new file does not exactly
88
+     * return the same contents of what was submitted here, you are strongly
89
+     * recommended to omit the ETag.
90
+     *
91
+     * @param string $name Name of the file
92
+     * @param resource|string $data Initial payload
93
+     * @return null|string
94
+     * @throws Exception\EntityTooLarge
95
+     * @throws Exception\UnsupportedMediaType
96
+     * @throws FileLocked
97
+     * @throws InvalidPath
98
+     * @throws \Sabre\DAV\Exception
99
+     * @throws \Sabre\DAV\Exception\BadRequest
100
+     * @throws \Sabre\DAV\Exception\Forbidden
101
+     * @throws \Sabre\DAV\Exception\ServiceUnavailable
102
+     */
103
+    public function createFile($name, $data = null) {
104
+        try {
105
+            if (!$this->fileView->isCreatable($this->path)) {
106
+                throw new \Sabre\DAV\Exception\Forbidden();
107
+            }
108
+
109
+            $this->fileView->verifyPath($this->path, $name);
110
+
111
+            $path = $this->fileView->getAbsolutePath($this->path) . '/' . $name;
112
+            // in case the file already exists/overwriting
113
+            $info = $this->fileView->getFileInfo($this->path . '/' . $name);
114
+            if (!$info) {
115
+                // use a dummy FileInfo which is acceptable here since it will be refreshed after the put is complete
116
+                $info = new \OC\Files\FileInfo($path, null, null, [
117
+                    'type' => FileInfo::TYPE_FILE
118
+                ], null);
119
+            }
120
+            $node = new File($this->fileView, $info);
121
+
122
+            // only allow 1 process to upload a file at once but still allow reading the file while writing the part file
123
+            $node->acquireLock(ILockingProvider::LOCK_SHARED);
124
+            $this->fileView->lockFile($this->path . '/' . $name . '.upload.part', ILockingProvider::LOCK_EXCLUSIVE);
125
+
126
+            $result = $node->put($data);
127
+
128
+            $this->fileView->unlockFile($this->path . '/' . $name . '.upload.part', ILockingProvider::LOCK_EXCLUSIVE);
129
+            $node->releaseLock(ILockingProvider::LOCK_SHARED);
130
+            return $result;
131
+        } catch (StorageNotAvailableException $e) {
132
+            throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage(), $e->getCode(), $e);
133
+        } catch (InvalidPathException $ex) {
134
+            throw new InvalidPath($ex->getMessage(), false, $ex);
135
+        } catch (ForbiddenException $ex) {
136
+            throw new Forbidden($ex->getMessage(), $ex->getRetry(), $ex);
137
+        } catch (LockedException $e) {
138
+            throw new FileLocked($e->getMessage(), $e->getCode(), $e);
139
+        }
140
+    }
141
+
142
+    /**
143
+     * Creates a new subdirectory
144
+     *
145
+     * @param string $name
146
+     * @throws FileLocked
147
+     * @throws InvalidPath
148
+     * @throws \Sabre\DAV\Exception\Forbidden
149
+     * @throws \Sabre\DAV\Exception\ServiceUnavailable
150
+     */
151
+    public function createDirectory($name) {
152
+        try {
153
+            if (!$this->info->isCreatable()) {
154
+                throw new \Sabre\DAV\Exception\Forbidden();
155
+            }
156
+
157
+            $this->fileView->verifyPath($this->path, $name);
158
+            $newPath = $this->path . '/' . $name;
159
+            if (!$this->fileView->mkdir($newPath)) {
160
+                throw new \Sabre\DAV\Exception\Forbidden('Could not create directory ' . $newPath);
161
+            }
162
+        } catch (StorageNotAvailableException $e) {
163
+            throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage(), 0, $e);
164
+        } catch (InvalidPathException $ex) {
165
+            throw new InvalidPath($ex->getMessage(), false, $ex);
166
+        } catch (ForbiddenException $ex) {
167
+            throw new Forbidden($ex->getMessage(), $ex->getRetry(), $ex);
168
+        } catch (LockedException $e) {
169
+            throw new FileLocked($e->getMessage(), $e->getCode(), $e);
170
+        }
171
+    }
172
+
173
+    /**
174
+     * Returns a specific child node, referenced by its name
175
+     *
176
+     * @param string $name
177
+     * @param FileInfo $info
178
+     * @return \Sabre\DAV\INode
179
+     * @throws InvalidPath
180
+     * @throws \Sabre\DAV\Exception\NotFound
181
+     * @throws \Sabre\DAV\Exception\ServiceUnavailable
182
+     */
183
+    public function getChild($name, $info = null, ?IRequest $request = null, ?IL10N $l10n = null) {
184
+        $storage = $this->info->getStorage();
185
+        $allowDirectory = false;
186
+
187
+        // Checking if we're in a file drop
188
+        // If we are, then only PUT and MKCOL are allowed (see plugin)
189
+        // so we are safe to return the directory without a risk of
190
+        // leaking files and folders structure.
191
+        if ($storage->instanceOfStorage(PublicShareWrapper::class)) {
192
+            $share = $storage->getShare();
193
+            $allowDirectory = ($share->getPermissions() & Constants::PERMISSION_READ) !== Constants::PERMISSION_READ;
194
+        }
195
+
196
+        // For file drop we need to be allowed to read the directory with the nickname
197
+        if (!$allowDirectory && !$this->info->isReadable()) {
198
+            // avoid detecting files through this way
199
+            throw new NotFound();
200
+        }
201
+
202
+        $path = $this->path . '/' . $name;
203
+        if (is_null($info)) {
204
+            try {
205
+                $this->fileView->verifyPath($this->path, $name, true);
206
+                $info = $this->fileView->getFileInfo($path);
207
+            } catch (StorageNotAvailableException $e) {
208
+                throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage(), 0, $e);
209
+            } catch (InvalidPathException $ex) {
210
+                throw new InvalidPath($ex->getMessage(), false, $ex);
211
+            } catch (ForbiddenException $e) {
212
+                throw new \Sabre\DAV\Exception\Forbidden($e->getMessage(), $e->getCode(), $e);
213
+            }
214
+        }
215
+
216
+        if (!$info) {
217
+            throw new \Sabre\DAV\Exception\NotFound('File with name ' . $path . ' could not be located');
218
+        }
219
+
220
+        if ($info->getMimeType() === FileInfo::MIMETYPE_FOLDER) {
221
+            $node = new Directory($this->fileView, $info, $this->tree, $this->shareManager);
222
+        } else {
223
+            // In case reading a directory was allowed but it turns out the node was a not a directory, reject it now.
224
+            if (!$this->info->isReadable()) {
225
+                throw new NotFound();
226
+            }
227
+
228
+            $node = new File($this->fileView, $info, $this->shareManager, $request, $l10n);
229
+        }
230
+        if ($this->tree) {
231
+            $this->tree->cacheNode($node);
232
+        }
233
+        return $node;
234
+    }
235
+
236
+    /**
237
+     * Returns an array with all the child nodes
238
+     *
239
+     * @return \Sabre\DAV\INode[]
240
+     * @throws \Sabre\DAV\Exception\Locked
241
+     * @throws Forbidden
242
+     */
243
+    public function getChildren() {
244
+        if (!is_null($this->dirContent)) {
245
+            return $this->dirContent;
246
+        }
247
+        try {
248
+            if (!$this->info->isReadable()) {
249
+                // return 403 instead of 404 because a 404 would make
250
+                // the caller believe that the collection itself does not exist
251
+                if (Server::get(IAppManager::class)->isEnabledForAnyone('files_accesscontrol')) {
252
+                    throw new Forbidden('No read permissions. This might be caused by files_accesscontrol, check your configured rules');
253
+                } else {
254
+                    throw new Forbidden('No read permissions');
255
+                }
256
+            }
257
+            $folderContent = $this->getNode()->getDirectoryListing();
258
+        } catch (LockedException $e) {
259
+            throw new Locked();
260
+        }
261
+
262
+        $nodes = [];
263
+        $request = Server::get(IRequest::class);
264
+        $l10nFactory = Server::get(IFactory::class);
265
+        $l10n = $l10nFactory->get(Application::APP_ID);
266
+        foreach ($folderContent as $info) {
267
+            $node = $this->getChild($info->getName(), $info, $request, $l10n);
268
+            $nodes[] = $node;
269
+        }
270
+        $this->dirContent = $nodes;
271
+        return $this->dirContent;
272
+    }
273
+
274
+    /**
275
+     * Checks if a child exists.
276
+     *
277
+     * @param string $name
278
+     * @return bool
279
+     */
280
+    public function childExists($name) {
281
+        // note: here we do NOT resolve the chunk file name to the real file name
282
+        // to make sure we return false when checking for file existence with a chunk
283
+        // file name.
284
+        // This is to make sure that "createFile" is still triggered
285
+        // (required old code) instead of "updateFile".
286
+        //
287
+        // TODO: resolve chunk file name here and implement "updateFile"
288
+        $path = $this->path . '/' . $name;
289
+        return $this->fileView->file_exists($path);
290
+    }
291
+
292
+    /**
293
+     * Deletes all files in this directory, and then itself
294
+     *
295
+     * @return void
296
+     * @throws FileLocked
297
+     * @throws \Sabre\DAV\Exception\Forbidden
298
+     */
299
+    public function delete() {
300
+        if ($this->path === '' || $this->path === '/' || !$this->info->isDeletable()) {
301
+            throw new \Sabre\DAV\Exception\Forbidden();
302
+        }
303
+
304
+        try {
305
+            if (!$this->fileView->rmdir($this->path)) {
306
+                // assume it wasn't possible to remove due to permission issue
307
+                throw new \Sabre\DAV\Exception\Forbidden();
308
+            }
309
+        } catch (ForbiddenException $ex) {
310
+            throw new Forbidden($ex->getMessage(), $ex->getRetry());
311
+        } catch (LockedException $e) {
312
+            throw new FileLocked($e->getMessage(), $e->getCode(), $e);
313
+        }
314
+    }
315
+
316
+    private function getLogger(): LoggerInterface {
317
+        return Server::get(LoggerInterface::class);
318
+    }
319
+
320
+    /**
321
+     * Returns available diskspace information
322
+     *
323
+     * @return array
324
+     */
325
+    public function getQuotaInfo() {
326
+        if ($this->quotaInfo) {
327
+            return $this->quotaInfo;
328
+        }
329
+        $relativePath = $this->fileView->getRelativePath($this->info->getPath());
330
+        if ($relativePath === null) {
331
+            $this->getLogger()->warning('error while getting quota as the relative path cannot be found');
332
+            return [0, 0];
333
+        }
334
+
335
+        try {
336
+            $storageInfo = \OC_Helper::getStorageInfo($relativePath, $this->info, false);
337
+            if ($storageInfo['quota'] === FileInfo::SPACE_UNLIMITED) {
338
+                $free = FileInfo::SPACE_UNLIMITED;
339
+            } else {
340
+                $free = $storageInfo['free'];
341
+            }
342
+            $this->quotaInfo = [
343
+                $storageInfo['used'],
344
+                $free
345
+            ];
346
+            return $this->quotaInfo;
347
+        } catch (NotFoundException $e) {
348
+            $this->getLogger()->warning('error while getting quota into', ['exception' => $e]);
349
+            return [0, 0];
350
+        } catch (StorageNotAvailableException $e) {
351
+            $this->getLogger()->warning('error while getting quota into', ['exception' => $e]);
352
+            return [0, 0];
353
+        } catch (NotPermittedException $e) {
354
+            $this->getLogger()->warning('error while getting quota into', ['exception' => $e]);
355
+            return [0, 0];
356
+        }
357
+    }
358
+
359
+    /**
360
+     * Moves a node into this collection.
361
+     *
362
+     * It is up to the implementors to:
363
+     *   1. Create the new resource.
364
+     *   2. Remove the old resource.
365
+     *   3. Transfer any properties or other data.
366
+     *
367
+     * Generally you should make very sure that your collection can easily move
368
+     * the move.
369
+     *
370
+     * If you don't, just return false, which will trigger sabre/dav to handle
371
+     * the move itself. If you return true from this function, the assumption
372
+     * is that the move was successful.
373
+     *
374
+     * @param string $targetName New local file/collection name.
375
+     * @param string $fullSourcePath Full path to source node
376
+     * @param INode $sourceNode Source node itself
377
+     * @return bool
378
+     * @throws BadRequest
379
+     * @throws ServiceUnavailable
380
+     * @throws Forbidden
381
+     * @throws FileLocked
382
+     * @throws \Sabre\DAV\Exception\Forbidden
383
+     */
384
+    public function moveInto($targetName, $fullSourcePath, INode $sourceNode) {
385
+        if (!$sourceNode instanceof Node) {
386
+            // it's a file of another kind, like FutureFile
387
+            if ($sourceNode instanceof IFile) {
388
+                // fallback to default copy+delete handling
389
+                return false;
390
+            }
391
+            throw new BadRequest('Incompatible node types');
392
+        }
393
+
394
+        $destinationPath = $this->getPath() . '/' . $targetName;
395
+
396
+
397
+        $targetNodeExists = $this->childExists($targetName);
398
+
399
+        // at getNodeForPath we also check the path for isForbiddenFileOrDir
400
+        // with that we have covered both source and destination
401
+        if ($sourceNode instanceof Directory && $targetNodeExists) {
402
+            throw new \Sabre\DAV\Exception\Forbidden('Could not copy directory ' . $sourceNode->getName() . ', target exists');
403
+        }
404
+
405
+        [$sourceDir,] = \Sabre\Uri\split($sourceNode->getPath());
406
+        $destinationDir = $this->getPath();
407
+
408
+        $sourcePath = $sourceNode->getPath();
409
+
410
+        $isMovableMount = false;
411
+        $sourceMount = Server::get(IMountManager::class)->find($this->fileView->getAbsolutePath($sourcePath));
412
+        $internalPath = $sourceMount->getInternalPath($this->fileView->getAbsolutePath($sourcePath));
413
+        if ($sourceMount instanceof MoveableMount && $internalPath === '') {
414
+            $isMovableMount = true;
415
+        }
416
+
417
+        try {
418
+            $sameFolder = ($sourceDir === $destinationDir);
419
+            // if we're overwriting or same folder
420
+            if ($targetNodeExists || $sameFolder) {
421
+                // note that renaming a share mount point is always allowed
422
+                if (!$this->fileView->isUpdatable($destinationDir) && !$isMovableMount) {
423
+                    throw new \Sabre\DAV\Exception\Forbidden();
424
+                }
425
+            } else {
426
+                if (!$this->fileView->isCreatable($destinationDir)) {
427
+                    throw new \Sabre\DAV\Exception\Forbidden();
428
+                }
429
+            }
430
+
431
+            if (!$sameFolder) {
432
+                // moving to a different folder, source will be gone, like a deletion
433
+                // note that moving a share mount point is always allowed
434
+                if (!$this->fileView->isDeletable($sourcePath) && !$isMovableMount) {
435
+                    throw new \Sabre\DAV\Exception\Forbidden();
436
+                }
437
+            }
438
+
439
+            $fileName = basename($destinationPath);
440
+            try {
441
+                $this->fileView->verifyPath($destinationDir, $fileName);
442
+            } catch (InvalidPathException $ex) {
443
+                throw new InvalidPath($ex->getMessage());
444
+            }
445
+
446
+            $renameOkay = $this->fileView->rename($sourcePath, $destinationPath);
447
+            if (!$renameOkay) {
448
+                throw new \Sabre\DAV\Exception\Forbidden('');
449
+            }
450
+        } catch (StorageNotAvailableException $e) {
451
+            throw new ServiceUnavailable($e->getMessage(), $e->getCode(), $e);
452
+        } catch (ForbiddenException $ex) {
453
+            throw new Forbidden($ex->getMessage(), $ex->getRetry(), $ex);
454
+        } catch (LockedException $e) {
455
+            throw new FileLocked($e->getMessage(), $e->getCode(), $e);
456
+        }
457
+
458
+        return true;
459
+    }
460
+
461
+
462
+    public function copyInto($targetName, $sourcePath, INode $sourceNode) {
463
+        if ($sourceNode instanceof File || $sourceNode instanceof Directory) {
464
+            try {
465
+                $destinationPath = $this->getPath() . '/' . $targetName;
466
+                $sourcePath = $sourceNode->getPath();
467
+
468
+                if (!$this->fileView->isCreatable($this->getPath())) {
469
+                    throw new \Sabre\DAV\Exception\Forbidden();
470
+                }
471
+
472
+                try {
473
+                    $this->fileView->verifyPath($this->getPath(), $targetName);
474
+                } catch (InvalidPathException $ex) {
475
+                    throw new InvalidPath($ex->getMessage());
476
+                }
477
+
478
+                $copyOkay = $this->fileView->copy($sourcePath, $destinationPath);
479
+
480
+                if (!$copyOkay) {
481
+                    throw new \Sabre\DAV\Exception\Forbidden('Copy did not proceed');
482
+                }
483
+
484
+                return true;
485
+            } catch (StorageNotAvailableException $e) {
486
+                throw new ServiceUnavailable($e->getMessage(), $e->getCode(), $e);
487
+            } catch (ForbiddenException $ex) {
488
+                throw new Forbidden($ex->getMessage(), $ex->getRetry(), $ex);
489
+            } catch (LockedException $e) {
490
+                throw new FileLocked($e->getMessage(), $e->getCode(), $e);
491
+            }
492
+        }
493
+
494
+        return false;
495
+    }
496
+
497
+    public function getNode(): Folder {
498
+        return $this->node;
499
+    }
500
+
501
+    public function getNodeForPath($path): INode {
502
+        $storage = $this->info->getStorage();
503
+        $allowDirectory = false;
504
+
505
+        // Checking if we're in a file drop
506
+        // If we are, then only PUT and MKCOL are allowed (see plugin)
507
+        // so we are safe to return the directory without a risk of
508
+        // leaking files and folders structure.
509
+        if ($storage->instanceOfStorage(PublicShareWrapper::class)) {
510
+            $share = $storage->getShare();
511
+            $allowDirectory = ($share->getPermissions() & Constants::PERMISSION_READ) !== Constants::PERMISSION_READ;
512
+        }
513
+
514
+        // For file drop we need to be allowed to read the directory with the nickname
515
+        if (!$allowDirectory && !$this->info->isReadable()) {
516
+            // avoid detecting files through this way
517
+            throw new NotFound();
518
+        }
519
+
520
+        $destinationPath = PathHelper::normalizePath($this->getPath() . '/' . $path);
521
+        $destinationDir = dirname($destinationPath);
522
+
523
+        try {
524
+            $info = $this->getNode()->get($path);
525
+        } catch (NotFoundException $e) {
526
+            throw new \Sabre\DAV\Exception\NotFound('File with name ' . $destinationPath
527
+                . ' could not be located');
528
+        } catch (StorageNotAvailableException $e) {
529
+            throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage(), 0, $e);
530
+        } catch (NotPermittedException $ex) {
531
+            throw new InvalidPath($ex->getMessage(), false, $ex);
532
+        }
533
+
534
+        // if not in a public share with no read permissions, throw Forbidden
535
+        if (!$allowDirectory && !$info->isReadable()) {
536
+            if (Server::get(IAppManager::class)->isEnabledForAnyone('files_accesscontrol')) {
537
+                throw new Forbidden('No read permissions. This might be caused by files_accesscontrol, check your configured rules');
538
+            }
539
+
540
+            throw new Forbidden('No read permissions');
541
+        }
542
+
543
+        if ($info->getMimeType() === FileInfo::MIMETYPE_FOLDER) {
544
+            $node = new \OCA\DAV\Connector\Sabre\Directory($this->fileView, $info, $this->tree, $this->shareManager);
545
+        } else {
546
+            // In case reading a directory was allowed but it turns out the node was a not a directory, reject it now.
547
+            if (!$this->info->isReadable()) {
548
+                throw new NotFound();
549
+            }
550
+
551
+            $node = new File($this->fileView, $info, $this->shareManager);
552
+        }
553
+        $this->tree?->cacheNode($node);
554
+
555
+        // recurse upwards until the root and check for read permissions to keep
556
+        // ACL checks working in files_accesscontrol
557
+        if (!$allowDirectory && $destinationDir !== '') {
558
+            $scanPath = $destinationPath;
559
+            while (($scanPath = dirname($scanPath)) !== '/') {
560
+                // fileView can get the parent info in a cheaper way compared
561
+                // to the node API
562
+                /** @psalm-suppress InternalMethod */
563
+                $info = $this->fileView->getFileInfo($scanPath, false);
564
+                $directory = new Directory($this->fileView, $info, $this->tree, $this->shareManager);
565
+                $readable = $directory->getNode()->isReadable();
566
+                if (!$readable) {
567
+                    throw new \Sabre\DAV\Exception\NotFound('File with name ' . $destinationPath
568
+                        . ' could not be located');
569
+                }
570
+            }
571
+        }
572
+
573
+        return $node;
574
+    }
575 575
 }
Please login to merge, or discard this patch.
Spacing   +16 added lines, -16 removed lines patch added patch discarded remove patch
@@ -108,9 +108,9 @@  discard block
 block discarded – undo
108 108
 
109 109
 			$this->fileView->verifyPath($this->path, $name);
110 110
 
111
-			$path = $this->fileView->getAbsolutePath($this->path) . '/' . $name;
111
+			$path = $this->fileView->getAbsolutePath($this->path).'/'.$name;
112 112
 			// in case the file already exists/overwriting
113
-			$info = $this->fileView->getFileInfo($this->path . '/' . $name);
113
+			$info = $this->fileView->getFileInfo($this->path.'/'.$name);
114 114
 			if (!$info) {
115 115
 				// use a dummy FileInfo which is acceptable here since it will be refreshed after the put is complete
116 116
 				$info = new \OC\Files\FileInfo($path, null, null, [
@@ -121,11 +121,11 @@  discard block
 block discarded – undo
121 121
 
122 122
 			// only allow 1 process to upload a file at once but still allow reading the file while writing the part file
123 123
 			$node->acquireLock(ILockingProvider::LOCK_SHARED);
124
-			$this->fileView->lockFile($this->path . '/' . $name . '.upload.part', ILockingProvider::LOCK_EXCLUSIVE);
124
+			$this->fileView->lockFile($this->path.'/'.$name.'.upload.part', ILockingProvider::LOCK_EXCLUSIVE);
125 125
 
126 126
 			$result = $node->put($data);
127 127
 
128
-			$this->fileView->unlockFile($this->path . '/' . $name . '.upload.part', ILockingProvider::LOCK_EXCLUSIVE);
128
+			$this->fileView->unlockFile($this->path.'/'.$name.'.upload.part', ILockingProvider::LOCK_EXCLUSIVE);
129 129
 			$node->releaseLock(ILockingProvider::LOCK_SHARED);
130 130
 			return $result;
131 131
 		} catch (StorageNotAvailableException $e) {
@@ -155,9 +155,9 @@  discard block
 block discarded – undo
155 155
 			}
156 156
 
157 157
 			$this->fileView->verifyPath($this->path, $name);
158
-			$newPath = $this->path . '/' . $name;
158
+			$newPath = $this->path.'/'.$name;
159 159
 			if (!$this->fileView->mkdir($newPath)) {
160
-				throw new \Sabre\DAV\Exception\Forbidden('Could not create directory ' . $newPath);
160
+				throw new \Sabre\DAV\Exception\Forbidden('Could not create directory '.$newPath);
161 161
 			}
162 162
 		} catch (StorageNotAvailableException $e) {
163 163
 			throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage(), 0, $e);
@@ -199,7 +199,7 @@  discard block
 block discarded – undo
199 199
 			throw new NotFound();
200 200
 		}
201 201
 
202
-		$path = $this->path . '/' . $name;
202
+		$path = $this->path.'/'.$name;
203 203
 		if (is_null($info)) {
204 204
 			try {
205 205
 				$this->fileView->verifyPath($this->path, $name, true);
@@ -214,7 +214,7 @@  discard block
 block discarded – undo
214 214
 		}
215 215
 
216 216
 		if (!$info) {
217
-			throw new \Sabre\DAV\Exception\NotFound('File with name ' . $path . ' could not be located');
217
+			throw new \Sabre\DAV\Exception\NotFound('File with name '.$path.' could not be located');
218 218
 		}
219 219
 
220 220
 		if ($info->getMimeType() === FileInfo::MIMETYPE_FOLDER) {
@@ -285,7 +285,7 @@  discard block
 block discarded – undo
285 285
 		// (required old code) instead of "updateFile".
286 286
 		//
287 287
 		// TODO: resolve chunk file name here and implement "updateFile"
288
-		$path = $this->path . '/' . $name;
288
+		$path = $this->path.'/'.$name;
289 289
 		return $this->fileView->file_exists($path);
290 290
 	}
291 291
 
@@ -391,7 +391,7 @@  discard block
 block discarded – undo
391 391
 			throw new BadRequest('Incompatible node types');
392 392
 		}
393 393
 
394
-		$destinationPath = $this->getPath() . '/' . $targetName;
394
+		$destinationPath = $this->getPath().'/'.$targetName;
395 395
 
396 396
 
397 397
 		$targetNodeExists = $this->childExists($targetName);
@@ -399,10 +399,10 @@  discard block
 block discarded – undo
399 399
 		// at getNodeForPath we also check the path for isForbiddenFileOrDir
400 400
 		// with that we have covered both source and destination
401 401
 		if ($sourceNode instanceof Directory && $targetNodeExists) {
402
-			throw new \Sabre\DAV\Exception\Forbidden('Could not copy directory ' . $sourceNode->getName() . ', target exists');
402
+			throw new \Sabre\DAV\Exception\Forbidden('Could not copy directory '.$sourceNode->getName().', target exists');
403 403
 		}
404 404
 
405
-		[$sourceDir,] = \Sabre\Uri\split($sourceNode->getPath());
405
+		[$sourceDir, ] = \Sabre\Uri\split($sourceNode->getPath());
406 406
 		$destinationDir = $this->getPath();
407 407
 
408 408
 		$sourcePath = $sourceNode->getPath();
@@ -462,7 +462,7 @@  discard block
 block discarded – undo
462 462
 	public function copyInto($targetName, $sourcePath, INode $sourceNode) {
463 463
 		if ($sourceNode instanceof File || $sourceNode instanceof Directory) {
464 464
 			try {
465
-				$destinationPath = $this->getPath() . '/' . $targetName;
465
+				$destinationPath = $this->getPath().'/'.$targetName;
466 466
 				$sourcePath = $sourceNode->getPath();
467 467
 
468 468
 				if (!$this->fileView->isCreatable($this->getPath())) {
@@ -517,13 +517,13 @@  discard block
 block discarded – undo
517 517
 			throw new NotFound();
518 518
 		}
519 519
 
520
-		$destinationPath = PathHelper::normalizePath($this->getPath() . '/' . $path);
520
+		$destinationPath = PathHelper::normalizePath($this->getPath().'/'.$path);
521 521
 		$destinationDir = dirname($destinationPath);
522 522
 
523 523
 		try {
524 524
 			$info = $this->getNode()->get($path);
525 525
 		} catch (NotFoundException $e) {
526
-			throw new \Sabre\DAV\Exception\NotFound('File with name ' . $destinationPath
526
+			throw new \Sabre\DAV\Exception\NotFound('File with name '.$destinationPath
527 527
 				. ' could not be located');
528 528
 		} catch (StorageNotAvailableException $e) {
529 529
 			throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage(), 0, $e);
@@ -564,7 +564,7 @@  discard block
 block discarded – undo
564 564
 				$directory = new Directory($this->fileView, $info, $this->tree, $this->shareManager);
565 565
 				$readable = $directory->getNode()->isReadable();
566 566
 				if (!$readable) {
567
-					throw new \Sabre\DAV\Exception\NotFound('File with name ' . $destinationPath
567
+					throw new \Sabre\DAV\Exception\NotFound('File with name '.$destinationPath
568 568
 						. ' could not be located');
569 569
 				}
570 570
 			}
Please login to merge, or discard this patch.