1<?php 2/** 3 * @author Thomas Müller <thomas.mueller@tmit.eu> 4 * 5 * @copyright Copyright (c) 2018, ownCloud GmbH 6 * @license AGPL-3.0 7 * 8 * This code is free software: you can redistribute it and/or modify 9 * it under the terms of the GNU Affero General Public License, version 3, 10 * as published by the Free Software Foundation. 11 * 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU Affero General Public License for more details. 16 * 17 * You should have received a copy of the GNU Affero General Public License, version 3, 18 * along with this program. If not, see <http://www.gnu.org/licenses/> 19 * 20 */ 21 22namespace OCA\DAV\Files; 23 24use OCA\DAV\Connector\Sabre\Node; 25use OCA\DAV\Files\PublicFiles\PublicSharedRootNode; 26use OCA\DAV\Files\PublicFiles\SharedFile; 27use OCA\DAV\Files\PublicFiles\SharedFolder; 28use OCP\Files\NotFoundException; 29use OCP\Files\Storage\IPersistentLockingStorage; 30use OCP\Lock\Persistent\ILock; 31use Sabre\DAV\Exception\NotFound; 32use Sabre\DAV\Locks; 33use Sabre\DAV\Locks\Backend\BackendInterface; 34use Sabre\DAV\Tree; 35use OCP\AppFramework\Utility\ITimeFactory; 36use OCA\DAV\Connector\Sabre\ObjectTree; 37use OCA\DAV\Connector\Sabre\Exception\Forbidden; 38 39class FileLocksBackend implements BackendInterface { 40 41 /** @var Tree */ 42 private $tree; 43 /** @var bool */ 44 private $useV1; 45 /** @var ITimeFactory */ 46 private $timeFactory; 47 /** @var bool */ 48 private $isPublicEndpoint; 49 50 public function __construct($tree, $useV1, $timeFactory, $isPublicEndpoint = false) { 51 $this->tree = $tree; 52 $this->useV1 = $useV1; 53 $this->timeFactory = $timeFactory; 54 $this->isPublicEndpoint = $isPublicEndpoint; 55 } 56 57 /** 58 * Returns a list of Sabre\DAV\Locks\LockInfo objects 59 * 60 * This method should return all the locks for a particular uri, including 61 * locks that might be set on a parent uri. 62 * 63 * If returnChildLocks is set to true, this method should also look for 64 * any locks in the subtree of the uri for locks. 65 * 66 * @param string $uri 67 * @param bool $returnChildLocks 68 * @return array 69 */ 70 public function getLocks($uri, $returnChildLocks) { 71 try { 72 $node = $this->tree->getNodeForPath($uri); 73 74 if ($node instanceof SharedFile || $node instanceof SharedFolder) { 75 $node = $node->getNode(); 76 } elseif ($node instanceof PublicSharedRootNode) { 77 try { 78 $node = $node->getShare()->getNode(); 79 } catch (NotFoundException $e) { 80 return []; 81 } 82 } elseif (!$node instanceof Node) { 83 return []; 84 } 85 86 $storage = $node->getFileInfo()->getStorage(); 87 if (!$storage->instanceOfStorage(IPersistentLockingStorage::class)) { 88 return []; 89 } 90 91 /** @var IPersistentLockingStorage $storage */ 92 '@phan-var IPersistentLockingStorage $storage'; 93 $locks = $storage->getLocks($node->getFileInfo()->getInternalPath(), $returnChildLocks); 94 } catch (NotFound $e) { 95 if ($uri === '') { 96 // no more parents 97 return []; 98 } 99 100 // get parent storage and check for locks on the target path 101 list($parentPath, $childPath) = \Sabre\Uri\split($uri); 102 103 try { 104 $node = $this->tree->getNodeForPath($parentPath); 105 } catch (NotFound $e) { 106 return []; 107 } 108 109 if ($node instanceof SharedFile || $node instanceof SharedFolder) { 110 $node = $node->getNode(); 111 } elseif ($node instanceof PublicSharedRootNode) { 112 try { 113 $node = $node->getShare()->getNode(); 114 } catch (NotFoundException $e) { 115 return []; 116 } 117 } elseif (!$node instanceof Node) { 118 return []; 119 } 120 121 // use storage of parent 122 $storage = $node->getFileInfo()->getStorage(); 123 if (!$storage->instanceOfStorage(IPersistentLockingStorage::class)) { 124 return []; 125 } 126 127 /** @var IPersistentLockingStorage $storage */ 128 '@phan-var IPersistentLockingStorage $storage'; 129 $locks = $storage->getLocks($node->getFileInfo()->getInternalPath() . '/' . $childPath, $returnChildLocks); 130 } 131 132 $davLocks = []; 133 foreach ($locks as $lock) { 134 $lockInfo = new Locks\LockInfo(); 135 $fileName = $lock->getAbsoluteDavPath(); 136 $uid = $lock->getDavUserId(); 137 138 $pathInView = "/$uid/files/$fileName"; 139 140 // v1 object tree, also used by public link 141 if ($this->tree instanceof ObjectTree) { 142 // get path relative to the view root, which can 143 // either be the user's home or a public link share's root 144 $subPath = $this->tree->getView()->getRelativePath($pathInView); 145 if ($subPath === null) { 146 // path is above the current view, just tell that the lock is on the root then 147 $lockInfo->uri = ''; 148 } else { 149 $subPath = \ltrim($subPath, '/'); 150 $lockInfo->uri = $subPath; 151 } 152 } else { 153 if ($this->useV1) { 154 $lockInfo->uri = $fileName; 155 } else { 156 $lockInfo->uri = "files/$uid/$fileName"; 157 } 158 } 159 160 if (!$this->isPublicEndpoint) { 161 $lockInfo->token = $lock->getToken(); 162 $lockInfo->owner = $lock->getOwner(); 163 } 164 $lockInfo->created = $lock->getCreatedAt(); 165 $lockInfo->depth = $lock->getDepth(); 166 if ($lock->getScope() === ILock::LOCK_SCOPE_EXCLUSIVE) { 167 $lockInfo->scope = Locks\LockInfo::EXCLUSIVE; 168 } else { 169 $lockInfo->scope = Locks\LockInfo::SHARED; 170 } 171 $lockInfo->timeout = $lock->getTimeout() - ($this->timeFactory->getTime() - $lock->getCreatedAt()); 172 173 $davLocks[] = $lockInfo; 174 } 175 return $davLocks; 176 } 177 178 /** 179 * Locks a uri 180 * 181 * @param string $uri 182 * @param Locks\LockInfo $lockInfo 183 * @return bool 184 */ 185 public function lock($uri, Locks\LockInfo $lockInfo) { 186 if ($this->isPublicEndpoint) { 187 throw new Forbidden('Forbidden to lock from public endpoint'); 188 } 189 try { 190 $node = $this->tree->getNodeForPath($uri); 191 } catch (NotFound $e) { 192 return false; 193 } 194 if (!$node instanceof Node) { 195 return false; 196 } 197 198 $storage = $node->getFileInfo()->getStorage(); 199 if (!$storage->instanceOfStorage(IPersistentLockingStorage::class)) { 200 return false; 201 } 202 203 /** @var IPersistentLockingStorage $storage */ 204 '@phan-var IPersistentLockingStorage $storage'; 205 $lock = $storage->lockNodePersistent($node->getFileInfo()->getInternalPath(), [ 206 'token' => $lockInfo->token, 207 'scope' => $lockInfo->scope === Locks\LockInfo::EXCLUSIVE ? ILock::LOCK_SCOPE_EXCLUSIVE : ILock::LOCK_SCOPE_SHARED, 208 'depth' => $lockInfo->depth, 209 'owner' => $lockInfo->owner, 210 'timeout' => $lockInfo->timeout 211 ]); 212 213 // in case the timeout has not been accepted, adjust in lock info 214 $lockInfo->timeout = $lock->getTimeout(); 215 $lockInfo->owner = $lock->getOwner(); 216 217 return !empty($lock); 218 } 219 220 /** 221 * Removes a lock from a uri 222 * 223 * @param string $uri 224 * @param Locks\LockInfo $lockInfo 225 * @return bool 226 */ 227 public function unlock($uri, Locks\LockInfo $lockInfo) { 228 if ($this->isPublicEndpoint) { 229 throw new Forbidden('Forbidden to unlock from public endpoint'); 230 } 231 try { 232 $node = $this->tree->getNodeForPath($uri); 233 } catch (NotFound $e) { 234 return false; 235 } 236 if (!$node instanceof Node) { 237 return false; 238 } 239 240 $storage = $node->getFileInfo()->getStorage(); 241 if (!$storage->instanceOfStorage(IPersistentLockingStorage::class)) { 242 return false; 243 } 244 245 /** @var IPersistentLockingStorage $storage */ 246 '@phan-var IPersistentLockingStorage $storage'; 247 return $storage->unlockNodePersistent($node->getFileInfo()->getInternalPath(), [ 248 'token' => $lockInfo->token 249 ]); 250 } 251} 252