diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 5a6b9138a..5245cfe1c 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased +* fix(request): fix crash when an invalid redirection URL is encountered https://github.com/node-fetch/node-fetch/pull/1387 * fix: handle errors from the request body stream by @mdmitry01 in https://github.com/node-fetch/node-fetch/pull/1392 ## 3.1.0 diff --git a/src/index.js b/src/index.js index 38c076465..dc4bafd23 100644 --- a/src/index.js +++ b/src/index.js @@ -130,7 +130,19 @@ export default async function fetch(url, options_) { const location = headers.get('Location'); // HTTP fetch step 5.3 - const locationURL = location === null ? null : new URL(location, request.url); + let locationURL = null; + try { + locationURL = location === null ? null : new URL(location, request.url); + } catch { + // error here can only be invalid URL in Location: header + // do not throw when options.redirect == manual + // let the user extract the errorneous redirect URL + if (request.redirect !== 'manual') { + reject(new FetchError(`uri requested responds with an invalid redirect URL: ${location}`, 'invalid-redirect')); + finalize(); + return; + } + } // HTTP fetch step 5.5 switch (request.redirect) { diff --git a/test/main.js b/test/main.js index b9937fe0e..5932f758b 100644 --- a/test/main.js +++ b/test/main.js @@ -527,6 +527,28 @@ describe('node-fetch', () => { }); }); + it('should process an invalid redirect (manual)', () => { + const url = `${base}redirect/301/invalid`; + const options = { + redirect: 'manual' + }; + return fetch(url, options).then(res => { + expect(res.url).to.equal(url); + expect(res.status).to.equal(301); + expect(res.headers.get('location')).to.equal('//super:invalid:url%/'); + }); + }); + + it('should throw an error on invalid redirect url', () => { + const url = `${base}redirect/301/invalid`; + return fetch(url).then(() => { + expect.fail(); + }, error => { + expect(error).to.be.an.instanceof(FetchError); + expect(error.message).to.equal('uri requested responds with an invalid redirect URL: //super:invalid:url%/'); + }); + }); + it('should throw a TypeError on an invalid redirect option', () => { const url = `${base}redirect/301`; const options = { diff --git a/test/utils/server.js b/test/utils/server.js index 2a1e8e9b0..351a3cd73 100644 --- a/test/utils/server.js +++ b/test/utils/server.js @@ -239,6 +239,12 @@ export default class TestServer { res.end(); } + if (p === '/redirect/301/invalid') { + res.statusCode = 301; + res.setHeader('Location', '//super:invalid:url%/'); + res.end(); + } + if (p === '/redirect/302') { res.statusCode = 302; res.setHeader('Location', '/inspect');