Skip to content

Commit 76ac2f8

Browse files
committed
perf(normalization): Optimize path normalization
Signed-off-by: Carl Schwan <carlschwan@kde.org>
1 parent 8f8b441 commit 76ac2f8

File tree

1 file changed

+49
-18
lines changed

1 file changed

+49
-18
lines changed

lib/private/Files/Filesystem.php

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -603,17 +603,21 @@ public static function normalizePath($path, $stripTrailingSlash = true, $isAbsol
603603
*/
604604
$path = (string)$path;
605605

606-
if ($path === '') {
606+
if ($path === '' || $path === '/') {
607607
return '/';
608608
}
609609

610610
if (is_null(self::$normalizedPathCache)) {
611611
self::$normalizedPathCache = new CappedMemoryCache(2048);
612612
}
613613

614-
$cacheKey = json_encode([$path, $stripTrailingSlash, $isAbsolutePath, $keepUnicode]);
614+
$cacheKey =
615+
$path . "\0" .
616+
(int)$stripTrailingSlash . "\0" .
617+
(int)$isAbsolutePath . "\0" .
618+
(int)$keepUnicode;
615619

616-
if ($cacheKey && isset(self::$normalizedPathCache[$cacheKey])) {
620+
if (isset(self::$normalizedPathCache[$cacheKey])) {
617621
return self::$normalizedPathCache[$cacheKey];
618622
}
619623

@@ -622,26 +626,53 @@ public static function normalizePath($path, $stripTrailingSlash = true, $isAbsol
622626
$path = \OC_Util::normalizeUnicode($path);
623627
}
624628

625-
//add leading slash, if it is already there we strip it anyway
626-
$path = '/' . $path;
629+
$len = strlen($path);
630+
$out = [];
631+
$outLen = 0;
627632

628-
$patterns = [
629-
'#\\\\#s', // no windows style '\\' slashes
630-
'#/\.(/\.)*/#s', // remove '/./'
631-
'#\//+#s', // remove sequence of slashes
632-
'#/\.$#s', // remove trailing '/.'
633-
];
633+
// Force leading slash
634+
$out[$outLen++] = '/';
634635

635-
do {
636-
$count = 0;
637-
$path = preg_replace($patterns, '/', $path, -1, $count);
638-
} while ($count > 0);
636+
$prev = '/';
637+
$i = 0;
639638

640-
//remove trailing slash
641-
if ($stripTrailingSlash && strlen($path) > 1) {
642-
$path = rtrim($path, '/');
639+
while ($i < $len) {
640+
$c = $path[$i++];
641+
642+
// Normalize Windows slashes
643+
if ($c === '\\') {
644+
$c = '/';
645+
}
646+
647+
// Collapse multiple slashes
648+
if ($c === '/' && $prev === '/') {
649+
continue;
650+
}
651+
652+
// Handle "/./" and trailing "/."
653+
if ($c === '.' && $prev === '/') {
654+
$next = $i < $len ? $path[$i] : null;
655+
656+
if ($next === '/' || $next === null) {
657+
// Skip "./"
658+
if ($next === '/') {
659+
$i++;
660+
}
661+
continue;
662+
}
663+
}
664+
665+
$out[$outLen++] = $c;
666+
$prev = $c;
643667
}
644668

669+
// Remove trailing slash
670+
if ($stripTrailingSlash && $outLen > 1 && $out[$outLen - 1] === '/') {
671+
array_pop($out);
672+
}
673+
674+
$path = implode('', $out);
675+
645676
self::$normalizedPathCache[$cacheKey] = $path;
646677

647678
return $path;

0 commit comments

Comments
 (0)