PageRenderTime 49ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/private/files/view.php

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