Skip to content

Commit f86a019

Browse files
author
Michael Smith
committed
deps: @npmcli/metavuln-calculator@10.0.0
1 parent 4d234b2 commit f86a019

457 files changed

Lines changed: 76187 additions & 38 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
'use strict'
2+
3+
const net = require('net')
4+
const tls = require('tls')
5+
const { once } = require('events')
6+
const timers = require('timers/promises')
7+
const { normalizeOptions, cacheOptions } = require('./options')
8+
const { getProxy, getProxyAgent, proxyCache } = require('./proxy.js')
9+
const Errors = require('./errors.js')
10+
const { Agent: AgentBase } = require('agent-base')
11+
12+
module.exports = class Agent extends AgentBase {
13+
#options
14+
#timeouts
15+
#proxy
16+
#noProxy
17+
#ProxyAgent
18+
19+
constructor (options = {}) {
20+
const { timeouts, proxy, noProxy, ...normalizedOptions } = normalizeOptions(options)
21+
22+
super(normalizedOptions)
23+
24+
this.#options = normalizedOptions
25+
this.#timeouts = timeouts
26+
27+
if (proxy) {
28+
this.#proxy = new URL(proxy)
29+
this.#noProxy = noProxy
30+
this.#ProxyAgent = getProxyAgent(proxy)
31+
}
32+
}
33+
34+
get proxy () {
35+
return this.#proxy ? { url: this.#proxy } : {}
36+
}
37+
38+
#getProxy (options) {
39+
if (!this.#proxy) {
40+
return
41+
}
42+
43+
const proxy = getProxy(`${options.protocol}//${options.host}:${options.port}`, {
44+
proxy: this.#proxy,
45+
noProxy: this.#noProxy,
46+
})
47+
48+
if (!proxy) {
49+
return
50+
}
51+
52+
const cacheKey = cacheOptions({
53+
...options,
54+
...this.#options,
55+
timeouts: this.#timeouts,
56+
proxy,
57+
})
58+
59+
if (proxyCache.has(cacheKey)) {
60+
return proxyCache.get(cacheKey)
61+
}
62+
63+
let ProxyAgent = this.#ProxyAgent
64+
if (Array.isArray(ProxyAgent)) {
65+
ProxyAgent = this.isSecureEndpoint(options) ? ProxyAgent[1] : ProxyAgent[0]
66+
}
67+
68+
const proxyAgent = new ProxyAgent(proxy, {
69+
...this.#options,
70+
socketOptions: { family: this.#options.family },
71+
})
72+
proxyCache.set(cacheKey, proxyAgent)
73+
74+
return proxyAgent
75+
}
76+
77+
// takes an array of promises and races them against the connection timeout
78+
// which will throw the necessary error if it is hit. This will return the
79+
// result of the promise race.
80+
async #timeoutConnection ({ promises, options, timeout }, ac = new AbortController()) {
81+
if (timeout) {
82+
const connectionTimeout = timers.setTimeout(timeout, null, { signal: ac.signal })
83+
.then(() => {
84+
throw new Errors.ConnectionTimeoutError(`${options.host}:${options.port}`)
85+
}).catch((err) => {
86+
if (err.name === 'AbortError') {
87+
return
88+
}
89+
throw err
90+
})
91+
promises.push(connectionTimeout)
92+
}
93+
94+
let result
95+
try {
96+
result = await Promise.race(promises)
97+
ac.abort()
98+
} catch (err) {
99+
ac.abort()
100+
throw err
101+
}
102+
return result
103+
}
104+
105+
async connect (request, options) {
106+
// if the connection does not have its own lookup function
107+
// set, then use the one from our options
108+
options.lookup ??= this.#options.lookup
109+
110+
let socket
111+
let timeout = this.#timeouts.connection
112+
const isSecureEndpoint = this.isSecureEndpoint(options)
113+
114+
const proxy = this.#getProxy(options)
115+
if (proxy) {
116+
// some of the proxies will wait for the socket to fully connect before
117+
// returning so we have to await this while also racing it against the
118+
// connection timeout.
119+
const start = Date.now()
120+
socket = await this.#timeoutConnection({
121+
options,
122+
timeout,
123+
promises: [proxy.connect(request, options)],
124+
})
125+
// see how much time proxy.connect took and subtract it from
126+
// the timeout
127+
if (timeout) {
128+
timeout = timeout - (Date.now() - start)
129+
}
130+
} else {
131+
socket = (isSecureEndpoint ? tls : net).connect(options)
132+
}
133+
134+
socket.setKeepAlive(this.keepAlive, this.keepAliveMsecs)
135+
socket.setNoDelay(this.keepAlive)
136+
137+
const abortController = new AbortController()
138+
const { signal } = abortController
139+
140+
const connectPromise = socket[isSecureEndpoint ? 'secureConnecting' : 'connecting']
141+
? once(socket, isSecureEndpoint ? 'secureConnect' : 'connect', { signal })
142+
: Promise.resolve()
143+
144+
await this.#timeoutConnection({
145+
options,
146+
timeout,
147+
promises: [
148+
connectPromise,
149+
once(socket, 'error', { signal }).then((err) => {
150+
throw err[0]
151+
}),
152+
],
153+
}, abortController)
154+
155+
if (this.#timeouts.idle) {
156+
socket.setTimeout(this.#timeouts.idle, () => {
157+
socket.destroy(new Errors.IdleTimeoutError(`${options.host}:${options.port}`))
158+
})
159+
}
160+
161+
return socket
162+
}
163+
164+
addRequest (request, options) {
165+
const proxy = this.#getProxy(options)
166+
// it would be better to call proxy.addRequest here but this causes the
167+
// http-proxy-agent to call its super.addRequest which causes the request
168+
// to be added to the agent twice. since we only support 3 agents
169+
// currently (see the required agents in proxy.js) we have manually
170+
// checked that the only public methods we need to call are called in the
171+
// next block. this could change in the future and presumably we would get
172+
// failing tests until we have properly called the necessary methods on
173+
// each of our proxy agents
174+
if (proxy?.setRequestProps) {
175+
proxy.setRequestProps(request, options)
176+
}
177+
178+
request.setHeader('connection', this.keepAlive ? 'keep-alive' : 'close')
179+
180+
if (this.#timeouts.response) {
181+
let responseTimeout
182+
request.once('finish', () => {
183+
setTimeout(() => {
184+
request.destroy(new Errors.ResponseTimeoutError(request, this.#proxy))
185+
}, this.#timeouts.response)
186+
})
187+
request.once('response', () => {
188+
clearTimeout(responseTimeout)
189+
})
190+
}
191+
192+
if (this.#timeouts.transfer) {
193+
let transferTimeout
194+
request.once('response', (res) => {
195+
setTimeout(() => {
196+
res.destroy(new Errors.TransferTimeoutError(request, this.#proxy))
197+
}, this.#timeouts.transfer)
198+
res.once('close', () => {
199+
clearTimeout(transferTimeout)
200+
})
201+
})
202+
}
203+
204+
return super.addRequest(request, options)
205+
}
206+
207+
// When connect() rejects, agent-base removes only its placeholder socket, so Node never drains this.requests[name] and requests queued past maxSockets hang forever.
208+
// On a failure we dispatch the next queued request ourselves.
209+
// See npm/cli#9386 and TooTallNate/proxy-agents#427.
210+
createSocket (req, options, cb) {
211+
super.createSocket(req, options, (err, socket) => {
212+
if (err) {
213+
this.#drainPendingRequests(req, options)
214+
}
215+
cb(err, socket)
216+
})
217+
}
218+
219+
// Dispatch the next request queued behind maxSockets, reusing the slot the failed connection freed.
220+
#drainPendingRequests (failedReq, options) {
221+
const name = this.getName(options)
222+
const queue = this.requests[name]
223+
if (!queue || queue.length === 0) {
224+
return
225+
}
226+
227+
// Node's removeSocket() picks a queued request without shifting it off, so drop the failed one to avoid dispatching it twice.
228+
const failedIndex = queue.indexOf(failedReq)
229+
if (failedIndex !== -1) {
230+
queue.splice(failedIndex, 1)
231+
}
232+
if (queue.length === 0) {
233+
delete this.requests[name]
234+
return
235+
}
236+
237+
// Safety belt: only dispatch if a socket slot is genuinely free.
238+
const socketCount = this.sockets[name] ? this.sockets[name].length : 0
239+
if (socketCount >= this.maxSockets || this.totalSocketCount >= this.maxTotalSockets) {
240+
return
241+
}
242+
243+
const nextReq = queue.shift()
244+
if (queue.length === 0) {
245+
delete this.requests[name]
246+
}
247+
248+
// All queued requests share this origin, so the failed request's options suit the next one.
249+
// createSocket() recurses here if this connection also fails, draining the whole queue.
250+
this.createSocket(nextReq, options, (err, socket) => {
251+
if (err) {
252+
nextReq.onSocket(null, err)
253+
} else {
254+
nextReq.onSocket(socket)
255+
}
256+
})
257+
}
258+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
'use strict'
2+
3+
const { LRUCache } = require('lru-cache')
4+
const dns = require('dns')
5+
6+
// this is a factory so that each request can have its own opts (i.e. ttl)
7+
// while still sharing the cache across all requests
8+
const cache = new LRUCache({ max: 50 })
9+
10+
const getOptions = ({
11+
family = 0,
12+
hints = dns.ADDRCONFIG,
13+
all = false,
14+
verbatim = undefined,
15+
ttl = 5 * 60 * 1000,
16+
lookup = dns.lookup,
17+
}) => ({
18+
// hints and lookup are returned since both are top level properties to (net|tls).connect
19+
hints,
20+
lookup: (hostname, ...args) => {
21+
const callback = args.pop() // callback is always last arg
22+
const lookupOptions = args[0] ?? {}
23+
24+
const options = {
25+
family,
26+
hints,
27+
all,
28+
verbatim,
29+
...(typeof lookupOptions === 'number' ? { family: lookupOptions } : lookupOptions),
30+
}
31+
32+
const key = JSON.stringify({ hostname, ...options })
33+
34+
if (cache.has(key)) {
35+
const cached = cache.get(key)
36+
return process.nextTick(callback, null, ...cached)
37+
}
38+
39+
lookup(hostname, options, (err, ...result) => {
40+
if (err) {
41+
return callback(err)
42+
}
43+
44+
cache.set(key, result, { ttl })
45+
return callback(null, ...result)
46+
})
47+
},
48+
})
49+
50+
module.exports = {
51+
cache,
52+
getOptions,
53+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
'use strict'
2+
3+
class InvalidProxyProtocolError extends Error {
4+
constructor (url) {
5+
super(`Invalid protocol \`${url.protocol}\` connecting to proxy \`${url.host}\``)
6+
this.code = 'EINVALIDPROXY'
7+
this.proxy = url
8+
}
9+
}
10+
11+
class ConnectionTimeoutError extends Error {
12+
constructor (host) {
13+
super(`Timeout connecting to host \`${host}\``)
14+
this.code = 'ECONNECTIONTIMEOUT'
15+
this.host = host
16+
}
17+
}
18+
19+
class IdleTimeoutError extends Error {
20+
constructor (host) {
21+
super(`Idle timeout reached for host \`${host}\``)
22+
this.code = 'EIDLETIMEOUT'
23+
this.host = host
24+
}
25+
}
26+
27+
class ResponseTimeoutError extends Error {
28+
constructor (request, proxy) {
29+
let msg = 'Response timeout '
30+
if (proxy) {
31+
msg += `from proxy \`${proxy.host}\` `
32+
}
33+
msg += `connecting to host \`${request.host}\``
34+
super(msg)
35+
this.code = 'ERESPONSETIMEOUT'
36+
this.proxy = proxy
37+
this.request = request
38+
}
39+
}
40+
41+
class TransferTimeoutError extends Error {
42+
constructor (request, proxy) {
43+
let msg = 'Transfer timeout '
44+
if (proxy) {
45+
msg += `from proxy \`${proxy.host}\` `
46+
}
47+
msg += `for \`${request.host}\``
48+
super(msg)
49+
this.code = 'ETRANSFERTIMEOUT'
50+
this.proxy = proxy
51+
this.request = request
52+
}
53+
}
54+
55+
module.exports = {
56+
InvalidProxyProtocolError,
57+
ConnectionTimeoutError,
58+
IdleTimeoutError,
59+
ResponseTimeoutError,
60+
TransferTimeoutError,
61+
}

0 commit comments

Comments
 (0)