FilesystemTest.php 15 KB

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