Skip to content
Merged
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
99 changes: 49 additions & 50 deletions packages/router-core/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -489,64 +489,63 @@ export function findLast<T>(
return undefined
}

const DECODE_IGNORE_LIST = Array.from(
new Map([
['%', '%25'],
['\\', '%5C'],
]).values(),
)
const DECODE_IGNORE_LIST = [
'%25', // %
'%5C', // \
]

export function decodePath(
function splitAndDecode(
part: string,
decodeIgnore: Array<string> = DECODE_IGNORE_LIST,
decodeIgnore: Array<string>,
startIndex = 0,
): string {
function splitAndDecode(
part: string,
decodeIgnore: Array<string>,
startIndex = 0,
): string {
// decode the path / path segment by splitting it into parts defined by the ignore list.
// once these pieces have been decoded, join them back together to form the final decoded path segment with the ignored character in place.
// we walk through the ignore list linearly, breaking the segment up into pieces and decoding each piece individually.
// use index traversal to avoid making unnecessary copies of the array.
for (let i = startIndex; i < decodeIgnore.length; i++) {
const char = decodeIgnore[i]!.toUpperCase()

// check if the part includes the current ignore character
// if it doesn't continue to the next ignore character
if (part.includes(char)) {
// split the part into pieces that needs to be checked and decoded
const partsToDecode = part.split(char)
const partsToJoin: Array<string> = []

// now check and decode each piece individually taking into consideration the remaining ignored characters.
// since we are walking through the list linearly, we only need to consider ignore items not yet traversed.
for (const partToDecode of partsToDecode) {
// once we have traversed the entire ignore list, each decoded part is returned.
partsToJoin.push(splitAndDecode(partToDecode, decodeIgnore, i + 1))
}

// and join them back together to form the final decoded path segment with the ignored character in place.
return partsToJoin.join(char)
// decode the path / path segment by splitting it into parts defined by the ignore list.
// once these pieces have been decoded, join them back together to form the final decoded path segment with the ignored character in place.
// we walk through the ignore list linearly, breaking the segment up into pieces and decoding each piece individually.
// use index traversal to avoid making unnecessary copies of the array.
for (let i = startIndex; i < decodeIgnore.length; i++) {
const char = decodeIgnore[i]!.toUpperCase()

// check if the part includes the current ignore character
// if it doesn't continue to the next ignore character
if (part.includes(char)) {
// split the part into pieces that needs to be checked and decoded
const partsToDecode = part.split(char)
const partsToJoin = new Array<string>(partsToDecode.length)

// now check and decode each piece individually taking into consideration the remaining ignored characters.
// since we are walking through the list linearly, we only need to consider ignore items not yet traversed.
for (let j = 0; j < partsToDecode.length; j++) {
const partToDecode = partsToDecode[j]!
// once we have traversed the entire ignore list, each decoded part is returned.
partsToJoin[j] = splitAndDecode(partToDecode, decodeIgnore, i + 1)
}
}

// once we have reached the end of the ignore list, we start walking back returning each decoded part.
// should there be no matching characters, the path segment as a whole will be decoded.
try {
return decodeURI(part)
} catch {
// if the decoding fails, try to decode the various parts leaving the malformed tags in place
return part.replaceAll(/%[0-9A-F]{2}/g, (match) => {
try {
return decodeURI(match)
} catch {
return match
}
})
// and join them back together to form the final decoded path segment with the ignored character in place.
return partsToJoin.join(char)
}
}

// once we have reached the end of the ignore list, we start walking back returning each decoded part.
// should there be no matching characters, the path segment as a whole will be decoded.
try {
return decodeURI(part)
} catch {
// if the decoding fails, try to decode the various parts leaving the malformed tags in place
return part.replaceAll(/%[0-9A-F]{2}/g, (match) => {
try {
return decodeURI(match)
} catch {
return match
}
})
}
}

export function decodePath(
part: string,
decodeIgnore: Array<string> = DECODE_IGNORE_LIST,
): string {
// if the path segment does not contain any encoded uri components return the path as is
if (part === '' || !/%[0-9A-Fa-f]{2}/g.test(part)) return part

Expand Down
Loading