filesystemview.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  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){
  46. $path='/';
  47. }
  48. if($path[0]!=='/'){
  49. $path='/'.$path;
  50. }
  51. return $this->fakeRoot.$path;
  52. }
  53. /**
  54. * change the root to a fake toor
  55. * @param string fakeRoot
  56. * @return bool
  57. */
  58. public function chroot($fakeRoot) {
  59. if(!$fakeRoot==''){
  60. if($fakeRoot[0]!=='/') {
  61. $fakeRoot='/'.$fakeRoot;
  62. }
  63. }
  64. $this->fakeRoot=$fakeRoot;
  65. }
  66. /**
  67. * get the fake root
  68. * @return string
  69. */
  70. public function getRoot() {
  71. return $this->fakeRoot;
  72. }
  73. /**
  74. * get the part of the path relative to the mountpoint of the storage it's stored in
  75. * @param string path
  76. * @return bool
  77. */
  78. public function getInternalPath($path) {
  79. if (!isset($this->internal_path_cache[$path])) {
  80. $this->internal_path_cache[$path] = OC_Filesystem::getInternalPath($this->getAbsolutePath($path));
  81. }
  82. return $this->internal_path_cache[$path];
  83. }
  84. /**
  85. * get path relative to the root of the view
  86. * @param string path
  87. * @return string
  88. */
  89. public function getRelativePath($path) {
  90. if($this->fakeRoot=='') {
  91. return $path;
  92. }
  93. if(strpos($path, $this->fakeRoot)!==0) {
  94. return null;
  95. }else{
  96. $path=substr($path, strlen($this->fakeRoot));
  97. if(strlen($path)===0){
  98. return '/';
  99. }else{
  100. return $path;
  101. }
  102. }
  103. }
  104. /**
  105. * get the storage object for a path
  106. * @param string path
  107. * @return OC_Filestorage
  108. */
  109. public function getStorage($path) {
  110. if (!isset($this->storage_cache[$path])) {
  111. $this->storage_cache[$path] = OC_Filesystem::getStorage($this->getAbsolutePath($path));
  112. }
  113. return $this->storage_cache[$path];
  114. }
  115. /**
  116. * get the mountpoint of the storage object for a path
  117. ( 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
  118. *
  119. * @param string path
  120. * @return string
  121. */
  122. public function getMountPoint($path) {
  123. return OC_Filesystem::getMountPoint($this->getAbsolutePath($path));
  124. }
  125. /**
  126. * return the path to a local version of the file
  127. * 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
  128. * @param string path
  129. * @return string
  130. */
  131. public function getLocalFile($path) {
  132. $parent=substr($path, 0, strrpos($path,'/'));
  133. if(OC_Filesystem::isValidPath($parent) and $storage=$this->getStorage($path)) {
  134. return $storage->getLocalFile($this->getInternalPath($path));
  135. }
  136. }
  137. /**
  138. * @param string path
  139. * @return string
  140. */
  141. public function getLocalFolder($path) {
  142. $parent=substr($path, 0, strrpos($path,'/'));
  143. if(OC_Filesystem::isValidPath($parent) and $storage=$this->getStorage($path)) {
  144. return $storage->getLocalFolder($this->getInternalPath($path));
  145. }
  146. }
  147. /**
  148. * the following functions operate with arguments and return values identical
  149. * to those of their PHP built-in equivalents. Mostly they are merely wrappers
  150. * for OC_Filestorage via basicOperation().
  151. */
  152. public function mkdir($path) {
  153. return $this->basicOperation('mkdir', $path, array('create', 'write'));
  154. }
  155. public function rmdir($path) {
  156. return $this->basicOperation('rmdir', $path, array('delete'));
  157. }
  158. public function opendir($path) {
  159. return $this->basicOperation('opendir', $path, array('read'));
  160. }
  161. public function readdir($handle) {
  162. $fsLocal= new OC_Filestorage_Local( array( 'datadir' => '/' ) );
  163. return $fsLocal->readdir( $handle );
  164. }
  165. public function is_dir($path) {
  166. if($path=='/'){
  167. return true;
  168. }
  169. return $this->basicOperation('is_dir', $path);
  170. }
  171. public function is_file($path) {
  172. if($path=='/'){
  173. return false;
  174. }
  175. return $this->basicOperation('is_file', $path);
  176. }
  177. public function stat($path) {
  178. return $this->basicOperation('stat', $path);
  179. }
  180. public function filetype($path) {
  181. return $this->basicOperation('filetype', $path);
  182. }
  183. public function filesize($path) {
  184. return $this->basicOperation('filesize', $path);
  185. }
  186. public function readfile($path) {
  187. @ob_end_clean();
  188. $handle=$this->fopen($path, 'rb');
  189. if ($handle) {
  190. $chunkSize = 8192;// 8 MB chunks
  191. while (!feof($handle)) {
  192. echo fread($handle, $chunkSize);
  193. flush();
  194. }
  195. $size=$this->filesize($path);
  196. return $size;
  197. }
  198. return false;
  199. }
  200. /**
  201. * @deprecated Replaced by isReadable() as part of CRUDS
  202. */
  203. public function is_readable($path){
  204. return $this->basicOperation('isReadable',$path);
  205. }
  206. /**
  207. * @deprecated Replaced by isCreatable(), isUpdatable(), isDeletable() as part of CRUDS
  208. */
  209. public function is_writable($path){
  210. return $this->basicOperation('isUpdatable',$path);
  211. }
  212. public function isCreatable($path) {
  213. return $this->basicOperation('isCreatable', $path);
  214. }
  215. public function isReadable($path) {
  216. return $this->basicOperation('isReadable', $path);
  217. }
  218. public function isUpdatable($path) {
  219. return $this->basicOperation('isUpdatable', $path);
  220. }
  221. public function isDeletable($path) {
  222. return $this->basicOperation('isDeletable', $path);
  223. }
  224. public function isSharable($path) {
  225. return $this->basicOperation('isSharable', $path);
  226. }
  227. public function file_exists($path) {
  228. if($path=='/'){
  229. return true;
  230. }
  231. return $this->basicOperation('file_exists', $path);
  232. }
  233. public function filectime($path) {
  234. return $this->basicOperation('filectime', $path);
  235. }
  236. public function filemtime($path) {
  237. return $this->basicOperation('filemtime', $path);
  238. }
  239. public function touch($path, $mtime=null) {
  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(!$exists) {
  253. OC_Hook::emit(
  254. OC_Filesystem::CLASSNAME,
  255. OC_Filesystem::signal_create,
  256. array(
  257. OC_Filesystem::signal_param_path => $path,
  258. OC_Filesystem::signal_param_run => &$run
  259. )
  260. );
  261. }
  262. OC_Hook::emit(
  263. OC_Filesystem::CLASSNAME,
  264. OC_Filesystem::signal_write,
  265. array(
  266. OC_Filesystem::signal_param_path => $path,
  267. OC_Filesystem::signal_param_run => &$run
  268. )
  269. );
  270. if(!$run) {
  271. return false;
  272. }
  273. $target=$this->fopen($path, 'w');
  274. if($target) {
  275. $count=OC_Helper::streamCopy($data, $target);
  276. fclose($target);
  277. fclose($data);
  278. if(!$exists) {
  279. OC_Hook::emit(
  280. OC_Filesystem::CLASSNAME,
  281. OC_Filesystem::signal_post_create,
  282. array( OC_Filesystem::signal_param_path => $path)
  283. );
  284. }
  285. OC_Hook::emit(
  286. OC_Filesystem::CLASSNAME,
  287. OC_Filesystem::signal_post_write,
  288. array( OC_Filesystem::signal_param_path => $path)
  289. );
  290. OC_FileProxy::runPostProxies('file_put_contents', $absolutePath, $count);
  291. return $count > 0;
  292. }else{
  293. return false;
  294. }
  295. }
  296. }else{
  297. return $this->basicOperation('file_put_contents', $path, array('create', 'write'), $data);
  298. }
  299. }
  300. public function unlink($path) {
  301. return $this->basicOperation('unlink', $path, array('delete'));
  302. }
  303. public function deleteAll( $directory, $empty = false ) {
  304. return $this->basicOperation( 'deleteAll', $directory, array('delete'), $empty );
  305. }
  306. public function rename($path1, $path2) {
  307. $postFix1=(substr($path1,-1,1)==='/')?'/':'';
  308. $postFix2=(substr($path2,-1,1)==='/')?'/':'';
  309. $absolutePath1 = OC_Filesystem::normalizePath($this->getAbsolutePath($path1));
  310. $absolutePath2 = OC_Filesystem::normalizePath($this->getAbsolutePath($path2));
  311. if(OC_FileProxy::runPreProxies('rename', $absolutePath1, $absolutePath2) and OC_Filesystem::isValidPath($path2)) {
  312. $path1 = $this->getRelativePath($absolutePath1);
  313. $path2 = $this->getRelativePath($absolutePath2);
  314. if($path1 == null or $path2 == null) {
  315. return false;
  316. }
  317. $run=true;
  318. OC_Hook::emit(
  319. OC_Filesystem::CLASSNAME, OC_Filesystem::signal_rename,
  320. array(
  321. OC_Filesystem::signal_param_oldpath => $path1,
  322. OC_Filesystem::signal_param_newpath => $path2,
  323. OC_Filesystem::signal_param_run => &$run
  324. )
  325. );
  326. if($run) {
  327. $mp1 = $this->getMountPoint($path1.$postFix1);
  328. $mp2 = $this->getMountPoint($path2.$postFix2);
  329. if($mp1 == $mp2) {
  330. if($storage = $this->getStorage($path1)) {
  331. $result = $storage->rename($this->getInternalPath($path1.$postFix1), $this->getInternalPath($path2.$postFix2));
  332. }
  333. } else {
  334. $source = $this->fopen($path1.$postFix1, 'r');
  335. $target = $this->fopen($path2.$postFix2, 'w');
  336. $count = OC_Helper::streamCopy($source, $target);
  337. $storage1 = $this->getStorage($path1);
  338. $storage1->unlink($this->getInternalPath($path1.$postFix1));
  339. $result = $count>0;
  340. }
  341. OC_Hook::emit(
  342. OC_Filesystem::CLASSNAME,
  343. OC_Filesystem::signal_post_rename,
  344. array(
  345. OC_Filesystem::signal_param_oldpath => $path1,
  346. OC_Filesystem::signal_param_newpath => $path2
  347. )
  348. );
  349. return $result;
  350. }
  351. }
  352. }
  353. public function copy($path1, $path2) {
  354. $postFix1=(substr($path1,-1,1)==='/')?'/':'';
  355. $postFix2=(substr($path2,-1,1)==='/')?'/':'';
  356. $absolutePath1 = OC_Filesystem::normalizePath($this->getAbsolutePath($path1));
  357. $absolutePath2 = OC_Filesystem::normalizePath($this->getAbsolutePath($path2));
  358. if(OC_FileProxy::runPreProxies('copy', $absolutePath1, $absolutePath2) and OC_Filesystem::isValidPath($path2)) {
  359. $path1 = $this->getRelativePath($absolutePath1);
  360. $path2 = $this->getRelativePath($absolutePath2);
  361. if($path1 == null or $path2 == null) {
  362. return false;
  363. }
  364. $run=true;
  365. OC_Hook::emit(
  366. OC_Filesystem::CLASSNAME,
  367. OC_Filesystem::signal_copy,
  368. array(
  369. OC_Filesystem::signal_param_oldpath => $path1,
  370. OC_Filesystem::signal_param_newpath=>$path2,
  371. OC_Filesystem::signal_param_run => &$run
  372. )
  373. );
  374. $exists=$this->file_exists($path2);
  375. if($run and !$exists) {
  376. OC_Hook::emit(
  377. OC_Filesystem::CLASSNAME,
  378. OC_Filesystem::signal_create,
  379. array(
  380. OC_Filesystem::signal_param_path => $path2,
  381. OC_Filesystem::signal_param_run => &$run
  382. )
  383. );
  384. }
  385. if($run) {
  386. OC_Hook::emit(
  387. OC_Filesystem::CLASSNAME,
  388. OC_Filesystem::signal_write,
  389. array(
  390. OC_Filesystem::signal_param_path => $path2,
  391. OC_Filesystem::signal_param_run => &$run
  392. )
  393. );
  394. }
  395. if($run) {
  396. $mp1=$this->getMountPoint($path1.$postFix1);
  397. $mp2=$this->getMountPoint($path2.$postFix2);
  398. if($mp1 == $mp2){
  399. if($storage = $this->getStorage($path1.$postFix1)) {
  400. $result=$storage->copy($this->getInternalPath($path1.$postFix1), $this->getInternalPath($path2.$postFix2));
  401. }
  402. } else {
  403. $source = $this->fopen($path1.$postFix1, 'r');
  404. $target = $this->fopen($path2.$postFix2, 'w');
  405. $result = OC_Helper::streamCopy($source, $target);
  406. }
  407. OC_Hook::emit(
  408. OC_Filesystem::CLASSNAME,
  409. OC_Filesystem::signal_post_copy,
  410. array(
  411. OC_Filesystem::signal_param_oldpath => $path1,
  412. OC_Filesystem::signal_param_newpath=>$path2
  413. )
  414. );
  415. if(!$exists) {
  416. OC_Hook::emit(
  417. OC_Filesystem::CLASSNAME,
  418. OC_Filesystem::signal_post_create,
  419. array(OC_Filesystem::signal_param_path => $path2)
  420. );
  421. }
  422. OC_Hook::emit(
  423. OC_Filesystem::CLASSNAME,
  424. OC_Filesystem::signal_post_write,
  425. array( OC_Filesystem::signal_param_path => $path2)
  426. );
  427. return $result;
  428. }
  429. }
  430. }
  431. public function fopen($path, $mode) {
  432. $hooks=array();
  433. switch($mode) {
  434. case 'r':
  435. case 'rb':
  436. $hooks[]='read';
  437. break;
  438. case 'r+':
  439. case 'rb+':
  440. case 'w+':
  441. case 'wb+':
  442. case 'x+':
  443. case 'xb+':
  444. case 'a+':
  445. case 'ab+':
  446. $hooks[]='read';
  447. $hooks[]='write';
  448. break;
  449. case 'w':
  450. case 'wb':
  451. case 'x':
  452. case 'xb':
  453. case 'a':
  454. case 'ab':
  455. $hooks[]='write';
  456. break;
  457. default:
  458. OC_Log::write('core','invalid mode ('.$mode.') for '.$path,OC_Log::ERROR);
  459. }
  460. return $this->basicOperation('fopen', $path, $hooks, $mode);
  461. }
  462. public function toTmpFile($path) {
  463. if(OC_Filesystem::isValidPath($path)) {
  464. $source = $this->fopen($path, 'r');
  465. if($source) {
  466. $extension='';
  467. $extOffset=strpos($path, '.');
  468. if($extOffset !== false) {
  469. $extension=substr($path, strrpos($path,'.'));
  470. }
  471. $tmpFile = OC_Helper::tmpFile($extension);
  472. file_put_contents($tmpFile, $source);
  473. return $tmpFile;
  474. }
  475. }
  476. }
  477. public function fromTmpFile($tmpFile, $path) {
  478. if(OC_Filesystem::isValidPath($path)) {
  479. if(!$tmpFile) {
  480. debug_print_backtrace();
  481. }
  482. $source=fopen($tmpFile, 'r');
  483. if($source) {
  484. $this->file_put_contents($path, $source);
  485. unlink($tmpFile);
  486. return true;
  487. } else {
  488. }
  489. } else {
  490. return false;
  491. }
  492. }
  493. public function getMimeType($path) {
  494. return $this->basicOperation('getMimeType', $path);
  495. }
  496. public function hash($type, $path, $raw = false) {
  497. $postFix=(substr($path,-1,1)==='/')?'/':'';
  498. $absolutePath = OC_Filesystem::normalizePath($this->getAbsolutePath($path));
  499. if (OC_FileProxy::runPreProxies('hash', $absolutePath) && OC_Filesystem::isValidPath($path)) {
  500. $path = $this->getRelativePath($absolutePath);
  501. if ($path == null) {
  502. return false;
  503. }
  504. if (OC_Filesystem::$loaded && $this->fakeRoot == OC_Filesystem::getRoot()) {
  505. OC_Hook::emit(
  506. OC_Filesystem::CLASSNAME,
  507. OC_Filesystem::signal_read,
  508. array( OC_Filesystem::signal_param_path => $path)
  509. );
  510. }
  511. if ($storage = $this->getStorage($path.$postFix)) {
  512. $result = $storage->hash($type, $this->getInternalPath($path.$postFix), $raw);
  513. $result = OC_FileProxy::runPostProxies('hash', $absolutePath, $result);
  514. return $result;
  515. }
  516. }
  517. return null;
  518. }
  519. public function free_space($path='/') {
  520. return $this->basicOperation('free_space', $path);
  521. }
  522. /**
  523. * @brief abstraction layer for basic filesystem functions: wrapper for OC_Filestorage
  524. * @param string $operation
  525. * @param string #path
  526. * @param array (optional) hooks
  527. * @param mixed (optional) $extraParam
  528. * @return mixed
  529. *
  530. * This method takes requests for basic filesystem functions (e.g. reading & writing
  531. * files), processes hooks and proxies, sanitises paths, and finally passes them on to
  532. * OC_Filestorage for delegation to a storage backend for execution
  533. */
  534. private function basicOperation($operation, $path, $hooks=array(), $extraParam=null) {
  535. $postFix=(substr($path,-1,1)==='/')?'/':'';
  536. $absolutePath = OC_Filesystem::normalizePath($this->getAbsolutePath($path));
  537. if(OC_FileProxy::runPreProxies($operation, $absolutePath, $extraParam) and OC_Filesystem::isValidPath($path)) {
  538. $path = $this->getRelativePath($absolutePath);
  539. if($path == null) {
  540. return false;
  541. }
  542. $internalPath = $this->getInternalPath($path.$postFix);
  543. $run=$this->runHooks($hooks,$path);
  544. if($run and $storage = $this->getStorage($path.$postFix)) {
  545. if(!is_null($extraParam)) {
  546. $result = $storage->$operation($internalPath, $extraParam);
  547. } else {
  548. $result = $storage->$operation($internalPath);
  549. }
  550. $result = OC_FileProxy::runPostProxies($operation, $this->getAbsolutePath($path), $result);
  551. if(OC_Filesystem::$loaded and $this->fakeRoot==OC_Filesystem::getRoot()) {
  552. if($operation!='fopen') {//no post hooks for fopen, the file stream is still open
  553. $this->runHooks($hooks,$path,true);
  554. }
  555. }
  556. return $result;
  557. }
  558. }
  559. return null;
  560. }
  561. private function runHooks($hooks,$path,$post=false){
  562. $prefix=($post)?'post_':'';
  563. $run=true;
  564. if(OC_Filesystem::$loaded and $this->fakeRoot==OC_Filesystem::getRoot()) {
  565. foreach($hooks as $hook) {
  566. if($hook!='read') {
  567. OC_Hook::emit(
  568. OC_Filesystem::CLASSNAME,
  569. $prefix.$hook,
  570. array(
  571. OC_Filesystem::signal_param_run => &$run,
  572. OC_Filesystem::signal_param_path => $path
  573. )
  574. );
  575. } elseif(!$post) {
  576. OC_Hook::emit(
  577. OC_Filesystem::CLASSNAME,
  578. $prefix.$hook,
  579. array(
  580. OC_Filesystem::signal_param_path => $path
  581. )
  582. );
  583. }
  584. }
  585. }
  586. return $run;
  587. }
  588. /**
  589. * check if a file or folder has been updated since $time
  590. * @param int $time
  591. * @return bool
  592. */
  593. public function hasUpdated($path, $time) {
  594. return $this->basicOperation('hasUpdated', $path, array(), $time);
  595. }
  596. }