@@ -95,7 +95,10 @@ class TestEngineRunner {
9595 }
9696 ) : TestEngineRunner {
9797 const responseHeaders : { [ key : string ] : string } = { } ;
98- const requestHeaders = params ?. httpHeaders ?? { host } ;
98+ const requestHeaders : IncomingHttpHeaders = {
99+ host,
100+ ...( params ?. httpHeaders || { } ) ,
101+ } ;
99102 /** used when resolving getRequestUrl() and getRequestOrigin() */
100103 const app = < Partial < Application > > {
101104 get :
@@ -109,8 +112,12 @@ class TestEngineRunner {
109112 protocol : 'https' ,
110113 originalUrl : url ,
111114 headers : requestHeaders ,
112- get : ( header : string ) : string | string [ ] | null | undefined => {
113- return requestHeaders [ header ] ;
115+ get : ( header : string ) : string | undefined => {
116+ const key = Object . keys ( requestHeaders ) . find (
117+ ( k ) => k . toLowerCase ( ) === header . toLowerCase ( )
118+ ) ;
119+ const value = key ? requestHeaders [ key ] : undefined ;
120+ return Array . isArray ( value ) ? value . join ( ', ' ) : value ;
114121 } ,
115122 app,
116123 connection : < Partial < Socket > > { } ,
@@ -181,9 +188,12 @@ describe('OptimizedSsrEngine', () => {
181188 renderingStrategyResolver : ( ) => RenderingStrategy . ALWAYS_SSR ,
182189 } ) ;
183190
184- expect ( consoleLogSpy . mock . lastCall [ 0 ] ) . toContain (
185- '[spartacus] SSR optimization engine initialized'
186- ) ;
191+ expect ( consoleLogSpy . mock . lastCall ) . toEqual ( [
192+ '[spartacus] SSR optimization engine initialized' ,
193+ expect . objectContaining ( {
194+ options : expect . any ( Object ) ,
195+ } ) ,
196+ ] ) ;
187197 } ) ;
188198 } ) ;
189199
@@ -573,7 +583,7 @@ describe('OptimizedSsrEngine', () => {
573583
574584 expect (
575585 engineRunner . optimizedSsrEngine [ 'isConcurrencyLimitExceeded' ]
576- ) . toHaveBeenCalledWith ( `https:// ${ host } ${ route } ` ) ;
586+ ) . toHaveBeenCalledWith ( `${ route } ` ) ;
577587 } ) ) ;
578588
579589 it ( 'should NOT use the x-forwarded-host header by default to prevent cache poisoning' , fakeAsync ( ( ) => {
@@ -645,6 +655,35 @@ describe('OptimizedSsrEngine', () => {
645655 ) . toHaveBeenCalledWith ( `${ domain } :${ route } ` ) ;
646656 } ) ) ;
647657
658+ it ( 'should use host header when x-forwarded-host is absent and host is allowlisted' , fakeAsync ( ( ) => {
659+ const domain = 'allowed.com' ;
660+ const engineRunner = new TestEngineRunner ( {
661+ timeout : 200 ,
662+ cache : true ,
663+ useHostInCacheKey : true ,
664+ allowedHosts : [ domain ] ,
665+ } ) ;
666+
667+ jest . spyOn (
668+ engineRunner . optimizedSsrEngine as any ,
669+ 'isConcurrencyLimitExceeded'
670+ ) ;
671+
672+ const route = 'home' ;
673+
674+ engineRunner . request ( route , {
675+ httpHeaders : {
676+ host : domain ,
677+ } ,
678+ } ) ;
679+
680+ tick ( 200 ) ;
681+
682+ expect (
683+ engineRunner . optimizedSsrEngine [ 'isConcurrencyLimitExceeded' ]
684+ ) . toHaveBeenCalledWith ( `${ domain } :${ route } ` ) ;
685+ } ) ) ;
686+
648687 it ( 'should use only first value from comma-separated x-forwarded-host' , fakeAsync ( ( ) => {
649688 const engineRunner = new TestEngineRunner ( {
650689 useHostInCacheKey : true ,
@@ -698,7 +737,7 @@ describe('OptimizedSsrEngine', () => {
698737 ) . toHaveBeenCalledWith ( `${ route } ` ) ;
699738 } ) ) ;
700739
701- it ( 'should normalize hosts and support subdomains' , fakeAsync ( ( ) => {
740+ it ( 'should normalize hosts but require strict matching (no implicit subdomains) ' , fakeAsync ( ( ) => {
702741 const engineRunner = new TestEngineRunner ( {
703742 useHostInCacheKey : true ,
704743 allowedHosts : [ 'example.com' ] ,
@@ -710,18 +749,31 @@ describe('OptimizedSsrEngine', () => {
710749 ) ;
711750
712751 const route = 'home' ;
713- // Test case, port, trailing dot AND subdomain
752+ // Test case, port, and trailing dot with matching host
753+ engineRunner . request ( route , {
754+ httpHeaders : {
755+ 'x-forwarded-host' : 'EXAMPLE.com:443.' ,
756+ } ,
757+ } ) ;
758+
759+ // Test non-matching subdomain
714760 engineRunner . request ( route , {
715761 httpHeaders : {
716- 'x-forwarded-host' : 'WWW.EXAMPLE .com:443. ' ,
762+ 'x-forwarded-host' : 'www.example .com' ,
717763 } ,
718764 } ) ;
719765
720766 tick ( 200 ) ;
721767
768+ // First one matches because example.com is allowlisted
769+ expect (
770+ engineRunner . optimizedSsrEngine [ 'isConcurrencyLimitExceeded' ]
771+ ) . toHaveBeenCalledWith ( 'example.com:home' ) ;
772+
773+ // Second one falls back to path-only because subdomain is not implicitly allowed anymore
722774 expect (
723775 engineRunner . optimizedSsrEngine [ 'isConcurrencyLimitExceeded' ]
724- ) . toHaveBeenCalledWith ( 'www.example.com: home' ) ;
776+ ) . toHaveBeenCalledWith ( 'home' ) ;
725777 } ) ) ;
726778
727779 it ( 'should reject invalid hostnames and too long hosts' , fakeAsync ( ( ) => {
@@ -749,13 +801,15 @@ describe('OptimizedSsrEngine', () => {
749801
750802 tick ( 200 ) ;
751803
752- // Both should fallback to path only key
753- expect (
754- engineRunner . optimizedSsrEngine [ 'isConcurrencyLimitExceeded' ]
755- ) . toHaveBeenCalledWith ( route ) ;
756- expect (
757- engineRunner . optimizedSsrEngine [ 'isConcurrencyLimitExceeded' ]
758- ) . toHaveBeenCalledTimes ( 2 ) ;
804+ const calls = (
805+ engineRunner . optimizedSsrEngine [
806+ 'isConcurrencyLimitExceeded'
807+ ] as jest . Mock
808+ ) . mock . calls ;
809+
810+ calls . forEach ( ( call ) => {
811+ expect ( call [ 0 ] ) . toBe ( route ) ;
812+ } ) ;
759813 } ) ) ;
760814
761815 it ( 'should not generate different keys for many attacker-controlled hosts (prevents cache explosion)' , fakeAsync ( ( ) => {
@@ -1168,7 +1222,7 @@ describe('OptimizedSsrEngine', () => {
11681222 const differentUrl = 'b' ;
11691223
11701224 const getRenderingKey = ( requestUrlStr : string ) : string =>
1171- `https:// ${ host } ${ requestUrlStr } ` ;
1225+ `${ requestUrlStr } ` ;
11721226 const getRenderCallbacksCount = (
11731227 engineRunner : TestEngineRunner ,
11741228 requestUrlStr : string
@@ -1595,9 +1649,12 @@ describe('OptimizedSsrEngine', () => {
15951649 new TestEngineRunner ( {
15961650 logger : new MockExpressServerLogger ( ) as ExpressServerLogger ,
15971651 } ) ;
1598- expect ( consoleLogSpy . mock . lastCall [ 0 ] ) . toEqual (
1599- '[spartacus] SSR optimization engine initialized'
1600- ) ;
1652+ expect ( consoleLogSpy . mock . lastCall ) . toEqual ( [
1653+ '[spartacus] SSR optimization engine initialized' ,
1654+ expect . objectContaining ( {
1655+ options : expect . any ( Object ) ,
1656+ } ) ,
1657+ ] ) ;
16011658 } ) ;
16021659 } ) ;
16031660} ) ;
0 commit comments