aboutsummaryrefslogtreecommitdiffstats
path: root/node_modules/node-gyp/lib/download.js
blob: dfaca798cf827bd172f5d6cb65990f5b0712d35f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
const { Readable } = require('stream')
const { Agent, EnvHttpProxyAgent, RetryAgent, fetch } = require('undici')
const { promises: fs } = require('graceful-fs')
const log = require('./log')

async function download (gyp, url) {
  log.http('GET', url)

  const requestOpts = {
    headers: {
      'User-Agent': `node-gyp v${gyp.version} (node ${process.version})`,
      Connection: 'keep-alive'
    },
    dispatcher: await createDispatcher(gyp)
  }

  let res
  try {
    res = await fetch(url, requestOpts)
  } catch (err) {
    // Built-in fetch wraps low-level errors in "TypeError: fetch failed" with
    // the underlying error on .cause. Callers inspect .code (e.g. ENOTFOUND).
    if (err.cause) {
      throw err.cause
    }
    throw err
  }

  log.http(res.status, res.url)

  const body = res.body ? Readable.fromWeb(res.body) : Readable.from([])
  return {
    status: res.status,
    url: res.url,
    body,
    text: async () => {
      let data = ''
      body.setEncoding('utf8')
      for await (const chunk of body) {
        data += chunk
      }
      return data
    }
  }
}

async function createDispatcher (gyp) {
  const env = process.env
  const hasProxyEnv = env.http_proxy || env.HTTP_PROXY || env.https_proxy || env.HTTPS_PROXY
  if (!gyp.opts.proxy && !gyp.opts.cafile && !hasProxyEnv) {
    return new RetryAgent(new Agent(), { maxRetries: 3 })
  }

  const opts = {}
  if (gyp.opts.cafile) {
    const ca = await readCAFile(gyp.opts.cafile)
    // EnvHttpProxyAgent forwards opts to both its internal Agent (direct) and
    // ProxyAgent (proxied). Agent reads TLS config from `connect`; ProxyAgent
    // reads it from `requestTls` (origin) / `proxyTls` (proxy). Set all three
    // so the custom CA is applied regardless of which path a request takes.
    opts.connect = { ca }
    opts.requestTls = { ca }
    opts.proxyTls = { ca }
  }
  if (gyp.opts.proxy) {
    opts.httpProxy = gyp.opts.proxy
    opts.httpsProxy = gyp.opts.proxy
  }
  if (gyp.opts.noproxy) {
    opts.noProxy = gyp.opts.noproxy
  }
  return new RetryAgent(new EnvHttpProxyAgent(opts), { maxRetries: 3 })
}

async function readCAFile (filename) {
  // The CA file can contain multiple certificates so split on certificate
  // boundaries.  [\S\s]*? is used to match everything including newlines.
  const ca = await fs.readFile(filename, 'utf8')
  const re = /(-----BEGIN CERTIFICATE-----[\S\s]*?-----END CERTIFICATE-----)/g
  return ca.match(re)
}

module.exports = {
  download,
  readCAFile
}