diff --git a/lib/handler/redirect-handler.js b/lib/handler/redirect-handler.js index bdc8cdfd004..35a4aa4232e 100644 --- a/lib/handler/redirect-handler.js +++ b/lib/handler/redirect-handler.js @@ -23,6 +23,10 @@ class RedirectHandler { throw new InvalidArgumentError('maxRedirections must be a positive number') } + if (opts.throwOnMaxRedirect != null && typeof opts.throwOnMaxRedirect !== 'boolean') { + throw new InvalidArgumentError('throwOnMaxRedirect must be a boolean') + } + this.dispatch = dispatch this.location = null const { maxRedirections: _, ...cleanOpts } = opts diff --git a/lib/interceptor/redirect.js b/lib/interceptor/redirect.js index b7df180433e..83b4c9a7b2f 100644 --- a/lib/interceptor/redirect.js +++ b/lib/interceptor/redirect.js @@ -2,16 +2,16 @@ const RedirectHandler = require('../handler/redirect-handler') -function createRedirectInterceptor ({ maxRedirections: defaultMaxRedirections } = {}) { +function createRedirectInterceptor ({ maxRedirections: defaultMaxRedirections, throwOnMaxRedirect: defaultThrowOnMaxRedirect } = {}) { return (dispatch) => { return function Intercept (opts, handler) { - const { maxRedirections = defaultMaxRedirections, ...rest } = opts + const { maxRedirections = defaultMaxRedirections, throwOnMaxRedirect = defaultThrowOnMaxRedirect, ...rest } = opts if (maxRedirections == null || maxRedirections === 0) { return dispatch(opts, handler) } - const dispatchOpts = { ...rest } // Stop sub dispatcher from also redirecting. + const dispatchOpts = { ...rest, throwOnMaxRedirect } // Stop sub dispatcher from also redirecting. const redirectHandler = new RedirectHandler(dispatch, maxRedirections, dispatchOpts, handler) return dispatch(dispatchOpts, redirectHandler) } diff --git a/test/interceptors/redirect.js b/test/interceptors/redirect.js index 88f89818f79..716379d65da 100644 --- a/test/interceptors/redirect.js +++ b/test/interceptors/redirect.js @@ -466,6 +466,43 @@ for (const factory of [ await t.completed }) + test('should throw when max redirections is reached and throwOnMaxRedirect is set as interceptor default', async t => { + t = tspl(t, { plan: 1 }) + + const server = await startRedirectingServer() + + const dispatcher = new undici.Agent().compose( + redirect({ maxRedirections: 2, throwOnMaxRedirect: true }) + ) + after(() => dispatcher.close()) + + try { + await undici.request(`http://${server}/300`, { dispatcher }) + t.fail('Did not throw') + } catch (error) { + t.strictEqual(error.message, 'max redirects') + } + + await t.completed + }) + + test('should not allow invalid throwOnMaxRedirect arguments', async t => { + t = tspl(t, { plan: 1 }) + + try { + await request(t, 'localhost', undefined, 'http://localhost', { + method: 'GET', + maxRedirections: 1, + throwOnMaxRedirect: 'INVALID' + }) + t.fail('Did not throw') + } catch (err) { + t.strictEqual(err.message, 'throwOnMaxRedirect must be a boolean') + } + + await t.completed + }) + test('when a Location response header is NOT present', async t => { t = tspl(t, { plan: 6 * 3 }) diff --git a/test/types/redirect-interceptor.test-d.ts b/test/types/redirect-interceptor.test-d.ts new file mode 100644 index 00000000000..42612e0dec7 --- /dev/null +++ b/test/types/redirect-interceptor.test-d.ts @@ -0,0 +1,10 @@ +import { expectAssignable, expectNotAssignable } from 'tsd' +import Interceptors from '../../types/interceptors' + +expectAssignable({}) +expectAssignable({ maxRedirections: 3 }) +expectAssignable({ throwOnMaxRedirect: true }) +expectAssignable({ maxRedirections: 3, throwOnMaxRedirect: true }) + +expectNotAssignable({ maxRedirections: 'INVALID' }) +expectNotAssignable({ throwOnMaxRedirect: 'INVALID' }) diff --git a/types/interceptors.d.ts b/types/interceptors.d.ts index 71983a768c0..3b90a28592f 100644 --- a/types/interceptors.d.ts +++ b/types/interceptors.d.ts @@ -8,7 +8,7 @@ export default Interceptors declare namespace Interceptors { export type DumpInterceptorOpts = { maxSize?: number } export type RetryInterceptorOpts = RetryHandler.RetryOptions - export type RedirectInterceptorOpts = { maxRedirections?: number } + export type RedirectInterceptorOpts = { maxRedirections?: number, throwOnMaxRedirect?: boolean } export type DecompressInterceptorOpts = { skipErrorResponses?: boolean skipStatusCodes?: number[]