File Explorer

/var/lang/lib/node_modules/npm/node_modules/minipass-fetch/lib

This explorer reads the filesystem of the server it runs on, so /workspace/user isn't present here. Browsing and the terminal still work against this server's own disk from /.

0 dirs
8 files
index.js12.8 KB · 377 lines
'use strict'const { URL } = require('url')const http = require('http')const https = require('https')const zlib = require('minizlib')const { Minipass } = require('minipass') const Body = require('./body.js')const { writeToStream, getTotalBytes } = Bodyconst Response = require('./response.js')const Headers = require('./headers.js')const { createHeadersLenient } = Headersconst Request = require('./request.js')const { getNodeRequestOptions } = Requestconst FetchError = require('./fetch-error.js')const AbortError = require('./abort-error.js') // XXX this should really be split up and unit-ized for easier testing// and better DRY implementation of data/http request abortingconst fetch = async (url, opts) => {  if (/^data:/.test(url)) {    const request = new Request(url, opts)    // 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')        }        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) => {    // build request object    const request = new Request(url, opts)    let options    try {      options = getNodeRequestOptions(request)    } catch (er) {      return reject(er)    }     const send = (options.protocol === 'https:' ? https : http).request    const { signal } = request    let response = null    const abort = () => {      const error = new AbortError('The user aborted a request.')      reject(error)      if (Minipass.isStream(request.body) &&          typeof request.body.destroy === 'function') {        request.body.destroy(error)      }      if (response && response.body) {        response.body.emit('error', error)      }    }     if (signal && signal.aborted) {      return abort()    }     const abortAndFinalize = () => {      abort()      finalize()    }     const finalize = () => {      req.abort()      if (signal) {        signal.removeEventListener('abort', abortAndFinalize)      }      clearTimeout(reqTimeout)    }     // send request    const req = send(options)     if (signal) {      signal.addEventListener('abort', abortAndFinalize)    }     let reqTimeout = null    if (request.timeout) {      req.once('socket', () => {        reqTimeout = setTimeout(() => {          reject(new FetchError(`network timeout at: ${            request.url}`, 'request-timeout'))          finalize()        }, request.timeout)      })    }     req.on('error', er => {      // if a 'response' event is emitted before the 'error' event, then by the      // time this handler is run it's too late to reject the Promise for the      // response. instead, we forward the error event to the response stream      // so that the error will surface to the user when they try to consume      // the body. this is done as a side effect of aborting the request except      // for in windows, where we must forward the event manually, otherwise      // there is no longer a ref'd socket attached to the request and the      // stream never ends so the event loop runs out of work and the process      // exits without warning.      // coverage skipped here due to the difficulty in testing      // istanbul ignore next      if (req.res) {        req.res.emit('error', er)      }      reject(new FetchError(`request to ${request.url} failed, reason: ${        er.message}`, 'system', er))      finalize()    })     req.on('response', res => {      clearTimeout(reqTimeout)       const headers = createHeadersLenient(res.headers)       // HTTP fetch step 5      if (fetch.isRedirect(res.statusCode)) {        // HTTP fetch step 5.2        const location = headers.get('Location')         // HTTP fetch step 5.3        let locationURL = null        try {          locationURL = location === null ? null : new URL(location, request.url).toString()        } 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') {            /* eslint-disable-next-line max-len */            reject(new FetchError(`uri requested responds with an invalid redirect URL: ${location}`, 'invalid-redirect'))            finalize()            return          }        }         // HTTP fetch step 5.5        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          }           // 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,          }           // if the redirect is to a new hostname, strip the authorization and cookie headers          const parsedOriginal = new URL(request.url)          const parsedRedirect = new URL(locationURL)          if (parsedOriginal.hostname !== parsedRedirect.hostname) {            requestOpts.headers.delete('authorization')            requestOpts.headers.delete('cookie')          }           // 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))      res.on('data', (chunk) => body.write(chunk))      res.on('end', () => body.end())       const responseOptions = {        url: request.url,        status: res.statusCode,        statusText: res.statusMessage,        headers: headers,        size: request.size,        timeout: request.timeout,        counter: request.counter,        trailer: new Promise(resolveTrailer =>          res.on('end', () => resolveTrailer(createHeadersLenient(res.trailers)))),      }       // HTTP-network fetch step 12.1.1.3      const codings = headers.get('Content-Encoding')       // HTTP-network fetch step 12.1.1.4: handle content codings       // in following scenarios we ignore compression support      // 1. compression support is disabled      // 2. HEAD request      // 3. no Content-Encoding header      // 4. no content response (204)      // 5. content not modified response (304)      if (!request.compress ||          request.method === 'HEAD' ||          codings === null ||          res.statusCode === 204 ||          res.statusCode === 304) {        response = new Response(body, responseOptions)        resolve(response)        return      }       // Be less strict when decoding compressed responses, since sometimes      // servers send slightly invalid responses that are still accepted      // by common browsers.      // Always using Z_SYNC_FLUSH is what cURL does.      const zlibOptions = {        flush: zlib.constants.Z_SYNC_FLUSH,        finishFlush: zlib.constants.Z_SYNC_FLUSH,      }       // for 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,          // but just in case we proxy it to the stream in use.          body.on('error', /* istanbul ignore next */ er => unzip.emit('error', er)).pipe(unzip),          responseOptions        )        resolve(response)        return      }       // for deflate      if (codings === 'deflate' || codings === 'x-deflate') {        // handle the infamous raw deflate response from old servers        // a hack for old IIS and Apache servers        res.once('data', chunk => {          // see http://stackoverflow.com/questions/37519828          const decoder = (chunk[0] & 0x0F) === 0x08            ? new zlib.Inflate()            : new zlib.InflateRaw()          // exceedingly rare that the stream would have an error,          // but just in case we proxy it to the stream in use.          body.on('error', /* istanbul ignore next */ er => decoder.emit('error', er)).pipe(decoder)          response = new Response(decoder, responseOptions)          resolve(response)        })        return      }       // for br      if (codings === 'br') {        // ignoring coverage so tests don't have to fake support (or lack of) for brotli        // istanbul ignore next        try {          var decoder = new zlib.BrotliDecompress()        } catch (err) {          reject(err)          finalize()          return        }        // exceedingly rare that the stream would have an error,        // but just in case we proxy it to the stream in use.        body.on('error', /* istanbul ignore next */ er => decoder.emit('error', er)).pipe(decoder)        response = new Response(decoder, responseOptions)        resolve(response)        return      }       // otherwise, use response as-is      response = new Response(body, responseOptions)      resolve(response)    })     writeToStream(req, request)  })} module.exports = fetch fetch.isRedirect = code =>  code === 301 ||  code === 302 ||  code === 303 ||  code === 307 ||  code === 308 fetch.Headers = Headersfetch.Request = Requestfetch.Response = Responsefetch.FetchError = FetchErrorfetch.AbortError = AbortError