diff --git a/lib/DAV/CorePlugin.php b/lib/DAV/CorePlugin.php index dbd8976b17..429832cdb8 100644 --- a/lib/DAV/CorePlugin.php +++ b/lib/DAV/CorePlugin.php @@ -589,6 +589,11 @@ public function httpMove(RequestInterface $request, ResponseInterface $response) $moveInfo = $this->server->getCopyAndMoveInfo($request); + // MOVE does only allow "infinity" every other header value is considered invalid + if ($moveInfo['depth'] !== 'infinity') { + throw new BadRequest('The HTTP Depth header must only contain "infinity" for MOVE'); + } + if ($moveInfo['destinationExists']) { if (!$this->server->emit('beforeUnbind', [$moveInfo['destination']])) { return false; diff --git a/lib/DAV/Server.php b/lib/DAV/Server.php index 3133e54ad3..19b1cda97c 100644 --- a/lib/DAV/Server.php +++ b/lib/DAV/Server.php @@ -725,10 +725,17 @@ public function getCopyAndMoveInfo(RequestInterface $request) throw new Exception\BadRequest('The destination header was not supplied'); } $destination = $this->calculateUri($request->getHeader('Destination')); - $overwrite = $request->getHeader('Overwrite'); - if (!$overwrite) { - $overwrite = 'T'; + + // Depth of inifinty is valid for MOVE and COPY. If it is not set the RFC requires to act like it was 'infinity'. + $depth = strtolower($request->getHeader('Depth') ?? 'infinity'); + if ($depth !== 'infinity' && is_numeric($depth)) { + $depth = (int)$depth; + if ($depth < 0) { + throw new Exception\BadRequest('The HTTP Depth header may only be "infinity", 0 or a positiv number'); + } } + + $overwrite = $request->getHeader('Overwrite') ?? 'T'; if ('T' == strtoupper($overwrite)) { $overwrite = true; } elseif ('F' == strtoupper($overwrite)) { @@ -773,6 +780,7 @@ public function getCopyAndMoveInfo(RequestInterface $request) // These are the three relevant properties we need to return return [ + 'depth' => $depth, 'destination' => $destination, 'destinationExists' => (bool) $destinationNode, 'destinationNode' => $destinationNode, diff --git a/tests/Sabre/DAV/CorePluginTest.php b/tests/Sabre/DAV/CorePluginTest.php index 152a50ff55..0749e56d08 100644 --- a/tests/Sabre/DAV/CorePluginTest.php +++ b/tests/Sabre/DAV/CorePluginTest.php @@ -4,6 +4,10 @@ namespace Sabre\DAV; +use PHPUnit\Framework\MockObject\MockObject; +use Sabre\DAV\Exception\BadRequest; +use Sabre\HTTP; + class CorePluginTest extends \PHPUnit\Framework\TestCase { public function testGetInfo() @@ -11,4 +15,50 @@ public function testGetInfo() $corePlugin = new CorePlugin(); self::assertEquals('core', $corePlugin->getPluginInfo()['name']); } + + public function moveInvalidDepthHeaderProvider() { + return [ + [0], + [1], + ]; + } + + /** + * MOVE does only allow "infinity" every other header value is considered invalid + * @dataProvider moveInvalidDepthHeaderProvider + */ + public function testMoveWithInvalidDepth($depthHeader) { + $request = new HTTP\Request('MOVE', '/path/'); + $response = new HTTP\Response(); + + /** @var Server|MockObject */ + $server = $this->getMockBuilder(Server::class)->getMock(); + $corePlugin = new CorePlugin(); + $corePlugin->initialize($server); + + $server->expects($this->once()) + ->method('getCopyAndMoveInfo') + ->willReturn(['depth' => $depthHeader]); + + $this->expectException(BadRequest::class); + $corePlugin->httpMove($request, $response); + } + + /** + * MOVE does only allow "infinity" every other header value is considered invalid + */ + public function testMoveSupportsDepth() { + $request = new HTTP\Request('MOVE', '/path/'); + $response = new HTTP\Response(); + + /** @var Server|MockObject */ + $server = $this->getMockBuilder(Server::class)->getMock(); + $corePlugin = new CorePlugin(); + $corePlugin->initialize($server); + + $server->expects($this->once()) + ->method('getCopyAndMoveInfo') + ->willReturn(['depth' => 'infinity', 'destinationExists' => true, 'destination' => 'dst']); + $corePlugin->httpMove($request, $response); + } } diff --git a/tests/Sabre/DAV/ServerCopyMoveTest.php b/tests/Sabre/DAV/ServerCopyMoveTest.php new file mode 100644 index 0000000000..df4f279694 --- /dev/null +++ b/tests/Sabre/DAV/ServerCopyMoveTest.php @@ -0,0 +1,68 @@ + $headerValue] : []); + + $this->expectException(BadRequest::class); + $this->server->getCopyAndMoveInfo($request); + } + + public function dataInvalidDepthHeader() { + return [ + ['-1'], + ['0.5'], + ['2f'], + ['inf'], + ]; + } + + /** + * Only 'infinity' and positiv (incl. 0) numbers are allowed + * @dataProvider dataDepthHeader + */ + public function testValidDepthHeader(array $depthHeader, string|int $expectedDepth) + { + $request = new HTTP\Request('COPY', '/', array_merge(['Destination' => '/dst'], $depthHeader)); + + $this->assertEquals($expectedDepth, $this->server->getCopyAndMoveInfo($request)['depth']); + } + + public function dataDepthHeader() { + return [ + [ + [], + 'infinity', + ], + [ + ['Depth' => 'infinity'], + 'infinity', + ], + [ + ['Depth' => 'INFINITY'], + 'infinity', + ], + [ + ['Depth' => '0'], + 0, + ], + [ + ['Depth' => '10'], + 10, + ], + ]; + } +}