From bd71a1b7b66f02b3630da44e24b48e29f3d02f17 Mon Sep 17 00:00:00 2001
From: Vincent Petry <pvince81@owncloud.com>
Date: Mon, 13 Jan 2014 13:14:05 +0100
Subject: [PATCH] Added file name check in webdav connector

- added file name check for the put, rename and setNames() methods which
  throw a "Bad Request" whenever invalid characters are used
- replaced \OC\Filesystem usage with $this->getFS() to be able to write
  unit tests
---
 lib/private/connector/sabre/file.php       | 10 ++++--
 lib/private/connector/sabre/node.php       |  9 +++--
 lib/private/connector/sabre/objecttree.php |  5 +++
 tests/lib/connector/sabre/file.php         | 40 ++++++++++++++++++++++
 tests/lib/connector/sabre/objecttree.php   | 16 +++++++++
 5 files changed, 76 insertions(+), 4 deletions(-)

diff --git a/lib/private/connector/sabre/file.php b/lib/private/connector/sabre/file.php
index ed27cef440..db0726e135 100644
--- a/lib/private/connector/sabre/file.php
+++ b/lib/private/connector/sabre/file.php
@@ -58,6 +58,11 @@ class OC_Connector_Sabre_File extends OC_Connector_Sabre_Node implements Sabre_D
 			throw new \Sabre_DAV_Exception_ServiceUnavailable();
 		}
 
+		$fileName = basename($this->path);
+		if (!\OCP\Util::isValidFileName($fileName)) {
+			throw new \Sabre_DAV_Exception_BadRequest();
+		}
+
 		// chunked handling
 		if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
 			return $this->createFileChunked($data);
