diff options
Diffstat (limited to 'deps/npm/node_modules/minipass-fetch/lib/index.js')
-rw-r--r-- | deps/npm/node_modules/minipass-fetch/lib/index.js | 225 |
1 files changed, 120 insertions, 105 deletions
diff --git a/deps/npm/node_modules/minipass-fetch/lib/index.js b/deps/npm/node_modules/minipass-fetch/lib/index.js index 2ffcba8510..473630e1a5 100644 --- a/deps/npm/node_modules/minipass-fetch/lib/index.js +++ b/deps/npm/node_modules/minipass-fetch/lib/index.js @@ -1,5 +1,5 @@ 'use strict' -const Url = require('url') +const { URL } = require('url') const http = require('http') const https = require('https') const zlib = require('minizlib') @@ -15,25 +15,41 @@ const { getNodeRequestOptions } = Request const FetchError = require('./fetch-error.js') const AbortError = require('./abort-error.js') -const resolveUrl = Url.resolve - -const fetch = (url, opts) => { +// XXX this should really be split up and unit-ized for easier testing +// and better DRY implementation of data/http request aborting +const fetch = async (url, opts) => { if (/^data:/.test(url)) { const request = new Request(url, opts) - try { - const split = url.split(',') - const data = Buffer.from(split[1], 'base64') - const type = split[0].match(/^data:(.*);base64$/)[1] - return Promise.resolve(new Response(data, { - headers: { - 'Content-Type': type, - 'Content-Length': data.length, + // delay 1 promise tick so that the consumer can abort right away + return Promise.resolve().then(() => new Promise((resolve, reject) => { + let type, data + try { + const { pathname, search } = new URL(url) + const split = pathname.split(',') + if (split.length < 2) { + throw new Error('invalid data: URI') } - })) - } catch (er) { - return Promise.reject(new FetchError(`[${request.method}] ${ - request.url} invalid URL, ${er.message}`, 'system', er)) - } + const mime = split.shift() + const base64 = /;base64$/.test(mime) + type = base64 ? mime.slice(0, -1 * ';base64'.length) : mime + const rawData = decodeURIComponent(split.join(',') + search) + data = base64 ? Buffer.from(rawData, 'base64') : Buffer.from(rawData) + } catch (er) { + return reject(new FetchError(`[${request.method}] ${ + request.url} invalid URL, ${er.message}`, 'system', er)) + } + + const { signal } = request + if (signal && signal.aborted) { + return reject(new AbortError('The user aborted a request.')) + } + + const headers = { 'Content-Length': data.length } + if (type) { + headers['Content-Type'] = type + } + return resolve(new Response(data, { headers })) + })) } return new Promise((resolve, reject) => { @@ -61,8 +77,9 @@ const fetch = (url, opts) => { } } - if (signal && signal.aborted) + if (signal && signal.aborted) { return abort() + } const abortAndFinalize = () => { abort() @@ -71,16 +88,18 @@ const fetch = (url, opts) => { const finalize = () => { req.abort() - if (signal) + if (signal) { signal.removeEventListener('abort', abortAndFinalize) + } clearTimeout(reqTimeout) } // send request const req = send(options) - if (signal) + if (signal) { signal.addEventListener('abort', abortAndFinalize) + } let reqTimeout = null if (request.timeout) { @@ -105,8 +124,9 @@ const fetch = (url, opts) => { // exits without warning. // coverage skipped here due to the difficulty in testing // istanbul ignore next - if (req.res) + if (req.res) { req.res.emit('error', er) + } reject(new FetchError(`request to ${request.url} failed, reason: ${ er.message}`, 'system', er)) finalize() @@ -124,98 +144,95 @@ const fetch = (url, opts) => { // HTTP fetch step 5.3 const locationURL = location === null ? null - : resolveUrl(request.url, location) + : (new URL(location, request.url)).toString() // HTTP fetch step 5.5 - switch (request.redirect) { - case 'error': - reject(new FetchError(`uri requested responds with a redirect, redirect mode is set to error: ${ - request.url}`, 'no-redirect')) + if (request.redirect === 'error') { + reject(new FetchError('uri requested responds with a redirect, ' + + `redirect mode is set to error: ${request.url}`, 'no-redirect')) + finalize() + return + } else if (request.redirect === 'manual') { + // node-fetch-specific step: make manual redirect a bit easier to + // use by setting the Location header value to the resolved URL. + if (locationURL !== null) { + // handle corrupted header + try { + headers.set('Location', locationURL) + } catch (err) { + /* istanbul ignore next: nodejs server prevent invalid + response headers, we can't test this through normal + request */ + reject(err) + } + } + } else if (request.redirect === 'follow' && locationURL !== null) { + // HTTP-redirect fetch step 5 + if (request.counter >= request.follow) { + reject(new FetchError(`maximum redirect reached at: ${ + request.url}`, 'max-redirect')) finalize() return - - case 'manual': - // node-fetch-specific step: make manual redirect a bit easier to - // use by setting the Location header value to the resolved URL. - if (locationURL !== null) { - // handle corrupted header - try { - headers.set('Location', locationURL) - } catch (err) { - /* istanbul ignore next: nodejs server prevent invalid - response headers, we can't test this through normal - request */ - reject(err) - } - } - break - - case 'follow': - // HTTP-redirect fetch step 2 - if (locationURL === null) { - break - } - - // HTTP-redirect fetch step 5 - if (request.counter >= request.follow) { - reject(new FetchError(`maximum redirect reached at: ${ - request.url}`, 'max-redirect')) - finalize() - return - } - - // HTTP-redirect fetch step 9 - if (res.statusCode !== 303 && - request.body && - getTotalBytes(request) === null) { - reject(new FetchError( - 'Cannot follow redirect with body being a readable stream', - 'unsupported-redirect' - )) - finalize() - return - } - - // Update host due to redirection - request.headers.set('host', Url.parse(locationURL).host) - - // HTTP-redirect fetch step 6 (counter increment) - // Create a new Request object. - const requestOpts = { - headers: new Headers(request.headers), - follow: request.follow, - counter: request.counter + 1, - agent: request.agent, - compress: request.compress, - method: request.method, - body: request.body, - signal: request.signal, - timeout: request.timeout, - } - - // HTTP-redirect fetch step 11 - if (res.statusCode === 303 || ( - (res.statusCode === 301 || res.statusCode === 302) && - request.method === 'POST' - )) { - requestOpts.method = 'GET' - requestOpts.body = undefined - requestOpts.headers.delete('content-length') - } - - // HTTP-redirect fetch step 15 - resolve(fetch(new Request(locationURL, requestOpts))) + } + + // HTTP-redirect fetch step 9 + if (res.statusCode !== 303 && + request.body && + getTotalBytes(request) === null) { + reject(new FetchError( + 'Cannot follow redirect with body being a readable stream', + 'unsupported-redirect' + )) finalize() return + } + + // Update host due to redirection + request.headers.set('host', (new URL(locationURL)).host) + + // HTTP-redirect fetch step 6 (counter increment) + // Create a new Request object. + const requestOpts = { + headers: new Headers(request.headers), + follow: request.follow, + counter: request.counter + 1, + agent: request.agent, + compress: request.compress, + method: request.method, + body: request.body, + signal: request.signal, + timeout: request.timeout, + } + + // HTTP-redirect fetch step 11 + if (res.statusCode === 303 || ( + (res.statusCode === 301 || res.statusCode === 302) && + request.method === 'POST' + )) { + requestOpts.method = 'GET' + requestOpts.body = undefined + requestOpts.headers.delete('content-length') + } + + // HTTP-redirect fetch step 15 + resolve(fetch(new Request(locationURL, requestOpts))) + finalize() + return } } // end if(isRedirect) - // prepare response res.once('end', () => signal && signal.removeEventListener('abort', abortAndFinalize)) const body = new Minipass() + // if an error occurs, either on the response stream itself, on one of the + // decoder streams, or a response length timeout from the Body class, we + // forward the error through to our internal body stream. If we see an + // error event on that, we call finalize to abort the request and ensure + // we don't leave a socket believing a request is in flight. + // this is difficult to test, so lacks specific coverage. + body.on('error', finalize) // exceedingly rare that the stream would have an error, // but just in case we proxy it to the stream in use. res.on('error', /* istanbul ignore next */ er => body.emit('error', er)) @@ -231,7 +248,7 @@ const fetch = (url, opts) => { timeout: request.timeout, counter: request.counter, trailer: new Promise(resolve => - res.on('end', () => resolve(createHeadersLenient(res.trailers)))) + res.on('end', () => resolve(createHeadersLenient(res.trailers)))), } // HTTP-network fetch step 12.1.1.3 @@ -255,7 +272,6 @@ const fetch = (url, opts) => { return } - // Be less strict when decoding compressed responses, since sometimes // servers send slightly invalid responses that are still accepted // by common browsers. @@ -266,7 +282,7 @@ const fetch = (url, opts) => { } // for gzip - if (codings == 'gzip' || codings == 'x-gzip') { + if (codings === 'gzip' || codings === 'x-gzip') { const unzip = new zlib.Gunzip(zlibOptions) response = new Response( // exceedingly rare that the stream would have an error, @@ -279,7 +295,7 @@ const fetch = (url, opts) => { } // for deflate - if (codings == 'deflate' || codings == 'x-deflate') { + if (codings === 'deflate' || codings === 'x-deflate') { // handle the infamous raw deflate response from old servers // a hack for old IIS and Apache servers const raw = res.pipe(new Minipass()) @@ -297,9 +313,8 @@ const fetch = (url, opts) => { return } - // for br - if (codings == 'br') { + if (codings === 'br') { // ignoring coverage so tests don't have to fake support (or lack of) for brotli // istanbul ignore next try { |