router icon indicating copy to clipboard operation
router copied to clipboard

Basepaths like "base%2Fpath" work properly in version 1.43.4 but not in 1.45.11

Open apata opened this issue 1 year ago • 4 comments

Describe the bug

I'm trying to create a router on a basepath containing the value %2F (URL encoded representation of /).

With @tanstack/react-router version 1.43.4 (edit: and 1.45.0), the router respects it, and renders links to /base%2Fpath/any-page correctly. Example: https://stackblitz.com/edit/tanstack-router-kaytm9?file=src%2Fmain.tsx

With version 1.45.11, the router, after root route mount, redirects the browser to /base/path and links are rendered incorrectly to /base/path/any-page. Example: https://stackblitz.com/edit/tanstack-router-dvwena?file=src%2Fmain.tsx

Your Example Website or App

https://stackblitz.com/edit/tanstack-router-dvwena?file=src%2Fmain.tsx

Steps to Reproduce the Bug or Issue

  1. Create router with basepath containing %2F, like /base%2Fpath and serve the page with the router at <domain>/base%2Fpath.
  2. With Chrome (or with probably any other browser), open <domain>/base%2Fpath.
  3. Router mounts, root route mounts, and changes the URL in the address bar to <domain>/base/path. Links are rendered pointing to locations with the changed basepath, for example <domain>/base/path/any-page.

Expected behavior

I expect browser location <domain>/base%2Fpath not to change. I expect Links to point to <domain>/base%2Fpath/any-page.

Screenshots or Videos

No response

Platform

  • OS: macOS 14
  • Browser: Chrome
  • Version: 126.0.6478.183 (Official Build) (arm64)

Additional context

As far as I can tell after debugging, the location is changed after rootRoute onEnter() hook. At that point, the basepath ends up being decoded here https://github.com/TanStack/router/blame/main/packages/react-router/src/path.ts#L184. There's no workarounds that I could find, even moving from basepath to splat.

apata avatar Jul 28 '24 08:07 apata

Tried some more versions. Does not reproduce in 1.45.0: https://stackblitz.com/edit/tanstack-router-dmarzj?file=src%2Fmain.tsx After that version, issue is present in all versions to 1.45.11.

apata avatar Jul 28 '24 08:07 apata

out of curiosity, why do you need this kind of basepath?

schiller-manuel avatar Jul 28 '24 13:07 schiller-manuel

out of curiosity, why do you need this kind of basepath?

Thanks for engaging!

We have a service that customers can install for <customer-domain> and/or <customer-other-domain>/<subpath>. From the standpoint of our service, these installs are equal and valid, and are accessed in one level deep path at <our-service-domain>/<customer-domain> and <our-service-domain>/<customer-other-domain>%2F<subpath> respectively.

Imagine https://www.whois.com/whois/example.xyz, https://www.whois.com/whois/example.xyz%2Fpage

I wasn't able to get this working refactoring to splat as well, so I think there's a deeper issue at play here.

apata avatar Jul 28 '24 16:07 apata

A naive fix occurred to me

In packages/react-router/src/path.ts parsePathname function

      const decodeURIComponentExceptForwardSlash = (
        pathSegment: string,
      ): string => {
        return pathSegment.split(/%2F/i).map(decodeURIComponent).join('%2F')
      }

      return {
        type: 'pathname',
        value: decodeURIComponentExceptForwardSlash(part),
      }

In packages/react-router/tests/router.test.tsx an additional case

describe('encoding: URL path segment', () => {
  it.each([
    {
      input: '/path-segment/%C3%A9',
      output: '/path-segment/é',
      type: 'is encoded',
    },
    {
      input: '/path-segment/é',
      output: '/path-segment/é',
      type: 'is not encoded',
    },
    {
      input: '/path-segment/%F0%9F%9A%80',
      output: '/path-segment/🚀',
      type: 'is encoded',
    },
    {
      input: '/path-segment/🚀',
      output: '/path-segment/🚀',
      type: 'is not encoded',
    },
    {
      input: '/%F0%9F%9A%80toMoon%2f🚀toMars%2F🚀toVenus/page',
      output: '/🚀toMoon%2F🚀toMars%2F🚀toVenus/page',
      type: 'contains some encoded characters and an encoded forward slash',
    },
  ])(
    'should resolve $input to $output when the path segment $type',
    async ({ input, output }) => {
      const { router } = createTestRouter(
        createMemoryHistory({ initialEntries: [input] }),
      )

      render(<RouterProvider router={router} />)
      await act(() => router.load())

      expect(router.state.location.pathname).toBe(output)
    },
  )
})

apata avatar Jul 29 '24 14:07 apata

@apata can you see if #2261 fixes your problem?

braineo avatar Sep 04 '24 08:09 braineo

Retested with the preview package https://pkg.pr.new/@tanstack/react-router@2261 in https://stackblitz.com/edit/tanstack-router-xevuta?file=package.json and can confirm that this issue is fixed.

SeanCassiere avatar Sep 04 '24 09:09 SeanCassiere