From f19b31ad93c313cb86d9a589c931f8f9718a7fb2 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Fri, 9 Jun 2023 12:06:00 +0200 Subject: [PATCH] Fix rsc payload fetch failures due to state tree encoding (#51017) Encode the state tree where the content could contain unicode when request the RSC payload, to avoid the fetch failure due to bad encoding for headers Fixes #48728 fix NEXT-1258 --- .../router-reducer/fetch-server-response.ts | 4 ++- ...parse-and-validate-flight-router-state.tsx | 4 ++- .../navigation/app/search-params/page.js | 12 +++++++ .../e2e/app-dir/navigation/navigation.test.ts | 33 +++++++++++++++++-- 4 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 test/e2e/app-dir/navigation/app/search-params/page.js diff --git a/packages/next/src/client/components/router-reducer/fetch-server-response.ts b/packages/next/src/client/components/router-reducer/fetch-server-response.ts index e1609761fa52..8028a8c46314 100644 --- a/packages/next/src/client/components/router-reducer/fetch-server-response.ts +++ b/packages/next/src/client/components/router-reducer/fetch-server-response.ts @@ -37,7 +37,9 @@ export async function fetchServerResponse( // Enable flight response [RSC]: '1', // Provide the current router state - [NEXT_ROUTER_STATE_TREE]: JSON.stringify(flightRouterState), + [NEXT_ROUTER_STATE_TREE]: encodeURIComponent( + JSON.stringify(flightRouterState) + ), } /** diff --git a/packages/next/src/server/app-render/parse-and-validate-flight-router-state.tsx b/packages/next/src/server/app-render/parse-and-validate-flight-router-state.tsx index d6bf3fa8c4f7..b8d4e43b10c6 100644 --- a/packages/next/src/server/app-render/parse-and-validate-flight-router-state.tsx +++ b/packages/next/src/server/app-render/parse-and-validate-flight-router-state.tsx @@ -23,7 +23,9 @@ export function parseAndValidateFlightRouterState( } try { - return flightRouterStateSchema.parse(JSON.parse(stateHeader)) + return flightRouterStateSchema.parse( + JSON.parse(decodeURIComponent(stateHeader)) + ) } catch { throw new Error('The router state header was sent but could not be parsed.') } diff --git a/test/e2e/app-dir/navigation/app/search-params/page.js b/test/e2e/app-dir/navigation/app/search-params/page.js new file mode 100644 index 000000000000..fb6eb4627272 --- /dev/null +++ b/test/e2e/app-dir/navigation/app/search-params/page.js @@ -0,0 +1,12 @@ +import Link from 'next/link' + +export default function page({ searchParams }) { + return ( +
+

{searchParams.name ?? ''}

+ + home + +
+ ) +} diff --git a/test/e2e/app-dir/navigation/navigation.test.ts b/test/e2e/app-dir/navigation/navigation.test.ts index 60ae96ac739a..0b8748bb9ba6 100644 --- a/test/e2e/app-dir/navigation/navigation.test.ts +++ b/test/e2e/app-dir/navigation/navigation.test.ts @@ -1,6 +1,6 @@ import { createNextDescribe } from 'e2e-utils' -import webdriver from 'next-webdriver' import { check } from 'next-test-utils' +import type { Request } from 'playwright-chromium' createNextDescribe( 'app dir - navigation', @@ -10,7 +10,7 @@ createNextDescribe( ({ next, isNextDev, isNextDeploy }) => { describe('query string', () => { it('should set query correctly', async () => { - const browser = await webdriver(next.url, '/') + const browser = await next.browser('/') expect(await browser.elementById('query').text()).toMatchInlineSnapshot( `""` ) @@ -25,6 +25,35 @@ createNextDescribe( const url = new URL(await browser.url()) expect(url.searchParams.toString()).toMatchInlineSnapshot(`"a=b&c=d"`) }) + + it('should handle unicode search params', async () => { + const requests = [] + + const browser = await next.browser('/search-params?name=名') + browser.on('request', async (req: Request) => { + const res = await req.response() + requests.push([ + new URL(req.url()).pathname, + res.ok(), + await res.headers(), + ]) + }) + expect(await browser.elementById('name').text()).toBe('名') + await browser.elementById('link').click() + + await check(async () => { + return requests.some((requestPair) => { + const [pathname, ok, headers] = requestPair + return ( + pathname === '/' && + ok && + headers['content-type'] === 'text/x-component' + ) + }) + ? 'success' + : JSON.stringify(requests) + }, 'success') + }) }) describe('hash', () => {