Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't throw errors when we give "asPathToSearchParams()" an invalid path with multiple leading slashes #70144

Open
wants to merge 1 commit into
base: canary
Choose a base branch
from

Conversation

takueof
Copy link

@takueof takueof commented Sep 16, 2024

For Contributors

Improving Documentation

Adding or Updating Examples

Fixing a bug

For Maintainers

  • Minimal description (aim for explaining to someone not on the team to understand the PR)
  • When linking to a Slack thread, you might want to share details of the conclusion
  • Link both the Linear (Fixes NEXT-xxx) and the GitHub issues
  • Add review comments if necessary to explain to the reviewer the logic behind a change

What?

If you access a root page that has two or more extra slashes at the end, such as https://foo.com//, errors throw in the URL constructor and Next.js will stop rendering.

TypeError: Failed to construct 'URL': Invalid URL UA (Web Browser) location bar screenshot

Why?

Because URL constructor in asPathToSearchParams() utility function can't parse URL root-relative paths that have two or more extra slashes, throw an error.

The root cause is that the path string returned by getURL() can have two or more slashes at the beginning.

How?

Add processing to the asPathToSearchParams() utility function to replace two or more leading slashes with one slash.

Note

  • This bug has been around for over 7 years before this PR was created date
  • This bug does not occur in HTTP web browser environments, only in HTTPS web browser environments
  • I created a PR because this bug can't be fixed without addressing it on the Next.js side

Closes NEXT-
Fixes #

@takueof takueof force-pushed the fix/wrong-value-set-to-asPath-when-root-index-page-have-multiple-slash branch from a559e15 to f807db6 Compare September 16, 2024 07:19
@ijjk
Copy link
Member

ijjk commented Sep 16, 2024

Allow CI Workflow Run

  • approve CI run for commit: bba9faa

Note: this should only be enabled once the PR is ready to go and can only be enabled by a maintainer

@takueof takueof marked this pull request as ready for review September 16, 2024 07:44
@takueof

This comment was marked as resolved.

@takueof takueof force-pushed the fix/wrong-value-set-to-asPath-when-root-index-page-have-multiple-slash branch from f807db6 to b211556 Compare September 16, 2024 08:23
@huozhi huozhi added CI approved Approve running CI for fork and removed CI approved Approve running CI for fork labels Sep 16, 2024
@huozhi
Copy link
Member

huozhi commented Sep 16, 2024

If you have more trailing slashes, even standard util will treat them as part of pathname, sounds correct it was thrown as invalid URL?

new URL('https://fooo.com//').pathname
>> '//'

@takueof
Copy link
Author

takueof commented Sep 16, 2024

@huozhi
Thank you for your reply.

Yes, but it doesn't happen in your example...

It occurs with the following pattern (this is the code pattern used within /packages/next/src/shared/lib/router/utils/as-path-to-search-params.ts):

new URL(asPath, 'http://n').searchParams

The following error should be thrown:

Uncaught TypeError: Failed to construct 'URL': Invalid URL

Screenshot:

new URL() throw by Google Chrome in JavaScript console

V8 (Blink), SpiderMonkey (Gecko) and JavaScriptCore (WebKit) all have the same behavior.

@takueof
Copy link
Author

takueof commented Sep 16, 2024

Initially, I thought that changing

new URL(asPath, 'https://n.com').searchParams

to

new URL(`https://n.com${asPath}`).searchParams

would complete the fix.

However, when I investigated deeper, I started to think that the fundamental solution might not be solved unless I changed getURL().

The reason is that the return value of getURL() is different for http:// and https://.

  • If we execute now getURL() on http://n.com//, / will be returned
  • If we execute now getURL() on https://n.com//, // will be returned

By fixing the core part, we can avoid problems that may occur in other code that uses the result of getURL().

@takueof takueof force-pushed the fix/wrong-value-set-to-asPath-when-root-index-page-have-multiple-slash branch 6 times, most recently from 80de3fd to 23fc909 Compare September 17, 2024 03:19
@huozhi
Copy link
Member

huozhi commented Sep 17, 2024

The test seem cannot demostrate the issue you describe, if I manually run in https mode and just visit the page, it looks working correctly 🤔

@takueof

This comment was marked as resolved.

@takueof takueof force-pushed the fix/wrong-value-set-to-asPath-when-root-index-page-have-multiple-slash branch 3 times, most recently from a2879fd to 7a8c3ee Compare September 17, 2024 14:23
@takueof
Copy link
Author

