Skip to content

Commit

Permalink
fix(CorePlugin): Handle Depth header in HTTP MOVE
Browse files Browse the repository at this point in the history
Only `Depth: infinity` is allowed, ref: rfc4918#section-9.9.2

Signed-off-by: Ferdinand Thiessen <[email protected]>
  • Loading branch information
susnux committed Sep 5, 2024
1 parent 6e72bc2 commit 9dfa4be
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 3 deletions.
5 changes: 5 additions & 0 deletions lib/DAV/CorePlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
14 changes: 11 additions & 3 deletions lib/DAV/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down Expand Up @@ -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,
Expand Down
50 changes: 50 additions & 0 deletions tests/Sabre/DAV/CorePluginTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,61 @@

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()
{
$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);
}
}
68 changes: 68 additions & 0 deletions tests/Sabre/DAV/ServerCopyMoveTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

declare(strict_types=1);

namespace Sabre\DAV;

use Sabre\DAV\Exception\BadRequest;
use Sabre\HTTP;

class ServerCopyMoveTest extends AbstractServer
{
/**
* Only 'infinity' and positiv (incl. 0) numbers are allowed
* @dataProvider dataInvalidDepthHeader
*/
public function testInvalidDepthHeader(?string $headerValue)
{
$request = new HTTP\Request('COPY', '/', $headerValue !== null ? ['Depth' => $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,
],
];
}
}

0 comments on commit 9dfa4be

Please sign in to comment.