@@ -153,6 +153,63 @@ describe('createHttpClient', () => {
153153 expect ( response . redirected ) . toBe ( true ) ;
154154 } ) ;
155155
156+ it ( 'rewrites origins that include a port' , async ( ) => {
157+ const body = 'See https://prod.example.com:8080/docs/guide for details.' ;
158+ globalThis . fetch = vi . fn ( async ( ) => makeTextResponse ( body , { contentType : 'text/plain' } ) ) ;
159+
160+ const client = createHttpClient ( {
161+ requestDelay : 0 ,
162+ requestTimeout : 5000 ,
163+ maxConcurrency : 10 ,
164+ canonicalOrigin : 'https://prod.example.com:8080' ,
165+ targetOrigin : 'http://localhost:3000' ,
166+ } ) ;
167+ const response = await client . fetch ( 'http://localhost:3000/docs/guide' ) ;
168+ const text = await response . text ( ) ;
169+
170+ expect ( text ) . toBe ( 'See http://localhost:3000/docs/guide for details.' ) ;
171+ } ) ;
172+
173+ it ( 'does not match a longer domain that starts with the canonical origin' , async ( ) => {
174+ const body = [
175+ 'https://prod.example.com/docs/guide' ,
176+ 'https://prod.example.com.evil.com/phishing' ,
177+ ] . join ( '\n' ) ;
178+ globalThis . fetch = vi . fn ( async ( ) => makeTextResponse ( body , { contentType : 'text/plain' } ) ) ;
179+
180+ const client = createHttpClient ( {
181+ requestDelay : 0 ,
182+ requestTimeout : 5000 ,
183+ maxConcurrency : 10 ,
184+ canonicalOrigin : 'https://prod.example.com' ,
185+ targetOrigin : 'https://preview.local' ,
186+ } ) ;
187+ const response = await client . fetch ( 'http://preview.local/docs' ) ;
188+ const text = await response . text ( ) ;
189+
190+ expect ( text ) . toContain ( 'https://preview.local/docs/guide' ) ;
191+ expect ( text ) . toContain ( 'https://prod.example.com.evil.com/phishing' ) ;
192+ } ) ;
193+
194+ it ( 'returns the same rewritten body on multiple text() calls' , async ( ) => {
195+ const body = 'Link: https://prod.example.com/page' ;
196+ globalThis . fetch = vi . fn ( async ( ) => makeTextResponse ( body , { contentType : 'text/plain' } ) ) ;
197+
198+ const client = createHttpClient ( {
199+ requestDelay : 0 ,
200+ requestTimeout : 5000 ,
201+ maxConcurrency : 10 ,
202+ canonicalOrigin : 'https://prod.example.com' ,
203+ targetOrigin : 'https://preview.local' ,
204+ } ) ;
205+ const response = await client . fetch ( 'http://preview.local/page' ) ;
206+ const first = await response . text ( ) ;
207+ const second = await response . text ( ) ;
208+
209+ expect ( first ) . toBe ( 'Link: https://preview.local/page' ) ;
210+ expect ( second ) . toBe ( first ) ;
211+ } ) ;
212+
156213 it ( 'skips rewrite for non-text content types' , async ( ) => {
157214 const original = 'https://prod.example.com/binary-data' ;
158215 globalThis . fetch = vi . fn ( async ( ) =>
0 commit comments