Skip to content

Commit

Permalink
fix(react-router): basepath case sensitivity during path resolution (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
SeanCassiere authored Oct 25, 2024
1 parent 62b97f5 commit 5d1702f
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 11 deletions.
36 changes: 25 additions & 11 deletions packages/react-router/src/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,18 @@ interface ResolvePathOptions {
base: string
to: string
trailingSlash?: 'always' | 'never' | 'preserve'
caseSensitive?: boolean
}

export function resolvePath({
basepath,
base,
to,
trailingSlash = 'never',
caseSensitive,
}: ResolvePathOptions) {
base = removeBasepath(basepath, base)
to = removeBasepath(basepath, to)
base = removeBasepath(basepath, base, caseSensitive)
to = removeBasepath(basepath, to, caseSensitive)

let baseSegments = parsePathname(base)
const toSegments = parsePathname(to)
Expand Down Expand Up @@ -260,15 +262,23 @@ export function matchPathname(
return pathParams ?? {}
}

export function removeBasepath(basepath: string, pathname: string) {
export function removeBasepath(
basepath: string,
pathname: string,
caseSensitive: boolean = false,
) {
// normalize basepath and pathname for case-insensitive comparison if needed
const normalizedBasepath = caseSensitive ? basepath : basepath.toLowerCase()
const normalizedPathname = caseSensitive ? pathname : pathname.toLowerCase()

switch (true) {
// default behaviour is to serve app from the root - pathname
// left untouched
case basepath === '/':
case normalizedBasepath === '/':
return pathname

// shortcut for removing the basepath from the equal pathname
case pathname === basepath:
// shortcut for removing the basepath if it matches the pathname
case normalizedPathname === normalizedBasepath:
return ''

// in case pathname is shorter than basepath - there is
Expand All @@ -280,11 +290,11 @@ export function removeBasepath(basepath: string, pathname: string) {
// earlier, otherwise, basepath separated from pathname with
// separator, therefore lack of separator means partial
// segment match (`/app` should not match `/application`)
case pathname[basepath.length] !== '/':
case normalizedPathname[normalizedBasepath.length] !== '/':
return pathname

// remove the basepath from the pathname in case there is any
case pathname.startsWith(basepath):
// remove the basepath from the pathname if it starts with it
case normalizedPathname.startsWith(normalizedBasepath):
return pathname.slice(basepath.length)

// otherwise, return the pathname as is
Expand All @@ -303,9 +313,13 @@ export function matchByPath(
return undefined
}
// Remove the base path from the pathname
from = removeBasepath(basepath, from)
from = removeBasepath(basepath, from, matchLocation.caseSensitive)
// Default to to $ (wildcard)
const to = removeBasepath(basepath, `${matchLocation.to ?? '$'}`)
const to = removeBasepath(
basepath,
`${matchLocation.to ?? '$'}`,
matchLocation.caseSensitive,
)

// Parse the from and to
const baseSegments = parsePathname(from)
Expand Down
2 changes: 2 additions & 0 deletions packages/react-router/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,7 @@ export class Router<
defaultPendingMinMs: 500,
context: undefined!,
...options,
caseSensitive: options.caseSensitive ?? false,
notFoundMode: options.notFoundMode ?? 'fuzzy',
stringifySearch: options.stringifySearch ?? defaultStringifySearch,
parseSearch: options.parseSearch ?? defaultParseSearch,
Expand Down Expand Up @@ -1007,6 +1008,7 @@ export class Router<
base: from,
to: cleanPath(path),
trailingSlash: this.options.trailingSlash,
caseSensitive: this.options.caseSensitive,
})
return resolvedPath
}
Expand Down
39 changes: 39 additions & 0 deletions packages/react-router/tests/path.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,45 @@ describe('removeBasepath', () => {
])('$name', ({ basepath, pathname, expected }) => {
expect(removeBasepath(basepath, pathname)).toBe(expected)
})
describe('case sensitivity', () => {
describe('caseSensitive = true', () => {
it.each([
{
name: 'should not remove basepath from the beginning of the pathname',
basepath: '/app',
pathname: '/App/path/App',
expected: '/App/path/App',
},
{
name: 'should not remove basepath from the beginning of the pathname with multiple segments',
basepath: '/app/New',
pathname: '/App/New/path/App',
expected: '/App/New/path/App',
},
])('$name', ({ basepath, pathname, expected }) => {
expect(removeBasepath(basepath, pathname, true)).toBe(expected)
})
})

describe('caseSensitive = false', () => {
it.each([
{
name: 'should remove basepath from the beginning of the pathname',
basepath: '/App',
pathname: '/app/path/app',
expected: '/path/app',
},
{
name: 'should remove multisegment basepath from the beginning of the pathname',
basepath: '/App/New',
pathname: '/app/new/path/app',
expected: '/path/app',
},
])('$name', ({ basepath, pathname, expected }) => {
expect(removeBasepath(basepath, pathname, false)).toBe(expected)
})
})
})
})

describe.each([{ basepath: '/' }, { basepath: '/app' }, { basepath: '/app/' }])(
Expand Down

0 comments on commit 5d1702f

Please sign in to comment.