[6a3a178] | 1 | const Fetcher = require('./fetcher.js')
|
---|
| 2 | const RemoteFetcher = require('./remote.js')
|
---|
| 3 | const _tarballFromResolved = Symbol.for('pacote.Fetcher._tarballFromResolved')
|
---|
| 4 | const pacoteVersion = require('../package.json').version
|
---|
| 5 | const npa = require('npm-package-arg')
|
---|
| 6 | const rpj = require('read-package-json-fast')
|
---|
| 7 | const pickManifest = require('npm-pick-manifest')
|
---|
| 8 | const ssri = require('ssri')
|
---|
| 9 | const Minipass = require('minipass')
|
---|
| 10 |
|
---|
| 11 | // Corgis are cute. 🐕🐶
|
---|
| 12 | const corgiDoc = 'application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*'
|
---|
| 13 | const fullDoc = 'application/json'
|
---|
| 14 |
|
---|
| 15 | const fetch = require('npm-registry-fetch')
|
---|
| 16 |
|
---|
| 17 | // TODO: memoize reg requests, so we don't even have to check cache
|
---|
| 18 |
|
---|
| 19 | const _headers = Symbol('_headers')
|
---|
| 20 | class RegistryFetcher extends Fetcher {
|
---|
| 21 | constructor (spec, opts) {
|
---|
| 22 | super(spec, opts)
|
---|
| 23 |
|
---|
| 24 | // you usually don't want to fetch the same packument multiple times in
|
---|
| 25 | // the span of a given script or command, no matter how many pacote calls
|
---|
| 26 | // are made, so this lets us avoid doing that. It's only relevant for
|
---|
| 27 | // registry fetchers, because other types simulate their packument from
|
---|
| 28 | // the manifest, which they memoize on this.package, so it's very cheap
|
---|
| 29 | // already.
|
---|
| 30 | this.packumentCache = this.opts.packumentCache || null
|
---|
| 31 |
|
---|
| 32 | // handle case when npm-package-arg guesses wrong.
|
---|
| 33 | if (this.spec.type === 'tag' &&
|
---|
| 34 | this.spec.rawSpec === '' &&
|
---|
| 35 | this.defaultTag !== 'latest')
|
---|
| 36 | this.spec = npa(`${this.spec.name}@${this.defaultTag}`)
|
---|
| 37 | this.registry = fetch.pickRegistry(spec, opts)
|
---|
| 38 | this.packumentUrl = this.registry.replace(/\/*$/, '/') +
|
---|
| 39 | this.spec.escapedName
|
---|
| 40 |
|
---|
| 41 | // XXX pacote <=9 has some logic to ignore opts.resolved if
|
---|
| 42 | // the resolved URL doesn't go to the same registry.
|
---|
| 43 | // Consider reproducing that here, to throw away this.resolved
|
---|
| 44 | // in that case.
|
---|
| 45 | }
|
---|
| 46 |
|
---|
| 47 | resolve () {
|
---|
| 48 | if (this.resolved)
|
---|
| 49 | return Promise.resolve(this.resolved)
|
---|
| 50 |
|
---|
| 51 | // fetching the manifest sets resolved and (usually) integrity
|
---|
| 52 | return this.manifest().then(() => {
|
---|
| 53 | if (this.resolved)
|
---|
| 54 | return this.resolved
|
---|
| 55 |
|
---|
| 56 | throw Object.assign(
|
---|
| 57 | new Error('Invalid package manifest: no `dist.tarball` field'),
|
---|
| 58 | { package: this.spec.toString() }
|
---|
| 59 | )
|
---|
| 60 | })
|
---|
| 61 | }
|
---|
| 62 |
|
---|
| 63 | [_headers] () {
|
---|
| 64 | return {
|
---|
| 65 | // npm will override UA, but ensure that we always send *something*
|
---|
| 66 | 'user-agent': this.opts.userAgent ||
|
---|
| 67 | `pacote/${pacoteVersion} node/${process.version}`,
|
---|
| 68 | ...(this.opts.headers || {}),
|
---|
| 69 | 'pacote-version': pacoteVersion,
|
---|
| 70 | 'pacote-req-type': 'packument',
|
---|
| 71 | 'pacote-pkg-id': `registry:${this.spec.name}`,
|
---|
| 72 | accept: this.fullMetadata ? fullDoc : corgiDoc,
|
---|
| 73 | }
|
---|
| 74 | }
|
---|
| 75 |
|
---|
| 76 | async packument () {
|
---|
| 77 | // note this might be either an in-flight promise for a request,
|
---|
| 78 | // or the actual packument, but we never want to make more than
|
---|
| 79 | // one request at a time for the same thing regardless.
|
---|
| 80 | if (this.packumentCache && this.packumentCache.has(this.packumentUrl))
|
---|
| 81 | return this.packumentCache.get(this.packumentUrl)
|
---|
| 82 |
|
---|
| 83 | // npm-registry-fetch the packument
|
---|
| 84 | // set the appropriate header for corgis if fullMetadata isn't set
|
---|
| 85 | // return the res.json() promise
|
---|
| 86 | const p = fetch(this.packumentUrl, {
|
---|
| 87 | ...this.opts,
|
---|
| 88 | headers: this[_headers](),
|
---|
| 89 | spec: this.spec,
|
---|
| 90 | // never check integrity for packuments themselves
|
---|
| 91 | integrity: null,
|
---|
| 92 | }).then(res => res.json().then(packument => {
|
---|
| 93 | packument._cached = res.headers.has('x-local-cache')
|
---|
| 94 | packument._contentLength = +res.headers.get('content-length')
|
---|
| 95 | if (this.packumentCache)
|
---|
| 96 | this.packumentCache.set(this.packumentUrl, packument)
|
---|
| 97 | return packument
|
---|
| 98 | })).catch(er => {
|
---|
| 99 | if (this.packumentCache)
|
---|
| 100 | this.packumentCache.delete(this.packumentUrl)
|
---|
| 101 | if (er.code === 'E404' && !this.fullMetadata) {
|
---|
| 102 | // possible that corgis are not supported by this registry
|
---|
| 103 | this.fullMetadata = true
|
---|
| 104 | return this.packument()
|
---|
| 105 | }
|
---|
| 106 | throw er
|
---|
| 107 | })
|
---|
| 108 | if (this.packumentCache)
|
---|
| 109 | this.packumentCache.set(this.packumentUrl, p)
|
---|
| 110 | return p
|
---|
| 111 | }
|
---|
| 112 |
|
---|
| 113 | manifest () {
|
---|
| 114 | if (this.package)
|
---|
| 115 | return Promise.resolve(this.package)
|
---|
| 116 |
|
---|
| 117 | return this.packument()
|
---|
| 118 | .then(packument => pickManifest(packument, this.spec.fetchSpec, {
|
---|
| 119 | ...this.opts,
|
---|
| 120 | defaultTag: this.defaultTag,
|
---|
| 121 | before: this.before,
|
---|
| 122 | }) /* XXX add ETARGET and E403 revalidation of cached packuments here */)
|
---|
| 123 | .then(mani => {
|
---|
| 124 | // add _resolved and _integrity from dist object
|
---|
| 125 | const { dist } = mani
|
---|
| 126 | if (dist) {
|
---|
| 127 | this.resolved = mani._resolved = dist.tarball
|
---|
| 128 | mani._from = this.from
|
---|
| 129 | const distIntegrity = dist.integrity ? ssri.parse(dist.integrity)
|
---|
| 130 | : dist.shasum ? ssri.fromHex(dist.shasum, 'sha1', {...this.opts})
|
---|
| 131 | : null
|
---|
| 132 | if (distIntegrity) {
|
---|
| 133 | if (!this.integrity)
|
---|
| 134 | this.integrity = distIntegrity
|
---|
| 135 | else if (!this.integrity.match(distIntegrity)) {
|
---|
| 136 | // only bork if they have algos in common.
|
---|
| 137 | // otherwise we end up breaking if we have saved a sha512
|
---|
| 138 | // previously for the tarball, but the manifest only
|
---|
| 139 | // provides a sha1, which is possible for older publishes.
|
---|
| 140 | // Otherwise, this is almost certainly a case of holding it
|
---|
| 141 | // wrong, and will result in weird or insecure behavior
|
---|
| 142 | // later on when building package tree.
|
---|
| 143 | for (const algo of Object.keys(this.integrity)) {
|
---|
| 144 | if (distIntegrity[algo]) {
|
---|
| 145 | throw Object.assign(new Error(
|
---|
| 146 | `Integrity checksum failed when using ${algo}: `+
|
---|
| 147 | `wanted ${this.integrity} but got ${distIntegrity}.`
|
---|
| 148 | ), { code: 'EINTEGRITY' })
|
---|
| 149 | }
|
---|
| 150 | }
|
---|
| 151 | // made it this far, the integrity is worthwhile. accept it.
|
---|
| 152 | // the setter here will take care of merging it into what we
|
---|
| 153 | // already had.
|
---|
| 154 | this.integrity = distIntegrity
|
---|
| 155 | }
|
---|
| 156 | }
|
---|
| 157 | }
|
---|
| 158 | if (this.integrity)
|
---|
| 159 | mani._integrity = String(this.integrity)
|
---|
| 160 | this.package = rpj.normalize(mani)
|
---|
| 161 | return this.package
|
---|
| 162 | })
|
---|
| 163 | }
|
---|
| 164 |
|
---|
| 165 | [_tarballFromResolved] () {
|
---|
| 166 | // we use a RemoteFetcher to get the actual tarball stream
|
---|
| 167 | return new RemoteFetcher(this.resolved, {
|
---|
| 168 | ...this.opts,
|
---|
| 169 | resolved: this.resolved,
|
---|
| 170 | pkgid: `registry:${this.spec.name}@${this.resolved}`,
|
---|
| 171 | })[_tarballFromResolved]()
|
---|
| 172 | }
|
---|
| 173 |
|
---|
| 174 | get types () {
|
---|
| 175 | return [
|
---|
| 176 | 'tag',
|
---|
| 177 | 'version',
|
---|
| 178 | 'range',
|
---|
| 179 | ]
|
---|
| 180 | }
|
---|
| 181 | }
|
---|
| 182 | module.exports = RegistryFetcher
|
---|