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 Christoph Wurst <christoph@winzerhof-wurst.at> 8 * @author Daniel Calviño Sánchez <danxuliu@gmail.com> 9 * @author Jakob Sack <mail@jakobsack.de> 10 * @author Joas Schilling <coding@schilljs.com> 11 * @author Jörn Friedrich Dreyer <jfd@butonic.de> 12 * @author Klaas Freitag <freitag@owncloud.com> 13 * @author Markus Goetz <markus@woboq.com> 14 * @author Morris Jobke <hey@morrisjobke.de> 15 * @author Robin Appelman <robin@icewind.nl> 16 * @author Roeland Jago Douma <roeland@famdouma.nl> 17 * @author Thomas Müller <thomas.mueller@tmit.eu> 18 * @author Tobias Kaminsky <tobias@kaminsky.me> 19 * @author Vincent Petry <vincent@nextcloud.com> 20 * 21 * @license AGPL-3.0 22 * 23 * This code is free software: you can redistribute it and/or modify 24 * it under the terms of the GNU Affero General Public License, version 3, 25 * as published by the Free Software Foundation. 26 * 27 * This program is distributed in the hope that it will be useful, 28 * but WITHOUT ANY WARRANTY; without even the implied warranty of 29 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 30 * GNU Affero General Public License for more details. 31 * 32 * You should have received a copy of the GNU Affero General Public License, version 3, 33 * along with this program. If not, see <http://www.gnu.org/licenses/> 34 * 35 */ 36namespace OCA\DAV\Connector\Sabre; 37 38use OC\Files\Mount\MoveableMount; 39use OC\Files\View; 40use OCA\DAV\Connector\Sabre\Exception\InvalidPath; 41use OCP\Files\FileInfo; 42use OCP\Files\StorageNotAvailableException; 43use OCP\Share\IShare; 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 $view, FileInfo $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 */ 125 public function setName($name) { 126 127 // rename is only allowed if the update privilege is granted 128 if (!($this->info->isUpdateable() || ($this->info->getMountPoint() instanceof MoveableMount && $this->info->getInternalPath() === ''))) { 129 throw new \Sabre\DAV\Exception\Forbidden(); 130 } 131 132 [$parentPath,] = \Sabre\Uri\split($this->path); 133 [, $newName] = \Sabre\Uri\split($name); 134 135 // verify path of the target 136 $this->verifyPath(); 137 138 $newPath = $parentPath . '/' . $newName; 139 140 if (!$this->fileView->rename($this->path, $newPath)) { 141 throw new \Sabre\DAV\Exception('Failed to rename '. $this->path . ' to ' . $newPath); 142 } 143 144 $this->path = $newPath; 145 146 $this->refreshInfo(); 147 } 148 149 public function setPropertyCache($property_cache) { 150 $this->property_cache = $property_cache; 151 } 152 153 /** 154 * Returns the last modification time, as a unix timestamp 155 * 156 * @return int timestamp as integer 157 */ 158 public function getLastModified() { 159 $timestamp = $this->info->getMtime(); 160 if (!empty($timestamp)) { 161 return (int)$timestamp; 162 } 163 return $timestamp; 164 } 165 166 /** 167 * sets the last modification time of the file (mtime) to the value given 168 * in the second parameter or to now if the second param is empty. 169 * Even if the modification time is set to a custom value the access time is set to now. 170 */ 171 public function touch($mtime) { 172 $mtime = $this->sanitizeMtime($mtime); 173 $this->fileView->touch($this->path, $mtime); 174 $this->refreshInfo(); 175 } 176 177 /** 178 * Returns the ETag for a file 179 * 180 * An ETag is a unique identifier representing the current version of the 181 * file. If the file changes, the ETag MUST change. The ETag is an 182 * arbitrary string, but MUST be surrounded by double-quotes. 183 * 184 * Return null if the ETag can not effectively be determined 185 * 186 * @return string 187 */ 188 public function getETag() { 189 return '"' . $this->info->getEtag() . '"'; 190 } 191 192 /** 193 * Sets the ETag 194 * 195 * @param string $etag 196 * 197 * @return int file id of updated file or -1 on failure 198 */ 199 public function setETag($etag) { 200 return $this->fileView->putFileInfo($this->path, ['etag' => $etag]); 201 } 202 203 public function setCreationTime(int $time) { 204 return $this->fileView->putFileInfo($this->path, ['creation_time' => $time]); 205 } 206 207 public function setUploadTime(int $time) { 208 return $this->fileView->putFileInfo($this->path, ['upload_time' => $time]); 209 } 210 211 /** 212 * Returns the size of the node, in bytes 213 * 214 * @return integer 215 */ 216 public function getSize() { 217 return $this->info->getSize(); 218 } 219 220 /** 221 * Returns the cache's file id 222 * 223 * @return int 224 */ 225 public function getId() { 226 return $this->info->getId(); 227 } 228 229 /** 230 * @return string|null 231 */ 232 public function getFileId() { 233 if ($this->info->getId()) { 234 $instanceId = \OC_Util::getInstanceId(); 235 $id = sprintf('%08d', $this->info->getId()); 236 return $id . $instanceId; 237 } 238 239 return null; 240 } 241 242 /** 243 * @return integer 244 */ 245 public function getInternalFileId() { 246 return $this->info->getId(); 247 } 248 249 /** 250 * @param string $user 251 * @return int 252 */ 253 public function getSharePermissions($user) { 254 255 // check of we access a federated share 256 if ($user !== null) { 257 try { 258 $share = $this->shareManager->getShareByToken($user); 259 return $share->getPermissions(); 260 } catch (ShareNotFound $e) { 261 // ignore 262 } 263 } 264 265 try { 266 $storage = $this->info->getStorage(); 267 } catch (StorageNotAvailableException $e) { 268 $storage = null; 269 } 270 271 if ($storage && $storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) { 272 /** @var \OCA\Files_Sharing\SharedStorage $storage */ 273 $permissions = (int)$storage->getShare()->getPermissions(); 274 } else { 275 $permissions = $this->info->getPermissions(); 276 } 277 278 /* 279 * We can always share non moveable mount points with DELETE and UPDATE 280 * Eventually we need to do this properly 281 */ 282 $mountpoint = $this->info->getMountPoint(); 283 if (!($mountpoint instanceof MoveableMount)) { 284 $mountpointpath = $mountpoint->getMountPoint(); 285 if (substr($mountpointpath, -1) === '/') { 286 $mountpointpath = substr($mountpointpath, 0, -1); 287 } 288 289 if (!$mountpoint->getOption('readonly', false) && $mountpointpath === $this->info->getPath()) { 290 $permissions |= \OCP\Constants::PERMISSION_DELETE | \OCP\Constants::PERMISSION_UPDATE; 291 } 292 } 293 294 /* 295 * Files can't have create or delete permissions 296 */ 297 if ($this->info->getType() === \OCP\Files\FileInfo::TYPE_FILE) { 298 $permissions &= ~(\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_DELETE); 299 } 300 301 return $permissions; 302 } 303 304 /** 305 * @param string $user 306 * @return string 307 */ 308 public function getNoteFromShare($user) { 309 if ($user === null) { 310 return ''; 311 } 312 313 $types = [ 314 IShare::TYPE_USER, 315 IShare::TYPE_GROUP, 316 IShare::TYPE_CIRCLE, 317 IShare::TYPE_ROOM 318 ]; 319 320 foreach ($types as $shareType) { 321 $shares = $this->shareManager->getSharedWith($user, $shareType, $this, -1); 322 foreach ($shares as $share) { 323 $note = $share->getNote(); 324 if ($share->getShareOwner() !== $user && !empty($note)) { 325 return $note; 326 } 327 } 328 } 329 330 return ''; 331 } 332 333 /** 334 * @return string 335 */ 336 public function getDavPermissions() { 337 $p = ''; 338 if ($this->info->isShared()) { 339 $p .= 'S'; 340 } 341 if ($this->info->isShareable()) { 342 $p .= 'R'; 343 } 344 if ($this->info->isMounted()) { 345 $p .= 'M'; 346 } 347 if ($this->info->isReadable()) { 348 $p .= 'G'; 349 } 350 if ($this->info->isDeletable()) { 351 $p .= 'D'; 352 } 353 if ($this->info->isUpdateable()) { 354 $p .= 'NV'; // Renameable, Moveable 355 } 356 if ($this->info->getType() === \OCP\Files\FileInfo::TYPE_FILE) { 357 if ($this->info->isUpdateable()) { 358 $p .= 'W'; 359 } 360 } else { 361 if ($this->info->isCreatable()) { 362 $p .= 'CK'; 363 } 364 } 365 return $p; 366 } 367 368 public function getOwner() { 369 return $this->info->getOwner(); 370 } 371 372 protected function verifyPath() { 373 try { 374 $fileName = basename($this->info->getPath()); 375 $this->fileView->verifyPath($this->path, $fileName); 376 } catch (\OCP\Files\InvalidPathException $ex) { 377 throw new InvalidPath($ex->getMessage()); 378 } 379 } 380 381 /** 382 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE 383 */ 384 public function acquireLock($type) { 385 $this->fileView->lockFile($this->path, $type); 386 } 387 388 /** 389 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE 390 */ 391 public function releaseLock($type) { 392 $this->fileView->unlockFile($this->path, $type); 393 } 394 395 /** 396 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE 397 */ 398 public function changeLock($type) { 399 $this->fileView->changeLock($this->path, $type); 400 } 401 402 public function getFileInfo() { 403 return $this->info; 404 } 405 406 protected function sanitizeMtime($mtimeFromRequest) { 407 // In PHP 5.X "is_numeric" returns true for strings in hexadecimal 408 // notation. This is no longer the case in PHP 7.X, so this check 409 // ensures that strings with hexadecimal notations fail too in PHP 5.X. 410 $isHexadecimal = is_string($mtimeFromRequest) && preg_match('/^\s*0[xX]/', $mtimeFromRequest); 411 if ($isHexadecimal || !is_numeric($mtimeFromRequest)) { 412 throw new \InvalidArgumentException('X-OC-MTime header must be an integer (unix timestamp).'); 413 } 414 415 return (int)$mtimeFromRequest; 416 } 417} 418