You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

923 lines
24 KiB

3 years ago
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. *
  5. * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
  6. * @author Bart Visscher <bartv@thisnet.nl>
  7. * @author Christopher Schäpers <kondou@ts.unde.re>
  8. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  9. * @author Florin Peter <github@florin-peter.de>
  10. * @author Joas Schilling <coding@schilljs.com>
  11. * @author Jörn Friedrich Dreyer <jfd@butonic.de>
  12. * @author korelstar <korelstar@users.noreply.github.com>
  13. * @author Lukas Reschke <lukas@statuscode.ch>
  14. * @author Michael Gapczynski <GapczynskiM@gmail.com>
  15. * @author Morris Jobke <hey@morrisjobke.de>
  16. * @author Robin Appelman <robin@icewind.nl>
  17. * @author Robin McCorkell <robin@mccorkell.me.uk>
  18. * @author Roeland Jago Douma <roeland@famdouma.nl>
  19. * @author Sam Tuke <mail@samtuke.com>
  20. * @author Stephan Peijnik <speijnik@anexia-it.com>
  21. * @author Vincent Petry <pvince81@owncloud.com>
  22. *
  23. * @license AGPL-3.0
  24. *
  25. * This code is free software: you can redistribute it and/or modify
  26. * it under the terms of the GNU Affero General Public License, version 3,
  27. * as published by the Free Software Foundation.
  28. *
  29. * This program is distributed in the hope that it will be useful,
  30. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  31. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  32. * GNU Affero General Public License for more details.
  33. *
  34. * You should have received a copy of the GNU Affero General Public License, version 3,
  35. * along with this program. If not, see <http://www.gnu.org/licenses/>
  36. *
  37. */
  38. /**
  39. * Class for abstraction of filesystem functions
  40. * This class won't call any filesystem functions for itself but will pass them to the correct OC_Filestorage object
  41. * this class should also handle all the file permission related stuff
  42. *
  43. * Hooks provided:
  44. * read(path)
  45. * write(path, &run)
  46. * post_write(path)
  47. * create(path, &run) (when a file is created, both create and write will be emitted in that order)
  48. * post_create(path)
  49. * delete(path, &run)
  50. * post_delete(path)
  51. * rename(oldpath,newpath, &run)
  52. * post_rename(oldpath,newpath)
  53. * copy(oldpath,newpath, &run) (if the newpath doesn't exists yes, copy, create and write will be emitted in that order)
  54. * post_rename(oldpath,newpath)
  55. * post_initMountPoints(user, user_dir)
  56. *
  57. * the &run parameter can be set to false to prevent the operation from occurring
  58. */
  59. namespace OC\Files;
  60. use OC\Cache\CappedMemoryCache;
  61. use OC\Files\Config\MountProviderCollection;
  62. use OC\Files\Mount\MountPoint;
  63. use OC\Lockdown\Filesystem\NullStorage;
  64. use OCP\Files\Config\IMountProvider;
  65. use OCP\Files\NotFoundException;
  66. use OCP\Files\Storage\IStorageFactory;
  67. use OCP\ILogger;
  68. use OCP\IUserManager;
  69. class Filesystem {
  70. /**
  71. * @var Mount\Manager $mounts
  72. */
  73. private static $mounts;
  74. public static $loaded = false;
  75. /**
  76. * @var \OC\Files\View $defaultInstance
  77. */
  78. private static $defaultInstance;
  79. private static $usersSetup = [];
  80. private static $normalizedPathCache = null;
  81. private static $listeningForProviders = false;
  82. /**
  83. * classname which used for hooks handling
  84. * used as signalclass in OC_Hooks::emit()
  85. */
  86. public const CLASSNAME = 'OC_Filesystem';
  87. /**
  88. * signalname emitted before file renaming
  89. *
  90. * @param string $oldpath
  91. * @param string $newpath
  92. */
  93. public const signal_rename = 'rename';
  94. /**
  95. * signal emitted after file renaming
  96. *
  97. * @param string $oldpath
  98. * @param string $newpath
  99. */
  100. public const signal_post_rename = 'post_rename';
  101. /**
  102. * signal emitted before file/dir creation
  103. *
  104. * @param string $path
  105. * @param bool $run changing this flag to false in hook handler will cancel event
  106. */
  107. public const signal_create = 'create';
  108. /**
  109. * signal emitted after file/dir creation
  110. *
  111. * @param string $path
  112. * @param bool $run changing this flag to false in hook handler will cancel event
  113. */
  114. public const signal_post_create = 'post_create';
  115. /**
  116. * signal emits before file/dir copy
  117. *
  118. * @param string $oldpath
  119. * @param string $newpath
  120. * @param bool $run changing this flag to false in hook handler will cancel event
  121. */
  122. public const signal_copy = 'copy';
  123. /**
  124. * signal emits after file/dir copy
  125. *
  126. * @param string $oldpath
  127. * @param string $newpath
  128. */
  129. public const signal_post_copy = 'post_copy';
  130. /**
  131. * signal emits before file/dir save
  132. *
  133. * @param string $path
  134. * @param bool $run changing this flag to false in hook handler will cancel event
  135. */
  136. public const signal_write = 'write';
  137. /**
  138. * signal emits after file/dir save
  139. *
  140. * @param string $path
  141. */
  142. public const signal_post_write = 'post_write';
  143. /**
  144. * signal emitted before file/dir update
  145. *
  146. * @param string $path
  147. * @param bool $run changing this flag to false in hook handler will cancel event
  148. */
  149. public const signal_update = 'update';
  150. /**
  151. * signal emitted after file/dir update
  152. *
  153. * @param string $path
  154. * @param bool $run changing this flag to false in hook handler will cancel event
  155. */
  156. public const signal_post_update = 'post_update';
  157. /**
  158. * signal emits when reading file/dir
  159. *
  160. * @param string $path
  161. */
  162. public const signal_read = 'read';
  163. /**
  164. * signal emits when removing file/dir
  165. *
  166. * @param string $path
  167. */
  168. public const signal_delete = 'delete';
  169. /**
  170. * parameters definitions for signals
  171. */
  172. public const signal_param_path = 'path';
  173. public const signal_param_oldpath = 'oldpath';
  174. public const signal_param_newpath = 'newpath';
  175. /**
  176. * run - changing this flag to false in hook handler will cancel event
  177. */
  178. public const signal_param_run = 'run';
  179. public const signal_create_mount = 'create_mount';
  180. public const signal_delete_mount = 'delete_mount';
  181. public const signal_param_mount_type = 'mounttype';
  182. public const signal_param_users = 'users';
  183. /**
  184. * @var \OC\Files\Storage\StorageFactory $loader
  185. */
  186. private static $loader;
  187. /** @var bool */
  188. private static $logWarningWhenAddingStorageWrapper = true;
  189. /**
  190. * @param bool $shouldLog
  191. * @return bool previous value
  192. * @internal
  193. */
  194. public static function logWarningWhenAddingStorageWrapper($shouldLog) {
  195. $previousValue = self::$logWarningWhenAddingStorageWrapper;
  196. self::$logWarningWhenAddingStorageWrapper = (bool) $shouldLog;
  197. return $previousValue;
  198. }
  199. /**
  200. * @param string $wrapperName
  201. * @param callable $wrapper
  202. * @param int $priority
  203. */
  204. public static function addStorageWrapper($wrapperName, $wrapper, $priority = 50) {
  205. if (self::$logWarningWhenAddingStorageWrapper) {
  206. \OC::$server->getLogger()->warning("Storage wrapper '{wrapper}' was not registered via the 'OC_Filesystem - preSetup' hook which could cause potential problems.", [
  207. 'wrapper' => $wrapperName,
  208. 'app' => 'filesystem',
  209. ]);
  210. }
  211. $mounts = self::getMountManager()->getAll();
  212. if (!self::getLoader()->addStorageWrapper($wrapperName, $wrapper, $priority, $mounts)) {
  213. // do not re-wrap if storage with this name already existed
  214. return;
  215. }
  216. }
  217. /**
  218. * Returns the storage factory
  219. *
  220. * @return IStorageFactory
  221. */
  222. public static function getLoader() {
  223. if (!self::$loader) {
  224. self::$loader = \OC::$server->query(IStorageFactory::class);
  225. }
  226. return self::$loader;
  227. }
  228. /**
  229. * Returns the mount manager
  230. *
  231. * @return \OC\Files\Mount\Manager
  232. */
  233. public static function getMountManager($user = '') {
  234. if (!self::$mounts) {
  235. \OC_Util::setupFS($user);
  236. }
  237. return self::$mounts;
  238. }
  239. /**
  240. * get the mountpoint of the storage object for a path
  241. * ( note: because a storage is not always mounted inside the fakeroot, the
  242. * returned mountpoint is relative to the absolute root of the filesystem
  243. * and doesn't take the chroot into account )
  244. *
  245. * @param string $path
  246. * @return string
  247. */
  248. public static function getMountPoint($path) {
  249. if (!self::$mounts) {
  250. \OC_Util::setupFS();
  251. }
  252. $mount = self::$mounts->find($path);
  253. if ($mount) {
  254. return $mount->getMountPoint();
  255. } else {
  256. return '';
  257. }
  258. }
  259. /**
  260. * get a list of all mount points in a directory
  261. *
  262. * @param string $path
  263. * @return string[]
  264. */
  265. public static function getMountPoints($path) {
  266. if (!self::$mounts) {
  267. \OC_Util::setupFS();
  268. }
  269. $result = [];
  270. $mounts = self::$mounts->findIn($path);
  271. foreach ($mounts as $mount) {
  272. $result[] = $mount->getMountPoint();
  273. }
  274. return $result;
  275. }
  276. /**
  277. * get the storage mounted at $mountPoint
  278. *
  279. * @param string $mountPoint
  280. * @return \OC\Files\Storage\Storage
  281. */
  282. public static function getStorage($mountPoint) {
  283. if (!self::$mounts) {
  284. \OC_Util::setupFS();
  285. }
  286. $mount = self::$mounts->find($mountPoint);
  287. return $mount->getStorage();
  288. }
  289. /**
  290. * @param string $id
  291. * @return Mount\MountPoint[]
  292. */
  293. public static function getMountByStorageId($id) {
  294. if (!self::$mounts) {
  295. \OC_Util::setupFS();
  296. }
  297. return self::$mounts->findByStorageId($id);
  298. }
  299. /**
  300. * @param int $id
  301. * @return Mount\MountPoint[]
  302. */
  303. public static function getMountByNumericId($id) {
  304. if (!self::$mounts) {
  305. \OC_Util::setupFS();
  306. }
  307. return self::$mounts->findByNumericId($id);
  308. }
  309. /**
  310. * resolve a path to a storage and internal path
  311. *
  312. * @param string $path
  313. * @return array an array consisting of the storage and the internal path
  314. */
  315. public static function resolvePath($path) {
  316. if (!self::$mounts) {
  317. \OC_Util::setupFS();
  318. }
  319. $mount = self::$mounts->find($path);
  320. if ($mount) {
  321. return [$mount->getStorage(), rtrim($mount->getInternalPath($path), '/')];
  322. } else {
  323. return [null, null];
  324. }
  325. }
  326. public static function init($user, $root) {
  327. if (self::$defaultInstance) {
  328. return false;
  329. }
  330. self::getLoader();
  331. self::$defaultInstance = new View($root);
  332. if (!self::$mounts) {
  333. self::$mounts = \OC::$server->getMountManager();
  334. }
  335. //load custom mount config
  336. self::initMountPoints($user);
  337. self::$loaded = true;
  338. return true;
  339. }
  340. public static function initMountManager() {
  341. if (!self::$mounts) {
  342. self::$mounts = \OC::$server->getMountManager();
  343. }
  344. }
  345. /**
  346. * Initialize system and personal mount points for a user
  347. *
  348. * @param string $user
  349. * @throws \OC\User\NoUserException if the user is not available
  350. */
  351. public static function initMountPoints($user = '') {
  352. if ($user == '') {
  353. $user = \OC_User::getUser();
  354. }
  355. if ($user === null || $user === false || $user === '') {
  356. throw new \OC\User\NoUserException('Attempted to initialize mount points for null user and no user in session');
  357. }
  358. if (isset(self::$usersSetup[$user])) {
  359. return;
  360. }
  361. self::$usersSetup[$user] = true;
  362. $userManager = \OC::$server->getUserManager();
  363. $userObject = $userManager->get($user);
  364. if (is_null($userObject)) {
  365. \OCP\Util::writeLog('files', ' Backends provided no user object for ' . $user, ILogger::ERROR);
  366. // reset flag, this will make it possible to rethrow the exception if called again
  367. unset(self::$usersSetup[$user]);
  368. throw new \OC\User\NoUserException('Backends provided no user object for ' . $user);
  369. }
  370. $realUid = $userObject->getUID();
  371. // workaround in case of different casings
  372. if ($user !== $realUid) {
  373. $stack = json_encode(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 50));
  374. \OCP\Util::writeLog('files', 'initMountPoints() called with wrong user casing. This could be a bug. Expected: "' . $realUid . '" got "' . $user . '". Stack: ' . $stack, ILogger::WARN);
  375. $user = $realUid;
  376. // again with the correct casing
  377. if (isset(self::$usersSetup[$user])) {
  378. return;
  379. }
  380. self::$usersSetup[$user] = true;
  381. }
  382. if (\OC::$server->getLockdownManager()->canAccessFilesystem()) {
  383. /** @var \OC\Files\Config\MountProviderCollection $mountConfigManager */
  384. $mountConfigManager = \OC::$server->getMountProviderCollection();
  385. // home mounts are handled seperate since we need to ensure this is mounted before we call the other mount providers
  386. $homeMount = $mountConfigManager->getHomeMountForUser($userObject);
  387. self::getMountManager()->addMount($homeMount);
  388. if ($homeMount->getStorageRootId() === -1) {
  389. $homeMount->getStorage()->mkdir('');
  390. $homeMount->getStorage()->getScanner()->scan('');
  391. }
  392. \OC\Files\Filesystem::getStorage($user);
  393. // Chance to mount for other storages
  394. if ($userObject) {
  395. $mounts = $mountConfigManager->addMountForUser($userObject, self::getMountManager());
  396. $mounts[] = $homeMount;
  397. $mountConfigManager->registerMounts($userObject, $mounts);
  398. }
  399. self::listenForNewMountProviders($mountConfigManager, $userManager);
  400. } else {
  401. self::getMountManager()->addMount(new MountPoint(
  402. new NullStorage([]),
  403. '/' . $user
  404. ));
  405. self::getMountManager()->addMount(new MountPoint(
  406. new NullStorage([]),
  407. '/' . $user . '/files'
  408. ));
  409. }
  410. \OC_Hook::emit('OC_Filesystem', 'post_initMountPoints', ['user' => $user]);
  411. }
  412. /**
  413. * Get mounts from mount providers that are registered after setup
  414. *
  415. * @param MountProviderCollection $mountConfigManager
  416. * @param IUserManager $userManager
  417. */
  418. private static function listenForNewMountProviders(MountProviderCollection $mountConfigManager, IUserManager $userManager) {
  419. if (!self::$listeningForProviders) {
  420. self::$listeningForProviders = true;
  421. $mountConfigManager->listen('\OC\Files\Config', 'registerMountProvider', function (IMountProvider $provider) use ($userManager) {
  422. foreach (Filesystem::$usersSetup as $user => $setup) {
  423. $userObject = $userManager->get($user);
  424. if ($userObject) {
  425. $mounts = $provider->getMountsForUser($userObject, Filesystem::getLoader());
  426. array_walk($mounts, [self::$mounts, 'addMount']);
  427. }
  428. }
  429. });
  430. }
  431. }
  432. /**
  433. * get the default filesystem view
  434. *
  435. * @return View
  436. */
  437. public static function getView() {
  438. return self::$defaultInstance;
  439. }
  440. /**
  441. * tear down the filesystem, removing all storage providers
  442. */
  443. public static function tearDown() {
  444. self::clearMounts();
  445. self::$defaultInstance = null;
  446. }
  447. /**
  448. * get the relative path of the root data directory for the current user
  449. *
  450. * @return string
  451. *
  452. * Returns path like /admin/files
  453. */
  454. public static function getRoot() {
  455. if (!self::$defaultInstance) {
  456. return null;
  457. }
  458. return self::$defaultInstance->getRoot();
  459. }
  460. /**
  461. * clear all mounts and storage backends
  462. */
  463. public static function clearMounts() {
  464. if (self::$mounts) {
  465. self::$usersSetup = [];
  466. self::$mounts->clear();
  467. }
  468. }
  469. /**
  470. * mount an \OC\Files\Storage\Storage in our virtual filesystem
  471. *
  472. * @param \OC\Files\Storage\Storage|string $class
  473. * @param array $arguments
  474. * @param string $mountpoint
  475. */
  476. public static function mount($class, $arguments, $mountpoint) {
  477. if (!self::$mounts) {
  478. \OC_Util::setupFS();
  479. }
  480. $mount = new Mount\MountPoint($class, $mountpoint, $arguments, self::getLoader());
  481. self::$mounts->addMount($mount);
  482. }
  483. /**
  484. * return the path to a local version of the file
  485. * we need this because we can't know if a file is stored local or not from
  486. * outside the filestorage and for some purposes a local file is needed
  487. *
  488. * @param string $path
  489. * @return string
  490. */
  491. public static function getLocalFile($path) {
  492. return self::$defaultInstance->getLocalFile($path);
  493. }
  494. /**
  495. * @param string $path
  496. * @return string
  497. */
  498. public static function getLocalFolder($path) {
  499. return self::$defaultInstance->getLocalFolder($path);
  500. }
  501. /**
  502. * return path to file which reflects one visible in browser
  503. *
  504. * @param string $path
  505. * @return string
  506. */
  507. public static function getLocalPath($path) {
  508. $datadir = \OC_User::getHome(\OC_User::getUser()) . '/files';
  509. $newpath = $path;
  510. if (strncmp($newpath, $datadir, strlen($datadir)) == 0) {
  511. $newpath = substr($path, strlen($datadir));
  512. }
  513. return $newpath;
  514. }
  515. /**
  516. * check if the requested path is valid
  517. *
  518. * @param string $path
  519. * @return bool
  520. */
  521. public static function isValidPath($path) {
  522. $path = self::normalizePath($path);
  523. if (!$path || $path[0] !== '/') {
  524. $path = '/' . $path;
  525. }
  526. if (strpos($path, '/../') !== false || strrchr($path, '/') === '/..') {
  527. return false;
  528. }
  529. return true;
  530. }
  531. /**
  532. * checks if a file is blacklisted for storage in the filesystem
  533. * Listens to write and rename hooks
  534. *
  535. * @param array $data from hook
  536. */
  537. public static function isBlacklisted($data) {
  538. if (isset($data['path'])) {
  539. $path = $data['path'];
  540. } elseif (isset($data['newpath'])) {
  541. $path = $data['newpath'];
  542. }
  543. if (isset($path)) {
  544. if (self::isFileBlacklisted($path)) {
  545. $data['run'] = false;
  546. }
  547. }
  548. }
  549. /**
  550. * @param string $filename
  551. * @return bool
  552. */
  553. public static function isFileBlacklisted($filename) {
  554. $filename = self::normalizePath($filename);
  555. $blacklist = \OC::$server->getConfig()->getSystemValue('blacklisted_files', ['.htaccess']);
  556. $filename = strtolower(basename($filename));
  557. return in_array($filename, $blacklist);
  558. }
  559. /**
  560. * check if the directory should be ignored when scanning
  561. * NOTE: the special directories . and .. would cause never ending recursion
  562. *
  563. * @param string $dir
  564. * @return boolean
  565. */
  566. public static function isIgnoredDir($dir) {
  567. if ($dir === '.' || $dir === '..') {
  568. return true;
  569. }
  570. return false;
  571. }
  572. /**
  573. * following functions are equivalent to their php builtin equivalents for arguments/return values.
  574. */
  575. public static function mkdir($path) {
  576. return self::$defaultInstance->mkdir($path);
  577. }
  578. public static function rmdir($path) {
  579. return self::$defaultInstance->rmdir($path);
  580. }
  581. public static function is_dir($path) {
  582. return self::$defaultInstance->is_dir($path);
  583. }
  584. public static function is_file($path) {
  585. return self::$defaultInstance->is_file($path);
  586. }
  587. public static function stat($path) {
  588. return self::$defaultInstance->stat($path);
  589. }
  590. public static function filetype($path) {
  591. return self::$defaultInstance->filetype($path);
  592. }
  593. public static function filesize($path) {
  594. return self::$defaultInstance->filesize($path);
  595. }
  596. public static function readfile($path) {
  597. return self::$defaultInstance->readfile($path);
  598. }
  599. public static function isCreatable($path) {
  600. return self::$defaultInstance->isCreatable($path);
  601. }
  602. public static function isReadable($path) {
  603. return self::$defaultInstance->isReadable($path);
  604. }
  605. public static function isUpdatable($path) {
  606. return self::$defaultInstance->isUpdatable($path);
  607. }
  608. public static function isDeletable($path) {
  609. return self::$defaultInstance->isDeletable($path);
  610. }
  611. public static function isSharable($path) {
  612. return self::$defaultInstance->isSharable($path);
  613. }
  614. public static function file_exists($path) {
  615. return self::$defaultInstance->file_exists($path);
  616. }
  617. public static function filemtime($path) {
  618. return self::$defaultInstance->filemtime($path);
  619. }
  620. public static function touch($path, $mtime = null) {
  621. return self::$defaultInstance->touch($path, $mtime);
  622. }
  623. /**
  624. * @return string
  625. */
  626. public static function file_get_contents($path) {
  627. return self::$defaultInstance->file_get_contents($path);
  628. }
  629. public static function file_put_contents($path, $data) {
  630. return self::$defaultInstance->file_put_contents($path, $data);
  631. }
  632. public static function unlink($path) {
  633. return self::$defaultInstance->unlink($path);
  634. }
  635. public static function rename($path1, $path2) {
  636. return self::$defaultInstance->rename($path1, $path2);
  637. }
  638. public static function copy($path1, $path2) {
  639. return self::$defaultInstance->copy($path1, $path2);
  640. }
  641. public static function fopen($path, $mode) {
  642. return self::$defaultInstance->fopen($path, $mode);
  643. }
  644. /**
  645. * @return string
  646. */
  647. public static function toTmpFile($path) {
  648. return self::$defaultInstance->toTmpFile($path);
  649. }
  650. public static function fromTmpFile($tmpFile, $path) {
  651. return self::$defaultInstance->fromTmpFile($tmpFile, $path);
  652. }
  653. public static function getMimeType($path) {
  654. return self::$defaultInstance->getMimeType($path);
  655. }
  656. public static function hash($type, $path, $raw = false) {
  657. return self::$defaultInstance->hash($type, $path, $raw);
  658. }
  659. public static function free_space($path = '/') {
  660. return self::$defaultInstance->free_space($path);
  661. }
  662. public static function search($query) {
  663. return self::$defaultInstance->search($query);
  664. }
  665. /**
  666. * @param string $query
  667. */
  668. public static function searchByMime($query) {
  669. return self::$defaultInstance->searchByMime($query);
  670. }
  671. /**
  672. * @param string|int $tag name or tag id
  673. * @param string $userId owner of the tags
  674. * @return FileInfo[] array or file info
  675. */
  676. public static function searchByTag($tag, $userId) {
  677. return self::$defaultInstance->searchByTag($tag, $userId);
  678. }
  679. /**
  680. * check if a file or folder has been updated since $time
  681. *
  682. * @param string $path
  683. * @param int $time
  684. * @return bool
  685. */
  686. public static function hasUpdated($path, $time) {
  687. return self::$defaultInstance->hasUpdated($path, $time);
  688. }
  689. /**
  690. * Fix common problems with a file path
  691. *
  692. * @param string $path
  693. * @param bool $stripTrailingSlash whether to strip the trailing slash
  694. * @param bool $isAbsolutePath whether the given path is absolute
  695. * @param bool $keepUnicode true to disable unicode normalization
  696. * @return string
  697. */
  698. public static function normalizePath($path, $stripTrailingSlash = true, $isAbsolutePath = false, $keepUnicode = false) {
  699. if (is_null(self::$normalizedPathCache)) {
  700. self::$normalizedPathCache = new CappedMemoryCache(2048);
  701. }
  702. /**
  703. * FIXME: This is a workaround for existing classes and files which call
  704. * this function with another type than a valid string. This
  705. * conversion should get removed as soon as all existing
  706. * function calls have been fixed.
  707. */
  708. $path = (string)$path;
  709. $cacheKey = json_encode([$path, $stripTrailingSlash, $isAbsolutePath, $keepUnicode]);
  710. if ($cacheKey && isset(self::$normalizedPathCache[$cacheKey])) {
  711. return self::$normalizedPathCache[$cacheKey];
  712. }
  713. if ($path === '') {
  714. return '/';
  715. }
  716. //normalize unicode if possible
  717. if (!$keepUnicode) {
  718. $path = \OC_Util::normalizeUnicode($path);
  719. }
  720. //add leading slash, if it is already there we strip it anyway
  721. $path = '/' . $path;
  722. $patterns = [
  723. '/\\\\/s', // no windows style slashes
  724. '/\/\.(\/\.)?\//s', // remove '/./'
  725. '/\/{2,}/s', // remove sequence of slashes
  726. '/\/\.$/s', // remove trailing /.
  727. ];
  728. do {
  729. $count = 0;
  730. $path = preg_replace($patterns, '/', $path, -1, $count);
  731. } while ($count > 0);
  732. //remove trailing slash
  733. if ($stripTrailingSlash && strlen($path) > 1) {
  734. $path = rtrim($path, '/');
  735. }
  736. self::$normalizedPathCache[$cacheKey] = $path;
  737. return $path;
  738. }
  739. /**
  740. * get the filesystem info
  741. *
  742. * @param string $path
  743. * @param boolean $includeMountPoints whether to add mountpoint sizes,
  744. * defaults to true
  745. * @return \OC\Files\FileInfo|bool False if file does not exist
  746. */
  747. public static function getFileInfo($path, $includeMountPoints = true) {
  748. return self::$defaultInstance->getFileInfo($path, $includeMountPoints);
  749. }
  750. /**
  751. * change file metadata
  752. *
  753. * @param string $path
  754. * @param array $data
  755. * @return int
  756. *
  757. * returns the fileid of the updated file
  758. */
  759. public static function putFileInfo($path, $data) {
  760. return self::$defaultInstance->putFileInfo($path, $data);
  761. }
  762. /**
  763. * get the content of a directory
  764. *
  765. * @param string $directory path under datadirectory
  766. * @param string $mimetype_filter limit returned content to this mimetype or mimepart
  767. * @return \OC\Files\FileInfo[]
  768. */
  769. public static function getDirectoryContent($directory, $mimetype_filter = '') {
  770. return self::$defaultInstance->getDirectoryContent($directory, $mimetype_filter);
  771. }
  772. /**
  773. * Get the path of a file by id
  774. *
  775. * Note that the resulting path is not guaranteed to be unique for the id, multiple paths can point to the same file
  776. *
  777. * @param int $id
  778. * @throws NotFoundException
  779. * @return string
  780. */
  781. public static function getPath($id) {
  782. return self::$defaultInstance->getPath($id);
  783. }
  784. /**
  785. * Get the owner for a file or folder
  786. *
  787. * @param string $path
  788. * @return string
  789. */
  790. public static function getOwner($path) {
  791. return self::$defaultInstance->getOwner($path);
  792. }
  793. /**
  794. * get the ETag for a file or folder
  795. *
  796. * @param string $path
  797. * @return string
  798. */
  799. public static function getETag($path) {
  800. return self::$defaultInstance->getETag($path);
  801. }
  802. }