1<?php 2/** 3 * @author Arthur Schiwon <blizzz@arthur-schiwon.de> 4 * @author Bart Visscher <bartv@thisnet.nl> 5 * @author Björn Schießle <bjoern@schiessle.org> 6 * @author Jakob Sack <mail@jakobsack.de> 7 * @author Joas Schilling <coding@schilljs.com> 8 * @author Jörn Friedrich Dreyer <jfd@butonic.de> 9 * @author Klaas Freitag <freitag@owncloud.com> 10 * @author Markus Goetz <markus@woboq.com> 11 * @author Martin Mattel <martin.mattel@diemattels.at> 12 * @author Morris Jobke <hey@morrisjobke.de> 13 * @author Robin Appelman <icewind@owncloud.com> 14 * @author Roeland Jago Douma <rullzer@owncloud.com> 15 * @author Thomas Müller <thomas.mueller@tmit.eu> 16 * @author Vincent Petry <pvince81@owncloud.com> 17 * 18 * @copyright Copyright (c) 2018, ownCloud GmbH 19 * @license AGPL-3.0 20 * 21 * This code is free software: you can redistribute it and/or modify 22 * it under the terms of the GNU Affero General Public License, version 3, 23 * as published by the Free Software Foundation. 24 * 25 * This program is distributed in the hope that it will be useful, 26 * but WITHOUT ANY WARRANTY; without even the implied warranty of 27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 * GNU Affero General Public License for more details. 29 * 30 * You should have received a copy of the GNU Affero General Public License, version 3, 31 * along with this program. If not, see <http://www.gnu.org/licenses/> 32 * 33 */ 34 35namespace OCA\DAV\Connector\Sabre; 36 37use OC\Files\Mount\MoveableMount; 38use OC\Lock\Persistent\Lock; 39use OCA\DAV\Connector\Sabre\Exception\Forbidden; 40use OCA\DAV\Connector\Sabre\Exception\InvalidPath; 41use OCA\Files_Sharing\SharedMount; 42use OCP\Files\ForbiddenException; 43use OCP\Files\Storage\IPersistentLockingStorage; 44use OCP\Share\Exceptions\ShareNotFound; 45use OCP\Share\IManager; 46 47abstract class Node implements \Sabre\DAV\INode { 48 49 /** 50 * @var \OC\Files\View 51 */ 52 protected $fileView; 53 54 /** 55 * The path to the current node 56 * 57 * @var string 58 */ 59 protected $path; 60 61 /** 62 * node properties cache 63 * 64 * @var array 65 */ 66 protected $property_cache = null; 67 68 /** 69 * @var \OCP\Files\FileInfo 70 */ 71 protected $info; 72 73 /** 74 * @var IManager 75 */ 76 protected $shareManager; 77 78 /** 79 * Sets up the node, expects a full path name 80 * 81 * @param \OC\Files\View $view 82 * @param \OCP\Files\FileInfo $info 83 * @param IManager $shareManager 84 */ 85 public function __construct($view, $info, IManager $shareManager = null) { 86 $this->fileView = $view; 87 $this->path = $this->fileView->getRelativePath($info->getPath()); 88 $this->info = $info; 89 if ($shareManager) { 90 $this->shareManager = $shareManager; 91 } else { 92 $this->shareManager = \OC::$server->getShareManager(); 93 } 94 } 95 96 protected function refreshInfo() { 97 $this->info = $this->fileView->getFileInfo($this->path); 98 } 99 100 /** 101 * Returns the name of the node 102 * 103 * @return string 104 */ 105 public function getName() { 106 return $this->info->getName(); 107 } 108 109 /** 110 * Returns the full path 111 * 112 * @return string 113 */ 114 public function getPath() { 115 return $this->path; 116 } 117 118 /** 119 * Renames the node 120 * 121 * @param string $name The new name 122 * @throws \Sabre\DAV\Exception\BadRequest 123 * @throws \Sabre\DAV\Exception\Forbidden 124 * @throws InvalidPath 125 */ 126 public function setName($name) { 127 $mountPoint = $this->info->getMountPoint(); 128 // rename of a shared mount should always be possible 129 if (!($mountPoint instanceof SharedMount) && !$this->info->isUpdateable()) { 130 throw new \Sabre\DAV\Exception\Forbidden(); 131 } 132 133 // verify path of the source 134 $this->verifyPath(); 135 136 list($parentPath, ) = \Sabre\Uri\split($this->path); 137 list(, $newName) = \Sabre\Uri\split($name); 138 139 // verify path of target 140 if (\OC\Files\Filesystem::isForbiddenFileOrDir($parentPath . '/' . $newName)) { 141 throw new \Sabre\DAV\Exception\Forbidden(); 142 } 143 144 try { 145 $this->fileView->verifyPath($parentPath, $newName); 146 } catch (\OCP\Files\InvalidPathException $ex) { 147 throw new InvalidPath($ex->getMessage()); 148 } 149 150 $newPath = $parentPath . '/' . $newName; 151 152 try { 153 $result = $this->fileView->rename($this->path, $newPath); 154 if ($result === false) { 155 throw new Forbidden('Rename operation failed'); 156 } 157 } catch (ForbiddenException $ex) { 158 throw new Forbidden($ex->getMessage(), $ex->getRetry()); 159 } 160 161 $this->path = $newPath; 162 163 $this->refreshInfo(); 164 } 165 166 public function setPropertyCache($property_cache) { 167 $this->property_cache = $property_cache; 168 } 169 170 /** 171 * Returns the last modification time, as a unix timestamp 172 * 173 * @return int timestamp as integer 174 */ 175 public function getLastModified() { 176 $timestamp = $this->info->getMtime(); 177 if (!empty($timestamp)) { 178 return (int)$timestamp; 179 } 180 return $timestamp; 181 } 182 183 /** 184 * sets the last modification time of the file (mtime) to the value given 185 * in the second parameter or to now if the second param is empty. 186 * Even if the modification time is set to a custom value the access time is set to now. 187 */ 188 public function touch($mtime) { 189 $mtime = $this->sanitizeMtime($mtime); 190 $this->fileView->touch($this->path, $mtime); 191 $this->refreshInfo(); 192 } 193 194 /** 195 * Returns the ETag for a file 196 * 197 * An ETag is a unique identifier representing the current version of the 198 * file. If the file changes, the ETag MUST change. The ETag is an 199 * arbitrary string, but MUST be surrounded by double-quotes. 200 * 201 * Return null if the ETag can not effectively be determined 202 * 203 * @return string 204 */ 205 public function getETag() { 206 return '"' . $this->info->getEtag() . '"'; 207 } 208 209 /** 210 * Sets the ETag 211 * 212 * @param string $etag 213 * 214 * @return int file id of updated file or -1 on failure 215 */ 216 public function setETag($etag) { 217 return $this->fileView->putFileInfo($this->path, ['etag' => $etag]); 218 } 219 220 /** 221 * Returns the size of the node, in bytes, or null if the size is unknown 222 * (GoogleDrive documents returns \OCP\Files\FileInfo::SPACE_UNKNOWN for their 223 * size because the actual size value isn't returned by google. This function 224 * will return null in this case) 225 * 226 * @return integer|null 227 */ 228 public function getSize() { 229 $size = $this->info->getSize(); 230 if ($size < 0) { 231 return null; 232 } else { 233 return $size; 234 } 235 } 236 237 /** 238 * Returns the cache's file id 239 * 240 * @return int 241 */ 242 public function getId() { 243 return $this->info->getId(); 244 } 245 246 /** 247 * @return string|null 248 */ 249 public function getFileId() { 250 if ($this->info->getId()) { 251 $instanceId = \OC_Util::getInstanceId(); 252 $id = \sprintf('%08d', $this->info->getId()); 253 return $id . $instanceId; 254 } 255 256 return null; 257 } 258 259 /** 260 * @return integer 261 */ 262 public function getInternalFileId() { 263 return $this->info->getId(); 264 } 265 266 /** 267 * @param string $user 268 * @return int 269 */ 270 public function getSharePermissions($user) { 271 272 // check of we access a federated share 273 if ($user !== null) { 274 try { 275 $share = $this->shareManager->getShareByToken($user); 276 return $share->getPermissions(); 277 } catch (ShareNotFound $e) { 278 // ignore 279 } 280 } 281 282 $storage = $this->info->getStorage(); 283 284 $path = $this->info->getInternalPath(); 285 286 if ($storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) { 287 /** @var \OCA\Files_Sharing\SharedStorage $storage */ 288 '@phan-var \OCA\Files_Sharing\SharedStorage $storage'; 289 $permissions = (int)$storage->getShare()->getPermissions(); 290 } else { 291 $permissions = $storage->getPermissions($path); 292 } 293 294 /* 295 * We can always share non moveable mount points with DELETE and UPDATE 296 * Eventually we need to do this properly 297 */ 298 $mountpoint = $this->info->getMountPoint(); 299 if (!($mountpoint instanceof MoveableMount)) { 300 $mountpointpath = $mountpoint->getMountPoint(); 301 if (\substr($mountpointpath, -1) === '/') { 302 $mountpointpath = \substr($mountpointpath, 0, -1); 303 } 304 305 if ($mountpointpath === $this->info->getPath()) { 306 $permissions |= \OCP\Constants::PERMISSION_DELETE | \OCP\Constants::PERMISSION_UPDATE; 307 } 308 } 309 310 /* 311 * Files can't have create or delete permissions 312 */ 313 if ($this->info->getType() === \OCP\Files\FileInfo::TYPE_FILE) { 314 $permissions &= ~(\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_DELETE); 315 } 316 317 return $permissions; 318 } 319 320 /** 321 * @return string 322 */ 323 public function getDavPermissions() { 324 $p = ''; 325 if ($this->info->isShared()) { 326 $p .= 'S'; 327 } 328 if ($this->info->isShareable()) { 329 $p .= 'R'; 330 } 331 if ($this->info->isMounted()) { 332 $p .= 'M'; 333 } 334 if ($this->info->isDeletable()) { 335 $p .= 'D'; 336 } 337 if ($this->info->isUpdateable()) { 338 $p .= 'NV'; // Renameable, Moveable 339 } 340 if ($this->info->getType() === \OCP\Files\FileInfo::TYPE_FILE) { 341 if ($this->info->isUpdateable()) { 342 $p .= 'W'; 343 } 344 } else { 345 if ($this->info->isCreatable()) { 346 $p .= 'CK'; 347 } 348 } 349 return $p; 350 } 351 352 public function getOwner() { 353 return $this->info->getOwner(); 354 } 355 356 protected function verifyPath() { 357 if (\OC\Files\Filesystem::isForbiddenFileOrDir($this->info->getPath())) { 358 throw new \Sabre\DAV\Exception\Forbidden(); 359 } 360 361 try { 362 $fileName = \basename($this->info->getPath()); 363 $this->fileView->verifyPath($this->path, $fileName); 364 } catch (\OCP\Files\InvalidPathException $ex) { 365 throw new InvalidPath($ex->getMessage()); 366 } 367 } 368 369 /** 370 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE 371 */ 372 public function acquireLock($type) { 373 $this->fileView->lockFile($this->path, $type); 374 } 375 376 /** 377 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE 378 */ 379 public function releaseLock($type) { 380 $this->fileView->unlockFile($this->path, $type); 381 } 382 383 /** 384 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE 385 * 386 * @throws \OCP\Lock\LockedException 387 */ 388 public function changeLock($type) { 389 $this->fileView->changeLock($this->path, $type); 390 } 391 392 /** 393 * @return \OCP\Files\FileInfo 394 */ 395 public function getFileInfo() { 396 return $this->info; 397 } 398 399 protected function sanitizeMtime($mtimeFromRequest) { 400 $mtime = (float) $mtimeFromRequest; 401 if ($mtime >= PHP_INT_MAX) { 402 $mtime = PHP_INT_MAX; 403 } elseif ($mtime <= (PHP_INT_MAX*-1)) { 404 $mtime = (PHP_INT_MAX*-1); 405 } else { 406 $mtime = (int) $mtimeFromRequest; 407 } 408 return $mtime; 409 } 410} 411