Skip to content

Commit 125e447

Browse files
committed
Update redirection strategies to drop confidential headers
1 parent a17d0bd commit 125e447

File tree

2 files changed

+56
-11
lines changed

2 files changed

+56
-11
lines changed

Diff for: index.js

+33-8
Original file line numberDiff line numberDiff line change
@@ -534,13 +534,20 @@ export const matchStatus = f => fs => res => {
534534
return (hasProp (statusCode) (fs) ? fs[statusCode] : f) (res);
535535
};
536536

537-
// mergeUrls :: Url -> Any -> String
538-
const mergeUrls = base => input => (
537+
// mergeUrls :: (Url, Any) -> String
538+
const mergeUrls = (base, input) => (
539539
typeof input === 'string' ?
540540
new URL (input, base).href :
541541
base
542542
);
543543

544+
// sameHost :: (Url, Url) -> Boolean
545+
const sameHost = (parent, child) => {
546+
const p = new URL (parent);
547+
const c = new URL (child);
548+
return p.host === c.host || c.host.endsWith ('.' + p.host);
549+
};
550+
544551
// overHeaders :: (Request, Array2 String String -> Array2 String String)
545552
// -> Request
546553
const overHeaders = (request, f) => {
@@ -551,21 +558,34 @@ const overHeaders = (request, f) => {
551558
(Request.body (request));
552559
};
553560

561+
// confidentialHeaders :: Array String
562+
const confidentialHeaders = [
563+
'authorization',
564+
'cookie',
565+
];
566+
554567
//# redirectAnyRequest :: Response -> Request
555568
//.
556569
//. A redirection strategy that simply reissues the original Request to the
557570
//. Location specified in the given Response.
558571
//.
572+
//. If the new location is on an external host, then any confidential headers
573+
//. (such as the cookie header) will be dropped from the new request.
574+
//.
559575
//. Used in the [`defaultRedirectionPolicy`](#defaultRedirectionPolicy) and
560576
//. the [`aggressiveRedirectionPolicy`](#aggressiveRedirectionPolicy).
561577
export const redirectAnyRequest = response => {
562578
const {headers: {location}} = Response.message (response);
563579
const original = Response.request (response);
564580
const oldUrl = Request.url (original);
565-
const newUrl = mergeUrls (oldUrl) (location);
566-
return (Request (Request.options (original))
567-
(newUrl)
568-
(Request.body (original)));
581+
const newUrl = mergeUrls (oldUrl, location);
582+
const request = Request (Request.options (original))
583+
(newUrl)
584+
(Request.body (original));
585+
586+
return sameHost (oldUrl, newUrl) ? request : overHeaders (request, xs => (
587+
xs.filter (([name]) => !confidentialHeaders.includes (name.toLowerCase ()))
588+
));
569589
};
570590

571591
//# redirectIfGetMethod :: Response -> Request
@@ -574,6 +594,9 @@ export const redirectAnyRequest = response => {
574594
//. Location specified in the given Response, but only if the original request
575595
//. was using the GET method.
576596
//.
597+
//. If the new location is on an external host, then any confidential headers
598+
//. (such as the cookie header) will be dropped from the new request.
599+
//.
577600
//. Used in [`followRedirectsStrict`](#followRedirectsStrict).
578601
export const redirectIfGetMethod = response => {
579602
const {method} = cleanRequestOptions (Response.request (response));
@@ -590,8 +613,10 @@ export const redirectIfGetMethod = response => {
590613
//. request to the Location specified in the given Response. If the response
591614
//. does not contain a valid location, the request is not redirected.
592615
//.
593-
//. The original request method and body are discarded, but all the options
594-
//. are preserved.
616+
//. The original request method and body are discarded, but other options
617+
//. are preserved. If the new location is on an external host, then any
618+
//. confidential headers (such as the cookie header) will be dropped from the
619+
//. new request.
595620
//.
596621
//. Used in the [`defaultRedirectionPolicy`](#defaultRedirectionPolicy) and
597622
//. the [`aggressiveRedirectionPolicy`](#aggressiveRedirectionPolicy).

Diff for: test/index.js

+23-3
Original file line numberDiff line numberDiff line change
@@ -259,18 +259,38 @@ test ('redirectAnyRequest', () => Promise.all ([
259259
assertResolves (fl.map (fn.redirectAnyRequest) (mockResponse ({})))
260260
(getRequest),
261261
assertResolves (fl.map (fn.redirectAnyRequest) (getResponse (301) ('ftp://xxx')))
262-
(fn.Request ({}) ('ftp://xxx/') (fn.emptyStream)),
262+
(fn.Request ({headers: {}}) ('ftp://xxx/') (fn.emptyStream)),
263263
assertResolves (fl.map (fn.redirectAnyRequest) (getResponse (301) ('/echo')))
264264
(fn.Request ({}) ('https://example.com/echo') (fn.emptyStream)),
265265
assertResolves (fl.map (fn.redirectAnyRequest) (postResponse (301) ('/echo')))
266266
(fn.Request ({method: 'POST'}) ('https://example.com/echo') (fn.streamOf (Buffer.from ('test')))),
267+
assertResolves (fl.map (fn.redirectAnyRequest)
268+
(mockResponse ({code: 301,
269+
headers: {location: 'https://example.com/path'},
270+
request: fn.Request ({headers: {cookie: 'yum'}}) ('https://example.com') (fn.emptyStream)})))
271+
(fn.Request ({headers: {cookie: 'yum'}}) ('https://example.com/path') (fn.emptyStream)),
272+
assertResolves (fl.map (fn.redirectAnyRequest)
273+
(mockResponse ({code: 301,
274+
headers: {location: 'https://sub.example.com/'},
275+
request: fn.Request ({headers: {cookie: 'yum'}}) ('https://example.com') (fn.emptyStream)})))
276+
(fn.Request ({headers: {cookie: 'yum'}}) ('https://sub.example.com/') (fn.emptyStream)),
277+
assertResolves (fl.map (fn.redirectAnyRequest)
278+
(mockResponse ({code: 301,
279+
headers: {location: 'https://bigsub.example.com/'},
280+
request: fn.Request ({headers: {cookie: 'yum'}}) ('https://example.com') (fn.emptyStream)})))
281+
(fn.Request ({headers: {cookie: 'yum'}}) ('https://bigsub.example.com/') (fn.emptyStream)),
282+
assertResolves (fl.map (fn.redirectAnyRequest)
283+
(mockResponse ({code: 301,
284+
headers: {location: 'https://elsewhere.com/'},
285+
request: fn.Request ({headers: {cookie: 'yum'}}) ('https://example.com') (fn.emptyStream)})))
286+
(fn.Request ({headers: {}}) ('https://elsewhere.com/') (fn.emptyStream)),
267287
]));
268288

269289
test ('redirectIfGetMethod', () => Promise.all ([
270290
assertResolves (fl.map (fn.redirectIfGetMethod) (mockResponse ({})))
271291
(getRequest),
272292
assertResolves (fl.map (fn.redirectIfGetMethod) (getResponse (301) ('ftp://xxx')))
273-
(fn.Request ({}) ('ftp://xxx/') (fn.emptyStream)),
293+
(fn.Request ({headers: {}}) ('ftp://xxx/') (fn.emptyStream)),
274294
assertResolves (fl.map (fn.redirectIfGetMethod) (getResponse (301) ('/echo')))
275295
(fn.Request ({}) ('https://example.com/echo') (fn.emptyStream)),
276296
assertResolves (fl.map (fn.redirectIfGetMethod) (postResponse (301) ('/echo')))
@@ -281,7 +301,7 @@ test ('redirectUsingGetMethod', () => Promise.all ([
281301
assertResolves (fl.map (fn.redirectUsingGetMethod) (mockResponse ({})))
282302
(fn.Request ({method: 'GET'}) ('https://example.com') (fn.emptyStream)),
283303
assertResolves (fl.map (fn.redirectUsingGetMethod) (getResponse (301) ('ftp://xxx')))
284-
(fn.Request ({method: 'GET'}) ('ftp://xxx/') (fn.emptyStream)),
304+
(fn.Request ({method: 'GET', headers: {}}) ('ftp://xxx/') (fn.emptyStream)),
285305
assertResolves (fl.map (fn.redirectUsingGetMethod) (getResponse (200) ('/echo')))
286306
(fn.Request ({method: 'GET'}) ('https://example.com/echo') (fn.emptyStream)),
287307
assertResolves (fl.map (fn.redirectUsingGetMethod) (postResponse (200) ('/echo')))

0 commit comments

Comments
 (0)