filesystem.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609
  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. /**
  23. * Class for abstraction of filesystem functions
  24. * This class won't call any filesystem functions for itself but but will pass them to the correct OC_Filestorage object
  25. * this class should also handle all the file permission related stuff
  26. *
  27. * Hooks provided:
  28. * read(path)
  29. * write(path, &run)
  30. * post_write(path)
  31. * create(path, &run) (when a file is created, both create and write will be emited in that order)
  32. * post_create(path)
  33. * delete(path, &run)
  34. * post_delete(path)
  35. * rename(oldpath, newpath, &run)
  36. * post_rename(oldpath, newpath)
  37. * copy(oldpath, newpath, &run) (if the newpath doesn't exists yes, copy, create and write will be emited in that order)
  38. * post_rename(oldpath, newpath)
  39. *
  40. * the &run parameter can be set to false to prevent the operation from occuring
  41. */
  42. class OC_Filesystem{
  43. static private $storages=array();
  44. static private $mounts=array();
  45. static private $loadedUsers=array();
  46. public static $loaded=false;
  47. /**
  48. * @var OC_Filestorage $defaultInstance
  49. */
  50. static private $defaultInstance;
  51. /**
  52. * classname which used for hooks handling
  53. * used as signalclass in OC_Hooks::emit()
  54. */
  55. const CLASSNAME = 'OC_Filesystem';
  56. /**
  57. * signalname emited before file renaming
  58. * @param oldpath
  59. * @param newpath
  60. */
  61. const signal_rename = 'rename';
  62. /**
  63. * signal emited after file renaming
  64. * @param oldpath
  65. * @param newpath
  66. */
  67. const signal_post_rename = 'post_rename';
  68. /**
  69. * signal emited before file/dir creation
  70. * @param path
  71. * @param run changing this flag to false in hook handler will cancel event
  72. */
  73. const signal_create = 'create';
  74. /**
  75. * signal emited after file/dir creation
  76. * @param path
  77. * @param run changing this flag to false in hook handler will cancel event
  78. */
  79. const signal_post_create = 'post_create';
  80. /**
  81. * signal emits before file/dir copy
  82. * @param oldpath
  83. * @param newpath
  84. * @param run changing this flag to false in hook handler will cancel event
  85. */
  86. const signal_copy = 'copy';
  87. /**
  88. * signal emits after file/dir copy
  89. * @param oldpath
  90. * @param newpath
  91. */
  92. const signal_post_copy = 'post_copy';
  93. /**
  94. * signal emits before file/dir save
  95. * @param path
  96. * @param run changing this flag to false in hook handler will cancel event
  97. */
  98. const signal_write = 'write';
  99. /**
  100. * signal emits after file/dir save
  101. * @param path
  102. */
  103. const signal_post_write = 'post_write';
  104. /**
  105. * signal emits when reading file/dir
  106. * @param path
  107. */
  108. const signal_read = 'read';
  109. /**
  110. * signal emits when removing file/dir
  111. * @param path
  112. */
  113. const signal_delete = 'delete';
  114. /**
  115. * parameters definitions for signals
  116. */
  117. const signal_param_path = 'path';
  118. const signal_param_oldpath = 'oldpath';
  119. const signal_param_newpath = 'newpath';
  120. /**
  121. * run - changing this flag to false in hook handler will cancel event
  122. */
  123. const signal_param_run = 'run';
  124. /**
  125. * get the mountpoint of the storage object for a path
  126. ( 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
  127. *
  128. * @param string path
  129. * @return string
  130. */
  131. static public function getMountPoint($path) {
  132. OC_Hook::emit(self::CLASSNAME, 'get_mountpoint', array('path'=>$path));
  133. if(!$path) {
  134. $path='/';
  135. }
  136. if($path[0]!=='/') {
  137. $path='/'.$path;
  138. }
  139. $path=str_replace('//', '/', $path);
  140. $foundMountPoint='';
  141. $mountPoints=array_keys(OC_Filesystem::$mounts);
  142. foreach($mountPoints as $mountpoint) {
  143. if($mountpoint==$path) {
  144. return $mountpoint;
  145. }
  146. if(strpos($path, $mountpoint)===0 and strlen($mountpoint)>strlen($foundMountPoint)) {
  147. $foundMountPoint=$mountpoint;
  148. }
  149. }
  150. return $foundMountPoint;
  151. }
  152. /**
  153. * get the part of the path relative to the mountpoint of the storage it's stored in
  154. * @param string path
  155. * @return bool
  156. */
  157. static public function getInternalPath($path) {
  158. $mountPoint=self::getMountPoint($path);
  159. $internalPath=substr($path, strlen($mountPoint));
  160. return $internalPath;
  161. }
  162. static private function mountPointsLoaded($user) {
  163. return in_array($user, self::$loadedUsers);
  164. }
  165. /**
  166. * get the storage object for a path
  167. * @param string path
  168. * @return OC_Filestorage
  169. */
  170. static public function getStorage($path) {
  171. $user = ltrim(substr($path, 0, strpos($path, '/', 1)), '/');
  172. // check mount points if file was shared from a different user
  173. if ($user != OC_User::getUser() && !self::mountPointsLoaded($user)) {
  174. OC_Util::loadUserMountPoints($user);
  175. self::loadSystemMountPoints($user);
  176. self::$loadedUsers[] = $user;
  177. }
  178. $mountpoint=self::getMountPoint($path);
  179. if($mountpoint) {
  180. if(!isset(OC_Filesystem::$storages[$mountpoint])) {
  181. $mount=OC_Filesystem::$mounts[$mountpoint];
  182. OC_Filesystem::$storages[$mountpoint]=OC_Filesystem::createStorage($mount['class'], $mount['arguments']);
  183. }
  184. return OC_Filesystem::$storages[$mountpoint];
  185. }
  186. }
  187. static private function loadSystemMountPoints($user) {
  188. if(is_file(OC::$SERVERROOT.'/config/mount.php')) {
  189. $mountConfig=include OC::$SERVERROOT.'/config/mount.php';
  190. if(isset($mountConfig['global'])) {
  191. foreach($mountConfig['global'] as $mountPoint=>$options) {
  192. self::mount($options['class'], $options['options'], $mountPoint);
  193. }
  194. }
  195. if(isset($mountConfig['group'])) {
  196. foreach($mountConfig['group'] as $group=>$mounts) {
  197. if(OC_Group::inGroup($user, $group)) {
  198. foreach($mounts as $mountPoint=>$options) {
  199. $mountPoint=self::setUserVars($mountPoint, $user);
  200. foreach($options as &$option) {
  201. $option=self::setUserVars($option, $user);
  202. }
  203. self::mount($options['class'], $options['options'], $mountPoint);
  204. }
  205. }
  206. }
  207. }
  208. if(isset($mountConfig['user'])) {
  209. foreach($mountConfig['user'] as $mountUser=>$mounts) {
  210. if($user==='all' or strtolower($mountUser)===strtolower($user)) {
  211. foreach($mounts as $mountPoint=>$options) {
  212. $mountPoint=self::setUserVars($mountPoint, $user);
  213. foreach($options as &$option) {
  214. $option=self::setUserVars($option, $user);
  215. }
  216. self::mount($options['class'], $options['options'], $mountPoint);
  217. }
  218. }
  219. }
  220. }
  221. $mtime=filemtime(OC::$SERVERROOT.'/config/mount.php');
  222. $previousMTime=OC_Appconfig::getValue('files', 'mountconfigmtime', 0);
  223. if($mtime>$previousMTime) {//mount config has changed, filecache needs to be updated
  224. OC_FileCache::triggerUpdate();
  225. OC_Appconfig::setValue('files', 'mountconfigmtime', $mtime);
  226. }
  227. }
  228. }
  229. static public function init($root, $user = '') {
  230. if(self::$defaultInstance) {
  231. return false;
  232. }
  233. self::$defaultInstance=new OC_FilesystemView($root);
  234. //load custom mount config
  235. if (!isset($user)) {
  236. $user = OC_User::getUser();
  237. }
  238. self::loadSystemMountPoints($user);
  239. self::$loaded=true;
  240. }
  241. /**
  242. * fill in the correct values for $user, and $password placeholders
  243. * @param string intput
  244. * @return string
  245. */
  246. private static function setUserVars($input, $user) {
  247. if (isset($user)) {
  248. return str_replace('$user', $user, $input);
  249. } else {
  250. return str_replace('$user', OC_User::getUser(), $input);
  251. }
  252. }
  253. /**
  254. * get the default filesystem view
  255. * @return OC_FilesystemView
  256. */
  257. static public function getView() {
  258. return self::$defaultInstance;
  259. }
  260. /**
  261. * tear down the filesystem, removing all storage providers
  262. */
  263. static public function tearDown() {
  264. self::$storages=array();
  265. }
  266. /**
  267. * create a new storage of a specific type
  268. * @param string type
  269. * @param array arguments
  270. * @return OC_Filestorage
  271. */
  272. static private function createStorage($class, $arguments) {
  273. if(class_exists($class)) {
  274. try {
  275. return new $class($arguments);
  276. } catch (Exception $exception) {
  277. OC_Log::write('core', $exception->getMessage(), OC_Log::ERROR);
  278. return false;
  279. }
  280. }else{
  281. OC_Log::write('core', 'storage backend '.$class.' not found', OC_Log::ERROR);
  282. return false;
  283. }
  284. }
  285. /**
  286. * change the root to a fake root
  287. * @param string fakeRoot
  288. * @return bool
  289. */
  290. static public function chroot($fakeRoot) {
  291. return self::$defaultInstance->chroot($fakeRoot);
  292. }
  293. /**
  294. * @brief get the relative path of the root data directory for the current user
  295. * @return string
  296. *
  297. * Returns path like /admin/files
  298. */
  299. static public function getRoot() {
  300. return self::$defaultInstance->getRoot();
  301. }
  302. /**
  303. * clear all mounts and storage backends
  304. */
  305. public static function clearMounts() {
  306. self::$mounts=array();
  307. self::$storages=array();
  308. }
  309. /**
  310. * mount an OC_Filestorage in our virtual filesystem
  311. * @param OC_Filestorage storage
  312. * @param string mountpoint
  313. */
  314. static public function mount($class, $arguments, $mountpoint) {
  315. if($mountpoint[0]!='/') {
  316. $mountpoint='/'.$mountpoint;
  317. }
  318. if(substr($mountpoint, -1)!=='/') {
  319. $mountpoint=$mountpoint.'/';
  320. }
  321. self::$mounts[$mountpoint]=array('class'=>$class, 'arguments'=>$arguments);
  322. }
  323. /**
  324. * return the path to a local version of the file
  325. * 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
  326. * @param string path
  327. * @return string
  328. */
  329. static public function getLocalFile($path) {
  330. return self::$defaultInstance->getLocalFile($path);
  331. }
  332. /**
  333. * @param string path
  334. * @return string
  335. */
  336. static public function getLocalFolder($path) {
  337. return self::$defaultInstance->getLocalFolder($path);
  338. }
  339. /**
  340. * return path to file which reflects one visible in browser
  341. * @param string path
  342. * @return string
  343. */
  344. static public function getLocalPath($path) {
  345. $datadir = OC_User::getHome(OC_User::getUser()).'/files';
  346. $newpath = $path;
  347. if (strncmp($newpath, $datadir, strlen($datadir)) == 0) {
  348. $newpath = substr($path, strlen($datadir));
  349. }
  350. return $newpath;
  351. }
  352. /**
  353. * check if the requested path is valid
  354. * @param string path
  355. * @return bool
  356. */
  357. static public function isValidPath($path) {
  358. $path = self::normalizePath($path);
  359. if(!$path || $path[0]!=='/') {
  360. $path='/'.$path;
  361. }
  362. if(strstr($path, '/../') || strrchr($path, '/') === '/..' ) {
  363. return false;
  364. }
  365. if(self::isFileBlacklisted($path)) {
  366. return false;
  367. }
  368. return true;
  369. }
  370. /**
  371. * checks if a file is blacklsited for storage in the filesystem
  372. * Listens to write and rename hooks
  373. * @param array $data from hook
  374. */
  375. static public function isBlacklisted($data) {
  376. if (isset($data['path'])) {
  377. $path = $data['path'];
  378. } else if (isset($data['newpath'])) {
  379. $path = $data['newpath'];
  380. }
  381. if (isset($path)) {
  382. $data['run'] = !self::isFileBlacklisted($path);
  383. }
  384. }
  385. static public function isFileBlacklisted($path) {
  386. $blacklist = array('.htaccess');
  387. $filename = strtolower(basename($path));
  388. return in_array($filename, $blacklist);
  389. }
  390. /**
  391. * following functions are equivilent to their php buildin equivilents for arguments/return values.
  392. */
  393. static public function mkdir($path) {
  394. return self::$defaultInstance->mkdir($path);
  395. }
  396. static public function rmdir($path) {
  397. return self::$defaultInstance->rmdir($path);
  398. }
  399. static public function opendir($path) {
  400. return self::$defaultInstance->opendir($path);
  401. }
  402. static public function readdir($path) {
  403. return self::$defaultInstance->readdir($path);
  404. }
  405. static public function is_dir($path) {
  406. return self::$defaultInstance->is_dir($path);
  407. }
  408. static public function is_file($path) {
  409. return self::$defaultInstance->is_file($path);
  410. }
  411. static public function stat($path) {
  412. return self::$defaultInstance->stat($path);
  413. }
  414. static public function filetype($path) {
  415. return self::$defaultInstance->filetype($path);
  416. }
  417. static public function filesize($path) {
  418. return self::$defaultInstance->filesize($path);
  419. }
  420. static public function readfile($path) {
  421. return self::$defaultInstance->readfile($path);
  422. }
  423. /**
  424. * @deprecated Replaced by isReadable() as part of CRUDS
  425. */
  426. static public function is_readable($path) {
  427. return self::$defaultInstance->is_readable($path);
  428. }
  429. /**
  430. * @deprecated Replaced by isCreatable(), isUpdatable(), isDeletable() as part of CRUDS
  431. */
  432. static public function is_writable($path) {
  433. return self::$defaultInstance->is_writable($path);
  434. }
  435. static public function isCreatable($path) {
  436. return self::$defaultInstance->isCreatable($path);
  437. }
  438. static public function isReadable($path) {
  439. return self::$defaultInstance->isReadable($path);
  440. }
  441. static public function isUpdatable($path) {
  442. return self::$defaultInstance->isUpdatable($path);
  443. }
  444. static public function isDeletable($path) {
  445. return self::$defaultInstance->isDeletable($path);
  446. }
  447. static public function isSharable($path) {
  448. return self::$defaultInstance->isSharable($path);
  449. }
  450. static public function file_exists($path) {
  451. return self::$defaultInstance->file_exists($path);
  452. }
  453. static public function filectime($path) {
  454. return self::$defaultInstance->filectime($path);
  455. }
  456. static public function filemtime($path) {
  457. return self::$defaultInstance->filemtime($path);
  458. }
  459. static public function touch($path, $mtime=null) {
  460. return self::$defaultInstance->touch($path, $mtime);
  461. }
  462. static public function file_get_contents($path) {
  463. return self::$defaultInstance->file_get_contents($path);
  464. }
  465. static public function file_put_contents($path, $data) {
  466. return self::$defaultInstance->file_put_contents($path, $data);
  467. }
  468. static public function unlink($path) {
  469. return self::$defaultInstance->unlink($path);
  470. }
  471. static public function rename($path1, $path2) {
  472. return self::$defaultInstance->rename($path1, $path2);
  473. }
  474. static public function copy($path1, $path2) {
  475. return self::$defaultInstance->copy($path1, $path2);
  476. }
  477. static public function fopen($path, $mode) {
  478. return self::$defaultInstance->fopen($path, $mode);
  479. }
  480. static public function toTmpFile($path) {
  481. return self::$defaultInstance->toTmpFile($path);
  482. }
  483. static public function fromTmpFile($tmpFile, $path) {
  484. return self::$defaultInstance->fromTmpFile($tmpFile, $path);
  485. }
  486. static public function getMimeType($path) {
  487. return self::$defaultInstance->getMimeType($path);
  488. }
  489. static public function hash($type, $path, $raw = false) {
  490. return self::$defaultInstance->hash($type, $path, $raw);
  491. }
  492. static public function free_space($path='/') {
  493. return self::$defaultInstance->free_space($path);
  494. }
  495. static public function search($query) {
  496. return OC_FileCache::search($query);
  497. }
  498. /**
  499. * check if a file or folder has been updated since $time
  500. * @param int $time
  501. * @return bool
  502. */
  503. static public function hasUpdated($path, $time) {
  504. return self::$defaultInstance->hasUpdated($path, $time);
  505. }
  506. static public function removeETagHook($params, $root = false) {
  507. if (isset($params['path'])) {
  508. $path=$params['path'];
  509. } else {
  510. $path=$params['oldpath'];
  511. }
  512. if ($root) { // reduce path to the required part of it (no 'username/files')
  513. $fakeRootView = new OC_FilesystemView($root);
  514. $count = 1;
  515. $path=str_replace(OC_App::getStorage("files")->getAbsolutePath(), "", $fakeRootView->getAbsolutePath($path), $count);
  516. }
  517. $path = self::normalizePath($path);
  518. OC_Connector_Sabre_Node::removeETagPropertyForPath($path);
  519. }
  520. /**
  521. * normalize a path
  522. * @param string path
  523. * @param bool $stripTrailingSlash
  524. * @return string
  525. */
  526. public static function normalizePath($path, $stripTrailingSlash=true) {
  527. if($path=='') {
  528. return '/';
  529. }
  530. //no windows style slashes
  531. $path=str_replace('\\', '/', $path);
  532. //add leading slash
  533. if($path[0]!=='/') {
  534. $path='/'.$path;
  535. }
  536. //remove trainling slash
  537. if($stripTrailingSlash and strlen($path)>1 and substr($path, -1, 1)==='/') {
  538. $path=substr($path, 0, -1);
  539. }
  540. //remove duplicate slashes
  541. while(strpos($path, '//')!==false) {
  542. $path=str_replace('//', '/', $path);
  543. }
  544. //normalize unicode if possible
  545. if(class_exists('Normalizer')) {
  546. $path=Normalizer::normalize($path);
  547. }
  548. return $path;
  549. }
  550. }
  551. OC_Hook::connect('OC_Filesystem', 'post_write', 'OC_Filesystem', 'removeETagHook');
  552. OC_Hook::connect('OC_Filesystem', 'post_delete', 'OC_Filesystem', 'removeETagHook');
  553. OC_Hook::connect('OC_Filesystem', 'post_rename', 'OC_Filesystem', 'removeETagHook');
  554. OC_Util::setupFS();
  555. require_once 'filecache.php';