Skip to content

Commit 30ab64b

Browse files
refactor(Filesystem): use PathHelper for canonical normalization
Signed-off-by: Josh <[email protected]>
1 parent a6bcb18 commit 30ab64b

File tree

1 file changed

+41
-39
lines changed

1 file changed

+41
-39
lines changed

lib/private/Files/Filesystem.php

Lines changed: 41 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
use OC\Files\Mount\MountPoint;
1111
use OC\Files\Storage\StorageFactory;
12+
use OC\Files\Utils\PathHelper;
1213
use OC\User\NoUserException;
1314
use OCP\Cache\CappedMemoryCache;
1415
use OCP\EventDispatcher\IEventDispatcher;
@@ -585,66 +586,67 @@ public static function hasUpdated($path, $time) {
585586
}
586587

587588
/**
588-
* Fix common problems with a file path
589+
* Normalizes a file path for consistent use within the virtual filesystem.
590+
*
591+
* Applies Unicode normalization (optional), removes dot-segments and duplicate slashes,
592+
* enforces leading slash, and optionally strips trailing slashes. Results are cached
593+
* per unique input to improve performance.
594+
*
595+
* TODO: Better unify with Pathhelper::normalizePath() (and possibly others)
596+
* TODO: Benchmark whether the cache is still beneficial today
589597
*
590-
* @param string $path
591-
* @param bool $stripTrailingSlash whether to strip the trailing slash
592-
* @param bool $isAbsolutePath whether the given path is absolute
593-
* @param bool $keepUnicode true to disable unicode normalization
594598
* @psalm-taint-escape file
595-
* @return string
596-
*/
597-
public static function normalizePath($path, $stripTrailingSlash = true, $isAbsolutePath = false, $keepUnicode = false) {
598-
/**
599-
* FIXME: This is a workaround for existing classes and files which call
600-
* this function with another type than a valid string. This
601-
* conversion should get removed as soon as all existing
602-
* function calls have been fixed.
603-
*/
604-
$path = (string)$path;
605-
606-
if ($path === '') {
599+
*
600+
* @param string $path The file path to normalize.
601+
* @param bool $stripTrailingSlash Remove the trailing slash (except root). Default: true.
602+
* @param bool $isAbsolutePath Ignored; kept for legacy compatibility.
603+
* @param bool $keepUnicode If true, skips Unicode normalization. Default: false.
604+
* @return string Normalized path.
605+
*/
606+
public static function normalizePath(
607+
string $path,
608+
bool $stripTrailingSlash = true,
609+
bool $isAbsolutePath = false,
610+
bool $keepUnicode = false
611+
): string {
612+
// Early return for root and empty string
613+
if ($path === '' || $path === '/') {
607614
return '/';
608615
}
609616

617+
// Prepare cache
610618
if (is_null(self::$normalizedPathCache)) {
611619
self::$normalizedPathCache = new CappedMemoryCache(2048);
612620
}
613-
614621
$cacheKey = json_encode([$path, $stripTrailingSlash, $isAbsolutePath, $keepUnicode]);
615-
616622
if ($cacheKey && isset(self::$normalizedPathCache[$cacheKey])) {
617623
return self::$normalizedPathCache[$cacheKey];
618624
}
619625

620-
//normalize unicode if possible
626+
// Unicode normalization
621627
if (!$keepUnicode) {
622628
$path = \OC_Util::normalizeUnicode($path);
623629
}
624630

625-
//add leading slash, if it is already there we strip it anyway
626-
$path = '/' . $path;
627-
628-
$patterns = [
629-
'#\\\\#s', // no windows style '\\' slashes
630-
'#/\.(/\.)*/#s', // remove '/./'
631-
'#\//+#s', // remove sequence of slashes
632-
'#/\.$#s', // remove trailing '/.'
633-
];
631+
// Canonical normalization via PathHelper
632+
$normalized = PathHelper::normalizePath($path);
634633

635-
do {
636-
$count = 0;
637-
$path = preg_replace($patterns, '/', $path, -1, $count);
638-
} while ($count > 0);
639-
640-
//remove trailing slash
641-
if ($stripTrailingSlash && strlen($path) > 1) {
642-
$path = rtrim($path, '/');
634+
// TEMPORARY: Remove dot-segments here until PathHelper::normalizePath() is assessed/updated to handle them natively
635+
while (\str_contains($normalized, '/./')) {
636+
$normalized = \str_replace('/./', '/', $normalized);
637+
}
638+
// Remove trailing '/.' (unless the whole thing is '/.')
639+
if (\substr($normalized, -2) === '/.' && \strlen($normalized) > 2) {
640+
$normalized = \substr($normalized, 0, -2);
643641
}
644642

645-
self::$normalizedPathCache[$cacheKey] = $path;
643+
// Optionally strip trailing slash unless root
644+
if ($stripTrailingSlash && \strlen($normalized) > 1) {
645+
$normalized = \rtrim($normalized, '/');
646+
}
646647

647-
return $path;
648+
self::$normalizedPathCache[$cacheKey] = $normalized;
649+
return $normalized;
648650
}
649651

650652
/**

0 commit comments

Comments
 (0)