PageRenderTime 50ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/private/files/view.php

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