FilesystemTest.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  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', '/.///././//./foo/.///././//./bar/././././'],
  130. ['/foo/bar/', '/.///././//./foo/.///././//./bar/././././', 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/..', '\\foo\\..\\'],
  168. ];
  169. }
  170. /**
  171. * @dataProvider normalizePathData
  172. */
  173. public function testNormalizePath($expected, $path, $stripTrailingSlash = true) {
  174. $this->assertEquals($expected, \OC\Files\Filesystem::normalizePath($path, $stripTrailingSlash));
  175. }
  176. public function normalizePathKeepUnicodeData() {
  177. $nfdName = 'ümlaut';
  178. $nfcName = 'ümlaut';
  179. return [
  180. ['/' . $nfcName, $nfcName, true],
  181. ['/' . $nfcName, $nfcName, false],
  182. ['/' . $nfdName, $nfdName, true],
  183. ['/' . $nfcName, $nfdName, false],
  184. ];
  185. }
  186. /**
  187. * @dataProvider normalizePathKeepUnicodeData
  188. */
  189. public function testNormalizePathKeepUnicode($expected, $path, $keepUnicode = false) {
  190. $this->assertEquals($expected, \OC\Files\Filesystem::normalizePath($path, true, false, $keepUnicode));
  191. }
  192. public function testNormalizePathKeepUnicodeCache() {
  193. $nfdName = 'ümlaut';
  194. $nfcName = 'ümlaut';
  195. // call in succession due to cache
  196. $this->assertEquals('/' . $nfcName, \OC\Files\Filesystem::normalizePath($nfdName, true, false, false));
  197. $this->assertEquals('/' . $nfdName, \OC\Files\Filesystem::normalizePath($nfdName, true, false, true));
  198. }
  199. public function isValidPathData() {
  200. return [
  201. ['/', true],
  202. ['/path', true],
  203. ['/foo/bar', true],
  204. ['/foo//bar/', 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/././..bar', true],
  212. ['/foo/bar/././..bar/a', true],
  213. ['/foo/bar/././..', false],
  214. ['/foo/bar/././../', false],
  215. ['/foo/bar/.././', false],
  216. ['/foo/bar/../../', false],
  217. ['/foo/bar/../..\\', false],
  218. ['..', false],
  219. ['../', false],
  220. ['../foo/bar', false],
  221. ['..\foo/bar', false],
  222. ];
  223. }
  224. /**
  225. * @dataProvider isValidPathData
  226. */
  227. public function testIsValidPath($path, $expected) {
  228. $this->assertSame($expected, \OC\Files\Filesystem::isValidPath($path));
  229. }
  230. public function isFileBlacklistedData() {
  231. return [
  232. ['/etc/foo/bar/foo.txt', false],
  233. ['\etc\foo/bar\foo.txt', false],
  234. ['.htaccess', true],
  235. ['.htaccess/', true],
  236. ['.htaccess\\', true],
  237. ['/etc/foo\bar/.htaccess\\', true],
  238. ['/etc/foo\bar/.htaccess/', true],
  239. ['/etc/foo\bar/.htaccess/foo', false],
  240. ['//foo//bar/\.htaccess/', true],
  241. ['\foo\bar\.HTAccess', true],
  242. ];
  243. }
  244. /**
  245. * @dataProvider isFileBlacklistedData
  246. */
  247. public function testIsFileBlacklisted($path, $expected) {
  248. $this->assertSame($expected, \OC\Files\Filesystem::isFileBlacklisted($path));
  249. }
  250. public function testNormalizePathUTF8() {
  251. if (!class_exists('Patchwork\PHP\Shim\Normalizer')) {
  252. $this->markTestSkipped('UTF8 normalizer Patchwork was not found');
  253. }
  254. $this->assertEquals("/foo/bar\xC3\xBC", \OC\Files\Filesystem::normalizePath("/foo/baru\xCC\x88"));
  255. $this->assertEquals("/foo/bar\xC3\xBC", \OC\Files\Filesystem::normalizePath("\\foo\\baru\xCC\x88"));
  256. }
  257. public function testHooks() {
  258. if (\OC\Files\Filesystem::getView()) {
  259. $user = \OC_User::getUser();
  260. } else {
  261. $user = self::TEST_FILESYSTEM_USER1;
  262. $backend = new \Test\Util\User\Dummy();
  263. \OC_User::useBackend($backend);
  264. $backend->createUser($user, $user);
  265. $userObj = \OC::$server->getUserManager()->get($user);
  266. \OC::$server->getUserSession()->setUser($userObj);
  267. \OC\Files\Filesystem::init($user, '/' . $user . '/files');
  268. }
  269. \OC_Hook::clear('OC_Filesystem');
  270. \OC_Hook::connect('OC_Filesystem', 'post_write', $this, 'dummyHook');
  271. \OC\Files\Filesystem::mount('OC\Files\Storage\Temporary', [], '/');
  272. $rootView = new \OC\Files\View('');
  273. $rootView->mkdir('/' . $user);
  274. $rootView->mkdir('/' . $user . '/files');
  275. // \OC\Files\Filesystem::file_put_contents('/foo', 'foo');
  276. \OC\Files\Filesystem::mkdir('/bar');
  277. // \OC\Files\Filesystem::file_put_contents('/bar//foo', 'foo');
  278. $tmpFile = \OC::$server->getTempManager()->getTemporaryFile();
  279. file_put_contents($tmpFile, 'foo');
  280. $fh = fopen($tmpFile, 'r');
  281. // \OC\Files\Filesystem::file_put_contents('/bar//foo', $fh);
  282. }
  283. /**
  284. * Tests that an exception is thrown when passed user does not exist.
  285. *
  286. */
  287. public function testLocalMountWhenUserDoesNotExist() {
  288. $this->expectException(\OC\User\NoUserException::class);
  289. $userId = $this->getUniqueID('user_');
  290. \OC\Files\Filesystem::initMountPoints($userId);
  291. }
  292. public function testNullUserThrows() {
  293. $this->expectException(\OC\User\NoUserException::class);
  294. \OC\Files\Filesystem::initMountPoints(null);
  295. }
  296. public function testNullUserThrowsTwice() {
  297. $thrown = 0;
  298. try {
  299. \OC\Files\Filesystem::initMountPoints(null);
  300. } catch (NoUserException $e) {
  301. $thrown++;
  302. }
  303. try {
  304. \OC\Files\Filesystem::initMountPoints(null);
  305. } catch (NoUserException $e) {
  306. $thrown++;
  307. }
  308. $this->assertEquals(2, $thrown);
  309. }
  310. /**
  311. * Tests that an exception is thrown when passed user does not exist.
  312. */
  313. public function testLocalMountWhenUserDoesNotExistTwice() {
  314. $thrown = 0;
  315. $userId = $this->getUniqueID('user_');
  316. try {
  317. \OC\Files\Filesystem::initMountPoints($userId);
  318. } catch (NoUserException $e) {
  319. $thrown++;
  320. }
  321. try {
  322. \OC\Files\Filesystem::initMountPoints($userId);
  323. } catch (NoUserException $e) {
  324. $thrown++;
  325. }
  326. $this->assertEquals(2, $thrown);
  327. }
  328. /**
  329. * Tests that the home storage is used for the user's mount point
  330. */
  331. public function testHomeMount() {
  332. $userId = $this->getUniqueID('user_');
  333. \OC::$server->getUserManager()->createUser($userId, $userId);
  334. \OC\Files\Filesystem::initMountPoints($userId);
  335. $homeMount = \OC\Files\Filesystem::getStorage('/' . $userId . '/');
  336. $this->assertTrue($homeMount->instanceOfStorage('\OCP\Files\IHomeStorage'));
  337. if ($homeMount->instanceOfStorage('\OC\Files\ObjectStore\HomeObjectStoreStorage')) {
  338. $this->assertEquals('object::user:' . $userId, $homeMount->getId());
  339. } elseif ($homeMount->instanceOfStorage('\OC\Files\Storage\Home')) {
  340. $this->assertEquals('home::' . $userId, $homeMount->getId());
  341. }
  342. $user = \OC::$server->getUserManager()->get($userId);
  343. if ($user !== null) {
  344. $user->delete();
  345. }
  346. }
  347. public function dummyHook($arguments) {
  348. $path = $arguments['path'];
  349. $this->assertEquals($path, \OC\Files\Filesystem::normalizePath($path)); //the path passed to the hook should already be normalized
  350. }
  351. /**
  352. * Test that the default cache dir is part of the user's home
  353. */
  354. public function testMountDefaultCacheDir() {
  355. $userId = $this->getUniqueID('user_');
  356. $config = \OC::$server->getConfig();
  357. $oldCachePath = $config->getSystemValue('cache_path', '');
  358. // no cache path configured
  359. $config->setSystemValue('cache_path', '');
  360. \OC::$server->getUserManager()->createUser($userId, $userId);
  361. \OC\Files\Filesystem::initMountPoints($userId);
  362. $this->assertEquals(
  363. '/' . $userId . '/',
  364. \OC\Files\Filesystem::getMountPoint('/' . $userId . '/cache')
  365. );
  366. [$storage, $internalPath] = \OC\Files\Filesystem::resolvePath('/' . $userId . '/cache');
  367. $this->assertTrue($storage->instanceOfStorage('\OCP\Files\IHomeStorage'));
  368. $this->assertEquals('cache', $internalPath);
  369. $user = \OC::$server->getUserManager()->get($userId);
  370. if ($user !== null) {
  371. $user->delete();
  372. }
  373. $config->setSystemValue('cache_path', $oldCachePath);
  374. }
  375. /**
  376. * Test that an external cache is mounted into
  377. * the user's home
  378. */
  379. public function testMountExternalCacheDir() {
  380. $userId = $this->getUniqueID('user_');
  381. $config = \OC::$server->getConfig();
  382. $oldCachePath = $config->getSystemValue('cache_path', '');
  383. // set cache path to temp dir
  384. $cachePath = \OC::$server->getTempManager()->getTemporaryFolder() . '/extcache';
  385. $config->setSystemValue('cache_path', $cachePath);
  386. \OC::$server->getUserManager()->createUser($userId, $userId);
  387. \OC\Files\Filesystem::initMountPoints($userId);
  388. $this->assertEquals(
  389. '/' . $userId . '/cache/',
  390. \OC\Files\Filesystem::getMountPoint('/' . $userId . '/cache')
  391. );
  392. [$storage, $internalPath] = \OC\Files\Filesystem::resolvePath('/' . $userId . '/cache');
  393. $this->assertTrue($storage->instanceOfStorage('\OC\Files\Storage\Local'));
  394. $this->assertEquals('', $internalPath);
  395. $user = \OC::$server->getUserManager()->get($userId);
  396. if ($user !== null) {
  397. $user->delete();
  398. }
  399. $config->setSystemValue('cache_path', $oldCachePath);
  400. }
  401. public function testRegisterMountProviderAfterSetup() {
  402. \OC\Files\Filesystem::initMountPoints(self::TEST_FILESYSTEM_USER2);
  403. $this->assertEquals('/', \OC\Files\Filesystem::getMountPoint('/foo/bar'));
  404. $mount = new MountPoint(new Temporary([]), '/foo/bar');
  405. $mountProvider = new DummyMountProvider([self::TEST_FILESYSTEM_USER2 => [$mount]]);
  406. \OC::$server->getMountProviderCollection()->registerProvider($mountProvider);
  407. $this->assertEquals('/foo/bar/', \OC\Files\Filesystem::getMountPoint('/foo/bar'));
  408. }
  409. }