@blamejs/core 0.7.18 → 0.7.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +425 -423
- package/README.md +150 -150
- package/bin/blamejs.js +0 -0
- package/index.js +310 -308
- package/lib/api-key.js +660 -660
- package/lib/api-snapshot.js +338 -338
- package/lib/app-shutdown.js +385 -385
- package/lib/app.js +365 -365
- package/lib/archive.js +250 -250
- package/lib/atomic-file.js +544 -544
- package/lib/audit-chain.js +177 -177
- package/lib/audit-sign.js +344 -344
- package/lib/audit-tools.js +677 -677
- package/lib/audit.js +766 -766
- package/lib/auth/jwt-external.js +365 -0
- package/lib/auth/jwt.js +337 -311
- package/lib/auth/lockout.js +436 -436
- package/lib/auth/oauth.js +721 -721
- package/lib/auth/passkey.js +181 -181
- package/lib/auth/password.js +628 -594
- package/lib/backup/bundle.js +217 -217
- package/lib/backup/crypto.js +176 -176
- package/lib/backup/index.js +515 -515
- package/lib/backup/manifest.js +282 -282
- package/lib/break-glass.js +1338 -1338
- package/lib/bundler.js +441 -441
- package/lib/cache-redis.js +256 -256
- package/lib/cache.js +1206 -1206
- package/lib/canonical-json.js +115 -115
- package/lib/chain-writer.js +234 -234
- package/lib/cli-helpers.js +206 -206
- package/lib/cli.js +2334 -2334
- package/lib/cluster-provider-db.js +317 -317
- package/lib/cluster-storage.js +226 -226
- package/lib/cluster.js +703 -703
- package/lib/config-drift.js +301 -301
- package/lib/consent.js +222 -222
- package/lib/constants.js +191 -191
- package/lib/cookies.js +315 -315
- package/lib/credential-hash.js +322 -322
- package/lib/crypto.js +266 -266
- package/lib/csv.js +275 -275
- package/lib/db-declare-row-policy.js +267 -267
- package/lib/db-declare-view.js +420 -420
- package/lib/db-query.js +406 -406
- package/lib/db-schema.js +319 -319
- package/lib/db.js +1288 -1288
- package/lib/deprecate.js +222 -222
- package/lib/dev.js +335 -335
- package/lib/dual-control.js +473 -473
- package/lib/error-page.js +420 -420
- package/lib/external-db-migrate.js +441 -441
- package/lib/external-db.js +1061 -1061
- package/lib/file-type.js +273 -273
- package/lib/forms.js +422 -422
- package/lib/framework-error.js +293 -293
- package/lib/framework-schema.js +717 -717
- package/lib/handlers.js +350 -350
- package/lib/http-client-cookie-jar.js +508 -508
- package/lib/http-client.js +1195 -1195
- package/lib/i18n.js +878 -878
- package/lib/jobs.js +185 -185
- package/lib/log-stream-cloudwatch.js +369 -369
- package/lib/log-stream-local.js +146 -146
- package/lib/log-stream-otlp-grpc.js +410 -410
- package/lib/log-stream-otlp.js +286 -286
- package/lib/log-stream-syslog.js +302 -302
- package/lib/log-stream-webhook.js +199 -199
- package/lib/log-stream.js +330 -330
- package/lib/log.js +500 -500
- package/lib/mail-bounce.js +528 -528
- package/lib/mail-dkim.js +369 -369
- package/lib/mail.js +981 -981
- package/lib/metrics.js +683 -683
- package/lib/middleware/api-encrypt.js +936 -936
- package/lib/middleware/attach-user.js +157 -157
- package/lib/middleware/bearer-auth.js +152 -0
- package/lib/middleware/body-parser.js +1170 -1170
- package/lib/middleware/bot-guard.js +178 -178
- package/lib/middleware/compression.js +452 -452
- package/lib/middleware/cors.js +314 -314
- package/lib/middleware/csp-nonce.js +348 -348
- package/lib/middleware/csrf-protect.js +316 -316
- package/lib/middleware/db-role-for.js +264 -264
- package/lib/middleware/health.js +392 -392
- package/lib/middleware/index.js +82 -79
- package/lib/middleware/rate-limit.js +358 -358
- package/lib/middleware/request-id.js +61 -61
- package/lib/middleware/request-log.js +168 -168
- package/lib/middleware/require-auth.js +104 -104
- package/lib/middleware/security-headers.js +116 -116
- package/lib/middleware/sse.js +166 -166
- package/lib/migrations.js +383 -383
- package/lib/mtls-ca.js +518 -518
- package/lib/mtls-engine-default.js +481 -481
- package/lib/network-dns.js +632 -632
- package/lib/network-heartbeat.js +290 -290
- package/lib/network-nts.js +574 -574
- package/lib/network-proxy.js +265 -265
- package/lib/network-tls.js +328 -328
- package/lib/network.js +233 -233
- package/lib/notify.js +612 -612
- package/lib/ntp-check.js +229 -229
- package/lib/numeric-bounds.js +111 -111
- package/lib/object-store/azure-blob-bucket-ops.js +349 -349
- package/lib/object-store/azure-blob.js +488 -488
- package/lib/object-store/gcs-bucket-ops.js +351 -351
- package/lib/object-store/gcs.js +519 -519
- package/lib/object-store/http-put.js +153 -153
- package/lib/object-store/index.js +197 -197
- package/lib/object-store/sigv4-bucket-ops.js +1092 -1092
- package/lib/object-store/sigv4.js +903 -903
- package/lib/observability.js +151 -151
- package/lib/otel-export.js +269 -269
- package/lib/pagination.js +464 -464
- package/lib/parsers/index.js +80 -80
- package/lib/parsers/safe-env.js +642 -642
- package/lib/parsers/safe-ini.js +292 -292
- package/lib/parsers/safe-toml.js +784 -784
- package/lib/parsers/safe-xml.js +390 -390
- package/lib/parsers/safe-yaml.js +1015 -1015
- package/lib/permissions.js +708 -708
- package/lib/pqc-agent.js +87 -87
- package/lib/pqc-gate.js +279 -279
- package/lib/protobuf-encoder.js +190 -190
- package/lib/protocol-dispatcher.js +161 -161
- package/lib/pubsub-redis.js +167 -167
- package/lib/pubsub.js +429 -429
- package/lib/queue-local.js +476 -476
- package/lib/queue-redis.js +745 -745
- package/lib/queue-sqs.js +319 -319
- package/lib/queue.js +695 -695
- package/lib/redis-client.js +519 -519
- package/lib/request-helpers.js +340 -340
- package/lib/restore-bundle.js +237 -237
- package/lib/restore-rollback.js +259 -259
- package/lib/restore.js +409 -409
- package/lib/retry.js +376 -376
- package/lib/router.js +748 -748
- package/lib/safe-async.js +735 -735
- package/lib/safe-buffer.js +237 -237
- package/lib/safe-json.js +541 -541
- package/lib/safe-schema.js +1266 -1266
- package/lib/safe-url.js +159 -159
- package/lib/scheduler.js +706 -706
- package/lib/security-assert.js +373 -373
- package/lib/seeders.js +618 -618
- package/lib/session.js +535 -478
- package/lib/slug.js +269 -269
- package/lib/ssrf-guard.js +401 -401
- package/lib/storage.js +471 -471
- package/lib/subject.js +281 -281
- package/lib/template.js +791 -791
- package/lib/testing.js +798 -798
- package/lib/time.js +310 -310
- package/lib/totp.js +302 -302
- package/lib/tracing.js +494 -494
- package/lib/uuid.js +132 -132
- package/lib/validate-opts.js +340 -340
- package/lib/vault/index.js +308 -308
- package/lib/vault/rotate.js +784 -784
- package/lib/vault/wrap.js +296 -296
- package/lib/vendor/noble-ciphers.cjs +9 -9
- package/lib/webhook.js +595 -595
- package/lib/websocket.js +1048 -1048
- package/package.json +77 -77
- package/sbom.cyclonedx.json +7 -7
package/lib/network-proxy.js
CHANGED
|
@@ -1,265 +1,265 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
var http = require("node:http");
|
|
4
|
-
var https = require("node:https");
|
|
5
|
-
var net = require("node:net");
|
|
6
|
-
var tls = require("node:tls");
|
|
7
|
-
|
|
8
|
-
var C = require("./constants");
|
|
9
|
-
var lazyRequire = require("./lazy-require");
|
|
10
|
-
var safeUrl = require("./safe-url");
|
|
11
|
-
var validateOpts = require("./validate-opts");
|
|
12
|
-
var { defineClass } = require("./framework-error");
|
|
13
|
-
|
|
14
|
-
var ProxyError = defineClass("ProxyError", { alwaysPermanent: false });
|
|
15
|
-
|
|
16
|
-
// Protocol-fixed byte counts — passthrough through C.BYTES so the codebase
|
|
17
|
-
// has a single source of truth for "what shape is this number".
|
|
18
|
-
var IPV4_PREFIX_MAX_BITS = C.BYTES.bytes(32); // RFC 791 §3.1 IPv4 address bit-width
|
|
19
|
-
var DEFAULT_HTTPS_PORT = 443; // RFC 9110 §4.2.2
|
|
20
|
-
var DEFAULT_HTTP_PORT = C.BYTES.bytes(80); // RFC 9110 §4.2.1
|
|
21
|
-
|
|
22
|
-
var observability = lazyRequire(function () { return require("./observability"); });
|
|
23
|
-
|
|
24
|
-
var STATE = {
|
|
25
|
-
http: null,
|
|
26
|
-
https: null,
|
|
27
|
-
noProxy: [],
|
|
28
|
-
auth: null,
|
|
29
|
-
agentCache: new Map(),
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
function _parseAuth(spec) {
|
|
33
|
-
if (!spec) return null;
|
|
34
|
-
var idx = spec.indexOf(":");
|
|
35
|
-
if (idx === -1) return null;
|
|
36
|
-
return { user: spec.slice(0, idx), pass: spec.slice(idx + 1) };
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function _parseProxyUrl(value) {
|
|
40
|
-
if (!value) return null;
|
|
41
|
-
try {
|
|
42
|
-
return safeUrl.parse(value, {
|
|
43
|
-
allowedProtocols: safeUrl.ALLOW_HTTP_ALL,
|
|
44
|
-
allowUserinfo: true, // proxy auth is conventionally placed in the URL
|
|
45
|
-
errorClass: ProxyError,
|
|
46
|
-
});
|
|
47
|
-
} catch (e) {
|
|
48
|
-
if (e && e.isSafeUrlError && e.code === "safe-url/protocol-disallowed") {
|
|
49
|
-
throw new ProxyError("proxy/bad-protocol", "proxy URL must be http:// or https://, got " + value);
|
|
50
|
-
}
|
|
51
|
-
throw new ProxyError("proxy/bad-url", "invalid proxy URL: " + value);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function _parseNoProxy(spec) {
|
|
56
|
-
if (!spec) return [];
|
|
57
|
-
return String(spec).split(",").map(function (s) { return s.trim(); }).filter(Boolean);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function set(opts) {
|
|
61
|
-
opts = opts || {};
|
|
62
|
-
validateOpts(opts, ["http", "https", "no", "auth"], "proxy.set");
|
|
63
|
-
if (opts.http !== undefined) STATE.http = opts.http ? _parseProxyUrl(opts.http) : null;
|
|
64
|
-
if (opts.https !== undefined) STATE.https = opts.https ? _parseProxyUrl(opts.https) : null;
|
|
65
|
-
if (opts.no !== undefined) STATE.noProxy = _parseNoProxy(opts.no);
|
|
66
|
-
if (opts.auth !== undefined) STATE.auth = opts.auth ? _parseAuth(opts.auth) : null;
|
|
67
|
-
STATE.agentCache.clear();
|
|
68
|
-
_emitObs("network.proxy.set", {
|
|
69
|
-
httpSet: !!STATE.http,
|
|
70
|
-
httpsSet: !!STATE.https,
|
|
71
|
-
noProxyCount: STATE.noProxy.length,
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function fromEnv(envObj) {
|
|
76
|
-
var env = envObj || process.env;
|
|
77
|
-
var httpProxy = env.HTTP_PROXY || env.http_proxy;
|
|
78
|
-
var httpsProxy = env.HTTPS_PROXY || env.https_proxy;
|
|
79
|
-
var noProxy = env.NO_PROXY || env.no_proxy;
|
|
80
|
-
var allProxy = env.ALL_PROXY || env.all_proxy;
|
|
81
|
-
var authEnv = env.BLAMEJS_PROXY_AUTH;
|
|
82
|
-
var changed = false;
|
|
83
|
-
if (httpProxy) { STATE.http = _parseProxyUrl(httpProxy); changed = true; }
|
|
84
|
-
else if (allProxy) { STATE.http = _parseProxyUrl(allProxy); changed = true; }
|
|
85
|
-
if (httpsProxy) { STATE.https = _parseProxyUrl(httpsProxy); changed = true; }
|
|
86
|
-
else if (allProxy) { STATE.https = _parseProxyUrl(allProxy); changed = true; }
|
|
87
|
-
if (noProxy) { STATE.noProxy = _parseNoProxy(noProxy); changed = true; }
|
|
88
|
-
if (authEnv) { STATE.auth = _parseAuth(authEnv); changed = true; }
|
|
89
|
-
if (changed) {
|
|
90
|
-
STATE.agentCache.clear();
|
|
91
|
-
_emitObs("network.proxy.from_env", {
|
|
92
|
-
httpSet: !!STATE.http,
|
|
93
|
-
httpsSet: !!STATE.https,
|
|
94
|
-
noProxyCount: STATE.noProxy.length,
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
return changed;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function _ipv4ToInt(ip) {
|
|
101
|
-
var p = ip.split(".");
|
|
102
|
-
return ((p[0] | 0) << 24 >>> 0) + ((p[1] | 0) << 16) + ((p[2] | 0) << 8) + (p[3] | 0);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function _matchCidr(cidr, host) {
|
|
106
|
-
var slash = cidr.indexOf("/");
|
|
107
|
-
if (slash === -1) return false;
|
|
108
|
-
var network = cidr.slice(0, slash);
|
|
109
|
-
var prefix = parseInt(cidr.slice(slash + 1), 10);
|
|
110
|
-
if (net.isIP(host) !== 4 || net.isIP(network) !== 4) return false;
|
|
111
|
-
if (!isFinite(prefix) || prefix < 0 || prefix > IPV4_PREFIX_MAX_BITS) return false;
|
|
112
|
-
if (prefix === 0) return true;
|
|
113
|
-
var mask = (0xffffffff << (IPV4_PREFIX_MAX_BITS - prefix)) >>> 0;
|
|
114
|
-
return (_ipv4ToInt(host) & mask) === (_ipv4ToInt(network) & mask);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function _parseTarget(targetUrl) {
|
|
118
|
-
if (targetUrl instanceof URL) return targetUrl;
|
|
119
|
-
return safeUrl.parse(String(targetUrl), {
|
|
120
|
-
allowedProtocols: safeUrl.ALLOW_HTTP_ALL,
|
|
121
|
-
errorClass: ProxyError,
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function shouldProxy(targetUrl) {
|
|
126
|
-
var u = _parseTarget(targetUrl);
|
|
127
|
-
var host = u.hostname;
|
|
128
|
-
var rules = STATE.noProxy;
|
|
129
|
-
for (var i = 0; i < rules.length; i++) {
|
|
130
|
-
var r = rules[i];
|
|
131
|
-
if (r === "*") return false;
|
|
132
|
-
if (r.indexOf("/") !== -1 && _matchCidr(r, host)) return false;
|
|
133
|
-
var bare = r.charAt(0) === "." ? r.slice(1) : r;
|
|
134
|
-
if (host === bare) return false;
|
|
135
|
-
if (host.length > bare.length && host.slice(host.length - bare.length - 1) === "." + bare) return false;
|
|
136
|
-
}
|
|
137
|
-
if (u.protocol === "https:") return !!STATE.https;
|
|
138
|
-
if (u.protocol === "http:") return !!STATE.http;
|
|
139
|
-
return false;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function _proxyForUrl(targetUrl) {
|
|
143
|
-
var u = _parseTarget(targetUrl);
|
|
144
|
-
if (!shouldProxy(u)) return null;
|
|
145
|
-
return u.protocol === "https:" ? STATE.https : STATE.http;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function _proxyAuthHeader(proxyUrl) {
|
|
149
|
-
var user = proxyUrl.username || (STATE.auth && STATE.auth.user) || "";
|
|
150
|
-
var pass = proxyUrl.password || (STATE.auth && STATE.auth.pass) || "";
|
|
151
|
-
if (!user && !pass) return null;
|
|
152
|
-
return "Basic " + Buffer.from(decodeURIComponent(user) + ":" + decodeURIComponent(pass), "utf8").toString("base64");
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function _connectThroughTunnel(proxyUrl, targetHost, targetPort, callback) {
|
|
156
|
-
var proxyPort = proxyUrl.port || (proxyUrl.protocol === "https:" ? DEFAULT_HTTPS_PORT : DEFAULT_HTTP_PORT);
|
|
157
|
-
var proxySocket = proxyUrl.protocol === "https:"
|
|
158
|
-
? tls.connect({
|
|
159
|
-
host: proxyUrl.hostname,
|
|
160
|
-
port: proxyPort,
|
|
161
|
-
servername: proxyUrl.hostname,
|
|
162
|
-
minVersion: "TLSv1.3",
|
|
163
|
-
ecdhCurve: C.TLS_GROUP_CURVE_STR,
|
|
164
|
-
})
|
|
165
|
-
: net.connect({ host: proxyUrl.hostname, port: proxyPort });
|
|
166
|
-
var settled = false;
|
|
167
|
-
function done(err, sock) { if (settled) return; settled = true; callback(err, sock); }
|
|
168
|
-
proxySocket.on("error", function (e) { done(e); });
|
|
169
|
-
proxySocket.on(proxyUrl.protocol === "https:" ? "secureConnect" : "connect", function () {
|
|
170
|
-
var lines = [
|
|
171
|
-
"CONNECT " + targetHost + ":" + targetPort + " HTTP/1.1",
|
|
172
|
-
"Host: " + targetHost + ":" + targetPort,
|
|
173
|
-
];
|
|
174
|
-
var auth = _proxyAuthHeader(proxyUrl);
|
|
175
|
-
if (auth) lines.push("Proxy-Authorization: " + auth);
|
|
176
|
-
lines.push("", "");
|
|
177
|
-
proxySocket.write(lines.join("\r\n"));
|
|
178
|
-
var buf = Buffer.alloc(0);
|
|
179
|
-
function onData(chunk) {
|
|
180
|
-
buf = Buffer.concat([buf, chunk]);
|
|
181
|
-
var idx = buf.indexOf("\r\n\r\n");
|
|
182
|
-
if (idx === -1) return;
|
|
183
|
-
proxySocket.removeListener("data", onData);
|
|
184
|
-
var head = buf.slice(0, idx).toString("ascii");
|
|
185
|
-
var status = head.split("\r\n")[0] || "";
|
|
186
|
-
var match = /HTTP\/1\.[01] (\d{3})/.exec(status);
|
|
187
|
-
if (!match || parseInt(match[1], 10) !== 200) {
|
|
188
|
-
done(new ProxyError("proxy/connect-failed", "proxy CONNECT to " + targetHost + ":" + targetPort + " failed: " + status));
|
|
189
|
-
try { proxySocket.destroy(); } catch (_e) { /* best-effort socket teardown */ }
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
done(null, proxySocket);
|
|
193
|
-
}
|
|
194
|
-
proxySocket.on("data", onData);
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function agentFor(targetUrl) {
|
|
199
|
-
var proxy = _proxyForUrl(targetUrl);
|
|
200
|
-
if (!proxy) return null;
|
|
201
|
-
var u = _parseTarget(targetUrl);
|
|
202
|
-
var key = proxy.toString() + "|" + u.protocol;
|
|
203
|
-
if (STATE.agentCache.has(key)) return STATE.agentCache.get(key);
|
|
204
|
-
|
|
205
|
-
var agent;
|
|
206
|
-
if (u.protocol === "https:") {
|
|
207
|
-
agent = new https.Agent({
|
|
208
|
-
keepAlive: true,
|
|
209
|
-
minVersion: "TLSv1.3",
|
|
210
|
-
ecdhCurve: C.TLS_GROUP_CURVE_STR,
|
|
211
|
-
});
|
|
212
|
-
agent.createConnection = function (options, cb) {
|
|
213
|
-
_connectThroughTunnel(proxy, options.host, options.port, function (err, tunnel) {
|
|
214
|
-
if (err) return cb(err);
|
|
215
|
-
var secure = tls.connect({
|
|
216
|
-
socket: tunnel,
|
|
217
|
-
servername: options.servername || options.host,
|
|
218
|
-
minVersion: "TLSv1.3",
|
|
219
|
-
ecdhCurve: C.TLS_GROUP_CURVE_STR,
|
|
220
|
-
ALPNProtocols: options.ALPNProtocols,
|
|
221
|
-
}, function () { cb(null, secure); });
|
|
222
|
-
secure.on("error", function (e) { cb(e); });
|
|
223
|
-
});
|
|
224
|
-
};
|
|
225
|
-
} else {
|
|
226
|
-
agent = new http.Agent({ keepAlive: true });
|
|
227
|
-
agent.createConnection = function (options, cb) {
|
|
228
|
-
_connectThroughTunnel(proxy, options.host, options.port, function (err, tunnel) {
|
|
229
|
-
if (err) return cb(err);
|
|
230
|
-
cb(null, tunnel);
|
|
231
|
-
});
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
STATE.agentCache.set(key, agent);
|
|
235
|
-
_emitObs("network.proxy.agent.created", { protocol: u.protocol });
|
|
236
|
-
return agent;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
function snapshot() {
|
|
240
|
-
return {
|
|
241
|
-
http: STATE.http ? STATE.http.toString() : null,
|
|
242
|
-
https: STATE.https ? STATE.https.toString() : null,
|
|
243
|
-
noProxy: STATE.noProxy.slice(),
|
|
244
|
-
authSet: !!STATE.auth,
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
function _emitObs(name, fields) {
|
|
249
|
-
try { observability().emit(name, fields || {}); } catch (_e) { /* obs best-effort */ }
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
function _resetForTest() {
|
|
253
|
-
STATE.http = null; STATE.https = null; STATE.noProxy = []; STATE.auth = null;
|
|
254
|
-
STATE.agentCache.clear();
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
module.exports = {
|
|
258
|
-
set: set,
|
|
259
|
-
fromEnv: fromEnv,
|
|
260
|
-
shouldProxy: shouldProxy,
|
|
261
|
-
agentFor: agentFor,
|
|
262
|
-
snapshot: snapshot,
|
|
263
|
-
ProxyError: ProxyError,
|
|
264
|
-
_resetForTest: _resetForTest,
|
|
265
|
-
};
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var http = require("node:http");
|
|
4
|
+
var https = require("node:https");
|
|
5
|
+
var net = require("node:net");
|
|
6
|
+
var tls = require("node:tls");
|
|
7
|
+
|
|
8
|
+
var C = require("./constants");
|
|
9
|
+
var lazyRequire = require("./lazy-require");
|
|
10
|
+
var safeUrl = require("./safe-url");
|
|
11
|
+
var validateOpts = require("./validate-opts");
|
|
12
|
+
var { defineClass } = require("./framework-error");
|
|
13
|
+
|
|
14
|
+
var ProxyError = defineClass("ProxyError", { alwaysPermanent: false });
|
|
15
|
+
|
|
16
|
+
// Protocol-fixed byte counts — passthrough through C.BYTES so the codebase
|
|
17
|
+
// has a single source of truth for "what shape is this number".
|
|
18
|
+
var IPV4_PREFIX_MAX_BITS = C.BYTES.bytes(32); // RFC 791 §3.1 IPv4 address bit-width
|
|
19
|
+
var DEFAULT_HTTPS_PORT = 443; // RFC 9110 §4.2.2
|
|
20
|
+
var DEFAULT_HTTP_PORT = C.BYTES.bytes(80); // RFC 9110 §4.2.1
|
|
21
|
+
|
|
22
|
+
var observability = lazyRequire(function () { return require("./observability"); });
|
|
23
|
+
|
|
24
|
+
var STATE = {
|
|
25
|
+
http: null,
|
|
26
|
+
https: null,
|
|
27
|
+
noProxy: [],
|
|
28
|
+
auth: null,
|
|
29
|
+
agentCache: new Map(),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function _parseAuth(spec) {
|
|
33
|
+
if (!spec) return null;
|
|
34
|
+
var idx = spec.indexOf(":");
|
|
35
|
+
if (idx === -1) return null;
|
|
36
|
+
return { user: spec.slice(0, idx), pass: spec.slice(idx + 1) };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function _parseProxyUrl(value) {
|
|
40
|
+
if (!value) return null;
|
|
41
|
+
try {
|
|
42
|
+
return safeUrl.parse(value, {
|
|
43
|
+
allowedProtocols: safeUrl.ALLOW_HTTP_ALL,
|
|
44
|
+
allowUserinfo: true, // proxy auth is conventionally placed in the URL
|
|
45
|
+
errorClass: ProxyError,
|
|
46
|
+
});
|
|
47
|
+
} catch (e) {
|
|
48
|
+
if (e && e.isSafeUrlError && e.code === "safe-url/protocol-disallowed") {
|
|
49
|
+
throw new ProxyError("proxy/bad-protocol", "proxy URL must be http:// or https://, got " + value);
|
|
50
|
+
}
|
|
51
|
+
throw new ProxyError("proxy/bad-url", "invalid proxy URL: " + value);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function _parseNoProxy(spec) {
|
|
56
|
+
if (!spec) return [];
|
|
57
|
+
return String(spec).split(",").map(function (s) { return s.trim(); }).filter(Boolean);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function set(opts) {
|
|
61
|
+
opts = opts || {};
|
|
62
|
+
validateOpts(opts, ["http", "https", "no", "auth"], "proxy.set");
|
|
63
|
+
if (opts.http !== undefined) STATE.http = opts.http ? _parseProxyUrl(opts.http) : null;
|
|
64
|
+
if (opts.https !== undefined) STATE.https = opts.https ? _parseProxyUrl(opts.https) : null;
|
|
65
|
+
if (opts.no !== undefined) STATE.noProxy = _parseNoProxy(opts.no);
|
|
66
|
+
if (opts.auth !== undefined) STATE.auth = opts.auth ? _parseAuth(opts.auth) : null;
|
|
67
|
+
STATE.agentCache.clear();
|
|
68
|
+
_emitObs("network.proxy.set", {
|
|
69
|
+
httpSet: !!STATE.http,
|
|
70
|
+
httpsSet: !!STATE.https,
|
|
71
|
+
noProxyCount: STATE.noProxy.length,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function fromEnv(envObj) {
|
|
76
|
+
var env = envObj || process.env;
|
|
77
|
+
var httpProxy = env.HTTP_PROXY || env.http_proxy;
|
|
78
|
+
var httpsProxy = env.HTTPS_PROXY || env.https_proxy;
|
|
79
|
+
var noProxy = env.NO_PROXY || env.no_proxy;
|
|
80
|
+
var allProxy = env.ALL_PROXY || env.all_proxy;
|
|
81
|
+
var authEnv = env.BLAMEJS_PROXY_AUTH;
|
|
82
|
+
var changed = false;
|
|
83
|
+
if (httpProxy) { STATE.http = _parseProxyUrl(httpProxy); changed = true; }
|
|
84
|
+
else if (allProxy) { STATE.http = _parseProxyUrl(allProxy); changed = true; }
|
|
85
|
+
if (httpsProxy) { STATE.https = _parseProxyUrl(httpsProxy); changed = true; }
|
|
86
|
+
else if (allProxy) { STATE.https = _parseProxyUrl(allProxy); changed = true; }
|
|
87
|
+
if (noProxy) { STATE.noProxy = _parseNoProxy(noProxy); changed = true; }
|
|
88
|
+
if (authEnv) { STATE.auth = _parseAuth(authEnv); changed = true; }
|
|
89
|
+
if (changed) {
|
|
90
|
+
STATE.agentCache.clear();
|
|
91
|
+
_emitObs("network.proxy.from_env", {
|
|
92
|
+
httpSet: !!STATE.http,
|
|
93
|
+
httpsSet: !!STATE.https,
|
|
94
|
+
noProxyCount: STATE.noProxy.length,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return changed;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function _ipv4ToInt(ip) {
|
|
101
|
+
var p = ip.split(".");
|
|
102
|
+
return ((p[0] | 0) << 24 >>> 0) + ((p[1] | 0) << 16) + ((p[2] | 0) << 8) + (p[3] | 0);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function _matchCidr(cidr, host) {
|
|
106
|
+
var slash = cidr.indexOf("/");
|
|
107
|
+
if (slash === -1) return false;
|
|
108
|
+
var network = cidr.slice(0, slash);
|
|
109
|
+
var prefix = parseInt(cidr.slice(slash + 1), 10);
|
|
110
|
+
if (net.isIP(host) !== 4 || net.isIP(network) !== 4) return false;
|
|
111
|
+
if (!isFinite(prefix) || prefix < 0 || prefix > IPV4_PREFIX_MAX_BITS) return false;
|
|
112
|
+
if (prefix === 0) return true;
|
|
113
|
+
var mask = (0xffffffff << (IPV4_PREFIX_MAX_BITS - prefix)) >>> 0;
|
|
114
|
+
return (_ipv4ToInt(host) & mask) === (_ipv4ToInt(network) & mask);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function _parseTarget(targetUrl) {
|
|
118
|
+
if (targetUrl instanceof URL) return targetUrl;
|
|
119
|
+
return safeUrl.parse(String(targetUrl), {
|
|
120
|
+
allowedProtocols: safeUrl.ALLOW_HTTP_ALL,
|
|
121
|
+
errorClass: ProxyError,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function shouldProxy(targetUrl) {
|
|
126
|
+
var u = _parseTarget(targetUrl);
|
|
127
|
+
var host = u.hostname;
|
|
128
|
+
var rules = STATE.noProxy;
|
|
129
|
+
for (var i = 0; i < rules.length; i++) {
|
|
130
|
+
var r = rules[i];
|
|
131
|
+
if (r === "*") return false;
|
|
132
|
+
if (r.indexOf("/") !== -1 && _matchCidr(r, host)) return false;
|
|
133
|
+
var bare = r.charAt(0) === "." ? r.slice(1) : r;
|
|
134
|
+
if (host === bare) return false;
|
|
135
|
+
if (host.length > bare.length && host.slice(host.length - bare.length - 1) === "." + bare) return false;
|
|
136
|
+
}
|
|
137
|
+
if (u.protocol === "https:") return !!STATE.https;
|
|
138
|
+
if (u.protocol === "http:") return !!STATE.http;
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function _proxyForUrl(targetUrl) {
|
|
143
|
+
var u = _parseTarget(targetUrl);
|
|
144
|
+
if (!shouldProxy(u)) return null;
|
|
145
|
+
return u.protocol === "https:" ? STATE.https : STATE.http;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function _proxyAuthHeader(proxyUrl) {
|
|
149
|
+
var user = proxyUrl.username || (STATE.auth && STATE.auth.user) || "";
|
|
150
|
+
var pass = proxyUrl.password || (STATE.auth && STATE.auth.pass) || "";
|
|
151
|
+
if (!user && !pass) return null;
|
|
152
|
+
return "Basic " + Buffer.from(decodeURIComponent(user) + ":" + decodeURIComponent(pass), "utf8").toString("base64");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function _connectThroughTunnel(proxyUrl, targetHost, targetPort, callback) {
|
|
156
|
+
var proxyPort = proxyUrl.port || (proxyUrl.protocol === "https:" ? DEFAULT_HTTPS_PORT : DEFAULT_HTTP_PORT);
|
|
157
|
+
var proxySocket = proxyUrl.protocol === "https:"
|
|
158
|
+
? tls.connect({
|
|
159
|
+
host: proxyUrl.hostname,
|
|
160
|
+
port: proxyPort,
|
|
161
|
+
servername: proxyUrl.hostname,
|
|
162
|
+
minVersion: "TLSv1.3",
|
|
163
|
+
ecdhCurve: C.TLS_GROUP_CURVE_STR,
|
|
164
|
+
})
|
|
165
|
+
: net.connect({ host: proxyUrl.hostname, port: proxyPort });
|
|
166
|
+
var settled = false;
|
|
167
|
+
function done(err, sock) { if (settled) return; settled = true; callback(err, sock); }
|
|
168
|
+
proxySocket.on("error", function (e) { done(e); });
|
|
169
|
+
proxySocket.on(proxyUrl.protocol === "https:" ? "secureConnect" : "connect", function () {
|
|
170
|
+
var lines = [
|
|
171
|
+
"CONNECT " + targetHost + ":" + targetPort + " HTTP/1.1",
|
|
172
|
+
"Host: " + targetHost + ":" + targetPort,
|
|
173
|
+
];
|
|
174
|
+
var auth = _proxyAuthHeader(proxyUrl);
|
|
175
|
+
if (auth) lines.push("Proxy-Authorization: " + auth);
|
|
176
|
+
lines.push("", "");
|
|
177
|
+
proxySocket.write(lines.join("\r\n"));
|
|
178
|
+
var buf = Buffer.alloc(0);
|
|
179
|
+
function onData(chunk) {
|
|
180
|
+
buf = Buffer.concat([buf, chunk]);
|
|
181
|
+
var idx = buf.indexOf("\r\n\r\n");
|
|
182
|
+
if (idx === -1) return;
|
|
183
|
+
proxySocket.removeListener("data", onData);
|
|
184
|
+
var head = buf.slice(0, idx).toString("ascii");
|
|
185
|
+
var status = head.split("\r\n")[0] || "";
|
|
186
|
+
var match = /HTTP\/1\.[01] (\d{3})/.exec(status);
|
|
187
|
+
if (!match || parseInt(match[1], 10) !== 200) {
|
|
188
|
+
done(new ProxyError("proxy/connect-failed", "proxy CONNECT to " + targetHost + ":" + targetPort + " failed: " + status));
|
|
189
|
+
try { proxySocket.destroy(); } catch (_e) { /* best-effort socket teardown */ }
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
done(null, proxySocket);
|
|
193
|
+
}
|
|
194
|
+
proxySocket.on("data", onData);
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function agentFor(targetUrl) {
|
|
199
|
+
var proxy = _proxyForUrl(targetUrl);
|
|
200
|
+
if (!proxy) return null;
|
|
201
|
+
var u = _parseTarget(targetUrl);
|
|
202
|
+
var key = proxy.toString() + "|" + u.protocol;
|
|
203
|
+
if (STATE.agentCache.has(key)) return STATE.agentCache.get(key);
|
|
204
|
+
|
|
205
|
+
var agent;
|
|
206
|
+
if (u.protocol === "https:") {
|
|
207
|
+
agent = new https.Agent({
|
|
208
|
+
keepAlive: true,
|
|
209
|
+
minVersion: "TLSv1.3",
|
|
210
|
+
ecdhCurve: C.TLS_GROUP_CURVE_STR,
|
|
211
|
+
});
|
|
212
|
+
agent.createConnection = function (options, cb) {
|
|
213
|
+
_connectThroughTunnel(proxy, options.host, options.port, function (err, tunnel) {
|
|
214
|
+
if (err) return cb(err);
|
|
215
|
+
var secure = tls.connect({
|
|
216
|
+
socket: tunnel,
|
|
217
|
+
servername: options.servername || options.host,
|
|
218
|
+
minVersion: "TLSv1.3",
|
|
219
|
+
ecdhCurve: C.TLS_GROUP_CURVE_STR,
|
|
220
|
+
ALPNProtocols: options.ALPNProtocols,
|
|
221
|
+
}, function () { cb(null, secure); });
|
|
222
|
+
secure.on("error", function (e) { cb(e); });
|
|
223
|
+
});
|
|
224
|
+
};
|
|
225
|
+
} else {
|
|
226
|
+
agent = new http.Agent({ keepAlive: true });
|
|
227
|
+
agent.createConnection = function (options, cb) {
|
|
228
|
+
_connectThroughTunnel(proxy, options.host, options.port, function (err, tunnel) {
|
|
229
|
+
if (err) return cb(err);
|
|
230
|
+
cb(null, tunnel);
|
|
231
|
+
});
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
STATE.agentCache.set(key, agent);
|
|
235
|
+
_emitObs("network.proxy.agent.created", { protocol: u.protocol });
|
|
236
|
+
return agent;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function snapshot() {
|
|
240
|
+
return {
|
|
241
|
+
http: STATE.http ? STATE.http.toString() : null,
|
|
242
|
+
https: STATE.https ? STATE.https.toString() : null,
|
|
243
|
+
noProxy: STATE.noProxy.slice(),
|
|
244
|
+
authSet: !!STATE.auth,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function _emitObs(name, fields) {
|
|
249
|
+
try { observability().emit(name, fields || {}); } catch (_e) { /* obs best-effort */ }
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function _resetForTest() {
|
|
253
|
+
STATE.http = null; STATE.https = null; STATE.noProxy = []; STATE.auth = null;
|
|
254
|
+
STATE.agentCache.clear();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
module.exports = {
|
|
258
|
+
set: set,
|
|
259
|
+
fromEnv: fromEnv,
|
|
260
|
+
shouldProxy: shouldProxy,
|
|
261
|
+
agentFor: agentFor,
|
|
262
|
+
snapshot: snapshot,
|
|
263
|
+
ProxyError: ProxyError,
|
|
264
|
+
_resetForTest: _resetForTest,
|
|
265
|
+
};
|