summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/files/ajax/newfile.php14
-rw-r--r--apps/files/ajax/newfolder.php4
-rw-r--r--lib/private/connector/sabre/file.php10
-rw-r--r--lib/private/connector/sabre/node.php9
-rw-r--r--lib/private/connector/sabre/objecttree.php5
-rwxr-xr-xlib/private/util.php21
-rw-r--r--lib/public/constants.php3
-rw-r--r--lib/public/util.php9
-rw-r--r--tests/lib/connector/sabre/file.php40
-rw-r--r--tests/lib/connector/sabre/objecttree.php16
-rw-r--r--tests/lib/util.php48
11 files changed, 169 insertions, 10 deletions
diff --git a/apps/files/ajax/newfile.php b/apps/files/ajax/newfile.php
index 1853098c507..0187b200759 100644
--- a/apps/files/ajax/newfile.php
+++ b/apps/files/ajax/newfile.php
@@ -50,16 +50,22 @@ $l10n = \OC_L10n::get('files');
$result = array(
'success' => false,
'data' => NULL
- );
+);
+$trimmedFileName = trim($filename);
-if(trim($filename) === '') {
+if($trimmedFileName === '') {
$result['data'] = array('message' => (string)$l10n->t('File name cannot be empty.'));
OCP\JSON::error($result);
exit();
}
+if($trimmedFileName === '.' || $trimmedFileName === '..') {
+ $result['data'] = array('message' => (string)$l10n->t('"%s" is an invalid file name.', $trimmedFileName));
+ OCP\JSON::error($result);
+ exit();
+}
-if(strpos($filename, '/') !== false) {
- $result['data'] = array('message' => (string)$l10n->t('File name must not contain "/". Please choose a different name.'));
+if(!OCP\Util::isValidFileName($filename)) {
+ $result['data'] = array('message' => (string)$l10n->t("Invalid name, '\\', '/', '<', '>', ':', '\"', '|', '?' and '*' are not allowed."));
OCP\JSON::error($result);
exit();
}
diff --git a/apps/files/ajax/newfolder.php b/apps/files/ajax/newfolder.php
index 4cfcae3090d..b2b4fb27f74 100644
--- a/apps/files/ajax/newfolder.php
+++ b/apps/files/ajax/newfolder.php
@@ -23,8 +23,8 @@ if(trim($foldername) === '') {
exit();
}
-if(strpos($foldername, '/') !== false) {
- $result['data'] = array('message' => $l10n->t('Folder name must not contain "/". Please choose a different name.'));
+if(!OCP\Util::isValidFileName($foldername)) {
+ $result['data'] = array('message' => (string)$l10n->t("Invalid name, '\\', '/', '<', '>', ':', '\"', '|', '?' and '*' are not allowed."));
OCP\JSON::error($result);
exit();
}
diff --git a/lib/private/connector/sabre/file.php b/lib/private/connector/sabre/file.php
index 5ef6365f657..ef6caaf22a7 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 05d2d2291ec..5807c5c7f71 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 d1e179af2ec..d2fa425b22c 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/lib/private/util.php b/lib/private/util.php
index 829eedce044..b7856436527 100755
--- a/lib/private/util.php
+++ b/lib/private/util.php
@@ -1155,4 +1155,25 @@ class OC_Util {
}
return $version;
}
+
+ /**
+ * Returns whether the given file name is valid
+ * @param $file string file name to check
+ * @return bool true if the file name is valid, false otherwise
+ */
+ public static function isValidFileName($file) {
+ $trimmed = trim($file);
+ if ($trimmed === '') {
+ return false;
+ }
+ if ($trimmed === '.' || $trimmed === '..') {
+ return false;
+ }
+ foreach (str_split($trimmed) as $char) {
+ if (strpos(\OCP\FILENAME_INVALID_CHARS, $char) !== false) {
+ return false;
+ }
+ }
+ return true;
+ }
}
diff --git a/lib/public/constants.php b/lib/public/constants.php
index 1495c620dc9..350646a0ac0 100644
--- a/lib/public/constants.php
+++ b/lib/public/constants.php
@@ -35,3 +35,6 @@ const PERMISSION_UPDATE = 2;
const PERMISSION_DELETE = 8;
const PERMISSION_SHARE = 16;
const PERMISSION_ALL = 31;
+
+const FILENAME_INVALID_CHARS = "\\/<>:\"|?*\n";
+
diff --git a/lib/public/util.php b/lib/public/util.php
index 570283e2a8a..585c5d22634 100644
--- a/lib/public/util.php
+++ b/lib/public/util.php
@@ -486,4 +486,13 @@ class Util {
public static function uploadLimit() {
return \OC_Helper::uploadLimit();
}
+
+ /**
+ * Returns whether the given file name is valid
+ * @param $file string file name to check
+ * @return bool true if the file name is valid, false otherwise
+ */
+ public static function isValidFileName($file) {
+ return \OC_Util::isValidFileName($file);
+ }
}
diff --git a/tests/lib/connector/sabre/file.php b/tests/lib/connector/sabre/file.php
index e1fed0384c6..50b8711a90d 100644
--- a/tests/lib/connector/sabre/file.php
+++ b/tests/lib/connector/sabre/file.php
@@ -36,6 +36,46 @@ class Test_OC_Connector_Sabre_File extends PHPUnit_Framework_TestCase {
}
/**
+ * @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
*/
public function testDeleteSharedFails() {
diff --git a/tests/lib/connector/sabre/objecttree.php b/tests/lib/connector/sabre/objecttree.php
index e32f2365f95..fb50c736edd 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)),
);
}
diff --git a/tests/lib/util.php b/tests/lib/util.php
index bfe68f5f680..ee336aa1118 100644
--- a/tests/lib/util.php
+++ b/tests/lib/util.php
@@ -170,4 +170,52 @@ class Test_Util extends PHPUnit_Framework_TestCase {
array('442aa682de2a64db1e010f50e60fd9c9', 'local::C:\Users\ADMINI~1\AppData\Local\Temp\2/442aa682de2a64db1e010f50e60fd9c9/')
);
}
+
+ /**
+ * @dataProvider filenameValidationProvider
+ */
+ public function testFilenameValidation($file, $valid) {
+ // private API
+ $this->assertEquals($valid, \OC_Util::isValidFileName($file));
+ // public API
+ $this->assertEquals($valid, \OCP\Util::isValidFileName($file));
+ }
+
+ public function filenameValidationProvider() {
+ return array(
+ // valid names
+ array('boringname', true),
+ array('something.with.extension', true),
+ array('now with spaces', true),
+ array('.a', true),
+ array('..a', true),
+ array('.dotfile', true),
+ array('single\'quote', true),
+ array(' spaces before', true),
+ array('spaces after ', true),
+ array('allowed chars including the crazy ones $%&_-^@!,()[]{}=;#', true),
+ array('汉字也能用', true),
+ array('und Ümläüte sind auch willkommen', true),
+ // disallowed names
+ array('', false),
+ array(' ', false),
+ array('.', false),
+ array('..', false),
+ array('back\\slash', false),
+ array('sl/ash', false),
+ array('lt<lt', false),
+ array('gt>gt', false),
+ array('col:on', false),
+ array('double"quote', false),
+ array('pi|pe', false),
+ array('dont?ask?questions?', false),
+ array('super*star', false),
+ array('new\nline', false),
+ // better disallow these to avoid unexpected trimming to have side effects
+ array(' ..', false),
+ array('.. ', false),
+ array('. ', false),
+ array(' .', false),
+ );
+ }
}