aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMorris Jobke <hey@morrisjobke.de>2020-08-13 20:47:08 +0200
committerGitHub <noreply@github.com>2020-08-13 20:47:08 +0200
commite9f5c7f649b923ec45d7a37eb24369e11727ccbf (patch)
treee978e971c21d7f5509178c6053ff201f3f4aea31
parented461155930219c2de3a648e7dfdf75778af2f7a (diff)
parent6722246aca71e392ab54600a3c91750075a595f2 (diff)
downloadnextcloud-server-e9f5c7f649b923ec45d7a37eb24369e11727ccbf.tar.gz
nextcloud-server-e9f5c7f649b923ec45d7a37eb24369e11727ccbf.zip
Merge pull request #22128 from nextcloud/bugfix/noid/cleanup-chunks-on-failure
Delete chunks if the move on an upload failed
-rw-r--r--apps/dav/lib/Upload/ChunkingPlugin.php19
-rw-r--r--apps/dav/tests/unit/Upload/ChunkingPluginTest.php10
2 files changed, 19 insertions, 10 deletions
diff --git a/apps/dav/lib/Upload/ChunkingPlugin.php b/apps/dav/lib/Upload/ChunkingPlugin.php
index 0a89b144ae1..6dd05c24ebf 100644
--- a/apps/dav/lib/Upload/ChunkingPlugin.php
+++ b/apps/dav/lib/Upload/ChunkingPlugin.php
@@ -26,6 +26,7 @@
namespace OCA\DAV\Upload;
use OCA\DAV\Connector\Sabre\Directory;
+use OCA\DAV\Connector\Sabre\Exception\Forbidden;
use Sabre\DAV\Exception\BadRequest;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\INode;
@@ -87,13 +88,17 @@ class ChunkingPlugin extends ServerPlugin {
* @return bool|void false to stop handling, void to skip this handler
*/
public function performMove($path, $destination) {
- if (!$this->server->tree->nodeExists($destination)) {
- // skip and let the default handler do its work
- return;
- }
-
+ $fileExists = $this->server->tree->nodeExists($destination);
// do a move manually, skipping Sabre's default "delete" for existing nodes
- $this->server->tree->move($path, $destination);
+ try {
+ $this->server->tree->move($path, $destination);
+ } catch (Forbidden $e) {
+ $sourceNode = $this->server->tree->getNodeForPath($path);
+ if ($sourceNode instanceof FutureFile) {
+ $sourceNode->delete();
+ }
+ throw $e;
+ }
// trigger all default events (copied from CorePlugin::move)
$this->server->emit('afterMove', [$path, $destination]);
@@ -102,7 +107,7 @@ class ChunkingPlugin extends ServerPlugin {
$response = $this->server->httpResponse;
$response->setHeader('Content-Length', '0');
- $response->setStatus(204);
+ $response->setStatus($fileExists ? 204 : 201);
return false;
}
diff --git a/apps/dav/tests/unit/Upload/ChunkingPluginTest.php b/apps/dav/tests/unit/Upload/ChunkingPluginTest.php
index 465bdf58eda..6391bc0a8e5 100644
--- a/apps/dav/tests/unit/Upload/ChunkingPluginTest.php
+++ b/apps/dav/tests/unit/Upload/ChunkingPluginTest.php
@@ -128,14 +128,18 @@ class ChunkingPluginTest extends TestCase {
->method('nodeExists')
->with('target')
->willReturn(false);
- $this->response->expects($this->never())
- ->method('setStatus');
+ $this->response->expects($this->once())
+ ->method('setHeader')
+ ->with('Content-Length', '0');
+ $this->response->expects($this->once())
+ ->method('setStatus')
+ ->with(201);
$this->request->expects($this->once())
->method('getHeader')
->with('OC-Total-Length')
->willReturn(4);
- $this->assertNull($this->plugin->beforeMove('source', 'target'));
+ $this->assertFalse($this->plugin->beforeMove('source', 'target'));
}
public function testBeforeMoveFutureFileMoveIt() {
'#n262'>262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
<?php
/**
 * @copyright Copyright (c) 2016, ownCloud, Inc.
 *
 * @author Lars <winnetou+github@catolic.de>
 * @author Lukas Reschke <lukas@statuscode.ch>
 * @author Martin Mattel <martin.mattel@diemattels.at>
 * @author Morris Jobke <hey@morrisjobke.de>
 * @author Olivier Paroz <github@oparoz.com>
 * @author Robin Appelman <robin@icewind.nl>
 * @author Robin McCorkell <robin@mccorkell.me.uk>
 * @author Roeland Jago Douma <roeland@famdouma.nl>
 * @author Stefan Weil <sw@weilnetz.de>
 *
 * @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/>
 *
 */

namespace OC;

use OCP\IConfig;
use OCP\ILogger;
use OCP\ITempManager;

class TempManager implements ITempManager {
	/** @var string[] Current temporary files and folders, used for cleanup */
	protected $current = [];
	/** @var string i.e. /tmp on linux systems */
	protected $tmpBaseDir;
	/** @var ILogger */
	protected $log;
	/** @var IConfig */
	protected $config;

	/** Prefix */
	const TMP_PREFIX = 'oc_tmp_';

	/**
	 * @param \OCP\ILogger $logger
	 * @param \OCP\IConfig $config
	 */
	public function __construct(ILogger $logger, IConfig $config) {
		$this->log = $logger;
		$this->config = $config;
		$this->tmpBaseDir = $this->getTempBaseDir();
	}

	/**
	 * Builds the filename with suffix and removes potential dangerous characters
	 * such as directory separators.
	 *
	 * @param string $absolutePath Absolute path to the file / folder
	 * @param string $postFix Postfix appended to the temporary file name, may be user controlled
	 * @return string
	 */
	private function buildFileNameWithSuffix($absolutePath, $postFix = '') {
		if($postFix !== '') {
			$postFix = '.' . ltrim($postFix, '.');
			$postFix = str_replace(['\\', '/'], '', $postFix);
			$absolutePath .= '-';
		}

		return $absolutePath . $postFix;
	}

	/**
	 * Create a temporary file and return the path
	 *
	 * @param string $postFix Postfix appended to the temporary file name
	 * @return string
	 */
	public function getTemporaryFile($postFix = '') {
		if (is_writable($this->tmpBaseDir)) {
			// To create an unique file and prevent the risk of race conditions
			// or duplicated temporary files by other means such as collisions
			// we need to create the file using `tempnam` and append a possible
			// postfix to it later
			$file = tempnam($this->tmpBaseDir, self::TMP_PREFIX);
			$this->current[] = $file;

			// If a postfix got specified sanitize it and create a postfixed
			// temporary file
			if($postFix !== '') {
				$fileNameWithPostfix = $this->buildFileNameWithSuffix($file, $postFix);
				touch($fileNameWithPostfix);
				chmod($fileNameWithPostfix, 0600);
				$this->current[] = $fileNameWithPostfix;
				return $fileNameWithPostfix;
			}

			return $file;
		} else {
			$this->log->warning(
				'Can not create a temporary file in directory {dir}. Check it exists and has correct permissions',
				[
					'dir' => $this->tmpBaseDir,
				]
			);
			return false;
		}
	}

	/**
	 * Create a temporary folder and return the path
	 *
	 * @param string $postFix Postfix appended to the temporary folder name
	 * @return string
	 */
	public function getTemporaryFolder($postFix = '') {
		if (is_writable($this->tmpBaseDir)) {
			// To create an unique directory and prevent the risk of race conditions
			// or duplicated temporary files by other means such as collisions
			// we need to create the file using `tempnam` and append a possible
			// postfix to it later
			$uniqueFileName = tempnam($this->tmpBaseDir, self::TMP_PREFIX);
			$this->current[] = $uniqueFileName;

			// Build a name without postfix
			$path = $this->buildFileNameWithSuffix($uniqueFileName . '-folder', $postFix);
			mkdir($path, 0700);
			$this->current[] = $path;

			return $path . '/';
		} else {
			$this->log->warning(
				'Can not create a temporary folder in directory {dir}. Check it exists and has correct permissions',
				[
					'dir' => $this->tmpBaseDir,
				]
			);
			return false;
		}
	}

	/**
	 * Remove the temporary files and folders generated during this request
	 */
	public function clean() {
		$this->cleanFiles($this->current);
	}

	/**
	 * @param string[] $files
	 */
	protected function cleanFiles($files) {
		foreach ($files as $file) {
			if (file_exists($file)) {
				try {
					\OC_Helper::rmdirr($file);
				} catch (\UnexpectedValueException $ex) {
					$this->log->warning(
						"Error deleting temporary file/folder: {file} - Reason: {error}",
						[
							'file' => $file,
							'error' => $ex->getMessage(),
						]
					);
				}
			}
		}
	}

	/**
	 * Remove old temporary files and folders that were failed to be cleaned
	 */
	public function cleanOld() {
		$this->cleanFiles($this->getOldFiles());
	}

	/**
	 * Get all temporary files and folders generated by oc older than an hour
	 *
	 * @return string[]
	 */
	protected function getOldFiles() {
		$cutOfTime = time() - 3600;
		$files = [];
		$dh = opendir($this->tmpBaseDir);
		if ($dh) {
			while (($file = readdir($dh)) !== false) {
				if (substr($file, 0, 7) === self::TMP_PREFIX) {
					$path = $this->tmpBaseDir . '/' . $file;
					$mtime = filemtime($path);
					if ($mtime < $cutOfTime) {
						$files[] = $path;
					}
				}
			}
		}
		return $files;
	}

	/**
	 * Get the temporary base directory configured on the server
	 *
	 * @return string Path to the temporary directory or null
	 * @throws \UnexpectedValueException
	 */
	public function getTempBaseDir() {
		if ($this->tmpBaseDir) {
			return $this->tmpBaseDir;
		}

		$directories = [];
		if ($temp = $this->config->getSystemValue('tempdirectory', null)) {
			$directories[] = $temp;
		}
		if ($temp = \OC::$server->getIniWrapper()->get('upload_tmp_dir')) {
			$directories[] = $temp;
		}
		if ($temp = getenv('TMP')) {
			$directories[] = $temp;
		}
		if ($temp = getenv('TEMP')) {
			$directories[] = $temp;
		}
		if ($temp = getenv('TMPDIR')) {
			$directories[] = $temp;
		}
		if ($temp = sys_get_temp_dir()) {
			$directories[] = $temp;
		}

		foreach ($directories as $dir) {
			if ($this->checkTemporaryDirectory($dir)) {
				return $dir;
			}
		}

		$temp = tempnam(dirname(__FILE__), '');
		if (file_exists($temp)) {
			unlink($temp);
			return dirname($temp);
		}
		throw new \UnexpectedValueException('Unable to detect system temporary directory');
	}

	/**
	 * Check if a temporary directory is ready for use
	 *
	 * @param mixed $directory
	 * @return bool
	 */
	private function checkTemporaryDirectory($directory) {
		// suppress any possible errors caused by is_writable
		// checks missing or invalid path or characters, wrong permissions etc
		try {
			if (is_writable($directory)) {
				return true;
			}
		} catch (\Exception $e) {
		}
		$this->log->warning('Temporary directory {dir} is not present or writable',
			['dir' => $directory]
		);
		return false;
	}

	/**
	 * Override the temporary base directory
	 *
	 * @param string $directory
	 */
	public function overrideTempBaseDir($directory) {
		$this->tmpBaseDir = $directory;
	}

}