• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// this is the public class that is used by consumers.
2// the Advisory class handles all the calculation, and this
3// class handles all the IO with the registry and cache.
4const pacote = require('pacote')
5const cacache = require('cacache')
6const Advisory = require('./advisory.js')
7const { homedir } = require('os')
8const jsonParse = require('json-parse-even-better-errors')
9
10const _packument = Symbol('packument')
11const _cachePut = Symbol('cachePut')
12const _cacheGet = Symbol('cacheGet')
13const _cacheData = Symbol('cacheData')
14const _packuments = Symbol('packuments')
15const _cache = Symbol('cache')
16const _options = Symbol('options')
17const _advisories = Symbol('advisories')
18const _calculate = Symbol('calculate')
19
20class Calculator {
21  constructor (options = {}) {
22    this[_options] = { ...options }
23    this[_cache] = this[_options].cache || (homedir() + '/.npm/_cacache')
24    this[_options].cache = this[_cache]
25    this[_packuments] = new Map()
26    this[_cacheData] = new Map()
27    this[_advisories] = new Map()
28  }
29
30  get cache () {
31    return this[_cache]
32  }
33
34  get options () {
35    return { ...this[_options] }
36  }
37
38  async calculate (name, source) {
39    const k = `security-advisory:${name}:${source.id}`
40    if (this[_advisories].has(k)) {
41      return this[_advisories].get(k)
42    }
43
44    const p = this[_calculate](name, source)
45    this[_advisories].set(k, p)
46    return p
47  }
48
49  async [_calculate] (name, source) {
50    const k = `security-advisory:${name}:${source.id}`
51    const t = `metavuln:calculate:${k}`
52    process.emit('time', t)
53    const advisory = new Advisory(name, source, this[_options])
54    // load packument and cached advisory
55    const [cached, packument] = await Promise.all([
56      this[_cacheGet](advisory),
57      this[_packument](name),
58    ])
59    process.emit('time', `metavuln:load:${k}`)
60    advisory.load(cached, packument)
61    process.emit('timeEnd', `metavuln:load:${k}`)
62    if (advisory.updated) {
63      await this[_cachePut](advisory)
64    }
65    this[_advisories].set(k, advisory)
66    process.emit('timeEnd', t)
67    return advisory
68  }
69
70  async [_cachePut] (advisory) {
71    const { name, id } = advisory
72    const key = `security-advisory:${name}:${id}`
73    process.emit('time', `metavuln:cache:put:${key}`)
74    const data = JSON.stringify(advisory)
75    const options = { ...this[_options] }
76    this[_cacheData].set(key, jsonParse(data))
77    await cacache.put(this[_cache], key, data, options).catch(() => {})
78    process.emit('timeEnd', `metavuln:cache:put:${key}`)
79  }
80
81  async [_cacheGet] (advisory) {
82    const { name, id } = advisory
83    const key = `security-advisory:${name}:${id}`
84    /* istanbul ignore if - should be impossible, since we memoize the
85     * advisory object itself using the same key, just being cautious */
86    if (this[_cacheData].has(key)) {
87      return this[_cacheData].get(key)
88    }
89
90    process.emit('time', `metavuln:cache:get:${key}`)
91    const p = cacache.get(this[_cache], key, { ...this[_options] })
92      .catch(() => ({ data: '{}' }))
93      .then(({ data }) => {
94        data = jsonParse(data)
95        process.emit('timeEnd', `metavuln:cache:get:${key}`)
96        this[_cacheData].set(key, data)
97        return data
98      })
99    this[_cacheData].set(key, p)
100    return p
101  }
102
103  async [_packument] (name) {
104    if (this[_packuments].has(name)) {
105      return this[_packuments].get(name)
106    }
107
108    process.emit('time', `metavuln:packument:${name}`)
109    const p = pacote.packument(name, { ...this[_options] })
110      .catch((er) => {
111        // presumably not something from the registry.
112        // an empty packument will have an effective range of *
113        return {
114          name,
115          versions: {},
116        }
117      })
118      .then(paku => {
119        process.emit('timeEnd', `metavuln:packument:${name}`)
120        this[_packuments].set(name, paku)
121        return paku
122      })
123    this[_packuments].set(name, p)
124    return p
125  }
126}
127
128module.exports = Calculator
129