takueof commented Sep 17, 2024

Oh, after splitting the test file into http and https, the e2e test is now working.

If you git pull the latest commit and run the following command, it does what I want:

pnpm build && pnpm test-start test/e2e/invalid-url-slash

I'm very sorry.

@takueof takueof force-pushed the fix/wrong-value-set-to-asPath-when-root-index-page-have-multiple-slash branch 5 times, most recently from 1ea30c7 to ed53e03 Compare September 21, 2024 02:52
@huozhi
Copy link
Member

huozhi commented Sep 21, 2024

Tests are passed regardless we have the changes

@takueof takueof force-pushed the fix/wrong-value-set-to-asPath-when-root-index-page-have-multiple-slash branch from ed53e03 to e5a015e Compare September 21, 2024 12:35
@takueof
Copy link
Author

takueof commented Sep 21, 2024

@huozhi
Thanks to reply.

I also confirmed that the e2e test did not fail using the old code.

Then I remembered that the case I encountered was a Pages Router, not an App Router.

When I rewrote the e2e test using Pages Router, changed utils.ts to its pre-patch state, and checked again, the e2e test failed.

After applying the patch to utils.ts, the e2e test now passes.

Thank you for making me realize that.

I have pushed the latest code, so please test it yourself 🙏

@takueof takueof force-pushed the fix/wrong-value-set-to-asPath-when-root-index-page-have-multiple-slash branch 7 times, most recently from 8f14e57 to 725bb2a Compare September 28, 2024 10:15
@takueof takueof force-pushed the fix/wrong-value-set-to-asPath-when-root-index-page-have-multiple-slash branch 2 times, most recently from 26b30aa to 6691621 Compare October 3, 2024 22:47
@takueof takueof force-pushed the fix/wrong-value-set-to-asPath-when-root-index-page-have-multiple-slash branch from 6691621 to 6a78b62 Compare October 14, 2024 00:10
@takueof
Copy link
Author

takueof commented Oct 14, 2024

Hello, maintainers!
Thank you for considering my PR.

It's been about 4 weeks since I last commented.
If you don't mind, would it be possible for you to tell me about your current review status...?

Sorry for the inconvenience 🙏

@huozhi huozhi added the CI approved Approve running CI for fork label Oct 14, 2024
@ijjk
Copy link
Member

ijjk commented Oct 14, 2024

Failing test suites

Commit: 6a78b62

pnpm test-dev test/development/acceptance-app/undefined-default-export.test.ts

  • Undefined default export > should error when page component export is not valid
Expand output

● Undefined default export › should error when page component export is not valid

Expected Redbox but found none

  61 |     )
  62 |
> 63 |     await session.assertHasRedbox()
     |     ^
  64 |     expect(await session.getRedboxDescription()).toInclude(
  65 |       'The default export is not a React Component in "/server-with-errors/page-export/page"'
  66 |     )

  at Object.<anonymous> (development/acceptance-app/undefined-default-export.test.ts:63:5)

Read more about building and testing Next.js in contributing.md.

pnpm test test/integration/repeated-slashes/test/index.test.js

  • 404 handling > custom _error > production mode > next export > should handle double slashes correctly
  • 404 handling > custom _error > production mode > next export > should handle double slashes correctly with query
  • 404 handling > custom _error > production mode > next export > should handle double slashes correctly with hash
  • 404 handling > custom _error > production mode > next export > should handle backslashes correctly
  • 404 handling > custom _error > production mode > next export > should handle mixed backslashes/forward slashes correctly
  • 404 handling > pages/404 > production mode > production mode > next export > should handle double slashes correctly
  • 404 handling > pages/404 > production mode > production mode > next export > should handle double slashes correctly with query
  • 404 handling > pages/404 > production mode > production mode > next export > should handle double slashes correctly with hash
  • 404 handling > pages/404 > production mode > production mode > next export > should handle backslashes correctly
  • 404 handling > pages/404 > production mode > production mode > next export > should handle mixed backslashes/forward slashes correctly
Expand output

● 404 handling › custom _error › production mode › next export › should handle double slashes correctly

expect(received).toBe(expected) // Object.is equality

Expected: "//google.com"
Received: "/google.com"

  108 |     const browser = await webdriver(appPort, '//google.com')
  109 |     await didNotReload(browser)
