ChunkingPlugin.php 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2017, ownCloud GmbH
  4. *
  5. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  6. * @author Daniel Kesselberg <mail@danielkesselberg.de>
  7. * @author Julius Härtl <jus@bitgrid.net>
  8. * @author Morris Jobke <hey@morrisjobke.de>
  9. * @author Thomas Müller <thomas.mueller@tmit.eu>
  10. *
  11. * @license AGPL-3.0
  12. *
  13. * This code is free software: you can redistribute it and/or modify
  14. * it under the terms of the GNU Affero General Public License, version 3,
  15. * as published by the Free Software Foundation.
  16. *
  17. * This program is distributed in the hope that it will be useful,
  18. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. * GNU Affero General Public License for more details.
  21. *
  22. * You should have received a copy of the GNU Affero General Public License, version 3,
  23. * along with this program. If not, see <http://www.gnu.org/licenses/>
  24. *
  25. */
  26. namespace OCA\DAV\Upload;
  27. use OCA\DAV\Connector\Sabre\Directory;
  28. use OCA\DAV\Connector\Sabre\Exception\Forbidden;
  29. use Sabre\DAV\Exception\BadRequest;
  30. use Sabre\DAV\Exception\NotFound;
  31. use Sabre\DAV\INode;
  32. use Sabre\DAV\Server;
  33. use Sabre\DAV\ServerPlugin;
  34. class ChunkingPlugin extends ServerPlugin {
  35. /** @var Server */
  36. private $server;
  37. /** @var FutureFile */
  38. private $sourceNode;
  39. /**
  40. * @inheritdoc
  41. */
  42. public function initialize(Server $server) {
  43. $server->on('beforeMove', [$this, 'beforeMove']);
  44. $this->server = $server;
  45. }
  46. /**
  47. * @param string $sourcePath source path
  48. * @param string $destination destination path
  49. * @return bool|void
  50. * @throws BadRequest
  51. * @throws NotFound
  52. */
  53. public function beforeMove($sourcePath, $destination) {
  54. $this->sourceNode = $this->server->tree->getNodeForPath($sourcePath);
  55. if (!$this->sourceNode instanceof FutureFile) {
  56. // skip handling as the source is not a chunked FutureFile
  57. return;
  58. }
  59. try {
  60. /** @var INode $destinationNode */
  61. $destinationNode = $this->server->tree->getNodeForPath($destination);
  62. if ($destinationNode instanceof Directory) {
  63. throw new BadRequest("The given destination $destination is a directory.");
  64. }
  65. } catch (NotFound $e) {
  66. // If the destination does not exist yet it's not a directory either ;)
  67. }
  68. $this->verifySize();
  69. return $this->performMove($sourcePath, $destination);
  70. }
  71. /**
  72. * Move handler for future file.
  73. *
  74. * This overrides the default move behavior to prevent Sabre
  75. * to delete the target file before moving. Because deleting would
  76. * lose the file id and metadata.
  77. *
  78. * @param string $path source path
  79. * @param string $destination destination path
  80. * @return bool|void false to stop handling, void to skip this handler
  81. */
  82. public function performMove($path, $destination) {
  83. $fileExists = $this->server->tree->nodeExists($destination);
  84. // do a move manually, skipping Sabre's default "delete" for existing nodes
  85. try {
  86. $this->server->tree->move($path, $destination);
  87. } catch (Forbidden $e) {
  88. $sourceNode = $this->server->tree->getNodeForPath($path);
  89. if ($sourceNode instanceof FutureFile) {
  90. $sourceNode->delete();
  91. }
  92. throw $e;
  93. }
  94. // trigger all default events (copied from CorePlugin::move)
  95. $this->server->emit('afterMove', [$path, $destination]);
  96. $this->server->emit('afterUnbind', [$path]);
  97. $this->server->emit('afterBind', [$destination]);
  98. $response = $this->server->httpResponse;
  99. $response->setHeader('Content-Length', '0');
  100. $response->setStatus($fileExists ? 204 : 201);
  101. return false;
  102. }
  103. /**
  104. * @throws BadRequest
  105. */
  106. private function verifySize() {
  107. $expectedSize = $this->server->httpRequest->getHeader('OC-Total-Length');
  108. if ($expectedSize === null) {
  109. return;
  110. }
  111. $actualSize = $this->sourceNode->getSize();
  112. // casted to string because cast to float cause equality for non equal numbers
  113. // and integer has the problem of limited size on 32 bit systems
  114. if ((string)$expectedSize !== (string)$actualSize) {
  115. throw new BadRequest("Chunks on server do not sum up to $expectedSize but to $actualSize bytes");
  116. }
  117. }
  118. }