diff --git a/lib/handler/redirect-handler.js b/lib/handler/redirect-handler.js index b79c9441c31..5121cf2263b 100644 --- a/lib/handler/redirect-handler.js +++ b/lib/handler/redirect-handler.js @@ -75,7 +75,8 @@ class RedirectHandler { this.opts.body && typeof this.opts.body !== 'string' && !ArrayBuffer.isView(this.opts.body) && - util.isIterable(this.opts.body) + util.isIterable(this.opts.body) && + !util.isFormDataLike(this.opts.body) ) { // TODO: Should we allow re-using iterable if !this.opts.idempotent // or through some other flag? diff --git a/test/interceptors/redirect-issue-3803.js b/test/interceptors/redirect-issue-3803.js new file mode 100644 index 00000000000..a66bfa237a9 --- /dev/null +++ b/test/interceptors/redirect-issue-3803.js @@ -0,0 +1,40 @@ +'use strict' + +const { FormData, request, Agent, interceptors } = require('../..') +const { test } = require('node:test') +const { createServer } = require('node:http') +const { once } = require('node:events') +const { tspl } = require('@matteo.collina/tspl') + +test('redirecting works with a FormData body', async (t) => { + const plan = tspl(t, { plan: 1 }) + + const server = createServer((req, res) => { + if (req.url === '/1') { + res.writeHead(302, undefined, { location: '/2' }) + res.end() + } else { + res.end('OK') + } + }).listen(0) + + t.after(() => server.close()) + await once(server, 'listening') + + const agent = new Agent().compose(interceptors.redirect({ maxRedirections: 1 })) + + const body = new FormData() + body.append('hello', 'world') + + const { context } = await request(`http://localhost:${server.address().port}/1`, { + body, + method: 'POST', + dispatcher: agent, + maxRedirections: 1 + }) + + plan.deepStrictEqual(context.history, [ + new URL(`http://localhost:${server.address().port}/1`), + new URL(`http://localhost:${server.address().port}/2`) + ]) +}) diff --git a/test/types/interceptor.test-d.ts b/test/types/interceptor.test-d.ts deleted file mode 100644 index fa87fe1e6a4..00000000000 --- a/test/types/interceptor.test-d.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { expectAssignable } from 'tsd' -import Undici from '../..' -import Dispatcher from '../../types/dispatcher' - -expectAssignable(Undici.createRedirectInterceptor({ maxRedirections: 3 })) diff --git a/types/index.d.ts b/types/index.d.ts index fed36ab8643..bd3c1d47b23 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -40,7 +40,6 @@ declare namespace Undici { const RedirectHandler: typeof import ('./handlers').RedirectHandler const DecoratorHandler: typeof import ('./handlers').DecoratorHandler const RetryHandler: typeof import ('./retry-handler').default - const createRedirectInterceptor: typeof import ('./interceptors').default.createRedirectInterceptor const BalancedPool: typeof import('./balanced-pool').default const Client: typeof import('./client').default const buildConnector: typeof import('./connector').default diff --git a/types/interceptors.d.ts b/types/interceptors.d.ts index 7ce6cc3b175..5a6fcb28ba7 100644 --- a/types/interceptors.d.ts +++ b/types/interceptors.d.ts @@ -25,7 +25,6 @@ declare namespace Interceptors { affinity?: 4 | 6 } - export function createRedirectInterceptor (opts: RedirectInterceptorOpts): Dispatcher.DispatcherComposeInterceptor export function dump (opts?: DumpInterceptorOpts): Dispatcher.DispatcherComposeInterceptor export function retry (opts?: RetryInterceptorOpts): Dispatcher.DispatcherComposeInterceptor export function redirect (opts?: RedirectInterceptorOpts): Dispatcher.DispatcherComposeInterceptor