@@ -142,15 +147,16 @@ class OC_Connector_Sabre_File extends OC_Connector_Sabre_Node implements Sabre_D
 	 * @throws Sabre_DAV_Exception_Forbidden
 	 */
 	public function delete() {
+		$fs = $this->getFS();
 
 		if ($this->path === 'Shared') {
 			throw new \Sabre_DAV_Exception_Forbidden();
 		}
 
-		if (!\OC\Files\Filesystem::isDeletable($this->path)) {
+		if (!$fs->isDeletable($this->path)) {
 			throw new \Sabre_DAV_Exception_Forbidden();
 		}
-		\OC\Files\Filesystem::unlink($this->path);
+		$fs->unlink($this->path);
 
 		// remove properties
 		$this->removeProperties();
diff --git a/lib/private/connector/sabre/node.php b/lib/private/connector/sabre/node.php
index 993aa73fae..bf7a04f5b1 100644
--- a/lib/private/connector/sabre/node.php
+++ b/lib/private/connector/sabre/node.php
@@ -85,19 +85,24 @@ abstract class OC_Connector_Sabre_Node implements Sabre_DAV_INode, Sabre_DAV_IPr
 	 * @return void
 	 */
 	public function setName($name) {
+		$fs = $this->getFS();
 
 		// rename is only allowed if the update privilege is granted
-		if (!\OC\Files\Filesystem::isUpdatable($this->path)) {
+		if (!$fs->isUpdatable($this->path)) {
 			throw new \Sabre_DAV_Exception_Forbidden();
 		}
 
 		list($parentPath, ) = Sabre_DAV_URLUtil::splitPath($this->path);
 		list(, $newName) = Sabre_DAV_URLUtil::splitPath($name);
 
+		if (!\OCP\Util::isValidFileName($newName)) {
+			throw new \Sabre_DAV_Exception_BadRequest();
+		}
+
 		$newPath = $parentPath . '/' . $newName;
 		$oldPath = $this->path;
 
-		\OC\Files\Filesystem::rename($this->path, $newPath);
+		$fs->rename($this->path, $newPath);
 
 		$this->path = $newPath;
 
diff --git a/lib/private/connector/sabre/objecttree.php b/lib/private/connector/sabre/objecttree.php
index d1e179af2e..d2fa425b22 100644
--- a/lib/private/connector/sabre/objecttree.php
+++ b/lib/private/connector/sabre/objecttree.php
@@ -105,6 +105,11 @@ class ObjectTree extends \Sabre_DAV_ObjectTree {
 			}
 		}
 
+		$fileName = basename($destinationPath);
+		if (!\OCP\Util::isValidFileName($fileName)) {
+			throw new \Sabre_DAV_Exception_BadRequest();
+		}
+
 		$renameOkay = $fs->rename($sourcePath, $destinationPath);
 		if (!$renameOkay) {
 			throw new \Sabre_DAV_Exception_Forbidden('');
diff --git a/tests/lib/connector/sabre/file.php b/tests/lib/connector/sabre/file.php
index e1fed0384c..50b8711a90 100644
--- a/tests/lib/connector/sabre/file.php
+++ b/tests/lib/connector/sabre/file.php
@@ -35,6 +35,46 @@ class Test_OC_Connector_Sabre_File extends PHPUnit_Framework_TestCase {
 		$etag = $file->put('test data');
 	}
 
+	/**
+	 * @expectedException Sabre_DAV_Exception_BadRequest
+	 */
+	public function testSimplePutInvalidChars() {
+		// setup
+		$file = new OC_Connector_Sabre_File('/super*star.txt');
+		$file->fileView = $this->getMock('\OC\Files\View', array('file_put_contents'), array(), '', FALSE);
+		$file->fileView->expects($this->any())->method('file_put_contents')->withAnyParameters()->will($this->returnValue(false));
+
+		// action
+		$etag = $file->put('test data');
+	}
+
+	/**
+	 * Test setting name with setName()
+	 */
+	public function testSetName() {
+		// setup
+		$file = new OC_Connector_Sabre_File('/test.txt');
+		$file->fileView = $this->getMock('\OC\Files\View', array('isUpdatable'), array(), '', FALSE);
+		$file->fileView->expects($this->any())->method('isUpdatable')->withAnyParameters()->will($this->returnValue(true));
+		$etag = $file->put('test data');
+		$file->setName('/renamed.txt');
+		$this->assertTrue($file->fileView->file_exists('/renamed.txt'));
+		// clean up
+		$file->delete();
+	}
+
+	/**
+	 * Test setting name with setName() with invalid chars
+	 * @expectedException Sabre_DAV_Exception_BadRequest
+	 */
+	public function testSetNameInvalidChars() {
+		// setup
+		$file = new OC_Connector_Sabre_File('/test.txt');
+		$file->fileView = $this->getMock('\OC\Files\View', array('isUpdatable'), array(), '', FALSE);
+		$file->fileView->expects($this->any())->method('isUpdatable')->withAnyParameters()->will($this->returnValue(true));
+		$file->setName('/super*star.txt');
+	}
+
 	/**
 	 * @expectedException Sabre_DAV_Exception_Forbidden
 	 */
diff --git a/tests/lib/connector/sabre/objecttree.php b/tests/lib/connector/sabre/objecttree.php
index e32f2365f9..fb50c736ed 100644
--- a/tests/lib/connector/sabre/objecttree.php
+++ b/tests/lib/connector/sabre/objecttree.php
@@ -52,6 +52,20 @@ class ObjectTree extends PHPUnit_Framework_TestCase {
 		$this->assertTrue(true);
 	}
 
+	/**
+	 * @dataProvider moveFailedInvalidCharsProvider
+	 * @expectedException Sabre_DAV_Exception_BadRequest
+	 */
+	public function testMoveFailedInvalidChars($source, $dest, $updatables, $deletables) {
+		$this->moveTest($source, $dest, $updatables, $deletables);
+	}
+
+	function moveFailedInvalidCharsProvider() {
+		return array(
+			array('a/b', 'a/c*', array('a' => false, 'a/b' => true, 'a/c*' => false), array()),
+		);
+	}
+
 	function moveFailedProvider() {
 		return array(
 			array('a/b', 'a/c', array('a' => false, 'a/b' => false, 'a/c' => false), array()),
@@ -66,6 +80,8 @@ class ObjectTree extends PHPUnit_Framework_TestCase {
 		return array(
 			array('a/b', 'a/c', array('a' => false, 'a/b' => true, 'a/c' => false), array()),
 			array('a/b', 'b/b', array('a' => true, 'a/b' => true, 'b' => true, 'b/b' => false), array('a/b' => true)),
+			// older files with special chars can still be renamed to valid names
+			array('a/b*', 'b/b', array('a' => true, 'a/b*' => true, 'b' => true, 'b/b' => false), array('a/b*' => true)),
 		);
 	}
 
-- 
GitLab