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 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586
  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. * @breif get id of the mount point
  35. * @return string
  36. */
  37. public function getId() {
  38. return 'shared::' . $this->getMountPoint();
  39. }
  40. /**
  41. * @breif 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. * @brief Get the source file path, permissions, and owner for a shared file
  49. * @param string Shared target file path
  50. * @param string $target
  51. * @return Returns array with the keys path, permissions, and owner or false if not found
  52. */
  53. public function getFile($target) {
  54. if (!isset($this->files[$target])) {
  55. // Check for partial files
  56. if (pathinfo($target, PATHINFO_EXTENSION) === 'part') {
  57. $source = \OC_Share_Backend_File::getSource(substr($target, 0, -5), $this->getMountPoint(), $this->getItemType());
  58. if ($source) {
  59. $source['path'] .= '.part';
  60. // All partial files have delete permission
  61. $source['permissions'] |= \OCP\PERMISSION_DELETE;
  62. }
  63. } else {
  64. $source = \OC_Share_Backend_File::getSource($target, $this->getMountPoint(), $this->getItemType());
  65. }
  66. $this->files[$target] = $source;
  67. }
  68. return $this->files[$target];
  69. }
  70. /**
  71. * @brief Get the source file path for a shared file
  72. * @param string Shared target file path
  73. * @param string $target
  74. * @return string source file path or false if not found
  75. */
  76. public function getSourcePath($target) {
  77. $source = $this->getFile($target);
  78. if ($source) {
  79. if (!isset($source['fullPath'])) {
  80. \OC\Files\Filesystem::initMountPoints($source['fileOwner']);
  81. $mount = \OC\Files\Filesystem::getMountByNumericId($source['storage']);
  82. if (is_array($mount) && !empty($mount)) {
  83. $this->files[$target]['fullPath'] = $mount[key($mount)]->getMountPoint() . $source['path'];
  84. } else {
  85. $this->files[$target]['fullPath'] = false;
  86. \OCP\Util::writeLog('files_sharing', "Unable to get mount for shared storage '" . $source['storage'] . "' user '" . $source['fileOwner'] . "'", \OCP\Util::ERROR);
  87. }
  88. }
  89. return $this->files[$target]['fullPath'];
  90. }
  91. return false;
  92. }
  93. /**
  94. * @brief Get the permissions granted for a shared file
  95. * @param string Shared target file path
  96. * @return int CRUDS permissions granted
  97. */
  98. public function getPermissions($target) {
  99. $permissions = $this->share['permissions'];
  100. // part file are always have delete permissions
  101. if (pathinfo($target, PATHINFO_EXTENSION) === 'part') {
  102. $permissions |= \OCP\PERMISSION_DELETE;
  103. }
  104. return $permissions;
  105. }
  106. public function mkdir($path) {
  107. if ($path == '' || $path == '/' || !$this->isCreatable(dirname($path))) {
  108. return false;
  109. } else if ($source = $this->getSourcePath($path)) {
  110. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  111. return $storage->mkdir($internalPath);
  112. }
  113. return false;
  114. }
  115. public function rmdir($path) {
  116. if (($source = $this->getSourcePath($path)) && $this->isDeletable($path)) {
  117. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  118. return $storage->rmdir($internalPath);
  119. }
  120. return false;
  121. }
  122. public function opendir($path) {
  123. $source = $this->getSourcePath($path);
  124. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  125. return $storage->opendir($internalPath);
  126. }
  127. public function is_dir($path) {
  128. $source = $this->getSourcePath($path);
  129. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  130. return $storage->is_dir($internalPath);
  131. }
  132. public function is_file($path) {
  133. if ($source = $this->getSourcePath($path)) {
  134. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  135. return $storage->is_file($internalPath);
  136. }
  137. return false;
  138. }
  139. public function stat($path) {
  140. if ($path == '' || $path == '/') {
  141. $stat['size'] = $this->filesize($path);
  142. $stat['mtime'] = $this->filemtime($path);
  143. return $stat;
  144. } else if ($source = $this->getSourcePath($path)) {
  145. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  146. return $storage->stat($internalPath);
  147. }
  148. return false;
  149. }
  150. public function filetype($path) {
  151. if ($path == '' || $path == '/') {
  152. return 'dir';
  153. } else if ($source = $this->getSourcePath($path)) {
  154. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  155. return $storage->filetype($internalPath);
  156. }
  157. return false;
  158. }
  159. public function filesize($path) {
  160. if ($path == '' || $path == '/' || $this->is_dir($path)) {
  161. return 0;
  162. } else if ($source = $this->getSourcePath($path)) {
  163. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  164. return $storage->filesize($internalPath);
  165. }
  166. return false;
  167. }
  168. public function isCreatable($path) {
  169. return ($this->getPermissions($path) & \OCP\PERMISSION_CREATE);
  170. }
  171. public function isReadable($path) {
  172. return $this->file_exists($path);
  173. }
  174. public function isUpdatable($path) {
  175. return ($this->getPermissions($path) & \OCP\PERMISSION_UPDATE);
  176. }
  177. public function isDeletable($path) {
  178. return ($this->getPermissions($path) & \OCP\PERMISSION_DELETE);
  179. }
  180. public function isSharable($path) {
  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. * @brief 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. * @brief 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 instanceof \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 the user renames a mount point from a group share we need to create a new db entry
  276. // for the unique name
  277. if ($this->getShareType() === \OCP\Share::SHARE_TYPE_GROUP && $this->uniqueNameSet() === false) {
  278. $query = \OC_DB::prepare('INSERT INTO `*PREFIX*share` (`item_type`, `item_source`, `item_target`,'
  279. .' `share_type`, `share_with`, `uid_owner`, `permissions`, `stime`, `file_source`,'
  280. .' `file_target`, `token`, `parent`) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)');
  281. $arguments = array($this->share['item_type'], $this->share['item_source'], $this->share['item_target'],
  282. 2, \OCP\User::getUser(), $this->share['uid_owner'], $this->share['permissions'], $this->share['stime'], $this->share['file_source'],
  283. $relTargetPath, $this->share['token'], $this->share['id']);
  284. } else {
  285. // rename mount point
  286. $query = \OC_DB::prepare(
  287. 'Update `*PREFIX*share`
  288. SET `file_target` = ?
  289. WHERE `id` = ?'
  290. );
  291. $arguments = array($relTargetPath, $this->getShareId());
  292. }
  293. $result = $query->execute($arguments);
  294. if ($result) {
  295. // update the mount manager with the new paths
  296. $mountManager = \OC\Files\Filesystem::getMountManager();
  297. $mount = $mountManager->find($sourcePath);
  298. $mount->setMountPoint($targetPath . '/');
  299. $mountManager->addMount($mount);
  300. $mountManager->removeMount($sourcePath . '/');
  301. $this->setUniqueName();
  302. $this->setMountPoint($relTargetPath);
  303. } else {
  304. \OCP\Util::writeLog('file sharing',
  305. 'Could not rename mount point for shared folder "' . $sourcePath . '" to "' . $targetPath . '"',
  306. \OCP\Util::ERROR);
  307. }
  308. return $result;
  309. }
  310. public function rename($path1, $path2) {
  311. $sourceMountPoint = \OC\Files\Filesystem::getMountPoint($path1);
  312. $targetMountPoint = \OC\Files\Filesystem::getMountPoint($path2);
  313. $relPath1 = \OCA\Files_Sharing\Helper::stripUserFilesPath($path1);
  314. $relPath2 = \OCA\Files_Sharing\Helper::stripUserFilesPath($path2);
  315. // if we renamed the mount point we need to adjust the file_target in the
  316. // database
  317. if (\OC\Files\Filesystem::normalizePath($sourceMountPoint) === \OC\Files\Filesystem::normalizePath($path1)) {
  318. return $this->renameMountPoint($path1, $path2);
  319. }
  320. if ( // Within the same mount point, we only need UPDATE permissions
  321. ($sourceMountPoint === $targetMountPoint && $this->isUpdatable($sourceMountPoint)) ||
  322. // otherwise DELETE and CREATE permissions required
  323. ($this->isDeletable($path1) && $this->isCreatable(dirname($path2)))) {
  324. $pathinfo = pathinfo($relPath1);
  325. // for part files we need to ask for the owner and path from the parent directory because
  326. // the file cache doesn't return any results for part files
  327. if ($pathinfo['extension'] === 'part') {
  328. list($user1, $path1) = \OCA\Files_Sharing\Helper::getUidAndFilename($pathinfo['dirname']);
  329. $path1 = $path1 . '/' . $pathinfo['basename'];
  330. } else {
  331. list($user1, $path1) = \OCA\Files_Sharing\Helper::getUidAndFilename($relPath1);
  332. }
  333. $targetFilename = basename($relPath2);
  334. list($user2, $path2) = \OCA\Files_Sharing\Helper::getUidAndFilename(dirname($relPath2));
  335. $rootView = new \OC\Files\View('');
  336. return $rootView->rename('/' . $user1 . '/files/' . $path1, '/' . $user2 . '/files/' . $path2 . '/' . $targetFilename);
  337. }
  338. return false;
  339. }
  340. public function copy($path1, $path2) {
  341. // Copy the file if CREATE permission is granted
  342. if ($this->isCreatable(dirname($path2))) {
  343. $oldSource = $this->getSourcePath($path1);
  344. $newSource = $this->getSourcePath(dirname($path2)) . '/' . basename($path2);
  345. $rootView = new \OC\Files\View('');
  346. return $rootView->copy($oldSource, $newSource);
  347. }
  348. return false;
  349. }
  350. public function fopen($path, $mode) {
  351. if ($source = $this->getSourcePath($path)) {
  352. switch ($mode) {
  353. case 'r+':
  354. case 'rb+':
  355. case 'w+':
  356. case 'wb+':
  357. case 'x+':
  358. case 'xb+':
  359. case 'a+':
  360. case 'ab+':
  361. case 'w':
  362. case 'wb':
  363. case 'x':
  364. case 'xb':
  365. case 'a':
  366. case 'ab':
  367. $exists = $this->file_exists($path);
  368. if ($exists && !$this->isUpdatable($path)) {
  369. return false;
  370. }
  371. if (!$exists && !$this->isCreatable(dirname($path))) {
  372. return false;
  373. }
  374. }
  375. $info = array(
  376. 'target' => $this->getMountPoint() . $path,
  377. 'source' => $source,
  378. 'mode' => $mode,
  379. );
  380. \OCP\Util::emitHook('\OC\Files\Storage\Shared', 'fopen', $info);
  381. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  382. return $storage->fopen($internalPath, $mode);
  383. }
  384. return false;
  385. }
  386. public function getMimeType($path) {
  387. if ($source = $this->getSourcePath($path)) {
  388. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  389. return $storage->getMimeType($internalPath);
  390. }
  391. return false;
  392. }
  393. public function free_space($path) {
  394. $source = $this->getSourcePath($path);
  395. if ($source) {
  396. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  397. return $storage->free_space($internalPath);
  398. }
  399. return \OC\Files\SPACE_UNKNOWN;
  400. }
  401. public function getLocalFile($path) {
  402. if ($source = $this->getSourcePath($path)) {
  403. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  404. return $storage->getLocalFile($internalPath);
  405. }
  406. return false;
  407. }
  408. public function touch($path, $mtime = null) {
  409. if ($source = $this->getSourcePath($path)) {
  410. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  411. return $storage->touch($internalPath, $mtime);
  412. }
  413. return false;
  414. }
  415. public static function setup($options) {
  416. $shares = \OCP\Share::getItemsSharedWith('file');
  417. if (!\OCP\User::isLoggedIn() || \OCP\User::getUser() != $options['user']
  418. || $shares
  419. ) {
  420. foreach ($shares as $share) {
  421. \OC\Files\Filesystem::mount('\OC\Files\Storage\Shared',
  422. array(
  423. 'share' => $share,
  424. ),
  425. $options['user_dir'] . '/' . $share['file_target']);
  426. }
  427. }
  428. }
  429. /**
  430. * @brief return mount point of share, relative to data/user/files
  431. * @return string
  432. */
  433. public function getMountPoint() {
  434. return $this->share['file_target'];
  435. }
  436. /**
  437. * @brief get share type
  438. * @return integer can be single user share (0) group share (1), unique group share name (2)
  439. */
  440. private function getShareType() {
  441. return $this->share['share_type'];
  442. }
  443. private function setMountPoint($path) {
  444. $this->share['file_target'] = $path;
  445. }
  446. /**
  447. * @brief does the group share already has a user specific unique name
  448. * @return bool
  449. */
  450. private function uniqueNameSet() {
  451. return (isset($this->share['unique_name']) && $this->share['unique_name']);
  452. }
  453. /**
  454. * @brief the share now uses a unique name of this user
  455. */
  456. private function setUniqueName() {
  457. $this->share['unique_name'] = true;
  458. }
  459. /**
  460. * @brief get share ID
  461. * @return integer unique share ID
  462. */
  463. private function getShareId() {
  464. return $this->share['id'];
  465. }
  466. /**
  467. * @brief get the user who shared the file
  468. * @return string
  469. */
  470. public function getSharedFrom() {
  471. return $this->share['uid_owner'];
  472. }
  473. /**
  474. * @brief return share type, can be "file" or "folder"
  475. * @return string
  476. */
  477. public function getItemType() {
  478. return $this->share['item_type'];
  479. }
  480. public function hasUpdated($path, $time) {
  481. return $this->filemtime($path) > $time;
  482. }
  483. public function getCache($path = '') {
  484. return new \OC\Files\Cache\Shared_Cache($this);
  485. }
  486. public function getScanner($path = '') {
  487. return new \OC\Files\Cache\Scanner($this);
  488. }
  489. public function getPermissionsCache($path = '') {
  490. return new \OC\Files\Cache\Shared_Permissions($this);
  491. }
  492. public function getWatcher($path = '') {
  493. return new \OC\Files\Cache\Shared_Watcher($this);
  494. }
  495. public function getOwner($path) {
  496. if ($path == '') {
  497. $path = $this->getMountPoint();
  498. }
  499. $source = $this->getFile($path);
  500. if ($source) {
  501. return $source['fileOwner'];
  502. }
  503. return false;
  504. }
  505. public function getETag($path) {
  506. if ($path == '') {
  507. $path = $this->getMountPoint();
  508. }
  509. if ($source = $this->getSourcePath($path)) {
  510. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  511. return $storage->getETag($internalPath);
  512. }
  513. return null;
  514. }
  515. }