FilesystemTest.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. <?php
  2. /**
  3. * ownCloud
  4. *
  5. * @author Robin Appelman
  6. * @copyright 2012 Robin Appelman icewind@owncloud.com
  7. *
  8. * This library is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
  10. * License as published by the Free Software Foundation; either
  11. * version 3 of the License, or any later version.
  12. *
  13. * This library is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
  17. *
  18. * You should have received a copy of the GNU Affero General Public
  19. * License along with this library. If not, see <http://www.gnu.org/licenses/>.
  20. *
  21. */
  22. namespace Test\Files;
  23. use OC\Files\Mount\MountPoint;
  24. use OC\Files\Storage\Temporary;
  25. use OC\User\NoUserException;
  26. use OCP\Files\Config\IMountProvider;
  27. use OCP\Files\Storage\IStorageFactory;
  28. use OCP\IUser;
  29. class DummyMountProvider implements IMountProvider {
  30. private $mounts = [];
  31. /**
  32. * @param array $mounts
  33. */
  34. public function __construct(array $mounts) {
  35. $this->mounts = $mounts;
  36. }
  37. /**
  38. * Get the pre-registered mount points
  39. *
  40. * @param IUser $user
  41. * @param IStorageFactory $loader
  42. * @return \OCP\Files\Mount\IMountPoint[]
  43. */
  44. public function getMountsForUser(IUser $user, IStorageFactory $loader) {
  45. return isset($this->mounts[$user->getUID()]) ? $this->mounts[$user->getUID()] : [];
  46. }
  47. }
  48. /**
  49. * Class FilesystemTest
  50. *
  51. * @group DB
  52. *
  53. * @package Test\Files
  54. */
  55. class FilesystemTest extends \Test\TestCase {
  56. public const TEST_FILESYSTEM_USER1 = "test-filesystem-user1";
  57. public const TEST_FILESYSTEM_USER2 = "test-filesystem-user1";
  58. /**
  59. * @var array tmpDirs
  60. */
  61. private $tmpDirs = [];
  62. /**
  63. * @return array
  64. */
  65. private function getStorageData() {
  66. $dir = \OC::$server->getTempManager()->getTemporaryFolder();
  67. $this->tmpDirs[] = $dir;
  68. return ['datadir' => $dir];
  69. }
  70. protected function setUp(): void {
  71. parent::setUp();
  72. $userBackend = new \Test\Util\User\Dummy();
  73. $userBackend->createUser(self::TEST_FILESYSTEM_USER1, self::TEST_FILESYSTEM_USER1);
  74. $userBackend->createUser(self::TEST_FILESYSTEM_USER2, self::TEST_FILESYSTEM_USER2);
  75. \OC::$server->getUserManager()->registerBackend($userBackend);
  76. $this->loginAsUser();
  77. }
  78. protected function tearDown(): void {
  79. foreach ($this->tmpDirs as $dir) {
  80. \OC_Helper::rmdirr($dir);
  81. }
  82. $this->logout();
  83. $this->invokePrivate('\OC\Files\Filesystem', 'normalizedPathCache', [null]);
  84. parent::tearDown();
  85. }
  86. public function testMount() {
  87. \OC\Files\Filesystem::mount('\OC\Files\Storage\Local', self::getStorageData(), '/');
  88. $this->assertEquals('/', \OC\Files\Filesystem::getMountPoint('/'));
  89. $this->assertEquals('/', \OC\Files\Filesystem::getMountPoint('/some/folder'));
  90. [, $internalPath] = \OC\Files\Filesystem::resolvePath('/');
  91. $this->assertEquals('', $internalPath);
  92. [, $internalPath] = \OC\Files\Filesystem::resolvePath('/some/folder');
  93. $this->assertEquals('some/folder', $internalPath);
  94. \OC\Files\Filesystem::mount('\OC\Files\Storage\Local', self::getStorageData(), '/some');
  95. $this->assertEquals('/', \OC\Files\Filesystem::getMountPoint('/'));
  96. $this->assertEquals('/some/', \OC\Files\Filesystem::getMountPoint('/some/folder'));
  97. $this->assertEquals('/some/', \OC\Files\Filesystem::getMountPoint('/some/'));
  98. $this->assertEquals('/some/', \OC\Files\Filesystem::getMountPoint('/some'));
  99. [, $internalPath] = \OC\Files\Filesystem::resolvePath('/some/folder');
  100. $this->assertEquals('folder', $internalPath);
  101. }
  102. public function normalizePathData() {
  103. return [
  104. ['/', ''],
  105. ['/', '/'],
  106. ['/', '//'],
  107. ['/', '/', false],
  108. ['/', '//', false],
  109. ['/path', '/path/'],
  110. ['/path/', '/path/', false],
  111. ['/path', 'path'],
  112. ['/foo/bar', '/foo//bar/'],
  113. ['/foo/bar/', '/foo//bar/', false],
  114. ['/foo/bar', '/foo////bar'],
  115. ['/foo/bar', '/foo/////bar'],
  116. ['/foo/bar', '/foo/bar/.'],
  117. ['/foo/bar', '/foo/bar/./'],
  118. ['/foo/bar/', '/foo/bar/./', false],
  119. ['/foo/bar', '/foo/bar/./.'],
  120. ['/foo/bar', '/foo/bar/././'],
  121. ['/foo/bar/', '/foo/bar/././', false],
  122. ['/foo/bar', '/foo/./bar/'],
  123. ['/foo/bar/', '/foo/./bar/', false],
  124. ['/foo/.bar', '/foo/.bar/'],
  125. ['/foo/.bar/', '/foo/.bar/', false],
  126. ['/foo/.bar/tee', '/foo/.bar/tee'],
  127. ['/foo/bar.', '/foo/bar./'],
  128. ['/foo/bar./', '/foo/bar./', false],
  129. ['/foo/bar./tee', '/foo/bar./tee'],
  130. ['/foo/.bar.', '/foo/.bar./'],
  131. ['/foo/.bar./', '/foo/.bar./', false],
  132. ['/foo/.bar./tee', '/foo/.bar./tee'],
  133. ['/foo/bar', '/.////././//./foo/.///././//./bar/././/./.'],
  134. ['/foo/bar/', '/.////././//./foo/.///././//./bar/./././.', false],
  135. ['/foo/bar', '/.////././//./foo/.///././//./bar/././/././'],
  136. ['/foo/bar/', '/.////././//./foo/.///././//./bar/././/././', false],
  137. ['/foo/.bar', '/.////././//./foo/./././/./.bar/././/././'],
  138. ['/foo/.bar/', '/.////././//./foo/./././/./.bar/././/././', false],
  139. ['/foo/.bar/tee./', '/.////././//./foo/./././/./.bar/tee././/././', false],
  140. ['/foo/bar.', '/.////././//./foo/./././/./bar./././/././'],
  141. ['/foo/bar./', '/.////././//./foo/./././/./bar./././/././', false],
  142. ['/foo/bar./tee./', '/.////././//./foo/./././/./bar./tee././/././', false],
  143. ['/foo/.bar.', '/.////././//./foo/./././/./.bar./././/././'],
  144. ['/foo/.bar./', '/.////././//./foo/./././/./.bar./././././', false],
  145. ['/foo/.bar./tee./', '/.////././//./foo/./././/./.bar./tee././././', false],
  146. // Windows paths
  147. ['/', ''],
  148. ['/', '\\'],
  149. ['/', '\\', false],
  150. ['/', '\\\\'],
  151. ['/', '\\\\', false],
  152. ['/path', '\\path'],
  153. ['/path', '\\path', false],
  154. ['/path', '\\path\\'],
  155. ['/path/', '\\path\\', false],
  156. ['/foo/bar', '\\foo\\\\bar\\'],
  157. ['/foo/bar/', '\\foo\\\\bar\\', false],
  158. ['/foo/bar', '\\foo\\\\\\\\bar'],
  159. ['/foo/bar', '\\foo\\\\\\\\\\bar'],
  160. ['/foo/bar', '\\foo\\bar\\.'],
  161. ['/foo/bar', '\\foo\\bar\\.\\'],
  162. ['/foo/bar/', '\\foo\\bar\\.\\', false],
  163. ['/foo/bar', '\\foo\\bar\\.\\.'],
  164. ['/foo/bar', '\\foo\\bar\\.\\.\\'],
  165. ['/foo/bar/', '\\foo\\bar\\.\\.\\', false],
  166. ['/foo/bar', '\\foo\\.\\bar\\'],
  167. ['/foo/bar/', '\\foo\\.\\bar\\', false],
  168. ['/foo/.bar', '\\foo\\.bar\\'],
  169. ['/foo/.bar/', '\\foo\\.bar\\', false],
  170. ['/foo/.bar/tee', '\\foo\\.bar\\tee'],
  171. // Absolute windows paths NOT marked as absolute
  172. ['/C:', 'C:\\'],
  173. ['/C:/', 'C:\\', false],
  174. ['/C:/tests', 'C:\\tests'],
  175. ['/C:/tests', 'C:\\tests', false],
  176. ['/C:/tests', 'C:\\tests\\'],
  177. ['/C:/tests/', 'C:\\tests\\', false],
  178. ['/C:/tests/bar', 'C:\\tests\\.\\.\\bar'],
  179. ['/C:/tests/bar/', 'C:\\tests\\.\\.\\bar\\.\\', false],
  180. // normalize does not resolve '..' (by design)
  181. ['/foo/..', '/foo/../'],
  182. ['/foo/../bar', '/foo/../bar/.'],
  183. ['/foo/..', '\\foo\\..\\'],
  184. ['/foo/../bar', '\\foo\\..\\bar'],
  185. ];
  186. }
  187. /**
  188. * @dataProvider normalizePathData
  189. */
  190. public function testNormalizePath($expected, $path, $stripTrailingSlash = true) {
  191. $this->assertEquals($expected, \OC\Files\Filesystem::normalizePath($path, $stripTrailingSlash));
  192. }
  193. public function normalizePathKeepUnicodeData() {
  194. $nfdName = 'ümlaut';
  195. $nfcName = 'ümlaut';
  196. return [
  197. ['/' . $nfcName, $nfcName, true],
  198. ['/' . $nfcName, $nfcName, false],
  199. ['/' . $nfdName, $nfdName, true],
  200. ['/' . $nfcName, $nfdName, false],
  201. ];
  202. }
  203. /**
  204. * @dataProvider normalizePathKeepUnicodeData
  205. */
  206. public function testNormalizePathKeepUnicode($expected, $path, $keepUnicode = false) {
  207. $this->assertEquals($expected, \OC\Files\Filesystem::normalizePath($path, true, false, $keepUnicode));
  208. }
  209. public function testNormalizePathKeepUnicodeCache() {
  210. $nfdName = 'ümlaut';
  211. $nfcName = 'ümlaut';
  212. // call in succession due to cache
  213. $this->assertEquals('/' . $nfcName, \OC\Files\Filesystem::normalizePath($nfdName, true, false, false));
  214. $this->assertEquals('/' . $nfdName, \OC\Files\Filesystem::normalizePath($nfdName, true, false, true));
  215. }
  216. public function isValidPathData() {
  217. return [
  218. ['/', true],
  219. ['/path', true],
  220. ['/foo/bar', true],
  221. ['/foo//bar/', true],
  222. ['/foo////bar', true],
  223. ['/foo//\///bar', true],
  224. ['/foo/bar/.', true],
  225. ['/foo/bar/./', true],
  226. ['/foo/bar/./.', true],
  227. ['/foo/bar/././', true],
  228. ['/foo/bar/././..bar', true],
  229. ['/foo/bar/././..bar/a', true],
  230. ['/foo/bar/././..', false],
  231. ['/foo/bar/././../', false],
  232. ['/foo/bar/.././', false],
  233. ['/foo/bar/../../', false],
  234. ['/foo/bar/../..\\', false],
  235. ['..', false],
  236. ['../', false],
  237. ['../foo/bar', false],
  238. ['..\foo/bar', false],
  239. ];
  240. }
  241. /**
  242. * @dataProvider isValidPathData
  243. */
  244. public function testIsValidPath($path, $expected) {
  245. $this->assertSame($expected, \OC\Files\Filesystem::isValidPath($path));
  246. }
  247. public function isFileBlacklistedData() {
  248. return [
  249. ['/etc/foo/bar/foo.txt', false],
  250. ['\etc\foo/bar\foo.txt', false],
  251. ['.htaccess', true],
  252. ['.htaccess/', true],
  253. ['.htaccess\\', true],
  254. ['/etc/foo\bar/.htaccess\\', true],
  255. ['/etc/foo\bar/.htaccess/', true],
  256. ['/etc/foo\bar/.htaccess/foo', false],
  257. ['//foo//bar/\.htaccess/', true],
  258. ['\foo\bar\.HTAccess', true],
  259. ];
  260. }
  261. /**
  262. * @dataProvider isFileBlacklistedData
  263. */
  264. public function testIsFileBlacklisted($path, $expected) {
  265. $this->assertSame($expected, \OC\Files\Filesystem::isFileBlacklisted($path));
  266. }
  267. public function testNormalizePathUTF8() {
  268. if (!class_exists('Patchwork\PHP\Shim\Normalizer')) {
  269. $this->markTestSkipped('UTF8 normalizer Patchwork was not found');
  270. }
  271. $this->assertEquals("/foo/bar\xC3\xBC", \OC\Files\Filesystem::normalizePath("/foo/baru\xCC\x88"));
  272. $this->assertEquals("/foo/bar\xC3\xBC", \OC\Files\Filesystem::normalizePath("\\foo\\baru\xCC\x88"));
  273. }
  274. public function testHooks() {
  275. if (\OC\Files\Filesystem::getView()) {
  276. $user = \OC_User::getUser();
  277. } else {
  278. $user = self::TEST_FILESYSTEM_USER1;
  279. $backend = new \Test\Util\User\Dummy();
  280. \OC_User::useBackend($backend);
  281. $backend->createUser($user, $user);
  282. $userObj = \OC::$server->getUserManager()->get($user);
  283. \OC::$server->getUserSession()->setUser($userObj);
  284. \OC\Files\Filesystem::init($user, '/' . $user . '/files');
  285. }
  286. \OC_Hook::clear('OC_Filesystem');
  287. \OC_Hook::connect('OC_Filesystem', 'post_write', $this, 'dummyHook');
  288. \OC\Files\Filesystem::mount('OC\Files\Storage\Temporary', [], '/');
  289. $rootView = new \OC\Files\View('');
  290. $rootView->mkdir('/' . $user);
  291. $rootView->mkdir('/' . $user . '/files');
  292. // \OC\Files\Filesystem::file_put_contents('/foo', 'foo');
  293. \OC\Files\Filesystem::mkdir('/bar');
  294. // \OC\Files\Filesystem::file_put_contents('/bar//foo', 'foo');
  295. $tmpFile = \OC::$server->getTempManager()->getTemporaryFile();
  296. file_put_contents($tmpFile, 'foo');
  297. $fh = fopen($tmpFile, 'r');
  298. // \OC\Files\Filesystem::file_put_contents('/bar//foo', $fh);
  299. }
  300. /**
  301. * Tests that an exception is thrown when passed user does not exist.
  302. *
  303. */
  304. public function testLocalMountWhenUserDoesNotExist() {
  305. $this->expectException(\OC\User\NoUserException::class);
  306. $userId = $this->getUniqueID('user_');
  307. \OC\Files\Filesystem::initMountPoints($userId);
  308. }
  309. public function testNullUserThrows() {
  310. $this->expectException(\OC\User\NoUserException::class);
  311. \OC\Files\Filesystem::initMountPoints(null);
  312. }
  313. public function testNullUserThrowsTwice() {
  314. $thrown = 0;
  315. try {
  316. \OC\Files\Filesystem::initMountPoints(null);
  317. } catch (NoUserException $e) {
  318. $thrown++;
  319. }
  320. try {
  321. \OC\Files\Filesystem::initMountPoints(null);
  322. } catch (NoUserException $e) {
  323. $thrown++;
  324. }
  325. $this->assertEquals(2, $thrown);
  326. }
  327. /**
  328. * Tests that an exception is thrown when passed user does not exist.
  329. */
  330. public function testLocalMountWhenUserDoesNotExistTwice() {
  331. $thrown = 0;
  332. $userId = $this->getUniqueID('user_');
  333. try {
  334. \OC\Files\Filesystem::initMountPoints($userId);
  335. } catch (NoUserException $e) {
  336. $thrown++;
  337. }
  338. try {
  339. \OC\Files\Filesystem::initMountPoints($userId);
  340. } catch (NoUserException $e) {
  341. $thrown++;
  342. }
  343. $this->assertEquals(2, $thrown);
  344. }
  345. /**
  346. * Tests that the home storage is used for the user's mount point
  347. */
  348. public function testHomeMount() {
  349. $userId = $this->getUniqueID('user_');
  350. \OC::$server->getUserManager()->createUser($userId, $userId);
  351. \OC\Files\Filesystem::initMountPoints($userId);
  352. $homeMount = \OC\Files\Filesystem::getStorage('/' . $userId . '/');
  353. $this->assertTrue($homeMount->instanceOfStorage('\OCP\Files\IHomeStorage'));
  354. if ($homeMount->instanceOfStorage('\OC\Files\ObjectStore\HomeObjectStoreStorage')) {
  355. $this->assertEquals('object::user:' . $userId, $homeMount->getId());
  356. } elseif ($homeMount->instanceOfStorage('\OC\Files\Storage\Home')) {
  357. $this->assertEquals('home::' . $userId, $homeMount->getId());
  358. }
  359. $user = \OC::$server->getUserManager()->get($userId);
  360. if ($user !== null) {
  361. $user->delete();
  362. }
  363. }
  364. public function dummyHook($arguments) {
  365. $path = $arguments['path'];
  366. $this->assertEquals($path, \OC\Files\Filesystem::normalizePath($path)); //the path passed to the hook should already be normalized
  367. }
  368. /**
  369. * Test that the default cache dir is part of the user's home
  370. */
  371. public function testMountDefaultCacheDir() {
  372. $userId = $this->getUniqueID('user_');
  373. $config = \OC::$server->getConfig();
  374. $oldCachePath = $config->getSystemValue('cache_path', '');
  375. // no cache path configured
  376. $config->setSystemValue('cache_path', '');
  377. \OC::$server->getUserManager()->createUser($userId, $userId);
  378. \OC\Files\Filesystem::initMountPoints($userId);
  379. $this->assertEquals(
  380. '/' . $userId . '/',
  381. \OC\Files\Filesystem::getMountPoint('/' . $userId . '/cache')
  382. );
  383. [$storage, $internalPath] = \OC\Files\Filesystem::resolvePath('/' . $userId . '/cache');
  384. $this->assertTrue($storage->instanceOfStorage('\OCP\Files\IHomeStorage'));
  385. $this->assertEquals('cache', $internalPath);
  386. $user = \OC::$server->getUserManager()->get($userId);
  387. if ($user !== null) {
  388. $user->delete();
  389. }
  390. $config->setSystemValue('cache_path', $oldCachePath);
  391. }
  392. /**
  393. * Test that an external cache is mounted into
  394. * the user's home
  395. */
  396. public function testMountExternalCacheDir() {
  397. $userId = $this->getUniqueID('user_');
  398. $config = \OC::$server->getConfig();
  399. $oldCachePath = $config->getSystemValue('cache_path', '');
  400. // set cache path to temp dir
  401. $cachePath = \OC::$server->getTempManager()->getTemporaryFolder() . '/extcache';
  402. $config->setSystemValue('cache_path', $cachePath);
  403. \OC::$server->getUserManager()->createUser($userId, $userId);
  404. \OC\Files\Filesystem::initMountPoints($userId);
  405. $this->assertEquals(
  406. '/' . $userId . '/cache/',
  407. \OC\Files\Filesystem::getMountPoint('/' . $userId . '/cache')
  408. );
  409. [$storage, $internalPath] = \OC\Files\Filesystem::resolvePath('/' . $userId . '/cache');
  410. $this->assertTrue($storage->instanceOfStorage('\OC\Files\Storage\Local'));
  411. $this->assertEquals('', $internalPath);
  412. $user = \OC::$server->getUserManager()->get($userId);
  413. if ($user !== null) {
  414. $user->delete();
  415. }
  416. $config->setSystemValue('cache_path', $oldCachePath);
  417. }
  418. public function testRegisterMountProviderAfterSetup() {
  419. \OC\Files\Filesystem::initMountPoints(self::TEST_FILESYSTEM_USER2);
  420. $this->assertEquals('/', \OC\Files\Filesystem::getMountPoint('/foo/bar'));
  421. $mount = new MountPoint(new Temporary([]), '/foo/bar');
  422. $mountProvider = new DummyMountProvider([self::TEST_FILESYSTEM_USER2 => [$mount]]);
  423. \OC::$server->getMountProviderCollection()->registerProvider($mountProvider);
  424. $this->assertEquals('/foo/bar/', \OC\Files\Filesystem::getMountPoint('/foo/bar'));
  425. }
  426. }