BulkUploadPlugin.php 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2021, Louis Chemineau <louis@chmn.me>
  4. *
  5. * @author Louis Chemineau <louis@chmn.me>
  6. * @author Côme Chilliet <come.chilliet@nextcloud.com>
  7. *
  8. * @license AGPL-3.0
  9. *
  10. * This code is free software: you can redistribute it and/or modify
  11. * it under the terms of the GNU Affero General Public License, version 3,
  12. * as published by the Free Software Foundation.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU Affero General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU Affero General Public License, version 3,
  20. * along with this program. If not, see <http://www.gnu.org/licenses/>
  21. *
  22. */
  23. namespace OCA\DAV\BulkUpload;
  24. use OCA\DAV\Connector\Sabre\MtimeSanitizer;
  25. use OCP\AppFramework\Http;
  26. use OCP\Files\DavUtil;
  27. use OCP\Files\Folder;
  28. use Psr\Log\LoggerInterface;
  29. use Sabre\DAV\Server;
  30. use Sabre\DAV\ServerPlugin;
  31. use Sabre\HTTP\RequestInterface;
  32. use Sabre\HTTP\ResponseInterface;
  33. class BulkUploadPlugin extends ServerPlugin {
  34. private Folder $userFolder;
  35. private LoggerInterface $logger;
  36. public function __construct(
  37. Folder $userFolder,
  38. LoggerInterface $logger
  39. ) {
  40. $this->userFolder = $userFolder;
  41. $this->logger = $logger;
  42. }
  43. /**
  44. * Register listener on POST requests with the httpPost method.
  45. */
  46. public function initialize(Server $server): void {
  47. $server->on('method:POST', [$this, 'httpPost'], 10);
  48. }
  49. /**
  50. * Handle POST requests on /dav/bulk
  51. * - parsing is done with a MultipartContentsParser object
  52. * - writing is done with the userFolder service
  53. *
  54. * Will respond with an object containing an ETag for every written files.
  55. */
  56. public function httpPost(RequestInterface $request, ResponseInterface $response): bool {
  57. // Limit bulk upload to the /dav/bulk endpoint
  58. if ($request->getPath() !== "bulk") {
  59. return true;
  60. }
  61. $multiPartParser = new MultipartRequestParser($request, $this->logger);
  62. $writtenFiles = [];
  63. while (!$multiPartParser->isAtLastBoundary()) {
  64. try {
  65. [$headers, $content] = $multiPartParser->parseNextPart();
  66. } catch (\Exception $e) {
  67. // Return early if an error occurs during parsing.
  68. $this->logger->error($e->getMessage());
  69. $response->setStatus(Http::STATUS_BAD_REQUEST);
  70. $response->setBody(json_encode($writtenFiles, JSON_THROW_ON_ERROR));
  71. return false;
  72. }
  73. try {
  74. // TODO: Remove 'x-file-mtime' when the desktop client no longer use it.
  75. if (isset($headers['x-file-mtime'])) {
  76. $mtime = MtimeSanitizer::sanitizeMtime($headers['x-file-mtime']);
  77. } elseif (isset($headers['x-oc-mtime'])) {
  78. $mtime = MtimeSanitizer::sanitizeMtime($headers['x-oc-mtime']);
  79. } else {
  80. $mtime = null;
  81. }
  82. $node = $this->userFolder->newFile($headers['x-file-path'], $content);
  83. $node->touch($mtime);
  84. $node = $this->userFolder->getById($node->getId())[0];
  85. $writtenFiles[$headers['x-file-path']] = [
  86. "error" => false,
  87. "etag" => $node->getETag(),
  88. "fileid" => DavUtil::getDavFileId($node->getId()),
  89. "permissions" => DavUtil::getDavPermissions($node),
  90. ];
  91. } catch (\Exception $e) {
  92. $this->logger->error($e->getMessage(), ['path' => $headers['x-file-path']]);
  93. $writtenFiles[$headers['x-file-path']] = [
  94. "error" => true,
  95. "message" => $e->getMessage(),
  96. ];
  97. }
  98. }
  99. $response->setStatus(Http::STATUS_OK);
  100. $response->setBody(json_encode($writtenFiles, JSON_THROW_ON_ERROR));
  101. return false;
  102. }
  103. }