diff --git a/db_structure.xml b/db_structure.xml index 3cb2af287a..21ac47a781 100644 --- a/db_structure.xml +++ b/db_structure.xml @@ -257,7 +257,7 @@ text false - 512 + 4000 diff --git a/lib/private/files/view.php b/lib/private/files/view.php index 940f31fe42..b698d87866 100644 --- a/lib/private/files/view.php +++ b/lib/private/files/view.php @@ -29,14 +29,13 @@ use OC\Files\Cache\Updater; class View { private $fakeRoot = ''; - private $internal_path_cache = array(); - private $storage_cache = array(); public function __construct($root = '') { $this->fakeRoot = $root; } public function getAbsolutePath($path = '/') { + $this->assertPathLength($path); if ($path === '') { $path = '/'; } @@ -77,6 +76,7 @@ class View { * @return string */ public function getRelativePath($path) { + $this->assertPathLength($path); if ($this->fakeRoot == '') { return $path; } @@ -208,6 +208,7 @@ class View { } public function readfile($path) { + $this->assertPathLength($path); @ob_end_clean(); $handle = $this->fopen($path, 'rb'); if ($handle) { @@ -595,6 +596,7 @@ class View { } public function toTmpFile($path) { + $this->assertPathLength($path); if (Filesystem::isValidPath($path)) { $source = $this->fopen($path, 'r'); if ($source) { @@ -611,7 +613,7 @@ class View { } public function fromTmpFile($tmpFile, $path) { - + $this->assertPathLength($path); if (Filesystem::isValidPath($path)) { // Get directory that the file is going into @@ -640,6 +642,7 @@ class View { } public function getMimeType($path) { + $this->assertPathLength($path); return $this->basicOperation('getMimeType', $path); } @@ -669,6 +672,7 @@ class View { } public function free_space($path = '/') { + $this->assertPathLength($path); return $this->basicOperation('free_space', $path); } @@ -808,6 +812,7 @@ class View { * @return \OC\Files\FileInfo|false */ public function getFileInfo($path, $includeMountPoints = true) { + $this->assertPathLength($path); $data = array(); if (!Filesystem::isValidPath($path)) { return $data; @@ -878,6 +883,7 @@ class View { * @return FileInfo[] */ public function getDirectoryContent($directory, $mimetype_filter = '') { + $this->assertPathLength($directory); $result = array(); if (!Filesystem::isValidPath($directory)) { return $result; @@ -1006,6 +1012,7 @@ class View { * returns the fileid of the updated file */ public function putFileInfo($path, $data) { + $this->assertPathLength($path); if ($data instanceof FileInfo) { $data = $data->getData(); } @@ -1153,4 +1160,12 @@ class View { } return null; } + + private function assertPathLength($path) { + $maxLen = min(PHP_MAXPATHLEN, 4000); + $pathLen = strlen($path); + if ($pathLen > $maxLen) { + throw new \OCP\Files\InvalidPathException("Path length($pathLen) exceeds max path length($maxLen): $path"); + } + } } diff --git a/tests/lib/files/view.php b/tests/lib/files/view.php index 201eb9ff6c..b5e4d79235 100644 --- a/tests/lib/files/view.php +++ b/tests/lib/files/view.php @@ -20,6 +20,7 @@ class View extends \PHPUnit_Framework_TestCase { * @var \OC\Files\Storage\Storage[] $storages */ private $storages = array(); + private $user; public function setUp() { \OC_User::clearBackends(); @@ -569,6 +570,47 @@ class View extends \PHPUnit_Framework_TestCase { } } + public function testLongPath() { + + $storage = new \OC\Files\Storage\Temporary(array()); + \OC\Files\Filesystem::mount($storage, array(), '/'); + + $rootView = new \OC\Files\View(''); + + $longPath = ''; + // 4000 is the maximum path length in file_cache.path + $folderName = 'abcdefghijklmnopqrstuvwxyz012345678901234567890123456789'; + $depth = (4000/57); + foreach (range(0, $depth-1) as $i) { + $longPath .= '/'.$folderName; + $result = $rootView->mkdir($longPath); + $this->assertTrue($result, "mkdir failed on $i - path length: " . strlen($longPath)); + + $result = $rootView->file_put_contents($longPath . '/test.txt', 'lorem'); + $this->assertEquals(5, $result, "file_put_contents failed on $i"); + + $this->assertTrue($rootView->file_exists($longPath)); + $this->assertTrue($rootView->file_exists($longPath . '/test.txt')); + } + + $cache = $storage->getCache(); + $scanner = $storage->getScanner(); + $scanner->scan(''); + + $longPath = $folderName; + foreach (range(0, $depth-1) as $i) { + $cachedFolder = $cache->get($longPath); + $this->assertTrue(is_array($cachedFolder), "No cache entry for folder at $i"); + $this->assertEquals($folderName, $cachedFolder['name'], "Wrong cache entry for folder at $i"); + + $cachedFile = $cache->get($longPath . '/test.txt'); + $this->assertTrue(is_array($cachedFile), "No cache entry for file at $i"); + $this->assertEquals('test.txt', $cachedFile['name'], "Wrong cache entry for file at $i"); + + $longPath .= '/' . $folderName; + } + } + public function testTouchNotSupported() { $storage = new TemporaryNoTouch(array()); $scanner = $storage->getScanner(); @@ -605,4 +647,83 @@ class View extends \PHPUnit_Framework_TestCase { array('/files/test', '/test'), ); } + + /** + * @dataProvider tooLongPathDataProvider + * @expectedException \OCP\Files\InvalidPathException + */ + public function testTooLongPath($operation, $param0 = NULL) { + + $longPath = ''; + // 4000 is the maximum path length in file_cache.path + $folderName = 'abcdefghijklmnopqrstuvwxyz012345678901234567890123456789'; + $depth = (4000/57); + foreach (range(0, $depth+1) as $i) { + $longPath .= '/'.$folderName; + } + + $storage = new \OC\Files\Storage\Temporary(array()); + \OC\Files\Filesystem::mount($storage, array(), '/'); + + $rootView = new \OC\Files\View(''); + + + if ($param0 === '@0') { + $param0 = $longPath; + } + + if ($operation === 'hash') { + $param0 = $longPath; + $longPath = 'md5'; + } + + call_user_func(array($rootView, $operation), $longPath, $param0); + + } + + public function tooLongPathDataProvider() { + return array( + array('getAbsolutePath'), + array('getRelativePath'), + array('getMountPoint'), + array('resolvePath'), + array('getLocalFile'), + array('getLocalFolder'), + array('mkdir'), + array('rmdir'), + array('opendir'), + array('is_dir'), + array('is_file'), + array('stat'), + array('filetype'), + array('filesize'), + array('readfile'), + array('isCreatable'), + array('isReadable'), + array('isUpdatable'), + array('isDeletable'), + array('isSharable'), + array('file_exists'), + array('filemtime'), + array('touch'), + array('file_get_contents'), + array('unlink'), + array('deleteAll'), + array('toTmpFile'), + array('getMimeType'), + array('free_space'), + array('getFileInfo'), + array('getDirectoryContent'), + array('getOwner'), + array('getETag'), + array('file_put_contents', 'ipsum'), + array('rename', '@0'), + array('copy', '@0'), + array('fopen', 'r'), + array('fromTmpFile', '@0'), + array('hash'), + array('hasUpdated', 0), + array('putFileInfo', array()), + ); + } } diff --git a/version.php b/version.php index 079af52545..28ef5ea72d 100644 --- a/version.php +++ b/version.php @@ -3,7 +3,7 @@ // We only can count up. The 4. digit is only for the internal patchlevel to trigger DB upgrades // between betas, final and RCs. This is _not_ the public version number. Reset minor/patchlevel // when updating major/minor version number. -$OC_Version=array(6, 90, 0, 2); +$OC_Version=array(6, 90, 0, 3); // The human readable string $OC_VersionString='7.0 pre alpha';