Browse Source
Merge pull request #7533 from nextcloud/oc-28545-handle-oc-total-length-in-new-chunking
Merge pull request #7533 from nextcloud/oc-28545-handle-oc-total-length-in-new-chunking
[oc] Handle OC-Total-Length in new chunkingpull/7686/head
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 351 additions and 121 deletions
-
4apps/dav/bin/chunkperf.php
-
1apps/dav/composer/composer/autoload_classmap.php
-
1apps/dav/composer/composer/autoload_static.php
-
12apps/dav/lib/Connector/Sabre/File.php
-
54apps/dav/lib/Connector/Sabre/FilesPlugin.php
-
14apps/dav/lib/Connector/Sabre/Node.php
-
2apps/dav/lib/Server.php
-
106apps/dav/lib/Upload/ChunkingPlugin.php
-
4apps/dav/tests/unit/Connector/Sabre/FileTest.php
-
57apps/dav/tests/unit/Connector/Sabre/FilesPluginTest.php
-
167apps/dav/tests/unit/Upload/ChunkingPluginTest.php
-
13apps/files/js/file-upload.js
-
17build/integration/features/bootstrap/WebDav.php
-
20build/integration/features/webdav-related.feature
@ -0,0 +1,106 @@ |
|||
<?php |
|||
/** |
|||
* @author Thomas Müller <thomas.mueller@tmit.eu> |
|||
* |
|||
* @copyright Copyright (c) 2017, ownCloud GmbH |
|||
* @license AGPL-3.0 |
|||
* |
|||
* This code is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License, version 3, |
|||
* as published by the Free Software Foundation. |
|||
* |
|||
* This program is distributed in the hope that it will be useful, |
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
* GNU Affero General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Affero General Public License, version 3, |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/> |
|||
* |
|||
*/ |
|||
|
|||
|
|||
namespace OCA\DAV\Upload; |
|||
|
|||
|
|||
use OCA\DAV\Connector\Sabre\File; |
|||
use Sabre\DAV\Exception\BadRequest; |
|||
use Sabre\DAV\Server; |
|||
use Sabre\DAV\ServerPlugin; |
|||
|
|||
class ChunkingPlugin extends ServerPlugin { |
|||
|
|||
/** @var Server */ |
|||
private $server; |
|||
/** @var FutureFile */ |
|||
private $sourceNode; |
|||
|
|||
/** |
|||
* @inheritdoc |
|||
*/ |
|||
function initialize(Server $server) { |
|||
$server->on('beforeMove', [$this, 'beforeMove']); |
|||
$this->server = $server; |
|||
} |
|||
|
|||
/** |
|||
* @param string $sourcePath source path |
|||
* @param string $destination destination path |
|||
*/ |
|||
function beforeMove($sourcePath, $destination) { |
|||
$this->sourceNode = $this->server->tree->getNodeForPath($sourcePath); |
|||
if (!$this->sourceNode instanceof FutureFile) { |
|||
// skip handling as the source is not a chunked FutureFile
|
|||
return; |
|||
} |
|||
|
|||
$this->verifySize(); |
|||
return $this->performMove($sourcePath, $destination); |
|||
} |
|||
|
|||
/** |
|||
* Move handler for future file. |
|||
* |
|||
* This overrides the default move behavior to prevent Sabre |
|||
* to delete the target file before moving. Because deleting would |
|||
* lose the file id and metadata. |
|||
* |
|||
* @param string $path source path |
|||
* @param string $destination destination path |
|||
* @return bool|void false to stop handling, void to skip this handler |
|||
*/ |
|||
public function performMove($path, $destination) { |
|||
if (!$this->server->tree->nodeExists($destination)) { |
|||
// skip and let the default handler do its work
|
|||
return; |
|||
} |
|||
|
|||
// do a move manually, skipping Sabre's default "delete" for existing nodes
|
|||
$this->server->tree->move($path, $destination); |
|||
|
|||
// trigger all default events (copied from CorePlugin::move)
|
|||
$this->server->emit('afterMove', [$path, $destination]); |
|||
$this->server->emit('afterUnbind', [$path]); |
|||
$this->server->emit('afterBind', [$destination]); |
|||
|
|||
$response = $this->server->httpResponse; |
|||
$response->setHeader('Content-Length', '0'); |
|||
$response->setStatus(204); |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* @throws BadRequest |
|||
*/ |
|||
private function verifySize() { |
|||
$expectedSize = $this->server->httpRequest->getHeader('OC-Total-Length'); |
|||
if ($expectedSize === null) { |
|||
return; |
|||
} |
|||
$actualSize = $this->sourceNode->getSize(); |
|||
if ((int)$expectedSize !== $actualSize) { |
|||
throw new BadRequest("Chunks on server do not sum up to $expectedSize but to $actualSize bytes"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,167 @@ |
|||
<?php |
|||
/** |
|||
* @author Thomas Müller <thomas.mueller@tmit.eu> |
|||
* |
|||
* @copyright Copyright (c) 2017, ownCloud GmbH |
|||
* @license AGPL-3.0 |
|||
* |
|||
* This code is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Affero General Public License, version 3, |
|||
* as published by the Free Software Foundation. |
|||
* |
|||
* This program is distributed in the hope that it will be useful, |
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
* GNU Affero General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Affero General Public License, version 3, |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/> |
|||
* |
|||
*/ |
|||
|
|||
|
|||
namespace OCA\DAV\Tests\unit\Upload; |
|||
|
|||
|
|||
use OCA\DAV\Upload\ChunkingPlugin; |
|||
use Sabre\HTTP\RequestInterface; |
|||
use Sabre\HTTP\ResponseInterface; |
|||
use Test\TestCase; |
|||
use OCA\DAV\Upload\FutureFile; |
|||
use OCA\DAV\Connector\Sabre\Directory; |
|||
|
|||
class ChunkingPluginTest extends TestCase { |
|||
|
|||
|
|||
/** |
|||
* @var \Sabre\DAV\Server | \PHPUnit_Framework_MockObject_MockObject |
|||
*/ |
|||
private $server; |
|||
|
|||
/** |
|||
* @var \Sabre\DAV\Tree | \PHPUnit_Framework_MockObject_MockObject |
|||
*/ |
|||
private $tree; |
|||
|
|||
/** |
|||
* @var ChunkingPlugin |
|||
*/ |
|||
private $plugin; |
|||
/** @var RequestInterface | \PHPUnit_Framework_MockObject_MockObject */ |
|||
private $request; |
|||
/** @var ResponseInterface | \PHPUnit_Framework_MockObject_MockObject */ |
|||
private $response; |
|||
|
|||
public function setUp() { |
|||
parent::setUp(); |
|||
|
|||
$this->server = $this->getMockBuilder('\Sabre\DAV\Server') |
|||
->disableOriginalConstructor() |
|||
->getMock(); |
|||
$this->tree = $this->getMockBuilder('\Sabre\DAV\Tree') |
|||
->disableOriginalConstructor() |
|||
->getMock(); |
|||
|
|||
$this->server->tree = $this->tree; |
|||
$this->plugin = new ChunkingPlugin(); |
|||
|
|||
$this->request = $this->createMock(RequestInterface::class); |
|||
$this->response = $this->createMock(ResponseInterface::class); |
|||
$this->server->httpRequest = $this->request; |
|||
$this->server->httpResponse = $this->response; |
|||
|
|||
$this->plugin->initialize($this->server); |
|||
} |
|||
|
|||
public function testBeforeMoveFutureFileSkip() { |
|||
$node = $this->createMock(Directory::class); |
|||
|
|||
$this->tree->expects($this->any()) |
|||
->method('getNodeForPath') |
|||
->with('source') |
|||
->will($this->returnValue($node)); |
|||
$this->response->expects($this->never()) |
|||
->method('setStatus'); |
|||
|
|||
$this->assertNull($this->plugin->beforeMove('source', 'target')); |
|||
} |
|||
|
|||
public function testBeforeMoveFutureFileSkipNonExisting() { |
|||
$sourceNode = $this->createMock(FutureFile::class); |
|||
$sourceNode->expects($this->once()) |
|||
->method('getSize') |
|||
->willReturn(4); |
|||
|
|||
$this->tree->expects($this->any()) |
|||
->method('getNodeForPath') |
|||
->with('source') |
|||
->will($this->returnValue($sourceNode)); |
|||
$this->tree->expects($this->any()) |
|||
->method('nodeExists') |
|||
->with('target') |
|||
->will($this->returnValue(false)); |
|||
$this->response->expects($this->never()) |
|||
->method('setStatus'); |
|||
$this->request->expects($this->once()) |
|||
->method('getHeader') |
|||
->with('OC-Total-Length') |
|||
->willReturn(4); |
|||
|
|||
$this->assertNull($this->plugin->beforeMove('source', 'target')); |
|||
} |
|||
|
|||
public function testBeforeMoveFutureFileMoveIt() { |
|||
$sourceNode = $this->createMock(FutureFile::class); |
|||
$sourceNode->expects($this->once()) |
|||
->method('getSize') |
|||
->willReturn(4); |
|||
|
|||
$this->tree->expects($this->any()) |
|||
->method('getNodeForPath') |
|||
->with('source') |
|||
->will($this->returnValue($sourceNode)); |
|||
$this->tree->expects($this->any()) |
|||
->method('nodeExists') |
|||
->with('target') |
|||
->will($this->returnValue(true)); |
|||
$this->tree->expects($this->once()) |
|||
->method('move') |
|||
->with('source', 'target'); |
|||
|
|||
$this->response->expects($this->once()) |
|||
->method('setHeader') |
|||
->with('Content-Length', '0'); |
|||
$this->response->expects($this->once()) |
|||
->method('setStatus') |
|||
->with(204); |
|||
$this->request->expects($this->once()) |
|||
->method('getHeader') |
|||
->with('OC-Total-Length') |
|||
->willReturn('4'); |
|||
|
|||
$this->assertFalse($this->plugin->beforeMove('source', 'target')); |
|||
} |
|||
|
|||
/** |
|||
* @expectedException \Sabre\DAV\Exception\BadRequest |
|||
* @expectedExceptionMessage Chunks on server do not sum up to 4 but to 3 bytes |
|||
*/ |
|||
public function testBeforeMoveSizeIsWrong() { |
|||
$sourceNode = $this->createMock(FutureFile::class); |
|||
$sourceNode->expects($this->once()) |
|||
->method('getSize') |
|||
->willReturn(3); |
|||
|
|||
$this->tree->expects($this->any()) |
|||
->method('getNodeForPath') |
|||
->with('source') |
|||
->will($this->returnValue($sourceNode)); |
|||
$this->request->expects($this->once()) |
|||
->method('getHeader') |
|||
->with('OC-Total-Length') |
|||
->willReturn('4'); |
|||
|
|||
$this->assertFalse($this->plugin->beforeMove('source', 'target')); |
|||
} |
|||
|
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue