view.php 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227
  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. */
  8. /**
  9. * Class to provide access to ownCloud filesystem via a "view", and methods for
  10. * working with files within that view (e.g. read, write, delete, etc.). Each
  11. * view is restricted to a set of directories via a virtual root. The default view
  12. * uses the currently logged in user's data directory as root (parts of
  13. * OC_Filesystem are merely a wrapper for OC\Files\View).
  14. *
  15. * Apps that need to access files outside of the user data folders (to modify files
  16. * belonging to a user other than the one currently logged in, for example) should
  17. * use this class directly rather than using OC_Filesystem, or making use of PHP's
  18. * built-in file manipulation functions. This will ensure all hooks and proxies
  19. * are triggered correctly.
  20. *
  21. * Filesystem functions are not called directly; they are passed to the correct
  22. * \OC\Files\Storage\Storage object
  23. */
  24. namespace OC\Files;
  25. use OC\Files\Cache\Updater;
  26. use OC\Files\Mount\MoveableMount;
  27. class View {
  28. private $fakeRoot = '';
  29. public function __construct($root = '') {
  30. $this->fakeRoot = $root;
  31. }
  32. public function getAbsolutePath($path = '/') {
  33. $this->assertPathLength($path);
  34. if ($path === '') {
  35. $path = '/';
  36. }
  37. if ($path[0] !== '/') {
  38. $path = '/' . $path;
  39. }
  40. return $this->fakeRoot . $path;
  41. }
  42. /**
  43. * change the root to a fake root
  44. *
  45. * @param string $fakeRoot
  46. * @return boolean|null
  47. */
  48. public function chroot($fakeRoot) {
  49. if (!$fakeRoot == '') {
  50. if ($fakeRoot[0] !== '/') {
  51. $fakeRoot = '/' . $fakeRoot;
  52. }
  53. }
  54. $this->fakeRoot = $fakeRoot;
  55. }
  56. /**
  57. * get the fake root
  58. *
  59. * @return string
  60. */
  61. public function getRoot() {
  62. return $this->fakeRoot;
  63. }
  64. /**
  65. * get path relative to the root of the view
  66. *
  67. * @param string $path
  68. * @return string
  69. */
  70. public function getRelativePath($path) {
  71. $this->assertPathLength($path);
  72. if ($this->fakeRoot == '') {
  73. return $path;
  74. }
  75. if (strpos($path, $this->fakeRoot) !== 0) {
  76. return null;
  77. } else {
  78. $path = substr($path, strlen($this->fakeRoot));
  79. if (strlen($path) === 0) {
  80. return '/';
  81. } else {
  82. return $path;
  83. }
  84. }
  85. }
  86. /**
  87. * get the mountpoint of the storage object for a path
  88. * ( note: because a storage is not always mounted inside the fakeroot, the
  89. * returned mountpoint is relative to the absolute root of the filesystem
  90. * and doesn't take the chroot into account )
  91. *
  92. * @param string $path
  93. * @return string
  94. */
  95. public function getMountPoint($path) {
  96. return Filesystem::getMountPoint($this->getAbsolutePath($path));
  97. }
  98. /**
  99. * resolve a path to a storage and internal path
  100. *
  101. * @param string $path
  102. * @return array an array consisting of the storage and the internal path
  103. */
  104. public function resolvePath($path) {
  105. $a = $this->getAbsolutePath($path);
  106. $p = Filesystem::normalizePath($a);
  107. return Filesystem::resolvePath($p);
  108. }
  109. /**
  110. * return the path to a local version of the file
  111. * we need this because we can't know if a file is stored local or not from
  112. * outside the filestorage and for some purposes a local file is needed
  113. *
  114. * @param string $path
  115. * @return string
  116. */
  117. public function getLocalFile($path) {
  118. $parent = substr($path, 0, strrpos($path, '/'));
  119. $path = $this->getAbsolutePath($path);
  120. list($storage, $internalPath) = Filesystem::resolvePath($path);
  121. if (Filesystem::isValidPath($parent) and $storage) {
  122. return $storage->getLocalFile($internalPath);
  123. } else {
  124. return null;
  125. }
  126. }
  127. /**
  128. * @param string $path
  129. * @return string
  130. */
  131. public function getLocalFolder($path) {
  132. $parent = substr($path, 0, strrpos($path, '/'));
  133. $path = $this->getAbsolutePath($path);
  134. list($storage, $internalPath) = Filesystem::resolvePath($path);
  135. if (Filesystem::isValidPath($parent) and $storage) {
  136. return $storage->getLocalFolder($internalPath);
  137. } else {
  138. return null;
  139. }
  140. }
  141. /**
  142. * the following functions operate with arguments and return values identical
  143. * to those of their PHP built-in equivalents. Mostly they are merely wrappers
  144. * for \OC\Files\Storage\Storage via basicOperation().
  145. */
  146. public function mkdir($path) {
  147. return $this->basicOperation('mkdir', $path, array('create', 'write'));
  148. }
  149. /**
  150. * remove mount point
  151. *
  152. * @param \OC\Files\Mount\MoveableMount $mount
  153. * @param string $path relative to data/
  154. * @return boolean
  155. */
  156. protected function removeMount($mount, $path){
  157. if ($mount instanceof MoveableMount) {
  158. // cut of /user/files to get the relative path to data/user/files
  159. $pathParts= explode('/', $path, 4);
  160. $relPath = '/' . $pathParts[3];
  161. \OC_Hook::emit(
  162. Filesystem::CLASSNAME, "umount",
  163. array(Filesystem::signal_param_path => $relPath)
  164. );
  165. $result = $mount->removeMount();
  166. if ($result) {
  167. \OC_Hook::emit(
  168. Filesystem::CLASSNAME, "post_umount",
  169. array(Filesystem::signal_param_path => $relPath)
  170. );
  171. }
  172. return $result;
  173. } else {
  174. // do not allow deleting the storage's root / the mount point
  175. // because for some storages it might delete the whole contents
  176. // but isn't supposed to work that way
  177. return false;
  178. }
  179. }
  180. public function rmdir($path) {
  181. $absolutePath= $this->getAbsolutePath($path);
  182. $mount = Filesystem::getMountManager()->find($absolutePath);
  183. if ($mount->getInternalPath($absolutePath) === '') {
  184. return $this->removeMount($mount, $path);
  185. }
  186. if ($this->is_dir($path)) {
  187. return $this->basicOperation('rmdir', $path, array('delete'));
  188. } else {
  189. return false;
  190. }
  191. }
  192. /**
  193. * @param string $path
  194. * @return resource
  195. */
  196. public function opendir($path) {
  197. return $this->basicOperation('opendir', $path, array('read'));
  198. }
  199. public function readdir($handle) {
  200. $fsLocal = new Storage\Local(array('datadir' => '/'));
  201. return $fsLocal->readdir($handle);
  202. }
  203. public function is_dir($path) {
  204. if ($path == '/') {
  205. return true;
  206. }
  207. return $this->basicOperation('is_dir', $path);
  208. }
  209. public function is_file($path) {
  210. if ($path == '/') {
  211. return false;
  212. }
  213. return $this->basicOperation('is_file', $path);
  214. }
  215. public function stat($path) {
  216. return $this->basicOperation('stat', $path);
  217. }
  218. public function filetype($path) {
  219. return $this->basicOperation('filetype', $path);
  220. }
  221. public function filesize($path) {
  222. return $this->basicOperation('filesize', $path);
  223. }
  224. public function readfile($path) {
  225. $this->assertPathLength($path);
  226. @ob_end_clean();
  227. $handle = $this->fopen($path, 'rb');
  228. if ($handle) {
  229. $chunkSize = 8192; // 8 kB chunks
  230. while (!feof($handle)) {
  231. echo fread($handle, $chunkSize);
  232. flush();
  233. }
  234. $size = $this->filesize($path);
  235. return $size;
  236. }
  237. return false;
  238. }
  239. public function isCreatable($path) {
  240. return $this->basicOperation('isCreatable', $path);
  241. }
  242. public function isReadable($path) {
  243. return $this->basicOperation('isReadable', $path);
  244. }
  245. public function isUpdatable($path) {
  246. return $this->basicOperation('isUpdatable', $path);
  247. }
  248. public function isDeletable($path) {
  249. return $this->basicOperation('isDeletable', $path);
  250. }
  251. public function isSharable($path) {
  252. return $this->basicOperation('isSharable', $path);
  253. }
  254. public function file_exists($path) {
  255. if ($path == '/') {
  256. return true;
  257. }
  258. return $this->basicOperation('file_exists', $path);
  259. }
  260. public function filemtime($path) {
  261. return $this->basicOperation('filemtime', $path);
  262. }
  263. public function touch($path, $mtime = null) {
  264. if (!is_null($mtime) and !is_numeric($mtime)) {
  265. $mtime = strtotime($mtime);
  266. }
  267. $hooks = array('touch');
  268. if (!$this->file_exists($path)) {
  269. $hooks[] = 'create';
  270. $hooks[] = 'write';
  271. }
  272. $result = $this->basicOperation('touch', $path, $hooks, $mtime);
  273. if (!$result) { //if native touch fails, we emulate it by changing the mtime in the cache
  274. $this->putFileInfo($path, array('mtime' => $mtime));
  275. }
  276. return true;
  277. }
  278. public function file_get_contents($path) {
  279. return $this->basicOperation('file_get_contents', $path, array('read'));
  280. }
  281. protected function emit_file_hooks_pre($exists, $path, &$run) {
  282. if (!$exists) {
  283. \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_create, array(
  284. Filesystem::signal_param_path => $this->getHookPath($path),
  285. Filesystem::signal_param_run => &$run,
  286. ));
  287. } else {
  288. \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_update, array(
  289. Filesystem::signal_param_path => $this->getHookPath($path),
  290. Filesystem::signal_param_run => &$run,
  291. ));
  292. }
  293. \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_write, array(
  294. Filesystem::signal_param_path => $this->getHookPath($path),
  295. Filesystem::signal_param_run => &$run,
  296. ));
  297. }
  298. protected function emit_file_hooks_post($exists, $path) {
  299. if (!$exists) {
  300. \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_create, array(
  301. Filesystem::signal_param_path => $this->getHookPath($path),
  302. ));
  303. } else {
  304. \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_update, array(
  305. Filesystem::signal_param_path => $this->getHookPath($path),
  306. ));
  307. }
  308. \OC_Hook::emit(Filesystem::CLASSNAME, Filesystem::signal_post_write, array(
  309. Filesystem::signal_param_path => $this->getHookPath($path),
  310. ));
  311. }
  312. public function file_put_contents($path, $data) {
  313. if (is_resource($data)) { //not having to deal with streams in file_put_contents makes life easier
  314. $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
  315. if (\OC_FileProxy::runPreProxies('file_put_contents', $absolutePath, $data)
  316. and Filesystem::isValidPath($path)
  317. and !Filesystem::isFileBlacklisted($path)
  318. ) {
  319. $path = $this->getRelativePath($absolutePath);
  320. $exists = $this->file_exists($path);
  321. $run = true;
  322. if ($this->shouldEmitHooks($path)) {
  323. $this->emit_file_hooks_pre($exists, $path, $run);
  324. }
  325. if (!$run) {
  326. return false;
  327. }
  328. $target = $this->fopen($path, 'w');
  329. if ($target) {
  330. list ($count, $result) = \OC_Helper::streamCopy($data, $target);
  331. fclose($target);
  332. fclose($data);
  333. if ($this->shouldEmitHooks($path) && $result !== false) {
  334. Updater::writeHook(array(
  335. 'path' => $this->getHookPath($path)
  336. ));
  337. $this->emit_file_hooks_post($exists, $path);
  338. }
  339. \OC_FileProxy::runPostProxies('file_put_contents', $absolutePath, $count);
  340. return $result;
  341. } else {
  342. return false;
  343. }
  344. } else {
  345. return false;
  346. }
  347. } else {
  348. $hooks = ($this->file_exists($path)) ? array('update', 'write') : array('create', 'write');
  349. return $this->basicOperation('file_put_contents', $path, $hooks, $data);
  350. }
  351. }
  352. public function unlink($path) {
  353. if ($path === '' || $path === '/') {
  354. // do not allow deleting the root
  355. return false;
  356. }
  357. $postFix = (substr($path, -1, 1) === '/') ? '/' : '';
  358. $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
  359. $mount = Filesystem::getMountManager()->find($absolutePath . $postFix);
  360. if ($mount->getInternalPath($absolutePath) === '') {
  361. return $this->removeMount($mount, $absolutePath);
  362. }
  363. return $this->basicOperation('unlink', $path, array('delete'));
  364. }
  365. /**
  366. * @param string $directory
  367. */
  368. public function deleteAll($directory, $empty = false) {
  369. return $this->rmdir($directory);
  370. }
  371. public function rename($path1, $path2) {
  372. $postFix1 = (substr($path1, -1, 1) === '/') ? '/' : '';
  373. $postFix2 = (substr($path2, -1, 1) === '/') ? '/' : '';
  374. $absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1));
  375. $absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2));
  376. if (
  377. \OC_FileProxy::runPreProxies('rename', $absolutePath1, $absolutePath2)
  378. and Filesystem::isValidPath($path2)
  379. and Filesystem::isValidPath($path1)
  380. and !Filesystem::isFileBlacklisted($path2)
  381. ) {
  382. $path1 = $this->getRelativePath($absolutePath1);
  383. $path2 = $this->getRelativePath($absolutePath2);
  384. $exists = $this->file_exists($path2);
  385. if ($path1 == null or $path2 == null) {
  386. return false;
  387. }
  388. $run = true;
  389. if ($this->shouldEmitHooks() && (Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2))) {
  390. // if it was a rename from a part file to a regular file it was a write and not a rename operation
  391. $this->emit_file_hooks_pre($exists, $path2, $run);
  392. } elseif ($this->shouldEmitHooks()) {
  393. \OC_Hook::emit(
  394. Filesystem::CLASSNAME, Filesystem::signal_rename,
  395. array(
  396. Filesystem::signal_param_oldpath => $this->getHookPath($path1),
  397. Filesystem::signal_param_newpath => $this->getHookPath($path2),
  398. Filesystem::signal_param_run => &$run
  399. )
  400. );
  401. }
  402. if ($run) {
  403. $mp1 = $this->getMountPoint($path1 . $postFix1);
  404. $mp2 = $this->getMountPoint($path2 . $postFix2);
  405. $manager = Filesystem::getMountManager();
  406. $mount = $manager->find($absolutePath1 . $postFix1);
  407. $storage1 = $mount->getStorage();
  408. $internalPath1 = $mount->getInternalPath($absolutePath1 . $postFix1);
  409. list(, $internalPath2) = Filesystem::resolvePath($absolutePath2 . $postFix2);
  410. if ($internalPath1 === '' and $mount instanceof MoveableMount) {
  411. if ($this->isTargetAllowed($absolutePath2)) {
  412. /**
  413. * @var \OC\Files\Mount\Mount | \OC\Files\Mount\MoveableMount $mount
  414. */
  415. $sourceMountPoint = $mount->getMountPoint();
  416. $result = $mount->moveMount($absolutePath2);
  417. $manager->moveMount($sourceMountPoint, $mount->getMountPoint());
  418. \OC_FileProxy::runPostProxies('rename', $absolutePath1, $absolutePath2);
  419. } else {
  420. $result = false;
  421. }
  422. } elseif ($mp1 == $mp2) {
  423. if ($storage1) {
  424. $result = $storage1->rename($internalPath1, $internalPath2);
  425. \OC_FileProxy::runPostProxies('rename', $absolutePath1, $absolutePath2);
  426. } else {
  427. $result = false;
  428. }
  429. } else {
  430. if ($this->is_dir($path1)) {
  431. $result = $this->copy($path1, $path2);
  432. if ($result === true) {
  433. $result = $storage1->rmdir($internalPath1);
  434. }
  435. } else {
  436. $source = $this->fopen($path1 . $postFix1, 'r');
  437. $target = $this->fopen($path2 . $postFix2, 'w');
  438. list($count, $result) = \OC_Helper::streamCopy($source, $target);
  439. // close open handle - especially $source is necessary because unlink below will
  440. // throw an exception on windows because the file is locked
  441. fclose($source);
  442. fclose($target);
  443. if ($result !== false) {
  444. $storage1->unlink($internalPath1);
  445. }
  446. }
  447. }
  448. if ($this->shouldEmitHooks() && (Cache\Scanner::isPartialFile($path1) && !Cache\Scanner::isPartialFile($path2)) && $result !== false) {
  449. // if it was a rename from a part file to a regular file it was a write and not a rename operation
  450. Updater::writeHook(array('path' => $this->getHookPath($path2)));
  451. $this->emit_file_hooks_post($exists, $path2);
  452. } elseif ($this->shouldEmitHooks() && $result !== false) {
  453. Updater::renameHook(array(
  454. 'oldpath' => $this->getHookPath($path1),
  455. 'newpath' => $this->getHookPath($path2)
  456. ));
  457. \OC_Hook::emit(
  458. Filesystem::CLASSNAME,
  459. Filesystem::signal_post_rename,
  460. array(
  461. Filesystem::signal_param_oldpath => $this->getHookPath($path1),
  462. Filesystem::signal_param_newpath => $this->getHookPath($path2)
  463. )
  464. );
  465. }
  466. return $result;
  467. } else {
  468. return false;
  469. }
  470. } else {
  471. return false;
  472. }
  473. }
  474. public function copy($path1, $path2) {
  475. $postFix1 = (substr($path1, -1, 1) === '/') ? '/' : '';
  476. $postFix2 = (substr($path2, -1, 1) === '/') ? '/' : '';
  477. $absolutePath1 = Filesystem::normalizePath($this->getAbsolutePath($path1));
  478. $absolutePath2 = Filesystem::normalizePath($this->getAbsolutePath($path2));
  479. if (
  480. \OC_FileProxy::runPreProxies('copy', $absolutePath1, $absolutePath2)
  481. and Filesystem::isValidPath($path2)
  482. and Filesystem::isValidPath($path1)
  483. and !Filesystem::isFileBlacklisted($path2)
  484. ) {
  485. $path1 = $this->getRelativePath($absolutePath1);
  486. $path2 = $this->getRelativePath($absolutePath2);
  487. if ($path1 == null or $path2 == null) {
  488. return false;
  489. }
  490. $run = true;
  491. $exists = $this->file_exists($path2);
  492. if ($this->shouldEmitHooks()) {
  493. \OC_Hook::emit(
  494. Filesystem::CLASSNAME,
  495. Filesystem::signal_copy,
  496. array(
  497. Filesystem::signal_param_oldpath => $this->getHookPath($path1),
  498. Filesystem::signal_param_newpath => $this->getHookPath($path2),
  499. Filesystem::signal_param_run => &$run
  500. )
  501. );
  502. $this->emit_file_hooks_pre($exists, $path2, $run);
  503. }
  504. if ($run) {
  505. $mp1 = $this->getMountPoint($path1 . $postFix1);
  506. $mp2 = $this->getMountPoint($path2 . $postFix2);
  507. if ($mp1 == $mp2) {
  508. list($storage, $internalPath1) = Filesystem::resolvePath($absolutePath1 . $postFix1);
  509. list(, $internalPath2) = Filesystem::resolvePath($absolutePath2 . $postFix2);
  510. if ($storage) {
  511. $result = $storage->copy($internalPath1, $internalPath2);
  512. } else {
  513. $result = false;
  514. }
  515. } else {
  516. if ($this->is_dir($path1) && ($dh = $this->opendir($path1))) {
  517. $result = $this->mkdir($path2);
  518. if (is_resource($dh)) {
  519. while (($file = readdir($dh)) !== false) {
  520. if (!Filesystem::isIgnoredDir($file)) {
  521. $result = $this->copy($path1 . '/' . $file, $path2 . '/' . $file);
  522. }
  523. }
  524. }
  525. } else {
  526. $source = $this->fopen($path1 . $postFix1, 'r');
  527. $target = $this->fopen($path2 . $postFix2, 'w');
  528. list($count, $result) = \OC_Helper::streamCopy($source, $target);
  529. fclose($source);
  530. fclose($target);
  531. }
  532. }
  533. if ($this->shouldEmitHooks() && $result !== false) {
  534. \OC_Hook::emit(
  535. Filesystem::CLASSNAME,
  536. Filesystem::signal_post_copy,
  537. array(
  538. Filesystem::signal_param_oldpath => $this->getHookPath($path1),
  539. Filesystem::signal_param_newpath => $this->getHookPath($path2)
  540. )
  541. );
  542. $this->emit_file_hooks_post($exists, $path2);
  543. }
  544. return $result;
  545. } else {
  546. return false;
  547. }
  548. } else {
  549. return false;
  550. }
  551. }
  552. /**
  553. * @param string $path
  554. * @param string $mode
  555. * @return resource
  556. */
  557. public function fopen($path, $mode) {
  558. $hooks = array();
  559. switch ($mode) {
  560. case 'r':
  561. case 'rb':
  562. $hooks[] = 'read';
  563. break;
  564. case 'r+':
  565. case 'rb+':
  566. case 'w+':
  567. case 'wb+':
  568. case 'x+':
  569. case 'xb+':
  570. case 'a+':
  571. case 'ab+':
  572. $hooks[] = 'read';
  573. $hooks[] = 'write';
  574. break;
  575. case 'w':
  576. case 'wb':
  577. case 'x':
  578. case 'xb':
  579. case 'a':
  580. case 'ab':
  581. $hooks[] = 'write';
  582. break;
  583. default:
  584. \OC_Log::write('core', 'invalid mode (' . $mode . ') for ' . $path, \OC_Log::ERROR);
  585. }
  586. return $this->basicOperation('fopen', $path, $hooks, $mode);
  587. }
  588. public function toTmpFile($path) {
  589. $this->assertPathLength($path);
  590. if (Filesystem::isValidPath($path)) {
  591. $source = $this->fopen($path, 'r');
  592. if ($source) {
  593. $extension = pathinfo($path, PATHINFO_EXTENSION);
  594. $tmpFile = \OC_Helper::tmpFile($extension);
  595. file_put_contents($tmpFile, $source);
  596. return $tmpFile;
  597. } else {
  598. return false;
  599. }
  600. } else {
  601. return false;
  602. }
  603. }
  604. public function fromTmpFile($tmpFile, $path) {
  605. $this->assertPathLength($path);
  606. if (Filesystem::isValidPath($path)) {
  607. // Get directory that the file is going into
  608. $filePath = dirname($path);
  609. // Create the directories if any
  610. if (!$this->file_exists($filePath)) {
  611. $this->mkdir($filePath);
  612. }
  613. if (!$tmpFile) {
  614. debug_print_backtrace();
  615. }
  616. $source = fopen($tmpFile, 'r');
  617. if ($source) {
  618. $this->file_put_contents($path, $source);
  619. unlink($tmpFile);
  620. return true;
  621. } else {
  622. return false;
  623. }
  624. } else {
  625. return false;
  626. }
  627. }
  628. public function getMimeType($path) {
  629. $this->assertPathLength($path);
  630. return $this->basicOperation('getMimeType', $path);
  631. }
  632. public function hash($type, $path, $raw = false) {
  633. $postFix = (substr($path, -1, 1) === '/') ? '/' : '';
  634. $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
  635. if (\OC_FileProxy::runPreProxies('hash', $absolutePath) && Filesystem::isValidPath($path)) {
  636. $path = $this->getRelativePath($absolutePath);
  637. if ($path == null) {
  638. return false;
  639. }
  640. if ($this->shouldEmitHooks($path)) {
  641. \OC_Hook::emit(
  642. Filesystem::CLASSNAME,
  643. Filesystem::signal_read,
  644. array(Filesystem::signal_param_path => $this->getHookPath($path))
  645. );
  646. }
  647. list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix);
  648. if ($storage) {
  649. $result = $storage->hash($type, $internalPath, $raw);
  650. $result = \OC_FileProxy::runPostProxies('hash', $absolutePath, $result);
  651. return $result;
  652. }
  653. }
  654. return null;
  655. }
  656. public function free_space($path = '/') {
  657. $this->assertPathLength($path);
  658. return $this->basicOperation('free_space', $path);
  659. }
  660. /**
  661. * abstraction layer for basic filesystem functions: wrapper for \OC\Files\Storage\Storage
  662. *
  663. * @param string $operation
  664. * @param string $path
  665. * @param array $hooks (optional)
  666. * @param mixed $extraParam (optional)
  667. * @return mixed
  668. *
  669. * This method takes requests for basic filesystem functions (e.g. reading & writing
  670. * files), processes hooks and proxies, sanitises paths, and finally passes them on to
  671. * \OC\Files\Storage\Storage for delegation to a storage backend for execution
  672. */
  673. private function basicOperation($operation, $path, $hooks = array(), $extraParam = null) {
  674. $postFix = (substr($path, -1, 1) === '/') ? '/' : '';
  675. $absolutePath = Filesystem::normalizePath($this->getAbsolutePath($path));
  676. if (\OC_FileProxy::runPreProxies($operation, $absolutePath, $extraParam)
  677. and Filesystem::isValidPath($path)
  678. and !Filesystem::isFileBlacklisted($path)
  679. ) {
  680. $path = $this->getRelativePath($absolutePath);
  681. if ($path == null) {
  682. return false;
  683. }
  684. $run = $this->runHooks($hooks, $path);
  685. list($storage, $internalPath) = Filesystem::resolvePath($absolutePath . $postFix);
  686. if ($run and $storage) {
  687. if (!is_null($extraParam)) {
  688. $result = $storage->$operation($internalPath, $extraParam);
  689. } else {
  690. $result = $storage->$operation($internalPath);
  691. }
  692. $result = \OC_FileProxy::runPostProxies($operation, $this->getAbsolutePath($path), $result);
  693. if ($this->shouldEmitHooks($path) && $result !== false) {
  694. if ($operation != 'fopen') { //no post hooks for fopen, the file stream is still open
  695. $this->runHooks($hooks, $path, true);
  696. }
  697. }
  698. return $result;
  699. }
  700. }
  701. return null;
  702. }
  703. /**
  704. * get the path relative to the default root for hook usage
  705. *
  706. * @param string $path
  707. * @return string
  708. */
  709. private function getHookPath($path) {
  710. if (!Filesystem::getView()) {
  711. return $path;
  712. }
  713. return Filesystem::getView()->getRelativePath($this->getAbsolutePath($path));
  714. }
  715. private function shouldEmitHooks($path = '') {
  716. if ($path && Cache\Scanner::isPartialFile($path)) {
  717. return false;
  718. }
  719. if (!Filesystem::$loaded) {
  720. return false;
  721. }
  722. $defaultRoot = Filesystem::getRoot();
  723. if ($this->fakeRoot === $defaultRoot) {
  724. return true;
  725. }
  726. return (strlen($this->fakeRoot) > strlen($defaultRoot)) && (substr($this->fakeRoot, 0, strlen($defaultRoot) + 1) === $defaultRoot . '/');
  727. }
  728. /**
  729. * @param string[] $hooks
  730. * @param string $path
  731. * @param bool $post
  732. * @return bool
  733. */
  734. private function runHooks($hooks, $path, $post = false) {
  735. $path = $this->getHookPath($path);
  736. $prefix = ($post) ? 'post_' : '';
  737. $run = true;
  738. if ($this->shouldEmitHooks($path)) {
  739. foreach ($hooks as $hook) {
  740. // manually triger updater hooks to ensure they are called first
  741. if ($post) {
  742. if ($hook == 'write') {
  743. Updater::writeHook(array('path' => $path));
  744. } elseif ($hook == 'touch') {
  745. Updater::touchHook(array('path' => $path));
  746. } else if ($hook == 'delete') {
  747. Updater::deleteHook(array('path' => $path));
  748. }
  749. }
  750. if ($hook != 'read') {
  751. \OC_Hook::emit(
  752. Filesystem::CLASSNAME,
  753. $prefix . $hook,
  754. array(
  755. Filesystem::signal_param_run => &$run,
  756. Filesystem::signal_param_path => $path
  757. )
  758. );
  759. } elseif (!$post) {
  760. \OC_Hook::emit(
  761. Filesystem::CLASSNAME,
  762. $prefix . $hook,
  763. array(
  764. Filesystem::signal_param_path => $path
  765. )
  766. );
  767. }
  768. }
  769. }
  770. return $run;
  771. }
  772. /**
  773. * check if a file or folder has been updated since $time
  774. *
  775. * @param string $path
  776. * @param int $time
  777. * @return bool
  778. */
  779. public function hasUpdated($path, $time) {
  780. return $this->basicOperation('hasUpdated', $path, array(), $time);
  781. }
  782. /**
  783. * get the filesystem info
  784. *
  785. * @param string $path
  786. * @param boolean|string $includeMountPoints true to add mountpoint sizes,
  787. * 'ext' to add only ext storage mount point sizes. Defaults to true.
  788. * defaults to true
  789. * @return \OC\Files\FileInfo|false
  790. */
  791. public function getFileInfo($path, $includeMountPoints = true) {
  792. $this->assertPathLength($path);
  793. $data = array();
  794. if (!Filesystem::isValidPath($path)) {
  795. return $data;
  796. }
  797. $path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
  798. $mount = Filesystem::getMountManager()->find($path);
  799. $storage = $mount->getStorage();
  800. $internalPath = $mount->getInternalPath($path);
  801. $data = null;
  802. if ($storage) {
  803. $cache = $storage->getCache($internalPath);
  804. if (!$cache->inCache($internalPath)) {
  805. if (!$storage->file_exists($internalPath)) {
  806. return false;
  807. }
  808. $scanner = $storage->getScanner($internalPath);
  809. $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
  810. } else {
  811. $watcher = $storage->getWatcher($internalPath);
  812. $data = $watcher->checkUpdate($internalPath);
  813. }
  814. if (!is_array($data)) {
  815. $data = $cache->get($internalPath);
  816. }
  817. if ($data and isset($data['fileid'])) {
  818. if ($data['permissions'] === 0) {
  819. $data['permissions'] = $storage->getPermissions($data['path']);
  820. $cache->update($data['fileid'], array('permissions' => $data['permissions']));
  821. }
  822. if ($includeMountPoints and $data['mimetype'] === 'httpd/unix-directory') {
  823. //add the sizes of other mount points to the folder
  824. $extOnly = ($includeMountPoints === 'ext');
  825. $mountPoints = Filesystem::getMountPoints($path);
  826. foreach ($mountPoints as $mountPoint) {
  827. $subStorage = Filesystem::getStorage($mountPoint);
  828. if ($subStorage) {
  829. // exclude shared storage ?
  830. if ($extOnly && $subStorage instanceof \OC\Files\Storage\Shared) {
  831. continue;
  832. }
  833. $subCache = $subStorage->getCache('');
  834. $rootEntry = $subCache->get('');
  835. $data['size'] += isset($rootEntry['size']) ? $rootEntry['size'] : 0;
  836. }
  837. }
  838. }
  839. }
  840. }
  841. if (!$data) {
  842. return false;
  843. }
  844. if ($mount instanceof MoveableMount && $internalPath === '') {
  845. $data['permissions'] |= \OCP\PERMISSION_DELETE | \OCP\PERMISSION_UPDATE;
  846. }
  847. $data = \OC_FileProxy::runPostProxies('getFileInfo', $path, $data);
  848. return new FileInfo($path, $storage, $internalPath, $data);
  849. }
  850. /**
  851. * get the content of a directory
  852. *
  853. * @param string $directory path under datadirectory
  854. * @param string $mimetype_filter limit returned content to this mimetype or mimepart
  855. * @return FileInfo[]
  856. */
  857. public function getDirectoryContent($directory, $mimetype_filter = '') {
  858. $this->assertPathLength($directory);
  859. $result = array();
  860. if (!Filesystem::isValidPath($directory)) {
  861. return $result;
  862. }
  863. $path = Filesystem::normalizePath($this->fakeRoot . '/' . $directory);
  864. list($storage, $internalPath) = Filesystem::resolvePath($path);
  865. if ($storage) {
  866. $cache = $storage->getCache($internalPath);
  867. $user = \OC_User::getUser();
  868. if ($cache->getStatus($internalPath) < Cache\Cache::COMPLETE) {
  869. $scanner = $storage->getScanner($internalPath);
  870. $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
  871. } else {
  872. $watcher = $storage->getWatcher($internalPath);
  873. $watcher->checkUpdate($internalPath);
  874. }
  875. $folderId = $cache->getId($internalPath);
  876. /**
  877. * @var \OC\Files\FileInfo[] $files
  878. */
  879. $files = array();
  880. $contents = $cache->getFolderContents($internalPath, $folderId); //TODO: mimetype_filter
  881. foreach ($contents as $content) {
  882. if ($content['permissions'] === 0) {
  883. $content['permissions'] = $storage->getPermissions($content['path']);
  884. $cache->update($content['fileid'], array('permissions' => $content['permissions']));
  885. }
  886. $files[] = new FileInfo($path . '/' . $content['name'], $storage, $content['path'], $content);
  887. }
  888. //add a folder for any mountpoint in this directory and add the sizes of other mountpoints to the folders
  889. $mounts = Filesystem::getMountManager()->findIn($path);
  890. $dirLength = strlen($path);
  891. foreach ($mounts as $mount) {
  892. $mountPoint = $mount->getMountPoint();
  893. $subStorage = Filesystem::getStorage($mountPoint);
  894. if ($subStorage) {
  895. $subCache = $subStorage->getCache('');
  896. if ($subCache->getStatus('') === Cache\Cache::NOT_FOUND) {
  897. $subScanner = $subStorage->getScanner('');
  898. $subScanner->scanFile('');
  899. }
  900. $rootEntry = $subCache->get('');
  901. if ($rootEntry) {
  902. $relativePath = trim(substr($mountPoint, $dirLength), '/');
  903. if ($pos = strpos($relativePath, '/')) {
  904. //mountpoint inside subfolder add size to the correct folder
  905. $entryName = substr($relativePath, 0, $pos);
  906. foreach ($files as &$entry) {
  907. if ($entry['name'] === $entryName) {
  908. $entry['size'] += $rootEntry['size'];
  909. }
  910. }
  911. } else { //mountpoint in this folder, add an entry for it
  912. $rootEntry['name'] = $relativePath;
  913. $rootEntry['type'] = $rootEntry['mimetype'] === 'httpd/unix-directory' ? 'dir' : 'file';
  914. $permissions = $rootEntry['permissions'];
  915. // do not allow renaming/deleting the mount point if they are not shared files/folders
  916. // for shared files/folders we use the permissions given by the owner
  917. if ($mount instanceof MoveableMount) {
  918. $rootEntry['permissions'] = $permissions | \OCP\PERMISSION_UPDATE | \OCP\PERMISSION_DELETE;
  919. } else {
  920. $rootEntry['permissions'] = $permissions & (\OCP\PERMISSION_ALL - (\OCP\PERMISSION_UPDATE | \OCP\PERMISSION_DELETE));
  921. }
  922. //remove any existing entry with the same name
  923. foreach ($files as $i => $file) {
  924. if ($file['name'] === $rootEntry['name']) {
  925. unset($files[$i]);
  926. break;
  927. }
  928. }
  929. $rootEntry['path'] = substr($path . '/' . $rootEntry['name'], strlen($user) + 2); // full path without /$user/
  930. $files[] = new FileInfo($path . '/' . $rootEntry['name'], $subStorage, '', $rootEntry);
  931. }
  932. }
  933. }
  934. }
  935. if ($mimetype_filter) {
  936. foreach ($files as $file) {
  937. if (strpos($mimetype_filter, '/')) {
  938. if ($file['mimetype'] === $mimetype_filter) {
  939. $result[] = $file;
  940. }
  941. } else {
  942. if ($file['mimepart'] === $mimetype_filter) {
  943. $result[] = $file;
  944. }
  945. }
  946. }
  947. } else {
  948. $result = $files;
  949. }
  950. }
  951. return $result;
  952. }
  953. /**
  954. * change file metadata
  955. *
  956. * @param string $path
  957. * @param array|\OCP\Files\FileInfo $data
  958. * @return int
  959. *
  960. * returns the fileid of the updated file
  961. */
  962. public function putFileInfo($path, $data) {
  963. $this->assertPathLength($path);
  964. if ($data instanceof FileInfo) {
  965. $data = $data->getData();
  966. }
  967. $path = Filesystem::normalizePath($this->fakeRoot . '/' . $path);
  968. /**
  969. * @var \OC\Files\Storage\Storage $storage
  970. * @var string $internalPath
  971. */
  972. list($storage, $internalPath) = Filesystem::resolvePath($path);
  973. if ($storage) {
  974. $cache = $storage->getCache($path);
  975. if (!$cache->inCache($internalPath)) {
  976. $scanner = $storage->getScanner($internalPath);
  977. $scanner->scan($internalPath, Cache\Scanner::SCAN_SHALLOW);
  978. }
  979. return $cache->put($internalPath, $data);
  980. } else {
  981. return -1;
  982. }
  983. }
  984. /**
  985. * search for files with the name matching $query
  986. *
  987. * @param string $query
  988. * @return FileInfo[]
  989. */
  990. public function search($query) {
  991. return $this->searchCommon('%' . $query . '%', 'search');
  992. }
  993. /**
  994. * search for files by mimetype
  995. *
  996. * @param string $mimetype
  997. * @return FileInfo[]
  998. */
  999. public function searchByMime($mimetype) {
  1000. return $this->searchCommon($mimetype, 'searchByMime');
  1001. }
  1002. /**
  1003. * @param string $query
  1004. * @param string $method
  1005. * @return FileInfo[]
  1006. */
  1007. private function searchCommon($query, $method) {
  1008. $files = array();
  1009. $rootLength = strlen($this->fakeRoot);
  1010. $mountPoint = Filesystem::getMountPoint($this->fakeRoot);
  1011. $storage = Filesystem::getStorage($mountPoint);
  1012. if ($storage) {
  1013. $cache = $storage->getCache('');
  1014. $results = $cache->$method($query);
  1015. foreach ($results as $result) {
  1016. if (substr($mountPoint . $result['path'], 0, $rootLength + 1) === $this->fakeRoot . '/') {
  1017. $internalPath = $result['path'];
  1018. $path = $mountPoint . $result['path'];
  1019. $result['path'] = substr($mountPoint . $result['path'], $rootLength);
  1020. $files[] = new FileInfo($path, $storage, $internalPath, $result);
  1021. }
  1022. }
  1023. $mountPoints = Filesystem::getMountPoints($this->fakeRoot);
  1024. foreach ($mountPoints as $mountPoint) {
  1025. $storage = Filesystem::getStorage($mountPoint);
  1026. if ($storage) {
  1027. $cache = $storage->getCache('');
  1028. $relativeMountPoint = substr($mountPoint, $rootLength);
  1029. $results = $cache->$method($query);
  1030. if ($results) {
  1031. foreach ($results as $result) {
  1032. $internalPath = $result['path'];
  1033. $result['path'] = rtrim($relativeMountPoint . $result['path'], '/');
  1034. $path = rtrim($mountPoint . $internalPath, '/');
  1035. $files[] = new FileInfo($path, $storage, $internalPath, $result);
  1036. }
  1037. }
  1038. }
  1039. }
  1040. }
  1041. return $files;
  1042. }
  1043. /**
  1044. * Get the owner for a file or folder
  1045. *
  1046. * @param string $path
  1047. * @return string
  1048. */
  1049. public function getOwner($path) {
  1050. return $this->basicOperation('getOwner', $path);
  1051. }
  1052. /**
  1053. * get the ETag for a file or folder
  1054. *
  1055. * @param string $path
  1056. * @return string
  1057. */
  1058. public function getETag($path) {
  1059. /**
  1060. * @var Storage\Storage $storage
  1061. * @var string $internalPath
  1062. */
  1063. list($storage, $internalPath) = $this->resolvePath($path);
  1064. if ($storage) {
  1065. return $storage->getETag($internalPath);
  1066. } else {
  1067. return null;
  1068. }
  1069. }
  1070. /**
  1071. * Get the path of a file by id, relative to the view
  1072. *
  1073. * Note that the resulting path is not guarantied to be unique for the id, multiple paths can point to the same file
  1074. *
  1075. * @param int $id
  1076. * @return string|null
  1077. */
  1078. public function getPath($id) {
  1079. $manager = Filesystem::getMountManager();
  1080. $mounts = $manager->findIn($this->fakeRoot);
  1081. $mounts[] = $manager->find($this->fakeRoot);
  1082. // reverse the array so we start with the storage this view is in
  1083. // which is the most likely to contain the file we're looking for
  1084. $mounts = array_reverse($mounts);
  1085. foreach ($mounts as $mount) {
  1086. /**
  1087. * @var \OC\Files\Mount\Mount $mount
  1088. */
  1089. if ($mount->getStorage()) {
  1090. $cache = $mount->getStorage()->getCache();
  1091. $internalPath = $cache->getPathById($id);
  1092. if (is_string($internalPath)) {
  1093. $fullPath = $mount->getMountPoint() . $internalPath;
  1094. if (!is_null($path = $this->getRelativePath($fullPath))) {
  1095. return $path;
  1096. }
  1097. }
  1098. }
  1099. }
  1100. return null;
  1101. }
  1102. private function assertPathLength($path) {
  1103. $maxLen = min(PHP_MAXPATHLEN, 4000);
  1104. $pathLen = strlen($path);
  1105. if ($pathLen > $maxLen) {
  1106. throw new \OCP\Files\InvalidPathException("Path length($pathLen) exceeds max path length($maxLen): $path");
  1107. }
  1108. }
  1109. /**
  1110. * check if it is allowed to move a mount point to a given target.
  1111. * It is not allowed to move a mount point into a different mount point
  1112. *
  1113. * @param string $target path
  1114. * @return boolean
  1115. */
  1116. private function isTargetAllowed($target) {
  1117. $result = false;
  1118. list($targetStorage,) = \OC\Files\Filesystem::resolvePath($target);
  1119. if ($targetStorage->instanceOfStorage('\OCP\Files\IHomeStorage')) {
  1120. $result = true;
  1121. } else {
  1122. \OCP\Util::writeLog('files',
  1123. 'It is not allowed to move one mount point into another one',
  1124. \OCP\Util::DEBUG);
  1125. }
  1126. return $result;
  1127. }
  1128. }