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.

555 lines
16 KiB

  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)) {
  83. $this->files[$target]['fullPath'] = $mount[key($mount)]->getMountPoint() . $source['path'];
  84. } else {
  85. $this->files[$target]['fullPath'] = false;
  86. }
  87. }
  88. return $this->files[$target]['fullPath'];
  89. }
  90. return false;
  91. }
  92. /**
  93. * @brief Get the permissions granted for a shared file
  94. * @param string Shared target file path
  95. * @return int CRUDS permissions granted or false if not found
  96. */
  97. public function getPermissions($target) {
  98. $source = $this->getFile($target);
  99. if ($source) {
  100. return $source['permissions'];
  101. }
  102. return false;
  103. }
  104. public function mkdir($path) {
  105. if ($path == '' || $path == '/' || !$this->isCreatable(dirname($path))) {
  106. return false;
  107. } else if ($source = $this->getSourcePath($path)) {
  108. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  109. return $storage->mkdir($internalPath);
  110. }
  111. return false;
  112. }
  113. public function rmdir($path) {
  114. if (($source = $this->getSourcePath($path)) && $this->isDeletable($path)) {
  115. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  116. return $storage->rmdir($internalPath);
  117. }
  118. return false;
  119. }
  120. public function opendir($path) {
  121. $source = $this->getSourcePath($path);
  122. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  123. return $storage->opendir($internalPath);
  124. }
  125. public function is_dir($path) {
  126. $source = $this->getSourcePath($path);
  127. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  128. return $storage->is_dir($internalPath);
  129. }
  130. public function is_file($path) {
  131. if ($source = $this->getSourcePath($path)) {
  132. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  133. return $storage->is_file($internalPath);
  134. }
  135. return false;
  136. }
  137. public function stat($path) {
  138. if ($path == '' || $path == '/') {
  139. $stat['size'] = $this->filesize($path);
  140. $stat['mtime'] = $this->filemtime($path);
  141. return $stat;
  142. } else if ($source = $this->getSourcePath($path)) {
  143. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  144. return $storage->stat($internalPath);
  145. }
  146. return false;
  147. }
  148. public function filetype($path) {
  149. if ($path == '' || $path == '/') {
  150. return 'dir';
  151. } else if ($source = $this->getSourcePath($path)) {
  152. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  153. return $storage->filetype($internalPath);
  154. }
  155. return false;
  156. }
  157. public function filesize($path) {
  158. if ($path == '' || $path == '/' || $this->is_dir($path)) {
  159. return 0;
  160. } else if ($source = $this->getSourcePath($path)) {
  161. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  162. return $storage->filesize($internalPath);
  163. }
  164. return false;
  165. }
  166. public function isCreatable($path) {
  167. if ($path == '') {
  168. $path = $this->getMountPoint();
  169. }
  170. return ($this->getPermissions($path) & \OCP\PERMISSION_CREATE);
  171. }
  172. public function isReadable($path) {
  173. return $this->file_exists($path);
  174. }
  175. public function isUpdatable($path) {
  176. if ($path == '') {
  177. $path = $this->getMountPoint();
  178. }
  179. return ($this->getPermissions($path) & \OCP\PERMISSION_UPDATE);
  180. }
  181. public function isDeletable($path) {
  182. if ($path == '') {
  183. $path = $this->getMountPoint();
  184. }
  185. return ($this->getPermissions($path) & \OCP\PERMISSION_DELETE);
  186. }
  187. public function isSharable($path) {
  188. if ($path == '') {
  189. $path = $this->getMountPoint();
  190. }
  191. return ($this->getPermissions($path) & \OCP\PERMISSION_SHARE);
  192. }
  193. public function file_exists($path) {
  194. if ($path == '' || $path == '/') {
  195. return true;
  196. } else if ($source = $this->getSourcePath($path)) {
  197. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  198. return $storage->file_exists($internalPath);
  199. }
  200. return false;
  201. }
  202. public function filemtime($path) {
  203. $source = $this->getSourcePath($path);
  204. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  205. return $storage->filemtime($internalPath);
  206. }
  207. public function file_get_contents($path) {
  208. $source = $this->getSourcePath($path);
  209. if ($source) {
  210. $info = array(
  211. 'target' => $this->getMountPoint() . $path,
  212. 'source' => $source,
  213. );
  214. \OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_get_contents', $info);
  215. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  216. return $storage->file_get_contents($internalPath);
  217. }
  218. }
  219. public function file_put_contents($path, $data) {
  220. if ($source = $this->getSourcePath($path)) {
  221. // Check if permission is granted
  222. if (($this->file_exists($path) && !$this->isUpdatable($path))
  223. || ($this->is_dir($path) && !$this->isCreatable($path))
  224. ) {
  225. return false;
  226. }
  227. $info = array(
  228. 'target' => $this->getMountPoint() . '/' . $path,
  229. 'source' => $source,
  230. );
  231. \OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_put_contents', $info);
  232. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  233. $result = $storage->file_put_contents($internalPath, $data);
  234. return $result;
  235. }
  236. return false;
  237. }
  238. public function unlink($path) {
  239. // Delete the file if DELETE permission is granted
  240. $path = ($path === false) ? '' : $path;
  241. if ($source = $this->getSourcePath($path)) {
  242. if ($this->isDeletable($path)) {
  243. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  244. return $storage->unlink($internalPath);
  245. }
  246. }
  247. return false;
  248. }
  249. /**
  250. * @brief Format a path to be relative to the /user/files/ directory
  251. * @param string $path the absolute path
  252. * @return string e.g. turns '/admin/files/test.txt' into '/test.txt'
  253. */
  254. private static function stripUserFilesPath($path) {
  255. $trimmed = ltrim($path, '/');
  256. $split = explode('/', $trimmed);
  257. // it is not a file relative to data/user/files
  258. if (count($split) < 3 || $split[1] !== 'files') {
  259. \OCP\Util::writeLog('file sharing',
  260. 'Can not strip userid and "files/" from path: ' . $path,
  261. \OCP\Util::DEBUG);
  262. return false;
  263. }
  264. // skip 'user' and 'files'
  265. $sliced = array_slice($split, 2);
  266. $relPath = implode('/', $sliced);
  267. return '/' . $relPath;
  268. }
  269. /**
  270. * @brief rename a shared foder/file
  271. * @param string $sourcePath
  272. * @param string $targetPath
  273. * @return bool
  274. */
  275. private function renameMountPoint($sourcePath, $targetPath) {
  276. // it shoulbn't be possible to move a Shared storage into another one
  277. list($targetStorage, ) = \OC\Files\Filesystem::resolvePath($targetPath);
  278. if ($targetStorage instanceof \OC\Files\Storage\Shared) {
  279. \OCP\Util::writeLog('file sharing',
  280. 'It is not allowed to move one mount point into another one',
  281. \OCP\Util::DEBUG);
  282. return false;
  283. }
  284. $relTargetPath = $this->stripUserFilesPath($targetPath);
  285. // rename mount point
  286. $query = \OC_DB::prepare(
  287. 'Update `*PREFIX*share`
  288. SET `file_target` = ?
  289. WHERE `id` = ?'
  290. );
  291. $result = $query->execute(array($relTargetPath, $this->getShareId()));
  292. if ($result) {
  293. // update the mount manager with the new paths
  294. $mountManager = \OC\Files\Filesystem::getMountManager();
  295. $mount = $mountManager->find($sourcePath);
  296. $mount->setMountPoint($targetPath . '/');
  297. $mountManager->addMount($mount);
  298. $mountManager->removeMount($sourcePath . '/');
  299. } else {
  300. \OCP\Util::writeLog('file sharing',
  301. 'Could not rename mount point for shared folder "' . $sourcePath . '" to "' . $targetPath . '"',
  302. \OCP\Util::ERROR);
  303. }
  304. return $result;
  305. }
  306. public function rename($path1, $path2) {
  307. $sourceMountPoint = \OC\Files\Filesystem::getMountPoint($path1);
  308. // if we renamed the mount point we need to adjust the file_target in the
  309. // database
  310. if (\OC\Files\Filesystem::normalizePath($sourceMountPoint) === \OC\Files\Filesystem::normalizePath($path1)) {
  311. return $this->renameMountPoint($path1, $path2);
  312. }
  313. // Renaming/moving is only allowed within shared folders
  314. $oldSource = $this->getSourcePath($path1);
  315. if ($oldSource) {
  316. $newSource = $this->getSourcePath(dirname($path2)) . '/' . basename($path2);
  317. // Within the same folder, we only need UPDATE permissions
  318. if (dirname($path1) == dirname($path2) and $this->isUpdatable($path1)) {
  319. list($storage, $oldInternalPath) = \OC\Files\Filesystem::resolvePath($oldSource);
  320. list(, $newInternalPath) = \OC\Files\Filesystem::resolvePath($newSource);
  321. return $storage->rename($oldInternalPath, $newInternalPath);
  322. // otherwise DELETE and CREATE permissions required
  323. } elseif ($this->isDeletable($path1) && $this->isCreatable(dirname($path2))) {
  324. $rootView = new \OC\Files\View('');
  325. return $rootView->rename($oldSource, $newSource);
  326. }
  327. }
  328. return false;
  329. }
  330. public function copy($path1, $path2) {
  331. // Copy the file if CREATE permission is granted
  332. if ($this->isCreatable(dirname($path2))) {
  333. $oldSource = $this->getSourcePath($path1);
  334. $newSource = $this->getSourcePath(dirname($path2)) . '/' . basename($path2);
  335. $rootView = new \OC\Files\View('');
  336. return $rootView->copy($oldSource, $newSource);
  337. }
  338. return false;
  339. }
  340. public function fopen($path, $mode) {
  341. if ($source = $this->getSourcePath($path)) {
  342. switch ($mode) {
  343. case 'r+':
  344. case 'rb+':
  345. case 'w+':
  346. case 'wb+':
  347. case 'x+':
  348. case 'xb+':
  349. case 'a+':
  350. case 'ab+':
  351. case 'w':
  352. case 'wb':
  353. case 'x':
  354. case 'xb':
  355. case 'a':
  356. case 'ab':
  357. $exists = $this->file_exists($path);
  358. if ($exists && !$this->isUpdatable($path)) {
  359. return false;
  360. }
  361. if (!$exists && !$this->isCreatable(dirname($path))) {
  362. return false;
  363. }
  364. }
  365. $info = array(
  366. 'target' => $this->getMountPoint() . $path,
  367. 'source' => $source,
  368. 'mode' => $mode,
  369. );
  370. \OCP\Util::emitHook('\OC\Files\Storage\Shared', 'fopen', $info);
  371. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  372. return $storage->fopen($internalPath, $mode);
  373. }
  374. return false;
  375. }
  376. public function getMimeType($path) {
  377. if ($source = $this->getSourcePath($path)) {
  378. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  379. return $storage->getMimeType($internalPath);
  380. }
  381. return false;
  382. }
  383. public function free_space($path) {
  384. if ($path == '') {
  385. $path = $this->getMountPoint();
  386. }
  387. $source = $this->getSourcePath($path);
  388. if ($source) {
  389. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  390. return $storage->free_space($internalPath);
  391. }
  392. }
  393. public function getLocalFile($path) {
  394. if ($source = $this->getSourcePath($path)) {
  395. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  396. return $storage->getLocalFile($internalPath);
  397. }
  398. return false;
  399. }
  400. public function touch($path, $mtime = null) {
  401. if ($source = $this->getSourcePath($path)) {
  402. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  403. return $storage->touch($internalPath, $mtime);
  404. }
  405. return false;
  406. }
  407. public static function setup($options) {
  408. $shares = \OCP\Share::getItemsSharedWith('file');
  409. if (!\OCP\User::isLoggedIn() || \OCP\User::getUser() != $options['user']
  410. || $shares
  411. ) {
  412. foreach ($shares as $share) {
  413. \OC\Files\Filesystem::mount('\OC\Files\Storage\Shared',
  414. array(
  415. 'share' => $share,
  416. ),
  417. $options['user_dir'] . '/' . $share['file_target']);
  418. }
  419. }
  420. }
  421. /**
  422. * @brief return mount point of share, relative to data/user/files
  423. * @return string
  424. */
  425. public function getMountPoint() {
  426. return $this->share['file_target'];
  427. }
  428. /**
  429. * @brief get share type
  430. * @return integer can be single user share (0) group share (1), unique group share name (2)
  431. */
  432. private function getShareType() {
  433. return $this->share['share_type'];
  434. }
  435. /**
  436. * @brief get share ID
  437. * @return integer unique share ID
  438. */
  439. private function getShareId() {
  440. return $this->share['id'];
  441. }
  442. /**
  443. * @brief get the user who shared the file
  444. * @return string
  445. */
  446. public function getSharedFrom() {
  447. return $this->share['uid_owner'];
  448. }
  449. /**
  450. * @brief return share type, can be "file" or "folder"
  451. * @return string
  452. */
  453. public function getItemType() {
  454. return $this->share['item_type'];
  455. }
  456. public function hasUpdated($path, $time) {
  457. return $this->filemtime($path) > $time;
  458. }
  459. public function getCache($path = '') {
  460. return new \OC\Files\Cache\Shared_Cache($this);
  461. }
  462. public function getScanner($path = '') {
  463. return new \OC\Files\Cache\Scanner($this);
  464. }
  465. public function getPermissionsCache($path = '') {
  466. return new \OC\Files\Cache\Shared_Permissions($this);
  467. }
  468. public function getWatcher($path = '') {
  469. return new \OC\Files\Cache\Shared_Watcher($this);
  470. }
  471. public function getOwner($path) {
  472. if ($path == '') {
  473. $path = $this->getMountPoint();
  474. }
  475. $source = $this->getFile($path);
  476. if ($source) {
  477. return $source['fileOwner'];
  478. }
  479. return false;
  480. }
  481. public function getETag($path) {
  482. if ($path == '') {
  483. $path = $this->getMountPoint();
  484. }
  485. if ($source = $this->getSourcePath($path)) {
  486. list($storage, $internalPath) = \OC\Files\Filesystem::resolvePath($source);
  487. return $storage->getETag($internalPath);
  488. }
  489. return null;
  490. }
  491. }