Skip to content

Commit 18edb31

Browse files
authored
refactor(router-core): simplify decodePath implementation, improve performance (#5867)
* refactor(router-core): simplify decodePath implementation, improve performance * simplify more
1 parent a096ca1 commit 18edb31

File tree

2 files changed

+17
-55
lines changed

2 files changed

+17
-55
lines changed

packages/router-core/src/utils.ts

Lines changed: 16 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -489,50 +489,12 @@ export function findLast<T>(
489489
return undefined
490490
}
491491

492-
const DECODE_IGNORE_LIST = [
493-
'%25', // %
494-
'%5C', // \
495-
]
496-
497-
function splitAndDecode(
498-
part: string,
499-
decodeIgnore: Array<string>,
500-
startIndex = 0,
501-
): string {
502-
// decode the path / path segment by splitting it into parts defined by the ignore list.
503-
// once these pieces have been decoded, join them back together to form the final decoded path segment with the ignored character in place.
504-
// we walk through the ignore list linearly, breaking the segment up into pieces and decoding each piece individually.
505-
// use index traversal to avoid making unnecessary copies of the array.
506-
for (let i = startIndex; i < decodeIgnore.length; i++) {
507-
const char = decodeIgnore[i]!.toUpperCase()
508-
509-
// check if the part includes the current ignore character
510-
// if it doesn't continue to the next ignore character
511-
if (part.includes(char)) {
512-
// split the part into pieces that needs to be checked and decoded
513-
const partsToDecode = part.split(char)
514-
const partsToJoin = new Array<string>(partsToDecode.length)
515-
516-
// now check and decode each piece individually taking into consideration the remaining ignored characters.
517-
// since we are walking through the list linearly, we only need to consider ignore items not yet traversed.
518-
for (let j = 0; j < partsToDecode.length; j++) {
519-
const partToDecode = partsToDecode[j]!
520-
// once we have traversed the entire ignore list, each decoded part is returned.
521-
partsToJoin[j] = splitAndDecode(partToDecode, decodeIgnore, i + 1)
522-
}
523-
524-
// and join them back together to form the final decoded path segment with the ignored character in place.
525-
return partsToJoin.join(char)
526-
}
527-
}
528-
529-
// once we have reached the end of the ignore list, we start walking back returning each decoded part.
530-
// should there be no matching characters, the path segment as a whole will be decoded.
492+
function decodeSegment(segment: string): string {
531493
try {
532-
return decodeURI(part)
494+
return decodeURI(segment)
533495
} catch {
534496
// if the decoding fails, try to decode the various parts leaving the malformed tags in place
535-
return part.replaceAll(/%[0-9A-F]{2}/g, (match) => {
497+
return segment.replaceAll(/%[0-9A-F]{2}/gi, (match) => {
536498
try {
537499
return decodeURI(match)
538500
} catch {
@@ -542,17 +504,17 @@ function splitAndDecode(
542504
}
543505
}
544506

545-
export function decodePath(
546-
part: string,
547-
decodeIgnore: Array<string> = DECODE_IGNORE_LIST,
548-
): string {
549-
// if the path segment does not contain any encoded uri components return the path as is
550-
if (part === '' || !/%[0-9A-Fa-f]{2}/g.test(part)) return part
551-
552-
// ensure all encoded characters are uppercase
553-
const normalizedPart = part.replaceAll(/%[0-9a-f]{2}/g, (match) =>
554-
match.toUpperCase(),
555-
)
556-
557-
return splitAndDecode(normalizedPart, decodeIgnore)
507+
export function decodePath(path: string, decodeIgnore?: Array<string>): string {
508+
if (!path) return path
509+
const re = decodeIgnore
510+
? new RegExp(`${decodeIgnore.join('|')}`, 'gi')
511+
: /%25|%5C/gi
512+
let cursor = 0
513+
let result = ''
514+
let match
515+
while (null !== (match = re.exec(path))) {
516+
result += decodeSegment(path.slice(cursor, match.index)) + match[0]
517+
cursor = re.lastIndex
518+
}
519+
return result + decodeSegment(cursor ? path.slice(cursor) : path)
558520
}

packages/router-core/tests/utils.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -607,7 +607,7 @@ describe('decodePath', () => {
607607

608608
const stringToCheckWithLowerCase = '/params-ps/named/foo%2Fabc/c%5C%2f%5cAh'
609609
const expectedResultWithLowerCase =
610-
'/params-ps/named/foo%2Fabc/c%5C%2F%5CAh'
610+
'/params-ps/named/foo%2Fabc/c%5C%2f%5cAh'
611611
expect(decodePath(stringToCheckWithLowerCase)).toBe(
612612
expectedResultWithLowerCase,
613613
)

0 commit comments

Comments
 (0)