PageRenderTime 96ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/filesystemview.php

https://github.com/jlgg/simple_trash
PHP | 658 lines | 510 code | 32 blank | 116 comment | 80 complexity | 88c65a50dd2c612ee4852b23e2dfc0c9 MD5 | raw file
Possible License(s): AGPL-3.0, AGPL-1.0, MPL-2.0-no-copyleft-exception
  1. <?php
  2. /**
  3. * ownCloud
  4. *
  5. * @author Frank Karlitschek
  6. * @copyright 2012 Frank Karlitschek frank@owncloud.org
  7. *
  8. * This library is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
  10. * License as published by the Free Software Foundation; either
  11. * version 3 of the License, or any later version.
  12. *
  13. * This library is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
  17. *
  18. * You should have received a copy of the GNU Affero General Public
  19. * License along with this library. If not, see <http://www.gnu.org/licenses/>.
  20. */
  21. /**
  22. * Class to provide access to ownCloud filesystem via a "view", and methods for
  23. * working with files within that view (e.g. read, write, delete, etc.). Each
  24. * view is restricted to a set of directories via a virtual root. The default view
  25. * uses the currently logged in user's data directory as root (parts of
  26. * OC_Filesystem are merely a wrapper for OC_FilesystemView).
  27. *
  28. * Apps that need to access files outside of the user data folders (to modify files
  29. * belonging to a user other than the one currently logged in, for example) should
  30. * use this class directly rather than using OC_Filesystem, or making use of PHP's
  31. * built-in file manipulation functions. This will ensure all hooks and proxies
  32. * are triggered correctly.
  33. *
  34. * Filesystem functions are not called directly; they are passed to the correct
  35. * OC_Filestorage object
  36. */
  37. class OC_FilesystemView {
  38. private $fakeRoot='';
  39. private $internal_path_cache=array();
  40. private $storage_cache=array();
  41. public function __construct($root) {
  42. $this->fakeRoot=$root;
  43. }
  44. public function getAbsolutePath($path = '/') {
  45. if(!$path || $path[0]!=='/') {
  46. $path='/'.$path;
  47. }
  48. return $this->fakeRoot.$path;
  49. }
  50. /**
  51. * change the root to a fake toor
  52. * @param string fakeRoot
  53. * @return bool
  54. */
  55. public function chroot($fakeRoot) {
  56. if(!$fakeRoot=='') {
  57. if($fakeRoot[0]!=='/') {
  58. $fakeRoot='/'.$fakeRoot;
  59. }
  60. }
  61. $this->fakeRoot=$fakeRoot;
  62. }
  63. /**
  64. * get the fake root
  65. * @return string
  66. */
  67. public function getRoot() {
  68. return $this->fakeRoot;
  69. }
  70. /**
  71. * get the part of the path relative to the mountpoint of the storage it's stored in
  72. * @param string path
  73. * @return bool
  74. */
  75. public function getInternalPath($path) {
  76. if (!isset($this->internal_path_cache[$path])) {
  77. $this->internal_path_cache[$path] = OC_Filesystem::getInternalPath($this->getAbsolutePath($path));
  78. }
  79. return $this->internal_path_cache[$path];
  80. }
  81. /**
  82. * get path relative to the root of the view
  83. * @param string path
  84. * @return string
  85. */
  86. public function getRelativePath($path) {
  87. if($this->fakeRoot=='') {
  88. return $path;
  89. }
  90. if(strpos($path, $this->fakeRoot)!==0) {
  91. return null;
  92. }else{
  93. $path=substr($path, strlen($this->fakeRoot));
  94. if(strlen($path)===0) {
  95. return '/';
  96. }else{
  97. return $path;
  98. }
  99. }
  100. }
  101. /**
  102. * get the storage object for a path
  103. * @param string path
  104. * @return OC_Filestorage
  105. */
  106. public function getStorage($path) {
  107. if (!isset($this->storage_cache[$path])) {
  108. $this->storage_cache[$path] = OC_Filesystem::getStorage($this->getAbsolutePath($path));
  109. }
  110. return $this->storage_cache[$path];
  111. }
  112. /**
  113. * get the mountpoint of the storage object for a path
  114. ( note: because a storage is not always mounted inside the fakeroot, the returned mountpoint is relative to the absolute root of the filesystem and doesn't take the chroot into account
  115. *
  116. * @param string path
  117. * @return string
  118. */
  119. public function getMountPoint($path) {
  120. return OC_Filesystem::getMountPoint($this->getAbsolutePath($path));
  121. }
  122. /**
  123. * return the path to a local version of the file
  124. * we need this because we can't know if a file is stored local or not from outside the filestorage and for some purposes a local file is needed
  125. * @param string path
  126. * @return string
  127. */
  128. public function getLocalFile($path) {
  129. $parent=substr($path, 0, strrpos($path, '/'));
  130. if(OC_Filesystem::isValidPath($parent) and $storage=$this->getStorage($path)) {
  131. return $storage->getLocalFile($this->getInternalPath($path));
  132. }
  133. }
  134. /**
  135. * @param string path
  136. * @return string
  137. */
  138. public function getLocalFolder($path) {
  139. $parent=substr($path, 0, strrpos($path, '/'));
  140. if(OC_Filesystem::isValidPath($parent) and $storage=$this->getStorage($path)) {
  141. return $storage->getLocalFolder($this->getInternalPath($path));
  142. }
  143. }
  144. /**
  145. * the following functions operate with arguments and return values identical
  146. * to those of their PHP built-in equivalents. Mostly they are merely wrappers
  147. * for OC_Filestorage via basicOperation().
  148. */
  149. public function mkdir($path) {
  150. return $this->basicOperation('mkdir', $path, array('create', 'write'));
  151. }
  152. public function rmdir($path) {
  153. return $this->basicOperation('rmdir', $path, array('delete'));
  154. }
  155. public function opendir($path) {
  156. return $this->basicOperation('opendir', $path, array('read'));
  157. }
  158. public function readdir($handle) {
  159. $fsLocal= new OC_Filestorage_Local( array( 'datadir' => '/' ) );
  160. return $fsLocal->readdir( $handle );
  161. }
  162. public function is_dir($path) {
  163. if($path=='/') {
  164. return true;
  165. }
  166. return $this->basicOperation('is_dir', $path);
  167. }
  168. public function is_file($path) {
  169. if($path=='/') {
  170. return false;
  171. }
  172. return $this->basicOperation('is_file', $path);
  173. }
  174. public function stat($path) {
  175. return $this->basicOperation('stat', $path);
  176. }
  177. public function filetype($path) {
  178. return $this->basicOperation('filetype', $path);
  179. }
  180. public function filesize($path) {
  181. return $this->basicOperation('filesize', $path);
  182. }
  183. public function readfile($path) {
  184. OC_Util::obEnd();
  185. $handle=$this->fopen($path, 'rb');
  186. if ($handle) {
  187. $chunkSize = 8192;// 8 MB chunks
  188. while (!feof($handle)) {
  189. echo fread($handle, $chunkSize);
  190. flush();
  191. }
  192. $size=$this->filesize($path);
  193. return $size;
  194. }
  195. return false;
  196. }
  197. /**
  198. * @deprecated Replaced by isReadable() as part of CRUDS
  199. */
  200. public function is_readable($path) {
  201. return $this->basicOperation('isReadable', $path);
  202. }
  203. /**
  204. * @deprecated Replaced by isCreatable(), isUpdatable(), isDeletable() as part of CRUDS
  205. */
  206. public function is_writable($path) {
  207. return $this->basicOperation('isUpdatable', $path);
  208. }
  209. public function isCreatable($path) {
  210. return $this->basicOperation('isCreatable', $path);
  211. }
  212. public function isReadable($path) {
  213. return $this->basicOperation('isReadable', $path);
  214. }
  215. public function isUpdatable($path) {
  216. return $this->basicOperation('isUpdatable', $path);
  217. }
  218. public function isDeletable($path) {
  219. return $this->basicOperation('isDeletable', $path);
  220. }
  221. public function isSharable($path) {
  222. return $this->basicOperation('isSharable', $path);
  223. }
  224. public function file_exists($path) {
  225. if($path=='/') {
  226. return true;
  227. }
  228. return $this->basicOperation('file_exists', $path);
  229. }
  230. public function filectime($path) {
  231. return $this->basicOperation('filectime', $path);
  232. }
  233. public function filemtime($path) {
  234. return $this->basicOperation('filemtime', $path);
  235. }
  236. public function touch($path, $mtime=null) {
  237. if(!is_null($mtime) and !is_numeric($mtime)) {
  238. $mtime = strtotime($mtime);
  239. }
  240. return $this->basicOperation('touch', $path, array('write'), $mtime);
  241. }
  242. public function file_get_contents($path) {
  243. return $this->basicOperation('file_get_contents', $path, array('read'));
  244. }
  245. public function file_put_contents($path, $data) {
  246. if(is_resource($data)) {//not having to deal with streams in file_put_contents makes life easier
  247. $absolutePath = OC_Filesystem::normalizePath($this->getAbsolutePath($path));
  248. if (OC_FileProxy::runPreProxies('file_put_contents', $absolutePath, $data) && OC_Filesystem::isValidPath($path)) {
  249. $path = $this->getRelativePath($absolutePath);
  250. $exists = $this->file_exists($path);
  251. $run = true;
  252. if( $this->fakeRoot==OC_Filesystem::getRoot() ) {
  253. if(!$exists) {
  254. OC_Hook::emit(
  255. OC_Filesystem::CLASSNAME,
  256. OC_Filesystem::signal_create,
  257. array(
  258. OC_Filesystem::signal_param_path => $path,
  259. OC_Filesystem::signal_param_run => &$run
  260. )
  261. );
  262. }
  263. OC_Hook::emit(
  264. OC_Filesystem::CLASSNAME,
  265. OC_Filesystem::signal_write,
  266. array(
  267. OC_Filesystem::signal_param_path => $path,
  268. OC_Filesystem::signal_param_run => &$run
  269. )
  270. );
  271. }
  272. if(!$run) {
  273. return false;
  274. }
  275. $target=$this->fopen($path, 'w');
  276. if($target) {
  277. $count=OC_Helper::streamCopy($data, $target);
  278. fclose($target);
  279. fclose($data);
  280. if( $this->fakeRoot==OC_Filesystem::getRoot() ) {
  281. if(!$exists) {
  282. OC_Hook::emit(
  283. OC_Filesystem::CLASSNAME,
  284. OC_Filesystem::signal_post_create,
  285. array( OC_Filesystem::signal_param_path => $path)
  286. );
  287. }
  288. OC_Hook::emit(
  289. OC_Filesystem::CLASSNAME,
  290. OC_Filesystem::signal_post_write,
  291. array( OC_Filesystem::signal_param_path => $path)
  292. );
  293. }
  294. OC_FileProxy::runPostProxies('file_put_contents', $absolutePath, $count);
  295. return $count > 0;
  296. }else{
  297. return false;
  298. }
  299. }
  300. }else{
  301. return $this->basicOperation('file_put_contents', $path, array('create', 'write'), $data);
  302. }
  303. }
  304. public function unlink($path) {
  305. return $this->basicOperation('unlink', $path, array('delete'));
  306. }
  307. public function deleteAll( $directory, $empty = false ) {
  308. return $this->basicOperation( 'deleteAll', $directory, array('delete'), $empty );
  309. }
  310. public function rename($path1, $path2) {
  311. $postFix1=(substr($path1, -1, 1)==='/')?'/':'';
  312. $postFix2=(substr($path2, -1, 1)==='/')?'/':'';
  313. $absolutePath1 = OC_Filesystem::normalizePath($this->getAbsolutePath($path1));
  314. $absolutePath2 = OC_Filesystem::normalizePath($this->getAbsolutePath($path2));
  315. if(OC_FileProxy::runPreProxies('rename', $absolutePath1, $absolutePath2) and OC_Filesystem::isValidPath($path2)) {
  316. $path1 = $this->getRelativePath($absolutePath1);
  317. $path2 = $this->getRelativePath($absolutePath2);
  318. if($path1 == null or $path2 == null) {
  319. return false;
  320. }
  321. $run=true;
  322. if( $this->fakeRoot==OC_Filesystem::getRoot() ) {
  323. OC_Hook::emit(
  324. OC_Filesystem::CLASSNAME, OC_Filesystem::signal_rename,
  325. array(
  326. OC_Filesystem::signal_param_oldpath => $path1,
  327. OC_Filesystem::signal_param_newpath => $path2,
  328. OC_Filesystem::signal_param_run => &$run
  329. )
  330. );
  331. }
  332. if($run) {
  333. $mp1 = $this->getMountPoint($path1.$postFix1);
  334. $mp2 = $this->getMountPoint($path2.$postFix2);
  335. if($mp1 == $mp2) {
  336. if($storage = $this->getStorage($path1)) {
  337. $result = $storage->rename($this->getInternalPath($path1.$postFix1), $this->getInternalPath($path2.$postFix2));
  338. }
  339. } else {
  340. $source = $this->fopen($path1.$postFix1, 'r');
  341. $target = $this->fopen($path2.$postFix2, 'w');
  342. $count = OC_Helper::streamCopy($source, $target);
  343. $storage1 = $this->getStorage($path1);
  344. $storage1->unlink($this->getInternalPath($path1.$postFix1));
  345. $result = $count>0;
  346. }
  347. if( $this->fakeRoot==OC_Filesystem::getRoot() ) {
  348. OC_Hook::emit(
  349. OC_Filesystem::CLASSNAME,
  350. OC_Filesystem::signal_post_rename,
  351. array(
  352. OC_Filesystem::signal_param_oldpath => $path1,
  353. OC_Filesystem::signal_param_newpath => $path2
  354. )
  355. );
  356. }
  357. return $result;
  358. }
  359. }
  360. }
  361. public function copy($path1, $path2) {
  362. $postFix1=(substr($path1, -1, 1)==='/')?'/':'';
  363. $postFix2=(substr($path2, -1, 1)==='/')?'/':'';
  364. $absolutePath1 = OC_Filesystem::normalizePath($this->getAbsolutePath($path1));
  365. $absolutePath2 = OC_Filesystem::normalizePath($this->getAbsolutePath($path2));
  366. if(OC_FileProxy::runPreProxies('copy', $absolutePath1, $absolutePath2) and OC_Filesystem::isValidPath($path2)) {
  367. $path1 = $this->getRelativePath($absolutePath1);
  368. $path2 = $this->getRelativePath($absolutePath2);
  369. if($path1 == null or $path2 == null) {
  370. return false;
  371. }
  372. $run=true;
  373. if( $this->fakeRoot==OC_Filesystem::getRoot() ) {
  374. OC_Hook::emit(
  375. OC_Filesystem::CLASSNAME,
  376. OC_Filesystem::signal_copy,
  377. array(
  378. OC_Filesystem::signal_param_oldpath => $path1,
  379. OC_Filesystem::signal_param_newpath=>$path2,
  380. OC_Filesystem::signal_param_run => &$run
  381. )
  382. );
  383. $exists=$this->file_exists($path2);
  384. if($run and !$exists) {
  385. OC_Hook::emit(
  386. OC_Filesystem::CLASSNAME,
  387. OC_Filesystem::signal_create,
  388. array(
  389. OC_Filesystem::signal_param_path => $path2,
  390. OC_Filesystem::signal_param_run => &$run
  391. )
  392. );
  393. }
  394. if($run) {
  395. OC_Hook::emit(
  396. OC_Filesystem::CLASSNAME,
  397. OC_Filesystem::signal_write,
  398. array(
  399. OC_Filesystem::signal_param_path => $path2,
  400. OC_Filesystem::signal_param_run => &$run
  401. )
  402. );
  403. }
  404. }
  405. if($run) {
  406. $mp1=$this->getMountPoint($path1.$postFix1);
  407. $mp2=$this->getMountPoint($path2.$postFix2);
  408. if($mp1 == $mp2) {
  409. if($storage = $this->getStorage($path1.$postFix1)) {
  410. $result=$storage->copy($this->getInternalPath($path1.$postFix1), $this->getInternalPath($path2.$postFix2));
  411. }
  412. } else {
  413. $source = $this->fopen($path1.$postFix1, 'r');
  414. $target = $this->fopen($path2.$postFix2, 'w');
  415. $result = OC_Helper::streamCopy($source, $target);
  416. }
  417. if( $this->fakeRoot==OC_Filesystem::getRoot() ) {
  418. // If the file to be copied originates within
  419. // the user's data directory
  420. OC_Hook::emit(
  421. OC_Filesystem::CLASSNAME,
  422. OC_Filesystem::signal_post_copy,
  423. array(
  424. OC_Filesystem::signal_param_oldpath => $path1,
  425. OC_Filesystem::signal_param_newpath=>$path2
  426. )
  427. );
  428. if(!$exists) {
  429. OC_Hook::emit(
  430. OC_Filesystem::CLASSNAME,
  431. OC_Filesystem::signal_post_create,
  432. array(OC_Filesystem::signal_param_path => $path2)
  433. );
  434. }
  435. OC_Hook::emit(
  436. OC_Filesystem::CLASSNAME,
  437. OC_Filesystem::signal_post_write,
  438. array( OC_Filesystem::signal_param_path => $path2)
  439. );
  440. } else {
  441. // If this is not a normal file copy operation
  442. // and the file originates somewhere else
  443. // (e.g. a version rollback operation), do not
  444. // perform all the other post_write actions
  445. // Update webdav properties
  446. OC_Filesystem::removeETagHook(array("path" => $path2), $this->fakeRoot);
  447. $splitPath2 = explode( '/', $path2 );
  448. // Only cache information about files
  449. // that are being copied from within
  450. // the user files directory. Caching
  451. // other files, like VCS backup files,
  452. // serves no purpose
  453. if ( $splitPath2[1] == 'files' ) {
  454. OC_FileCache_Update::update($path2, $this->fakeRoot);
  455. }
  456. }
  457. return $result;
  458. }
  459. }
  460. }
  461. public function fopen($path, $mode) {
  462. $hooks=array();
  463. switch($mode) {
  464. case 'r':
  465. case 'rb':
  466. $hooks[]='read';
  467. break;
  468. case 'r+':
  469. case 'rb+':
  470. case 'w+':
  471. case 'wb+':
  472. case 'x+':
  473. case 'xb+':
  474. case 'a+':
  475. case 'ab+':
  476. $hooks[]='read';
  477. $hooks[]='write';
  478. break;
  479. case 'w':
  480. case 'wb':
  481. case 'x':
  482. case 'xb':
  483. case 'a':
  484. case 'ab':
  485. $hooks[]='write';
  486. break;
  487. default:
  488. OC_Log::write('core', 'invalid mode ('.$mode.') for '.$path, OC_Log::ERROR);
  489. }
  490. return $this->basicOperation('fopen', $path, $hooks, $mode);
  491. }
  492. public function toTmpFile($path) {
  493. if(OC_Filesystem::isValidPath($path)) {
  494. $source = $this->fopen($path, 'r');
  495. if($source) {
  496. $extension='';
  497. $extOffset=strpos($path, '.');
  498. if($extOffset !== false) {
  499. $extension=substr($path, strrpos($path, '.'));
  500. }
  501. $tmpFile = OC_Helper::tmpFile($extension);
  502. file_put_contents($tmpFile, $source);
  503. return $tmpFile;
  504. }
  505. }
  506. }
  507. public function fromTmpFile($tmpFile, $path) {
  508. if(OC_Filesystem::isValidPath($path)) {
  509. if(!$tmpFile) {
  510. debug_print_backtrace();
  511. }
  512. $source=fopen($tmpFile, 'r');
  513. if($source) {
  514. $this->file_put_contents($path, $source);
  515. unlink($tmpFile);
  516. return true;
  517. } else {
  518. }
  519. } else {
  520. return false;
  521. }
  522. }
  523. public function getMimeType($path) {
  524. return $this->basicOperation('getMimeType', $path);
  525. }
  526. public function hash($type, $path, $raw = false) {
  527. $postFix=(substr($path, -1, 1)==='/')?'/':'';
  528. $absolutePath = OC_Filesystem::normalizePath($this->getAbsolutePath($path));
  529. if (OC_FileProxy::runPreProxies('hash', $absolutePath) && OC_Filesystem::isValidPath($path)) {
  530. $path = $this->getRelativePath($absolutePath);
  531. if ($path == null) {
  532. return false;
  533. }
  534. if (OC_Filesystem::$loaded && $this->fakeRoot == OC_Filesystem::getRoot()) {
  535. OC_Hook::emit(
  536. OC_Filesystem::CLASSNAME,
  537. OC_Filesystem::signal_read,
  538. array( OC_Filesystem::signal_param_path => $path)
  539. );
  540. }
  541. if ($storage = $this->getStorage($path.$postFix)) {
  542. $result = $storage->hash($type, $this->getInternalPath($path.$postFix), $raw);
  543. $result = OC_FileProxy::runPostProxies('hash', $absolutePath, $result);
  544. return $result;
  545. }
  546. }
  547. return null;
  548. }
  549. public function free_space($path='/') {
  550. return $this->basicOperation('free_space', $path);
  551. }
  552. /**
  553. * @brief abstraction layer for basic filesystem functions: wrapper for OC_Filestorage
  554. * @param string $operation
  555. * @param string #path
  556. * @param array (optional) hooks
  557. * @param mixed (optional) $extraParam
  558. * @return mixed
  559. *
  560. * This method takes requests for basic filesystem functions (e.g. reading & writing
  561. * files), processes hooks and proxies, sanitises paths, and finally passes them on to
  562. * OC_Filestorage for delegation to a storage backend for execution
  563. */
  564. private function basicOperation($operation, $path, $hooks=array(), $extraParam=null) {
  565. $postFix=(substr($path, -1, 1)==='/')?'/':'';
  566. $absolutePath = OC_Filesystem::normalizePath($this->getAbsolutePath($path));
  567. if(OC_FileProxy::runPreProxies($operation, $absolutePath, $extraParam) and OC_Filesystem::isValidPath($path)) {
  568. $path = $this->getRelativePath($absolutePath);
  569. if($path == null) {
  570. return false;
  571. }
  572. $internalPath = $this->getInternalPath($path.$postFix);
  573. $run=$this->runHooks($hooks, $path);
  574. if($run and $storage = $this->getStorage($path.$postFix)) {
  575. if(!is_null($extraParam)) {
  576. $result = $storage->$operation($internalPath, $extraParam);
  577. } else {
  578. $result = $storage->$operation($internalPath);
  579. }
  580. $result = OC_FileProxy::runPostProxies($operation, $this->getAbsolutePath($path), $result);
  581. if(OC_Filesystem::$loaded and $this->fakeRoot==OC_Filesystem::getRoot()) {
  582. if($operation!='fopen') {//no post hooks for fopen, the file stream is still open
  583. $this->runHooks($hooks, $path, true);
  584. }
  585. }
  586. return $result;
  587. }
  588. }
  589. return null;
  590. }
  591. private function runHooks($hooks, $path, $post=false) {
  592. $prefix=($post)?'post_':'';
  593. $run=true;
  594. if(OC_Filesystem::$loaded and $this->fakeRoot==OC_Filesystem::getRoot()) {
  595. foreach($hooks as $hook) {
  596. if($hook!='read') {
  597. OC_Hook::emit(
  598. OC_Filesystem::CLASSNAME,
  599. $prefix.$hook,
  600. array(
  601. OC_Filesystem::signal_param_run => &$run,
  602. OC_Filesystem::signal_param_path => $path
  603. )
  604. );
  605. } elseif(!$post) {
  606. OC_Hook::emit(
  607. OC_Filesystem::CLASSNAME,
  608. $prefix.$hook,
  609. array(
  610. OC_Filesystem::signal_param_path => $path
  611. )
  612. );
  613. }
  614. }
  615. }
  616. return $run;
  617. }
  618. /**
  619. * check if a file or folder has been updated since $time
  620. * @param int $time
  621. * @return bool
  622. */
  623. public function hasUpdated($path, $time) {
  624. return $this->basicOperation('hasUpdated', $path, array(), $time);
  625. }
  626. }