Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions build/psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3741,9 +3741,6 @@
</MoreSpecificReturnType>
</file>
<file src="lib/private/Files/Filesystem.php">
<InvalidNullableReturnType>
<code><![CDATA[string]]></code>
</InvalidNullableReturnType>
<NullableReturnStatement>
<code><![CDATA[$path]]></code>
</NullableReturnStatement>
Expand Down
49 changes: 33 additions & 16 deletions lib/private/Files/Filesystem.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use OC\Files\Storage\Storage;
use OC\Files\Storage\StorageFactory;
use OC\User\NoUserException;
use OCP\Cache\CappedMemoryCache;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Events\Node\FilesystemTornDownEvent;
use OCP\Files\InvalidPathException;
Expand Down Expand Up @@ -614,9 +615,9 @@
* @param bool $isAbsolutePath whether the given path is absolute
* @param bool $keepUnicode true to disable unicode normalization
* @psalm-taint-escape file
* @return string

Check failure on line 618 in lib/private/Files/Filesystem.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

InvalidNullableReturnType

lib/private/Files/Filesystem.php:618:13: InvalidNullableReturnType: The declared return type 'string' for OC\Files\Filesystem::normalizePath is not nullable, but 'null|string' contains null (see https://psalm.dev/144)
*/
public static function normalizePath($path, $stripTrailingSlash = true, $isAbsolutePath = false, $keepUnicode = false) {
public static function normalizePath($path, $stripTrailingSlash = true, $isAbsolutePath = false, bool $keepUnicode = false) {
/**
* FIXME: This is a workaround for existing classes and files which call
* this function with another type than a valid string. This
Expand All @@ -629,27 +630,43 @@
return '/';
}

//normalize unicode if possible
if (!$keepUnicode) {
// normalize unicode if possible, skipping the ICU normalizer entirely for
// plain ASCII paths, which are already in normalization form C
if (!$keepUnicode && preg_match('/[\x80-\xff]/', $path)) {
$path = \OC_Util::normalizeUnicode($path);
}

//add leading slash, if it is already there we strip it anyway
$path = '/' . $path;
// Add leading slash if it's missing
if ($path[0] !== '/') {
$path = '/' . $path;
}

// No null bytes
if (str_contains($path, chr(0))) {
$path = str_replace(chr(0), '', $path);
}

$patterns = [
'#\\\\#s', // no windows style '\\' slashes
'#/\.(/\.)*/#s', // remove '/./'
'#\//+#s', // remove sequence of slashes
'#/\.$#s', // remove trailing '/.'
];
// No windows style slashes
if (str_contains($path, '\\')) {
$path = str_replace('\\', '/', $path);
}

do {
$count = 0;
$path = preg_replace($patterns, '/', $path, -1, $count);
} while ($count > 0);
// most paths reaching here are already clean, so skip the regex engine entirely
// when neither a duplicate slash nor a '.' segment is present
if (str_contains($path, '//') || str_contains($path, '/.')) {
$patterns = [
'#/\.(/\.)*/#s', // remove '/./'
'#\//+#s', // remove sequence of slashes
'#/\.$#s', // remove trailing '/.'
];

do {
$count = 0;
$path = preg_replace($patterns, '/', $path, -1, $count);
} while ($count > 0);
}

//remove trailing slash
// remove trailing slash
if ($stripTrailingSlash && strlen($path) > 1) {
$path = rtrim($path, '/');
}
Expand Down
6 changes: 3 additions & 3 deletions lib/private/Files/Mount/MountPoint.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@

namespace OC\Files\Mount;

use OC\Files\Filesystem;
use OC\Files\Storage\Storage;
use OC\Files\Storage\StorageFactory;
use OC\Files\Utils\PathHelper;
use OCP\Files\Mount\IMountPoint;
use OCP\Files\Storage\IStorage;
use OCP\Files\Storage\IStorageFactory;
Expand Down Expand Up @@ -197,7 +197,7 @@ public function getNumericStorageId() {
*/
#[\Override]
public function getInternalPath($path) {
$path = Filesystem::normalizePath($path, true, false, true);
$path = PathHelper::normalizePath($path);
if ($this->mountPoint === $path || $this->mountPoint . '/' === $path) {
$internalPath = '';
} else {
Expand All @@ -212,7 +212,7 @@ public function getInternalPath($path) {
* @return string
*/
private function formatPath($path) {
$path = Filesystem::normalizePath($path);
$path = PathHelper::normalizePath($path);
if (strlen($path) > 1) {
$path .= '/';
}
Expand Down
17 changes: 3 additions & 14 deletions lib/private/Files/Node/Node.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,31 +29,20 @@
// FIXME: this class really should be abstract (+1)
class Node implements INode {
/**
* @var View $view
*/
protected $view;

protected IRootFolder $root;

/**
* @param View $view
* @param \OCP\Files\IRootFolder $root
* @param string $path
* @param FileInfo $fileInfo
*/
public function __construct(
IRootFolder $root,
$view,
protected IRootFolder $root,
protected View $view,
protected $path,
protected ?FileInfo $fileInfo = null,
protected ?INode $parent = null,
private bool $infoHasSubMountsIncluded = true,
) {
if (Filesystem::normalizePath($view->getRoot()) !== '/') {
if (PathHelper::normalizePath($this->view->getRoot()) !== '/') {
throw new PreConditionNotMetException('The view passed to the node should not have any fake root set');
}
$this->view = $view;
$this->root = $root;
}

/**
Expand Down
3 changes: 2 additions & 1 deletion lib/private/Files/Storage/DAV.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Icewind\Streams\CallbackWrapper;
use Icewind\Streams\IteratorDirectory;
use OC\Files\Filesystem;
use OC\Files\Utils\PathHelper;
use OC\MemCache\ArrayCache;
use OC\OCM\OCMSignatoryManager;
use OCP\AppFramework\Http;
Expand Down Expand Up @@ -990,7 +991,7 @@ public function cleanPath(string $path): string {
if ($path === '') {
return $path;
}
$path = Filesystem::normalizePath($path);
$path = PathHelper::normalizePath($path);
// remove leading slash
return substr($path, 1);
}
Expand Down
5 changes: 3 additions & 2 deletions lib/private/Files/Storage/Wrapper/Encryption.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use OC\Files\ObjectStore\ObjectStoreStorage;
use OC\Files\Storage\Common;
use OC\Files\Storage\LocalTempFileTrait;
use OC\Files\Utils\PathHelper;
use OC\Memcache\ArrayCache;
use OCP\Cache\CappedMemoryCache;
use OCP\Encryption\Exceptions\InvalidHeaderException;
Expand Down Expand Up @@ -784,7 +785,7 @@ public function hash(string $type, string $path, bool $raw = false): string|fals
* @return string full path including mount point
*/
protected function getFullPath(string $path): string {
return Filesystem::normalizePath($this->mountPoint . '/' . $path);
return PathHelper::normalizePath($this->mountPoint . '/' . $path);
}

/**
Expand Down Expand Up @@ -898,7 +899,7 @@ protected function copyKeys(string $source, string $target): bool {
* check if path points to a files version
*/
protected function isVersion(string $path): bool {
$normalized = Filesystem::normalizePath($path);
$normalized = PathHelper::normalizePath($path);
return substr($normalized, 0, strlen('/files_versions/')) === '/files_versions/';
}

Expand Down
20 changes: 14 additions & 6 deletions lib/private/Files/Storage/Wrapper/Jail.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
use OC\Files\Cache\Wrapper\CacheJail;
use OC\Files\Cache\Wrapper\JailPropagator;
use OC\Files\Cache\Wrapper\JailWatcher;
use OC\Files\Filesystem;
use OC\Files\Storage\FailedStorage;
use OC\Files\Utils\PathHelper;
use OCP\Files\Cache\ICache;
use OCP\Files\Cache\IPropagator;
use OCP\Files\Cache\IWatcher;
Expand All @@ -30,10 +30,10 @@
* This restricts access to a subfolder of the wrapped storage with the subfolder becoming the root folder new storage
*/
class Jail extends Wrapper {
/**
* @var string
*/
protected $rootPath;
protected string $rootPath;

/** Normalized, slash-trimmed version of $rootPath */
private ?string $normalizedRootPath = null;

/**
* @param array $parameters ['storage' => $storage, 'root' => $root]
Expand All @@ -47,7 +47,15 @@ public function __construct(array $parameters) {
}

public function getUnjailedPath(string $path): string {
return trim(Filesystem::normalizePath($this->rootPath . '/' . $path), '/');
if ($this->normalizedRootPath === null) {
$this->normalizedRootPath = trim(PathHelper::normalizePath($this->rootPath), '/');
}

$normalizedPath = trim(PathHelper::normalizePath($path), '/');
if ($this->normalizedRootPath === '') {
return $normalizedPath;
}
return $normalizedPath === '' ? $this->normalizedRootPath : $this->normalizedRootPath . '/' . $normalizedPath;
}

/**
Expand Down
43 changes: 22 additions & 21 deletions lib/private/Files/Utils/PathHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,48 +11,49 @@
class PathHelper {
/**
* Make a path relative to a root path, or return null if the path is outside the root
*
* @param string $root
* @param string $path
* @return ?string
*/
public static function getRelativePath(string $root, string $path) {
public static function getRelativePath(string $root, string $path): ?string {
if ($root === '' || $root === '/') {
return self::normalizePath($path);
}
if ($path === $root) {
return '/';
} elseif (!str_starts_with($path, $root . '/')) {
}

if (!str_starts_with($path, $root . '/')) {
return null;
} else {
$path = substr($path, strlen($root));
return self::normalizePath($path);
}

$path = substr($path, strlen($root));
return self::normalizePath($path);
}

/**
* @param string $path
* @return string
*/
public static function normalizePath(string $path): string {
if ($path === '' || $path === '/') {
return '/';
}

// No null bytes
$path = str_replace(chr(0), '', $path);
//no windows style slashes
$path = str_replace('\\', '/', $path);
//add leading slash
if (str_contains($path, chr(0))) {
$path = str_replace(chr(0), '', $path);
}

// No windows style slashes
if (str_contains($path, '\\')) {
$path = str_replace('\\', '/', $path);
}

// Add leading slash if it's missing
if ($path[0] !== '/') {
$path = '/' . $path;
}
//remove duplicate slashes

// Remove duplicate slashes
while (str_contains($path, '//')) {
$path = str_replace('//', '/', $path);
}
//remove trailing slash
$path = rtrim($path, '/');

return $path;
// Remove trailing slash
return rtrim($path, '/');
}
}
Loading