diff --git a/packages/angular/build/src/utils/server-rendering/utils.ts b/packages/angular/build/src/utils/server-rendering/utils.ts index 2848fba52e75..18ab7def9ec1 100644 --- a/packages/angular/build/src/utils/server-rendering/utils.ts +++ b/packages/angular/build/src/utils/server-rendering/utils.ts @@ -22,26 +22,53 @@ export function isSsrRequestHandler( return typeof value === 'function' && '__ng_request_handler__' in value; } +/** + * A mapping of the characters which have to be escaped to be interpolated into HTML, + * to their entity equivalents. + */ +const HTML_ESCAPE_CHARACTER_MAP: Record = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', +}; + +/** + * Escapes the characters of a value which is interpolated into HTML text or into a quoted + * attribute value. + * + * @param text - The value to escape. + * @returns The escaped value. + */ +function escapeHtml(text: string): string { + return text.replace(/[&<>"']/g, (character) => HTML_ESCAPE_CHARACTER_MAP[character]); +} + /** * Generates a static HTML page with a meta refresh tag to redirect the user to a specified URL. * * This function creates a simple HTML page that performs a redirect using a meta tag. * It includes a fallback link in case the meta-refresh doesn't work. * + * The provided URL is HTML-escaped before being interpolated. + * * @param url - The URL to which the page should redirect. * @returns The HTML content of the static redirect page. */ export function generateRedirectStaticPage(url: string): string { + const escapedUrl = escapeHtml(url); + return ` Redirecting - + -
Redirecting to ${url}
+
Redirecting to ${escapedUrl}
`.trim(); diff --git a/packages/angular/build/src/utils/server-rendering/utils_spec.ts b/packages/angular/build/src/utils/server-rendering/utils_spec.ts new file mode 100644 index 000000000000..c8d738f8eec1 --- /dev/null +++ b/packages/angular/build/src/utils/server-rendering/utils_spec.ts @@ -0,0 +1,34 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { generateRedirectStaticPage } from './utils'; + +describe('generateRedirectStaticPage', () => { + it('escapes the ampersands of a redirect target', () => { + const page = generateRedirectStaticPage('https://example.com/docs?from=ssg&next=/ssg'); + + expect(page).toContain( + '', + ); + expect(page).toContain( + '' + + 'https://example.com/docs?from=ssg&next=/ssg', + ); + }); + + it('escapes characters which would break out of the attribute or the tag', () => { + const page = generateRedirectStaticPage(`/">`); + + expect(page).not.toContain('