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