@@ -22,26 +22,90 @@ export function isSsrRequestHandler(
2222 return typeof value === 'function' && '__ng_request_handler__' in value ;
2323}
2424
25+ const htmlEscapeCharacters : Record < string , string > = {
26+ '&' : '&' ,
27+ '<' : '<' ,
28+ '>' : '>' ,
29+ '"' : '"' ,
30+ "'" : ''' ,
31+ } ;
32+
33+ function escapeHtml ( text : string ) : string {
34+ return text . replace ( / [ & < > " ' ] / g, ( character ) => htmlEscapeCharacters [ character ] ) ;
35+ }
36+
37+ const unsafeStaticRedirectCharacters = / [ \\ < > " ' ` ] / ;
38+ const allowedStaticRedirectProtocols = new Set ( [ 'http:' , 'https:' ] ) ;
39+
40+ function hasUnsafeStaticRedirectCharacters ( value : string ) : boolean {
41+ for ( let index = 0 ; index < value . length ; index ++ ) {
42+ const characterCode = value . charCodeAt ( index ) ;
43+ if ( characterCode <= 0x20 || characterCode === 0x7f ) {
44+ return true ;
45+ }
46+ }
47+
48+ return unsafeStaticRedirectCharacters . test ( value ) ;
49+ }
50+
51+ export function validateStaticRedirectUrl ( url : string ) : string | undefined {
52+ if ( hasUnsafeStaticRedirectCharacters ( url ) ) {
53+ return (
54+ `the value '${ url } ' contains characters that are not allowed in a statically ` +
55+ `emitted URL (control characters, whitespace, backslash, or HTML-significant characters).`
56+ ) ;
57+ }
58+
59+ if ( url . startsWith ( '/' ) ) {
60+ return url . startsWith ( '//' )
61+ ? 'protocol-relative URLs are not supported for statically emitted redirects. ' +
62+ 'Only HTTP(S) URLs and same-origin absolute paths are supported.'
63+ : undefined ;
64+ }
65+
66+ const schemeMatch = / ^ ( [ a - z A - Z ] [ a - z A - Z 0 - 9 + \- . ] * ) : / . exec ( url ) ;
67+ if ( ! schemeMatch ) {
68+ return (
69+ `the value '${ url } ' is not a valid absolute URL or same-origin absolute path. ` +
70+ `Only HTTP(S) URLs and same-origin absolute paths are supported.`
71+ ) ;
72+ }
73+
74+ const protocol = `${ schemeMatch [ 1 ] . toLowerCase ( ) } :` ;
75+ if ( ! allowedStaticRedirectProtocols . has ( protocol ) ) {
76+ return (
77+ `the protocol '${ protocol } ' is not allowed for a statically emitted redirect. ` +
78+ `Only HTTP(S) URLs and same-origin absolute paths are supported.`
79+ ) ;
80+ }
81+
82+ return undefined ;
83+ }
84+
2585/**
2686 * Generates a static HTML page with a meta refresh tag to redirect the user to a specified URL.
2787 *
2888 * This function creates a simple HTML page that performs a redirect using a meta tag.
2989 * It includes a fallback link in case the meta-refresh doesn't work.
3090 *
91+ * The provided URL is HTML-escaped before being interpolated.
92+ *
3193 * @param url - The URL to which the page should redirect.
3294 * @returns The HTML content of the static redirect page.
3395 */
3496export function generateRedirectStaticPage ( url : string ) : string {
97+ const escapedUrl = escapeHtml ( url ) ;
98+
3599 return `
36100<!DOCTYPE html>
37101<html>
38102 <head>
39103 <meta charset="utf-8">
40104 <title>Redirecting</title>
41- <meta http-equiv="refresh" content="0; url=${ url } ">
105+ <meta http-equiv="refresh" content="0; url=${ escapedUrl } ">
42106 </head>
43107 <body>
44- <pre>Redirecting to <a href="${ url } ">${ url } </a></pre>
108+ <pre>Redirecting to <a href="${ escapedUrl } ">${ escapedUrl } </a></pre>
45109 </body>
46110</html>
47111` . trim ( ) ;
0 commit comments