You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2714 lines
84 KiB

10 years ago
12 years ago
10 years ago
10 years ago
10 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
  1. <?php
  2. /**
  3. * Copyright (c) 2012 Robin Appelman <icewind@owncloud.com>
  4. * This file is licensed under the Affero General Public License version 3 or
  5. * later.
  6. * See the COPYING-README file. */
  7. namespace Test\Files;
  8. use OC\Cache\CappedMemoryCache;
  9. use OC\Files\Cache\Watcher;
  10. use OC\Files\Filesystem;
  11. use OC\Files\Mount\MountPoint;
  12. use OC\Files\Storage\Common;
  13. use OC\Files\Storage\Temporary;
  14. use OC\Files\View;
  15. use OCP\Constants;
  16. use OCP\Files\Config\IMountProvider;
  17. use OCP\Files\FileInfo;
  18. use OCP\Files\GenericFileException;
  19. use OCP\Files\Storage\IStorage;
  20. use OCP\Lock\ILockingProvider;
  21. use OCP\Lock\LockedException;
  22. use OCP\Share\IShare;
  23. use OCP\Util;
  24. use Test\HookHelper;
  25. use Test\TestMoveableMountPoint;
  26. use Test\Traits\UserTrait;
  27. class TemporaryNoTouch extends Temporary {
  28. public function touch($path, $mtime = null) {
  29. return false;
  30. }
  31. }
  32. class TemporaryNoCross extends Temporary {
  33. public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime = null) {
  34. return Common::copyFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath, $preserveMtime);
  35. }
  36. public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) {
  37. return Common::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
  38. }
  39. }
  40. class TemporaryNoLocal extends Temporary {
  41. public function instanceOfStorage($className) {
  42. if ($className === '\OC\Files\Storage\Local') {
  43. return false;
  44. } else {
  45. return parent::instanceOfStorage($className);
  46. }
  47. }
  48. }
  49. /**
  50. * Class ViewTest
  51. *
  52. * @group DB
  53. *
  54. * @package Test\Files
  55. */
  56. class ViewTest extends \Test\TestCase {
  57. use UserTrait;
  58. /**
  59. * @var \OC\Files\Storage\Storage[] $storages
  60. */
  61. private $storages = [];
  62. /**
  63. * @var string
  64. */
  65. private $user;
  66. /**
  67. * @var \OCP\IUser
  68. */
  69. private $userObject;
  70. /**
  71. * @var \OCP\IGroup
  72. */
  73. private $groupObject;
  74. /** @var \OC\Files\Storage\Storage */
  75. private $tempStorage;
  76. protected function setUp(): void {
  77. parent::setUp();
  78. \OC_Hook::clear();
  79. \OC_User::clearBackends();
  80. \OC_User::useBackend(new \Test\Util\User\Dummy());
  81. //login
  82. $userManager = \OC::$server->getUserManager();
  83. $groupManager = \OC::$server->getGroupManager();
  84. $this->user = 'test';
  85. $this->userObject = $userManager->createUser('test', 'test');
  86. $this->groupObject = $groupManager->createGroup('group1');
  87. $this->groupObject->addUser($this->userObject);
  88. self::loginAsUser($this->user);
  89. // clear mounts but somehow keep the root storage
  90. // that was initialized above...
  91. Filesystem::clearMounts();
  92. $this->tempStorage = null;
  93. }
  94. protected function tearDown(): void {
  95. \OC_User::setUserId($this->user);
  96. foreach ($this->storages as $storage) {
  97. $cache = $storage->getCache();
  98. $ids = $cache->getAll();
  99. $cache->clear();
  100. }
  101. if ($this->tempStorage) {
  102. system('rm -rf ' . escapeshellarg($this->tempStorage->getDataDir()));
  103. }
  104. self::logout();
  105. $this->userObject->delete();
  106. $this->groupObject->delete();
  107. $mountProviderCollection = \OC::$server->getMountProviderCollection();
  108. self::invokePrivate($mountProviderCollection, 'providers', [[]]);
  109. parent::tearDown();
  110. }
  111. /**
  112. * @medium
  113. */
  114. public function testCacheAPI() {
  115. $storage1 = $this->getTestStorage();
  116. $storage2 = $this->getTestStorage();
  117. $storage3 = $this->getTestStorage();
  118. $root = self::getUniqueID('/');
  119. Filesystem::mount($storage1, [], $root . '/');
  120. Filesystem::mount($storage2, [], $root . '/substorage');
  121. Filesystem::mount($storage3, [], $root . '/folder/anotherstorage');
  122. $textSize = strlen("dummy file data\n");
  123. $imageSize = filesize(\OC::$SERVERROOT . '/core/img/logo/logo.png');
  124. $storageSize = $textSize * 2 + $imageSize;
  125. $storageInfo = $storage3->getCache()->get('');
  126. $this->assertEquals($storageSize, $storageInfo['size']);
  127. $rootView = new View($root);
  128. $cachedData = $rootView->getFileInfo('/foo.txt');
  129. $this->assertEquals($textSize, $cachedData['size']);
  130. $this->assertEquals('text/plain', $cachedData['mimetype']);
  131. $this->assertNotEquals(-1, $cachedData['permissions']);
  132. $cachedData = $rootView->getFileInfo('/');
  133. $this->assertEquals($storageSize * 3, $cachedData['size']);
  134. $this->assertEquals('httpd/unix-directory', $cachedData['mimetype']);
  135. // get cached data excluding mount points
  136. $cachedData = $rootView->getFileInfo('/', false);
  137. $this->assertEquals($storageSize, $cachedData['size']);
  138. $this->assertEquals('httpd/unix-directory', $cachedData['mimetype']);
  139. $cachedData = $rootView->getFileInfo('/folder');
  140. $this->assertEquals($storageSize + $textSize, $cachedData['size']);
  141. $this->assertEquals('httpd/unix-directory', $cachedData['mimetype']);
  142. $folderData = $rootView->getDirectoryContent('/');
  143. /**
  144. * expected entries:
  145. * folder
  146. * foo.png
  147. * foo.txt
  148. * substorage
  149. */
  150. $this->assertCount(4, $folderData);
  151. $this->assertEquals('folder', $folderData[0]['name']);
  152. $this->assertEquals('foo.png', $folderData[1]['name']);
  153. $this->assertEquals('foo.txt', $folderData[2]['name']);
  154. $this->assertEquals('substorage', $folderData[3]['name']);
  155. $this->assertEquals($storageSize + $textSize, $folderData[0]['size']);
  156. $this->assertEquals($imageSize, $folderData[1]['size']);
  157. $this->assertEquals($textSize, $folderData[2]['size']);
  158. $this->assertEquals($storageSize, $folderData[3]['size']);
  159. $folderData = $rootView->getDirectoryContent('/substorage');
  160. /**
  161. * expected entries:
  162. * folder
  163. * foo.png
  164. * foo.txt
  165. */
  166. $this->assertCount(3, $folderData);
  167. $this->assertEquals('folder', $folderData[0]['name']);
  168. $this->assertEquals('foo.png', $folderData[1]['name']);
  169. $this->assertEquals('foo.txt', $folderData[2]['name']);
  170. $folderView = new View($root . '/folder');
  171. $this->assertEquals($rootView->getFileInfo('/folder'), $folderView->getFileInfo('/'));
  172. $cachedData = $rootView->getFileInfo('/foo.txt');
  173. $this->assertFalse($cachedData['encrypted']);
  174. $id = $rootView->putFileInfo('/foo.txt', ['encrypted' => true]);
  175. $cachedData = $rootView->getFileInfo('/foo.txt');
  176. $this->assertTrue($cachedData['encrypted']);
  177. $this->assertEquals($cachedData['fileid'], $id);
  178. $this->assertFalse($rootView->getFileInfo('/non/existing'));
  179. $this->assertEquals([], $rootView->getDirectoryContent('/non/existing'));
  180. }
  181. /**
  182. * @medium
  183. */
  184. public function testGetPath() {
  185. $storage1 = $this->getTestStorage();
  186. $storage2 = $this->getTestStorage();
  187. $storage3 = $this->getTestStorage();
  188. Filesystem::mount($storage1, [], '/');
  189. Filesystem::mount($storage2, [], '/substorage');
  190. Filesystem::mount($storage3, [], '/folder/anotherstorage');
  191. $rootView = new View('');
  192. $cachedData = $rootView->getFileInfo('/foo.txt');
  193. /** @var int $id1 */
  194. $id1 = $cachedData['fileid'];
  195. $this->assertEquals('/foo.txt', $rootView->getPath($id1));
  196. $cachedData = $rootView->getFileInfo('/substorage/foo.txt');
  197. /** @var int $id2 */
  198. $id2 = $cachedData['fileid'];
  199. $this->assertEquals('/substorage/foo.txt', $rootView->getPath($id2));
  200. $folderView = new View('/substorage');
  201. $this->assertEquals('/foo.txt', $folderView->getPath($id2));
  202. }
  203. public function testGetPathNotExisting() {
  204. $this->expectException(\OCP\Files\NotFoundException::class);
  205. $storage1 = $this->getTestStorage();
  206. Filesystem::mount($storage1, [], '/');
  207. $rootView = new View('');
  208. $cachedData = $rootView->getFileInfo('/foo.txt');
  209. /** @var int $id1 */
  210. $id1 = $cachedData['fileid'];
  211. $folderView = new View('/substorage');
  212. $this->assertNull($folderView->getPath($id1));
  213. }
  214. /**
  215. * @medium
  216. */
  217. public function testMountPointOverwrite() {
  218. $storage1 = $this->getTestStorage(false);
  219. $storage2 = $this->getTestStorage();
  220. $storage1->mkdir('substorage');
  221. Filesystem::mount($storage1, [], '/');
  222. Filesystem::mount($storage2, [], '/substorage');
  223. $rootView = new View('');
  224. $folderContent = $rootView->getDirectoryContent('/');
  225. $this->assertCount(4, $folderContent);
  226. }
  227. public function sharingDisabledPermissionProvider() {
  228. return [
  229. ['no', '', true],
  230. ['yes', 'group1', false],
  231. ];
  232. }
  233. /**
  234. * @dataProvider sharingDisabledPermissionProvider
  235. */
  236. public function testRemoveSharePermissionWhenSharingDisabledForUser($excludeGroups, $excludeGroupsList, $expectedShareable) {
  237. // Reset sharing disabled for users cache
  238. self::invokePrivate(\OC::$server->getShareManager(), 'sharingDisabledForUsersCache', [new CappedMemoryCache()]);
  239. $config = \OC::$server->getConfig();
  240. $oldExcludeGroupsFlag = $config->getAppValue('core', 'shareapi_exclude_groups', 'no');
  241. $oldExcludeGroupsList = $config->getAppValue('core', 'shareapi_exclude_groups_list', '');
  242. $config->setAppValue('core', 'shareapi_exclude_groups', $excludeGroups);
  243. $config->setAppValue('core', 'shareapi_exclude_groups_list', $excludeGroupsList);
  244. $storage1 = $this->getTestStorage();
  245. $storage2 = $this->getTestStorage();
  246. Filesystem::mount($storage1, [], '/');
  247. Filesystem::mount($storage2, [], '/mount');
  248. $view = new View('/');
  249. $folderContent = $view->getDirectoryContent('');
  250. $this->assertEquals($expectedShareable, $folderContent[0]->isShareable());
  251. $folderContent = $view->getDirectoryContent('mount');
  252. $this->assertEquals($expectedShareable, $folderContent[0]->isShareable());
  253. $config->setAppValue('core', 'shareapi_exclude_groups', $oldExcludeGroupsFlag);
  254. $config->setAppValue('core', 'shareapi_exclude_groups_list', $oldExcludeGroupsList);
  255. // Reset sharing disabled for users cache
  256. self::invokePrivate(\OC::$server->getShareManager(), 'sharingDisabledForUsersCache', [new CappedMemoryCache()]);
  257. }
  258. public function testCacheIncompleteFolder() {
  259. $storage1 = $this->getTestStorage(false);
  260. Filesystem::clearMounts();
  261. Filesystem::mount($storage1, [], '/incomplete');
  262. $rootView = new View('/incomplete');
  263. $entries = $rootView->getDirectoryContent('/');
  264. $this->assertCount(3, $entries);
  265. // /folder will already be in the cache but not scanned
  266. $entries = $rootView->getDirectoryContent('/folder');
  267. $this->assertCount(1, $entries);
  268. }
  269. public function testAutoScan() {
  270. $storage1 = $this->getTestStorage(false);
  271. $storage2 = $this->getTestStorage(false);
  272. Filesystem::mount($storage1, [], '/');
  273. Filesystem::mount($storage2, [], '/substorage');
  274. $textSize = strlen("dummy file data\n");
  275. $rootView = new View('');
  276. $cachedData = $rootView->getFileInfo('/');
  277. $this->assertEquals('httpd/unix-directory', $cachedData['mimetype']);
  278. $this->assertEquals(-1, $cachedData['size']);
  279. $folderData = $rootView->getDirectoryContent('/substorage/folder');
  280. $this->assertEquals('text/plain', $folderData[0]['mimetype']);
  281. $this->assertEquals($textSize, $folderData[0]['size']);
  282. }
  283. /**
  284. * @medium
  285. */
  286. public function testSearch() {
  287. $storage1 = $this->getTestStorage();
  288. $storage2 = $this->getTestStorage();
  289. $storage3 = $this->getTestStorage();
  290. Filesystem::mount($storage1, [], '/');
  291. Filesystem::mount($storage2, [], '/substorage');
  292. Filesystem::mount($storage3, [], '/folder/anotherstorage');
  293. $rootView = new View('');
  294. $results = $rootView->search('foo');
  295. $this->assertCount(6, $results);
  296. $paths = [];
  297. foreach ($results as $result) {
  298. $this->assertEquals($result['path'], Filesystem::normalizePath($result['path']));
  299. $paths[] = $result['path'];
  300. }
  301. $this->assertContains('/foo.txt', $paths);
  302. $this->assertContains('/foo.png', $paths);
  303. $this->assertContains('/substorage/foo.txt', $paths);
  304. $this->assertContains('/substorage/foo.png', $paths);
  305. $this->assertContains('/folder/anotherstorage/foo.txt', $paths);
  306. $this->assertContains('/folder/anotherstorage/foo.png', $paths);
  307. $folderView = new View('/folder');
  308. $results = $folderView->search('bar');
  309. $this->assertCount(2, $results);
  310. $paths = [];
  311. foreach ($results as $result) {
  312. $paths[] = $result['path'];
  313. }
  314. $this->assertContains('/anotherstorage/folder/bar.txt', $paths);
  315. $this->assertContains('/bar.txt', $paths);
  316. $results = $folderView->search('foo');
  317. $this->assertCount(2, $results);
  318. $paths = [];
  319. foreach ($results as $result) {
  320. $paths[] = $result['path'];
  321. }
  322. $this->assertContains('/anotherstorage/foo.txt', $paths);
  323. $this->assertContains('/anotherstorage/foo.png', $paths);
  324. $this->assertCount(6, $rootView->searchByMime('text'));
  325. $this->assertCount(3, $folderView->searchByMime('text'));
  326. }
  327. /**
  328. * @medium
  329. */
  330. public function testWatcher() {
  331. $storage1 = $this->getTestStorage();
  332. Filesystem::mount($storage1, [], '/');
  333. $storage1->getWatcher()->setPolicy(Watcher::CHECK_ALWAYS);
  334. $rootView = new View('');
  335. $cachedData = $rootView->getFileInfo('foo.txt');
  336. $this->assertEquals(16, $cachedData['size']);
  337. $rootView->putFileInfo('foo.txt', ['storage_mtime' => 10]);
  338. $storage1->file_put_contents('foo.txt', 'foo');
  339. clearstatcache();
  340. $cachedData = $rootView->getFileInfo('foo.txt');
  341. $this->assertEquals(3, $cachedData['size']);
  342. }
  343. /**
  344. * @medium
  345. */
  346. public function testCopyBetweenStorageNoCross() {
  347. $storage1 = $this->getTestStorage(true, TemporaryNoCross::class);
  348. $storage2 = $this->getTestStorage(true, TemporaryNoCross::class);
  349. $this->copyBetweenStorages($storage1, $storage2);
  350. }
  351. /**
  352. * @medium
  353. */
  354. public function testCopyBetweenStorageCross() {
  355. $storage1 = $this->getTestStorage();
  356. $storage2 = $this->getTestStorage();
  357. $this->copyBetweenStorages($storage1, $storage2);
  358. }
  359. /**
  360. * @medium
  361. */
  362. public function testCopyBetweenStorageCrossNonLocal() {
  363. $storage1 = $this->getTestStorage(true, TemporaryNoLocal::class);
  364. $storage2 = $this->getTestStorage(true, TemporaryNoLocal::class);
  365. $this->copyBetweenStorages($storage1, $storage2);
  366. }
  367. public function copyBetweenStorages($storage1, $storage2) {
  368. Filesystem::mount($storage1, [], '/');
  369. Filesystem::mount($storage2, [], '/substorage');
  370. $rootView = new View('');
  371. $rootView->mkdir('substorage/emptyfolder');
  372. $rootView->copy('substorage', 'anotherfolder');
  373. $this->assertTrue($rootView->is_dir('/anotherfolder'));
  374. $this->assertTrue($rootView->is_dir('/substorage'));
  375. $this->assertTrue($rootView->is_dir('/anotherfolder/emptyfolder'));
  376. $this->assertTrue($rootView->is_dir('/substorage/emptyfolder'));
  377. $this->assertTrue($rootView->file_exists('/anotherfolder/foo.txt'));
  378. $this->assertTrue($rootView->file_exists('/anotherfolder/foo.png'));
  379. $this->assertTrue($rootView->file_exists('/anotherfolder/folder/bar.txt'));
  380. $this->assertTrue($rootView->file_exists('/substorage/foo.txt'));
  381. $this->assertTrue($rootView->file_exists('/substorage/foo.png'));
  382. $this->assertTrue($rootView->file_exists('/substorage/folder/bar.txt'));
  383. }
  384. /**
  385. * @medium
  386. */
  387. public function testMoveBetweenStorageNoCross() {
  388. $storage1 = $this->getTestStorage(true, TemporaryNoCross::class);
  389. $storage2 = $this->getTestStorage(true, TemporaryNoCross::class);
  390. $this->moveBetweenStorages($storage1, $storage2);
  391. }
  392. /**
  393. * @medium
  394. */
  395. public function testMoveBetweenStorageCross() {
  396. $storage1 = $this->getTestStorage();
  397. $storage2 = $this->getTestStorage();
  398. $this->moveBetweenStorages($storage1, $storage2);
  399. }
  400. /**
  401. * @medium
  402. */
  403. public function testMoveBetweenStorageCrossNonLocal() {
  404. $storage1 = $this->getTestStorage(true, TemporaryNoLocal::class);
  405. $storage2 = $this->getTestStorage(true, TemporaryNoLocal::class);
  406. $this->moveBetweenStorages($storage1, $storage2);
  407. }
  408. public function moveBetweenStorages($storage1, $storage2) {
  409. Filesystem::mount($storage1, [], '/');
  410. Filesystem::mount($storage2, [], '/substorage');
  411. $rootView = new View('');
  412. $rootView->rename('foo.txt', 'substorage/folder/foo.txt');
  413. $this->assertFalse($rootView->file_exists('foo.txt'));
  414. $this->assertTrue($rootView->file_exists('substorage/folder/foo.txt'));
  415. $rootView->rename('substorage/folder', 'anotherfolder');
  416. $this->assertFalse($rootView->is_dir('substorage/folder'));
  417. $this->assertTrue($rootView->file_exists('anotherfolder/foo.txt'));
  418. $this->assertTrue($rootView->file_exists('anotherfolder/bar.txt'));
  419. }
  420. /**
  421. * @medium
  422. */
  423. public function testUnlink() {
  424. $storage1 = $this->getTestStorage();
  425. $storage2 = $this->getTestStorage();
  426. Filesystem::mount($storage1, [], '/');
  427. Filesystem::mount($storage2, [], '/substorage');
  428. $rootView = new View('');
  429. $rootView->file_put_contents('/foo.txt', 'asd');
  430. $rootView->file_put_contents('/substorage/bar.txt', 'asd');
  431. $this->assertTrue($rootView->file_exists('foo.txt'));
  432. $this->assertTrue($rootView->file_exists('substorage/bar.txt'));
  433. $this->assertTrue($rootView->unlink('foo.txt'));
  434. $this->assertTrue($rootView->unlink('substorage/bar.txt'));
  435. $this->assertFalse($rootView->file_exists('foo.txt'));
  436. $this->assertFalse($rootView->file_exists('substorage/bar.txt'));
  437. }
  438. public function rmdirOrUnlinkDataProvider() {
  439. return [['rmdir'], ['unlink']];
  440. }
  441. /**
  442. * @dataProvider rmdirOrUnlinkDataProvider
  443. */
  444. public function testRmdir($method) {
  445. $storage1 = $this->getTestStorage();
  446. Filesystem::mount($storage1, [], '/');
  447. $rootView = new View('');
  448. $rootView->mkdir('sub');
  449. $rootView->mkdir('sub/deep');
  450. $rootView->file_put_contents('/sub/deep/foo.txt', 'asd');
  451. $this->assertTrue($rootView->file_exists('sub/deep/foo.txt'));
  452. $this->assertTrue($rootView->$method('sub'));
  453. $this->assertFalse($rootView->file_exists('sub'));
  454. }
  455. /**
  456. * @medium
  457. */
  458. public function testUnlinkRootMustFail() {
  459. $storage1 = $this->getTestStorage();
  460. $storage2 = $this->getTestStorage();
  461. Filesystem::mount($storage1, [], '/');
  462. Filesystem::mount($storage2, [], '/substorage');
  463. $rootView = new View('');
  464. $rootView->file_put_contents('/foo.txt', 'asd');
  465. $rootView->file_put_contents('/substorage/bar.txt', 'asd');
  466. $this->assertFalse($rootView->unlink(''));
  467. $this->assertFalse($rootView->unlink('/'));
  468. $this->assertFalse($rootView->unlink('substorage'));
  469. $this->assertFalse($rootView->unlink('/substorage'));
  470. }
  471. /**
  472. * @medium
  473. */
  474. public function testTouch() {
  475. $storage = $this->getTestStorage(true, TemporaryNoTouch::class);
  476. Filesystem::mount($storage, [], '/');
  477. $rootView = new View('');
  478. $oldCachedData = $rootView->getFileInfo('foo.txt');
  479. $rootView->touch('foo.txt', 500);
  480. $cachedData = $rootView->getFileInfo('foo.txt');
  481. $this->assertEquals(500, $cachedData['mtime']);
  482. $this->assertEquals($oldCachedData['storage_mtime'], $cachedData['storage_mtime']);
  483. $rootView->putFileInfo('foo.txt', ['storage_mtime' => 1000]); //make sure the watcher detects the change
  484. $rootView->file_put_contents('foo.txt', 'asd');
  485. $cachedData = $rootView->getFileInfo('foo.txt');
  486. $this->assertGreaterThanOrEqual($oldCachedData['mtime'], $cachedData['mtime']);
  487. $this->assertEquals($cachedData['storage_mtime'], $cachedData['mtime']);
  488. }
  489. /**
  490. * @medium
  491. */
  492. public function testTouchFloat() {
  493. $storage = $this->getTestStorage(true, TemporaryNoTouch::class);
  494. Filesystem::mount($storage, [], '/');
  495. $rootView = new View('');
  496. $oldCachedData = $rootView->getFileInfo('foo.txt');
  497. $rootView->touch('foo.txt', 500.5);
  498. $cachedData = $rootView->getFileInfo('foo.txt');
  499. $this->assertEquals(500, $cachedData['mtime']);
  500. }
  501. /**
  502. * @medium
  503. */
  504. public function testViewHooks() {
  505. $storage1 = $this->getTestStorage();
  506. $storage2 = $this->getTestStorage();
  507. $defaultRoot = Filesystem::getRoot();
  508. Filesystem::mount($storage1, [], '/');
  509. Filesystem::mount($storage2, [], $defaultRoot . '/substorage');
  510. \OC_Hook::connect('OC_Filesystem', 'post_write', $this, 'dummyHook');
  511. $rootView = new View('');
  512. $subView = new View($defaultRoot . '/substorage');
  513. $this->hookPath = null;
  514. $rootView->file_put_contents('/foo.txt', 'asd');
  515. $this->assertNull($this->hookPath);
  516. $subView->file_put_contents('/foo.txt', 'asd');
  517. $this->assertEquals('/substorage/foo.txt', $this->hookPath);
  518. }
  519. private $hookPath;
  520. public function dummyHook($params) {
  521. $this->hookPath = $params['path'];
  522. }
  523. public function testSearchNotOutsideView() {
  524. $storage1 = $this->getTestStorage();
  525. Filesystem::mount($storage1, [], '/');
  526. $storage1->rename('folder', 'foo');
  527. $scanner = $storage1->getScanner();
  528. $scanner->scan('');
  529. $view = new View('/foo');
  530. $result = $view->search('.txt');
  531. $this->assertCount(1, $result);
  532. }
  533. /**
  534. * @param bool $scan
  535. * @param string $class
  536. * @return \OC\Files\Storage\Storage
  537. */
  538. private function getTestStorage($scan = true, $class = Temporary::class) {
  539. /**
  540. * @var \OC\Files\Storage\Storage $storage
  541. */
  542. $storage = new $class([]);
  543. $textData = "dummy file data\n";
  544. $imgData = file_get_contents(\OC::$SERVERROOT . '/core/img/logo/logo.png');
  545. $storage->mkdir('folder');
  546. $storage->file_put_contents('foo.txt', $textData);
  547. $storage->file_put_contents('foo.png', $imgData);
  548. $storage->file_put_contents('folder/bar.txt', $textData);
  549. if ($scan) {
  550. $scanner = $storage->getScanner();
  551. $scanner->scan('');
  552. }
  553. $this->storages[] = $storage;
  554. return $storage;
  555. }
  556. /**
  557. * @medium
  558. */
  559. public function testViewHooksIfRootStartsTheSame() {
  560. $storage1 = $this->getTestStorage();
  561. $storage2 = $this->getTestStorage();
  562. $defaultRoot = Filesystem::getRoot();
  563. Filesystem::mount($storage1, [], '/');
  564. Filesystem::mount($storage2, [], $defaultRoot . '_substorage');
  565. \OC_Hook::connect('OC_Filesystem', 'post_write', $this, 'dummyHook');
  566. $subView = new View($defaultRoot . '_substorage');
  567. $this->hookPath = null;
  568. $subView->file_put_contents('/foo.txt', 'asd');
  569. $this->assertNull($this->hookPath);
  570. }
  571. private $hookWritePath;
  572. private $hookCreatePath;
  573. private $hookUpdatePath;
  574. public function dummyHookWrite($params) {
  575. $this->hookWritePath = $params['path'];
  576. }
  577. public function dummyHookUpdate($params) {
  578. $this->hookUpdatePath = $params['path'];
  579. }
  580. public function dummyHookCreate($params) {
  581. $this->hookCreatePath = $params['path'];
  582. }
  583. public function testEditNoCreateHook() {
  584. $storage1 = $this->getTestStorage();
  585. $storage2 = $this->getTestStorage();
  586. $defaultRoot = Filesystem::getRoot();
  587. Filesystem::mount($storage1, [], '/');
  588. Filesystem::mount($storage2, [], $defaultRoot);
  589. \OC_Hook::connect('OC_Filesystem', 'post_create', $this, 'dummyHookCreate');
  590. \OC_Hook::connect('OC_Filesystem', 'post_update', $this, 'dummyHookUpdate');
  591. \OC_Hook::connect('OC_Filesystem', 'post_write', $this, 'dummyHookWrite');
  592. $view = new View($defaultRoot);
  593. $this->hookWritePath = $this->hookUpdatePath = $this->hookCreatePath = null;
  594. $view->file_put_contents('/asd.txt', 'foo');
  595. $this->assertEquals('/asd.txt', $this->hookCreatePath);
  596. $this->assertNull($this->hookUpdatePath);
  597. $this->assertEquals('/asd.txt', $this->hookWritePath);
  598. $this->hookWritePath = $this->hookUpdatePath = $this->hookCreatePath = null;
  599. $view->file_put_contents('/asd.txt', 'foo');
  600. $this->assertNull($this->hookCreatePath);
  601. $this->assertEquals('/asd.txt', $this->hookUpdatePath);
  602. $this->assertEquals('/asd.txt', $this->hookWritePath);
  603. \OC_Hook::clear('OC_Filesystem', 'post_create');
  604. \OC_Hook::clear('OC_Filesystem', 'post_update');
  605. \OC_Hook::clear('OC_Filesystem', 'post_write');
  606. }
  607. /**
  608. * @dataProvider resolvePathTestProvider
  609. */
  610. public function testResolvePath($expected, $pathToTest) {
  611. $storage1 = $this->getTestStorage();
  612. Filesystem::mount($storage1, [], '/');
  613. $view = new View('');
  614. $result = $view->resolvePath($pathToTest);
  615. $this->assertEquals($expected, $result[1]);
  616. $exists = $view->file_exists($pathToTest);
  617. $this->assertTrue($exists);
  618. $exists = $view->file_exists($result[1]);
  619. $this->assertTrue($exists);
  620. }
  621. public function resolvePathTestProvider() {
  622. return [
  623. ['foo.txt', 'foo.txt'],
  624. ['foo.txt', '/foo.txt'],
  625. ['folder', 'folder'],
  626. ['folder', '/folder'],
  627. ['folder', 'folder/'],
  628. ['folder', '/folder/'],
  629. ['folder/bar.txt', 'folder/bar.txt'],
  630. ['folder/bar.txt', '/folder/bar.txt'],
  631. ['', ''],
  632. ['', '/'],
  633. ];
  634. }
  635. public function testUTF8Names() {
  636. $names = ['虚', '和知しゃ和で', 'regular ascii', 'sɨˈrɪlɪk', 'ѨѬ', 'أنا أحب القراءة كثيرا'];
  637. $storage = new Temporary([]);
  638. Filesystem::mount($storage, [], '/');
  639. $rootView = new View('');
  640. foreach ($names as $name) {
  641. $rootView->file_put_contents('/' . $name, 'dummy content');
  642. }
  643. $list = $rootView->getDirectoryContent('/');
  644. $this->assertCount(count($names), $list);
  645. foreach ($list as $item) {
  646. $this->assertContains($item['name'], $names);
  647. }
  648. $cache = $storage->getCache();
  649. $scanner = $storage->getScanner();
  650. $scanner->scan('');
  651. $list = $cache->getFolderContents('');
  652. $this->assertCount(count($names), $list);
  653. foreach ($list as $item) {
  654. $this->assertContains($item['name'], $names);
  655. }
  656. }
  657. public function xtestLongPath() {
  658. $storage = new Temporary([]);
  659. Filesystem::mount($storage, [], '/');
  660. $rootView = new View('');
  661. $longPath = '';
  662. $ds = DIRECTORY_SEPARATOR;
  663. /*
  664. * 4096 is the maximum path length in file_cache.path in *nix
  665. * 1024 is the max path length in mac
  666. */
  667. $folderName = 'abcdefghijklmnopqrstuvwxyz012345678901234567890123456789';
  668. $tmpdirLength = strlen(\OC::$server->getTempManager()->getTemporaryFolder());
  669. if (\OC_Util::runningOnMac()) {
  670. $depth = ((1024 - $tmpdirLength) / 57);
  671. } else {
  672. $depth = ((4000 - $tmpdirLength) / 57);
  673. }
  674. foreach (range(0, $depth - 1) as $i) {
  675. $longPath .= $ds . $folderName;
  676. $result = $rootView->mkdir($longPath);
  677. $this->assertTrue($result, "mkdir failed on $i - path length: " . strlen($longPath));
  678. $result = $rootView->file_put_contents($longPath . "{$ds}test.txt", 'lorem');
  679. $this->assertEquals(5, $result, "file_put_contents failed on $i");
  680. $this->assertTrue($rootView->file_exists($longPath));
  681. $this->assertTrue($rootView->file_exists($longPath . "{$ds}test.txt"));
  682. }
  683. $cache = $storage->getCache();
  684. $scanner = $storage->getScanner();
  685. $scanner->scan('');
  686. $longPath = $folderName;
  687. foreach (range(0, $depth - 1) as $i) {
  688. $cachedFolder = $cache->get($longPath);
  689. $this->assertTrue(is_array($cachedFolder), "No cache entry for folder at $i");
  690. $this->assertEquals($folderName, $cachedFolder['name'], "Wrong cache entry for folder at $i");
  691. $cachedFile = $cache->get($longPath . '/test.txt');
  692. $this->assertTrue(is_array($cachedFile), "No cache entry for file at $i");
  693. $this->assertEquals('test.txt', $cachedFile['name'], "Wrong cache entry for file at $i");
  694. $longPath .= $ds . $folderName;
  695. }
  696. }
  697. public function testTouchNotSupported() {
  698. $storage = new TemporaryNoTouch([]);
  699. $scanner = $storage->getScanner();
  700. Filesystem::mount($storage, [], '/test/');
  701. $past = time() - 100;
  702. $storage->file_put_contents('test', 'foobar');
  703. $scanner->scan('');
  704. $view = new View('');
  705. $info = $view->getFileInfo('/test/test');
  706. $view->touch('/test/test', $past);
  707. $scanner->scanFile('test', \OC\Files\Cache\Scanner::REUSE_ETAG);
  708. $info2 = $view->getFileInfo('/test/test');
  709. $this->assertSame($info['etag'], $info2['etag']);
  710. }
  711. public function testWatcherEtagCrossStorage() {
  712. $storage1 = new Temporary([]);
  713. $storage2 = new Temporary([]);
  714. $scanner1 = $storage1->getScanner();
  715. $scanner2 = $storage2->getScanner();
  716. $storage1->mkdir('sub');
  717. Filesystem::mount($storage1, [], '/test/');
  718. Filesystem::mount($storage2, [], '/test/sub/storage');
  719. $past = time() - 100;
  720. $storage2->file_put_contents('test.txt', 'foobar');
  721. $scanner1->scan('');
  722. $scanner2->scan('');
  723. $view = new View('');
  724. $storage2->getWatcher('')->setPolicy(Watcher::CHECK_ALWAYS);
  725. $oldFileInfo = $view->getFileInfo('/test/sub/storage/test.txt');
  726. $oldFolderInfo = $view->getFileInfo('/test');
  727. $storage2->getCache()->update($oldFileInfo->getId(), [
  728. 'storage_mtime' => $past,
  729. ]);
  730. $oldEtag = $oldFolderInfo->getEtag();
  731. $view->getFileInfo('/test/sub/storage/test.txt');
  732. $newFolderInfo = $view->getFileInfo('/test');
  733. $this->assertNotEquals($newFolderInfo->getEtag(), $oldEtag);
  734. }
  735. /**
  736. * @dataProvider absolutePathProvider
  737. */
  738. public function testGetAbsolutePath($expectedPath, $relativePath) {
  739. $view = new View('/files');
  740. $this->assertEquals($expectedPath, $view->getAbsolutePath($relativePath));
  741. }
  742. public function testPartFileInfo() {
  743. $storage = new Temporary([]);
  744. $scanner = $storage->getScanner();
  745. Filesystem::mount($storage, [], '/test/');
  746. $storage->file_put_contents('test.part', 'foobar');
  747. $scanner->scan('');
  748. $view = new View('/test');
  749. $info = $view->getFileInfo('test.part');
  750. $this->assertInstanceOf('\OCP\Files\FileInfo', $info);
  751. $this->assertNull($info->getId());
  752. $this->assertEquals(6, $info->getSize());
  753. }
  754. public function absolutePathProvider() {
  755. return [
  756. ['/files/', ''],
  757. ['/files/0', '0'],
  758. ['/files/false', 'false'],
  759. ['/files/true', 'true'],
  760. ['/files/', '/'],
  761. ['/files/test', 'test'],
  762. ['/files/test', '/test'],
  763. ];
  764. }
  765. /**
  766. * @dataProvider chrootRelativePathProvider
  767. */
  768. public function testChrootGetRelativePath($root, $absolutePath, $expectedPath) {
  769. $view = new View('/files');
  770. $view->chroot($root);
  771. $this->assertEquals($expectedPath, $view->getRelativePath($absolutePath));
  772. }
  773. public function chrootRelativePathProvider() {
  774. return $this->relativePathProvider('/');
  775. }
  776. /**
  777. * @dataProvider initRelativePathProvider
  778. */
  779. public function testInitGetRelativePath($root, $absolutePath, $expectedPath) {
  780. $view = new View($root);
  781. $this->assertEquals($expectedPath, $view->getRelativePath($absolutePath));
  782. }
  783. public function initRelativePathProvider() {
  784. return $this->relativePathProvider(null);
  785. }
  786. public function relativePathProvider($missingRootExpectedPath) {
  787. return [
  788. // No root - returns the path
  789. ['', '/files', '/files'],
  790. ['', '/files/', '/files/'],
  791. // Root equals path - /
  792. ['/files/', '/files/', '/'],
  793. ['/files/', '/files', '/'],
  794. ['/files', '/files/', '/'],
  795. ['/files', '/files', '/'],
  796. // False negatives: chroot fixes those by adding the leading slash.
  797. // But setting them up with this root (instead of chroot($root))
  798. // will fail them, although they should be the same.
  799. // TODO init should be fixed, so it also adds the leading slash
  800. ['files/', '/files/', $missingRootExpectedPath],
  801. ['files', '/files/', $missingRootExpectedPath],
  802. ['files/', '/files', $missingRootExpectedPath],
  803. ['files', '/files', $missingRootExpectedPath],
  804. // False negatives: Paths provided to the method should have a leading slash
  805. // TODO input should be checked to have a leading slash
  806. ['/files/', 'files/', null],
  807. ['/files', 'files/', null],
  808. ['/files/', 'files', null],
  809. ['/files', 'files', null],
  810. // with trailing slashes
  811. ['/files/', '/files/0', '0'],
  812. ['/files/', '/files/false', 'false'],
  813. ['/files/', '/files/true', 'true'],
  814. ['/files/', '/files/test', 'test'],
  815. ['/files/', '/files/test/foo', 'test/foo'],
  816. // without trailing slashes
  817. // TODO false expectation: Should match "with trailing slashes"
  818. ['/files', '/files/0', '/0'],
  819. ['/files', '/files/false', '/false'],
  820. ['/files', '/files/true', '/true'],
  821. ['/files', '/files/test', '/test'],
  822. ['/files', '/files/test/foo', '/test/foo'],
  823. // leading slashes
  824. ['/files/', '/files_trashbin/', null],
  825. ['/files', '/files_trashbin/', null],
  826. ['/files/', '/files_trashbin', null],
  827. ['/files', '/files_trashbin', null],
  828. // no leading slashes
  829. ['files/', 'files_trashbin/', null],
  830. ['files', 'files_trashbin/', null],
  831. ['files/', 'files_trashbin', null],
  832. ['files', 'files_trashbin', null],
  833. // mixed leading slashes
  834. ['files/', '/files_trashbin/', null],
  835. ['/files/', 'files_trashbin/', null],
  836. ['files', '/files_trashbin/', null],
  837. ['/files', 'files_trashbin/', null],
  838. ['files/', '/files_trashbin', null],
  839. ['/files/', 'files_trashbin', null],
  840. ['files', '/files_trashbin', null],
  841. ['/files', 'files_trashbin', null],
  842. ['files', 'files_trashbin/test', null],
  843. ['/files', '/files_trashbin/test', null],
  844. ['/files', 'files_trashbin/test', null],
  845. ];
  846. }
  847. public function testFileView() {
  848. $storage = new Temporary([]);
  849. $scanner = $storage->getScanner();
  850. $storage->file_put_contents('foo.txt', 'bar');
  851. Filesystem::mount($storage, [], '/test/');
  852. $scanner->scan('');
  853. $view = new View('/test/foo.txt');
  854. $this->assertEquals('bar', $view->file_get_contents(''));
  855. $fh = tmpfile();
  856. fwrite($fh, 'foo');
  857. rewind($fh);
  858. $view->file_put_contents('', $fh);
  859. $this->assertEquals('foo', $view->file_get_contents(''));
  860. }
  861. /**
  862. * @dataProvider tooLongPathDataProvider
  863. */
  864. public function testTooLongPath($operation, $param0 = null) {
  865. $this->expectException(\OCP\Files\InvalidPathException::class);
  866. $longPath = '';
  867. // 4000 is the maximum path length in file_cache.path
  868. $folderName = 'abcdefghijklmnopqrstuvwxyz012345678901234567890123456789';
  869. $depth = (4000 / 57);
  870. foreach (range(0, $depth + 1) as $i) {
  871. $longPath .= '/' . $folderName;
  872. }
  873. $storage = new Temporary([]);
  874. $this->tempStorage = $storage; // for later hard cleanup
  875. Filesystem::mount($storage, [], '/');
  876. $rootView = new View('');
  877. if ($param0 === '@0') {
  878. $param0 = $longPath;
  879. }
  880. if ($operation === 'hash') {
  881. $param0 = $longPath;
  882. $longPath = 'md5';
  883. }
  884. call_user_func([$rootView, $operation], $longPath, $param0);
  885. }
  886. public function tooLongPathDataProvider() {
  887. return [
  888. ['getAbsolutePath'],
  889. ['getRelativePath'],
  890. ['getMountPoint'],
  891. ['resolvePath'],
  892. ['getLocalFile'],
  893. ['getLocalFolder'],
  894. ['mkdir'],
  895. ['rmdir'],
  896. ['opendir'],
  897. ['is_dir'],
  898. ['is_file'],
  899. ['stat'],
  900. ['filetype'],
  901. ['filesize'],
  902. ['readfile'],
  903. ['isCreatable'],
  904. ['isReadable'],
  905. ['isUpdatable'],
  906. ['isDeletable'],
  907. ['isSharable'],
  908. ['file_exists'],
  909. ['filemtime'],
  910. ['touch'],
  911. ['file_get_contents'],
  912. ['unlink'],
  913. ['deleteAll'],
  914. ['toTmpFile'],
  915. ['getMimeType'],
  916. ['free_space'],
  917. ['getFileInfo'],
  918. ['getDirectoryContent'],
  919. ['getOwner'],
  920. ['getETag'],
  921. ['file_put_contents', 'ipsum'],
  922. ['rename', '@0'],
  923. ['copy', '@0'],
  924. ['fopen', 'r'],
  925. ['fromTmpFile', '@0'],
  926. ['hash'],
  927. ['hasUpdated', 0],
  928. ['putFileInfo', []],
  929. ];
  930. }
  931. public function testRenameCrossStoragePreserveMtime() {
  932. $storage1 = new Temporary([]);
  933. $storage2 = new Temporary([]);
  934. $storage1->mkdir('sub');
  935. $storage1->mkdir('foo');
  936. $storage1->file_put_contents('foo.txt', 'asd');
  937. $storage1->file_put_contents('foo/bar.txt', 'asd');
  938. Filesystem::mount($storage1, [], '/test/');
  939. Filesystem::mount($storage2, [], '/test/sub/storage');
  940. $view = new View('');
  941. $time = time() - 200;
  942. $view->touch('/test/foo.txt', $time);
  943. $view->touch('/test/foo', $time);
  944. $view->touch('/test/foo/bar.txt', $time);
  945. $view->rename('/test/foo.txt', '/test/sub/storage/foo.txt');
  946. $this->assertEquals($time, $view->filemtime('/test/sub/storage/foo.txt'));
  947. $view->rename('/test/foo', '/test/sub/storage/foo');
  948. $this->assertEquals($time, $view->filemtime('/test/sub/storage/foo/bar.txt'));
  949. }
  950. public function testRenameFailDeleteTargetKeepSource() {
  951. $this->doTestCopyRenameFail('rename');
  952. }
  953. public function testCopyFailDeleteTargetKeepSource() {
  954. $this->doTestCopyRenameFail('copy');
  955. }
  956. private function doTestCopyRenameFail($operation) {
  957. $storage1 = new Temporary([]);
  958. /** @var \PHPUnit\Framework\MockObject\MockObject|Temporary $storage2 */
  959. $storage2 = $this->getMockBuilder(TemporaryNoCross::class)
  960. ->setConstructorArgs([[]])
  961. ->setMethods(['fopen', 'writeStream'])
  962. ->getMock();
  963. $storage2->method('writeStream')
  964. ->willThrowException(new GenericFileException("Failed to copy stream"));
  965. $storage1->mkdir('sub');
  966. $storage1->file_put_contents('foo.txt', '0123456789ABCDEFGH');
  967. $storage1->mkdir('dirtomove');
  968. $storage1->file_put_contents('dirtomove/indir1.txt', '0123456'); // fits
  969. $storage1->file_put_contents('dirtomove/indir2.txt', '0123456789ABCDEFGH'); // doesn't fit
  970. $storage2->file_put_contents('existing.txt', '0123');
  971. $storage1->getScanner()->scan('');
  972. $storage2->getScanner()->scan('');
  973. Filesystem::mount($storage1, [], '/test/');
  974. Filesystem::mount($storage2, [], '/test/sub/storage');
  975. // move file
  976. $view = new View('');
  977. $this->assertTrue($storage1->file_exists('foo.txt'));
  978. $this->assertFalse($storage2->file_exists('foo.txt'));
  979. $this->assertFalse($view->$operation('/test/foo.txt', '/test/sub/storage/foo.txt'));
  980. $this->assertFalse($storage2->file_exists('foo.txt'));
  981. $this->assertFalse($storage2->getCache()->get('foo.txt'));
  982. $this->assertTrue($storage1->file_exists('foo.txt'));
  983. // if target exists, it will be deleted too
  984. $this->assertFalse($view->$operation('/test/foo.txt', '/test/sub/storage/existing.txt'));
  985. $this->assertFalse($storage2->file_exists('existing.txt'));
  986. $this->assertFalse($storage2->getCache()->get('existing.txt'));
  987. $this->assertTrue($storage1->file_exists('foo.txt'));
  988. // move folder
  989. $this->assertFalse($view->$operation('/test/dirtomove/', '/test/sub/storage/dirtomove/'));
  990. // since the move failed, the full source tree is kept
  991. $this->assertTrue($storage1->file_exists('dirtomove/indir1.txt'));
  992. $this->assertTrue($storage1->file_exists('dirtomove/indir2.txt'));
  993. // second file not moved/copied
  994. $this->assertFalse($storage2->file_exists('dirtomove/indir2.txt'));
  995. $this->assertFalse($storage2->getCache()->get('dirtomove/indir2.txt'));
  996. }
  997. public function testDeleteFailKeepCache() {
  998. /** @var Temporary|\PHPUnit\Framework\MockObject\MockObject $storage */
  999. $storage = $this->getMockBuilder(Temporary::class)
  1000. ->setConstructorArgs([[]])
  1001. ->setMethods(['unlink'])
  1002. ->getMock();
  1003. $storage->expects($this->once())
  1004. ->method('unlink')
  1005. ->willReturn(false);
  1006. $scanner = $storage->getScanner();
  1007. $cache = $storage->getCache();
  1008. $storage->file_put_contents('foo.txt', 'asd');
  1009. $scanner->scan('');
  1010. Filesystem::mount($storage, [], '/test/');
  1011. $view = new View('/test');
  1012. $this->assertFalse($view->unlink('foo.txt'));
  1013. $this->assertTrue($cache->inCache('foo.txt'));
  1014. }
  1015. public function directoryTraversalProvider() {
  1016. return [
  1017. ['../test/'],
  1018. ['..\\test\\my/../folder'],
  1019. ['/test/my/../foo\\'],
  1020. ];
  1021. }
  1022. /**
  1023. * @dataProvider directoryTraversalProvider
  1024. * @param string $root
  1025. */
  1026. public function testConstructDirectoryTraversalException($root) {
  1027. $this->expectException(\Exception::class);
  1028. new View($root);
  1029. }
  1030. public function testRenameOverWrite() {
  1031. $storage = new Temporary([]);
  1032. $scanner = $storage->getScanner();
  1033. $storage->mkdir('sub');
  1034. $storage->mkdir('foo');
  1035. $storage->file_put_contents('foo.txt', 'asd');
  1036. $storage->file_put_contents('foo/bar.txt', 'asd');
  1037. $scanner->scan('');
  1038. Filesystem::mount($storage, [], '/test/');
  1039. $view = new View('');
  1040. $this->assertTrue($view->rename('/test/foo.txt', '/test/foo/bar.txt'));
  1041. }
  1042. public function testSetMountOptionsInStorage() {
  1043. $mount = new MountPoint(Temporary::class, '/asd/', [[]], Filesystem::getLoader(), ['foo' => 'bar']);
  1044. Filesystem::getMountManager()->addMount($mount);
  1045. /** @var \OC\Files\Storage\Common $storage */
  1046. $storage = $mount->getStorage();
  1047. $this->assertEquals($storage->getMountOption('foo'), 'bar');
  1048. }
  1049. public function testSetMountOptionsWatcherPolicy() {
  1050. $mount = new MountPoint(Temporary::class, '/asd/', [[]], Filesystem::getLoader(), ['filesystem_check_changes' => Watcher::CHECK_NEVER]);
  1051. Filesystem::getMountManager()->addMount($mount);
  1052. /** @var \OC\Files\Storage\Common $storage */
  1053. $storage = $mount->getStorage();
  1054. $watcher = $storage->getWatcher();
  1055. $this->assertEquals(Watcher::CHECK_NEVER, $watcher->getPolicy());
  1056. }
  1057. public function testGetAbsolutePathOnNull() {
  1058. $view = new View();
  1059. $this->assertNull($view->getAbsolutePath(null));
  1060. }
  1061. public function testGetRelativePathOnNull() {
  1062. $view = new View();
  1063. $this->assertNull($view->getRelativePath(null));
  1064. }
  1065. public function testNullAsRoot() {
  1066. $this->expectException(\InvalidArgumentException::class);
  1067. new View(null);
  1068. }
  1069. /**
  1070. * e.g. reading from a folder that's being renamed
  1071. *
  1072. *
  1073. * @dataProvider dataLockPaths
  1074. *
  1075. * @param string $rootPath
  1076. * @param string $pathPrefix
  1077. */
  1078. public function testReadFromWriteLockedPath($rootPath, $pathPrefix) {
  1079. $this->expectException(\OCP\Lock\LockedException::class);
  1080. $rootPath = str_replace('{folder}', 'files', $rootPath);
  1081. $pathPrefix = str_replace('{folder}', 'files', $pathPrefix);
  1082. $view = new View($rootPath);
  1083. $storage = new Temporary([]);
  1084. Filesystem::mount($storage, [], '/');
  1085. $this->assertTrue($view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_EXCLUSIVE));
  1086. $view->lockFile($pathPrefix . '/foo/bar/asd', ILockingProvider::LOCK_SHARED);
  1087. }
  1088. /**
  1089. * Reading from a files_encryption folder that's being renamed
  1090. *
  1091. * @dataProvider dataLockPaths
  1092. *
  1093. * @param string $rootPath
  1094. * @param string $pathPrefix
  1095. */
  1096. public function testReadFromWriteUnlockablePath($rootPath, $pathPrefix) {
  1097. $rootPath = str_replace('{folder}', 'files_encryption', $rootPath);
  1098. $pathPrefix = str_replace('{folder}', 'files_encryption', $pathPrefix);
  1099. $view = new View($rootPath);
  1100. $storage = new Temporary([]);
  1101. Filesystem::mount($storage, [], '/');
  1102. $this->assertFalse($view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_EXCLUSIVE));
  1103. $this->assertFalse($view->lockFile($pathPrefix . '/foo/bar/asd', ILockingProvider::LOCK_SHARED));
  1104. }
  1105. /**
  1106. * e.g. writing a file that's being downloaded
  1107. *
  1108. *
  1109. * @dataProvider dataLockPaths
  1110. *
  1111. * @param string $rootPath
  1112. * @param string $pathPrefix
  1113. */
  1114. public function testWriteToReadLockedFile($rootPath, $pathPrefix) {
  1115. $this->expectException(\OCP\Lock\LockedException::class);
  1116. $rootPath = str_replace('{folder}', 'files', $rootPath);
  1117. $pathPrefix = str_replace('{folder}', 'files', $pathPrefix);
  1118. $view = new View($rootPath);
  1119. $storage = new Temporary([]);
  1120. Filesystem::mount($storage, [], '/');
  1121. $this->assertTrue($view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_SHARED));
  1122. $view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_EXCLUSIVE);
  1123. }
  1124. /**
  1125. * Writing a file that's being downloaded
  1126. *
  1127. * @dataProvider dataLockPaths
  1128. *
  1129. * @param string $rootPath
  1130. * @param string $pathPrefix
  1131. */
  1132. public function testWriteToReadUnlockableFile($rootPath, $pathPrefix) {
  1133. $rootPath = str_replace('{folder}', 'files_encryption', $rootPath);
  1134. $pathPrefix = str_replace('{folder}', 'files_encryption', $pathPrefix);
  1135. $view = new View($rootPath);
  1136. $storage = new Temporary([]);
  1137. Filesystem::mount($storage, [], '/');
  1138. $this->assertFalse($view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_SHARED));
  1139. $this->assertFalse($view->lockFile($pathPrefix . '/foo/bar', ILockingProvider::LOCK_EXCLUSIVE));
  1140. }
  1141. /**
  1142. * Test that locks are on mount point paths instead of mount root
  1143. */
  1144. public function testLockLocalMountPointPathInsteadOfStorageRoot() {
  1145. $lockingProvider = \OC::$server->getLockingProvider();
  1146. $view = new View('/testuser/files/');
  1147. $storage = new Temporary([]);
  1148. Filesystem::mount($storage, [], '/');
  1149. $mountedStorage = new Temporary([]);
  1150. Filesystem::mount($mountedStorage, [], '/testuser/files/mountpoint');
  1151. $this->assertTrue(
  1152. $view->lockFile('/mountpoint', ILockingProvider::LOCK_EXCLUSIVE, true),
  1153. 'Can lock mount point'
  1154. );
  1155. // no exception here because storage root was not locked
  1156. $mountedStorage->acquireLock('', ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
  1157. $thrown = false;
  1158. try {
  1159. $storage->acquireLock('/testuser/files/mountpoint', ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
  1160. } catch (LockedException $e) {
  1161. $thrown = true;
  1162. }
  1163. $this->assertTrue($thrown, 'Mount point path was locked on root storage');
  1164. $lockingProvider->releaseAll();
  1165. }
  1166. /**
  1167. * Test that locks are on mount point paths and also mount root when requested
  1168. */
  1169. public function testLockStorageRootButNotLocalMountPoint() {
  1170. $lockingProvider = \OC::$server->getLockingProvider();
  1171. $view = new View('/testuser/files/');
  1172. $storage = new Temporary([]);
  1173. Filesystem::mount($storage, [], '/');
  1174. $mountedStorage = new Temporary([]);
  1175. Filesystem::mount($mountedStorage, [], '/testuser/files/mountpoint');
  1176. $this->assertTrue(
  1177. $view->lockFile('/mountpoint', ILockingProvider::LOCK_EXCLUSIVE, false),
  1178. 'Can lock mount point'
  1179. );
  1180. $thrown = false;
  1181. try {
  1182. $mountedStorage->acquireLock('', ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
  1183. } catch (LockedException $e) {
  1184. $thrown = true;
  1185. }
  1186. $this->assertTrue($thrown, 'Mount point storage root was locked on original storage');
  1187. // local mount point was not locked
  1188. $storage->acquireLock('/testuser/files/mountpoint', ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
  1189. $lockingProvider->releaseAll();
  1190. }
  1191. /**
  1192. * Test that locks are on mount point paths and also mount root when requested
  1193. */
  1194. public function testLockMountPointPathFailReleasesBoth() {
  1195. $lockingProvider = \OC::$server->getLockingProvider();
  1196. $view = new View('/testuser/files/');
  1197. $storage = new Temporary([]);
  1198. Filesystem::mount($storage, [], '/');
  1199. $mountedStorage = new Temporary([]);
  1200. Filesystem::mount($mountedStorage, [], '/testuser/files/mountpoint.txt');
  1201. // this would happen if someone is writing on the mount point
  1202. $mountedStorage->acquireLock('', ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
  1203. $thrown = false;
  1204. try {
  1205. // this actually acquires two locks, one on the mount point and one on the storage root,
  1206. // but the one on the storage root will fail
  1207. $view->lockFile('/mountpoint.txt', ILockingProvider::LOCK_SHARED);
  1208. } catch (LockedException $e) {
  1209. $thrown = true;
  1210. }
  1211. $this->assertTrue($thrown, 'Cannot acquire shared lock because storage root is already locked');
  1212. // from here we expect that the lock on the local mount point was released properly
  1213. // so acquiring an exclusive lock will succeed
  1214. $storage->acquireLock('/testuser/files/mountpoint.txt', ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider);
  1215. $lockingProvider->releaseAll();
  1216. }
  1217. public function dataLockPaths() {
  1218. return [
  1219. ['/testuser/{folder}', ''],
  1220. ['/testuser', '/{folder}'],
  1221. ['', '/testuser/{folder}'],
  1222. ];
  1223. }
  1224. public function pathRelativeToFilesProvider() {
  1225. return [
  1226. ['admin/files', ''],
  1227. ['admin/files/x', 'x'],
  1228. ['/admin/files', ''],
  1229. ['/admin/files/sub', 'sub'],
  1230. ['/admin/files/sub/', 'sub'],
  1231. ['/admin/files/sub/sub2', 'sub/sub2'],
  1232. ['//admin//files/sub//sub2', 'sub/sub2'],
  1233. ];
  1234. }
  1235. /**
  1236. * @dataProvider pathRelativeToFilesProvider
  1237. */
  1238. public function testGetPathRelativeToFiles($path, $expectedPath) {
  1239. $view = new View();
  1240. $this->assertEquals($expectedPath, $view->getPathRelativeToFiles($path));
  1241. }
  1242. public function pathRelativeToFilesProviderExceptionCases() {
  1243. return [
  1244. [''],
  1245. ['x'],
  1246. ['files'],
  1247. ['/files'],
  1248. ['/admin/files_versions/abc'],
  1249. ];
  1250. }
  1251. /**
  1252. * @dataProvider pathRelativeToFilesProviderExceptionCases
  1253. * @param string $path
  1254. */
  1255. public function testGetPathRelativeToFilesWithInvalidArgument($path) {
  1256. $this->expectException(\InvalidArgumentException::class);
  1257. $this->expectExceptionMessage('$absolutePath must be relative to "files"');
  1258. $view = new View();
  1259. $view->getPathRelativeToFiles($path);
  1260. }
  1261. public function testChangeLock() {
  1262. $view = new View('/testuser/files/');
  1263. $storage = new Temporary([]);
  1264. Filesystem::mount($storage, [], '/');
  1265. $view->lockFile('/test/sub', ILockingProvider::LOCK_SHARED);
  1266. $this->assertTrue($this->isFileLocked($view, '/test//sub', ILockingProvider::LOCK_SHARED));
  1267. $this->assertFalse($this->isFileLocked($view, '/test//sub', ILockingProvider::LOCK_EXCLUSIVE));
  1268. $view->changeLock('//test/sub', ILockingProvider::LOCK_EXCLUSIVE);
  1269. $this->assertTrue($this->isFileLocked($view, '/test//sub', ILockingProvider::LOCK_EXCLUSIVE));
  1270. $view->changeLock('test/sub', ILockingProvider::LOCK_SHARED);
  1271. $this->assertTrue($this->isFileLocked($view, '/test//sub', ILockingProvider::LOCK_SHARED));
  1272. $view->unlockFile('/test/sub/', ILockingProvider::LOCK_SHARED);
  1273. $this->assertFalse($this->isFileLocked($view, '/test//sub', ILockingProvider::LOCK_SHARED));
  1274. $this->assertFalse($this->isFileLocked($view, '/test//sub', ILockingProvider::LOCK_EXCLUSIVE));
  1275. }
  1276. public function hookPathProvider() {
  1277. return [
  1278. ['/foo/files', '/foo', true],
  1279. ['/foo/files/bar', '/foo', true],
  1280. ['/foo', '/foo', false],
  1281. ['/foo', '/files/foo', true],
  1282. ['/foo', 'filesfoo', false],
  1283. ['', '/foo/files', true],
  1284. ['', '/foo/files/bar.txt', true],
  1285. ];
  1286. }
  1287. /**
  1288. * @dataProvider hookPathProvider
  1289. * @param $root
  1290. * @param $path
  1291. * @param $shouldEmit
  1292. */
  1293. public function testHookPaths($root, $path, $shouldEmit) {
  1294. $filesystemReflection = new \ReflectionClass(Filesystem::class);
  1295. $defaultRootValue = $filesystemReflection->getProperty('defaultInstance');
  1296. $defaultRootValue->setAccessible(true);
  1297. $oldRoot = $defaultRootValue->getValue();
  1298. $defaultView = new View('/foo/files');
  1299. $defaultRootValue->setValue($defaultView);
  1300. $view = new View($root);
  1301. $result = self::invokePrivate($view, 'shouldEmitHooks', [$path]);
  1302. $defaultRootValue->setValue($oldRoot);
  1303. $this->assertEquals($shouldEmit, $result);
  1304. }
  1305. /**
  1306. * Create test movable mount points
  1307. *
  1308. * @param array $mountPoints array of mount point locations
  1309. * @return array array of MountPoint objects
  1310. */
  1311. private function createTestMovableMountPoints($mountPoints) {
  1312. $mounts = [];
  1313. foreach ($mountPoints as $mountPoint) {
  1314. $storage = $this->getMockBuilder(Temporary::class)
  1315. ->setMethods([])
  1316. ->getMock();
  1317. $mounts[] = $this->getMockBuilder(TestMoveableMountPoint::class)
  1318. ->setMethods(['moveMount'])
  1319. ->setConstructorArgs([$storage, $mountPoint])
  1320. ->getMock();
  1321. }
  1322. /** @var IMountProvider|\PHPUnit\Framework\MockObject\MockObject $mountProvider */
  1323. $mountProvider = $this->createMock(IMountProvider::class);
  1324. $mountProvider->expects($this->any())
  1325. ->method('getMountsForUser')
  1326. ->willReturn($mounts);
  1327. $mountProviderCollection = \OC::$server->getMountProviderCollection();
  1328. $mountProviderCollection->registerProvider($mountProvider);
  1329. return $mounts;
  1330. }
  1331. /**
  1332. * Test mount point move
  1333. */
  1334. public function testMountPointMove() {
  1335. self::loginAsUser($this->user);
  1336. [$mount1, $mount2] = $this->createTestMovableMountPoints([
  1337. $this->user . '/files/mount1',
  1338. $this->user . '/files/mount2',
  1339. ]);
  1340. $mount1->expects($this->once())
  1341. ->method('moveMount')
  1342. ->willReturn(true);
  1343. $mount2->expects($this->once())
  1344. ->method('moveMount')
  1345. ->willReturn(true);
  1346. $view = new View('/' . $this->user . '/files/');
  1347. $view->mkdir('sub');
  1348. $this->assertTrue($view->rename('mount1', 'renamed_mount'), 'Can rename mount point');
  1349. $this->assertTrue($view->rename('mount2', 'sub/moved_mount'), 'Can move a mount point into a subdirectory');
  1350. }
  1351. /**
  1352. * Test that moving a mount point into another is forbidden
  1353. */
  1354. public function testMoveMountPointIntoAnother() {
  1355. self::loginAsUser($this->user);
  1356. [$mount1, $mount2] = $this->createTestMovableMountPoints([
  1357. $this->user . '/files/mount1',
  1358. $this->user . '/files/mount2',
  1359. ]);
  1360. $mount1->expects($this->never())
  1361. ->method('moveMount');
  1362. $mount2->expects($this->never())
  1363. ->method('moveMount');
  1364. $view = new View('/' . $this->user . '/files/');
  1365. $this->assertFalse($view->rename('mount1', 'mount2'), 'Cannot overwrite another mount point');
  1366. $this->assertFalse($view->rename('mount1', 'mount2/sub'), 'Cannot move a mount point into another');
  1367. }
  1368. /**
  1369. * Test that moving a mount point into a shared folder is forbidden
  1370. */
  1371. public function testMoveMountPointIntoSharedFolder() {
  1372. self::loginAsUser($this->user);
  1373. [$mount1] = $this->createTestMovableMountPoints([
  1374. $this->user . '/files/mount1',
  1375. ]);
  1376. $mount1->expects($this->never())
  1377. ->method('moveMount');
  1378. $view = new View('/' . $this->user . '/files/');
  1379. $view->mkdir('shareddir');
  1380. $view->mkdir('shareddir/sub');
  1381. $view->mkdir('shareddir/sub2');
  1382. $fileId = $view->getFileInfo('shareddir')->getId();
  1383. $userObject = \OC::$server->getUserManager()->createUser('test2', 'IHateNonMockableStaticClasses');
  1384. $userFolder = \OC::$server->getUserFolder($this->user);
  1385. $shareDir = $userFolder->get('shareddir');
  1386. $shareManager = \OC::$server->getShareManager();
  1387. $share = $shareManager->newShare();
  1388. $share->setSharedWith('test2')
  1389. ->setSharedBy($this->user)
  1390. ->setShareType(IShare::TYPE_USER)
  1391. ->setPermissions(\OCP\Constants::PERMISSION_READ)
  1392. ->setId(42)
  1393. ->setProviderId('foo')
  1394. ->setNode($shareDir);
  1395. $shareManager->createShare($share);
  1396. $this->assertFalse($view->rename('mount1', 'shareddir'), 'Cannot overwrite shared folder');
  1397. $this->assertFalse($view->rename('mount1', 'shareddir/sub'), 'Cannot move mount point into shared folder');
  1398. $this->assertFalse($view->rename('mount1', 'shareddir/sub/sub2'), 'Cannot move mount point into shared subfolder');
  1399. $shareManager->deleteShare($share);
  1400. $userObject->delete();
  1401. }
  1402. public function basicOperationProviderForLocks() {
  1403. return [
  1404. // --- write hook ----
  1405. [
  1406. 'touch',
  1407. ['touch-create.txt'],
  1408. 'touch-create.txt',
  1409. 'create',
  1410. ILockingProvider::LOCK_SHARED,
  1411. ILockingProvider::LOCK_EXCLUSIVE,
  1412. ILockingProvider::LOCK_SHARED,
  1413. ],
  1414. [
  1415. 'fopen',
  1416. ['test-write.txt', 'w'],
  1417. 'test-write.txt',
  1418. 'write',
  1419. ILockingProvider::LOCK_SHARED,
  1420. ILockingProvider::LOCK_EXCLUSIVE,
  1421. null,
  1422. // exclusive lock stays until fclose
  1423. ILockingProvider::LOCK_EXCLUSIVE,
  1424. ],
  1425. [
  1426. 'mkdir',
  1427. ['newdir'],
  1428. 'newdir',
  1429. 'write',
  1430. ILockingProvider::LOCK_SHARED,
  1431. ILockingProvider::LOCK_EXCLUSIVE,
  1432. ILockingProvider::LOCK_SHARED,
  1433. ],
  1434. [
  1435. 'file_put_contents',
  1436. ['file_put_contents.txt', 'blah'],
  1437. 'file_put_contents.txt',
  1438. 'write',
  1439. ILockingProvider::LOCK_SHARED,
  1440. ILockingProvider::LOCK_EXCLUSIVE,
  1441. ILockingProvider::LOCK_SHARED,
  1442. ],
  1443. // ---- delete hook ----
  1444. [
  1445. 'rmdir',
  1446. ['dir'],
  1447. 'dir',
  1448. 'delete',
  1449. ILockingProvider::LOCK_SHARED,
  1450. ILockingProvider::LOCK_EXCLUSIVE,
  1451. ILockingProvider::LOCK_SHARED,
  1452. ],
  1453. [
  1454. 'unlink',
  1455. ['test.txt'],
  1456. 'test.txt',
  1457. 'delete',
  1458. ILockingProvider::LOCK_SHARED,
  1459. ILockingProvider::LOCK_EXCLUSIVE,
  1460. ILockingProvider::LOCK_SHARED,
  1461. ],
  1462. // ---- read hook (no post hooks) ----
  1463. [
  1464. 'file_get_contents',
  1465. ['test.txt'],
  1466. 'test.txt',
  1467. 'read',
  1468. ILockingProvider::LOCK_SHARED,
  1469. ILockingProvider::LOCK_SHARED,
  1470. null,
  1471. ],
  1472. [
  1473. 'fopen',
  1474. ['test.txt', 'r'],
  1475. 'test.txt',
  1476. 'read',
  1477. ILockingProvider::LOCK_SHARED,
  1478. ILockingProvider::LOCK_SHARED,
  1479. null,
  1480. ],
  1481. [
  1482. 'opendir',
  1483. ['dir'],
  1484. 'dir',
  1485. 'read',
  1486. ILockingProvider::LOCK_SHARED,
  1487. ILockingProvider::LOCK_SHARED,
  1488. null,
  1489. ],
  1490. // ---- no lock, touch hook ---
  1491. ['touch', ['test.txt'], 'test.txt', 'touch', null, null, null],
  1492. // ---- no hooks, no locks ---
  1493. ['is_dir', ['dir'], 'dir', null],
  1494. ['is_file', ['dir'], 'dir', null],
  1495. ['stat', ['dir'], 'dir', null],
  1496. ['filetype', ['dir'], 'dir', null],
  1497. ['filesize', ['dir'], 'dir', null],
  1498. ['isCreatable', ['dir'], 'dir', null],
  1499. ['isReadable', ['dir'], 'dir', null],
  1500. ['isUpdatable', ['dir'], 'dir', null],
  1501. ['isDeletable', ['dir'], 'dir', null],
  1502. ['isSharable', ['dir'], 'dir', null],
  1503. ['file_exists', ['dir'], 'dir', null],
  1504. ['filemtime', ['dir'], 'dir', null],
  1505. ];
  1506. }
  1507. /**
  1508. * Test whether locks are set before and after the operation
  1509. *
  1510. * @dataProvider basicOperationProviderForLocks
  1511. *
  1512. * @param string $operation operation name on the view
  1513. * @param array $operationArgs arguments for the operation
  1514. * @param string $lockedPath path of the locked item to check
  1515. * @param string $hookType hook type
  1516. * @param int $expectedLockBefore expected lock during pre hooks
  1517. * @param int $expectedLockDuring expected lock during operation
  1518. * @param int $expectedLockAfter expected lock during post hooks
  1519. * @param int $expectedStrayLock expected lock after returning, should
  1520. * be null (unlock) for most operations
  1521. */
  1522. public function testLockBasicOperation(
  1523. $operation,
  1524. $operationArgs,
  1525. $lockedPath,
  1526. $hookType,
  1527. $expectedLockBefore = ILockingProvider::LOCK_SHARED,
  1528. $expectedLockDuring = ILockingProvider::LOCK_SHARED,
  1529. $expectedLockAfter = ILockingProvider::LOCK_SHARED,
  1530. $expectedStrayLock = null
  1531. ) {
  1532. $view = new View('/' . $this->user . '/files/');
  1533. /** @var Temporary|\PHPUnit\Framework\MockObject\MockObject $storage */
  1534. $storage = $this->getMockBuilder(Temporary::class)
  1535. ->setMethods([$operation])
  1536. ->getMock();
  1537. Filesystem::mount($storage, [], $this->user . '/');
  1538. // work directly on disk because mkdir might be mocked
  1539. $realPath = $storage->getSourcePath('');
  1540. mkdir($realPath . '/files');
  1541. mkdir($realPath . '/files/dir');
  1542. file_put_contents($realPath . '/files/test.txt', 'blah');
  1543. $storage->getScanner()->scan('files');
  1544. $storage->expects($this->once())
  1545. ->method($operation)
  1546. ->willReturnCallback(
  1547. function () use ($view, $lockedPath, &$lockTypeDuring) {
  1548. $lockTypeDuring = $this->getFileLockType($view, $lockedPath);
  1549. return true;
  1550. }
  1551. );
  1552. $this->assertNull($this->getFileLockType($view, $lockedPath), 'File not locked before operation');
  1553. $this->connectMockHooks($hookType, $view, $lockedPath, $lockTypePre, $lockTypePost);
  1554. // do operation
  1555. call_user_func_array([$view, $operation], $operationArgs);
  1556. if ($hookType !== null) {
  1557. $this->assertEquals($expectedLockBefore, $lockTypePre, 'File locked properly during pre-hook');
  1558. $this->assertEquals($expectedLockAfter, $lockTypePost, 'File locked properly during post-hook');
  1559. $this->assertEquals($expectedLockDuring, $lockTypeDuring, 'File locked properly during operation');
  1560. } else {
  1561. $this->assertNull($lockTypeDuring, 'File not locked during operation');
  1562. }
  1563. $this->assertEquals($expectedStrayLock, $this->getFileLockType($view, $lockedPath));
  1564. }
  1565. /**
  1566. * Test locks for file_put_content with stream.
  1567. * This code path uses $storage->fopen instead
  1568. */
  1569. public function testLockFilePutContentWithStream() {
  1570. $view = new View('/' . $this->user . '/files/');
  1571. $path = 'test_file_put_contents.txt';
  1572. /** @var Temporary|\PHPUnit\Framework\MockObject\MockObject $storage */
  1573. $storage = $this->getMockBuilder(Temporary::class)
  1574. ->setMethods(['fopen'])
  1575. ->getMock();
  1576. Filesystem::mount($storage, [], $this->user . '/');
  1577. $storage->mkdir('files');
  1578. $storage->expects($this->once())
  1579. ->method('fopen')
  1580. ->willReturnCallback(
  1581. function () use ($view, $path, &$lockTypeDuring) {
  1582. $lockTypeDuring = $this->getFileLockType($view, $path);
  1583. return fopen('php://temp', 'r+');
  1584. }
  1585. );
  1586. $this->connectMockHooks('write', $view, $path, $lockTypePre, $lockTypePost);
  1587. $this->assertNull($this->getFileLockType($view, $path), 'File not locked before operation');
  1588. // do operation
  1589. $view->file_put_contents($path, fopen('php://temp', 'r+'));
  1590. $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypePre, 'File locked properly during pre-hook');
  1591. $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypePost, 'File locked properly during post-hook');
  1592. $this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $lockTypeDuring, 'File locked properly during operation');
  1593. $this->assertNull($this->getFileLockType($view, $path));
  1594. }
  1595. /**
  1596. * Test locks for fopen with fclose at the end
  1597. */
  1598. public function testLockFopen() {
  1599. $view = new View('/' . $this->user . '/files/');
  1600. $path = 'test_file_put_contents.txt';
  1601. /** @var Temporary|\PHPUnit\Framework\MockObject\MockObject $storage */
  1602. $storage = $this->getMockBuilder(Temporary::class)
  1603. ->setMethods(['fopen'])
  1604. ->getMock();
  1605. Filesystem::mount($storage, [], $this->user . '/');
  1606. $storage->mkdir('files');
  1607. $storage->expects($this->once())
  1608. ->method('fopen')
  1609. ->willReturnCallback(
  1610. function () use ($view, $path, &$lockTypeDuring) {
  1611. $lockTypeDuring = $this->getFileLockType($view, $path);
  1612. return fopen('php://temp', 'r+');
  1613. }
  1614. );
  1615. $this->connectMockHooks('write', $view, $path, $lockTypePre, $lockTypePost);
  1616. $this->assertNull($this->getFileLockType($view, $path), 'File not locked before operation');
  1617. // do operation
  1618. $res = $view->fopen($path, 'w');
  1619. $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypePre, 'File locked properly during pre-hook');
  1620. $this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $lockTypeDuring, 'File locked properly during operation');
  1621. $this->assertNull($lockTypePost, 'No post hook, no lock check possible');
  1622. $this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $lockTypeDuring, 'File still locked after fopen');
  1623. fclose($res);
  1624. $this->assertNull($this->getFileLockType($view, $path), 'File unlocked after fclose');
  1625. }
  1626. /**
  1627. * Test locks for fopen with fclose at the end
  1628. *
  1629. * @dataProvider basicOperationProviderForLocks
  1630. *
  1631. * @param string $operation operation name on the view
  1632. * @param array $operationArgs arguments for the operation
  1633. * @param string $path path of the locked item to check
  1634. */
  1635. public function testLockBasicOperationUnlocksAfterException(
  1636. $operation,
  1637. $operationArgs,
  1638. $path
  1639. ) {
  1640. if ($operation === 'touch') {
  1641. $this->markTestSkipped("touch handles storage exceptions internally");
  1642. }
  1643. $view = new View('/' . $this->user . '/files/');
  1644. /** @var Temporary|\PHPUnit\Framework\MockObject\MockObject $storage */
  1645. $storage = $this->getMockBuilder(Temporary::class)
  1646. ->setMethods([$operation])
  1647. ->getMock();
  1648. Filesystem::mount($storage, [], $this->user . '/');
  1649. // work directly on disk because mkdir might be mocked
  1650. $realPath = $storage->getSourcePath('');
  1651. mkdir($realPath . '/files');
  1652. mkdir($realPath . '/files/dir');
  1653. file_put_contents($realPath . '/files/test.txt', 'blah');
  1654. $storage->getScanner()->scan('files');
  1655. $storage->expects($this->once())
  1656. ->method($operation)
  1657. ->willReturnCallback(
  1658. function () {
  1659. throw new \Exception('Simulated exception');
  1660. }
  1661. );
  1662. $thrown = false;
  1663. try {
  1664. call_user_func_array([$view, $operation], $operationArgs);
  1665. } catch (\Exception $e) {
  1666. $thrown = true;
  1667. $this->assertEquals('Simulated exception', $e->getMessage());
  1668. }
  1669. $this->assertTrue($thrown, 'Exception was rethrown');
  1670. $this->assertNull($this->getFileLockType($view, $path), 'File got unlocked after exception');
  1671. }
  1672. public function testLockBasicOperationUnlocksAfterLockException() {
  1673. $view = new View('/' . $this->user . '/files/');
  1674. $storage = new Temporary([]);
  1675. Filesystem::mount($storage, [], $this->user . '/');
  1676. $storage->mkdir('files');
  1677. $storage->mkdir('files/dir');
  1678. $storage->file_put_contents('files/test.txt', 'blah');
  1679. $storage->getScanner()->scan('files');
  1680. // get a shared lock
  1681. $handle = $view->fopen('test.txt', 'r');
  1682. $thrown = false;
  1683. try {
  1684. // try (and fail) to get a write lock
  1685. $view->unlink('test.txt');
  1686. } catch (\Exception $e) {
  1687. $thrown = true;
  1688. $this->assertInstanceOf(LockedException::class, $e);
  1689. }
  1690. $this->assertTrue($thrown, 'Exception was rethrown');
  1691. // clean shared lock
  1692. fclose($handle);
  1693. $this->assertNull($this->getFileLockType($view, 'test.txt'), 'File got unlocked');
  1694. }
  1695. /**
  1696. * Test locks for fopen with fclose at the end
  1697. *
  1698. * @dataProvider basicOperationProviderForLocks
  1699. *
  1700. * @param string $operation operation name on the view
  1701. * @param array $operationArgs arguments for the operation
  1702. * @param string $path path of the locked item to check
  1703. * @param string $hookType hook type
  1704. */
  1705. public function testLockBasicOperationUnlocksAfterCancelledHook(
  1706. $operation,
  1707. $operationArgs,
  1708. $path,
  1709. $hookType
  1710. ) {
  1711. $view = new View('/' . $this->user . '/files/');
  1712. /** @var Temporary|\PHPUnit\Framework\MockObject\MockObject $storage */
  1713. $storage = $this->getMockBuilder(Temporary::class)
  1714. ->setMethods([$operation])
  1715. ->getMock();
  1716. Filesystem::mount($storage, [], $this->user . '/');
  1717. $storage->mkdir('files');
  1718. Util::connectHook(
  1719. Filesystem::CLASSNAME,
  1720. $hookType,
  1721. HookHelper::class,
  1722. 'cancellingCallback'
  1723. );
  1724. call_user_func_array([$view, $operation], $operationArgs);
  1725. $this->assertNull($this->getFileLockType($view, $path), 'File got unlocked after exception');
  1726. }
  1727. public function lockFileRenameOrCopyDataProvider() {
  1728. return [
  1729. ['rename', ILockingProvider::LOCK_EXCLUSIVE],
  1730. ['copy', ILockingProvider::LOCK_SHARED],
  1731. ];
  1732. }
  1733. /**
  1734. * Test locks for rename or copy operation
  1735. *
  1736. * @dataProvider lockFileRenameOrCopyDataProvider
  1737. *
  1738. * @param string $operation operation to be done on the view
  1739. * @param int $expectedLockTypeSourceDuring expected lock type on source file during
  1740. * the operation
  1741. */
  1742. public function testLockFileRename($operation, $expectedLockTypeSourceDuring) {
  1743. $view = new View('/' . $this->user . '/files/');
  1744. /** @var Temporary|\PHPUnit\Framework\MockObject\MockObject $storage */
  1745. $storage = $this->getMockBuilder(Temporary::class)
  1746. ->setMethods([$operation, 'getMetaData', 'filemtime'])
  1747. ->getMock();
  1748. $storage->expects($this->any())
  1749. ->method('getMetaData')
  1750. ->will($this->returnValue([
  1751. 'mtime' => 1885434487,
  1752. 'etag' => '',
  1753. 'mimetype' => 'text/plain',
  1754. 'permissions' => Constants::PERMISSION_ALL,
  1755. 'size' => 3
  1756. ]));
  1757. $storage->expects($this->any())
  1758. ->method('filemtime')
  1759. ->willReturn(123456789);
  1760. $sourcePath = 'original.txt';
  1761. $targetPath = 'target.txt';
  1762. Filesystem::mount($storage, [], $this->user . '/');
  1763. $storage->mkdir('files');
  1764. $view->file_put_contents($sourcePath, 'meh');
  1765. $storage->expects($this->once())
  1766. ->method($operation)
  1767. ->willReturnCallback(
  1768. function () use ($view, $sourcePath, $targetPath, &$lockTypeSourceDuring, &$lockTypeTargetDuring) {
  1769. $lockTypeSourceDuring = $this->getFileLockType($view, $sourcePath);
  1770. $lockTypeTargetDuring = $this->getFileLockType($view, $targetPath);
  1771. return true;
  1772. }
  1773. );
  1774. $this->connectMockHooks($operation, $view, $sourcePath, $lockTypeSourcePre, $lockTypeSourcePost);
  1775. $this->connectMockHooks($operation, $view, $targetPath, $lockTypeTargetPre, $lockTypeTargetPost);
  1776. $this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked before operation');
  1777. $this->assertNull($this->getFileLockType($view, $targetPath), 'Target file not locked before operation');
  1778. $view->$operation($sourcePath, $targetPath);
  1779. $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeSourcePre, 'Source file locked properly during pre-hook');
  1780. $this->assertEquals($expectedLockTypeSourceDuring, $lockTypeSourceDuring, 'Source file locked properly during operation');
  1781. $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeSourcePost, 'Source file locked properly during post-hook');
  1782. $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeTargetPre, 'Target file locked properly during pre-hook');
  1783. $this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $lockTypeTargetDuring, 'Target file locked properly during operation');
  1784. $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeTargetPost, 'Target file locked properly during post-hook');
  1785. $this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked after operation');
  1786. $this->assertNull($this->getFileLockType($view, $targetPath), 'Target file not locked after operation');
  1787. }
  1788. /**
  1789. * simulate a failed copy operation.
  1790. * We expect that we catch the exception, free the lock and re-throw it.
  1791. *
  1792. */
  1793. public function testLockFileCopyException() {
  1794. $this->expectException(\Exception::class);
  1795. $view = new View('/' . $this->user . '/files/');
  1796. /** @var Temporary|\PHPUnit\Framework\MockObject\MockObject $storage */
  1797. $storage = $this->getMockBuilder(Temporary::class)
  1798. ->setMethods(['copy'])
  1799. ->getMock();
  1800. $sourcePath = 'original.txt';
  1801. $targetPath = 'target.txt';
  1802. Filesystem::mount($storage, [], $this->user . '/');
  1803. $storage->mkdir('files');
  1804. $view->file_put_contents($sourcePath, 'meh');
  1805. $storage->expects($this->once())
  1806. ->method('copy')
  1807. ->willReturnCallback(
  1808. function () {
  1809. throw new \Exception();
  1810. }
  1811. );
  1812. $this->connectMockHooks('copy', $view, $sourcePath, $lockTypeSourcePre, $lockTypeSourcePost);
  1813. $this->connectMockHooks('copy', $view, $targetPath, $lockTypeTargetPre, $lockTypeTargetPost);
  1814. $this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked before operation');
  1815. $this->assertNull($this->getFileLockType($view, $targetPath), 'Target file not locked before operation');
  1816. try {
  1817. $view->copy($sourcePath, $targetPath);
  1818. } catch (\Exception $e) {
  1819. $this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked after operation');
  1820. $this->assertNull($this->getFileLockType($view, $targetPath), 'Target file not locked after operation');
  1821. throw $e;
  1822. }
  1823. }
  1824. /**
  1825. * Test rename operation: unlock first path when second path was locked
  1826. */
  1827. public function testLockFileRenameUnlockOnException() {
  1828. self::loginAsUser('test');
  1829. $view = new View('/' . $this->user . '/files/');
  1830. $sourcePath = 'original.txt';
  1831. $targetPath = 'target.txt';
  1832. $view->file_put_contents($sourcePath, 'meh');
  1833. // simulate that the target path is already locked
  1834. $view->lockFile($targetPath, ILockingProvider::LOCK_EXCLUSIVE);
  1835. $this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked before operation');
  1836. $this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $this->getFileLockType($view, $targetPath), 'Target file is locked before operation');
  1837. $thrown = false;
  1838. try {
  1839. $view->rename($sourcePath, $targetPath);
  1840. } catch (LockedException $e) {
  1841. $thrown = true;
  1842. }
  1843. $this->assertTrue($thrown, 'LockedException thrown');
  1844. $this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked after operation');
  1845. $this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $this->getFileLockType($view, $targetPath), 'Target file still locked after operation');
  1846. $view->unlockFile($targetPath, ILockingProvider::LOCK_EXCLUSIVE);
  1847. }
  1848. /**
  1849. * Test rename operation: unlock first path when second path was locked
  1850. */
  1851. public function testGetOwner() {
  1852. self::loginAsUser('test');
  1853. $view = new View('/test/files/');
  1854. $path = 'foo.txt';
  1855. $view->file_put_contents($path, 'meh');
  1856. $this->assertEquals('test', $view->getFileInfo($path)->getOwner()->getUID());
  1857. $folderInfo = $view->getDirectoryContent('');
  1858. $folderInfo = array_values(array_filter($folderInfo, function (FileInfo $info) {
  1859. return $info->getName() === 'foo.txt';
  1860. }));
  1861. $this->assertEquals('test', $folderInfo[0]->getOwner()->getUID());
  1862. $subStorage = new Temporary();
  1863. Filesystem::mount($subStorage, [], '/test/files/asd');
  1864. $folderInfo = $view->getDirectoryContent('');
  1865. $folderInfo = array_values(array_filter($folderInfo, function (FileInfo $info) {
  1866. return $info->getName() === 'asd';
  1867. }));
  1868. $this->assertEquals('test', $folderInfo[0]->getOwner()->getUID());
  1869. }
  1870. public function lockFileRenameOrCopyCrossStorageDataProvider() {
  1871. return [
  1872. ['rename', 'moveFromStorage', ILockingProvider::LOCK_EXCLUSIVE],
  1873. ['copy', 'copyFromStorage', ILockingProvider::LOCK_SHARED],
  1874. ];
  1875. }
  1876. /**
  1877. * Test locks for rename or copy operation cross-storage
  1878. *
  1879. * @dataProvider lockFileRenameOrCopyCrossStorageDataProvider
  1880. *
  1881. * @param string $viewOperation operation to be done on the view
  1882. * @param string $storageOperation operation to be mocked on the storage
  1883. * @param int $expectedLockTypeSourceDuring expected lock type on source file during
  1884. * the operation
  1885. */
  1886. public function testLockFileRenameCrossStorage($viewOperation, $storageOperation, $expectedLockTypeSourceDuring) {
  1887. $view = new View('/' . $this->user . '/files/');
  1888. /** @var Temporary|\PHPUnit\Framework\MockObject\MockObject $storage */
  1889. $storage = $this->getMockBuilder(Temporary::class)
  1890. ->setMethods([$storageOperation])
  1891. ->getMock();
  1892. /** @var Temporary|\PHPUnit\Framework\MockObject\MockObject $storage2 */
  1893. $storage2 = $this->getMockBuilder(Temporary::class)
  1894. ->setMethods([$storageOperation, 'getMetaData', 'filemtime'])
  1895. ->getMock();
  1896. $storage2->expects($this->any())
  1897. ->method('getMetaData')
  1898. ->will($this->returnValue([
  1899. 'mtime' => 1885434487,
  1900. 'etag' => '',
  1901. 'mimetype' => 'text/plain',
  1902. 'permissions' => Constants::PERMISSION_ALL,
  1903. 'size' => 3
  1904. ]));
  1905. $storage2->expects($this->any())
  1906. ->method('filemtime')
  1907. ->willReturn(123456789);
  1908. $sourcePath = 'original.txt';
  1909. $targetPath = 'substorage/target.txt';
  1910. Filesystem::mount($storage, [], $this->user . '/');
  1911. Filesystem::mount($storage2, [], $this->user . '/files/substorage');
  1912. $storage->mkdir('files');
  1913. $view->file_put_contents($sourcePath, 'meh');
  1914. $storage->expects($this->never())
  1915. ->method($storageOperation);
  1916. $storage2->expects($this->once())
  1917. ->method($storageOperation)
  1918. ->willReturnCallback(
  1919. function () use ($view, $sourcePath, $targetPath, &$lockTypeSourceDuring, &$lockTypeTargetDuring) {
  1920. $lockTypeSourceDuring = $this->getFileLockType($view, $sourcePath);
  1921. $lockTypeTargetDuring = $this->getFileLockType($view, $targetPath);
  1922. return true;
  1923. }
  1924. );
  1925. $this->connectMockHooks($viewOperation, $view, $sourcePath, $lockTypeSourcePre, $lockTypeSourcePost);
  1926. $this->connectMockHooks($viewOperation, $view, $targetPath, $lockTypeTargetPre, $lockTypeTargetPost);
  1927. $this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked before operation');
  1928. $this->assertNull($this->getFileLockType($view, $targetPath), 'Target file not locked before operation');
  1929. $view->$viewOperation($sourcePath, $targetPath);
  1930. $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeSourcePre, 'Source file locked properly during pre-hook');
  1931. $this->assertEquals($expectedLockTypeSourceDuring, $lockTypeSourceDuring, 'Source file locked properly during operation');
  1932. $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeSourcePost, 'Source file locked properly during post-hook');
  1933. $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeTargetPre, 'Target file locked properly during pre-hook');
  1934. $this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $lockTypeTargetDuring, 'Target file locked properly during operation');
  1935. $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeTargetPost, 'Target file locked properly during post-hook');
  1936. $this->assertNull($this->getFileLockType($view, $sourcePath), 'Source file not locked after operation');
  1937. $this->assertNull($this->getFileLockType($view, $targetPath), 'Target file not locked after operation');
  1938. }
  1939. /**
  1940. * Test locks when moving a mount point
  1941. */
  1942. public function testLockMoveMountPoint() {
  1943. self::loginAsUser('test');
  1944. [$mount] = $this->createTestMovableMountPoints([
  1945. $this->user . '/files/substorage',
  1946. ]);
  1947. $view = new View('/' . $this->user . '/files/');
  1948. $view->mkdir('subdir');
  1949. $sourcePath = 'substorage';
  1950. $targetPath = 'subdir/substorage_moved';
  1951. $mount->expects($this->once())
  1952. ->method('moveMount')
  1953. ->willReturnCallback(
  1954. function ($target) use ($mount, $view, $sourcePath, $targetPath, &$lockTypeSourceDuring, &$lockTypeTargetDuring, &$lockTypeSharedRootDuring) {
  1955. $lockTypeSourceDuring = $this->getFileLockType($view, $sourcePath, true);
  1956. $lockTypeTargetDuring = $this->getFileLockType($view, $targetPath, true);
  1957. $lockTypeSharedRootDuring = $this->getFileLockType($view, $sourcePath, false);
  1958. $mount->setMountPoint($target);
  1959. return true;
  1960. }
  1961. );
  1962. $this->connectMockHooks('rename', $view, $sourcePath, $lockTypeSourcePre, $lockTypeSourcePost, true);
  1963. $this->connectMockHooks('rename', $view, $targetPath, $lockTypeTargetPre, $lockTypeTargetPost, true);
  1964. // in pre-hook, mount point is still on $sourcePath
  1965. $this->connectMockHooks('rename', $view, $sourcePath, $lockTypeSharedRootPre, $dummy, false);
  1966. // in post-hook, mount point is now on $targetPath
  1967. $this->connectMockHooks('rename', $view, $targetPath, $dummy, $lockTypeSharedRootPost, false);
  1968. $this->assertNull($this->getFileLockType($view, $sourcePath, false), 'Shared storage root not locked before operation');
  1969. $this->assertNull($this->getFileLockType($view, $sourcePath, true), 'Source path not locked before operation');
  1970. $this->assertNull($this->getFileLockType($view, $targetPath, true), 'Target path not locked before operation');
  1971. $view->rename($sourcePath, $targetPath);
  1972. $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeSourcePre, 'Source path locked properly during pre-hook');
  1973. $this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $lockTypeSourceDuring, 'Source path locked properly during operation');
  1974. $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeSourcePost, 'Source path locked properly during post-hook');
  1975. $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeTargetPre, 'Target path locked properly during pre-hook');
  1976. $this->assertEquals(ILockingProvider::LOCK_EXCLUSIVE, $lockTypeTargetDuring, 'Target path locked properly during operation');
  1977. $this->assertEquals(ILockingProvider::LOCK_SHARED, $lockTypeTargetPost, 'Target path locked properly during post-hook');
  1978. $this->assertNull($lockTypeSharedRootPre, 'Shared storage root not locked during pre-hook');
  1979. $this->assertNull($lockTypeSharedRootDuring, 'Shared storage root not locked during move');
  1980. $this->assertNull($lockTypeSharedRootPost, 'Shared storage root not locked during post-hook');
  1981. $this->assertNull($this->getFileLockType($view, $sourcePath, false), 'Shared storage root not locked after operation');
  1982. $this->assertNull($this->getFileLockType($view, $sourcePath, true), 'Source path not locked after operation');
  1983. $this->assertNull($this->getFileLockType($view, $targetPath, true), 'Target path not locked after operation');
  1984. }
  1985. /**
  1986. * Connect hook callbacks for hook type
  1987. *
  1988. * @param string $hookType hook type or null for none
  1989. * @param View $view view to check the lock on
  1990. * @param string $path path for which to check the lock
  1991. * @param int $lockTypePre variable to receive lock type that was active in the pre-hook
  1992. * @param int $lockTypePost variable to receive lock type that was active in the post-hook
  1993. * @param bool $onMountPoint true to check the mount point instead of the
  1994. * mounted storage
  1995. */
  1996. private function connectMockHooks($hookType, $view, $path, &$lockTypePre, &$lockTypePost, $onMountPoint = false) {
  1997. if ($hookType === null) {
  1998. return;
  1999. }
  2000. $eventHandler = $this->getMockBuilder(\stdclass::class)
  2001. ->setMethods(['preCallback', 'postCallback'])
  2002. ->getMock();
  2003. $eventHandler->expects($this->any())
  2004. ->method('preCallback')
  2005. ->willReturnCallback(
  2006. function () use ($view, $path, $onMountPoint, &$lockTypePre) {
  2007. $lockTypePre = $this->getFileLockType($view, $path, $onMountPoint);
  2008. }
  2009. );
  2010. $eventHandler->expects($this->any())
  2011. ->method('postCallback')
  2012. ->willReturnCallback(
  2013. function () use ($view, $path, $onMountPoint, &$lockTypePost) {
  2014. $lockTypePost = $this->getFileLockType($view, $path, $onMountPoint);
  2015. }
  2016. );
  2017. if ($hookType !== null) {
  2018. Util::connectHook(
  2019. Filesystem::CLASSNAME,
  2020. $hookType,
  2021. $eventHandler,
  2022. 'preCallback'
  2023. );
  2024. Util::connectHook(
  2025. Filesystem::CLASSNAME,
  2026. 'post_' . $hookType,
  2027. $eventHandler,
  2028. 'postCallback'
  2029. );
  2030. }
  2031. }
  2032. /**
  2033. * Returns the file lock type
  2034. *
  2035. * @param View $view view
  2036. * @param string $path path
  2037. * @param bool $onMountPoint true to check the mount point instead of the
  2038. * mounted storage
  2039. *
  2040. * @return int lock type or null if file was not locked
  2041. */
  2042. private function getFileLockType(View $view, $path, $onMountPoint = false) {
  2043. if ($this->isFileLocked($view, $path, ILockingProvider::LOCK_EXCLUSIVE, $onMountPoint)) {
  2044. return ILockingProvider::LOCK_EXCLUSIVE;
  2045. } elseif ($this->isFileLocked($view, $path, ILockingProvider::LOCK_SHARED, $onMountPoint)) {
  2046. return ILockingProvider::LOCK_SHARED;
  2047. }
  2048. return null;
  2049. }
  2050. public function testRemoveMoveableMountPoint() {
  2051. $mountPoint = '/' . $this->user . '/files/mount/';
  2052. // Mock the mount point
  2053. /** @var TestMoveableMountPoint|\PHPUnit\Framework\MockObject\MockObject $mount */
  2054. $mount = $this->createMock(TestMoveableMountPoint::class);
  2055. $mount->expects($this->once())
  2056. ->method('getMountPoint')
  2057. ->willReturn($mountPoint);
  2058. $mount->expects($this->once())
  2059. ->method('removeMount')
  2060. ->willReturn('foo');
  2061. $mount->expects($this->any())
  2062. ->method('getInternalPath')
  2063. ->willReturn('');
  2064. // Register mount
  2065. Filesystem::getMountManager()->addMount($mount);
  2066. // Listen for events
  2067. $eventHandler = $this->getMockBuilder(\stdclass::class)
  2068. ->setMethods(['umount', 'post_umount'])
  2069. ->getMock();
  2070. $eventHandler->expects($this->once())
  2071. ->method('umount')
  2072. ->with([Filesystem::signal_param_path => '/mount']);
  2073. $eventHandler->expects($this->once())
  2074. ->method('post_umount')
  2075. ->with([Filesystem::signal_param_path => '/mount']);
  2076. Util::connectHook(
  2077. Filesystem::CLASSNAME,
  2078. 'umount',
  2079. $eventHandler,
  2080. 'umount'
  2081. );
  2082. Util::connectHook(
  2083. Filesystem::CLASSNAME,
  2084. 'post_umount',
  2085. $eventHandler,
  2086. 'post_umount'
  2087. );
  2088. //Delete the mountpoint
  2089. $view = new View('/' . $this->user . '/files');
  2090. $this->assertEquals('foo', $view->rmdir('mount'));
  2091. }
  2092. public function mimeFilterProvider() {
  2093. return [
  2094. [null, ['test1.txt', 'test2.txt', 'test3.md', 'test4.png']],
  2095. ['text/plain', ['test1.txt', 'test2.txt']],
  2096. ['text/markdown', ['test3.md']],
  2097. ['text', ['test1.txt', 'test2.txt', 'test3.md']],
  2098. ];
  2099. }
  2100. /**
  2101. * @param string $filter
  2102. * @param string[] $expected
  2103. * @dataProvider mimeFilterProvider
  2104. */
  2105. public function testGetDirectoryContentMimeFilter($filter, $expected) {
  2106. $storage1 = new Temporary();
  2107. $root = self::getUniqueID('/');
  2108. Filesystem::mount($storage1, [], $root . '/');
  2109. $view = new View($root);
  2110. $view->file_put_contents('test1.txt', 'asd');
  2111. $view->file_put_contents('test2.txt', 'asd');
  2112. $view->file_put_contents('test3.md', 'asd');
  2113. $view->file_put_contents('test4.png', '');
  2114. $content = $view->getDirectoryContent('', $filter);
  2115. $files = array_map(function (FileInfo $info) {
  2116. return $info->getName();
  2117. }, $content);
  2118. sort($files);
  2119. $this->assertEquals($expected, $files);
  2120. }
  2121. public function testFilePutContentsClearsChecksum() {
  2122. $storage = new Temporary([]);
  2123. $scanner = $storage->getScanner();
  2124. $storage->file_put_contents('foo.txt', 'bar');
  2125. Filesystem::mount($storage, [], '/test/');
  2126. $scanner->scan('');
  2127. $view = new View('/test/foo.txt');
  2128. $view->putFileInfo('.', ['checksum' => '42']);
  2129. $this->assertEquals('bar', $view->file_get_contents(''));
  2130. $fh = tmpfile();
  2131. fwrite($fh, 'fooo');
  2132. rewind($fh);
  2133. clearstatcache();
  2134. $view->file_put_contents('', $fh);
  2135. $this->assertEquals('fooo', $view->file_get_contents(''));
  2136. $data = $view->getFileInfo('.');
  2137. $this->assertEquals('', $data->getChecksum());
  2138. }
  2139. public function testDeleteGhostFile() {
  2140. $storage = new Temporary([]);
  2141. $scanner = $storage->getScanner();
  2142. $cache = $storage->getCache();
  2143. $storage->file_put_contents('foo.txt', 'bar');
  2144. Filesystem::mount($storage, [], '/test/');
  2145. $scanner->scan('');
  2146. $storage->unlink('foo.txt');
  2147. $this->assertTrue($cache->inCache('foo.txt'));
  2148. $view = new View('/test');
  2149. $rootInfo = $view->getFileInfo('');
  2150. $this->assertEquals(3, $rootInfo->getSize());
  2151. $view->unlink('foo.txt');
  2152. $newInfo = $view->getFileInfo('');
  2153. $this->assertFalse($cache->inCache('foo.txt'));
  2154. $this->assertNotEquals($rootInfo->getEtag(), $newInfo->getEtag());
  2155. $this->assertEquals(0, $newInfo->getSize());
  2156. }
  2157. public function testDeleteGhostFolder() {
  2158. $storage = new Temporary([]);
  2159. $scanner = $storage->getScanner();
  2160. $cache = $storage->getCache();
  2161. $storage->mkdir('foo');
  2162. $storage->file_put_contents('foo/foo.txt', 'bar');
  2163. Filesystem::mount($storage, [], '/test/');
  2164. $scanner->scan('');
  2165. $storage->rmdir('foo');
  2166. $this->assertTrue($cache->inCache('foo'));
  2167. $this->assertTrue($cache->inCache('foo/foo.txt'));
  2168. $view = new View('/test');
  2169. $rootInfo = $view->getFileInfo('');
  2170. $this->assertEquals(3, $rootInfo->getSize());
  2171. $view->rmdir('foo');
  2172. $newInfo = $view->getFileInfo('');
  2173. $this->assertFalse($cache->inCache('foo'));
  2174. $this->assertFalse($cache->inCache('foo/foo.txt'));
  2175. $this->assertNotEquals($rootInfo->getEtag(), $newInfo->getEtag());
  2176. $this->assertEquals(0, $newInfo->getSize());
  2177. }
  2178. public function testCreateParentDirectories() {
  2179. $view = $this->getMockBuilder(View::class)
  2180. ->disableOriginalConstructor()
  2181. ->setMethods([
  2182. 'is_file',
  2183. 'file_exists',
  2184. 'mkdir',
  2185. ])
  2186. ->getMock();
  2187. $view
  2188. ->expects($this->at(0))
  2189. ->method('is_file')
  2190. ->with('/new')
  2191. ->willReturn(false);
  2192. $view
  2193. ->expects($this->at(1))
  2194. ->method('file_exists')
  2195. ->with('/new')
  2196. ->willReturn(true);
  2197. $view
  2198. ->expects($this->at(2))
  2199. ->method('is_file')
  2200. ->with('/new/folder')
  2201. ->willReturn(false);
  2202. $view
  2203. ->expects($this->at(3))
  2204. ->method('file_exists')
  2205. ->with('/new/folder')
  2206. ->willReturn(false);
  2207. $view
  2208. ->expects($this->at(4))
  2209. ->method('mkdir')
  2210. ->with('/new/folder');
  2211. $view
  2212. ->expects($this->at(5))
  2213. ->method('is_file')
  2214. ->with('/new/folder/structure')
  2215. ->willReturn(false);
  2216. $view
  2217. ->expects($this->at(6))
  2218. ->method('file_exists')
  2219. ->with('/new/folder/structure')
  2220. ->willReturn(false);
  2221. $view
  2222. ->expects($this->at(7))
  2223. ->method('mkdir')
  2224. ->with('/new/folder/structure');
  2225. $this->assertTrue(self::invokePrivate($view, 'createParentDirectories', ['/new/folder/structure']));
  2226. }
  2227. public function testCreateParentDirectoriesWithExistingFile() {
  2228. $view = $this->getMockBuilder(View::class)
  2229. ->disableOriginalConstructor()
  2230. ->setMethods([
  2231. 'is_file',
  2232. 'file_exists',
  2233. 'mkdir',
  2234. ])
  2235. ->getMock();
  2236. $view
  2237. ->expects($this->once())
  2238. ->method('is_file')
  2239. ->with('/file.txt')
  2240. ->willReturn(true);
  2241. $this->assertFalse(self::invokePrivate($view, 'createParentDirectories', ['/file.txt/folder/structure']));
  2242. }
  2243. public function testCacheExtension() {
  2244. $storage = new Temporary([]);
  2245. $scanner = $storage->getScanner();
  2246. $storage->file_put_contents('foo.txt', 'bar');
  2247. $scanner->scan('');
  2248. Filesystem::mount($storage, [], '/test/');
  2249. $view = new View('/test');
  2250. $info = $view->getFileInfo('/foo.txt');
  2251. $this->assertEquals(0, $info->getUploadTime());
  2252. $this->assertEquals(0, $info->getCreationTime());
  2253. $view->putFileInfo('/foo.txt', ['upload_time' => 25]);
  2254. $info = $view->getFileInfo('/foo.txt');
  2255. $this->assertEquals(25, $info->getUploadTime());
  2256. $this->assertEquals(0, $info->getCreationTime());
  2257. }
  2258. }