@@ -198,6 +198,215 @@ describe('request utils', () => {
198198 data : { xx : 'a' , yy : 'z' } ,
199199 } ) ;
200200 } ) ;
201+
202+ describe ( 'x-forwarded headers support' , ( ) => {
203+ it ( 'should prioritize x-forwarded-proto header over explicit protocol parameter' , ( ) => {
204+ const actual = httpRequestToRequestData ( {
205+ url : '/test' ,
206+ headers : {
207+ host : 'example.com' ,
208+ 'x-forwarded-proto' : 'https' ,
209+ } ,
210+ protocol : 'http' ,
211+ } ) ;
212+
213+ expect ( actual ) . toEqual ( {
214+ url : 'https://example.com/test' ,
215+ headers : {
216+ host : 'example.com' ,
217+ 'x-forwarded-proto' : 'https' ,
218+ } ,
219+ } ) ;
220+ } ) ;
221+
222+ it ( 'should prioritize x-forwarded-proto header even when downgrading from https to http' , ( ) => {
223+ const actual = httpRequestToRequestData ( {
224+ url : '/test' ,
225+ headers : {
226+ host : 'example.com' ,
227+ 'x-forwarded-proto' : 'http' ,
228+ } ,
229+ protocol : 'https' ,
230+ } ) ;
231+
232+ expect ( actual ) . toEqual ( {
233+ url : 'http://example.com/test' ,
234+ headers : {
235+ host : 'example.com' ,
236+ 'x-forwarded-proto' : 'http' ,
237+ } ,
238+ } ) ;
239+ } ) ;
240+
241+ it ( 'should prioritize x-forwarded-proto header over socket encryption detection' , ( ) => {
242+ const actual = httpRequestToRequestData ( {
243+ url : '/test' ,
244+ headers : {
245+ host : 'example.com' ,
246+ 'x-forwarded-proto' : 'https' ,
247+ } ,
248+ socket : {
249+ encrypted : false ,
250+ } ,
251+ } ) ;
252+
253+ expect ( actual ) . toEqual ( {
254+ url : 'https://example.com/test' ,
255+ headers : {
256+ host : 'example.com' ,
257+ 'x-forwarded-proto' : 'https' ,
258+ } ,
259+ } ) ;
260+ } ) ;
261+
262+ it ( 'should prioritize x-forwarded-host header over standard host header' , ( ) => {
263+ const actual = httpRequestToRequestData ( {
264+ url : '/test' ,
265+ headers : {
266+ host : 'localhost:3000' ,
267+ 'x-forwarded-host' : 'example.com' ,
268+ 'x-forwarded-proto' : 'https' ,
269+ } ,
270+ } ) ;
271+
272+ expect ( actual ) . toEqual ( {
273+ url : 'https://example.com/test' ,
274+ headers : {
275+ host : 'localhost:3000' ,
276+ 'x-forwarded-host' : 'example.com' ,
277+ 'x-forwarded-proto' : 'https' ,
278+ } ,
279+ } ) ;
280+ } ) ;
281+
282+ it ( 'should construct URL correctly when both x-forwarded-proto and x-forwarded-host are present' , ( ) => {
283+ const actual = httpRequestToRequestData ( {
284+ method : 'POST' ,
285+ url : '/api/test?param=value' ,
286+ headers : {
287+ host : 'localhost:3000' ,
288+ 'x-forwarded-host' : 'api.example.com' ,
289+ 'x-forwarded-proto' : 'https' ,
290+ 'content-type' : 'application/json' ,
291+ } ,
292+ protocol : 'http' ,
293+ } ) ;
294+
295+ expect ( actual ) . toEqual ( {
296+ method : 'POST' ,
297+ url : 'https://api.example.com/api/test?param=value' ,
298+ query_string : 'param=value' ,
299+ headers : {
300+ host : 'localhost:3000' ,
301+ 'x-forwarded-host' : 'api.example.com' ,
302+ 'x-forwarded-proto' : 'https' ,
303+ 'content-type' : 'application/json' ,
304+ } ,
305+ } ) ;
306+ } ) ;
307+
308+ it ( 'should fall back to standard headers when x-forwarded headers are not present' , ( ) => {
309+ const actual = httpRequestToRequestData ( {
310+ url : '/test' ,
311+ headers : {
312+ host : 'example.com' ,
313+ } ,
314+ protocol : 'https' ,
315+ } ) ;
316+
317+ expect ( actual ) . toEqual ( {
318+ url : 'https://example.com/test' ,
319+ headers : {
320+ host : 'example.com' ,
321+ } ,
322+ } ) ;
323+ } ) ;
324+
325+ it ( 'should ignore x-forwarded headers when they contain non-string values' , ( ) => {
326+ const actual = httpRequestToRequestData ( {
327+ url : '/test' ,
328+ headers : {
329+ host : 'example.com' ,
330+ 'x-forwarded-host' : [ 'forwarded.example.com' ] as any ,
331+ 'x-forwarded-proto' : [ 'https' ] as any ,
332+ } ,
333+ protocol : 'http' ,
334+ } ) ;
335+
336+ expect ( actual ) . toEqual ( {
337+ url : 'http://example.com/test' ,
338+ headers : {
339+ host : 'example.com' ,
340+ } ,
341+ } ) ;
342+ } ) ;
343+
344+ it ( 'should correctly transform localhost request to public URL using x-forwarded headers' , ( ) => {
345+ const actual = httpRequestToRequestData ( {
346+ method : 'GET' ,
347+ url : '/' ,
348+ headers : {
349+ host : 'localhost:3000' ,
350+ 'x-forwarded-proto' : 'https' ,
351+ 'x-forwarded-host' : 'example.com' ,
352+ } ,
353+ } ) ;
354+
355+ expect ( actual ) . toEqual ( {
356+ method : 'GET' ,
357+ url : 'https://example.com/' ,
358+ headers : {
359+ host : 'localhost:3000' ,
360+ 'x-forwarded-proto' : 'https' ,
361+ 'x-forwarded-host' : 'example.com' ,
362+ } ,
363+ } ) ;
364+ } ) ;
365+
366+ it ( 'should respect x-forwarded-proto even when it downgrades from encrypted socket' , ( ) => {
367+ const actual = httpRequestToRequestData ( {
368+ url : '/test' ,
369+ headers : {
370+ host : 'example.com' ,
371+ 'x-forwarded-proto' : 'http' ,
372+ } ,
373+ socket : {
374+ encrypted : true ,
375+ } ,
376+ } ) ;
377+
378+ expect ( actual ) . toEqual ( {
379+ url : 'http://example.com/test' ,
380+ headers : {
381+ host : 'example.com' ,
382+ 'x-forwarded-proto' : 'http' ,
383+ } ,
384+ } ) ;
385+ } ) ;
386+
387+ it ( 'should preserve query parameters when constructing URL with x-forwarded headers' , ( ) => {
388+ const actual = httpRequestToRequestData ( {
389+ method : 'GET' ,
390+ url : '/search?q=test&category=api' ,
391+ headers : {
392+ host : 'localhost:8080' ,
393+ 'x-forwarded-host' : 'search.example.com' ,
394+ 'x-forwarded-proto' : 'https' ,
395+ } ,
396+ } ) ;
397+
398+ expect ( actual ) . toEqual ( {
399+ method : 'GET' ,
400+ url : 'https://search.example.com/search?q=test&category=api' ,
401+ query_string : 'q=test&category=api' ,
402+ headers : {
403+ host : 'localhost:8080' ,
404+ 'x-forwarded-host' : 'search.example.com' ,
405+ 'x-forwarded-proto' : 'https' ,
406+ } ,
407+ } ) ;
408+ } ) ;
409+ } ) ;
201410 } ) ;
202411
203412 describe ( 'extractQueryParamsFromUrl' , ( ) => {
0 commit comments