SharedStorage.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. *
  5. * @author Bart Visscher <bartv@thisnet.nl>
  6. * @author Björn Schießle <bjoern@schiessle.org>
  7. * @author Joas Schilling <coding@schilljs.com>
  8. * @author Michael Gapczynski <GapczynskiM@gmail.com>
  9. * @author Morris Jobke <hey@morrisjobke.de>
  10. * @author Robin Appelman <robin@icewind.nl>
  11. * @author Robin McCorkell <robin@mccorkell.me.uk>
  12. * @author Roeland Jago Douma <roeland@famdouma.nl>
  13. * @author scambra <sergio@entrecables.com>
  14. * @author Thomas Müller <thomas.mueller@tmit.eu>
  15. * @author Vincent Petry <pvince81@owncloud.com>
  16. *
  17. * @license AGPL-3.0
  18. *
  19. * This code is free software: you can redistribute it and/or modify
  20. * it under the terms of the GNU Affero General Public License, version 3,
  21. * as published by the Free Software Foundation.
  22. *
  23. * This program is distributed in the hope that it will be useful,
  24. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  25. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  26. * GNU Affero General Public License for more details.
  27. *
  28. * You should have received a copy of the GNU Affero General Public License, version 3,
  29. * along with this program. If not, see <http://www.gnu.org/licenses/>
  30. *
  31. */
  32. namespace OCA\Files_Sharing;
  33. use OC\Files\Cache\FailedCache;
  34. use OC\Files\Cache\NullWatcher;
  35. use OC\Files\Cache\Watcher;
  36. use OC\Files\Filesystem;
  37. use OC\Files\Storage\FailedStorage;
  38. use OC\Files\Storage\Wrapper\PermissionsMask;
  39. use OC\User\NoUserException;
  40. use OCA\Files_External\Config\ExternalMountPoint;
  41. use OCP\Constants;
  42. use OCP\Files\Cache\ICacheEntry;
  43. use OCP\Files\NotFoundException;
  44. use OCP\Files\Storage\IDisableEncryptionStorage;
  45. use OCP\Files\Storage\IStorage;
  46. use OCP\Lock\ILockingProvider;
  47. /**
  48. * Convert target path to source path and pass the function call to the correct storage provider
  49. */
  50. class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedStorage, IDisableEncryptionStorage {
  51. /** @var \OCP\Share\IShare */
  52. private $superShare;
  53. /** @var \OCP\Share\IShare[] */
  54. private $groupedShares;
  55. /**
  56. * @var \OC\Files\View
  57. */
  58. private $ownerView;
  59. private $initialized = false;
  60. /**
  61. * @var ICacheEntry
  62. */
  63. private $sourceRootInfo;
  64. /** @var string */
  65. private $user;
  66. /**
  67. * @var \OCP\ILogger
  68. */
  69. private $logger;
  70. /** @var IStorage */
  71. private $nonMaskedStorage;
  72. private $options;
  73. /** @var boolean */
  74. private $sharingDisabledForUser;
  75. public function __construct($arguments) {
  76. $this->ownerView = $arguments['ownerView'];
  77. $this->logger = \OC::$server->getLogger();
  78. $this->superShare = $arguments['superShare'];
  79. $this->groupedShares = $arguments['groupedShares'];
  80. $this->user = $arguments['user'];
  81. if (isset($arguments['sharingDisabledForUser'])) {
  82. $this->sharingDisabledForUser = $arguments['sharingDisabledForUser'];
  83. } else {
  84. $this->sharingDisabledForUser = false;
  85. }
  86. parent::__construct([
  87. 'storage' => null,
  88. 'root' => null,
  89. ]);
  90. }
  91. /**
  92. * @return ICacheEntry
  93. */
  94. private function getSourceRootInfo() {
  95. if (is_null($this->sourceRootInfo)) {
  96. if (is_null($this->superShare->getNodeCacheEntry())) {
  97. $this->init();
  98. $this->sourceRootInfo = $this->nonMaskedStorage->getCache()->get($this->rootPath);
  99. } else {
  100. $this->sourceRootInfo = $this->superShare->getNodeCacheEntry();
  101. }
  102. }
  103. return $this->sourceRootInfo;
  104. }
  105. private function init() {
  106. if ($this->initialized) {
  107. return;
  108. }
  109. $this->initialized = true;
  110. try {
  111. Filesystem::initMountPoints($this->superShare->getShareOwner());
  112. $storageId = $this->superShare->getNodeCacheEntry() ? $this->superShare->getNodeCacheEntry()->getStorageId() : null;
  113. $sourcePath = $this->ownerView->getPath($this->superShare->getNodeId(), $storageId);
  114. [$this->nonMaskedStorage, $this->rootPath] = $this->ownerView->resolvePath($sourcePath);
  115. $this->storage = new PermissionsMask([
  116. 'storage' => $this->nonMaskedStorage,
  117. 'mask' => $this->superShare->getPermissions(),
  118. ]);
  119. } catch (NotFoundException $e) {
  120. // original file not accessible or deleted, set FailedStorage
  121. $this->storage = new FailedStorage(['exception' => $e]);
  122. $this->cache = new FailedCache();
  123. $this->rootPath = '';
  124. } catch (NoUserException $e) {
  125. // sharer user deleted, set FailedStorage
  126. $this->storage = new FailedStorage(['exception' => $e]);
  127. $this->cache = new FailedCache();
  128. $this->rootPath = '';
  129. } catch (\Exception $e) {
  130. $this->storage = new FailedStorage(['exception' => $e]);
  131. $this->cache = new FailedCache();
  132. $this->rootPath = '';
  133. $this->logger->logException($e);
  134. }
  135. if (!$this->nonMaskedStorage) {
  136. $this->nonMaskedStorage = $this->storage;
  137. }
  138. }
  139. /**
  140. * @inheritdoc
  141. */
  142. public function instanceOfStorage($class) {
  143. if ($class === '\OC\Files\Storage\Common') {
  144. return true;
  145. }
  146. if (in_array($class, ['\OC\Files\Storage\Home', '\OC\Files\ObjectStore\HomeObjectStoreStorage', '\OCP\Files\IHomeStorage'])) {
  147. return false;
  148. }
  149. return parent::instanceOfStorage($class);
  150. }
  151. /**
  152. * @return string
  153. */
  154. public function getShareId() {
  155. return $this->superShare->getId();
  156. }
  157. private function isValid() {
  158. return $this->getSourceRootInfo() && ($this->getSourceRootInfo()->getPermissions() & Constants::PERMISSION_SHARE) === Constants::PERMISSION_SHARE;
  159. }
  160. /**
  161. * get id of the mount point
  162. *
  163. * @return string
  164. */
  165. public function getId() {
  166. return 'shared::' . $this->getMountPoint();
  167. }
  168. /**
  169. * Get the permissions granted for a shared file
  170. *
  171. * @param string $target Shared target file path
  172. * @return int CRUDS permissions granted
  173. */
  174. public function getPermissions($target = '') {
  175. if (!$this->isValid()) {
  176. return 0;
  177. }
  178. $permissions = parent::getPermissions($target) & $this->superShare->getPermissions();
  179. // part files and the mount point always have delete permissions
  180. if ($target === '' || pathinfo($target, PATHINFO_EXTENSION) === 'part') {
  181. $permissions |= \OCP\Constants::PERMISSION_DELETE;
  182. }
  183. if ($this->sharingDisabledForUser) {
  184. $permissions &= ~\OCP\Constants::PERMISSION_SHARE;
  185. }
  186. return $permissions;
  187. }
  188. public function isCreatable($path) {
  189. return ($this->getPermissions($path) & \OCP\Constants::PERMISSION_CREATE);
  190. }
  191. public function isReadable($path) {
  192. if (!$this->isValid()) {
  193. return false;
  194. }
  195. if (!$this->file_exists($path)) {
  196. return false;
  197. }
  198. /** @var IStorage $storage */
  199. /** @var string $internalPath */
  200. [$storage, $internalPath] = $this->resolvePath($path);
  201. return $storage->isReadable($internalPath);
  202. }
  203. public function isUpdatable($path) {
  204. return ($this->getPermissions($path) & \OCP\Constants::PERMISSION_UPDATE);
  205. }
  206. public function isDeletable($path) {
  207. return ($this->getPermissions($path) & \OCP\Constants::PERMISSION_DELETE);
  208. }
  209. public function isSharable($path) {
  210. if (\OCP\Util::isSharingDisabledForUser() || !\OC\Share\Share::isResharingAllowed()) {
  211. return false;
  212. }
  213. return ($this->getPermissions($path) & \OCP\Constants::PERMISSION_SHARE);
  214. }
  215. public function fopen($path, $mode) {
  216. $source = $this->getUnjailedPath($path);
  217. switch ($mode) {
  218. case 'r+':
  219. case 'rb+':
  220. case 'w+':
  221. case 'wb+':
  222. case 'x+':
  223. case 'xb+':
  224. case 'a+':
  225. case 'ab+':
  226. case 'w':
  227. case 'wb':
  228. case 'x':
  229. case 'xb':
  230. case 'a':
  231. case 'ab':
  232. $creatable = $this->isCreatable(dirname($path));
  233. $updatable = $this->isUpdatable($path);
  234. // if neither permissions given, no need to continue
  235. if (!$creatable && !$updatable) {
  236. if (pathinfo($path, PATHINFO_EXTENSION) === 'part') {
  237. $updatable = $this->isUpdatable(dirname($path));
  238. }
  239. if (!$updatable) {
  240. return false;
  241. }
  242. }
  243. $exists = $this->file_exists($path);
  244. // if a file exists, updatable permissions are required
  245. if ($exists && !$updatable) {
  246. return false;
  247. }
  248. // part file is allowed if !$creatable but the final file is $updatable
  249. if (pathinfo($path, PATHINFO_EXTENSION) !== 'part') {
  250. if (!$exists && !$creatable) {
  251. return false;
  252. }
  253. }
  254. }
  255. $info = [
  256. 'target' => $this->getMountPoint() . '/' . $path,
  257. 'source' => $source,
  258. 'mode' => $mode,
  259. ];
  260. \OCP\Util::emitHook('\OC\Files\Storage\Shared', 'fopen', $info);
  261. return $this->nonMaskedStorage->fopen($this->getUnjailedPath($path), $mode);
  262. }
  263. /**
  264. * see http://php.net/manual/en/function.rename.php
  265. *
  266. * @param string $path1
  267. * @param string $path2
  268. * @return bool
  269. */
  270. public function rename($path1, $path2) {
  271. $this->init();
  272. $isPartFile = pathinfo($path1, PATHINFO_EXTENSION) === 'part';
  273. $targetExists = $this->file_exists($path2);
  274. $sameFolder = dirname($path1) === dirname($path2);
  275. if ($targetExists || ($sameFolder && !$isPartFile)) {
  276. if (!$this->isUpdatable('')) {
  277. return false;
  278. }
  279. } else {
  280. if (!$this->isCreatable('')) {
  281. return false;
  282. }
  283. }
  284. return $this->nonMaskedStorage->rename($this->getUnjailedPath($path1), $this->getUnjailedPath($path2));
  285. }
  286. /**
  287. * return mount point of share, relative to data/user/files
  288. *
  289. * @return string
  290. */
  291. public function getMountPoint() {
  292. return $this->superShare->getTarget();
  293. }
  294. /**
  295. * @param string $path
  296. */
  297. public function setMountPoint($path) {
  298. $this->superShare->setTarget($path);
  299. foreach ($this->groupedShares as $share) {
  300. $share->setTarget($path);
  301. }
  302. }
  303. /**
  304. * get the user who shared the file
  305. *
  306. * @return string
  307. */
  308. public function getSharedFrom() {
  309. return $this->superShare->getShareOwner();
  310. }
  311. /**
  312. * @return \OCP\Share\IShare
  313. */
  314. public function getShare() {
  315. return $this->superShare;
  316. }
  317. /**
  318. * return share type, can be "file" or "folder"
  319. *
  320. * @return string
  321. */
  322. public function getItemType() {
  323. return $this->superShare->getNodeType();
  324. }
  325. /**
  326. * @param string $path
  327. * @param null $storage
  328. * @return Cache
  329. */
  330. public function getCache($path = '', $storage = null) {
  331. if ($this->cache) {
  332. return $this->cache;
  333. }
  334. if (!$storage) {
  335. $storage = $this;
  336. }
  337. $sourceRoot = $this->getSourceRootInfo();
  338. if ($this->storage instanceof FailedStorage) {
  339. return new FailedCache();
  340. }
  341. $this->cache = new \OCA\Files_Sharing\Cache($storage, $sourceRoot, $this->superShare);
  342. return $this->cache;
  343. }
  344. public function getScanner($path = '', $storage = null) {
  345. if (!$storage) {
  346. $storage = $this;
  347. }
  348. return new \OCA\Files_Sharing\Scanner($storage);
  349. }
  350. public function getOwner($path) {
  351. return $this->superShare->getShareOwner();
  352. }
  353. public function getWatcher($path = '', $storage = null): Watcher {
  354. $mountManager = \OC::$server->getMountManager();
  355. // Get node informations
  356. $node = $this->getShare()->getNodeCacheEntry();
  357. if ($node) {
  358. $mount = $mountManager->findByNumericId($node->getStorageId());
  359. // If the share is originating from an external storage
  360. if (count($mount) > 0 && $mount[0] instanceof ExternalMountPoint) {
  361. // Propagate original storage scan
  362. return parent::getWatcher($path, $storage);
  363. }
  364. }
  365. // cache updating is handled by the share source
  366. return new NullWatcher();
  367. }
  368. /**
  369. * unshare complete storage, also the grouped shares
  370. *
  371. * @return bool
  372. */
  373. public function unshareStorage() {
  374. foreach ($this->groupedShares as $share) {
  375. \OC::$server->getShareManager()->deleteFromSelf($share, $this->user);
  376. }
  377. return true;
  378. }
  379. /**
  380. * @param string $path
  381. * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
  382. * @param \OCP\Lock\ILockingProvider $provider
  383. * @throws \OCP\Lock\LockedException
  384. */
  385. public function acquireLock($path, $type, ILockingProvider $provider) {
  386. /** @var \OCP\Files\Storage $targetStorage */
  387. [$targetStorage, $targetInternalPath] = $this->resolvePath($path);
  388. $targetStorage->acquireLock($targetInternalPath, $type, $provider);
  389. // lock the parent folders of the owner when locking the share as recipient
  390. if ($path === '') {
  391. $sourcePath = $this->ownerView->getPath($this->superShare->getNodeId());
  392. $this->ownerView->lockFile(dirname($sourcePath), ILockingProvider::LOCK_SHARED, true);
  393. }
  394. }
  395. /**
  396. * @param string $path
  397. * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
  398. * @param \OCP\Lock\ILockingProvider $provider
  399. */
  400. public function releaseLock($path, $type, ILockingProvider $provider) {
  401. /** @var \OCP\Files\Storage $targetStorage */
  402. [$targetStorage, $targetInternalPath] = $this->resolvePath($path);
  403. $targetStorage->releaseLock($targetInternalPath, $type, $provider);
  404. // unlock the parent folders of the owner when unlocking the share as recipient
  405. if ($path === '') {
  406. $sourcePath = $this->ownerView->getPath($this->superShare->getNodeId());
  407. $this->ownerView->unlockFile(dirname($sourcePath), ILockingProvider::LOCK_SHARED, true);
  408. }
  409. }
  410. /**
  411. * @param string $path
  412. * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE
  413. * @param \OCP\Lock\ILockingProvider $provider
  414. */
  415. public function changeLock($path, $type, ILockingProvider $provider) {
  416. /** @var \OCP\Files\Storage $targetStorage */
  417. [$targetStorage, $targetInternalPath] = $this->resolvePath($path);
  418. $targetStorage->changeLock($targetInternalPath, $type, $provider);
  419. }
  420. /**
  421. * @return array [ available, last_checked ]
  422. */
  423. public function getAvailability() {
  424. // shares do not participate in availability logic
  425. return [
  426. 'available' => true,
  427. 'last_checked' => 0,
  428. ];
  429. }
  430. /**
  431. * @param bool $available
  432. */
  433. public function setAvailability($available) {
  434. // shares do not participate in availability logic
  435. }
  436. public function getSourceStorage() {
  437. $this->init();
  438. return $this->nonMaskedStorage;
  439. }
  440. public function getWrapperStorage() {
  441. $this->init();
  442. return $this->storage;
  443. }
  444. public function file_get_contents($path) {
  445. $info = [
  446. 'target' => $this->getMountPoint() . '/' . $path,
  447. 'source' => $this->getUnjailedPath($path),
  448. ];
  449. \OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_get_contents', $info);
  450. return parent::file_get_contents($path);
  451. }
  452. public function file_put_contents($path, $data) {
  453. $info = [
  454. 'target' => $this->getMountPoint() . '/' . $path,
  455. 'source' => $this->getUnjailedPath($path),
  456. ];
  457. \OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_put_contents', $info);
  458. return parent::file_put_contents($path, $data);
  459. }
  460. public function setMountOptions(array $options) {
  461. $this->mountOptions = $options;
  462. }
  463. public function getUnjailedPath($path) {
  464. $this->init();
  465. return parent::getUnjailedPath($path);
  466. }
  467. }