> 110 |     expect(await browser.eval('window.location.pathname')).toBe(
      |                                                            ^
  111 |       isExport ? '//google.com' : '/google.com'
  112 |     )
  113 |     expect(await browser.eval('window.location.search')).toBe('')

  at Object.toBe (integration/repeated-slashes/test/index.test.js:110:60)

● 404 handling › custom _error › production mode › next export › should handle double slashes correctly with query

expect(received).toBe(expected) // Object.is equality

Expected: "//google.com"
Received: "/google.com"

  137 |     const browser = await webdriver(appPort, '//google.com?h=1')
  138 |     await didNotReload(browser)
> 139 |     expect(await browser.eval('window.location.pathname')).toBe(
      |                                                            ^
  140 |       isExport ? '//google.com' : '/google.com'
  141 |     )
  142 |     expect(await browser.eval('window.location.search')).toBe('?h=1')

  at Object.toBe (integration/repeated-slashes/test/index.test.js:139:60)

● 404 handling › custom _error › production mode › next export › should handle double slashes correctly with hash

expect(received).toBe(expected) // Object.is equality

Expected: "//google.com"
Received: "/google.com"

  161 |     const browser = await webdriver(appPort, '//google.com#hello')
  162 |     await didNotReload(browser)
> 163 |     expect(await browser.eval('window.location.pathname')).toBe(
      |                                                            ^
  164 |       isExport ? '//google.com' : '/google.com'
  165 |     )
  166 |     expect(await browser.eval('window.location.hash')).toBe('#hello')

  at Object.toBe (integration/repeated-slashes/test/index.test.js:163:60)

● 404 handling › custom _error › production mode › next export › should handle backslashes correctly

expect(received).toBe(expected) // Object.is equality

Expected: "//google.com"
Received: "/google.com"

  250 |     const browser = await webdriver(appPort, '/\\google.com')
  251 |     await didNotReload(browser)
> 252 |     expect(await browser.eval('window.location.pathname')).toBe(
      |                                                            ^
  253 |       isExport ? '//google.com' : '/google.com'
  254 |     )
  255 |     expect(await browser.eval('window.location.hash')).toBe('')

  at Object.toBe (integration/repeated-slashes/test/index.test.js:252:60)

● 404 handling › custom _error › production mode › next export › should handle mixed backslashes/forward slashes correctly

expect(received).toBe(expected) // Object.is equality

Expected: "///google.com"
Received: "/google.com"

  275 |     const browser = await webdriver(appPort, '/\\/google.com#hello')
  276 |     await didNotReload(browser)
> 277 |     expect(await browser.eval('window.location.pathname')).toBe(
      |                                                            ^
  278 |       isExport ? '///google.com' : '/google.com'
  279 |     )
  280 |     expect(await browser.eval('window.location.hash')).toBe('#hello')

  at Object.toBe (integration/repeated-slashes/test/index.test.js:277:60)

● 404 handling › pages/404 › production mode › production mode › next export › should handle double slashes correctly

expect(received).toBe(expected) // Object.is equality

Expected: "//google.com"
Received: "/google.com"

  108 |     const browser = await webdriver(appPort, '//google.com')
  109 |     await didNotReload(browser)
> 110 |     expect(await browser.eval('window.location.pathname')).toBe(
      |                                                            ^
  111 |       isExport ? '//google.com' : '/google.com'
  112 |     )
  113 |     expect(await browser.eval('window.location.search')).toBe('')

  at Object.toBe (integration/repeated-slashes/test/index.test.js:110:60)

● 404 handling › pages/404 › production mode › production mode › next export › should handle double slashes correctly with query

expect(received).toBe(expected) // Object.is equality

Expected: "//google.com"
Received: "/google.com"

  137 |     const browser = await webdriver(appPort, '//google.com?h=1')
  138 |     await didNotReload(browser)
> 139 |     expect(await browser.eval('window.location.pathname')).toBe(
      |                                                            ^
  140 |       isExport ? '//google.com' : '/google.com'
  141 |     )
  142 |     expect(await browser.eval('window.location.search')).toBe('?h=1')

  at Object.toBe (integration/repeated-slashes/test/index.test.js:139:60)

● 404 handling › pages/404 › production mode › production mode › next export › should handle double slashes correctly with hash

expect(received).toBe(expected) // Object.is equality

