Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 31 additions & 44 deletions apps/dav/lib/Connector/Sabre/Directory.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
use OCP\Lock\ILockingProvider;
use OCP\Lock\LockedException;
use Psr\Log\LoggerInterface;
use Sabre\DAV\Exception;
use Sabre\DAV\Exception\BadRequest;
use Sabre\DAV\Exception\Locked;
use Sabre\DAV\Exception\NotFound;
Expand Down Expand Up @@ -102,33 +103,19 @@ public function __construct(View $view, FileInfo $info, ?CachingTree $tree = nul
* @param string $name Name of the file
* @param resource|string $data Initial payload
* @return null|string
* @throws Exception\EntityTooLarge
* @throws Exception\UnsupportedMediaType
* @throws FileLocked
* @throws InvalidPath
* @throws \Sabre\DAV\Exception
* @throws \Sabre\DAV\Exception\BadRequest
* @throws \Sabre\DAV\Exception\Forbidden
* @throws \Sabre\DAV\Exception\ServiceUnavailable
* @throws Exception
* @throws BadRequest
* @throws Exception\Forbidden
* @throws ServiceUnavailable
*/
public function createFile($name, $data = null) {
try {
// for chunked upload also updating a existing file is a "createFile"
// because we create all the chunks before re-assemble them to the existing file.
if (isset($_SERVER['HTTP_OC_CHUNKED'])) {

// exit if we can't create a new file and we don't updatable existing file
$chunkInfo = \OC_FileChunking::decodeName($name);
if (!$this->fileView->isCreatable($this->path) &&
!$this->fileView->isUpdatable($this->path . '/' . $chunkInfo['name'])
) {
throw new \Sabre\DAV\Exception\Forbidden();
}
} else {
// For non-chunked upload it is enough to check if we can create a new file
if (!$this->fileView->isCreatable($this->path)) {
throw new \Sabre\DAV\Exception\Forbidden();
}
// For non-chunked upload it is enough to check if we can create a new file
if (!$this->fileView->isCreatable($this->path)) {
throw new Exception\Forbidden();
}

$this->fileView->verifyPath($this->path, $name);
Expand All @@ -153,8 +140,8 @@ public function createFile($name, $data = null) {
$this->fileView->unlockFile($path . '.upload.part', ILockingProvider::LOCK_EXCLUSIVE);
$node->releaseLock(ILockingProvider::LOCK_SHARED);
return $result;
} catch (\OCP\Files\StorageNotAvailableException $e) {
throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage(), $e->getCode(), $e);
} catch (StorageNotAvailableException $e) {
throw new ServiceUnavailable($e->getMessage());
} catch (InvalidPathException $ex) {
throw new InvalidPath($ex->getMessage(), false, $ex);
} catch (ForbiddenException $ex) {
Expand All @@ -170,22 +157,22 @@ public function createFile($name, $data = null) {
* @param string $name
* @throws FileLocked
* @throws InvalidPath
* @throws \Sabre\DAV\Exception\Forbidden
* @throws \Sabre\DAV\Exception\ServiceUnavailable
* @throws Exception\Forbidden
* @throws ServiceUnavailable
*/
public function createDirectory($name) {
try {
if (!$this->info->isCreatable()) {
throw new \Sabre\DAV\Exception\Forbidden();
throw new Exception\Forbidden();
}

$this->fileView->verifyPath($this->path, $name);
$newPath = $this->path . '/' . $name;
if (!$this->fileView->mkdir($newPath)) {
throw new \Sabre\DAV\Exception\Forbidden('Could not create directory ' . $newPath);
throw new Exception\Forbidden('Could not create directory ' . $newPath);
}
} catch (\OCP\Files\StorageNotAvailableException $e) {
throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage());
} catch (StorageNotAvailableException $e) {
throw new ServiceUnavailable($e->getMessage());
} catch (InvalidPathException $ex) {
throw new InvalidPath($ex->getMessage());
} catch (ForbiddenException $ex) {
Expand All @@ -203,7 +190,7 @@ public function createDirectory($name) {
* @return \Sabre\DAV\INode
* @throws InvalidPath
* @throws \Sabre\DAV\Exception\NotFound
* @throws \Sabre\DAV\Exception\ServiceUnavailable
* @throws ServiceUnavailable
*/
public function getChild($name, $info = null) {
if (!$this->info->isReadable()) {
Expand All @@ -216,12 +203,12 @@ public function getChild($name, $info = null) {
try {
$this->fileView->verifyPath($this->path, $name);
$info = $this->fileView->getFileInfo($path);
} catch (\OCP\Files\StorageNotAvailableException $e) {
throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage());
} catch (StorageNotAvailableException $e) {
throw new ServiceUnavailable($e->getMessage());
} catch (InvalidPathException $ex) {
throw new InvalidPath($ex->getMessage());
} catch (ForbiddenException $e) {
throw new \Sabre\DAV\Exception\Forbidden();
throw new Exception\Forbidden();
}
}

Expand Down Expand Up @@ -298,17 +285,17 @@ public function childExists($name) {
*
* @return void
* @throws FileLocked
* @throws \Sabre\DAV\Exception\Forbidden
* @throws Exception\Forbidden
*/
public function delete() {
if ($this->path === '' || $this->path === '/' || !$this->info->isDeletable()) {
throw new \Sabre\DAV\Exception\Forbidden();
throw new Exception\Forbidden();
}

try {
if (!$this->fileView->rmdir($this->path)) {
// assume it wasn't possible to remove due to permission issue
throw new \Sabre\DAV\Exception\Forbidden();
throw new Exception\Forbidden();
}
} catch (ForbiddenException $ex) {
throw new Forbidden($ex->getMessage(), $ex->getRetry());
Expand Down Expand Up @@ -343,7 +330,7 @@ public function getQuotaInfo() {
} catch (\OCP\Files\NotFoundException $e) {
$logger->warning("error while getting quota into", ['exception' => $e]);
return [0, 0];
} catch (\OCP\Files\StorageNotAvailableException $e) {
} catch (StorageNotAvailableException $e) {
$logger->warning("error while getting quota into", ['exception' => $e]);
return [0, 0];
} catch (NotPermittedException $e) {
Expand Down Expand Up @@ -375,7 +362,7 @@ public function getQuotaInfo() {
* @throws ServiceUnavailable
* @throws Forbidden
* @throws FileLocked
* @throws \Sabre\DAV\Exception\Forbidden
* @throws Exception\Forbidden
*/
public function moveInto($targetName, $fullSourcePath, INode $sourceNode) {
if (!$sourceNode instanceof Node) {
Expand All @@ -399,7 +386,7 @@ public function moveInto($targetName, $fullSourcePath, INode $sourceNode) {
// at getNodeForPath we also check the path for isForbiddenFileOrDir
// with that we have covered both source and destination
if ($sourceNode instanceof Directory && $targetNodeExists) {
throw new \Sabre\DAV\Exception\Forbidden('Could not copy directory ' . $sourceNode->getName() . ', target exists');
throw new Exception\Forbidden('Could not copy directory ' . $sourceNode->getName() . ', target exists');
}

[$sourceDir,] = \Sabre\Uri\split($sourceNode->getPath());
Expand All @@ -420,19 +407,19 @@ public function moveInto($targetName, $fullSourcePath, INode $sourceNode) {
if ($targetNodeExists || $sameFolder) {
// note that renaming a share mount point is always allowed
if (!$this->fileView->isUpdatable($destinationDir) && !$isMovableMount) {
throw new \Sabre\DAV\Exception\Forbidden();
throw new Exception\Forbidden();
}
} else {
if (!$this->fileView->isCreatable($destinationDir)) {
throw new \Sabre\DAV\Exception\Forbidden();
throw new Exception\Forbidden();
}
}

if (!$sameFolder) {
// moving to a different folder, source will be gone, like a deletion
// note that moving a share mount point is always allowed
if (!$this->fileView->isDeletable($sourcePath) && !$isMovableMount) {
throw new \Sabre\DAV\Exception\Forbidden();
throw new Exception\Forbidden();
}
}

Expand All @@ -445,7 +432,7 @@ public function moveInto($targetName, $fullSourcePath, INode $sourceNode) {

$renameOkay = $this->fileView->rename($sourcePath, $destinationPath);
if (!$renameOkay) {
throw new \Sabre\DAV\Exception\Forbidden('');
throw new Exception\Forbidden('');
}
} catch (StorageNotAvailableException $e) {
throw new ServiceUnavailable($e->getMessage());
Expand All @@ -465,7 +452,7 @@ public function copyInto($targetName, $sourcePath, INode $sourceNode) {
$sourcePath = $sourceNode->getPath();

if (!$this->fileView->isCreatable($this->getPath())) {
throw new \Sabre\DAV\Exception\Forbidden();
throw new Exception\Forbidden();
}

try {
Expand Down
135 changes: 0 additions & 135 deletions apps/dav/lib/Connector/Sabre/File.php
Original file line number Diff line number Diff line change
Expand Up @@ -148,15 +148,6 @@ public function put($data) {
// 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);
$needsPartFile = $partStorage->needsPartFile() && (strlen($this->path) > 1);
Expand Down Expand Up @@ -577,132 +568,6 @@ public function getDirectDownload() {
return $storage->getDirectDownload($internalPath);
}

/**
* @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->get(LoggerInterface::class)->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
*
Expand Down
9 changes: 0 additions & 9 deletions apps/dav/lib/Connector/Sabre/FilesPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -577,15 +577,6 @@ public function handleUpdateProperties($path, PropPatch $propPatch) {
* @throws \Sabre\DAV\Exception\BadRequest
*/
public function sendFileIdHeader($filePath, \Sabre\DAV\INode $node = null) {
// chunked upload handling
if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
[$path, $name] = \Sabre\Uri\split($filePath);
$info = \OC_FileChunking::decodeName($name);
if (!empty($info)) {
$filePath = $path . '/' . $info['name'];
}
}

// we get the node for the given $filePath here because in case of afterCreateFile $node is the parent folder
if (!$this->server->tree->nodeExists($filePath)) {
return;
Expand Down
4 changes: 2 additions & 2 deletions apps/dav/lib/Connector/Sabre/LockPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public function initialize(\Sabre\DAV\Server $server) {
public function getLock(RequestInterface $request) {
// we can't listen on 'beforeMethod:PUT' due to order of operations with setting up the tree
// so instead we limit ourselves to the PUT method manually
if ($request->getMethod() !== 'PUT' || isset($_SERVER['HTTP_OC_CHUNKED'])) {
if ($request->getMethod() !== 'PUT') {
return;
}
try {
Expand All @@ -84,7 +84,7 @@ public function releaseLock(RequestInterface $request) {
if ($this->isLocked === false) {
return;
}
if ($request->getMethod() !== 'PUT' || isset($_SERVER['HTTP_OC_CHUNKED'])) {
if ($request->getMethod() !== 'PUT') {
return;
}
try {
Expand Down
Loading