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.

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