ChunkingPlugin.php 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-FileCopyrightText: 2017 ownCloud GmbH
  5. * SPDX-License-Identifier: AGPL-3.0-only
  6. */
  7. namespace OCA\DAV\Upload;
  8. use OCA\DAV\Connector\Sabre\Directory;
  9. use OCA\DAV\Connector\Sabre\Exception\Forbidden;
  10. use Sabre\DAV\Exception\BadRequest;
  11. use Sabre\DAV\Exception\NotFound;
  12. use Sabre\DAV\INode;
  13. use Sabre\DAV\Server;
  14. use Sabre\DAV\ServerPlugin;
  15. class ChunkingPlugin extends ServerPlugin {
  16. /** @var Server */
  17. private $server;
  18. /** @var FutureFile */
  19. private $sourceNode;
  20. /**
  21. * @inheritdoc
  22. */
  23. public function initialize(Server $server) {
  24. $server->on('beforeMove', [$this, 'beforeMove']);
  25. $this->server = $server;
  26. }
  27. /**
  28. * @param string $sourcePath source path
  29. * @param string $destination destination path
  30. * @return bool|void
  31. * @throws BadRequest
  32. * @throws NotFound
  33. */
  34. public function beforeMove($sourcePath, $destination) {
  35. $this->sourceNode = $this->server->tree->getNodeForPath($sourcePath);
  36. if (!$this->sourceNode instanceof FutureFile) {
  37. // skip handling as the source is not a chunked FutureFile
  38. return;
  39. }
  40. try {
  41. /** @var INode $destinationNode */
  42. $destinationNode = $this->server->tree->getNodeForPath($destination);
  43. if ($destinationNode instanceof Directory) {
  44. throw new BadRequest("The given destination $destination is a directory.");
  45. }
  46. } catch (NotFound $e) {
  47. // If the destination does not exist yet it's not a directory either ;)
  48. }
  49. $this->verifySize();
  50. return $this->performMove($sourcePath, $destination);
  51. }
  52. /**
  53. * Move handler for future file.
  54. *
  55. * This overrides the default move behavior to prevent Sabre
  56. * to delete the target file before moving. Because deleting would
  57. * lose the file id and metadata.
  58. *
  59. * @param string $path source path
  60. * @param string $destination destination path
  61. * @return bool|void false to stop handling, void to skip this handler
  62. */
  63. public function performMove($path, $destination) {
  64. $fileExists = $this->server->tree->nodeExists($destination);
  65. // do a move manually, skipping Sabre's default "delete" for existing nodes
  66. try {
  67. $this->server->tree->move($path, $destination);
  68. } catch (Forbidden $e) {
  69. $sourceNode = $this->server->tree->getNodeForPath($path);
  70. if ($sourceNode instanceof FutureFile) {
  71. $sourceNode->delete();
  72. }
  73. throw $e;
  74. }
  75. // trigger all default events (copied from CorePlugin::move)
  76. $this->server->emit('afterMove', [$path, $destination]);
  77. $this->server->emit('afterUnbind', [$path]);
  78. $this->server->emit('afterBind', [$destination]);
  79. $response = $this->server->httpResponse;
  80. $response->setHeader('Content-Length', '0');
  81. $response->setStatus($fileExists ? 204 : 201);
  82. return false;
  83. }
  84. /**
  85. * @throws BadRequest
  86. */
  87. private function verifySize() {
  88. $expectedSize = $this->server->httpRequest->getHeader('OC-Total-Length');
  89. if ($expectedSize === null) {
  90. return;
  91. }
  92. $actualSize = $this->sourceNode->getSize();
  93. // casted to string because cast to float cause equality for non equal numbers
  94. // and integer has the problem of limited size on 32 bit systems
  95. if ((string)$expectedSize !== (string)$actualSize) {
  96. throw new BadRequest("Chunks on server do not sum up to $expectedSize but to $actualSize bytes");
  97. }
  98. }
  99. }