mirror of
https://github.com/oven-sh/bun
synced 2026-02-16 22:01:47 +00:00
Compare commits
4 Commits
claude/fix
...
claude/fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7c14e8e86 | ||
|
|
58a3c2c863 | ||
|
|
22bebfc467 | ||
|
|
1800093a64 |
@@ -1,6 +1,7 @@
|
||||
#include "root.h"
|
||||
#include "headers-handwritten.h"
|
||||
#include "NodeModuleModule.h"
|
||||
#include "BunProcess.h"
|
||||
|
||||
#include <JavaScriptCore/JSCInlines.h>
|
||||
#include <JavaScriptCore/VM.h>
|
||||
@@ -870,6 +871,13 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionSyncBuiltinESMExports,
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(jsFunctionRegister, (JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
|
||||
{
|
||||
auto& vm = globalObject->vm();
|
||||
Bun::Process::emitWarning(
|
||||
globalObject,
|
||||
jsString(vm, String("module.register() is not implemented in Bun. Loaders registered with module.register() will not be invoked. To intercept and transform modules, consider using Bun's plugin API: https://bun.sh/docs/bundler/plugins"_s)),
|
||||
jsString(vm, String("Warning"_s)),
|
||||
jsString(vm, String("BUN_UNSUPPORTED_REGISTER"_s)),
|
||||
jsUndefined());
|
||||
return JSC::JSValue::encode(JSC::jsUndefined());
|
||||
}
|
||||
|
||||
|
||||
@@ -1708,8 +1708,14 @@ pub fn parseIntoBinaryLockfile(
|
||||
};
|
||||
|
||||
if (registry_str.len == 0) {
|
||||
// Use scope-specific registry if available, otherwise fall back to default
|
||||
const registry_url = if (manager) |mgr|
|
||||
mgr.scopeForPackageName(name_str).url.href
|
||||
else
|
||||
Npm.Registry.default_url;
|
||||
|
||||
const url = try ExtractTarball.buildURL(
|
||||
Npm.Registry.default_url,
|
||||
registry_url,
|
||||
strings.StringOrTinyString.init(name.slice(string_buf.bytes.items)),
|
||||
res.value.npm.version,
|
||||
string_buf.bytes.items,
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
const { isIP, isIPv6 } = require("internal/net/isIP");
|
||||
|
||||
const { checkIsHttpToken, validateFunction, validateInteger, validateBoolean } = require("internal/validators");
|
||||
const {
|
||||
checkIsHttpToken,
|
||||
validateFunction,
|
||||
validateInteger,
|
||||
validateBoolean,
|
||||
validateString,
|
||||
} = require("internal/validators");
|
||||
const { urlToHttpOptions } = require("internal/url");
|
||||
const { isValidTLSArray } = require("internal/tls");
|
||||
const { throwOnInvalidTLSArray } = require("internal/tls");
|
||||
const { validateHeaderName } = require("node:_http_common");
|
||||
const { getTimerDuration } = require("internal/timers");
|
||||
const { ConnResetException } = require("internal/shared");
|
||||
@@ -728,53 +734,48 @@ function ClientRequest(input, options, cb) {
|
||||
throw new Error("pfx is not supported");
|
||||
}
|
||||
|
||||
if (options.rejectUnauthorized !== undefined) this._ensureTls().rejectUnauthorized = options.rejectUnauthorized;
|
||||
else {
|
||||
let agentRejectUnauthorized = agent?.options?.rejectUnauthorized;
|
||||
if (agentRejectUnauthorized !== undefined) this._ensureTls().rejectUnauthorized = agentRejectUnauthorized;
|
||||
else {
|
||||
// popular https-proxy-agent uses connectOpts
|
||||
agentRejectUnauthorized = agent?.connectOpts?.rejectUnauthorized;
|
||||
if (agentRejectUnauthorized !== undefined) this._ensureTls().rejectUnauthorized = agentRejectUnauthorized;
|
||||
}
|
||||
}
|
||||
if (options.ca) {
|
||||
if (!isValidTLSArray(options.ca))
|
||||
throw new TypeError(
|
||||
"ca argument must be an string, Buffer, TypedArray, BunFile or an array containing string, Buffer, TypedArray or BunFile",
|
||||
);
|
||||
this._ensureTls().ca = options.ca;
|
||||
}
|
||||
if (options.cert) {
|
||||
if (!isValidTLSArray(options.cert))
|
||||
throw new TypeError(
|
||||
"cert argument must be an string, Buffer, TypedArray, BunFile or an array containing string, Buffer, TypedArray or BunFile",
|
||||
);
|
||||
this._ensureTls().cert = options.cert;
|
||||
}
|
||||
if (options.key) {
|
||||
if (!isValidTLSArray(options.key))
|
||||
throw new TypeError(
|
||||
"key argument must be an string, Buffer, TypedArray, BunFile or an array containing string, Buffer, TypedArray or BunFile",
|
||||
);
|
||||
this._ensureTls().key = options.key;
|
||||
}
|
||||
if (options.passphrase) {
|
||||
if (typeof options.passphrase !== "string") throw new TypeError("passphrase argument must be a string");
|
||||
this._ensureTls().passphrase = options.passphrase;
|
||||
}
|
||||
if (options.ciphers) {
|
||||
if (typeof options.ciphers !== "string") throw new TypeError("ciphers argument must be a string");
|
||||
this._ensureTls().ciphers = options.ciphers;
|
||||
}
|
||||
if (options.servername) {
|
||||
if (typeof options.servername !== "string") throw new TypeError("servername argument must be a string");
|
||||
this._ensureTls().servername = options.servername;
|
||||
}
|
||||
// Merge TLS options using spread operator, matching Node.js behavior in createSocket:
|
||||
// options = { __proto__: null, ...options, ...this.options };
|
||||
// https://github.com/nodejs/node/blob/v23.6.0/lib/_http_agent.js#L242
|
||||
// With spread, the last one wins, so agent.options overwrites request options.
|
||||
//
|
||||
// agent.options: Stored by Node.js Agent constructor
|
||||
// https://github.com/nodejs/node/blob/v23.6.0/lib/_http_agent.js#L96
|
||||
//
|
||||
// agent.connectOpts: Used by https-proxy-agent for TLS connection options (lowest priority)
|
||||
// https://github.com/TooTallNate/proxy-agents/blob/main/packages/https-proxy-agent/src/index.ts#L110-L117
|
||||
const mergedTlsOptions = { __proto__: null, ...agent?.connectOpts, ...options, ...agent?.options };
|
||||
|
||||
if (options.secureOptions) {
|
||||
if (typeof options.secureOptions !== "number") throw new TypeError("secureOptions argument must be a string");
|
||||
this._ensureTls().secureOptions = options.secureOptions;
|
||||
if (mergedTlsOptions.rejectUnauthorized !== undefined) {
|
||||
this._ensureTls().rejectUnauthorized = mergedTlsOptions.rejectUnauthorized;
|
||||
}
|
||||
if (mergedTlsOptions.ca) {
|
||||
throwOnInvalidTLSArray("options.ca", mergedTlsOptions.ca);
|
||||
this._ensureTls().ca = mergedTlsOptions.ca;
|
||||
}
|
||||
if (mergedTlsOptions.cert) {
|
||||
throwOnInvalidTLSArray("options.cert", mergedTlsOptions.cert);
|
||||
this._ensureTls().cert = mergedTlsOptions.cert;
|
||||
}
|
||||
if (mergedTlsOptions.key) {
|
||||
throwOnInvalidTLSArray("options.key", mergedTlsOptions.key);
|
||||
this._ensureTls().key = mergedTlsOptions.key;
|
||||
}
|
||||
if (mergedTlsOptions.passphrase) {
|
||||
validateString(mergedTlsOptions.passphrase, "options.passphrase");
|
||||
this._ensureTls().passphrase = mergedTlsOptions.passphrase;
|
||||
}
|
||||
if (mergedTlsOptions.ciphers) {
|
||||
validateString(mergedTlsOptions.ciphers, "options.ciphers");
|
||||
this._ensureTls().ciphers = mergedTlsOptions.ciphers;
|
||||
}
|
||||
if (mergedTlsOptions.servername) {
|
||||
validateString(mergedTlsOptions.servername, "options.servername");
|
||||
this._ensureTls().servername = mergedTlsOptions.servername;
|
||||
}
|
||||
if (mergedTlsOptions.secureOptions) {
|
||||
validateInteger(mergedTlsOptions.secureOptions, "options.secureOptions");
|
||||
this._ensureTls().secureOptions = mergedTlsOptions.secureOptions;
|
||||
}
|
||||
this[kPath] = options.path || "/";
|
||||
if (cb) {
|
||||
|
||||
30
test/js/node/http/fixtures/cert.encrypted.key
Normal file
30
test/js/node/http/fixtures/cert.encrypted.key
Normal file
@@ -0,0 +1,30 @@
|
||||
-----BEGIN ENCRYPTED PRIVATE KEY-----
|
||||
MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQieLggVjbubz09mX5
|
||||
GdRQAwICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEJ++f2E23qU4mbP4
|
||||
m3RnPasEggTQoS6zcBDvWURYyctw9Qma8L/ZnPg4SBclVzYbiZcvBPNRvCNLnYxQ
|
||||
ysimU/8PTCP9m944dcsMolRqPjj0gOQCnBpqbZmnc7elwDFZIhePRfMKC2bPHZeo
|
||||
ABonNOs2VstJ9gT3RA5x8Dj99dsoPdnV9rL6vkW0Gk86BPGgQq5i1ipJvYrpOtay
|
||||
Bq5JgpptVX86azXZVriB8FUNfJuFOPQfxfXIY7ogHpQWZ7rIVa5ug7LlJ7sLjakj
|
||||
ph/4corzRnRr88/eFfhYbV5rob/Lvoq8+I2Hgf25ypJ2XdOoWAgDOvl6+k01v/Ci
|
||||
VAYAE1v9RgmiAXFIE9uYbSIyhiVibmLU6QK7Vcydv0ZaZLdP/9HwfZ6Q5u1a23rj
|
||||
ltzRFOu5H7ipVXSoZU1ffw2EXi1RZJU2n5M3tU11qZsNpaDulEdcYZm74sUaqdjA
|
||||
zkYSO+RBehptEUfgjXBrW8HJ42fCfd6IvQ7NtT3e3zJup105cHIEfO8IiSSt/oW3
|
||||
SOupzjTpARHhAbPKSEmUVC1IXjGUvUuZs+NlN+byNkI4IhSTHp4vn5k87l22jccl
|
||||
4NwW5ZIouqawvV5gyOGgBcwgSfvd4H8mcSeFfZhVmEtRDKtubREr8mqqcUWq5V/W
|
||||
fEGR2LTQKRofhGGw56Jzw8FgNJNI0m6WBYIPQVtmwqqljPNPDuCQZ/icrhM6s0MR
|
||||
7IyDiCUHzsz2JZxRJJO9pzItSABym/I57DTtRg1XQTEuSU+dTwhVzwkytWVldHx3
|
||||
Rvbb6DUWrLtthoAs/LSDevjhrLYAdkLj4iaexqfYPcrRA22hj3KxxRpzV8zqMNvM
|
||||
hI703HrjIPzlVhrqf6gMiKs7iZu2XQ4RRsQyKzWlro9bOprUvIg/abFtaJDXKqN0
|
||||
sTJQ9rSpTJgUzG4sJEFiUeM0Wm2cLUO1w4N4/si89vOCcVJJUIjZgwsyFu8DpUIE
|
||||
7E9rgAzuWByIBOJQ0f1hfF7zGUxAJ75qRdHm0q2aDkDPLiJk1alR1MpMs1tIcaBO
|
||||
CAxnlZtORvq6QMQnERkpzuvX2PS5mtZ8w/qizPgb8GL3kU+Ex0lJHT8PBwspSXWV
|
||||
Gc9AvCZ1z+YLnflUsRch/dI/suGhpIcLOX4M3pfW9qfo/i92uR52JWzIAkRKFTOi
|
||||
fSiADLpar2WT2Kcz9aGfTB2swjhsL7Q6Tf8BWUCVYtfbf5FK07uPTCb9tyy+LxtU
|
||||
qvtHe3XyZTO3guRBBDZotEOqNKzJw+ZUKIO7vX5JGtpMudBHL2J1KH80Qy4+uR/H
|
||||
b9YyW0UFOyuOejmrMwHMP/iXkYyTsBiShETU0Uga33xvSuS10FhiCt87cXCI/WeZ
|
||||
Jw1fk29QA3nx5vw9zDcVFiJRwOu9l6/JxXFpGm0ZjhYudS98yJkam3sbwJThJ+1C
|
||||
fFzzCM69iUdPw/8JEPnD+Wd2okFiwjpEzHrZ+n1P5YGDF7UTyEB3gLpn3sgmBR9H
|
||||
2z4yiL+ST/WI7n3ykXxzxjzcEgkDEwLfzHlguqh7jhYWuIhsDmcch7EgH8+gsyke
|
||||
9lgUWJdoHXVfNZmWh4rMMkEUGi605WulXV8N9qQJJOJltN3lGdKZi+CBK6dTlPtJ
|
||||
iAj5mvrk++pP/b0SplcQtq3pspGnWmjw+jw0aOVzSpn8qrco1/FZWdw=
|
||||
-----END ENCRYPTED PRIVATE KEY-----
|
||||
@@ -1,28 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCIzOJskt6VkEJY
|
||||
XKSJv/Gdil3XYkjk3NVc/+m+kzqnkTRbPtT9w+IGWgmJhuf9DJPLCwHFAEFarVwV
|
||||
x16Q0PbU4ajXaLRHEYGhrH10oTMjQnJ24xVm26mxRXPQa5vaLpWJqNyIdNLIQLe+
|
||||
UXUOzSGGsFTRMAjvYrkzjBe4ZUnaZV+aFY/ug0jfzeA1dJjzKZs6+yTJRbsuWUEb
|
||||
8MsDmT4v+kBZDKdaDn7AFDWRVqx/38BnqsRzkM0CxpnyT2kRzw5zQajIE13gdTJo
|
||||
1EHvYSUkkxrY5m30Rl9BuBBZBjhMzOHq0fYVVooHO+sf4XHPgvFTTxJum85u7J1J
|
||||
oEUjrLKtAgMBAAECggEACInVNhaiqu4infZGVMy0rXMV8VwSlapM7O2SLtFsr0nK
|
||||
XUmaLK6dvGzBPKK9dxdiYCFzPlMKQTkhzsAvYFWSmm3tRmikG+11TFyCRhXLpc8/
|
||||
ark4vD9Io6ZkmKUmyKLwtXNjNGcqQtJ7RXc7Ga3nAkueN6JKZHqieZusXVeBGQ70
|
||||
YH1LKyVNBeJggbj+g9rqaksPyNJQ8EWiNTJkTRQPazZ0o1VX/fzDFyr/a5npFtHl
|
||||
4BHfafv9o1Xyr70Kie8CYYRJNViOCN+ylFs7Gd3XRaAkSkgMT/7DzrHdEM2zrrHK
|
||||
yNg2gyDVX9UeEJG2X5UtU0o9BVW7WBshz/2hqIUHoQKBgQC8zsRFvC7u/rGr5vRR
|
||||
mhZZG+Wvg03/xBSuIgOrzm+Qie6mAzOdVmfSL/pNV9EFitXt1yd2ROo31AbS7Evy
|
||||
Bm/QVKr2mBlmLgov3B7O/e6ABteooOL7769qV/v+yo8VdEg0biHmsfGIIXDe3Lwl
|
||||
OT0XwF9r/SeZLbw1zfkSsUVG/QKBgQC5fANM3Dc9LEek+6PHv5+eC1cKkyioEjUl
|
||||
/y1VUD00aABI1TUcdLF3BtFN2t/S6HW0hrP3KwbcUfqC25k+GDLh1nM6ZK/gI3Yn
|
||||
IGtCHxtE3S6jKhE9QcK/H+PzGVKWge9SezeYRP0GHJYDrTVTA8Kt9HgoZPPeReJl
|
||||
+Ss9c8ThcQKBgECX6HQHFnNzNSufXtSQB7dCoQizvjqTRZPxVRoxDOABIGExVTYt
|
||||
umUhPtu5AGyJ+/hblEeU+iBRbGg6qRzK8PPwE3E7xey8MYYAI5YjL7YjISKysBUL
|
||||
AhM6uJ6Jg/wOBSnSx8xZ8kzlS+0izUda1rjKeprCSArSp8IsjlrDxPStAoGAEcPr
|
||||
+P+altRX5Fhpvmb/Hb8OTif8G+TqjEIdkG9H/W38oP0ywg/3M2RGxcMx7txu8aR5
|
||||
NjI7zPxZFxF7YvQkY3cLwEsGgVxEI8k6HLIoBXd90Qjlb82NnoqqZY1GWL4HMwo0
|
||||
L/Rjm6M/Rwje852Hluu0WoIYzXA6F/Q+jPs6nzECgYAxx4IbDiGXuenkwSF1SUyj
|
||||
NwJXhx4HDh7U6EO/FiPZE5BHE3BoTrFu3o1lzverNk7G3m+j+m1IguEAalHlukYl
|
||||
rip9iUISlKYqbYZdLBoLwHAfHhszdrjqn8/v6oqbB5yR3HXjPFUWJo0WJ2pqJp56
|
||||
ZshgmQQ/5Khoj6x0/dMPSg==
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDlYzosgRgXHL6v
|
||||
Mh1V0ERFhsvlZrtRojSw6tafr3SQBphU793/rGiYZlL/lJ9HIlLkx9JMbuTjNm5U
|
||||
2eRwHiTQIeWD4aCIESwPlkdaVYtC+IOj55bJN8xNa7h5GyJwF7PnPetAsKyE8DMB
|
||||
n1gKMhaIis7HHOUtk4/K3Y4peU44d04z0yPt6JtY5Sbvi1E7pGX6T/2c9sHsdIDe
|
||||
DctWnewpXXs8zkAla0KNWQfpDnpS53wxAfStTA4lSrA9daxC7hZopQlLxFIbJk+0
|
||||
BLbEsXtrJ54T5iguHk+2MDVAy4MOqP9XbKV7eGHk73l6+CSwmHyHBxh4ChxRQeT5
|
||||
BP0MUTn1AgMBAAECggEABtPvC5uVGr0DjQX2GxONsK8cOxoVec7U+C4pUMwBcXcM
|
||||
yjxwlHdujpi/IDXtjsm+A2rSPu2vGPdKDfMFanPvPxW/Ne99noc6U0VzHsR8lnP8
|
||||
wSB328nyJhzOeyZcXk9KTtgIPF7156gZsJLsZTNL+ej90i3xQWvKxCxXmrLuad5O
|
||||
z/TrgZkC6wC3fgj1d3e8bMljQ7tLxbshJMYVI5o6RFTxy84DLI+rlvPkf7XbiMPf
|
||||
2lsm4jcJKvfx+164HZJ9QVlx8ncqOHAnGvxb2xHHfqv4JAbz615t7yRvtaw4Paj5
|
||||
6kQSf0VWnsVzgxNJWvnUZym/i/Qf5nQafjChCyKOEQKBgQD9f4SkvJrp/mFKWLHd
|
||||
kDvRpSIIltfJsa5KShn1IHsQXFwc0YgyP4SKQb3Ckv+/9UFHK9EzM+WlPxZi7ZOS
|
||||
hsWhIfkI4c4ORpxUQ+hPi0K2k+HIY7eYyONqDAzw5PGkKBo3mSGMHDXYywSqexhB
|
||||
CCMHuHdMhwyHdz4PWYOK3C2VMQKBgQDnpsrHK7lM9aVb8wNhTokbK5IlTSzH/5oJ
|
||||
lAVu6G6H3tM5YQeoDXztbZClvrvKU8DU5UzwaC+8AEWQwaram29QIDpAI3nVQQ0k
|
||||
dmHHp/pCeADdRG2whaGcl418UJMMv8AUpWTRm+kVLTLqfTHBC0ji4NlCQMHCUCfd
|
||||
U8TeUi5QBQKBgQDvJNd7mboDOUmLG7VgMetc0Y4T0EnuKsMjrlhimau/OYJkZX84
|
||||
+BcPXwmnf4nqC3Lzs3B9/12L0MJLvZjUSHQ0mJoZOPxtF0vvasjEEbp0B3qe0wOn
|
||||
DQ0NRCUJNNKJbJOfE8VEKnDZ/lx+f/XXk9eINwvElDrLqUBQtr+TxjbyYQKBgAxQ
|
||||
lZ8Y9/TbajsFJDzcC/XhzxckjyjisbGoqNFIkfevJNN8EQgiD24f0Py+swUChtHK
|
||||
jtiI8WCxMwGLCiYs9THxRKd8O1HW73fswy32BBvcfU9F//7OW9UTSXY+YlLfLrrq
|
||||
P/3UqAN0L6y/kxGMJAfLpEEdaC+IS1Y8yc531/ZxAoGASYiasDpePtmzXklDxk3h
|
||||
jEw64QAdXK2p/xTMjSeTtcqJ7fvaEbg+Mfpxq0mdTjfbTdR9U/nzAkwS7OoZZ4Du
|
||||
ueMVls0IVqcNnBtikG8wgdxN27b5JPXS+GzQ0zDSpWFfRPZiIh37BAXr0D1voluJ
|
||||
rEHkcals6p7hL98BoxjFIvA=
|
||||
-----END PRIVATE KEY-----
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIID5jCCAs6gAwIBAgIUN7coIsdMcLo9amZfkwogu0YkeLEwDQYJKoZIhvcNAQEL
|
||||
BQAwfjELMAkGA1UEBhMCU0UxDjAMBgNVBAgMBVN0YXRlMREwDwYDVQQHDAhMb2Nh
|
||||
dGlvbjEaMBgGA1UECgwRT3JnYW5pemF0aW9uIE5hbWUxHDAaBgNVBAsME09yZ2Fu
|
||||
aXphdGlvbmFsIFVuaXQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yMzA5MjExNDE2
|
||||
MjNaFw0yNDA5MjAxNDE2MjNaMH4xCzAJBgNVBAYTAlNFMQ4wDAYDVQQIDAVTdGF0
|
||||
ZTERMA8GA1UEBwwITG9jYXRpb24xGjAYBgNVBAoMEU9yZ2FuaXphdGlvbiBOYW1l
|
||||
MRwwGgYDVQQLDBNPcmdhbml6YXRpb25hbCBVbml0MRIwEAYDVQQDDAlsb2NhbGhv
|
||||
c3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCIzOJskt6VkEJYXKSJ
|
||||
v/Gdil3XYkjk3NVc/+m+kzqnkTRbPtT9w+IGWgmJhuf9DJPLCwHFAEFarVwVx16Q
|
||||
0PbU4ajXaLRHEYGhrH10oTMjQnJ24xVm26mxRXPQa5vaLpWJqNyIdNLIQLe+UXUO
|
||||
zSGGsFTRMAjvYrkzjBe4ZUnaZV+aFY/ug0jfzeA1dJjzKZs6+yTJRbsuWUEb8MsD
|
||||
mT4v+kBZDKdaDn7AFDWRVqx/38BnqsRzkM0CxpnyT2kRzw5zQajIE13gdTJo1EHv
|
||||
YSUkkxrY5m30Rl9BuBBZBjhMzOHq0fYVVooHO+sf4XHPgvFTTxJum85u7J1JoEUj
|
||||
rLKtAgMBAAGjXDBaMA4GA1UdDwEB/wQEAwIDiDATBgNVHSUEDDAKBggrBgEFBQcD
|
||||
ATAUBgNVHREEDTALgglsb2NhbGhvc3QwHQYDVR0OBBYEFNzx4Rfs9m8XR5ML0WsI
|
||||
sorKmB4PMA0GCSqGSIb3DQEBCwUAA4IBAQB87iQy8R0fiOky9WTcyzVeMaavS3MX
|
||||
iTe1BRn1OCyDq+UiwwoNz7zdzZJFEmRtFBwPNFOe4HzLu6E+7yLFR552eYRHlqIi
|
||||
/fiLb5JiZfPtokUHeqwELWBsoXtU8vKxViPiLZ09jkWOPZWo7b/xXd6QYykBfV91
|
||||
usUXLzyTD2orMagpqNksLDGS3p3ggHEJBZtRZA8R7kPEw98xZHznOQpr26iv8kYz
|
||||
ZWdLFoFdwgFBSfxePKax5rfo+FbwdrcTX0MhbORyiu2XsBAghf8s2vKDkHg2UQE8
|
||||
haonxFYMFaASfaZ/5vWKYDTCJkJ67m/BtkpRafFEO+ad1i1S61OjfxH4
|
||||
MIID4jCCAsqgAwIBAgIUcaRq6J/YF++Bo01Zc+HeQvCbnWMwDQYJKoZIhvcNAQEL
|
||||
BQAwaTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJh
|
||||
bmNpc2NvMQ0wCwYDVQQKDARPdmVuMREwDwYDVQQLDAhUZWFtIEJ1bjETMBEGA1UE
|
||||
AwwKc2VydmVyLWJ1bjAeFw0yNTA5MDYwMzAwNDlaFw0zNTA5MDQwMzAwNDlaMGkx
|
||||
CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNj
|
||||
bzENMAsGA1UECgwET3ZlbjERMA8GA1UECwwIVGVhbSBCdW4xEzARBgNVBAMMCnNl
|
||||
cnZlci1idW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDlYzosgRgX
|
||||
HL6vMh1V0ERFhsvlZrtRojSw6tafr3SQBphU793/rGiYZlL/lJ9HIlLkx9JMbuTj
|
||||
Nm5U2eRwHiTQIeWD4aCIESwPlkdaVYtC+IOj55bJN8xNa7h5GyJwF7PnPetAsKyE
|
||||
8DMBn1gKMhaIis7HHOUtk4/K3Y4peU44d04z0yPt6JtY5Sbvi1E7pGX6T/2c9sHs
|
||||
dIDeDctWnewpXXs8zkAla0KNWQfpDnpS53wxAfStTA4lSrA9daxC7hZopQlLxFIb
|
||||
Jk+0BLbEsXtrJ54T5iguHk+2MDVAy4MOqP9XbKV7eGHk73l6+CSwmHyHBxh4ChxR
|
||||
QeT5BP0MUTn1AgMBAAGjgYEwfzAdBgNVHQ4EFgQUw7nEnh4uOdZVZUapQzdAUaVa
|
||||
An0wHwYDVR0jBBgwFoAUw7nEnh4uOdZVZUapQzdAUaVaAn0wDwYDVR0TAQH/BAUw
|
||||
AwEB/zAsBgNVHREEJTAjgglsb2NhbGhvc3SHBH8AAAGHEAAAAAAAAAAAAAAAAAAA
|
||||
AAEwDQYJKoZIhvcNAQELBQADggEBAEA8r1fvDLMSCb8bkAURpFk8chn8pl5MChzT
|
||||
YUDaLdCCBjPXJkSXNdyuwS+T/ljAGyZbW5xuDccCNKltawO4CbyEXUEZbYr3w9eq
|
||||
j8uqymJPhFf0O1rKOI2han5GBCgHwG13QwKI+4uu7390nD+TlzLOhxFfvOG7OadH
|
||||
QNMNLNyldgF4Nb8vWdz0FtQiGUIrO7iq4LFhhd1lCxe0q+FAYSEYcc74WtF/Yo8V
|
||||
JQauXuXyoP5FqLzNt/yeNQhceyIXJGKCsjr5/bASBmVlCwgRfsD3jpG37L8YCJs1
|
||||
L4WEikcY4Lzb2NF9e94IyZdQsRqd9DFBF5zP013MSUiuhiow32k=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
710
test/js/node/http/node-http-agent-tls-options.test.mts
Normal file
710
test/js/node/http/node-http-agent-tls-options.test.mts
Normal file
@@ -0,0 +1,710 @@
|
||||
/**
|
||||
* All tests in this file run in both Bun and Node.js.
|
||||
*
|
||||
* Test that TLS options can be inherited from agent.options and agent.connectOpts.
|
||||
* This is important for compatibility with libraries like https-proxy-agent.
|
||||
*
|
||||
* The HttpsProxyAgent tests verify that TLS options are properly passed through
|
||||
* the proxy tunnel to the target HTTPS server.
|
||||
*/
|
||||
|
||||
import { once } from "node:events";
|
||||
import { readFileSync } from "node:fs";
|
||||
import http from "node:http";
|
||||
import https from "node:https";
|
||||
import { createRequire } from "node:module";
|
||||
import type { AddressInfo } from "node:net";
|
||||
import net from "node:net";
|
||||
import { dirname, join } from "node:path";
|
||||
import { describe, test } from "node:test";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
// Use createRequire for ESM compatibility
|
||||
const require = createRequire(import.meta.url);
|
||||
const { HttpsProxyAgent } = require("https-proxy-agent") as {
|
||||
HttpsProxyAgent: new (proxyUrl: string, options?: Record<string, unknown>) => http.Agent;
|
||||
};
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
// Self-signed certificate with SANs for localhost and 127.0.0.1
|
||||
// This cert is its own CA (self-signed)
|
||||
const tlsCerts = {
|
||||
cert: readFileSync(join(__dirname, "fixtures", "cert.pem"), "utf8"),
|
||||
key: readFileSync(join(__dirname, "fixtures", "cert.key"), "utf8"),
|
||||
encryptedKey: readFileSync(join(__dirname, "fixtures", "cert.encrypted.key"), "utf8"),
|
||||
passphrase: "testpassword",
|
||||
// Self-signed cert, so it's its own CA
|
||||
get ca() {
|
||||
return this.cert;
|
||||
},
|
||||
};
|
||||
|
||||
async function createHttpsServer(
|
||||
options: https.ServerOptions = {},
|
||||
): Promise<{ server: https.Server; port: number; hostname: string }> {
|
||||
const server = https.createServer({ key: tlsCerts.key, cert: tlsCerts.cert, ...options }, (req, res) => {
|
||||
res.writeHead(200);
|
||||
res.end("OK");
|
||||
});
|
||||
await once(server.listen(0, "127.0.0.1"), "listening");
|
||||
const { port } = server.address() as AddressInfo;
|
||||
return { server, port, hostname: "127.0.0.1" };
|
||||
}
|
||||
|
||||
async function createHttpServer(): Promise<{
|
||||
server: http.Server;
|
||||
port: number;
|
||||
hostname: string;
|
||||
}> {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.writeHead(200);
|
||||
res.end("OK");
|
||||
});
|
||||
await once(server.listen(0, "127.0.0.1"), "listening");
|
||||
const { port } = server.address() as AddressInfo;
|
||||
return { server, port, hostname: "127.0.0.1" };
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an HTTP CONNECT proxy server.
|
||||
* This proxy handles the CONNECT method to establish tunnels for HTTPS connections.
|
||||
*/
|
||||
function createConnectProxy(): net.Server {
|
||||
return net.createServer(clientSocket => {
|
||||
let buffer: Uint8Array = new Uint8Array(0);
|
||||
let tunnelEstablished = false;
|
||||
let targetSocket: net.Socket | null = null;
|
||||
|
||||
clientSocket.on("data", (data: Uint8Array) => {
|
||||
// If tunnel is already established, forward data directly
|
||||
if (tunnelEstablished && targetSocket) {
|
||||
targetSocket.write(data);
|
||||
return;
|
||||
}
|
||||
|
||||
// Concatenate buffers
|
||||
const newBuffer = new Uint8Array(buffer.length + data.length);
|
||||
newBuffer.set(buffer);
|
||||
newBuffer.set(data, buffer.length);
|
||||
buffer = newBuffer;
|
||||
|
||||
const bufferStr = new TextDecoder().decode(buffer);
|
||||
|
||||
// Check if we have complete headers
|
||||
const headerEnd = bufferStr.indexOf("\r\n\r\n");
|
||||
if (headerEnd === -1) return;
|
||||
|
||||
const headerPart = bufferStr.substring(0, headerEnd);
|
||||
const lines = headerPart.split("\r\n");
|
||||
const requestLine = lines[0];
|
||||
|
||||
// Check for CONNECT method
|
||||
const match = requestLine.match(/^CONNECT\s+([^:]+):(\d+)\s+HTTP/);
|
||||
if (!match) {
|
||||
clientSocket.write("HTTP/1.1 400 Bad Request\r\n\r\n");
|
||||
clientSocket.end();
|
||||
return;
|
||||
}
|
||||
|
||||
const [, targetHost, targetPort] = match;
|
||||
|
||||
// Get any data after the headers (shouldn't be any for CONNECT)
|
||||
// headerEnd is byte position in the string, need to account for UTF-8
|
||||
const headerBytes = new TextEncoder().encode(bufferStr.substring(0, headerEnd + 4)).length;
|
||||
const remainingData = buffer.subarray(headerBytes);
|
||||
|
||||
// Connect to target
|
||||
targetSocket = net.connect(parseInt(targetPort, 10), targetHost, () => {
|
||||
clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
|
||||
tunnelEstablished = true;
|
||||
|
||||
// Forward any remaining data
|
||||
if (remainingData.length > 0) {
|
||||
targetSocket!.write(remainingData);
|
||||
}
|
||||
|
||||
// Set up bidirectional piping
|
||||
targetSocket!.on("data", (chunk: Uint8Array) => {
|
||||
clientSocket.write(chunk);
|
||||
});
|
||||
});
|
||||
|
||||
targetSocket.on("error", () => {
|
||||
if (!tunnelEstablished) {
|
||||
clientSocket.write("HTTP/1.1 502 Bad Gateway\r\n\r\n");
|
||||
}
|
||||
clientSocket.end();
|
||||
});
|
||||
|
||||
targetSocket.on("close", () => clientSocket.destroy());
|
||||
clientSocket.on("close", () => targetSocket?.destroy());
|
||||
});
|
||||
|
||||
clientSocket.on("error", () => {
|
||||
targetSocket?.destroy();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to start a proxy server and get its port.
|
||||
*/
|
||||
async function startProxy(server: net.Server): Promise<number> {
|
||||
return new Promise<number>(resolve => {
|
||||
server.listen(0, "127.0.0.1", () => {
|
||||
const addr = server.address() as AddressInfo;
|
||||
resolve(addr.port);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe("https.request agent TLS options inheritance", () => {
|
||||
describe("agent.options", () => {
|
||||
test("inherits ca from agent.options", async () => {
|
||||
const { server, port, hostname } = await createHttpsServer();
|
||||
|
||||
try {
|
||||
// Create an agent with ca in options
|
||||
const agent = new https.Agent({
|
||||
ca: tlsCerts.ca,
|
||||
});
|
||||
|
||||
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
||||
const req = https.request(
|
||||
{
|
||||
hostname,
|
||||
port,
|
||||
path: "/",
|
||||
method: "GET",
|
||||
agent,
|
||||
// NO ca here - should inherit from agent.options
|
||||
},
|
||||
res => {
|
||||
res.on("data", () => {});
|
||||
res.on("end", resolve);
|
||||
},
|
||||
);
|
||||
req.on("error", reject);
|
||||
req.end();
|
||||
|
||||
await promise;
|
||||
} finally {
|
||||
server.close();
|
||||
}
|
||||
});
|
||||
|
||||
test("inherits rejectUnauthorized from agent.options", async () => {
|
||||
const { server, port, hostname } = await createHttpsServer();
|
||||
|
||||
try {
|
||||
// Create an agent with rejectUnauthorized: false in options
|
||||
const agent = new https.Agent({
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
|
||||
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
||||
const req = https.request(
|
||||
{
|
||||
hostname,
|
||||
port,
|
||||
path: "/",
|
||||
method: "GET",
|
||||
agent,
|
||||
// NO rejectUnauthorized here - should inherit from agent.options
|
||||
},
|
||||
res => {
|
||||
res.on("data", () => {});
|
||||
res.on("end", resolve);
|
||||
},
|
||||
);
|
||||
req.on("error", reject);
|
||||
req.end();
|
||||
|
||||
await promise;
|
||||
} finally {
|
||||
server.close();
|
||||
}
|
||||
});
|
||||
|
||||
test("inherits cert and key from agent.options", async () => {
|
||||
// Create a server that uses TLS
|
||||
const { server, port, hostname } = await createHttpsServer();
|
||||
|
||||
try {
|
||||
// Create an agent with cert/key in options
|
||||
const agent = new https.Agent({
|
||||
rejectUnauthorized: false,
|
||||
cert: tlsCerts.cert,
|
||||
key: tlsCerts.key,
|
||||
});
|
||||
|
||||
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
||||
const req = https.request(
|
||||
{
|
||||
hostname,
|
||||
port,
|
||||
path: "/",
|
||||
method: "GET",
|
||||
agent,
|
||||
// NO cert/key here - should inherit from agent.options
|
||||
},
|
||||
res => {
|
||||
res.on("data", () => {});
|
||||
res.on("end", resolve);
|
||||
},
|
||||
);
|
||||
req.on("error", reject);
|
||||
req.end();
|
||||
|
||||
await promise;
|
||||
} finally {
|
||||
server.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Test HttpsProxyAgent compatibility - these tests use real HttpsProxyAgent
|
||||
// to verify HTTPS requests work through the proxy tunnel with TLS options
|
||||
describe("HttpsProxyAgent TLS options", () => {
|
||||
test("HttpsProxyAgent with rejectUnauthorized: false", async () => {
|
||||
const { server, port, hostname } = await createHttpsServer();
|
||||
const proxy = createConnectProxy();
|
||||
const proxyPort = await startProxy(proxy);
|
||||
|
||||
try {
|
||||
// Create HttpsProxyAgent for the proxy connection
|
||||
const agent = new HttpsProxyAgent(`http://127.0.0.1:${proxyPort}`, {
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
|
||||
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
||||
const req = https.request(
|
||||
{
|
||||
hostname,
|
||||
port,
|
||||
path: "/",
|
||||
method: "GET",
|
||||
agent,
|
||||
// TLS options must also be passed here for Node.js compatibility
|
||||
// https-proxy-agent doesn't propagate these to target connection in Node.js
|
||||
// See: https://github.com/TooTallNate/node-https-proxy-agent/issues/35
|
||||
rejectUnauthorized: false,
|
||||
},
|
||||
res => {
|
||||
res.on("data", () => {});
|
||||
res.on("end", resolve);
|
||||
},
|
||||
);
|
||||
req.on("error", reject);
|
||||
req.end();
|
||||
|
||||
await promise;
|
||||
} finally {
|
||||
server.close();
|
||||
proxy.close();
|
||||
}
|
||||
});
|
||||
|
||||
test("HttpsProxyAgent with ca option", async () => {
|
||||
const { server, port, hostname } = await createHttpsServer();
|
||||
const proxy = createConnectProxy();
|
||||
const proxyPort = await startProxy(proxy);
|
||||
|
||||
try {
|
||||
// Create HttpsProxyAgent for the proxy connection
|
||||
const agent = new HttpsProxyAgent(`http://127.0.0.1:${proxyPort}`, {
|
||||
ca: tlsCerts.ca,
|
||||
});
|
||||
|
||||
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
||||
const req = https.request(
|
||||
{
|
||||
hostname,
|
||||
port,
|
||||
path: "/",
|
||||
method: "GET",
|
||||
agent,
|
||||
// TLS options must also be passed here for Node.js compatibility
|
||||
ca: tlsCerts.ca,
|
||||
},
|
||||
res => {
|
||||
res.on("data", () => {});
|
||||
res.on("end", resolve);
|
||||
},
|
||||
);
|
||||
req.on("error", reject);
|
||||
req.end();
|
||||
|
||||
await promise;
|
||||
} finally {
|
||||
server.close();
|
||||
proxy.close();
|
||||
}
|
||||
});
|
||||
|
||||
test("HttpsProxyAgent with cert and key options", async () => {
|
||||
const { server, port, hostname } = await createHttpsServer();
|
||||
const proxy = createConnectProxy();
|
||||
const proxyPort = await startProxy(proxy);
|
||||
|
||||
try {
|
||||
// Create HttpsProxyAgent for the proxy connection
|
||||
const agent = new HttpsProxyAgent(`http://127.0.0.1:${proxyPort}`, {
|
||||
rejectUnauthorized: false,
|
||||
cert: tlsCerts.cert,
|
||||
key: tlsCerts.key,
|
||||
});
|
||||
|
||||
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
||||
const req = https.request(
|
||||
{
|
||||
hostname,
|
||||
port,
|
||||
path: "/",
|
||||
method: "GET",
|
||||
agent,
|
||||
// TLS options must also be passed here for Node.js compatibility
|
||||
rejectUnauthorized: false,
|
||||
cert: tlsCerts.cert,
|
||||
key: tlsCerts.key,
|
||||
},
|
||||
res => {
|
||||
res.on("data", () => {});
|
||||
res.on("end", resolve);
|
||||
},
|
||||
);
|
||||
req.on("error", reject);
|
||||
req.end();
|
||||
|
||||
await promise;
|
||||
} finally {
|
||||
server.close();
|
||||
proxy.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("option precedence (matches Node.js)", () => {
|
||||
// In Node.js, options are merged via spread in createSocket:
|
||||
// options = { __proto__: null, ...options, ...this.options };
|
||||
// https://github.com/nodejs/node/blob/v23.6.0/lib/_http_agent.js#L365
|
||||
// With spread, the last one wins, so agent.options overwrites request options.
|
||||
|
||||
test("agent.options takes precedence over direct options", async () => {
|
||||
const { server, port, hostname } = await createHttpsServer();
|
||||
|
||||
try {
|
||||
// Create an agent with correct CA
|
||||
const agent = new https.Agent({
|
||||
ca: tlsCerts.ca, // Correct CA in agent.options - should be used
|
||||
});
|
||||
|
||||
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
||||
const req = https.request(
|
||||
{
|
||||
hostname,
|
||||
port,
|
||||
path: "/",
|
||||
method: "GET",
|
||||
agent,
|
||||
ca: "wrong-ca-that-would-fail", // Wrong CA in request - should be ignored
|
||||
},
|
||||
res => {
|
||||
res.on("data", () => {});
|
||||
res.on("end", resolve);
|
||||
},
|
||||
);
|
||||
req.on("error", reject);
|
||||
req.end();
|
||||
|
||||
await promise;
|
||||
} finally {
|
||||
server.close();
|
||||
}
|
||||
});
|
||||
|
||||
test("direct options used when agent.options not set", async () => {
|
||||
const { server, port, hostname } = await createHttpsServer();
|
||||
|
||||
try {
|
||||
// Create an agent without ca
|
||||
const agent = new https.Agent({});
|
||||
|
||||
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
||||
const req = https.request(
|
||||
{
|
||||
hostname,
|
||||
port,
|
||||
path: "/",
|
||||
method: "GET",
|
||||
agent,
|
||||
ca: tlsCerts.ca, // Direct option should be used since agent.options.ca is not set
|
||||
},
|
||||
res => {
|
||||
res.on("data", () => {});
|
||||
res.on("end", resolve);
|
||||
},
|
||||
);
|
||||
req.on("error", reject);
|
||||
req.end();
|
||||
|
||||
await promise;
|
||||
} finally {
|
||||
server.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("other TLS options", () => {
|
||||
test("inherits servername from agent.options", async () => {
|
||||
const { server, port, hostname } = await createHttpsServer();
|
||||
|
||||
try {
|
||||
const agent = new https.Agent({
|
||||
rejectUnauthorized: false,
|
||||
servername: "localhost", // Should be passed to TLS
|
||||
});
|
||||
|
||||
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
||||
const req = https.request(
|
||||
{
|
||||
hostname,
|
||||
port,
|
||||
path: "/",
|
||||
method: "GET",
|
||||
agent,
|
||||
},
|
||||
res => {
|
||||
res.on("data", () => {});
|
||||
res.on("end", resolve);
|
||||
},
|
||||
);
|
||||
req.on("error", reject);
|
||||
req.end();
|
||||
|
||||
await promise;
|
||||
} finally {
|
||||
server.close();
|
||||
}
|
||||
});
|
||||
|
||||
test("inherits ciphers from agent.options", async () => {
|
||||
const { server, port, hostname } = await createHttpsServer();
|
||||
|
||||
try {
|
||||
const agent = new https.Agent({
|
||||
rejectUnauthorized: false,
|
||||
ciphers: "HIGH:!aNULL:!MD5", // Custom cipher suite
|
||||
});
|
||||
|
||||
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
||||
const req = https.request(
|
||||
{
|
||||
hostname,
|
||||
port,
|
||||
path: "/",
|
||||
method: "GET",
|
||||
agent,
|
||||
},
|
||||
res => {
|
||||
res.on("data", () => {});
|
||||
res.on("end", resolve);
|
||||
},
|
||||
);
|
||||
req.on("error", reject);
|
||||
req.end();
|
||||
|
||||
await promise;
|
||||
} finally {
|
||||
server.close();
|
||||
}
|
||||
});
|
||||
|
||||
test("inherits passphrase from agent.options", async () => {
|
||||
// Create server that accepts connections with encrypted key
|
||||
const { server, port, hostname } = await createHttpsServer({
|
||||
key: tlsCerts.encryptedKey,
|
||||
passphrase: tlsCerts.passphrase,
|
||||
});
|
||||
|
||||
try {
|
||||
// Create an agent with encrypted key and passphrase in options
|
||||
const agent = new https.Agent({
|
||||
ca: tlsCerts.ca,
|
||||
cert: tlsCerts.cert,
|
||||
key: tlsCerts.encryptedKey,
|
||||
passphrase: tlsCerts.passphrase,
|
||||
});
|
||||
|
||||
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
||||
const req = https.request(
|
||||
{
|
||||
hostname,
|
||||
port,
|
||||
path: "/",
|
||||
method: "GET",
|
||||
agent,
|
||||
// NO passphrase here - should inherit from agent.options
|
||||
},
|
||||
res => {
|
||||
res.on("data", () => {});
|
||||
res.on("end", resolve);
|
||||
},
|
||||
);
|
||||
req.on("error", reject);
|
||||
req.end();
|
||||
|
||||
await promise;
|
||||
} finally {
|
||||
server.close();
|
||||
}
|
||||
});
|
||||
|
||||
test("supports multiple CAs (array)", async () => {
|
||||
const { server, port, hostname } = await createHttpsServer();
|
||||
|
||||
try {
|
||||
// Create an agent with CA as an array
|
||||
const agent = new https.Agent({
|
||||
ca: [tlsCerts.ca], // Array of CAs
|
||||
});
|
||||
|
||||
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
||||
const req = https.request(
|
||||
{
|
||||
hostname,
|
||||
port,
|
||||
path: "/",
|
||||
method: "GET",
|
||||
agent,
|
||||
},
|
||||
res => {
|
||||
res.on("data", () => {});
|
||||
res.on("end", resolve);
|
||||
},
|
||||
);
|
||||
req.on("error", reject);
|
||||
req.end();
|
||||
|
||||
await promise;
|
||||
} finally {
|
||||
server.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("TLS error handling", () => {
|
||||
test("rejects self-signed cert when rejectUnauthorized is true", async () => {
|
||||
const { server, port, hostname } = await createHttpsServer();
|
||||
|
||||
try {
|
||||
// Create an agent without CA and with rejectUnauthorized: true (default)
|
||||
const agent = new https.Agent({
|
||||
rejectUnauthorized: true,
|
||||
// NO ca - should fail because cert is self-signed
|
||||
});
|
||||
|
||||
const { promise, resolve, reject } = Promise.withResolvers<Error>();
|
||||
const req = https.request(
|
||||
{
|
||||
hostname,
|
||||
port,
|
||||
path: "/",
|
||||
method: "GET",
|
||||
agent,
|
||||
},
|
||||
() => {
|
||||
reject(new Error("Expected request to fail"));
|
||||
},
|
||||
);
|
||||
req.on("error", resolve);
|
||||
req.end();
|
||||
|
||||
const error = await promise;
|
||||
// Should get a certificate error (self-signed cert not trusted)
|
||||
if (
|
||||
!(
|
||||
error.message.includes("self-signed") ||
|
||||
error.message.includes("SELF_SIGNED") ||
|
||||
error.message.includes("certificate") ||
|
||||
error.message.includes("unable to verify")
|
||||
)
|
||||
) {
|
||||
throw new Error(`Expected certificate error, got: ${error.message}`);
|
||||
}
|
||||
} finally {
|
||||
server.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("http.request agent options", () => {
|
||||
test("does not fail when agent has TLS options (they are ignored for HTTP)", async () => {
|
||||
const { server, port, hostname } = await createHttpServer();
|
||||
|
||||
try {
|
||||
// Create an agent - TLS options passed via constructor should be ignored for HTTP
|
||||
// Using type assertion since http.Agent doesn't normally accept TLS options
|
||||
const agent = new (http.Agent as any)({
|
||||
rejectUnauthorized: false,
|
||||
ca: "some-ca",
|
||||
});
|
||||
|
||||
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
||||
const req = http.request(
|
||||
{
|
||||
hostname,
|
||||
port,
|
||||
path: "/",
|
||||
method: "GET",
|
||||
agent,
|
||||
},
|
||||
res => {
|
||||
res.on("data", () => {});
|
||||
res.on("end", resolve);
|
||||
},
|
||||
);
|
||||
req.on("error", reject);
|
||||
req.end();
|
||||
|
||||
await promise;
|
||||
} finally {
|
||||
server.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Only run in Bun to avoid infinite loop when Node.js runs this file
|
||||
if (typeof Bun !== "undefined") {
|
||||
const { bunEnv, nodeExe } = await import("harness");
|
||||
|
||||
describe("Node.js compatibility", () => {
|
||||
test("all tests pass in Node.js", async () => {
|
||||
const node = nodeExe();
|
||||
if (!node) {
|
||||
throw new Error("Node.js not found in PATH");
|
||||
}
|
||||
|
||||
const testFile = fileURLToPath(import.meta.url);
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [node, "--test", testFile],
|
||||
env: bunEnv,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
]);
|
||||
|
||||
if (exitCode !== 0) {
|
||||
throw new Error(`Node.js tests failed with code ${exitCode}\n${stderr}\n${stdout}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
105
test/regression/issue/026039.test.ts
Normal file
105
test/regression/issue/026039.test.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { bunEnv, bunExe, tempDirWithFiles } from "harness";
|
||||
|
||||
// Test for https://github.com/oven-sh/bun/issues/26039
|
||||
// When parsing a bun.lock file with an empty registry URL for a scoped package,
|
||||
// bun should use the scope-specific registry from bunfig.toml, not the default npm registry.
|
||||
test("frozen lockfile should use scope-specific registry for scoped packages", async () => {
|
||||
const dir = tempDirWithFiles("scoped-registry-test", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-scoped-registry",
|
||||
version: "1.0.0",
|
||||
dependencies: {
|
||||
"@example/test-package": "^1.0.0",
|
||||
},
|
||||
}),
|
||||
"bunfig.toml": `
|
||||
[install.scopes]
|
||||
example = { url = "https://npm.pkg.github.com" }
|
||||
`,
|
||||
// bun.lock with empty string for registry URL - this should trigger the scope lookup
|
||||
"bun.lock": JSON.stringify(
|
||||
{
|
||||
lockfileVersion: 1,
|
||||
workspaces: {
|
||||
"": {
|
||||
dependencies: {
|
||||
"@example/test-package": "^1.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
packages: {
|
||||
"@example/test-package": ["@example/test-package@1.0.0", "", {}, "sha512-AAAA"],
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
});
|
||||
|
||||
// Run bun install --frozen-lockfile. It will fail because the package doesn't exist,
|
||||
// but the error message should show the correct registry URL (npm.pkg.github.com, not registry.npmjs.org)
|
||||
const { stderr, exitCode } = Bun.spawnSync({
|
||||
cmd: [bunExe(), "install", "--frozen-lockfile"],
|
||||
cwd: dir,
|
||||
env: bunEnv,
|
||||
});
|
||||
|
||||
const stderrText = stderr.toString();
|
||||
|
||||
// Before the fix, this would try to fetch from https://registry.npmjs.org/@example/test-package/-/test-package-1.0.0.tgz
|
||||
// After the fix, it should try to fetch from https://npm.pkg.github.com/@example/test-package/-/test-package-1.0.0.tgz
|
||||
expect(stderrText).toContain("npm.pkg.github.com");
|
||||
expect(stderrText).not.toContain("registry.npmjs.org");
|
||||
// The install should fail because the package doesn't exist on the registry
|
||||
expect(exitCode).not.toBe(0);
|
||||
});
|
||||
|
||||
// Test that non-scoped packages still use the default registry when registry URL is empty
|
||||
test("frozen lockfile should use default registry for non-scoped packages", async () => {
|
||||
const dir = tempDirWithFiles("non-scoped-registry-test", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-non-scoped-registry",
|
||||
version: "1.0.0",
|
||||
dependencies: {
|
||||
"fake-nonexistent-package": "^1.0.0",
|
||||
},
|
||||
}),
|
||||
"bunfig.toml": `
|
||||
[install.scopes]
|
||||
example = { url = "https://npm.pkg.github.com" }
|
||||
`,
|
||||
// bun.lock with empty string for registry URL for non-scoped package
|
||||
"bun.lock": JSON.stringify(
|
||||
{
|
||||
lockfileVersion: 1,
|
||||
workspaces: {
|
||||
"": {
|
||||
dependencies: {
|
||||
"fake-nonexistent-package": "^1.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
packages: {
|
||||
"fake-nonexistent-package": ["fake-nonexistent-package@1.0.0", "", {}, "sha512-BBBB"],
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
});
|
||||
|
||||
const { stderr, exitCode } = Bun.spawnSync({
|
||||
cmd: [bunExe(), "install", "--frozen-lockfile"],
|
||||
cwd: dir,
|
||||
env: bunEnv,
|
||||
});
|
||||
|
||||
const stderrText = stderr.toString();
|
||||
|
||||
// Non-scoped packages should still use the default registry
|
||||
expect(stderrText).toContain("registry.npmjs.org");
|
||||
expect(stderrText).not.toContain("npm.pkg.github.com");
|
||||
// The install should fail because the package doesn't exist on the registry
|
||||
expect(exitCode).not.toBe(0);
|
||||
});
|
||||
68
test/regression/issue/3775.test.ts
Normal file
68
test/regression/issue/3775.test.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { bunEnv, bunExe } from "harness";
|
||||
|
||||
// https://github.com/oven-sh/bun/issues/3775
|
||||
// module.register() should emit a warning since it's not implemented
|
||||
test("module.register() emits a warning", async () => {
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "-e", `import { register } from 'node:module'; register('./test.mjs', import.meta.url);`],
|
||||
env: bunEnv,
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
// Check that the warning is emitted
|
||||
expect(stderr).toContain("module.register() is not implemented in Bun");
|
||||
expect(stderr).toContain("BUN_UNSUPPORTED_REGISTER");
|
||||
expect(stderr).toContain("https://bun.sh/docs/bundler/plugins");
|
||||
|
||||
// Exit code should be 0 (warning, not error)
|
||||
expect(exitCode).toBe(0);
|
||||
});
|
||||
|
||||
test("module.register() with require syntax emits a warning", async () => {
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "-e", `const { register } = require('node:module'); register('./test.mjs');`],
|
||||
env: bunEnv,
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
// Check that the warning is emitted
|
||||
expect(stderr).toContain("module.register() is not implemented in Bun");
|
||||
expect(stderr).toContain("BUN_UNSUPPORTED_REGISTER");
|
||||
expect(stderr).toContain("https://bun.sh/docs/bundler/plugins");
|
||||
|
||||
// Exit code should be 0 (warning, not error)
|
||||
expect(exitCode).toBe(0);
|
||||
});
|
||||
|
||||
test("module.register() emits warning only once per call", async () => {
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [
|
||||
bunExe(),
|
||||
"-e",
|
||||
`
|
||||
import { register } from 'node:module';
|
||||
register('./test1.mjs', import.meta.url);
|
||||
register('./test2.mjs', import.meta.url);
|
||||
`,
|
||||
],
|
||||
env: bunEnv,
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
// The warning should be emitted twice (once per call)
|
||||
const warningMatches = stderr.match(/module\.register\(\) is not implemented in Bun/g);
|
||||
expect(warningMatches?.length).toBe(2);
|
||||
|
||||
// Exit code should be 0
|
||||
expect(exitCode).toBe(0);
|
||||
});
|
||||
Reference in New Issue
Block a user