Skip to content

Commit 940e5bf

Browse files
committed
perf(normalization): Optimize path normalization
Signed-off-by: Carl Schwan <[email protected]>
1 parent 8f8b441 commit 940e5bf

File tree

1 file changed

+42
-31
lines changed

1 file changed

+42
-31
lines changed

lib/private/Files/Filesystem.php

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,8 @@
77
*/
88
namespace OC\Files;
99

10-
use OC\Files\Mount\MountPoint;
1110
use OC\Files\Storage\StorageFactory;
1211
use OC\User\NoUserException;
13-
use OCP\Cache\CappedMemoryCache;
1412
use OCP\EventDispatcher\IEventDispatcher;
1513
use OCP\Files\Events\Node\FilesystemTornDownEvent;
1614
use OCP\Files\Mount\IMountManager;
@@ -28,8 +26,6 @@ class Filesystem {
2826

2927
private static ?View $defaultInstance = null;
3028

31-
private static ?CappedMemoryCache $normalizedPathCache = null;
32-
3329
private static ?FilenameValidator $validator = null;
3430

3531
/**
@@ -603,46 +599,61 @@ public static function normalizePath($path, $stripTrailingSlash = true, $isAbsol
603599
*/
604600
$path = (string)$path;
605601

606-
if ($path === '') {
602+
if ($path === '' || $path === '/') {
607603
return '/';
608604
}
609605

610-
if (is_null(self::$normalizedPathCache)) {
611-
self::$normalizedPathCache = new CappedMemoryCache(2048);
612-
}
613-
614-
$cacheKey = json_encode([$path, $stripTrailingSlash, $isAbsolutePath, $keepUnicode]);
615-
616-
if ($cacheKey && isset(self::$normalizedPathCache[$cacheKey])) {
617-
return self::$normalizedPathCache[$cacheKey];
618-
}
619-
620606
//normalize unicode if possible
621607
if (!$keepUnicode) {
622608
$path = \OC_Util::normalizeUnicode($path);
623609
}
624610

625-
//add leading slash, if it is already there we strip it anyway
626-
$path = '/' . $path;
611+
$len = strlen($path);
612+
$out = [];
613+
$outLen = 0;
614+
615+
// Force leading slash
616+
$out[$outLen++] = '/';
617+
618+
$prev = '/';
619+
$i = 0;
627620

628-
$patterns = [
629-
'#\\\\#s', // no windows style '\\' slashes
630-
'#/\.(/\.)*/#s', // remove '/./'
631-
'#\//+#s', // remove sequence of slashes
632-
'#/\.$#s', // remove trailing '/.'
633-
];
621+
while ($i < $len) {
622+
$c = $path[$i++];
634623

635-
do {
636-
$count = 0;
637-
$path = preg_replace($patterns, '/', $path, -1, $count);
638-
} while ($count > 0);
624+
// Normalize Windows slashes
625+
if ($c === '\\') {
626+
$c = '/';
627+
}
628+
629+
// Collapse multiple slashes
630+
if ($c === '/' && $prev === '/') {
631+
continue;
632+
}
633+
634+
// Handle "/./" and trailing "/."
635+
if ($c === '.' && $prev === '/') {
636+
$next = $i < $len ? $path[$i] : null;
637+
638+
if ($next === '/' || $next === null) {
639+
// Skip "./"
640+
if ($next === '/') {
641+
$i++;
642+
}
643+
continue;
644+
}
645+
}
646+
647+
$out[$outLen++] = $c;
648+
$prev = $c;
649+
}
639650

640-
//remove trailing slash
641-
if ($stripTrailingSlash && strlen($path) > 1) {
642-
$path = rtrim($path, '/');
651+
// Remove trailing slash
652+
if ($stripTrailingSlash && $outLen > 1 && $out[$outLen - 1] === '/') {
653+
array_pop($out);
643654
}
644655

645-
self::$normalizedPathCache[$cacheKey] = $path;
656+
$path = implode('', $out);
646657

647658
return $path;
648659
}

0 commit comments

Comments
 (0)