@@ -26,7 +26,7 @@ import {BootstrapContext} from '@angular/platform-browser';
2626
2727import { platformServer } from './server' ;
2828import { PlatformState } from './platform_state' ;
29- import { BEFORE_APP_SERIALIZED , INITIAL_CONFIG } from './tokens' ;
29+ import { BEFORE_APP_SERIALIZED , INITIAL_CONFIG , PlatformConfig } from './tokens' ;
3030import { createScript } from './transfer_state' ;
3131
3232/**
@@ -39,9 +39,8 @@ import {createScript} from './transfer_state';
3939 */
4040export const EVENT_DISPATCH_SCRIPT_ID = 'ng-event-dispatch-contract' ;
4141
42- interface PlatformOptions {
42+ interface PlatformOptions extends Omit < PlatformConfig , 'document' > {
4343 document ?: string | Document ;
44- url ?: string ;
4544 platformProviders ?: Provider [ ] ;
4645}
4746
@@ -53,9 +52,16 @@ function createServerPlatform(options: PlatformOptions): PlatformRef {
5352 const extraProviders = options . platformProviders ?? [ ] ;
5453 const measuringLabel = 'createServerPlatform' ;
5554 startMeasuring ( measuringLabel ) ;
55+ const { document, url} = options ;
5656
5757 const platform = platformServer ( [
58- { provide : INITIAL_CONFIG , useValue : { document : options . document , url : options . url } } ,
58+ {
59+ provide : INITIAL_CONFIG ,
60+ useValue : {
61+ document,
62+ url,
63+ } ,
64+ } ,
5965 extraProviders ,
6066 ] ) ;
6167
@@ -265,14 +271,20 @@ function sanitizeServerContext(serverContext: string): string {
265271 * as a reference to the `document` instance.
266272 * - `url` - the URL for the current render request.
267273 * - `extraProviders` - set of platform level providers for the current render request.
268- *
274+ * - `allowedHosts` - the allowed hosts list for host validation in server-side rendering.
269275 * @publicApi
270276 */
271277export async function renderModule < T > (
272278 moduleType : Type < T > ,
273- options : { document ?: string | Document ; url ?: string ; extraProviders ?: StaticProvider [ ] } ,
279+ options : {
280+ document ?: string | Document ;
281+ url ?: string ;
282+ extraProviders ?: StaticProvider [ ] ;
283+ allowedHosts ?: Readonly < string > [ ] ;
284+ } ,
274285) : Promise < string > {
275- const { document, url, extraProviders : platformProviders } = options ;
286+ const { document, url, extraProviders : platformProviders , allowedHosts} = options ;
287+ validateAllowedHosts ( url , allowedHosts ) ;
276288 const platformRef = createServerPlatform ( { document, url, platformProviders} ) ;
277289 try {
278290 const moduleRef = await platformRef . bootstrapModule ( moduleType ) ;
@@ -315,18 +327,27 @@ export async function renderModule<T>(
315327 * as a reference to the `document` instance.
316328 * - `url` - the URL for the current render request.
317329 * - `platformProviders` - the platform level providers for the current render request.
330+ * - `allowedHosts` - the allowed hosts list for host validation in server-side rendering.
318331 *
319332 * @returns A Promise, that returns serialized (to a string) rendered page, once resolved.
320333 *
321334 * @publicApi
322335 */
323336export async function renderApplication (
324337 bootstrap : ( context : BootstrapContext ) => Promise < ApplicationRef > ,
325- options : { document ?: string | Document ; url ?: string ; platformProviders ?: Provider [ ] } ,
338+ options : {
339+ document ?: string | Document ;
340+ url ?: string ;
341+ platformProviders ?: Provider [ ] ;
342+ allowedHosts ?: Readonly < string > [ ] ;
343+ } ,
326344) : Promise < string > {
327345 const renderAppLabel = 'renderApplication' ;
328346 const bootstrapLabel = 'bootstrap' ;
329347 const _renderLabel = '_render' ;
348+ const { url, allowedHosts} = options ;
349+
350+ validateAllowedHosts ( url , allowedHosts ) ;
330351
331352 startMeasuring ( renderAppLabel ) ;
332353 const platformRef = createServerPlatform ( options ) ;
@@ -351,3 +372,40 @@ export async function renderApplication(
351372 stopMeasuring ( renderAppLabel ) ;
352373 }
353374}
375+
376+ function validateAllowedHosts ( url : string | undefined , allowedHosts : string [ ] | undefined ) {
377+ if ( typeof url === 'string' && URL . canParse ( url ) ) {
378+ const hostname = new URL ( url ) . hostname ;
379+ const allowedHostsSet : ReadonlySet < string > = new Set ( allowedHosts ) ;
380+ if ( ! isHostAllowed ( hostname , allowedHostsSet ) ) {
381+ throw new Error ( `Host ${ url } is not allowed. You can configure \`allowedHosts\` option.` ) ;
382+ }
383+ }
384+ }
385+
386+ /**
387+ * Checks if the hostname is allowed.
388+ * @param hostname - The hostname to check.
389+ * @param allowedHosts - A set of allowed hostnames.
390+ * @returns `true` if the hostname is allowed, `false` otherwise.
391+ * @note Used also in `@angular/ssr`.
392+ * @private
393+ */
394+ export function isHostAllowed ( hostname : string , allowedHosts : ReadonlySet < string > ) : boolean {
395+ if ( allowedHosts . has ( '*' ) || allowedHosts . has ( hostname ) ) {
396+ return true ;
397+ }
398+
399+ for ( const allowedHost of allowedHosts ) {
400+ if ( ! allowedHost . startsWith ( '*.' ) ) {
401+ continue ;
402+ }
403+
404+ const domain = allowedHost . slice ( 1 ) ;
405+ if ( hostname . endsWith ( domain ) ) {
406+ return true ;
407+ }
408+ }
409+
410+ return false ;
411+ }
0 commit comments