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