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.

sharedstorage.php 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. <?php
  2. /**
  3. * ownCloud
  4. *
  5. * @author Bjoern Schiessle, Michael Gapczynski
  6. * @copyright 2011 Michael Gapczynski <mtgap@owncloud.com>
  7. * 2014 Bjoern Schiessle <schiessle@owncloud.com>
  8. *
  9. * This library is free software; you can redistribute it and/or
  10. * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
  11. * License as published by the Free Software Foundation; either
  12. * version 3 of the License, or any later version.
  13. *
  14. * This library is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
  18. *
  19. * You should have received a copy of the GNU Affero General Public
  20. * License along with this library. If not, see <http://www.gnu.org/licenses/>.
  21. *
  22. */
  23. namespace OC\Files\Storage;
  24. /**
  25. * Convert target path to source path and pass the function call to the correct storage provider
  26. */
  27. class Shared extends \OC\Files\Storage\Common {
  28. private $share; // the shared resource
  29. private $files = array();
  30. public function __construct($arguments) {
  31. $this->share = $arguments['share'];
  32. }
  33. /**
  34. * get id of the mount point
  35. * @return string
  36. */
  37. public function getId() {
  38. return 'shared::' . $this->getMountPoint();
  39. }
  40. /**
  41. * get file cache of the shared item source
  42. * @return string
  43. */
  44. public function getSourceId() {
  45. return $this->share['file_source'];
  46. }
  47. /**
  48. * Get the source file path, permissions, and owner for a shared file
  49. * @param string $target Shared target file path
  50. * @return Returns array with the keys path, permissions, and owner or false if not found
  51. */
  52. public function getFile($target) {
  53. if (!isset($this->files[$target])) {
  54. // Check for partial files
  55. if (pathinfo($target, PATHINFO_EXTENSION) === 'part') {
  56. $source = \OC_Share_Backend_File::getSource(substr($target, 0, -5), $this->getMountPoint(), $this->getItemType());
  57. if ($source) {
  58. $source['path'] .= '.part';
  59. // All partial files have delete permission
  60. $source['permissions'] |= \OCP\PERMISSION_DELETE;
  61. }
  62. } else {
  63. $source = \OC_Share_Backend_File::getSource($target, $this->getMountPoint(), $this->getItemType());
  64. }
  65. $this->files[$target] = $source;
  66. }
  67. return $this->files[$target];
  68. }
  69. /**
  70. * Get the source file path for a shared file
  71. * @param string $target Shared target file path
  72. * @return string source file path or false if not found
  73. */
  74. public function getSourcePath($target) {
  75. $source = $this->getFile($target);
  76. if ($source) {
  77. if (!isset($source['fullPath'])) {
  78. \OC\Files\Filesystem::initMountPoints($source['fileOwner']);
  79. $mount = \OC\Files\Filesystem::getMountByNumericId($source['storage']);
  80. if (is_array($mount) && !empty($mount)) {
  81. $this->files[$target]['fullPath'] = $mount[key($mount)]->getMountPoint() . $source['path'];
  82. } else {
  83. $this->files[$target]['fullPath'] = false;
  84. \OCP\Util::writeLog('files_sharing', "Unable to get mount for shared storage '" . $source['storage'] . "' user '" . $source['fileOwner'] . "'", \OCP\Util::ERROR);
  85. }
  86. }
  87. return $this->files[$target]['fullPath'];
  88. }
  89. return false;
  90. }
  91. /**
  92. * Get the permissions granted for a shared file
  93. * @param string $target Shared target file path
  94. * @return int CRUDS permissions granted
  95. */
  96. public function getPermissions($target = '') {
  97. $permissions = $this->share['permissions'];
  98. // part file are always have delete permissions
  99. if (pathinfo($target, PATHINFO_EXTENSION) === 'part') {
  100. $permissions |= \OCP\PERMISSION_DELETE;
  101. }
  102. if (\OC_Util::isSharingDisabledForUser()) {
  103. $permissions &= ~\OCP\PERMISSION_SHARE;
  104. }
  105. return $permissions;
  106. }
  107. public function mkdir($path) {
  108. if ($path == '' || $path == '/' || !$this->isCreatable(dirname($path))) {
  109. return false;
  110. } else if ($source = $this->getSourcePath($path)) {
  111. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  112. return $storage->mkdir($internalPath);
  113. }
  114. return false;
  115. }
  116. public function rmdir($path) {
  117. if (($source = $this->getSourcePath($path)) && $this->isDeletable($path)) {
  118. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  119. return $storage->rmdir($internalPath);
  120. }
  121. return false;
  122. }
  123. public function opendir($path) {
  124. $source = $this->getSourcePath($path);
  125. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  126. return $storage->opendir($internalPath);
  127. }
  128. public function is_dir($path) {
  129. $source = $this->getSourcePath($path);
  130. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  131. return $storage->is_dir($internalPath);
  132. }
  133. public function is_file($path) {
  134. if ($source = $this->getSourcePath($path)) {
  135. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  136. return $storage->is_file($internalPath);
  137. }
  138. return false;
  139. }
  140. public function stat($path) {
  141. if ($path == '' || $path == '/') {
  142. $stat['size'] = $this->filesize($path);
  143. $stat['mtime'] = $this->filemtime($path);
  144. return $stat;
  145. } else if ($source = $this->getSourcePath($path)) {
  146. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  147. return $storage->stat($internalPath);
  148. }
  149. return false;
  150. }
  151. public function filetype($path) {
  152. if ($path == '' || $path == '/') {
  153. return 'dir';
  154. } else if ($source = $this->getSourcePath($path)) {
  155. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  156. return $storage->filetype($internalPath);
  157. }
  158. return false;
  159. }
  160. public function filesize($path) {
  161. $source = $this->getSourcePath($path);
  162. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  163. return $storage->filesize($internalPath);
  164. }
  165. public function isCreatable($path) {
  166. return ($this->getPermissions($path) & \OCP\PERMISSION_CREATE);
  167. }
  168. public function isReadable($path) {
  169. return $this->file_exists($path);
  170. }
  171. public function isUpdatable($path) {
  172. return ($this->getPermissions($path) & \OCP\PERMISSION_UPDATE);
  173. }
  174. public function isDeletable($path) {
  175. return ($this->getPermissions($path) & \OCP\PERMISSION_DELETE);
  176. }
  177. public function isSharable($path) {
  178. if (\OCP\Util::isSharingDisabledForUser()) {
  179. return false;
  180. }
  181. return ($this->getPermissions($path) & \OCP\PERMISSION_SHARE);
  182. }
  183. public function file_exists($path) {
  184. if ($path == '' || $path == '/') {
  185. return true;
  186. } else if ($source = $this->getSourcePath($path)) {
  187. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  188. return $storage->file_exists($internalPath);
  189. }
  190. return false;
  191. }
  192. public function filemtime($path) {
  193. $source = $this->getSourcePath($path);
  194. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  195. return $storage->filemtime($internalPath);
  196. }
  197. public function file_get_contents($path) {
  198. $source = $this->getSourcePath($path);
  199. if ($source) {
  200. $info = array(
  201. 'target' => $this->getMountPoint() . $path,
  202. 'source' => $source,
  203. );
  204. \OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_get_contents', $info);
  205. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  206. return $storage->file_get_contents($internalPath);
  207. }
  208. }
  209. public function file_put_contents($path, $data) {
  210. if ($source = $this->getSourcePath($path)) {
  211. // Check if permission is granted
  212. if (($this->file_exists($path) && !$this->isUpdatable($path))
  213. || ($this->is_dir($path) && !$this->isCreatable($path))
  214. ) {
  215. return false;
  216. }
  217. $info = array(
  218. 'target' => $this->getMountPoint() . '/' . $path,
  219. 'source' => $source,
  220. );
  221. \OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_put_contents', $info);
  222. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  223. $result = $storage->file_put_contents($internalPath, $data);
  224. return $result;
  225. }
  226. return false;
  227. }
  228. public function unlink($path) {
  229. // Delete the file if DELETE permission is granted
  230. $path = ($path === false) ? '' : $path;
  231. if ($source = $this->getSourcePath($path)) {
  232. if ($this->isDeletable($path)) {
  233. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  234. return $storage->unlink($internalPath);
  235. }
  236. }
  237. return false;
  238. }
  239. /**
  240. * Format a path to be relative to the /user/files/ directory
  241. * @param string $path the absolute path
  242. * @return string e.g. turns '/admin/files/test.txt' into '/test.txt'
  243. */
  244. private static function stripUserFilesPath($path) {
  245. $trimmed = ltrim($path, '/');
  246. $split = explode('/', $trimmed);
  247. // it is not a file relative to data/user/files
  248. if (count($split) < 3 || $split[1] !== 'files') {
  249. \OCP\Util::writeLog('file sharing',
  250. 'Can not strip userid and "files/" from path: ' . $path,
  251. \OCP\Util::DEBUG);
  252. return false;
  253. }
  254. // skip 'user' and 'files'
  255. $sliced = array_slice($split, 2);
  256. $relPath = implode('/', $sliced);
  257. return '/' . $relPath;
  258. }
  259. /**
  260. * rename a shared folder/file
  261. * @param string $sourcePath
  262. * @param string $targetPath
  263. * @return bool
  264. */
  265. private function renameMountPoint($sourcePath, $targetPath) {
  266. // it shouldn't be possible to move a Shared storage into another one
  267. list($targetStorage, ) = \OC\Files\Filesystem::resolvePath($targetPath);
  268. if ($targetStorage->instanceOfStorage('\OC\Files\Storage\Shared')) {
  269. \OCP\Util::writeLog('file sharing',
  270. 'It is not allowed to move one mount point into another one',
  271. \OCP\Util::DEBUG);
  272. return false;
  273. }
  274. $relTargetPath = $this->stripUserFilesPath($targetPath);
  275. if ($relTargetPath === false) {
  276. \OCP\Util::writeLog('file sharing', 'Wrong target path given: ' . $targetPath, \OCP\Util::ERROR);
  277. return false;
  278. }
  279. $result = self::updateFileTarget($relTargetPath, $this->share);
  280. if ($result) {
  281. // update the mount manager with the new paths
  282. $mountManager = \OC\Files\Filesystem::getMountManager();
  283. $mount = $mountManager->find($sourcePath);
  284. $mount->setMountPoint($targetPath . '/');
  285. $mountManager->addMount($mount);
  286. $mountManager->removeMount($sourcePath . '/');
  287. $this->setUniqueName();
  288. $this->setMountPoint($relTargetPath);
  289. } else {
  290. \OCP\Util::writeLog('file sharing',
  291. 'Could not rename mount point for shared folder "' . $sourcePath . '" to "' . $targetPath . '"',
  292. \OCP\Util::ERROR);
  293. }
  294. return (bool)$result;
  295. }
  296. /**
  297. * @update fileTarget in the database if the mount point changed
  298. * @param string $newPath
  299. * @param array $share reference to the share which should be modified
  300. * @return type
  301. */
  302. private static function updateFileTarget($newPath, &$share) {
  303. // if the user renames a mount point from a group share we need to create a new db entry
  304. // for the unique name
  305. if ($share['share_type'] === \OCP\Share::SHARE_TYPE_GROUP &&
  306. (isset($share['unique_name']) && $share['unique_name'])) {
  307. $query = \OC_DB::prepare('INSERT INTO `*PREFIX*share` (`item_type`, `item_source`, `item_target`,'
  308. .' `share_type`, `share_with`, `uid_owner`, `permissions`, `stime`, `file_source`,'
  309. .' `file_target`, `token`, `parent`) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)');
  310. $arguments = array($share['item_type'], $share['item_source'], $share['item_target'],
  311. 2, \OCP\User::getUser(), $share['uid_owner'], $share['permissions'], $share['stime'], $share['file_source'],
  312. $newPath, $share['token'], $share['id']);
  313. } else {
  314. // rename mount point
  315. $query = \OC_DB::prepare(
  316. 'Update `*PREFIX*share`
  317. SET `file_target` = ?
  318. WHERE `id` = ?'
  319. );
  320. $arguments = array($newPath, $share['id']);
  321. }
  322. return $query->execute($arguments);
  323. }
  324. public function rename($path1, $path2) {
  325. $sourceMountPoint = \OC\Files\Filesystem::getMountPoint($path1);
  326. $targetMountPoint = \OC\Files\Filesystem::getMountPoint($path2);
  327. $relPath1 = \OCA\Files_Sharing\Helper::stripUserFilesPath($path1);
  328. $relPath2 = \OCA\Files_Sharing\Helper::stripUserFilesPath($path2);
  329. // if we renamed the mount point we need to adjust the file_target in the
  330. // database
  331. if (\OC\Files\Filesystem::normalizePath($sourceMountPoint) === \OC\Files\Filesystem::normalizePath($path1)) {
  332. return $this->renameMountPoint($path1, $path2);
  333. }
  334. if ( // Within the same mount point, we only need UPDATE permissions
  335. ($sourceMountPoint === $targetMountPoint && $this->isUpdatable($sourceMountPoint)) ||
  336. // otherwise DELETE and CREATE permissions required
  337. ($this->isDeletable($path1) && $this->isCreatable(dirname($path2)))) {
  338. $pathinfo = pathinfo($relPath1);
  339. // for part files we need to ask for the owner and path from the parent directory because
  340. // the file cache doesn't return any results for part files
  341. if ($pathinfo['extension'] === 'part') {
  342. list($user1, $path1) = \OCA\Files_Sharing\Helper::getUidAndFilename($pathinfo['dirname']);
  343. $path1 = $path1 . '/' . $pathinfo['basename'];
  344. } else {
  345. list($user1, $path1) = \OCA\Files_Sharing\Helper::getUidAndFilename($relPath1);
  346. }
  347. $targetFilename = basename($relPath2);
  348. list($user2, $path2) = \OCA\Files_Sharing\Helper::getUidAndFilename(dirname($relPath2));
  349. $rootView = new \OC\Files\View('');
  350. return $rootView->rename('/' . $user1 . '/files/' . $path1, '/' . $user2 . '/files/' . $path2 . '/' . $targetFilename);
  351. }
  352. return false;
  353. }
  354. public function copy($path1, $path2) {
  355. // Copy the file if CREATE permission is granted
  356. if ($this->isCreatable(dirname($path2))) {
  357. $oldSource = $this->getSourcePath($path1);
  358. $newSource = $this->getSourcePath(dirname($path2)) . '/' . basename($path2);
  359. $rootView = new \OC\Files\View('');
  360. return $rootView->copy($oldSource, $newSource);
  361. }
  362. return false;
  363. }
  364. public function fopen($path, $mode) {
  365. if ($source = $this->getSourcePath($path)) {
  366. switch ($mode) {
  367. case 'r+':
  368. case 'rb+':
  369. case 'w+':
  370. case 'wb+':
  371. case 'x+':
  372. case 'xb+':
  373. case 'a+':
  374. case 'ab+':
  375. case 'w':
  376. case 'wb':
  377. case 'x':
  378. case 'xb':
  379. case 'a':
  380. case 'ab':
  381. $exists = $this->file_exists($path);
  382. if ($exists && !$this->isUpdatable($path)) {
  383. return false;
  384. }
  385. if (!$exists && !$this->isCreatable(dirname($path))) {
  386. return false;
  387. }
  388. }
  389. $info = array(
  390. 'target' => $this->getMountPoint() . $path,
  391. 'source' => $source,
  392. 'mode' => $mode,
  393. );
  394. \OCP\Util::emitHook('\OC\Files\Storage\Shared', 'fopen', $info);
  395. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  396. return $storage->fopen($internalPath, $mode);
  397. }
  398. return false;
  399. }
  400. public function getMimeType($path) {
  401. if ($source = $this->getSourcePath($path)) {
  402. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  403. return $storage->getMimeType($internalPath);
  404. }
  405. return false;
  406. }
  407. public function free_space($path) {
  408. $source = $this->getSourcePath($path);
  409. if ($source) {
  410. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  411. return $storage->free_space($internalPath);
  412. }
  413. return \OC\Files\SPACE_UNKNOWN;
  414. }
  415. public function getLocalFile($path) {
  416. if ($source = $this->getSourcePath($path)) {
  417. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  418. return $storage->getLocalFile($internalPath);
  419. }
  420. return false;
  421. }
  422. public function touch($path, $mtime = null) {
  423. if ($source = $this->getSourcePath($path)) {
  424. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  425. return $storage->touch($internalPath, $mtime);
  426. }
  427. return false;
  428. }
  429. public static function setup($options) {
  430. $shares = \OCP\Share::getItemsSharedWith('file');
  431. if (!\OCP\User::isLoggedIn() || \OCP\User::getUser() != $options['user']
  432. || $shares
  433. ) {
  434. foreach ($shares as $share) {
  435. self::verifyMountPoint($share);
  436. \OC\Files\Filesystem::mount('\OC\Files\Storage\Shared',
  437. array(
  438. 'share' => $share,
  439. ),
  440. $options['user_dir'] . '/' . $share['file_target']);
  441. }
  442. }
  443. }
  444. /**
  445. * check if the parent folder exists otherwise move the mount point up
  446. *
  447. * @param array $share reference to the share we want to check
  448. */
  449. private static function verifyMountPoint(&$share) {
  450. $mountPoint = basename($share['file_target']);
  451. $parent = dirname($share['file_target']);
  452. while (!\OC\Files\Filesystem::is_dir($parent)) {
  453. $parent = dirname($parent);
  454. }
  455. $newMountPoint = \OCA\Files_Sharing\Helper::generateUniqueTarget(
  456. \OC\Files\Filesystem::normalizePath($parent . '/' . $mountPoint),
  457. array(),
  458. new \OC\Files\View('/' . \OCP\User::getUser() . '/files')
  459. );
  460. if($newMountPoint !== $share['file_target']) {
  461. self::updateFileTarget($newMountPoint, $share);
  462. $share['file_target'] = $newMountPoint;
  463. }
  464. }
  465. /**
  466. * return mount point of share, relative to data/user/files
  467. *
  468. * @return string
  469. */
  470. public function getMountPoint() {
  471. return $this->share['file_target'];
  472. }
  473. private function setMountPoint($path) {
  474. $this->share['file_target'] = $path;
  475. }
  476. /**
  477. * the share now uses a unique name of this user
  478. *
  479. * @brief the share now uses a unique name of this user
  480. */
  481. private function setUniqueName() {
  482. $this->share['unique_name'] = true;
  483. }
  484. /**
  485. * @brief get the user who shared the file
  486. *
  487. * @return string
  488. */
  489. public function getSharedFrom() {
  490. return $this->share['uid_owner'];
  491. }
  492. /**
  493. * return share type, can be "file" or "folder"
  494. * @return string
  495. */
  496. public function getItemType() {
  497. return $this->share['item_type'];
  498. }
  499. public function hasUpdated($path, $time) {
  500. return $this->filemtime($path) > $time;
  501. }
  502. public function getCache($path = '') {
  503. return new \OC\Files\Cache\Shared_Cache($this);
  504. }
  505. public function getScanner($path = '') {
  506. return new \OC\Files\Cache\Scanner($this);
  507. }
  508. public function getWatcher($path = '') {
  509. return new \OC\Files\Cache\Shared_Watcher($this);
  510. }
  511. public function getOwner($path) {
  512. if ($path == '') {
  513. $path = $this->getMountPoint();
  514. }
  515. $source = $this->getFile($path);
  516. if ($source) {
  517. return $source['fileOwner'];
  518. }
  519. return false;
  520. }
  521. public function getETag($path) {
  522. if ($path == '') {
  523. $path = $this->getMountPoint();
  524. }
  525. if ($source = $this->getSourcePath($path)) {
  526. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  527. return $storage->getETag($internalPath);
  528. }
  529. return null;
  530. }
  531. }