aboutsummaryrefslogtreecommitdiffstats
path: root/apps/dav/lib/Connector/Sabre/File.php
diff options
context:
space:
mode:
Diffstat (limited to 'apps/dav/lib/Connector/Sabre/File.php')
-rw-r--r--apps/dav/lib/Connector/Sabre/File.php485
1 files changed, 171 insertions, 314 deletions
diff --git a/apps/dav/lib/Connector/Sabre/File.php b/apps/dav/lib/Connector/Sabre/File.php
index 6c379984995..d2a71eb3e7b 100644
--- a/apps/dav/lib/Connector/Sabre/File.php
+++ b/apps/dav/lib/Connector/Sabre/File.php
@@ -1,40 +1,9 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Bart Visscher <bartv@thisnet.nl>
- * @author Björn Schießle <bjoern@schiessle.org>
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Daniel Calviño Sánchez <danxuliu@gmail.com>
- * @author Jakob Sack <mail@jakobsack.de>
- * @author Jan-Philipp Litza <jplitza@users.noreply.github.com>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Jörn Friedrich Dreyer <jfd@butonic.de>
- * @author Julius Härtl <jus@bitgrid.net>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Owen Winkler <a_github@midnightcircus.com>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Semih Serhat Karakaya <karakayasemi@itu.edu.tr>
- * @author Stefan Schneider <stefan.schneider@squareweave.com.au>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- * @author Vincent Petry <vincent@nextcloud.com>
- *
- * @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/>
- *
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OCA\DAV\Connector\Sabre;
@@ -43,67 +12,71 @@ use OC\AppFramework\Http\Request;
use OC\Files\Filesystem;
use OC\Files\Stream\HashWrapper;
use OC\Files\View;
-use OC\Metadata\FileMetadata;
use OCA\DAV\AppInfo\Application;
use OCA\DAV\Connector\Sabre\Exception\EntityTooLarge;
use OCA\DAV\Connector\Sabre\Exception\FileLocked;
use OCA\DAV\Connector\Sabre\Exception\Forbidden as DAVForbiddenException;
use OCA\DAV\Connector\Sabre\Exception\UnsupportedMediaType;
-use OCA\DAV\Connector\Sabre\Exception\BadGateway;
+use OCP\App\IAppManager;
use OCP\Encryption\Exceptions\GenericEncryptionException;
+use OCP\Files;
use OCP\Files\EntityTooLargeException;
use OCP\Files\FileInfo;
use OCP\Files\ForbiddenException;
use OCP\Files\GenericFileException;
+use OCP\Files\IMimeTypeDetector;
use OCP\Files\InvalidContentException;
use OCP\Files\InvalidPathException;
use OCP\Files\LockNotAcquiredException;
use OCP\Files\NotFoundException;
use OCP\Files\NotPermittedException;
-use OCP\Files\Storage;
+use OCP\Files\Storage\IWriteStreamStorage;
use OCP\Files\StorageNotAvailableException;
+use OCP\IConfig;
use OCP\IL10N;
-use OCP\ILogger;
+use OCP\IRequest;
use OCP\L10N\IFactory as IL10NFactory;
use OCP\Lock\ILockingProvider;
use OCP\Lock\LockedException;
+use OCP\Server;
use OCP\Share\IManager;
+use Psr\Log\LoggerInterface;
use Sabre\DAV\Exception;
use Sabre\DAV\Exception\BadRequest;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\NotFound;
-use Sabre\DAV\Exception\NotImplemented;
use Sabre\DAV\Exception\ServiceUnavailable;
use Sabre\DAV\IFile;
class File extends Node implements IFile {
- protected $request;
-
+ protected IRequest $request;
protected IL10N $l10n;
- /** @var array<string, FileMetadata> */
- private array $metadata = [];
-
/**
* Sets up the node, expects a full path name
*
- * @param \OC\Files\View $view
- * @param \OCP\Files\FileInfo $info
- * @param \OCP\Share\IManager $shareManager
- * @param \OC\AppFramework\Http\Request $request
+ * @param View $view
+ * @param FileInfo $info
+ * @param ?\OCP\Share\IManager $shareManager
+ * @param ?IRequest $request
+ * @param ?IL10N $l10n
*/
- public function __construct(View $view, FileInfo $info, IManager $shareManager = null, Request $request = null) {
+ public function __construct(View $view, FileInfo $info, ?IManager $shareManager = null, ?IRequest $request = null, ?IL10N $l10n = null) {
parent::__construct($view, $info, $shareManager);
- // Querying IL10N directly results in a dependency loop
- /** @var IL10NFactory $l10nFactory */
- $l10nFactory = \OC::$server->get(IL10NFactory::class);
- $this->l10n = $l10nFactory->get(Application::APP_ID);
+ if ($l10n) {
+ $this->l10n = $l10n;
+ } else {
+ // Querying IL10N directly results in a dependency loop
+ /** @var IL10NFactory $l10nFactory */
+ $l10nFactory = Server::get(IL10NFactory::class);
+ $this->l10n = $l10nFactory->get(Application::APP_ID);
+ }
if (isset($request)) {
$this->request = $request;
} else {
- $this->request = \OC::$server->getRequest();
+ $this->request = Server::get(IRequest::class);
}
}
@@ -124,7 +97,7 @@ class File extends Node implements IFile {
* different object on a subsequent GET you are strongly recommended to not
* return an ETag, and just return null.
*
- * @param resource $data
+ * @param resource|string $data
*
* @throws Forbidden
* @throws UnsupportedMediaType
@@ -138,7 +111,7 @@ class File extends Node implements IFile {
public function put($data) {
try {
$exists = $this->fileView->file_exists($this->path);
- if ($this->info && $exists && !$this->info->isUpdateable()) {
+ if ($exists && !$this->info->isUpdateable()) {
throw new Forbidden();
}
} catch (StorageNotAvailableException $e) {
@@ -148,24 +121,18 @@ class File extends Node implements IFile {
// verify path of the target
$this->verifyPath();
- // chunked handling
- if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
- try {
- return $this->createFileChunked($data);
- } catch (\Exception $e) {
- $this->convertToSabreException($e);
- }
- }
-
- /** @var Storage $partStorage */
[$partStorage] = $this->fileView->resolvePath($this->path);
+ if ($partStorage === null) {
+ throw new ServiceUnavailable($this->l10n->t('Failed to get storage for file'));
+ }
$needsPartFile = $partStorage->needsPartFile() && (strlen($this->path) > 1);
- $view = \OC\Files\Filesystem::getView();
+ $view = Filesystem::getView();
if ($needsPartFile) {
+ $transferId = \rand();
// mark file as partial while uploading (ignored by the scanner)
- $partFilePath = $this->getPartFileBasePath($this->path) . '.ocTransferId' . rand() . '.part';
+ $partFilePath = $this->getPartFileBasePath($this->path) . '.ocTransferId' . $transferId . '.part';
if (!$view->isCreatable($partFilePath) && $view->isUpdatable($this->path)) {
$needsPartFile = false;
@@ -181,10 +148,11 @@ class File extends Node implements IFile {
}
// the part file and target file might be on a different storage in case of a single file storage (e.g. single file share)
- /** @var \OC\Files\Storage\Storage $partStorage */
[$partStorage, $internalPartPath] = $this->fileView->resolvePath($partFilePath);
- /** @var \OC\Files\Storage\Storage $storage */
[$storage, $internalPath] = $this->fileView->resolvePath($this->path);
+ if ($partStorage === null || $storage === null) {
+ throw new ServiceUnavailable($this->l10n->t('Failed to get storage for file'));
+ }
try {
if (!$needsPartFile) {
try {
@@ -215,93 +183,97 @@ class File extends Node implements IFile {
$data = $tmpData;
}
- $data = HashWrapper::wrap($data, 'md5', function ($hash) {
- $this->header('X-Hash-MD5: ' . $hash);
- });
- $data = HashWrapper::wrap($data, 'sha1', function ($hash) {
- $this->header('X-Hash-SHA1: ' . $hash);
- });
- $data = HashWrapper::wrap($data, 'sha256', function ($hash) {
- $this->header('X-Hash-SHA256: ' . $hash);
- });
-
- if ($partStorage->instanceOfStorage(Storage\IWriteStreamStorage::class)) {
- $isEOF = false;
- $wrappedData = CallbackWrapper::wrap($data, null, null, null, null, function ($stream) use (&$isEOF) {
- $isEOF = feof($stream);
- });
+ if ($this->request->getHeader('X-HASH') !== '') {
+ $hash = $this->request->getHeader('X-HASH');
+ if ($hash === 'all' || $hash === 'md5') {
+ $data = HashWrapper::wrap($data, 'md5', function ($hash): void {
+ $this->header('X-Hash-MD5: ' . $hash);
+ });
+ }
- $result = true;
- $count = -1;
- try {
- $count = $partStorage->writeStream($internalPartPath, $wrappedData);
- } catch (GenericFileException $e) {
- $result = false;
- } catch (BadGateway $e) {
- throw $e;
+ if ($hash === 'all' || $hash === 'sha1') {
+ $data = HashWrapper::wrap($data, 'sha1', function ($hash): void {
+ $this->header('X-Hash-SHA1: ' . $hash);
+ });
}
+ if ($hash === 'all' || $hash === 'sha256') {
+ $data = HashWrapper::wrap($data, 'sha256', function ($hash): void {
+ $this->header('X-Hash-SHA256: ' . $hash);
+ });
+ }
+ }
+
+ $lengthHeader = $this->request->getHeader('content-length');
+ $expected = $lengthHeader !== '' ? (int)$lengthHeader : null;
+
+ if ($partStorage->instanceOfStorage(IWriteStreamStorage::class)) {
+ $isEOF = false;
+ $wrappedData = CallbackWrapper::wrap($data, null, null, null, null, function ($stream) use (&$isEOF): void {
+ $isEOF = feof($stream);
+ });
- if ($result === false) {
- $result = $isEOF;
- if (is_resource($wrappedData)) {
- $result = feof($wrappedData);
+ $result = is_resource($wrappedData);
+ if ($result) {
+ $count = -1;
+ try {
+ /** @var IWriteStreamStorage $partStorage */
+ $count = $partStorage->writeStream($internalPartPath, $wrappedData, $expected);
+ } catch (GenericFileException $e) {
+ $logger = Server::get(LoggerInterface::class);
+ $logger->error('Error while writing stream to storage: ' . $e->getMessage(), ['exception' => $e, 'app' => 'webdav']);
+ $result = $isEOF;
+ if (is_resource($wrappedData)) {
+ $result = feof($wrappedData);
+ }
}
}
} else {
$target = $partStorage->fopen($internalPartPath, 'wb');
if ($target === false) {
- \OC::$server->getLogger()->error('\OC\Files\Filesystem::fopen() failed', ['app' => 'webdav']);
+ Server::get(LoggerInterface::class)->error('\OC\Files\Filesystem::fopen() failed', ['app' => 'webdav']);
// because we have no clue about the cause we can only throw back a 500/Internal Server Error
throw new Exception($this->l10n->t('Could not write file contents'));
}
- [$count, $result] = \OC_Helper::streamCopy($data, $target);
+ [$count, $result] = Files::streamCopy($data, $target, true);
fclose($target);
}
-
- if ($result === false) {
- $expected = -1;
- if (isset($_SERVER['CONTENT_LENGTH'])) {
- $expected = $_SERVER['CONTENT_LENGTH'];
- }
- if ($expected !== "0") {
- throw new Exception(
- $this->l10n->t(
- 'Error while copying file to target location (copied: %1$s, expected filesize: %2$s)',
- [
- $this->l10n->n('%n byte', '%n bytes', $count),
- $this->l10n->n('%n byte', '%n bytes', $expected),
- ],
- )
- );
- }
+ if ($result === false && $expected !== null) {
+ throw new Exception(
+ $this->l10n->t(
+ 'Error while copying file to target location (copied: %1$s, expected filesize: %2$s)',
+ [
+ $this->l10n->n('%n byte', '%n bytes', $count),
+ $this->l10n->n('%n byte', '%n bytes', $expected),
+ ],
+ )
+ );
}
// if content length is sent by client:
// double check if the file was fully received
// compare expected and actual size
- if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
- $expected = (int)$_SERVER['CONTENT_LENGTH'];
- if ($count !== $expected) {
- throw new BadRequest(
- $this->l10n->t(
- 'Expected filesize of %1$s but read (from Nextcloud client) and wrote (to Nextcloud storage) %2$s. Could either be a network problem on the sending side or a problem writing to the storage on the server side.',
- [
- $this->l10n->n('%n byte', '%n bytes', $expected),
- $this->l10n->n('%n byte', '%n bytes', $count),
- ],
- )
- );
- }
+ if ($expected !== null
+ && $expected !== $count
+ && $this->request->getMethod() === 'PUT'
+ ) {
+ throw new BadRequest(
+ $this->l10n->t(
+ 'Expected filesize of %1$s but read (from Nextcloud client) and wrote (to Nextcloud storage) %2$s. Could either be a network problem on the sending side or a problem writing to the storage on the server side.',
+ [
+ $this->l10n->n('%n byte', '%n bytes', $expected),
+ $this->l10n->n('%n byte', '%n bytes', $count),
+ ],
+ )
+ );
}
} catch (\Exception $e) {
- $context = [];
-
if ($e instanceof LockedException) {
- $context['level'] = ILogger::DEBUG;
+ Server::get(LoggerInterface::class)->debug($e->getMessage(), ['exception' => $e]);
+ } else {
+ Server::get(LoggerInterface::class)->error($e->getMessage(), ['exception' => $e]);
}
- \OC::$server->getLogger()->logException($e, $context);
if ($needsPartFile) {
$partStorage->unlink($internalPartPath);
}
@@ -340,7 +312,7 @@ class File extends Node implements IFile {
$renameOkay = $storage->moveFromStorage($partStorage, $internalPartPath, $internalPath);
$fileExists = $storage->file_exists($internalPath);
if ($renameOkay === false || $fileExists === false) {
- \OC::$server->getLogger()->error('renaming part file to final file failed $renameOkay: ' . ($renameOkay ? 'true' : 'false') . ', $fileExists: ' . ($fileExists ? 'true' : 'false') . ')', ['app' => 'webdav']);
+ Server::get(LoggerInterface::class)->error('renaming part file to final file failed $renameOkay: ' . ($renameOkay ? 'true' : 'false') . ', $fileExists: ' . ($fileExists ? 'true' : 'false') . ')', ['app' => 'webdav']);
throw new Exception($this->l10n->t('Could not rename part file to final file'));
}
} catch (ForbiddenException $ex) {
@@ -364,8 +336,9 @@ class File extends Node implements IFile {
}
// allow sync clients to send the mtime along in a header
- if (isset($this->request->server['HTTP_X_OC_MTIME'])) {
- $mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']);
+ $mtimeHeader = $this->request->getHeader('x-oc-mtime');
+ if ($mtimeHeader !== '') {
+ $mtime = $this->sanitizeMtime($mtimeHeader);
if ($this->fileView->touch($this->path, $mtime)) {
$this->header('X-OC-MTime: accepted');
}
@@ -376,8 +349,9 @@ class File extends Node implements IFile {
];
// allow sync clients to send the creation time along in a header
- if (isset($this->request->server['HTTP_X_OC_CTIME'])) {
- $ctime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_CTIME']);
+ $ctimeHeader = $this->request->getHeader('x-oc-ctime');
+ if ($ctimeHeader) {
+ $ctime = $this->sanitizeMtime($ctimeHeader);
$fileInfoUpdate['creation_time'] = $ctime;
$this->header('X-OC-CTime: accepted');
}
@@ -390,8 +364,9 @@ class File extends Node implements IFile {
$this->refreshInfo();
- if (isset($this->request->server['HTTP_OC_CHECKSUM'])) {
- $checksum = trim($this->request->server['HTTP_OC_CHECKSUM']);
+ $checksumHeader = $this->request->getHeader('oc-checksum');
+ if ($checksumHeader) {
+ $checksum = trim($checksumHeader);
$this->setChecksum($checksum);
} elseif ($this->getChecksum() !== null && $this->getChecksum() !== '') {
$this->setChecksum('');
@@ -404,61 +379,68 @@ class File extends Node implements IFile {
}
private function getPartFileBasePath($path) {
- $partFileInStorage = \OC::$server->getConfig()->getSystemValue('part_file_in_storage', true);
+ $partFileInStorage = Server::get(IConfig::class)->getSystemValue('part_file_in_storage', true);
if ($partFileInStorage) {
- return $path;
+ $filename = basename($path);
+ // hash does not need to be secure but fast and semi unique
+ $hashedFilename = hash('xxh128', $filename);
+ return substr($path, 0, strlen($path) - strlen($filename)) . $hashedFilename;
} else {
- return md5($path); // will place it in the root of the view with a unique name
+ // will place the .part file in the users root directory
+ // therefor we need to make the name (semi) unique - hash does not need to be secure but fast.
+ return hash('xxh128', $path);
}
}
- /**
- * @param string $path
- */
- private function emitPreHooks($exists, $path = null) {
+ private function emitPreHooks(bool $exists, ?string $path = null): bool {
if (is_null($path)) {
$path = $this->path;
}
$hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
+ if ($hookPath === null) {
+ // We only trigger hooks from inside default view
+ return true;
+ }
$run = true;
if (!$exists) {
- \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_create, [
- \OC\Files\Filesystem::signal_param_path => $hookPath,
- \OC\Files\Filesystem::signal_param_run => &$run,
+ \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_create, [
+ Filesystem::signal_param_path => $hookPath,
+ Filesystem::signal_param_run => &$run,
]);
} else {
- \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_update, [
- \OC\Files\Filesystem::signal_param_path => $hookPath,
- \OC\Files\Filesystem::signal_param_run => &$run,
+ \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_update, [
+ Filesystem::signal_param_path => $hookPath,
+ Filesystem::signal_param_run => &$run,
]);
}
- \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_write, [
- \OC\Files\Filesystem::signal_param_path => $hookPath,
- \OC\Files\Filesystem::signal_param_run => &$run,
+ \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_write, [
+ Filesystem::signal_param_path => $hookPath,
+ Filesystem::signal_param_run => &$run,
]);
return $run;
}
- /**
- * @param string $path
- */
- private function emitPostHooks($exists, $path = null) {
+ private function emitPostHooks(bool $exists, ?string $path = null): void {
if (is_null($path)) {
$path = $this->path;
}
$hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path));
+ if ($hookPath === null) {
+ // We only trigger hooks from inside default view
+ return;
+ }
if (!$exists) {
- \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_create, [
- \OC\Files\Filesystem::signal_param_path => $hookPath
+ \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_create, [
+ Filesystem::signal_param_path => $hookPath
]);
} else {
- \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_update, [
- \OC\Files\Filesystem::signal_param_path => $hookPath
+ \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_update, [
+ Filesystem::signal_param_path => $hookPath
]);
}
- \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_write, [
- \OC\Files\Filesystem::signal_param_path => $hookPath
+ \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_write, [
+ Filesystem::signal_param_path => $hookPath
]);
}
@@ -476,14 +458,31 @@ class File extends Node implements IFile {
// do a if the file did not exist
throw new NotFound();
}
+ $path = ltrim($this->path, '/');
try {
- $res = $this->fileView->fopen(ltrim($this->path, '/'), 'rb');
+ $res = $this->fileView->fopen($path, 'rb');
} catch (\Exception $e) {
$this->convertToSabreException($e);
}
+
if ($res === false) {
- throw new ServiceUnavailable($this->l10n->t('Could not open file'));
+ if ($this->fileView->file_exists($path)) {
+ throw new ServiceUnavailable($this->l10n->t('Could not open file: %1$s, file does seem to exist', [$path]));
+ } else {
+ throw new ServiceUnavailable($this->l10n->t('Could not open file: %1$s, file doesn\'t seem to exist', [$path]));
+ }
}
+
+ // comparing current file size with the one in DB
+ // if different, fix DB and refresh cache.
+ if ($this->getSize() !== $this->fileView->filesize($this->getPath())) {
+ $logger = Server::get(LoggerInterface::class);
+ $logger->warning('fixing cached size of file id=' . $this->getId());
+
+ $this->getFileInfo()->getStorage()->getUpdater()->update($this->getFileInfo()->getInternalPath());
+ $this->refreshInfo();
+ }
+
return $res;
} catch (GenericEncryptionException $e) {
// returning 503 will allow retry of the operation at a later point in time
@@ -533,20 +532,19 @@ class File extends Node implements IFile {
$mimeType = $this->info->getMimetype();
// PROPFIND needs to return the correct mime type, for consistency with the web UI
- if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PROPFIND') {
+ if ($this->request->getMethod() === 'PROPFIND') {
return $mimeType;
}
- return \OC::$server->getMimeTypeDetector()->getSecureMimeType($mimeType);
+ return Server::get(IMimeTypeDetector::class)->getSecureMimeType($mimeType);
}
/**
* @return array|bool
*/
public function getDirectDownload() {
- if (\OCP\App::isEnabled('encryption')) {
+ if (Server::get(IAppManager::class)->isEnabledForUser('encryption')) {
return [];
}
- /** @var \OCP\Files\Storage $storage */
[$storage, $internalPath] = $this->fileView->resolvePath($this->path);
if (is_null($storage)) {
return [];
@@ -556,132 +554,6 @@ class File extends Node implements IFile {
}
/**
- * @param resource $data
- * @return null|string
- * @throws Exception
- * @throws BadRequest
- * @throws NotImplemented
- * @throws ServiceUnavailable
- */
- private function createFileChunked($data) {
- [$path, $name] = \Sabre\Uri\split($this->path);
-
- $info = \OC_FileChunking::decodeName($name);
- if (empty($info)) {
- throw new NotImplemented($this->l10n->t('Invalid chunk name'));
- }
-
- $chunk_handler = new \OC_FileChunking($info);
- $bytesWritten = $chunk_handler->store($info['index'], $data);
-
- //detect aborted upload
- if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
- if (isset($_SERVER['CONTENT_LENGTH'])) {
- $expected = (int)$_SERVER['CONTENT_LENGTH'];
- if ($bytesWritten !== $expected) {
- $chunk_handler->remove($info['index']);
- throw new BadRequest(
- $this->l10n->t(
- 'Expected filesize of %1$s but read (from Nextcloud client) and wrote (to Nextcloud storage) %2$s. Could either be a network problem on the sending side or a problem writing to the storage on the server side.',
- [
- $this->l10n->n('%n byte', '%n bytes', $expected),
- $this->l10n->n('%n byte', '%n bytes', $bytesWritten),
- ],
- )
- );
- }
- }
- }
-
- if ($chunk_handler->isComplete()) {
- /** @var Storage $storage */
- [$storage,] = $this->fileView->resolvePath($path);
- $needsPartFile = $storage->needsPartFile();
- $partFile = null;
-
- $targetPath = $path . '/' . $info['name'];
- /** @var \OC\Files\Storage\Storage $targetStorage */
- [$targetStorage, $targetInternalPath] = $this->fileView->resolvePath($targetPath);
-
- $exists = $this->fileView->file_exists($targetPath);
-
- try {
- $this->fileView->lockFile($targetPath, ILockingProvider::LOCK_SHARED);
-
- $this->emitPreHooks($exists, $targetPath);
- $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_EXCLUSIVE);
- /** @var \OC\Files\Storage\Storage $targetStorage */
- [$targetStorage, $targetInternalPath] = $this->fileView->resolvePath($targetPath);
-
- if ($needsPartFile) {
- // we first assembly the target file as a part file
- $partFile = $this->getPartFileBasePath($path . '/' . $info['name']) . '.ocTransferId' . $info['transferid'] . '.part';
- /** @var \OC\Files\Storage\Storage $targetStorage */
- [$partStorage, $partInternalPath] = $this->fileView->resolvePath($partFile);
-
-
- $chunk_handler->file_assemble($partStorage, $partInternalPath);
-
- // here is the final atomic rename
- $renameOkay = $targetStorage->moveFromStorage($partStorage, $partInternalPath, $targetInternalPath);
- $fileExists = $targetStorage->file_exists($targetInternalPath);
- if ($renameOkay === false || $fileExists === false) {
- \OC::$server->getLogger()->error('\OC\Files\Filesystem::rename() failed', ['app' => 'webdav']);
- // only delete if an error occurred and the target file was already created
- if ($fileExists) {
- // set to null to avoid double-deletion when handling exception
- // stray part file
- $partFile = null;
- $targetStorage->unlink($targetInternalPath);
- }
- $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
- throw new Exception($this->l10n->t('Could not rename part file assembled from chunks'));
- }
- } else {
- // assemble directly into the final file
- $chunk_handler->file_assemble($targetStorage, $targetInternalPath);
- }
-
- // allow sync clients to send the mtime along in a header
- if (isset($this->request->server['HTTP_X_OC_MTIME'])) {
- $mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']);
- if ($targetStorage->touch($targetInternalPath, $mtime)) {
- $this->header('X-OC-MTime: accepted');
- }
- }
-
- // since we skipped the view we need to scan and emit the hooks ourselves
- $targetStorage->getUpdater()->update($targetInternalPath);
-
- $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
-
- $this->emitPostHooks($exists, $targetPath);
-
- // FIXME: should call refreshInfo but can't because $this->path is not the of the final file
- $info = $this->fileView->getFileInfo($targetPath);
-
- if (isset($this->request->server['HTTP_OC_CHECKSUM'])) {
- $checksum = trim($this->request->server['HTTP_OC_CHECKSUM']);
- $this->fileView->putFileInfo($targetPath, ['checksum' => $checksum]);
- } elseif ($info->getChecksum() !== null && $info->getChecksum() !== '') {
- $this->fileView->putFileInfo($this->path, ['checksum' => '']);
- }
-
- $this->fileView->unlockFile($targetPath, ILockingProvider::LOCK_SHARED);
-
- return $info->getEtag();
- } catch (\Exception $e) {
- if ($partFile !== null) {
- $targetStorage->unlink($targetInternalPath);
- }
- $this->convertToSabreException($e);
- }
- }
-
- return null;
- }
-
- /**
* Convert the given exception to a SabreException instance
*
* @param \Exception $e
@@ -737,9 +609,6 @@ class File extends Node implements IFile {
* @return string|null
*/
public function getChecksum() {
- if (!$this->info) {
- return null;
- }
return $this->info->getChecksum();
}
@@ -761,16 +630,4 @@ class File extends Node implements IFile {
public function getNode(): \OCP\Files\File {
return $this->node;
}
-
- public function getMetadata(string $group): FileMetadata {
- return $this->metadata[$group];
- }
-
- public function setMetadata(string $group, FileMetadata $metadata): void {
- $this->metadata[$group] = $metadata;
- }
-
- public function hasMetadata(string $group) {
- return array_key_exists($group, $this->metadata);
- }
}