diff --git a/.changeset/kind-shirts-turn.md b/.changeset/kind-shirts-turn.md new file mode 100644 index 0000000000..dc26b6af24 --- /dev/null +++ b/.changeset/kind-shirts-turn.md @@ -0,0 +1,5 @@ +--- +"@react-router/dev": patch +--- + +Escape redirect locations in prerendered redirect HTML diff --git a/packages/react-router-dev/vite/plugin.ts b/packages/react-router-dev/vite/plugin.ts index 6eaf1297a3..6dd897b04f 100644 --- a/packages/react-router-dev/vite/plugin.ts +++ b/packages/react-router-dev/vite/plugin.ts @@ -2752,15 +2752,17 @@ export const reactRouterVitePlugin: ReactRouterVitePlugin = () => { // A short delay causes Google to interpret the redirect as temporary. // https://developers.google.com/search/docs/crawling-indexing/301-redirects#metarefresh let delay = response.status === 302 ? 2 : 0; + let escapedLocation = escapeHtml(location ?? ""); + let escapedPathname = escapeHtml(pathname); html = ` -Redirecting to: ${location} - +Redirecting to: ${escapedLocation} + - - Redirecting from ${pathname} to ${location} + + Redirecting from ${escapedPathname} to ${escapedLocation} `; @@ -3345,15 +3347,17 @@ async function prerenderRoute( // A short delay causes Google to interpret the redirect as temporary. // https://developers.google.com/search/docs/crawling-indexing/301-redirects#metarefresh let delay = response.status === 302 ? 2 : 0; + let escapedLocation = escapeHtml(location ?? ""); + let escapedNormalizedPath = escapeHtml(normalizedPath); html = ` -Redirecting to: ${location} - +Redirecting to: ${escapedLocation} + - - Redirecting from ${normalizedPath} to ${location} + + Redirecting from ${escapedNormalizedPath} to ${escapedLocation} `; @@ -4328,3 +4332,18 @@ function createSpaModeRequest( metadata: { type: "spa", path: "/" }, }; } + +// Note: Duplicated from react-router/lib/dom/ssr/markup +// Must be kept in sync with the original implementation. +const ESCAPE_REGEX = /[&><\u2028\u2029]/g; +const ESCAPE_LOOKUP: { [match: string]: string } = { + "&": "\\u0026", + ">": "\\u003e", + "<": "\\u003c", + "\u2028": "\\u2028", + "\u2029": "\\u2029", +}; + +function escapeHtml(html: string) { + return html.replace(ESCAPE_REGEX, (match) => ESCAPE_LOOKUP[match]); +}