1<?php 2/** 3 * @copyright Copyright (c) 2016, ownCloud, Inc. 4 * 5 * @author Christoph Wurst <christoph@winzerhof-wurst.at> 6 * @author Joas Schilling <coding@schilljs.com> 7 * @author Julius Härtl <jus@bitgrid.net> 8 * @author Morris Jobke <hey@morrisjobke.de> 9 * @author Robin Appelman <robin@icewind.nl> 10 * @author Roeland Jago Douma <roeland@famdouma.nl> 11 * @author Tobias Kaminsky <tobias@kaminsky.me> 12 * @author Vincent Petry <vincent@nextcloud.com> 13 * 14 * @license AGPL-3.0 15 * 16 * This code is free software: you can redistribute it and/or modify 17 * it under the terms of the GNU Affero General Public License, version 3, 18 * as published by the Free Software Foundation. 19 * 20 * This program is distributed in the hope that it will be useful, 21 * but WITHOUT ANY WARRANTY; without even the implied warranty of 22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 * GNU Affero General Public License for more details. 24 * 25 * You should have received a copy of the GNU Affero General Public License, version 3, 26 * along with this program. If not, see <http://www.gnu.org/licenses/> 27 * 28 */ 29namespace OCA\DAV\Connector\Sabre; 30 31use OCA\DAV\Connector\Sabre\Node as DavNode; 32use OCP\Files\Folder; 33use OCP\Files\NotFoundException; 34use OCP\IUserSession; 35use OCP\Share\IShare; 36use Sabre\DAV\PropFind; 37 38/** 39 * Sabre Plugin to provide share-related properties 40 */ 41class SharesPlugin extends \Sabre\DAV\ServerPlugin { 42 public const NS_OWNCLOUD = 'http://owncloud.org/ns'; 43 public const NS_NEXTCLOUD = 'http://nextcloud.org/ns'; 44 public const SHARETYPES_PROPERTYNAME = '{http://owncloud.org/ns}share-types'; 45 public const SHAREES_PROPERTYNAME = '{http://nextcloud.org/ns}sharees'; 46 47 /** 48 * Reference to main server object 49 * 50 * @var \Sabre\DAV\Server 51 */ 52 private $server; 53 54 /** @var \OCP\Share\IManager */ 55 private $shareManager; 56 57 /** @var \Sabre\DAV\Tree */ 58 private $tree; 59 60 /** @var string */ 61 private $userId; 62 63 /** @var \OCP\Files\Folder */ 64 private $userFolder; 65 66 /** @var IShare[][] */ 67 private $cachedShares = []; 68 69 /** @var string[] */ 70 private $cachedFolders = []; 71 72 /** 73 * @param \Sabre\DAV\Tree $tree tree 74 * @param IUserSession $userSession user session 75 * @param \OCP\Files\Folder $userFolder user home folder 76 * @param \OCP\Share\IManager $shareManager share manager 77 */ 78 public function __construct( 79 \Sabre\DAV\Tree $tree, 80 IUserSession $userSession, 81 \OCP\Files\Folder $userFolder, 82 \OCP\Share\IManager $shareManager 83 ) { 84 $this->tree = $tree; 85 $this->shareManager = $shareManager; 86 $this->userFolder = $userFolder; 87 $this->userId = $userSession->getUser()->getUID(); 88 } 89 90 /** 91 * This initializes the plugin. 92 * 93 * This function is called by \Sabre\DAV\Server, after 94 * addPlugin is called. 95 * 96 * This method should set up the required event subscriptions. 97 * 98 * @param \Sabre\DAV\Server $server 99 */ 100 public function initialize(\Sabre\DAV\Server $server) { 101 $server->xml->namespacesMap[self::NS_OWNCLOUD] = 'oc'; 102 $server->xml->elementMap[self::SHARETYPES_PROPERTYNAME] = ShareTypeList::class; 103 $server->protectedProperties[] = self::SHARETYPES_PROPERTYNAME; 104 $server->protectedProperties[] = self::SHAREES_PROPERTYNAME; 105 106 $this->server = $server; 107 $this->server->on('propFind', [$this, 'handleGetProperties']); 108 } 109 110 /** 111 * @param \OCP\Files\Node $node 112 * @return IShare[] 113 */ 114 private function getShare(\OCP\Files\Node $node): array { 115 $result = []; 116 $requestedShareTypes = [ 117 IShare::TYPE_USER, 118 IShare::TYPE_GROUP, 119 IShare::TYPE_LINK, 120 IShare::TYPE_REMOTE, 121 IShare::TYPE_EMAIL, 122 IShare::TYPE_ROOM, 123 IShare::TYPE_CIRCLE, 124 IShare::TYPE_DECK, 125 ]; 126 foreach ($requestedShareTypes as $requestedShareType) { 127 $shares = $this->shareManager->getSharesBy( 128 $this->userId, 129 $requestedShareType, 130 $node, 131 false, 132 -1 133 ); 134 foreach ($shares as $share) { 135 $result[] = $share; 136 } 137 } 138 return $result; 139 } 140 141 /** 142 * @param Folder $node 143 * @return IShare[][] 144 */ 145 private function getSharesFolder(Folder $node): array { 146 return $this->shareManager->getSharesInFolder( 147 $this->userId, 148 $node, 149 true 150 ); 151 } 152 153 /** 154 * @param DavNode $sabreNode 155 * @return IShare[] 156 */ 157 private function getShares(DavNode $sabreNode): array { 158 if (isset($this->cachedShares[$sabreNode->getId()])) { 159 $shares = $this->cachedShares[$sabreNode->getId()]; 160 } else { 161 [$parentPath,] = \Sabre\Uri\split($sabreNode->getPath()); 162 if ($parentPath === '') { 163 $parentPath = '/'; 164 } 165 // if we already cached the folder this file is in we know there are no shares for this file 166 if (array_search($parentPath, $this->cachedFolders) === false) { 167 try { 168 $node = $this->userFolder->get($sabreNode->getPath()); 169 } catch (NotFoundException $e) { 170 return []; 171 } 172 $shares = $this->getShare($node); 173 $this->cachedShares[$sabreNode->getId()] = $shares; 174 } else { 175 return []; 176 } 177 } 178 179 return $shares; 180 } 181 182 /** 183 * Adds shares to propfind response 184 * 185 * @param PropFind $propFind propfind object 186 * @param \Sabre\DAV\INode $sabreNode sabre node 187 */ 188 public function handleGetProperties( 189 PropFind $propFind, 190 \Sabre\DAV\INode $sabreNode 191 ) { 192 if (!($sabreNode instanceof DavNode)) { 193 return; 194 } 195 196 // need prefetch ? 197 if ($sabreNode instanceof Directory 198 && $propFind->getDepth() !== 0 199 && ( 200 !is_null($propFind->getStatus(self::SHARETYPES_PROPERTYNAME)) || 201 !is_null($propFind->getStatus(self::SHAREES_PROPERTYNAME)) 202 ) 203 ) { 204 try { 205 $folderNode = $this->userFolder->get($sabreNode->getPath()); 206 } catch (NotFoundException $e) { 207 // If the folder can't be properly found just return 208 return; 209 } 210 211 if (!($folderNode instanceof Folder)) { 212 // Safety check 213 return; 214 } 215 216 $this->cachedFolders[] = $sabreNode->getPath(); 217 $childShares = $this->getSharesFolder($folderNode); 218 foreach ($childShares as $id => $shares) { 219 $this->cachedShares[$id] = $shares; 220 } 221 } 222 223 $propFind->handle(self::SHARETYPES_PROPERTYNAME, function () use ($sabreNode) { 224 $shares = $this->getShares($sabreNode); 225 226 $shareTypes = array_unique(array_map(function (IShare $share) { 227 return $share->getShareType(); 228 }, $shares)); 229 230 return new ShareTypeList($shareTypes); 231 }); 232 233 $propFind->handle(self::SHAREES_PROPERTYNAME, function () use ($sabreNode) { 234 $shares = $this->getShares($sabreNode); 235 236 return new ShareeList($shares); 237 }); 238 } 239} 240