1<?php 2/** 3 * @copyright Copyright (c) 2016, ownCloud, Inc. 4 * 5 * @author Arthur Schiwon <blizzz@arthur-schiwon.de> 6 * @author Bernhard Posselt <dev@bernhard-posselt.com> 7 * @author Christoph Wurst <christoph@winzerhof-wurst.at> 8 * @author Joas Schilling <coding@schilljs.com> 9 * @author Julius Härtl <jus@bitgrid.net> 10 * @author Morris Jobke <hey@morrisjobke.de> 11 * @author Robin Appelman <robin@icewind.nl> 12 * @author Roeland Jago Douma <roeland@famdouma.nl> 13 * @author Vincent Petry <vincent@nextcloud.com> 14 * 15 * @license AGPL-3.0 16 * 17 * This code is free software: you can redistribute it and/or modify 18 * it under the terms of the GNU Affero General Public License, version 3, 19 * as published by the Free Software Foundation. 20 * 21 * This program is distributed in the hope that it will be useful, 22 * but WITHOUT ANY WARRANTY; without even the implied warranty of 23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 24 * GNU Affero General Public License for more details. 25 * 26 * You should have received a copy of the GNU Affero General Public License, version 3, 27 * along with this program. If not, see <http://www.gnu.org/licenses/> 28 * 29 */ 30namespace OC\Files\Node; 31 32use OC\Files\Filesystem; 33use OC\Files\Mount\MoveableMount; 34use OCP\Files\FileInfo; 35use OCP\Files\InvalidPathException; 36use OCP\Files\NotFoundException; 37use OCP\Files\NotPermittedException; 38use OCP\Lock\LockedException; 39use Symfony\Component\EventDispatcher\GenericEvent; 40 41// FIXME: this class really should be abstract 42class Node implements \OCP\Files\Node { 43 /** 44 * @var \OC\Files\View $view 45 */ 46 protected $view; 47 48 /** 49 * @var \OC\Files\Node\Root $root 50 */ 51 protected $root; 52 53 /** 54 * @var string $path 55 */ 56 protected $path; 57 58 /** 59 * @var \OCP\Files\FileInfo 60 */ 61 protected $fileInfo; 62 63 /** 64 * @param \OC\Files\View $view 65 * @param \OCP\Files\IRootFolder $root 66 * @param string $path 67 * @param FileInfo $fileInfo 68 */ 69 public function __construct($root, $view, $path, $fileInfo = null) { 70 $this->view = $view; 71 $this->root = $root; 72 $this->path = $path; 73 $this->fileInfo = $fileInfo; 74 } 75 76 /** 77 * Creates a Node of the same type that represents a non-existing path 78 * 79 * @param string $path path 80 * @return string non-existing node class 81 * @throws \Exception 82 */ 83 protected function createNonExistingNode($path) { 84 throw new \Exception('Must be implemented by subclasses'); 85 } 86 87 /** 88 * Returns the matching file info 89 * 90 * @return FileInfo 91 * @throws InvalidPathException 92 * @throws NotFoundException 93 */ 94 public function getFileInfo() { 95 if (!Filesystem::isValidPath($this->path)) { 96 throw new InvalidPathException(); 97 } 98 if (!$this->fileInfo) { 99 $fileInfo = $this->view->getFileInfo($this->path); 100 if ($fileInfo instanceof FileInfo) { 101 $this->fileInfo = $fileInfo; 102 } else { 103 throw new NotFoundException(); 104 } 105 } 106 return $this->fileInfo; 107 } 108 109 /** 110 * @param string[] $hooks 111 */ 112 protected function sendHooks($hooks, array $args = null) { 113 $args = !empty($args) ? $args : [$this]; 114 $dispatcher = \OC::$server->getEventDispatcher(); 115 foreach ($hooks as $hook) { 116 $this->root->emit('\OC\Files', $hook, $args); 117 $dispatcher->dispatch('\OCP\Files::' . $hook, new GenericEvent($args)); 118 } 119 } 120 121 /** 122 * @param int $permissions 123 * @return bool 124 * @throws InvalidPathException 125 * @throws NotFoundException 126 */ 127 protected function checkPermissions($permissions) { 128 return ($this->getPermissions() & $permissions) === $permissions; 129 } 130 131 public function delete() { 132 } 133 134 /** 135 * @param int $mtime 136 * @throws InvalidPathException 137 * @throws NotFoundException 138 * @throws NotPermittedException 139 */ 140 public function touch($mtime = null) { 141 if ($this->checkPermissions(\OCP\Constants::PERMISSION_UPDATE)) { 142 $this->sendHooks(['preTouch']); 143 $this->view->touch($this->path, $mtime); 144 $this->sendHooks(['postTouch']); 145 if ($this->fileInfo) { 146 if (is_null($mtime)) { 147 $mtime = time(); 148 } 149 $this->fileInfo['mtime'] = $mtime; 150 } 151 } else { 152 throw new NotPermittedException(); 153 } 154 } 155 156 /** 157 * @return \OC\Files\Storage\Storage 158 * @throws \OCP\Files\NotFoundException 159 */ 160 public function getStorage() { 161 [$storage,] = $this->view->resolvePath($this->path); 162 return $storage; 163 } 164 165 /** 166 * @return string 167 */ 168 public function getPath() { 169 return $this->path; 170 } 171 172 /** 173 * @return string 174 */ 175 public function getInternalPath() { 176 [, $internalPath] = $this->view->resolvePath($this->path); 177 return $internalPath; 178 } 179 180 /** 181 * @return int 182 * @throws InvalidPathException 183 * @throws NotFoundException 184 */ 185 public function getId() { 186 return $this->getFileInfo()->getId(); 187 } 188 189 /** 190 * @return array 191 */ 192 public function stat() { 193 return $this->view->stat($this->path); 194 } 195 196 /** 197 * @return int 198 * @throws InvalidPathException 199 * @throws NotFoundException 200 */ 201 public function getMTime() { 202 return $this->getFileInfo()->getMTime(); 203 } 204 205 /** 206 * @param bool $includeMounts 207 * @return int 208 * @throws InvalidPathException 209 * @throws NotFoundException 210 */ 211 public function getSize($includeMounts = true) { 212 return $this->getFileInfo()->getSize($includeMounts); 213 } 214 215 /** 216 * @return string 217 * @throws InvalidPathException 218 * @throws NotFoundException 219 */ 220 public function getEtag() { 221 return $this->getFileInfo()->getEtag(); 222 } 223 224 /** 225 * @return int 226 * @throws InvalidPathException 227 * @throws NotFoundException 228 */ 229 public function getPermissions() { 230 return $this->getFileInfo()->getPermissions(); 231 } 232 233 /** 234 * @return bool 235 * @throws InvalidPathException 236 * @throws NotFoundException 237 */ 238 public function isReadable() { 239 return $this->getFileInfo()->isReadable(); 240 } 241 242 /** 243 * @return bool 244 * @throws InvalidPathException 245 * @throws NotFoundException 246 */ 247 public function isUpdateable() { 248 return $this->getFileInfo()->isUpdateable(); 249 } 250 251 /** 252 * @return bool 253 * @throws InvalidPathException 254 * @throws NotFoundException 255 */ 256 public function isDeletable() { 257 return $this->getFileInfo()->isDeletable(); 258 } 259 260 /** 261 * @return bool 262 * @throws InvalidPathException 263 * @throws NotFoundException 264 */ 265 public function isShareable() { 266 return $this->getFileInfo()->isShareable(); 267 } 268 269 /** 270 * @return bool 271 * @throws InvalidPathException 272 * @throws NotFoundException 273 */ 274 public function isCreatable() { 275 return $this->getFileInfo()->isCreatable(); 276 } 277 278 /** 279 * @return Node 280 */ 281 public function getParent() { 282 $newPath = dirname($this->path); 283 if ($newPath === '' || $newPath === '.' || $newPath === '/') { 284 return $this->root; 285 } 286 return $this->root->get($newPath); 287 } 288 289 /** 290 * @return string 291 */ 292 public function getName() { 293 return basename($this->path); 294 } 295 296 /** 297 * @param string $path 298 * @return string 299 */ 300 protected function normalizePath($path) { 301 if ($path === '' or $path === '/') { 302 return '/'; 303 } 304 //no windows style slashes 305 $path = str_replace('\\', '/', $path); 306 //add leading slash 307 if ($path[0] !== '/') { 308 $path = '/' . $path; 309 } 310 //remove duplicate slashes 311 while (strpos($path, '//') !== false) { 312 $path = str_replace('//', '/', $path); 313 } 314 //remove trailing slash 315 $path = rtrim($path, '/'); 316 317 return $path; 318 } 319 320 /** 321 * check if the requested path is valid 322 * 323 * @param string $path 324 * @return bool 325 */ 326 public function isValidPath($path) { 327 if (!$path || $path[0] !== '/') { 328 $path = '/' . $path; 329 } 330 if (strstr($path, '/../') || strrchr($path, '/') === '/..') { 331 return false; 332 } 333 return true; 334 } 335 336 public function isMounted() { 337 return $this->getFileInfo()->isMounted(); 338 } 339 340 public function isShared() { 341 return $this->getFileInfo()->isShared(); 342 } 343 344 public function getMimeType() { 345 return $this->getFileInfo()->getMimetype(); 346 } 347 348 public function getMimePart() { 349 return $this->getFileInfo()->getMimePart(); 350 } 351 352 public function getType() { 353 return $this->getFileInfo()->getType(); 354 } 355 356 public function isEncrypted() { 357 return $this->getFileInfo()->isEncrypted(); 358 } 359 360 public function getMountPoint() { 361 return $this->getFileInfo()->getMountPoint(); 362 } 363 364 public function getOwner() { 365 return $this->getFileInfo()->getOwner(); 366 } 367 368 public function getChecksum() { 369 } 370 371 public function getExtension(): string { 372 return $this->getFileInfo()->getExtension(); 373 } 374 375 /** 376 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE 377 * @throws LockedException 378 */ 379 public function lock($type) { 380 $this->view->lockFile($this->path, $type); 381 } 382 383 /** 384 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE 385 * @throws LockedException 386 */ 387 public function changeLock($type) { 388 $this->view->changeLock($this->path, $type); 389 } 390 391 /** 392 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE 393 * @throws LockedException 394 */ 395 public function unlock($type) { 396 $this->view->unlockFile($this->path, $type); 397 } 398 399 /** 400 * @param string $targetPath 401 * @return \OC\Files\Node\Node 402 * @throws InvalidPathException 403 * @throws NotFoundException 404 * @throws NotPermittedException if copy not allowed or failed 405 */ 406 public function copy($targetPath) { 407 $targetPath = $this->normalizePath($targetPath); 408 $parent = $this->root->get(dirname($targetPath)); 409 if ($parent instanceof Folder and $this->isValidPath($targetPath) and $parent->isCreatable()) { 410 $nonExisting = $this->createNonExistingNode($targetPath); 411 $this->sendHooks(['preCopy'], [$this, $nonExisting]); 412 $this->sendHooks(['preWrite'], [$nonExisting]); 413 if (!$this->view->copy($this->path, $targetPath)) { 414 throw new NotPermittedException('Could not copy ' . $this->path . ' to ' . $targetPath); 415 } 416 $targetNode = $this->root->get($targetPath); 417 $this->sendHooks(['postCopy'], [$this, $targetNode]); 418 $this->sendHooks(['postWrite'], [$targetNode]); 419 return $targetNode; 420 } else { 421 throw new NotPermittedException('No permission to copy to path ' . $targetPath); 422 } 423 } 424 425 /** 426 * @param string $targetPath 427 * @return \OC\Files\Node\Node 428 * @throws InvalidPathException 429 * @throws NotFoundException 430 * @throws NotPermittedException if move not allowed or failed 431 * @throws LockedException 432 */ 433 public function move($targetPath) { 434 $targetPath = $this->normalizePath($targetPath); 435 $parent = $this->root->get(dirname($targetPath)); 436 if ( 437 $parent instanceof Folder and 438 $this->isValidPath($targetPath) and 439 ( 440 $parent->isCreatable() || 441 ($parent->getInternalPath() === '' && $parent->getMountPoint() instanceof MoveableMount) 442 ) 443 ) { 444 $nonExisting = $this->createNonExistingNode($targetPath); 445 $this->sendHooks(['preRename'], [$this, $nonExisting]); 446 $this->sendHooks(['preWrite'], [$nonExisting]); 447 if (!$this->view->rename($this->path, $targetPath)) { 448 throw new NotPermittedException('Could not move ' . $this->path . ' to ' . $targetPath); 449 } 450 $targetNode = $this->root->get($targetPath); 451 $this->sendHooks(['postRename'], [$this, $targetNode]); 452 $this->sendHooks(['postWrite'], [$targetNode]); 453 $this->path = $targetPath; 454 return $targetNode; 455 } else { 456 throw new NotPermittedException('No permission to move to path ' . $targetPath); 457 } 458 } 459 460 public function getCreationTime(): int { 461 return $this->getFileInfo()->getCreationTime(); 462 } 463 464 public function getUploadTime(): int { 465 return $this->getFileInfo()->getUploadTime(); 466 } 467} 468