filesystem.php 16 KB

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