summaryrefslogtreecommitdiff
path: root/deps/npm/node_modules/minipass-fetch/lib/index.js
diff options
context:
space:
mode:
Diffstat (limited to 'deps/npm/node_modules/minipass-fetch/lib/index.js')
-rw-r--r--deps/npm/node_modules/minipass-fetch/lib/index.js225
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 {