• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict'
2
3const BB = require('bluebird')
4
5const fetch = require('npm-registry-fetch')
6const manifest = require('./manifest')
7const optCheck = require('../../util/opt-check')
8const PassThrough = require('stream').PassThrough
9const ssri = require('ssri')
10const url = require('url')
11
12module.exports = tarball
13function tarball (spec, opts) {
14  opts = optCheck(opts)
15  const registry = fetch.pickRegistry(spec, opts)
16  const stream = new PassThrough()
17  let mani
18  if (
19    opts.resolved &&
20    // spec.type === 'version' &&
21    opts.resolved.indexOf(registry) === 0
22  ) {
23    // fakeChild is a shortcut to avoid looking up a manifest!
24    mani = BB.resolve({
25      name: spec.name,
26      version: spec.fetchSpec,
27      _integrity: opts.integrity,
28      _resolved: opts.resolved,
29      _fakeChild: true
30    })
31  } else {
32    // We can't trust opts.resolved if it's going to a separate host.
33    mani = manifest(spec, opts)
34  }
35
36  mani.then(mani => {
37    !mani._fakeChild && stream.emit('manifest', mani)
38    const fetchStream = fromManifest(mani, spec, opts).on(
39      'integrity', i => stream.emit('integrity', i)
40    )
41    fetchStream.on('error', err => stream.emit('error', err))
42    fetchStream.pipe(stream)
43    return null
44  }).catch(err => stream.emit('error', err))
45  return stream
46}
47
48module.exports.fromManifest = fromManifest
49function fromManifest (manifest, spec, opts) {
50  opts = optCheck(opts)
51  if (spec.scope) { opts = opts.concat({ scope: spec.scope }) }
52  const stream = new PassThrough()
53  const registry = fetch.pickRegistry(spec, opts)
54  const uri = getTarballUrl(spec, registry, manifest, opts)
55  fetch(uri, opts.concat({
56    headers: {
57      'pacote-req-type': 'tarball',
58      'pacote-pkg-id': `registry:${manifest.name}@${uri}`
59    },
60    integrity: manifest._integrity,
61    algorithms: [
62      manifest._integrity
63        ? ssri.parse(manifest._integrity).pickAlgorithm()
64        : 'sha1'
65    ],
66    spec
67  }, opts))
68    .then(res => {
69      const hash = res.headers.get('x-local-cache-hash')
70      if (hash) {
71        stream.emit('integrity', decodeURIComponent(hash))
72      }
73      res.body.on('error', err => stream.emit('error', err))
74      res.body.pipe(stream)
75      return null
76    })
77    .catch(err => stream.emit('error', err))
78  return stream
79}
80
81function getTarballUrl (spec, registry, mani, opts) {
82  const reg = url.parse(registry)
83  const tarball = url.parse(mani._resolved)
84  // https://github.com/npm/npm/pull/9471
85  //
86  // TL;DR: Some alternative registries host tarballs on http and packuments
87  // on https, and vice-versa. There's also a case where people who can't use
88  // SSL to access the npm registry, for example, might use
89  // `--registry=http://registry.npmjs.org/`. In this case, we need to
90  // rewrite `tarball` to match the protocol.
91  //
92  if (reg.hostname === tarball.hostname && reg.protocol !== tarball.protocol) {
93    tarball.protocol = reg.protocol
94    // Ports might be same host different protocol!
95    if (reg.port !== tarball.port) {
96      delete tarball.host
97      tarball.port = reg.port
98    }
99    delete tarball.href
100  }
101  return url.format(tarball)
102}
103