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.

Share.php 34KB

10 vuotta sitten
7 vuotta sitten
7 vuotta sitten
7 vuotta sitten
8 vuotta sitten
7 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
8 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
9 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
8 vuotta sitten
9 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
9 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
10 vuotta sitten
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922
  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 Bernhard Reiter <ockham@raz.or.at>
  8. * @author Björn Schießle <bjoern@schiessle.org>
  9. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  10. * @author Joas Schilling <coding@schilljs.com>
  11. * @author Morris Jobke <hey@morrisjobke.de>
  12. * @author Robin Appelman <robin@icewind.nl>
  13. * @author Robin McCorkell <robin@mccorkell.me.uk>
  14. * @author Roeland Jago Douma <roeland@famdouma.nl>
  15. * @author Sebastian Döll <sebastian.doell@libasys.de>
  16. * @author Thomas Müller <thomas.mueller@tmit.eu>
  17. * @author Vincent Petry <vincent@nextcloud.com>
  18. * @author Volkan Gezer <volkangezer@gmail.com>
  19. *
  20. * @license AGPL-3.0
  21. *
  22. * This code is free software: you can redistribute it and/or modify
  23. * it under the terms of the GNU Affero General Public License, version 3,
  24. * as published by the Free Software Foundation.
  25. *
  26. * This program is distributed in the hope that it will be useful,
  27. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  28. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  29. * GNU Affero General Public License for more details.
  30. *
  31. * You should have received a copy of the GNU Affero General Public License, version 3,
  32. * along with this program. If not, see <http://www.gnu.org/licenses/>
  33. *
  34. */
  35. namespace OC\Share;
  36. use OCP\DB\QueryBuilder\IQueryBuilder;
  37. use OCP\ILogger;
  38. use OCP\Share\IShare;
  39. /**
  40. * This class provides the ability for apps to share their content between users.
  41. * Apps must create a backend class that implements OCP\Share_Backend and register it with this class.
  42. *
  43. * It provides the following hooks:
  44. * - post_shared
  45. */
  46. class Share extends Constants {
  47. /** CRUDS permissions (Create, Read, Update, Delete, Share) using a bitmask
  48. * Construct permissions for share() and setPermissions with Or (|) e.g.
  49. * Give user read and update permissions: PERMISSION_READ | PERMISSION_UPDATE
  50. *
  51. * Check if permission is granted with And (&) e.g. Check if delete is
  52. * granted: if ($permissions & PERMISSION_DELETE)
  53. *
  54. * Remove permissions with And (&) and Not (~) e.g. Remove the update
  55. * permission: $permissions &= ~PERMISSION_UPDATE
  56. *
  57. * Apps are required to handle permissions on their own, this class only
  58. * stores and manages the permissions of shares
  59. * @see lib/public/constants.php
  60. */
  61. /**
  62. * Register a sharing backend class that implements OCP\Share_Backend for an item type
  63. * @param string $itemType Item type
  64. * @param string $class Backend class
  65. * @param string $collectionOf (optional) Depends on item type
  66. * @param array $supportedFileExtensions (optional) List of supported file extensions if this item type depends on files
  67. * @return boolean true if backend is registered or false if error
  68. */
  69. public static function registerBackend($itemType, $class, $collectionOf = null, $supportedFileExtensions = null) {
  70. if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_enabled', 'yes') == 'yes') {
  71. if (!isset(self::$backendTypes[$itemType])) {
  72. self::$backendTypes[$itemType] = [
  73. 'class' => $class,
  74. 'collectionOf' => $collectionOf,
  75. 'supportedFileExtensions' => $supportedFileExtensions
  76. ];
  77. return true;
  78. }
  79. \OCP\Util::writeLog('OCP\Share',
  80. 'Sharing backend '.$class.' not registered, '.self::$backendTypes[$itemType]['class']
  81. .' is already registered for '.$itemType,
  82. ILogger::WARN);
  83. }
  84. return false;
  85. }
  86. /**
  87. * Get the items of item type shared with the current user
  88. * @param string $itemType
  89. * @param int $format (optional) Format type must be defined by the backend
  90. * @param mixed $parameters (optional)
  91. * @param int $limit Number of items to return (optional) Returns all by default
  92. * @param boolean $includeCollections (optional)
  93. * @return mixed Return depends on format
  94. * @deprecated TESTS ONLY - this methods is only used by tests
  95. * called like this:
  96. * \OC\Share\Share::getItemsSharedWith('folder'); (apps/files_sharing/tests/UpdaterTest.php)
  97. */
  98. public static function getItemsSharedWith() {
  99. return self::getItems('folder', null, self::$shareTypeUserAndGroups, \OC_User::getUser());
  100. }
  101. /**
  102. * Get the items of item type shared with a user
  103. * @param string $itemType
  104. * @param string $user id for which user we want the shares
  105. * @param int $format (optional) Format type must be defined by the backend
  106. * @param mixed $parameters (optional)
  107. * @param int $limit Number of items to return (optional) Returns all by default
  108. * @param boolean $includeCollections (optional)
  109. * @return mixed Return depends on format
  110. * @deprecated TESTS ONLY - this methods is only used by tests
  111. * called like this:
  112. * \OC\Share\Share::getItemsSharedWithUser('test', $shareWith); (tests/lib/Share/Backend.php)
  113. */
  114. public static function getItemsSharedWithUser($itemType, $user) {
  115. return self::getItems('test', null, self::$shareTypeUserAndGroups, $user);
  116. }
  117. /**
  118. * Get the item of item type shared with a given user by source
  119. * @param string $itemType
  120. * @param string $itemSource
  121. * @param string $user User to whom the item was shared
  122. * @param string $owner Owner of the share
  123. * @param int $shareType only look for a specific share type
  124. * @return array Return list of items with file_target, permissions and expiration
  125. */
  126. public static function getItemSharedWithUser($itemType, $itemSource, $user, $owner = null, $shareType = null) {
  127. $shares = [];
  128. $fileDependent = false;
  129. $where = 'WHERE';
  130. $fileDependentWhere = '';
  131. if ($itemType === 'file' || $itemType === 'folder') {
  132. $fileDependent = true;
  133. $column = 'file_source';
  134. $fileDependentWhere = 'INNER JOIN `*PREFIX*filecache` ON `file_source` = `*PREFIX*filecache`.`fileid` ';
  135. $fileDependentWhere .= 'INNER JOIN `*PREFIX*storages` ON `numeric_id` = `*PREFIX*filecache`.`storage` ';
  136. } else {
  137. $column = 'item_source';
  138. }
  139. $select = self::createSelectStatement(self::FORMAT_NONE, $fileDependent);
  140. $where .= ' `' . $column . '` = ? AND `item_type` = ? ';
  141. $arguments = [$itemSource, $itemType];
  142. // for link shares $user === null
  143. if ($user !== null) {
  144. $where .= ' AND `share_with` = ? ';
  145. $arguments[] = $user;
  146. }
  147. if ($shareType !== null) {
  148. $where .= ' AND `share_type` = ? ';
  149. $arguments[] = $shareType;
  150. }
  151. if ($owner !== null) {
  152. $where .= ' AND `uid_owner` = ? ';
  153. $arguments[] = $owner;
  154. }
  155. $query = \OC_DB::prepare('SELECT ' . $select . ' FROM `*PREFIX*share` '. $fileDependentWhere . $where);
  156. $result = \OC_DB::executeAudited($query, $arguments);
  157. while ($row = $result->fetchRow()) {
  158. if ($fileDependent && !self::isFileReachable($row['path'], $row['storage_id'])) {
  159. continue;
  160. }
  161. if ($fileDependent && (int)$row['file_parent'] === -1) {
  162. // if it is a mount point we need to get the path from the mount manager
  163. $mountManager = \OC\Files\Filesystem::getMountManager();
  164. $mountPoint = $mountManager->findByStorageId($row['storage_id']);
  165. if (!empty($mountPoint)) {
  166. $path = $mountPoint[0]->getMountPoint();
  167. $path = trim($path, '/');
  168. $path = substr($path, strlen($owner) + 1); //normalize path to 'files/foo.txt`
  169. $row['path'] = $path;
  170. } else {
  171. \OC::$server->getLogger()->warning(
  172. 'Could not resolve mount point for ' . $row['storage_id'],
  173. ['app' => 'OCP\Share']
  174. );
  175. }
  176. }
  177. $shares[] = $row;
  178. }
  179. $result->closeCursor();
  180. //if didn't found a result than let's look for a group share.
  181. if (empty($shares) && $user !== null) {
  182. $userObject = \OC::$server->getUserManager()->get($user);
  183. $groups = [];
  184. if ($userObject) {
  185. $groups = \OC::$server->getGroupManager()->getUserGroupIds($userObject);
  186. }
  187. if (!empty($groups)) {
  188. $where = $fileDependentWhere . ' WHERE `' . $column . '` = ? AND `item_type` = ? AND `share_with` in (?)';
  189. $arguments = [$itemSource, $itemType, $groups];
  190. $types = [null, null, IQueryBuilder::PARAM_STR_ARRAY];
  191. if ($owner !== null) {
  192. $where .= ' AND `uid_owner` = ?';
  193. $arguments[] = $owner;
  194. $types[] = null;
  195. }
  196. // TODO: inject connection, hopefully one day in the future when this
  197. // class isn't static anymore...
  198. $conn = \OC::$server->getDatabaseConnection();
  199. $result = $conn->executeQuery(
  200. 'SELECT ' . $select . ' FROM `*PREFIX*share` ' . $where,
  201. $arguments,
  202. $types
  203. );
  204. while ($row = $result->fetch()) {
  205. $shares[] = $row;
  206. }
  207. }
  208. }
  209. return $shares;
  210. }
  211. /**
  212. * Get the shared item of item type owned by the current user
  213. * @param string $itemType
  214. * @param string $itemSource
  215. * @param int $format (optional) Format type must be defined by the backend
  216. * @param mixed $parameters
  217. * @param boolean $includeCollections
  218. * @return mixed Return depends on format
  219. *
  220. * Refactoring notes:
  221. * * defacto $parameters and $format is always the default and therefore is removed in the subsequent call
  222. */
  223. public static function getItemShared($itemType, $itemSource, $format = self::FORMAT_NONE,
  224. $parameters = null, $includeCollections = false) {
  225. return self::getItems($itemType, $itemSource, null, null, \OC_User::getUser(), self::FORMAT_NONE,
  226. null, -1, $includeCollections);
  227. }
  228. /**
  229. * Get the backend class for the specified item type
  230. * @param string $itemType
  231. * @throws \Exception
  232. * @return \OCP\Share_Backend
  233. */
  234. public static function getBackend($itemType) {
  235. $l = \OC::$server->getL10N('lib');
  236. if (isset(self::$backends[$itemType])) {
  237. return self::$backends[$itemType];
  238. } elseif (isset(self::$backendTypes[$itemType]['class'])) {
  239. $class = self::$backendTypes[$itemType]['class'];
  240. if (class_exists($class)) {
  241. self::$backends[$itemType] = new $class;
  242. if (!(self::$backends[$itemType] instanceof \OCP\Share_Backend)) {
  243. $message = 'Sharing backend %s must implement the interface OCP\Share_Backend';
  244. $message_t = $l->t('Sharing backend %s must implement the interface OCP\Share_Backend', [$class]);
  245. \OCP\Util::writeLog('OCP\Share', sprintf($message, $class), ILogger::ERROR);
  246. throw new \Exception($message_t);
  247. }
  248. return self::$backends[$itemType];
  249. } else {
  250. $message = 'Sharing backend %s not found';
  251. $message_t = $l->t('Sharing backend %s not found', [$class]);
  252. \OCP\Util::writeLog('OCP\Share', sprintf($message, $class), ILogger::ERROR);
  253. throw new \Exception($message_t);
  254. }
  255. }
  256. $message = 'Sharing backend for %s not found';
  257. $message_t = $l->t('Sharing backend for %s not found', [$itemType]);
  258. \OCP\Util::writeLog('OCP\Share', sprintf($message, $itemType), ILogger::ERROR);
  259. throw new \Exception($message_t);
  260. }
  261. /**
  262. * Check if resharing is allowed
  263. * @return boolean true if allowed or false
  264. *
  265. * Resharing is allowed by default if not configured
  266. */
  267. public static function isResharingAllowed() {
  268. if (!isset(self::$isResharingAllowed)) {
  269. if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_allow_resharing', 'yes') == 'yes') {
  270. self::$isResharingAllowed = true;
  271. } else {
  272. self::$isResharingAllowed = false;
  273. }
  274. }
  275. return self::$isResharingAllowed;
  276. }
  277. /**
  278. * Get a list of collection item types for the specified item type
  279. * @param string $itemType
  280. * @return array
  281. */
  282. private static function getCollectionItemTypes($itemType) {
  283. $collectionTypes = [$itemType];
  284. foreach (self::$backendTypes as $type => $backend) {
  285. if (in_array($backend['collectionOf'], $collectionTypes)) {
  286. $collectionTypes[] = $type;
  287. }
  288. }
  289. // TODO Add option for collections to be collection of themselves, only 'folder' does it now...
  290. if (isset(self::$backendTypes[$itemType]) && (!self::getBackend($itemType) instanceof \OCP\Share_Backend_Collection || $itemType != 'folder')) {
  291. unset($collectionTypes[0]);
  292. }
  293. // Return array if collections were found or the item type is a
  294. // collection itself - collections can be inside collections
  295. if (count($collectionTypes) > 0) {
  296. return $collectionTypes;
  297. }
  298. return false;
  299. }
  300. /**
  301. * Get shared items from the database
  302. * @param string $itemType
  303. * @param string $item Item source or target (optional)
  304. * @param int $shareType SHARE_TYPE_USER, SHARE_TYPE_GROUP, SHARE_TYPE_LINK, $shareTypeUserAndGroups, or $shareTypeGroupUserUnique
  305. * @param string $shareWith User or group the item is being shared with
  306. * @param string $uidOwner User that is the owner of shared items (optional)
  307. * @param int $format Format to convert items to with formatItems() (optional)
  308. * @param mixed $parameters to pass to formatItems() (optional)
  309. * @param int $limit Number of items to return, -1 to return all matches (optional)
  310. * @param boolean $includeCollections Include collection item types (optional)
  311. * @param boolean $itemShareWithBySource (optional)
  312. * @param boolean $checkExpireDate
  313. * @return array
  314. *
  315. * See public functions getItem(s)... for parameter usage
  316. *
  317. * Refactoring notes:
  318. * * defacto $limit, $itemsShareWithBySource, $checkExpireDate, $parameters and $format is always the default and therefore is removed in the subsequent call
  319. */
  320. public static function getItems($itemType, $item = null, $shareType = null, $shareWith = null,
  321. $uidOwner = null, $format = self::FORMAT_NONE, $parameters = null, $limit = -1,
  322. $includeCollections = false, $itemShareWithBySource = false, $checkExpireDate = true) {
  323. if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_enabled', 'yes') != 'yes') {
  324. return [];
  325. }
  326. $backend = self::getBackend($itemType);
  327. $collectionTypes = false;
  328. // Get filesystem root to add it to the file target and remove from the
  329. // file source, match file_source with the file cache
  330. if ($itemType == 'file' || $itemType == 'folder') {
  331. if (!is_null($uidOwner)) {
  332. $root = \OC\Files\Filesystem::getRoot();
  333. } else {
  334. $root = '';
  335. }
  336. $where = 'INNER JOIN `*PREFIX*filecache` ON `file_source` = `*PREFIX*filecache`.`fileid` ';
  337. if (!isset($item)) {
  338. $where .= ' AND `file_target` IS NOT NULL ';
  339. }
  340. $where .= 'INNER JOIN `*PREFIX*storages` ON `numeric_id` = `*PREFIX*filecache`.`storage` ';
  341. $fileDependent = true;
  342. $queryArgs = [];
  343. } else {
  344. $fileDependent = false;
  345. $root = '';
  346. $collectionTypes = self::getCollectionItemTypes($itemType);
  347. if ($includeCollections && !isset($item) && $collectionTypes) {
  348. // If includeCollections is true, find collections of this item type, e.g. a music album contains songs
  349. if (!in_array($itemType, $collectionTypes)) {
  350. $itemTypes = array_merge([$itemType], $collectionTypes);
  351. } else {
  352. $itemTypes = $collectionTypes;
  353. }
  354. $placeholders = implode(',', array_fill(0, count($itemTypes), '?'));
  355. $where = ' WHERE `item_type` IN ('.$placeholders.'))';
  356. $queryArgs = $itemTypes;
  357. } else {
  358. $where = ' WHERE `item_type` = ?';
  359. $queryArgs = [$itemType];
  360. }
  361. }
  362. if (\OC::$server->getConfig()->getAppValue('core', 'shareapi_allow_links', 'yes') !== 'yes') {
  363. $where .= ' AND `share_type` != ?';
  364. $queryArgs[] = IShare::TYPE_LINK;
  365. }
  366. if (isset($shareType)) {
  367. // Include all user and group items
  368. if ($shareType == self::$shareTypeUserAndGroups && isset($shareWith)) {
  369. $where .= ' AND ((`share_type` in (?, ?) AND `share_with` = ?) ';
  370. $queryArgs[] = IShare::TYPE_USER;
  371. $queryArgs[] = self::$shareTypeGroupUserUnique;
  372. $queryArgs[] = $shareWith;
  373. $user = \OC::$server->getUserManager()->get($shareWith);
  374. $groups = [];
  375. if ($user) {
  376. $groups = \OC::$server->getGroupManager()->getUserGroupIds($user);
  377. }
  378. if (!empty($groups)) {
  379. $placeholders = implode(',', array_fill(0, count($groups), '?'));
  380. $where .= ' OR (`share_type` = ? AND `share_with` IN ('.$placeholders.')) ';
  381. $queryArgs[] = IShare::TYPE_GROUP;
  382. $queryArgs = array_merge($queryArgs, $groups);
  383. }
  384. $where .= ')';
  385. // Don't include own group shares
  386. $where .= ' AND `uid_owner` != ?';
  387. $queryArgs[] = $shareWith;
  388. } else {
  389. $where .= ' AND `share_type` = ?';
  390. $queryArgs[] = $shareType;
  391. if (isset($shareWith)) {
  392. $where .= ' AND `share_with` = ?';
  393. $queryArgs[] = $shareWith;
  394. }
  395. }
  396. }
  397. if (isset($uidOwner)) {
  398. $where .= ' AND `uid_owner` = ?';
  399. $queryArgs[] = $uidOwner;
  400. if (!isset($shareType)) {
  401. // Prevent unique user targets for group shares from being selected
  402. $where .= ' AND `share_type` != ?';
  403. $queryArgs[] = self::$shareTypeGroupUserUnique;
  404. }
  405. if ($fileDependent) {
  406. $column = 'file_source';
  407. } else {
  408. $column = 'item_source';
  409. }
  410. } else {
  411. if ($fileDependent) {
  412. $column = 'file_target';
  413. } else {
  414. $column = 'item_target';
  415. }
  416. }
  417. if (isset($item)) {
  418. $collectionTypes = self::getCollectionItemTypes($itemType);
  419. if ($includeCollections && $collectionTypes && !in_array('folder', $collectionTypes)) {
  420. $where .= ' AND (';
  421. } else {
  422. $where .= ' AND';
  423. }
  424. // If looking for own shared items, check item_source else check item_target
  425. if (isset($uidOwner)) {
  426. // If item type is a file, file source needs to be checked in case the item was converted
  427. if ($fileDependent) {
  428. $where .= ' `file_source` = ?';
  429. $column = 'file_source';
  430. } else {
  431. $where .= ' `item_source` = ?';
  432. $column = 'item_source';
  433. }
  434. } else {
  435. if ($fileDependent) {
  436. $where .= ' `file_target` = ?';
  437. $item = \OC\Files\Filesystem::normalizePath($item);
  438. } else {
  439. $where .= ' `item_target` = ?';
  440. }
  441. }
  442. $queryArgs[] = $item;
  443. if ($includeCollections && $collectionTypes && !in_array('folder', $collectionTypes)) {
  444. $placeholders = implode(',', array_fill(0, count($collectionTypes), '?'));
  445. $where .= ' OR `item_type` IN ('.$placeholders.'))';
  446. $queryArgs = array_merge($queryArgs, $collectionTypes);
  447. }
  448. }
  449. $where .= ' ORDER BY `*PREFIX*share`.`id` ASC';
  450. $queryLimit = null;
  451. $select = self::createSelectStatement(self::FORMAT_NONE, $fileDependent, $uidOwner);
  452. $root = strlen($root);
  453. $query = \OC_DB::prepare('SELECT '.$select.' FROM `*PREFIX*share` '.$where, $queryLimit);
  454. $result = $query->execute($queryArgs);
  455. if ($result === false) {
  456. \OCP\Util::writeLog('OCP\Share',
  457. \OC_DB::getErrorMessage() . ', select=' . $select . ' where=',
  458. ILogger::ERROR);
  459. }
  460. $items = [];
  461. $targets = [];
  462. $switchedItems = [];
  463. $mounts = [];
  464. while ($row = $result->fetchRow()) {
  465. self::transformDBResults($row);
  466. // Filter out duplicate group shares for users with unique targets
  467. if ($fileDependent && !self::isFileReachable($row['path'], $row['storage_id'])) {
  468. continue;
  469. }
  470. if ($row['share_type'] == self::$shareTypeGroupUserUnique && isset($items[$row['parent']])) {
  471. $row['share_type'] = IShare::TYPE_GROUP;
  472. $row['unique_name'] = true; // remember that we use a unique name for this user
  473. $row['share_with'] = $items[$row['parent']]['share_with'];
  474. // if the group share was unshared from the user we keep the permission, otherwise
  475. // we take the permission from the parent because this is always the up-to-date
  476. // permission for the group share
  477. if ($row['permissions'] > 0) {
  478. $row['permissions'] = $items[$row['parent']]['permissions'];
  479. }
  480. // Remove the parent group share
  481. unset($items[$row['parent']]);
  482. if ($row['permissions'] == 0) {
  483. continue;
  484. }
  485. } elseif (!isset($uidOwner)) {
  486. // Check if the same target already exists
  487. if (isset($targets[$row['id']])) {
  488. // Check if the same owner shared with the user twice
  489. // through a group and user share - this is allowed
  490. $id = $targets[$row['id']];
  491. if (isset($items[$id]) && $items[$id]['uid_owner'] == $row['uid_owner']) {
  492. // Switch to group share type to ensure resharing conditions aren't bypassed
  493. if ($items[$id]['share_type'] != IShare::TYPE_GROUP) {
  494. $items[$id]['share_type'] = IShare::TYPE_GROUP;
  495. $items[$id]['share_with'] = $row['share_with'];
  496. }
  497. // Switch ids if sharing permission is granted on only
  498. // one share to ensure correct parent is used if resharing
  499. if (~(int)$items[$id]['permissions'] & \OCP\Constants::PERMISSION_SHARE
  500. && (int)$row['permissions'] & \OCP\Constants::PERMISSION_SHARE) {
  501. $items[$row['id']] = $items[$id];
  502. $switchedItems[$id] = $row['id'];
  503. unset($items[$id]);
  504. $id = $row['id'];
  505. }
  506. $items[$id]['permissions'] |= (int)$row['permissions'];
  507. }
  508. continue;
  509. } elseif (!empty($row['parent'])) {
  510. $targets[$row['parent']] = $row['id'];
  511. }
  512. }
  513. // Remove root from file source paths if retrieving own shared items
  514. if (isset($uidOwner) && isset($row['path'])) {
  515. if (isset($row['parent'])) {
  516. $query = \OC::$server->getDatabaseConnection()->getQueryBuilder();
  517. $query->select('file_target')
  518. ->from('share')
  519. ->where($query->expr()->eq('id', $query->createNamedParameter($row['parent'])));
  520. $parentResult = $query->execute();
  521. $parentRow = $parentResult->fetch();
  522. $parentResult->closeCursor();
  523. if ($parentRow === false) {
  524. \OCP\Util::writeLog('OCP\Share', 'Can\'t select parent: ' .
  525. \OC_DB::getErrorMessage() . ', select=' . $select . ' where=' . $where,
  526. ILogger::ERROR);
  527. } else {
  528. $tmpPath = $parentRow['file_target'];
  529. // find the right position where the row path continues from the target path
  530. $pos = strrpos($row['path'], $parentRow['file_target']);
  531. $subPath = substr($row['path'], $pos);
  532. $splitPath = explode('/', $subPath);
  533. foreach (array_slice($splitPath, 2) as $pathPart) {
  534. $tmpPath = $tmpPath . '/' . $pathPart;
  535. }
  536. $row['path'] = $tmpPath;
  537. }
  538. } else {
  539. if (!isset($mounts[$row['storage']])) {
  540. $mountPoints = \OC\Files\Filesystem::getMountByNumericId($row['storage']);
  541. if (is_array($mountPoints) && !empty($mountPoints)) {
  542. $mounts[$row['storage']] = current($mountPoints);
  543. }
  544. }
  545. if (!empty($mounts[$row['storage']])) {
  546. $path = $mounts[$row['storage']]->getMountPoint().$row['path'];
  547. $relPath = substr($path, $root); // path relative to data/user
  548. $row['path'] = rtrim($relPath, '/');
  549. }
  550. }
  551. }
  552. // Check if resharing is allowed, if not remove share permission
  553. if (isset($row['permissions']) && (!self::isResharingAllowed() | \OCP\Util::isSharingDisabledForUser())) {
  554. $row['permissions'] &= ~\OCP\Constants::PERMISSION_SHARE;
  555. }
  556. // Add display names to result
  557. $row['share_with_displayname'] = $row['share_with'];
  558. if (isset($row['share_with']) && $row['share_with'] != '' &&
  559. $row['share_type'] === IShare::TYPE_USER) {
  560. $shareWithUser = \OC::$server->getUserManager()->get($row['share_with']);
  561. $row['share_with_displayname'] = $shareWithUser === null ? $row['share_with'] : $shareWithUser->getDisplayName();
  562. } elseif (isset($row['share_with']) && $row['share_with'] != '' &&
  563. $row['share_type'] === IShare::TYPE_REMOTE) {
  564. $addressBookEntries = \OC::$server->getContactsManager()->search($row['share_with'], ['CLOUD'], [
  565. 'limit' => 1,
  566. 'enumeration' => false,
  567. 'fullmatch' => false,
  568. 'strict_search' => true,
  569. ]);
  570. foreach ($addressBookEntries as $entry) {
  571. foreach ($entry['CLOUD'] as $cloudID) {
  572. if ($cloudID === $row['share_with']) {
  573. $row['share_with_displayname'] = $entry['FN'];
  574. }
  575. }
  576. }
  577. }
  578. if (isset($row['uid_owner']) && $row['uid_owner'] != '') {
  579. $ownerUser = \OC::$server->getUserManager()->get($row['uid_owner']);
  580. $row['displayname_owner'] = $ownerUser === null ? $row['uid_owner'] : $ownerUser->getDisplayName();
  581. }
  582. if ($row['permissions'] > 0) {
  583. $items[$row['id']] = $row;
  584. }
  585. }
  586. // group items if we are looking for items shared with the current user
  587. if (isset($shareWith) && $shareWith === \OC_User::getUser()) {
  588. $items = self::groupItems($items, $itemType);
  589. }
  590. if (!empty($items)) {
  591. $collectionItems = [];
  592. foreach ($items as &$row) {
  593. // Check if this is a collection of the requested item type
  594. if ($includeCollections && $collectionTypes && $row['item_type'] !== 'folder' && in_array($row['item_type'], $collectionTypes)) {
  595. if (($collectionBackend = self::getBackend($row['item_type']))
  596. && $collectionBackend instanceof \OCP\Share_Backend_Collection) {
  597. // Collections can be inside collections, check if the item is a collection
  598. if (isset($item) && $row['item_type'] == $itemType && $row[$column] == $item) {
  599. $collectionItems[] = $row;
  600. } else {
  601. $collection = [];
  602. $collection['item_type'] = $row['item_type'];
  603. if ($row['item_type'] == 'file' || $row['item_type'] == 'folder') {
  604. $collection['path'] = basename($row['path']);
  605. }
  606. $row['collection'] = $collection;
  607. // Fetch all of the children sources
  608. $children = $collectionBackend->getChildren($row[$column]);
  609. foreach ($children as $child) {
  610. $childItem = $row;
  611. $childItem['item_type'] = $itemType;
  612. if ($row['item_type'] != 'file' && $row['item_type'] != 'folder') {
  613. $childItem['item_source'] = $child['source'];
  614. $childItem['item_target'] = $child['target'];
  615. }
  616. if ($backend instanceof \OCP\Share_Backend_File_Dependent) {
  617. if ($row['item_type'] == 'file' || $row['item_type'] == 'folder') {
  618. $childItem['file_source'] = $child['source'];
  619. } else { // TODO is this really needed if we already know that we use the file backend?
  620. $meta = \OC\Files\Filesystem::getFileInfo($child['file_path']);
  621. $childItem['file_source'] = $meta['fileid'];
  622. }
  623. $childItem['file_target'] =
  624. \OC\Files\Filesystem::normalizePath($child['file_path']);
  625. }
  626. if (isset($item)) {
  627. if ($childItem[$column] == $item) {
  628. $collectionItems[] = $childItem;
  629. }
  630. } else {
  631. $collectionItems[] = $childItem;
  632. }
  633. }
  634. }
  635. }
  636. // Remove collection item
  637. $toRemove = $row['id'];
  638. if (array_key_exists($toRemove, $switchedItems)) {
  639. $toRemove = $switchedItems[$toRemove];
  640. }
  641. unset($items[$toRemove]);
  642. } elseif ($includeCollections && $collectionTypes && in_array($row['item_type'], $collectionTypes)) {
  643. // FIXME: Thats a dirty hack to improve file sharing performance,
  644. // see github issue #10588 for more details
  645. // Need to find a solution which works for all back-ends
  646. $collectionBackend = self::getBackend($row['item_type']);
  647. $sharedParents = $collectionBackend->getParents($row['item_source']);
  648. foreach ($sharedParents as $parent) {
  649. $collectionItems[] = $parent;
  650. }
  651. }
  652. }
  653. if (!empty($collectionItems)) {
  654. $collectionItems = array_unique($collectionItems, SORT_REGULAR);
  655. $items = array_merge($items, $collectionItems);
  656. }
  657. // filter out invalid items, these can appear when subshare entries exist
  658. // for a group in which the requested user isn't a member any more
  659. $items = array_filter($items, function ($item) {
  660. return $item['share_type'] !== self::$shareTypeGroupUserUnique;
  661. });
  662. return self::formatResult($items, $column, $backend);
  663. } elseif ($includeCollections && $collectionTypes && in_array('folder', $collectionTypes)) {
  664. // FIXME: Thats a dirty hack to improve file sharing performance,
  665. // see github issue #10588 for more details
  666. // Need to find a solution which works for all back-ends
  667. $collectionItems = [];
  668. $collectionBackend = self::getBackend('folder');
  669. $sharedParents = $collectionBackend->getParents($item, $shareWith, $uidOwner);
  670. foreach ($sharedParents as $parent) {
  671. $collectionItems[] = $parent;
  672. }
  673. return self::formatResult($collectionItems, $column, $backend);
  674. }
  675. return [];
  676. }
  677. /**
  678. * group items with link to the same source
  679. *
  680. * @param array $items
  681. * @param string $itemType
  682. * @return array of grouped items
  683. */
  684. protected static function groupItems($items, $itemType) {
  685. $fileSharing = $itemType === 'file' || $itemType === 'folder';
  686. $result = [];
  687. foreach ($items as $item) {
  688. $grouped = false;
  689. foreach ($result as $key => $r) {
  690. // for file/folder shares we need to compare file_source, otherwise we compare item_source
  691. // only group shares if they already point to the same target, otherwise the file where shared
  692. // before grouping of shares was added. In this case we don't group them toi avoid confusions
  693. if (($fileSharing && $item['file_source'] === $r['file_source'] && $item['file_target'] === $r['file_target']) ||
  694. (!$fileSharing && $item['item_source'] === $r['item_source'] && $item['item_target'] === $r['item_target'])) {
  695. // add the first item to the list of grouped shares
  696. if (!isset($result[$key]['grouped'])) {
  697. $result[$key]['grouped'][] = $result[$key];
  698. }
  699. $result[$key]['permissions'] = (int) $item['permissions'] | (int) $r['permissions'];
  700. $result[$key]['grouped'][] = $item;
  701. $grouped = true;
  702. break;
  703. }
  704. }
  705. if (!$grouped) {
  706. $result[] = $item;
  707. }
  708. }
  709. return $result;
  710. }
  711. /**
  712. * construct select statement
  713. * @param int $format
  714. * @param boolean $fileDependent ist it a file/folder share or a generla share
  715. * @param string $uidOwner
  716. * @return string select statement
  717. */
  718. private static function createSelectStatement($format, $fileDependent, $uidOwner = null) {
  719. $select = '*';
  720. if ($format == self::FORMAT_STATUSES) {
  721. if ($fileDependent) {
  722. $select = '`*PREFIX*share`.`id`, `*PREFIX*share`.`parent`, `share_type`, `path`, `storage`, '
  723. . '`share_with`, `uid_owner` , `file_source`, `stime`, `*PREFIX*share`.`permissions`, '
  724. . '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`, '
  725. . '`uid_initiator`';
  726. } else {
  727. $select = '`id`, `parent`, `share_type`, `share_with`, `uid_owner`, `item_source`, `stime`, `*PREFIX*share`.`permissions`';
  728. }
  729. } else {
  730. if (isset($uidOwner)) {
  731. if ($fileDependent) {
  732. $select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `*PREFIX*share`.`parent`,'
  733. . ' `share_type`, `share_with`, `file_source`, `file_target`, `path`, `*PREFIX*share`.`permissions`, `stime`,'
  734. . ' `expiration`, `token`, `storage`, `mail_send`, `uid_owner`, '
  735. . '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`';
  736. } else {
  737. $select = '`id`, `item_type`, `item_source`, `parent`, `share_type`, `share_with`, `*PREFIX*share`.`permissions`,'
  738. . ' `stime`, `file_source`, `expiration`, `token`, `mail_send`, `uid_owner`';
  739. }
  740. } else {
  741. if ($fileDependent) {
  742. if ($format == \OCA\Files_Sharing\ShareBackend\File::FORMAT_GET_FOLDER_CONTENTS || $format == \OCA\Files_Sharing\ShareBackend\File::FORMAT_FILE_APP_ROOT) {
  743. $select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `*PREFIX*share`.`parent`, `uid_owner`, '
  744. . '`share_type`, `share_with`, `file_source`, `path`, `file_target`, `stime`, '
  745. . '`*PREFIX*share`.`permissions`, `expiration`, `storage`, `*PREFIX*filecache`.`parent` as `file_parent`, '
  746. . '`name`, `mtime`, `mimetype`, `mimepart`, `size`, `encrypted`, `etag`, `mail_send`';
  747. } else {
  748. $select = '`*PREFIX*share`.`id`, `item_type`, `item_source`, `item_target`,'
  749. . '`*PREFIX*share`.`parent`, `share_type`, `share_with`, `uid_owner`,'
  750. . '`file_source`, `path`, `file_target`, `*PREFIX*share`.`permissions`,'
  751. . '`stime`, `expiration`, `token`, `storage`, `mail_send`,'
  752. . '`*PREFIX*storages`.`id` AS `storage_id`, `*PREFIX*filecache`.`parent` as `file_parent`';
  753. }
  754. }
  755. }
  756. }
  757. return $select;
  758. }
  759. /**
  760. * transform db results
  761. * @param array $row result
  762. */
  763. private static function transformDBResults(&$row) {
  764. if (isset($row['id'])) {
  765. $row['id'] = (int) $row['id'];
  766. }
  767. if (isset($row['share_type'])) {
  768. $row['share_type'] = (int) $row['share_type'];
  769. }
  770. if (isset($row['parent'])) {
  771. $row['parent'] = (int) $row['parent'];
  772. }
  773. if (isset($row['file_parent'])) {
  774. $row['file_parent'] = (int) $row['file_parent'];
  775. }
  776. if (isset($row['file_source'])) {
  777. $row['file_source'] = (int) $row['file_source'];
  778. }
  779. if (isset($row['permissions'])) {
  780. $row['permissions'] = (int) $row['permissions'];
  781. }
  782. if (isset($row['storage'])) {
  783. $row['storage'] = (int) $row['storage'];
  784. }
  785. if (isset($row['stime'])) {
  786. $row['stime'] = (int) $row['stime'];
  787. }
  788. if (isset($row['expiration']) && $row['share_type'] !== IShare::TYPE_LINK) {
  789. // discard expiration date for non-link shares, which might have been
  790. // set by ancient bugs
  791. $row['expiration'] = null;
  792. }
  793. }
  794. /**
  795. * format result
  796. * @param array $items result
  797. * @param string $column is it a file share or a general share ('file_target' or 'item_target')
  798. * @param \OCP\Share_Backend $backend sharing backend
  799. * @param int $format
  800. * @param array $parameters additional format parameters
  801. * @return array format result
  802. */
  803. private static function formatResult($items, $column, $backend, $format = self::FORMAT_NONE, $parameters = null) {
  804. if ($format === self::FORMAT_NONE) {
  805. return $items;
  806. } elseif ($format === self::FORMAT_STATUSES) {
  807. $statuses = [];
  808. foreach ($items as $item) {
  809. if ($item['share_type'] === IShare::TYPE_LINK) {
  810. if ($item['uid_initiator'] !== \OC::$server->getUserSession()->getUser()->getUID()) {
  811. continue;
  812. }
  813. $statuses[$item[$column]]['link'] = true;
  814. } elseif (!isset($statuses[$item[$column]])) {
  815. $statuses[$item[$column]]['link'] = false;
  816. }
  817. if (!empty($item['file_target'])) {
  818. $statuses[$item[$column]]['path'] = $item['path'];
  819. }
  820. }
  821. return $statuses;
  822. } else {
  823. return $backend->formatItems($items, $format, $parameters);
  824. }
  825. }
  826. /**
  827. * remove protocol from URL
  828. *
  829. * @param string $url
  830. * @return string
  831. */
  832. public static function removeProtocolFromUrl($url) {
  833. if (strpos($url, 'https://') === 0) {
  834. return substr($url, strlen('https://'));
  835. } elseif (strpos($url, 'http://') === 0) {
  836. return substr($url, strlen('http://'));
  837. }
  838. return $url;
  839. }
  840. /**
  841. * @return int
  842. */
  843. public static function getExpireInterval() {
  844. return (int)\OC::$server->getConfig()->getAppValue('core', 'shareapi_expire_after_n_days', '7');
  845. }
  846. /**
  847. * Checks whether the given path is reachable for the given owner
  848. *
  849. * @param string $path path relative to files
  850. * @param string $ownerStorageId storage id of the owner
  851. *
  852. * @return boolean true if file is reachable, false otherwise
  853. */
  854. private static function isFileReachable($path, $ownerStorageId) {
  855. // if outside the home storage, file is always considered reachable
  856. if (!(substr($ownerStorageId, 0, 6) === 'home::' ||
  857. substr($ownerStorageId, 0, 13) === 'object::user:'
  858. )) {
  859. return true;
  860. }
  861. // if inside the home storage, the file has to be under "/files/"
  862. $path = ltrim($path, '/');
  863. if (substr($path, 0, 6) === 'files/') {
  864. return true;
  865. }
  866. return false;
  867. }
  868. }