Expected: "//google.com"
Received: "/google.com"

  161 |     const browser = await webdriver(appPort, '//google.com#hello')
  162 |     await didNotReload(browser)
> 163 |     expect(await browser.eval('window.location.pathname')).toBe(
      |                                                            ^
  164 |       isExport ? '//google.com' : '/google.com'
  165 |     )
  166 |     expect(await browser.eval('window.location.hash')).toBe('#hello')

  at Object.toBe (integration/repeated-slashes/test/index.test.js:163:60)

● 404 handling › pages/404 › production mode › production mode › next export › should handle backslashes correctly

expect(received).toBe(expected) // Object.is equality

Expected: "//google.com"
Received: "/google.com"

  250 |     const browser = await webdriver(appPort, '/\\google.com')
  251 |     await didNotReload(browser)
> 252 |     expect(await browser.eval('window.location.pathname')).toBe(
      |                                                            ^
  253 |       isExport ? '//google.com' : '/google.com'
  254 |     )
  255 |     expect(await browser.eval('window.location.hash')).toBe('')

  at Object.toBe (integration/repeated-slashes/test/index.test.js:252:60)

● 404 handling › pages/404 › production mode › production mode › next export › should handle mixed backslashes/forward slashes correctly

expect(received).toBe(expected) // Object.is equality

Expected: "///google.com"
Received: "/google.com"

  275 |     const browser = await webdriver(appPort, '/\\/google.com#hello')
  276 |     await didNotReload(browser)
> 277 |     expect(await browser.eval('window.location.pathname')).toBe(
      |                                                            ^
  278 |       isExport ? '///google.com' : '/google.com'
  279 |     )
  280 |     expect(await browser.eval('window.location.hash')).toBe('#hello')

  at Object.toBe (integration/repeated-slashes/test/index.test.js:277:60)

Read more about building and testing Next.js in contributing.md.

@huozhi huozhi removed the CI approved Approve running CI for fork label Oct 14, 2024
@takueof
Copy link
Author

takueof commented Oct 15, 2024

@ijjk
Thank you for your comment.

I didn't expect this PR at this point to have such an impact on other features (testing).
I will create a patch PR in my local environment while doing full testing again.

I apologize for taking up your valuable time.

@takueof takueof force-pushed the fix/wrong-value-set-to-asPath-when-root-index-page-have-multiple-slash branch from 6a78b62 to 14f9732 Compare October 15, 2024 10:55
@takueof takueof changed the title Fix invalid pathname slashes in "getURL()" utility Don't throw errors when we give "asPathToSearchParams()" an invalid path with multiple leading slashes. Oct 16, 2024
@takueof takueof changed the title Don't throw errors when we give "asPathToSearchParams()" an invalid path with multiple leading slashes. Don't throw errors when we give "asPathToSearchParams()" an invalid path with multiple leading slashes Oct 16, 2024
@takueof takueof force-pushed the fix/wrong-value-set-to-asPath-when-root-index-page-have-multiple-slash branch 2 times, most recently from 965ef85 to 1877dba Compare October 16, 2024 21:33
@takueof
Copy link
Author

takueof commented Oct 17, 2024

@ijjk (Cc: @huozhi)
Hello.
Thank you for letting me know about the test failure the other day.

After that, I reviewed the patch and changed the patch so that the existing test set would not fail and the new test set would pass.

I apologize for the inconvenience, but I would appreciate it if you could review the PR again 🙏

The following screenshots are the results of the retest:

pnpm test-dev test/development/acceptance-app/undefined-default-export.test.ts

pnpm test test/integration/repeated-slashes/test/index.test.js

@takueof takueof force-pushed the fix/wrong-value-set-to-asPath-when-root-index-page-have-multiple-slash branch 2 times, most recently from 2b25d12 to 1632a03 Compare October 25, 2024 21:19
@takueof takueof force-pushed the fix/wrong-value-set-to-asPath-when-root-index-page-have-multiple-slash branch from 1632a03 to 70b12cc Compare November 7, 2024 23:52
@huozhi huozhi added CI approved Approve running CI for fork and removed CI approved Approve running CI for fork labels Nov 8, 2024
@takueof takueof force-pushed the fix/wrong-value-set-to-asPath-when-root-index-page-have-multiple-slash branch from 70b12cc to bba9faa Compare November 8, 2024 04:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants