1<?php 2/** 3 * @copyright Copyright (c) 2016, ownCloud, Inc. 4 * 5 * @author Christoph Wurst <christoph@winzerhof-wurst.at> 6 * @author J0WI <J0WI@users.noreply.github.com> 7 * @author Julius Härtl <jus@bitgrid.net> 8 * @author Lukas Reschke <lukas@statuscode.ch> 9 * @author Morris Jobke <hey@morrisjobke.de> 10 * @author Robin Appelman <robin@icewind.nl> 11 * @author Roeland Jago Douma <roeland@famdouma.nl> 12 * @author Tigran Mkrtchyan <tigran.mkrtchyan@desy.de> 13 * 14 * @license AGPL-3.0 15 * 16 * This code is free software: you can redistribute it and/or modify 17 * it under the terms of the GNU Affero General Public License, version 3, 18 * as published by the Free Software Foundation. 19 * 20 * This program is distributed in the hope that it will be useful, 21 * but WITHOUT ANY WARRANTY; without even the implied warranty of 22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 * GNU Affero General Public License for more details. 24 * 25 * You should have received a copy of the GNU Affero General Public License, version 3, 26 * along with this program. If not, see <http://www.gnu.org/licenses/> 27 * 28 */ 29namespace OC\Files\Storage\Wrapper; 30 31use OC\Files\Cache\Wrapper\CacheJail; 32use OC\Files\Cache\Wrapper\JailPropagator; 33use OC\Files\Filesystem; 34use OCP\Files\Storage\IStorage; 35use OCP\Files\Storage\IWriteStreamStorage; 36use OCP\Lock\ILockingProvider; 37 38/** 39 * Jail to a subdirectory of the wrapped storage 40 * 41 * This restricts access to a subfolder of the wrapped storage with the subfolder becoming the root folder new storage 42 */ 43class Jail extends Wrapper { 44 /** 45 * @var string 46 */ 47 protected $rootPath; 48 49 /** 50 * @param array $arguments ['storage' => $storage, 'root' => $root] 51 * 52 * $storage: The storage that will be wrapper 53 * $root: The folder in the wrapped storage that will become the root folder of the wrapped storage 54 */ 55 public function __construct($arguments) { 56 parent::__construct($arguments); 57 $this->rootPath = $arguments['root']; 58 } 59 60 public function getUnjailedPath($path) { 61 return trim(Filesystem::normalizePath($this->rootPath . '/' . $path), '/'); 62 } 63 64 /** 65 * This is separate from Wrapper::getWrapperStorage so we can get the jailed storage consistently even if the jail is inside another wrapper 66 */ 67 public function getUnjailedStorage() { 68 return $this->storage; 69 } 70 71 72 public function getJailedPath($path) { 73 $root = rtrim($this->rootPath, '/') . '/'; 74 75 if ($path !== $this->rootPath && strpos($path, $root) !== 0) { 76 return null; 77 } else { 78 $path = substr($path, strlen($this->rootPath)); 79 return trim($path, '/'); 80 } 81 } 82 83 public function getId() { 84 return parent::getId(); 85 } 86 87 /** 88 * see https://www.php.net/manual/en/function.mkdir.php 89 * 90 * @param string $path 91 * @return bool 92 */ 93 public function mkdir($path) { 94 return $this->getWrapperStorage()->mkdir($this->getUnjailedPath($path)); 95 } 96 97 /** 98 * see https://www.php.net/manual/en/function.rmdir.php 99 * 100 * @param string $path 101 * @return bool 102 */ 103 public function rmdir($path) { 104 return $this->getWrapperStorage()->rmdir($this->getUnjailedPath($path)); 105 } 106 107 /** 108 * see https://www.php.net/manual/en/function.opendir.php 109 * 110 * @param string $path 111 * @return resource|bool 112 */ 113 public function opendir($path) { 114 return $this->getWrapperStorage()->opendir($this->getUnjailedPath($path)); 115 } 116 117 /** 118 * see https://www.php.net/manual/en/function.is_dir.php 119 * 120 * @param string $path 121 * @return bool 122 */ 123 public function is_dir($path) { 124 return $this->getWrapperStorage()->is_dir($this->getUnjailedPath($path)); 125 } 126 127 /** 128 * see https://www.php.net/manual/en/function.is_file.php 129 * 130 * @param string $path 131 * @return bool 132 */ 133 public function is_file($path) { 134 return $this->getWrapperStorage()->is_file($this->getUnjailedPath($path)); 135 } 136 137 /** 138 * see https://www.php.net/manual/en/function.stat.php 139 * only the following keys are required in the result: size and mtime 140 * 141 * @param string $path 142 * @return array|bool 143 */ 144 public function stat($path) { 145 return $this->getWrapperStorage()->stat($this->getUnjailedPath($path)); 146 } 147 148 /** 149 * see https://www.php.net/manual/en/function.filetype.php 150 * 151 * @param string $path 152 * @return bool 153 */ 154 public function filetype($path) { 155 return $this->getWrapperStorage()->filetype($this->getUnjailedPath($path)); 156 } 157 158 /** 159 * see https://www.php.net/manual/en/function.filesize.php 160 * The result for filesize when called on a folder is required to be 0 161 * 162 * @param string $path 163 * @return int|bool 164 */ 165 public function filesize($path) { 166 return $this->getWrapperStorage()->filesize($this->getUnjailedPath($path)); 167 } 168 169 /** 170 * check if a file can be created in $path 171 * 172 * @param string $path 173 * @return bool 174 */ 175 public function isCreatable($path) { 176 return $this->getWrapperStorage()->isCreatable($this->getUnjailedPath($path)); 177 } 178 179 /** 180 * check if a file can be read 181 * 182 * @param string $path 183 * @return bool 184 */ 185 public function isReadable($path) { 186 return $this->getWrapperStorage()->isReadable($this->getUnjailedPath($path)); 187 } 188 189 /** 190 * check if a file can be written to 191 * 192 * @param string $path 193 * @return bool 194 */ 195 public function isUpdatable($path) { 196 return $this->getWrapperStorage()->isUpdatable($this->getUnjailedPath($path)); 197 } 198 199 /** 200 * check if a file can be deleted 201 * 202 * @param string $path 203 * @return bool 204 */ 205 public function isDeletable($path) { 206 return $this->getWrapperStorage()->isDeletable($this->getUnjailedPath($path)); 207 } 208 209 /** 210 * check if a file can be shared 211 * 212 * @param string $path 213 * @return bool 214 */ 215 public function isSharable($path) { 216 return $this->getWrapperStorage()->isSharable($this->getUnjailedPath($path)); 217 } 218 219 /** 220 * get the full permissions of a path. 221 * Should return a combination of the PERMISSION_ constants defined in lib/public/constants.php 222 * 223 * @param string $path 224 * @return int 225 */ 226 public function getPermissions($path) { 227 return $this->getWrapperStorage()->getPermissions($this->getUnjailedPath($path)); 228 } 229 230 /** 231 * see https://www.php.net/manual/en/function.file_exists.php 232 * 233 * @param string $path 234 * @return bool 235 */ 236 public function file_exists($path) { 237 return $this->getWrapperStorage()->file_exists($this->getUnjailedPath($path)); 238 } 239 240 /** 241 * see https://www.php.net/manual/en/function.filemtime.php 242 * 243 * @param string $path 244 * @return int|bool 245 */ 246 public function filemtime($path) { 247 return $this->getWrapperStorage()->filemtime($this->getUnjailedPath($path)); 248 } 249 250 /** 251 * see https://www.php.net/manual/en/function.file_get_contents.php 252 * 253 * @param string $path 254 * @return string|bool 255 */ 256 public function file_get_contents($path) { 257 return $this->getWrapperStorage()->file_get_contents($this->getUnjailedPath($path)); 258 } 259 260 /** 261 * see https://www.php.net/manual/en/function.file_put_contents.php 262 * 263 * @param string $path 264 * @param mixed $data 265 * @return int|false 266 */ 267 public function file_put_contents($path, $data) { 268 return $this->getWrapperStorage()->file_put_contents($this->getUnjailedPath($path), $data); 269 } 270 271 /** 272 * see https://www.php.net/manual/en/function.unlink.php 273 * 274 * @param string $path 275 * @return bool 276 */ 277 public function unlink($path) { 278 return $this->getWrapperStorage()->unlink($this->getUnjailedPath($path)); 279 } 280 281 /** 282 * see https://www.php.net/manual/en/function.rename.php 283 * 284 * @param string $path1 285 * @param string $path2 286 * @return bool 287 */ 288 public function rename($path1, $path2) { 289 return $this->getWrapperStorage()->rename($this->getUnjailedPath($path1), $this->getUnjailedPath($path2)); 290 } 291 292 /** 293 * see https://www.php.net/manual/en/function.copy.php 294 * 295 * @param string $path1 296 * @param string $path2 297 * @return bool 298 */ 299 public function copy($path1, $path2) { 300 return $this->getWrapperStorage()->copy($this->getUnjailedPath($path1), $this->getUnjailedPath($path2)); 301 } 302 303 /** 304 * see https://www.php.net/manual/en/function.fopen.php 305 * 306 * @param string $path 307 * @param string $mode 308 * @return resource|bool 309 */ 310 public function fopen($path, $mode) { 311 return $this->getWrapperStorage()->fopen($this->getUnjailedPath($path), $mode); 312 } 313 314 /** 315 * get the mimetype for a file or folder 316 * The mimetype for a folder is required to be "httpd/unix-directory" 317 * 318 * @param string $path 319 * @return string|bool 320 */ 321 public function getMimeType($path) { 322 return $this->getWrapperStorage()->getMimeType($this->getUnjailedPath($path)); 323 } 324 325 /** 326 * see https://www.php.net/manual/en/function.hash.php 327 * 328 * @param string $type 329 * @param string $path 330 * @param bool $raw 331 * @return string|bool 332 */ 333 public function hash($type, $path, $raw = false) { 334 return $this->getWrapperStorage()->hash($type, $this->getUnjailedPath($path), $raw); 335 } 336 337 /** 338 * see https://www.php.net/manual/en/function.free_space.php 339 * 340 * @param string $path 341 * @return int|bool 342 */ 343 public function free_space($path) { 344 return $this->getWrapperStorage()->free_space($this->getUnjailedPath($path)); 345 } 346 347 /** 348 * search for occurrences of $query in file names 349 * 350 * @param string $query 351 * @return array|bool 352 */ 353 public function search($query) { 354 return $this->getWrapperStorage()->search($query); 355 } 356 357 /** 358 * see https://www.php.net/manual/en/function.touch.php 359 * If the backend does not support the operation, false should be returned 360 * 361 * @param string $path 362 * @param int $mtime 363 * @return bool 364 */ 365 public function touch($path, $mtime = null) { 366 return $this->getWrapperStorage()->touch($this->getUnjailedPath($path), $mtime); 367 } 368 369 /** 370 * get the path to a local version of the file. 371 * The local version of the file can be temporary and doesn't have to be persistent across requests 372 * 373 * @param string $path 374 * @return string|bool 375 */ 376 public function getLocalFile($path) { 377 return $this->getWrapperStorage()->getLocalFile($this->getUnjailedPath($path)); 378 } 379 380 /** 381 * check if a file or folder has been updated since $time 382 * 383 * @param string $path 384 * @param int $time 385 * @return bool 386 * 387 * hasUpdated for folders should return at least true if a file inside the folder is add, removed or renamed. 388 * returning true for other changes in the folder is optional 389 */ 390 public function hasUpdated($path, $time) { 391 return $this->getWrapperStorage()->hasUpdated($this->getUnjailedPath($path), $time); 392 } 393 394 /** 395 * get a cache instance for the storage 396 * 397 * @param string $path 398 * @param \OC\Files\Storage\Storage|null (optional) the storage to pass to the cache 399 * @return \OC\Files\Cache\Cache 400 */ 401 public function getCache($path = '', $storage = null) { 402 if (!$storage) { 403 $storage = $this->getWrapperStorage(); 404 } 405 $sourceCache = $this->getWrapperStorage()->getCache($this->getUnjailedPath($path), $storage); 406 return new CacheJail($sourceCache, $this->rootPath); 407 } 408 409 /** 410 * get the user id of the owner of a file or folder 411 * 412 * @param string $path 413 * @return string 414 */ 415 public function getOwner($path) { 416 return $this->getWrapperStorage()->getOwner($this->getUnjailedPath($path)); 417 } 418 419 /** 420 * get a watcher instance for the cache 421 * 422 * @param string $path 423 * @param \OC\Files\Storage\Storage (optional) the storage to pass to the watcher 424 * @return \OC\Files\Cache\Watcher 425 */ 426 public function getWatcher($path = '', $storage = null) { 427 if (!$storage) { 428 $storage = $this; 429 } 430 return $this->getWrapperStorage()->getWatcher($this->getUnjailedPath($path), $storage); 431 } 432 433 /** 434 * get the ETag for a file or folder 435 * 436 * @param string $path 437 * @return string|bool 438 */ 439 public function getETag($path) { 440 return $this->getWrapperStorage()->getETag($this->getUnjailedPath($path)); 441 } 442 443 public function getMetaData($path) { 444 return $this->getWrapperStorage()->getMetaData($this->getUnjailedPath($path)); 445 } 446 447 /** 448 * @param string $path 449 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE 450 * @param \OCP\Lock\ILockingProvider $provider 451 * @throws \OCP\Lock\LockedException 452 */ 453 public function acquireLock($path, $type, ILockingProvider $provider) { 454 $this->getWrapperStorage()->acquireLock($this->getUnjailedPath($path), $type, $provider); 455 } 456 457 /** 458 * @param string $path 459 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE 460 * @param \OCP\Lock\ILockingProvider $provider 461 */ 462 public function releaseLock($path, $type, ILockingProvider $provider) { 463 $this->getWrapperStorage()->releaseLock($this->getUnjailedPath($path), $type, $provider); 464 } 465 466 /** 467 * @param string $path 468 * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE 469 * @param \OCP\Lock\ILockingProvider $provider 470 */ 471 public function changeLock($path, $type, ILockingProvider $provider) { 472 $this->getWrapperStorage()->changeLock($this->getUnjailedPath($path), $type, $provider); 473 } 474 475 /** 476 * Resolve the path for the source of the share 477 * 478 * @param string $path 479 * @return array 480 */ 481 public function resolvePath($path) { 482 return [$this->getWrapperStorage(), $this->getUnjailedPath($path)]; 483 } 484 485 /** 486 * @param IStorage $sourceStorage 487 * @param string $sourceInternalPath 488 * @param string $targetInternalPath 489 * @return bool 490 */ 491 public function copyFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) { 492 if ($sourceStorage === $this) { 493 return $this->copy($sourceInternalPath, $targetInternalPath); 494 } 495 return $this->getWrapperStorage()->copyFromStorage($sourceStorage, $sourceInternalPath, $this->getUnjailedPath($targetInternalPath)); 496 } 497 498 /** 499 * @param IStorage $sourceStorage 500 * @param string $sourceInternalPath 501 * @param string $targetInternalPath 502 * @return bool 503 */ 504 public function moveFromStorage(IStorage $sourceStorage, $sourceInternalPath, $targetInternalPath) { 505 if ($sourceStorage === $this) { 506 return $this->rename($sourceInternalPath, $targetInternalPath); 507 } 508 return $this->getWrapperStorage()->moveFromStorage($sourceStorage, $sourceInternalPath, $this->getUnjailedPath($targetInternalPath)); 509 } 510 511 public function getPropagator($storage = null) { 512 if (isset($this->propagator)) { 513 return $this->propagator; 514 } 515 516 if (!$storage) { 517 $storage = $this; 518 } 519 $this->propagator = new JailPropagator($storage, \OC::$server->getDatabaseConnection()); 520 return $this->propagator; 521 } 522 523 public function writeStream(string $path, $stream, int $size = null): int { 524 $storage = $this->getWrapperStorage(); 525 if ($storage->instanceOfStorage(IWriteStreamStorage::class)) { 526 /** @var IWriteStreamStorage $storage */ 527 return $storage->writeStream($this->getUnjailedPath($path), $stream, $size); 528 } else { 529 $target = $this->fopen($path, 'w'); 530 [$count, $result] = \OC_Helper::streamCopy($stream, $target); 531 fclose($stream); 532 fclose($target); 533 return $count; 534 } 535 } 536 537 public function getDirectoryContent($directory): \Traversable { 538 return $this->getWrapperStorage()->getDirectoryContent($this->getUnjailedPath($directory)); 539 } 540} 541