Skip to content

Commit

Permalink
__staticRouterHydrationData html tag escape (#10068)
Browse files Browse the repository at this point in the history
Co-authored-by: Matt Brophy <matt@brophy.org>
  • Loading branch information
zheng-chuang and brophdawg11 authored Feb 14, 2023
1 parent 1d00b40 commit 5706fbd
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .changeset/witty-melons-sip.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-router-dom": patch
---

Properly escape HTML characters in StaticRouterProvider serialized hydration data
1 change: 1 addition & 0 deletions contributors.yml
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,4 @@
- xavier-lc
- xcsnowcity
- yuleicul
- zheng-chuang
31 changes: 31 additions & 0 deletions packages/react-router-dom/__tests__/data-static-router-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,37 @@ describe("A <StaticRouterProvider>", () => {
);
});

it("escapes HTML tags in serialized hydration data", async () => {
let routes = [
{
path: "/",
loader: () => ({
key: "uh </script> oh",
}),
element: <h1>👋</h1>,
},
];
let { query } = createStaticHandler(routes);

let context = (await query(
new Request("http://localhost/", {
signal: new AbortController().signal,
})
)) as StaticHandlerContext;

let html = ReactDOMServer.renderToStaticMarkup(
<React.StrictMode>
<StaticRouterProvider
router={createStaticRouter(routes, context)}
context={context}
/>
</React.StrictMode>
);
expect(html).toMatchInlineSnapshot(
`"<h1>👋</h1><script>window.__staticRouterHydrationData = JSON.parse("{\\"loaderData\\":{\\"0\\":{\\"key\\":\\"uh \\u003c/script\\u003e oh\\"}},\\"actionData\\":null,\\"errors\\":null}");</script>"`
);
});

it("serializes ErrorResponse instances", async () => {
let routes = [
{
Expand Down
18 changes: 17 additions & 1 deletion packages/react-router-dom/server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export function StaticRouterProvider({
// up parsing on the client. Dual-stringify is needed to ensure all quotes
// are properly escaped in the resulting string. See:
// https://v8.dev/blog/cost-of-javascript-2019#json
let json = JSON.stringify(JSON.stringify(data));
let json = htmlEscape(JSON.stringify(JSON.stringify(data)));
hydrateScript = `window.__staticRouterHydrationData = JSON.parse(${json});`;
}

Expand Down Expand Up @@ -323,3 +323,19 @@ function encodeLocation(to: To): Path {
hash: path.hash || "",
};
}

// This utility is based on https://github.com/zertosh/htmlescape
// License: https://github.com/zertosh/htmlescape/blob/0527ca7156a524d256101bb310a9f970f63078ad/LICENSE
const ESCAPE_LOOKUP: { [match: string]: string } = {
"&": "\\u0026",
">": "\\u003e",
"<": "\\u003c",
"\u2028": "\\u2028",
"\u2029": "\\u2029",
};

const ESCAPE_REGEX = /[&><\u2028\u2029]/g;

function htmlEscape(str: string): string {
return str.replace(ESCAPE_REGEX, (match) => ESCAPE_LOOKUP[match]);
}

0 comments on commit 5706fbd

Please sign in to comment.