Signed-off-by: Salvatore Martire <4652631+salmart-dev@users.noreply.github.com>pull/53621/head
parent
d00519d7ec
commit
8167b07118
@ -0,0 +1,68 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
namespace OCA\DAV\Upload; |
||||
|
||||
use Sabre\DAV\Exception\NotFound; |
||||
use Sabre\DAV\ICollection; |
||||
use Sabre\DAV\Server; |
||||
use Sabre\DAV\ServerPlugin; |
||||
use Sabre\HTTP\RequestInterface; |
||||
use Sabre\HTTP\ResponseInterface; |
||||
use function Sabre\Uri\split as uriSplit; |
||||
|
||||
/** |
||||
* Class that allows automatically creating non-existing collections on file |
||||
* upload. |
||||
* |
||||
* Since this functionality is not WebDAV compliant, it needs a special |
||||
* header to be activated. |
||||
*/ |
||||
class UploadAutoMkcolPlugin extends ServerPlugin { |
||||
|
||||
private Server $server; |
||||
|
||||
public function initialize(Server $server): void { |
||||
$server->on('beforeMethod:PUT', [$this, 'beforeMethod']); |
||||
$this->server = $server; |
||||
} |
||||
|
||||
/** |
||||
* @throws NotFound a node expected to exist cannot be found |
||||
*/ |
||||
public function beforeMethod(RequestInterface $request, ResponseInterface $response): bool { |
||||
if ($request->getHeader('X-NC-WebDAV-Auto-Mkcol') !== '1') { |
||||
return true; |
||||
} |
||||
|
||||
[$path,] = uriSplit($request->getPath()); |
||||
|
||||
if ($this->server->tree->nodeExists($path)) { |
||||
return true; |
||||
} |
||||
|
||||
$parts = explode('/', trim($path, '/')); |
||||
$rootPath = array_shift($parts); |
||||
$node = $this->server->tree->getNodeForPath('/' . $rootPath); |
||||
|
||||
if (!($node instanceof ICollection)) { |
||||
// the root node is not a collection, let SabreDAV handle it |
||||
return true; |
||||
} |
||||
|
||||
foreach ($parts as $part) { |
||||
if (!$node->childExists($part)) { |
||||
$node->createDirectory($part); |
||||
} |
||||
|
||||
$node = $node->getChild($part); |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
} |
||||
@ -0,0 +1,135 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
namespace OCA\DAV\Tests\unit\Upload; |
||||
|
||||
use Generator; |
||||
use OCA\DAV\Upload\UploadAutoMkcolPlugin; |
||||
use PHPUnit\Framework\MockObject\MockObject; |
||||
use Sabre\DAV\ICollection; |
||||
use Sabre\DAV\INode; |
||||
use Sabre\DAV\Server; |
||||
use Sabre\DAV\Tree; |
||||
use Sabre\HTTP\RequestInterface; |
||||
use Sabre\HTTP\ResponseInterface; |
||||
use Test\TestCase; |
||||
|
||||
class UploadAutoMkcolPluginTest extends TestCase { |
||||
|
||||
private Tree&MockObject $tree; |
||||
private RequestInterface&MockObject $request; |
||||
private ResponseInterface&MockObject $response; |
||||
|
||||
public static function dataMissingHeaderShouldReturnTrue(): Generator { |
||||
yield 'missing X-NC-WebDAV-Auto-Mkcol header' => [null]; |
||||
yield 'empty X-NC-WebDAV-Auto-Mkcol header' => ['']; |
||||
yield 'invalid X-NC-WebDAV-Auto-Mkcol header' => ['enable']; |
||||
} |
||||
|
||||
public function testBeforeMethodWithRootNodeNotAnICollectionShouldReturnTrue(): void { |
||||
$this->request->method('getHeader')->willReturn('1'); |
||||
$this->request->expects(self::once()) |
||||
->method('getPath') |
||||
->willReturn('/non-relevant/path.txt'); |
||||
$this->tree->expects(self::once()) |
||||
->method('nodeExists') |
||||
->with('/non-relevant') |
||||
->willReturn(false); |
||||
|
||||
$mockNode = $this->getMockBuilder(INode::class); |
||||
$this->tree->expects(self::once()) |
||||
->method('getNodeForPath') |
||||
->willReturn($mockNode); |
||||
|
||||
$return = $this->plugin->beforeMethod($this->request, $this->response); |
||||
$this->assertTrue($return); |
||||
} |
||||
|
||||
/** |
||||
* @dataProvider dataMissingHeaderShouldReturnTrue |
||||
*/ |
||||
public function testBeforeMethodWithMissingHeaderShouldReturnTrue(?string $header): void { |
||||
$this->request->expects(self::once()) |
||||
->method('getHeader') |
||||
->with('X-NC-WebDAV-Auto-Mkcol') |
||||
->willReturn($header); |
||||
|
||||
$this->request->expects(self::never()) |
||||
->method('getPath'); |
||||
|
||||
$return = $this->plugin->beforeMethod($this->request, $this->response); |
||||
self::assertTrue($return); |
||||
} |
||||
|
||||
public function testBeforeMethodWithExistingPathShouldReturnTrue(): void { |
||||
$this->request->method('getHeader')->willReturn('1'); |
||||
$this->request->expects(self::once()) |
||||
->method('getPath') |
||||
->willReturn('/files/user/deep/image.jpg'); |
||||
$this->tree->expects(self::once()) |
||||
->method('nodeExists') |
||||
->with('/files/user/deep') |
||||
->willReturn(true); |
||||
|
||||
$this->tree->expects(self::never()) |
||||
->method('getNodeForPath'); |
||||
|
||||
$return = $this->plugin->beforeMethod($this->request, $this->response); |
||||
self::assertTrue($return); |
||||
} |
||||
|
||||
public function testBeforeMethodShouldSucceed(): void { |
||||
$this->request->method('getHeader')->willReturn('1'); |
||||
$this->request->expects(self::once()) |
||||
->method('getPath') |
||||
->willReturn('/files/user/my/deep/path/image.jpg'); |
||||
$this->tree->expects(self::once()) |
||||
->method('nodeExists') |
||||
->with('/files/user/my/deep/path') |
||||
->willReturn(false); |
||||
|
||||
$mockNode = $this->createMock(ICollection::class); |
||||
$this->tree->expects(self::once()) |
||||
->method('getNodeForPath') |
||||
->with('/files') |
||||
->willReturn($mockNode); |
||||
$mockNode->expects(self::exactly(4)) |
||||
->method('childExists') |
||||
->willReturnMap([ |
||||
['user', true], |
||||
['my', true], |
||||
['deep', false], |
||||
['path', false], |
||||
]); |
||||
$mockNode->expects(self::exactly(2)) |
||||
->method('createDirectory'); |
||||
$mockNode->expects(self::exactly(4)) |
||||
->method('getChild') |
||||
->willReturn($mockNode); |
||||
|
||||
$return = $this->plugin->beforeMethod($this->request, $this->response); |
||||
self::assertTrue($return); |
||||
} |
||||
|
||||
protected function setUp(): void { |
||||
parent::setUp(); |
||||
|
||||
$server = $this->createMock(Server::class); |
||||
$this->tree = $this->createMock(Tree::class); |
||||
|
||||
$server->tree = $this->tree; |
||||
$this->plugin = new UploadAutoMkcolPlugin(); |
||||
|
||||
$this->request = $this->createMock(RequestInterface::class); |
||||
$this->response = $this->createMock(ResponseInterface::class); |
||||
$server->httpRequest = $this->request; |
||||
$server->httpResponse = $this->response; |
||||
|
||||
$this->plugin->initialize($server); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue