Fix test-{https,tls}-options-boolean-check (#19225)

This commit is contained in:
Alistair Smith
2025-04-23 22:18:43 -07:00
committed by GitHub
parent c97bbe6428
commit 9f0ba15995
7 changed files with 480 additions and 163 deletions

View File

@@ -1,5 +1,3 @@
const { isTypedArray, isArrayBuffer } = require("node:util/types");
const {
getHeader,
setHeader,
@@ -195,19 +193,6 @@ function validateMsecs(numberlike: any, field: string) {
return numberlike;
}
function isValidTLSArray(obj) {
if (typeof obj === "string" || isTypedArray(obj) || isArrayBuffer(obj) || $inheritsBlob(obj)) return true;
if (Array.isArray(obj)) {
const length = obj.length;
for (var i = 0; i < length; i++) {
const item = obj[i];
if (typeof item !== "string" && !isTypedArray(item) && !isArrayBuffer(item) && !$inheritsBlob(item)) return false; // prettier-ignore
}
return true;
}
return false;
}
class ConnResetException extends Error {
constructor(msg) {
super(msg);
@@ -367,83 +352,82 @@ function emitErrorNt(msg, err, callback) {
}
export {
kDeprecatedReplySymbol,
kBodyChunks,
kPath,
kPort,
kMethod,
kHost,
kProtocol,
kAgent,
kFetchRequest,
kTls,
kUseDefaultPort,
kRes,
kUpgradeOrConnect,
kParser,
kMaxHeadersCount,
kReusedSocket,
kTimeoutTimer,
kOptions,
kSocketPath,
kSignal,
kMaxHeaderSize,
abortedSymbol,
kClearTimeout,
emitErrorNextTickIfErrorListenerNT,
headerStateSymbol,
kEmitState,
assignHeadersFast,
bodyStreamSymbol,
controllerSymbol,
runSymbol,
deferredSymbol,
eofInProgress,
fakeSocketSymbol,
firstWriteSymbol,
headersSymbol,
isTlsSymbol,
kHandle,
kRealListen,
noBodySymbol,
optionsSymbol,
reqSymbol,
timeoutTimerSymbol,
tlsSymbol,
typeSymbol,
webRequestOrResponse,
statusCodeSymbol,
kAbortController,
statusMessageSymbol,
kInternalSocketData,
serverSymbol,
kPendingCallbacks,
kRequest,
kCloseCallback,
kDeferredTimeouts,
isAbortError,
kEmptyObject,
getIsNextIncomingMessageHTTPS,
setIsNextIncomingMessageHTTPS,
callCloseCallback,
ConnResetException,
controllerSymbol,
deferredSymbol,
drainMicrotasks,
emitCloseNT,
emitCloseNTAndComplete,
emitEOFIncomingMessage,
validateMsecs,
isValidTLSArray,
ConnResetException,
METHODS,
STATUS_CODES,
hasServerResponseFinished,
getHeader,
setHeader,
Headers,
assignHeadersFast,
setRequestTimeout,
headersTuple,
webRequestOrResponseHasBodyValue,
emitErrorNextTickIfErrorListenerNT,
eofInProgress,
fakeSocketSymbol,
firstWriteSymbol,
getCompleteWebRequestOrResponseBodyValueAsArrayBuffer,
drainMicrotasks,
setServerIdleTimeout,
getHeader,
getIsNextIncomingMessageHTTPS,
getRawKeys,
hasServerResponseFinished,
Headers,
headersSymbol,
headerStateSymbol,
headersTuple,
isAbortError,
isTlsSymbol,
kAbortController,
kAgent,
kBodyChunks,
kClearTimeout,
kCloseCallback,
kDeferredTimeouts,
kDeprecatedReplySymbol,
kEmitState,
kEmptyObject,
kFetchRequest,
kHandle,
kHost,
kInternalSocketData,
kMaxHeadersCount,
kMaxHeaderSize,
kMethod,
kOptions,
kParser,
kPath,
kPendingCallbacks,
kPort,
kProtocol,
kRealListen,
kRequest,
kRes,
kReusedSocket,
kSignal,
kSocketPath,
kTimeoutTimer,
kTls,
kUpgradeOrConnect,
kUseDefaultPort,
METHODS,
noBodySymbol,
optionsSymbol,
reqSymbol,
runSymbol,
serverSymbol,
setHeader,
setIsNextIncomingMessageHTTPS,
setRequestTimeout,
setRequireHostHeader,
setServerIdleTimeout,
STATUS_CODES,
statusCodeSymbol,
statusMessageSymbol,
timeoutTimerSymbol,
tlsSymbol,
typeSymbol,
validateMsecs,
webRequestOrResponse,
webRequestOrResponseHasBodyValue,
};

53
src/js/internal/tls.ts Normal file
View File

@@ -0,0 +1,53 @@
const { isTypedArray, isArrayBuffer } = require("node:util/types");
function isPemObject(obj: unknown): obj is { pem: unknown } {
return $isObject(obj) && "pem" in obj;
}
function isPemArray(obj: unknown): obj is [{ pem: unknown }] {
// if (obj instanceof Object && "pem" in obj) return isValidTLSArray(obj.pem);
return $isArray(obj) && obj.every(isPemObject);
}
function isValidTLSItem(obj: unknown) {
if (typeof obj === "string" || isTypedArray(obj) || isArrayBuffer(obj) || $inheritsBlob(obj) || isPemArray(obj)) {
return true;
}
return false;
}
function findInvalidTLSItem(obj: unknown) {
if ($isArray(obj)) {
for (var i = 0, length = obj.length; i < length; i++) {
const item = obj[i];
if (!isValidTLSItem(item)) return item;
}
}
return obj;
}
function throwOnInvalidTLSArray(name: string, value: unknown) {
if (!isValidTLSArray(value)) {
throw $ERR_INVALID_ARG_TYPE(name, VALID_TLS_ERROR_MESSAGE_TYPES, findInvalidTLSItem(value));
}
}
function isValidTLSArray(obj: unknown) {
if (isValidTLSItem(obj)) return true;
if ($isArray(obj)) {
for (var i = 0, length = obj.length; i < length; i++) {
const item = obj[i];
if (!isValidTLSItem(item)) return false;
}
return true;
}
return false;
}
const VALID_TLS_ERROR_MESSAGE_TYPES = "string or an instance of Buffer, TypedArray, DataView, or BunFile";
export { isValidTLSArray, isValidTLSItem, throwOnInvalidTLSArray, VALID_TLS_ERROR_MESSAGE_TYPES };

View File

@@ -2,6 +2,7 @@ const { isIP, isIPv6 } = require("node:net");
const { checkIsHttpToken, validateFunction, validateInteger, validateBoolean } = require("internal/validators");
const { urlToHttpOptions } = require("internal/url");
const { isValidTLSArray } = require("internal/tls");
const {
kBodyChunks,
abortedSymbol,
@@ -39,7 +40,6 @@ const {
callCloseCallback,
emitCloseNTAndComplete,
validateMsecs,
isValidTLSArray,
ConnResetException,
} = require("internal/http");

View File

@@ -3,7 +3,7 @@ const { Duplex, Stream } = require("node:stream");
const { validateObject, validateLinkHeaderValue, validateBoolean, validateInteger } = require("internal/validators");
const { isPrimary } = require("internal/cluster/isPrimary");
const { throwOnInvalidTLSArray } = require("internal/tls");
const {
kInternalSocketData,
serverSymbol,
@@ -25,7 +25,6 @@ const {
setIsNextIncomingMessageHTTPS,
callCloseCallback,
emitCloseNT,
isValidTLSArray,
ConnResetException,
NodeHTTPResponseAbortEvent,
STATUS_CODES,
@@ -601,47 +600,38 @@ const Server = function Server(options, callback) {
} else {
validateObject(options, "options");
options = { ...options };
let key = options.key;
if (key) {
if (!isValidTLSArray(key)) {
throw new TypeError(
"key argument must be a string, Buffer, TypedArray or BunFile, or an array containing string, Buffer, TypedArray or BunFile",
);
}
this[isTlsSymbol] = true;
}
let cert = options.cert;
if (cert) {
if (!isValidTLSArray(cert)) {
throw new TypeError(
"cert argument must be a string, Buffer, TypedArray or BunFile, or an array containing string, Buffer, TypedArray or BunFile",
);
}
throwOnInvalidTLSArray("options.cert", cert);
this[isTlsSymbol] = true;
}
let key = options.key;
if (key) {
throwOnInvalidTLSArray("options.key", key);
this[isTlsSymbol] = true;
}
let ca = options.ca;
if (ca) {
if (!isValidTLSArray(ca)) {
throw new TypeError(
"ca argument must be a string, Buffer, TypedArray or BunFile, or an array containing string, Buffer, TypedArray or BunFile",
);
}
throwOnInvalidTLSArray("options.ca", ca);
this[isTlsSymbol] = true;
}
let passphrase = options.passphrase;
if (passphrase && typeof passphrase !== "string") {
throw new TypeError("passphrase argument must be a string");
throw $ERR_INVALID_ARG_TYPE("options.passphrase", "string", passphrase);
}
let serverName = options.servername;
if (serverName && typeof serverName !== "string") {
throw new TypeError("serverName argument must be a string");
throw $ERR_INVALID_ARG_TYPE("options.servername", "string", serverName);
}
let secureOptions = options.secureOptions || 0;
if (secureOptions && typeof secureOptions !== "number") {
throw new TypeError("secureOptions argument must be an number");
throw $ERR_INVALID_ARG_TYPE("options.secureOptions", "number", secureOptions);
}
if (this[isTlsSymbol]) {

View File

@@ -1,9 +1,10 @@
// Hardcoded module "node:tls"
const { isArrayBufferView, isArrayBuffer, isTypedArray } = require("node:util/types");
const { isArrayBufferView, isTypedArray } = require("node:util/types");
const net = require("node:net");
const { Duplex } = require("node:stream");
const { addServerName } = require("internal/net");
const { throwNotImplemented } = require("internal/shared");
const { throwOnInvalidTLSArray } = require("internal/tls");
const { Server: NetServer, Socket: NetSocket } = net;
@@ -37,17 +38,6 @@ function parseCertString() {
const rejectUnauthorizedDefault =
process.env.NODE_TLS_REJECT_UNAUTHORIZED !== "0" && process.env.NODE_TLS_REJECT_UNAUTHORIZED !== "false";
function isValidTLSArray(obj) {
if (typeof obj === "string" || isTypedArray(obj) || isArrayBuffer(obj) || $inheritsBlob(obj)) return true;
if (Array.isArray(obj)) {
for (var i = 0; i < obj.length; i++) {
const item = obj[i];
if (typeof item !== "string" && !isTypedArray(item) && !isArrayBuffer(item) && !$inheritsBlob(item)) return false;
}
return true;
}
return false;
}
function unfqdn(host) {
return RegExpPrototypeSymbolReplace.$call(/[.]$/, host, "");
@@ -215,33 +205,23 @@ var InternalSecureContext = class SecureContext {
constructor(options) {
const context = {};
if (options) {
let key = options.key;
if (key) {
if (!isValidTLSArray(key)) {
throw new TypeError(
"key argument must be an string, Buffer, TypedArray, BunFile or an array containing string, Buffer, TypedArray or BunFile",
);
}
this.key = key;
}
let cert = options.cert;
if (cert) {
if (!isValidTLSArray(cert)) {
throw new TypeError(
"cert argument must be an string, Buffer, TypedArray, BunFile or an array containing string, Buffer, TypedArray or BunFile",
);
}
throwOnInvalidTLSArray("options.cert", cert);
this.cert = cert;
}
let key = options.key;
if (key) {
throwOnInvalidTLSArray("options.key", key);
this.key = key;
}
let ca = options.ca;
if (ca) {
if (!isValidTLSArray(ca)) {
throw new TypeError(
"ca argument must be an string, Buffer, TypedArray, BunFile or an array containing string, Buffer, TypedArray or BunFile",
);
}
throwOnInvalidTLSArray("options.ca", ca);
this.ca = ca;
}
@@ -536,50 +516,39 @@ function Server(options, secureConnectionListener): void {
convertALPNProtocols(ALPNProtocols, this);
}
let key = options.key;
if (key) {
if (!isValidTLSArray(key)) {
throw new TypeError(
"key argument must be an string, Buffer, TypedArray, BunFile or an array containing string, Buffer, TypedArray or BunFile",
);
}
this.key = key;
}
let cert = options.cert;
if (cert) {
if (!isValidTLSArray(cert)) {
throw new TypeError(
"cert argument must be an string, Buffer, TypedArray, BunFile or an array containing string, Buffer, TypedArray or BunFile",
);
}
throwOnInvalidTLSArray("options.cert", cert);
this.cert = cert;
}
let key = options.key;
if (key) {
throwOnInvalidTLSArray("options.key", key);
this.key = key;
}
let ca = options.ca;
if (ca) {
if (!isValidTLSArray(ca)) {
throw new TypeError(
"ca argument must be an string, Buffer, TypedArray, BunFile or an array containing string, Buffer, TypedArray or BunFile",
);
}
throwOnInvalidTLSArray("options.ca", ca);
this.ca = ca;
}
let passphrase = options.passphrase;
if (passphrase && typeof passphrase !== "string") {
throw new TypeError("passphrase argument must be an string");
throw $ERR_INVALID_ARG_TYPE("options.passphrase", "string", passphrase);
}
this.passphrase = passphrase;
let servername = options.servername;
if (servername && typeof servername !== "string") {
throw new TypeError("servername argument must be an string");
throw $ERR_INVALID_ARG_TYPE("options.servername", "string", servername);
}
this.servername = servername;
let secureOptions = options.secureOptions || 0;
if (secureOptions && typeof secureOptions !== "number") {
throw new TypeError("secureOptions argument must be an number");
throw $ERR_INVALID_ARG_TYPE("options.secureOptions", "number", secureOptions);
}
this.secureOptions = secureOptions;

View File

@@ -0,0 +1,154 @@
const common = require('../common');
const fixtures = require('../common/fixtures');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const https = require('https');
function toArrayBuffer(buf) {
const ab = new ArrayBuffer(buf.length);
const view = new Uint8Array(ab);
return buf.map((b, i) => view[i] = b);
}
function toDataView(buf) {
const ab = new ArrayBuffer(buf.length);
const view = new DataView(ab);
return buf.map((b, i) => view[i] = b);
}
const keyBuff = fixtures.readKey('agent1-key.pem');
const certBuff = fixtures.readKey('agent1-cert.pem');
const keyBuff2 = fixtures.readKey('ec-key.pem');
const certBuff2 = fixtures.readKey('ec-cert.pem');
const caCert = fixtures.readKey('ca1-cert.pem');
const caCert2 = fixtures.readKey('ca2-cert.pem');
const keyStr = keyBuff.toString();
const certStr = certBuff.toString();
const keyStr2 = keyBuff2.toString();
const certStr2 = certBuff2.toString();
const caCertStr = caCert.toString();
const caCertStr2 = caCert2.toString();
const keyArrBuff = toArrayBuffer(keyBuff);
const certArrBuff = toArrayBuffer(certBuff);
const caArrBuff = toArrayBuffer(caCert);
const keyDataView = toDataView(keyBuff);
const certDataView = toDataView(certBuff);
const caArrDataView = toDataView(caCert);
// Checks to ensure https.createServer doesn't throw an error
// Format ['key', 'cert']
[
[keyBuff, certBuff],
[false, certBuff],
[keyBuff, false],
[keyStr, certStr],
[false, certStr],
[keyStr, false],
[false, false],
[keyArrBuff, certArrBuff],
[keyArrBuff, false],
[false, certArrBuff],
[keyDataView, certDataView],
[keyDataView, false],
[false, certDataView],
[[keyBuff, keyBuff2], [certBuff, certBuff2]],
[[keyStr, keyStr2], [certStr, certStr2]],
[[keyStr, keyStr2], false],
[false, [certStr, certStr2]],
[[{ pem: keyBuff }], false],
[[{ pem: keyBuff }, { pem: keyBuff }], false],
].forEach(([key, cert]) => {
https.createServer({ key, cert });
});
// Checks to ensure https.createServer predictably throws an error
// Format ['key', 'cert', 'expected message']
[
[true, certBuff],
[true, certStr],
[true, certArrBuff],
[true, certDataView],
[true, false],
[true, false],
[{ pem: keyBuff }, false],
[1, false],
[[keyBuff, true], [certBuff, certBuff2], 1],
[[true, keyStr2], [certStr, certStr2], 0],
[[true, false], [certBuff, certBuff2], 0],
[true, [certBuff, certBuff2]],
].forEach(([key, cert, index]) => {
const val = index === undefined ? key : key[index];
assert.throws(() => {
https.createServer({ key, cert });
}, {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: 'The "options.key" property must be of type string or an ' +
'instance of Buffer, TypedArray, DataView, or BunFile.' +
common.invalidArgTypeHelper(val)
});
});
[
[keyBuff, true],
[keyStr, true],
[keyArrBuff, true],
[keyDataView, true],
[true, true],
[false, true],
[false, { pem: keyBuff }],
[false, 1],
[[keyBuff, keyBuff2], [true, certBuff2], 0],
[[keyStr, keyStr2], [certStr, true], 1],
[[keyStr, keyStr2], [true, false], 0],
[[keyStr, keyStr2], true],
].forEach(([key, cert, index]) => {
const val = index === undefined ? cert : cert[index];
assert.throws(() => {
https.createServer({ key, cert });
}, {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: 'The "options.cert" property must be of type string or an ' +
'instance of Buffer, TypedArray, DataView, or BunFile.' +
common.invalidArgTypeHelper(val)
});
});
// Checks to ensure https.createServer works with the CA parameter
// Format ['key', 'cert', 'ca']
[
[keyBuff, certBuff, caCert],
[keyBuff, certBuff, [caCert, caCert2]],
[keyBuff, certBuff, caCertStr],
[keyBuff, certBuff, [caCertStr, caCertStr2]],
[keyBuff, certBuff, caArrBuff],
[keyBuff, certBuff, caArrDataView],
[keyBuff, certBuff, false],
].forEach(([key, cert, ca]) => {
https.createServer({ key, cert, ca });
});
// Checks to ensure https.createServer throws an error for CA assignment
// Format ['key', 'cert', 'ca']
[
[keyBuff, certBuff, true],
[keyBuff, certBuff, {}],
[keyBuff, certBuff, 1],
[keyBuff, certBuff, true],
[keyBuff, certBuff, [caCert, true], 1],
].forEach(([key, cert, ca, index]) => {
const val = index === undefined ? ca : ca[index];
assert.throws(() => {
https.createServer({ key, cert, ca });
}, {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: 'The "options.ca" property must be of type string or an instance' +
' of Buffer, TypedArray, DataView, or BunFile.' +
common.invalidArgTypeHelper(val)
});
});

View File

@@ -0,0 +1,167 @@
'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
function toArrayBuffer(buf) {
const ab = new ArrayBuffer(buf.length);
const view = new Uint8Array(ab);
return buf.map((b, i) => view[i] = b);
}
function toDataView(buf) {
const ab = new ArrayBuffer(buf.length);
const view = new DataView(ab);
return buf.map((b, i) => view[i] = b);
}
const keyBuff = fixtures.readKey('agent1-key.pem');
const certBuff = fixtures.readKey('agent1-cert.pem');
const keyBuff2 = fixtures.readKey('ec-key.pem');
const certBuff2 = fixtures.readKey('ec-cert.pem');
const caCert = fixtures.readKey('ca1-cert.pem');
const caCert2 = fixtures.readKey('ca2-cert.pem');
const keyStr = keyBuff.toString();
const certStr = certBuff.toString();
const keyStr2 = keyBuff2.toString();
const certStr2 = certBuff2.toString();
const caCertStr = caCert.toString();
const caCertStr2 = caCert2.toString();
const keyArrBuff = toArrayBuffer(keyBuff);
const certArrBuff = toArrayBuffer(certBuff);
const caArrBuff = toArrayBuffer(caCert);
const keyDataView = toDataView(keyBuff);
const certDataView = toDataView(certBuff);
const caArrDataView = toDataView(caCert);
// Checks to ensure tls.createServer doesn't throw an error
// Format ['key', 'cert']
[
[keyBuff, certBuff],
[false, certBuff],
[keyBuff, false],
[keyStr, certStr],
[false, certStr],
[keyStr, false],
[false, false],
[keyArrBuff, certArrBuff],
[keyArrBuff, false],
[false, certArrBuff],
[keyDataView, certDataView],
[keyDataView, false],
[false, certDataView],
[[keyBuff, keyBuff2], [certBuff, certBuff2]],
[[keyStr, keyStr2], [certStr, certStr2]],
[[keyStr, keyStr2], false],
[false, [certStr, certStr2]],
[[{ pem: keyBuff }], false],
[[{ pem: keyBuff }, { pem: keyBuff }], false],
].forEach(([key, cert]) => {
tls.createServer({ key, cert });
});
// Checks to ensure tls.createServer predictably throws an error
// Format ['key', 'cert', 'expected message']
[
[true, certBuff],
[true, certStr],
[true, certArrBuff],
[true, certDataView],
[true, false],
[true, false],
[{ pem: keyBuff }, false],
[[keyBuff, true], [certBuff, certBuff2], 1],
[[true, keyStr2], [certStr, certStr2], 0],
[[true, false], [certBuff, certBuff2], 0],
[true, [certBuff, certBuff2]],
].forEach(([key, cert, index]) => {
const val = index === undefined ? key : key[index];
assert.throws(() => {
tls.createServer({ key, cert });
}, {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: 'The "options.key" property must be of type string or an ' +
'instance of Buffer, TypedArray, DataView, or BunFile.' +
common.invalidArgTypeHelper(val)
});
});
[
[keyBuff, true],
[keyStr, true],
[keyArrBuff, true],
[keyDataView, true],
[true, true],
[false, true],
[false, { pem: keyBuff }],
[false, 1],
[[keyBuff, keyBuff2], [true, certBuff2], 0],
[[keyStr, keyStr2], [certStr, true], 1],
[[keyStr, keyStr2], [true, false], 0],
[[keyStr, keyStr2], true],
].forEach(([key, cert, index]) => {
const val = index === undefined ? cert : cert[index];
assert.throws(() => {
tls.createServer({ key, cert });
}, {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: 'The "options.cert" property must be of type string or an ' +
'instance of Buffer, TypedArray, DataView, or BunFile.' +
common.invalidArgTypeHelper(val)
});
});
// Checks to ensure tls.createServer works with the CA parameter
// Format ['key', 'cert', 'ca']
[
[keyBuff, certBuff, caCert],
[keyBuff, certBuff, [caCert, caCert2]],
[keyBuff, certBuff, caCertStr],
[keyBuff, certBuff, [caCertStr, caCertStr2]],
[keyBuff, certBuff, caArrBuff],
[keyBuff, certBuff, caArrDataView],
[keyBuff, certBuff, false],
].forEach(([key, cert, ca]) => {
tls.createServer({ key, cert, ca });
});
// Checks to ensure tls.createServer throws an error for CA assignment
// Format ['key', 'cert', 'ca']
[
[keyBuff, certBuff, true],
[keyBuff, certBuff, {}],
[keyBuff, certBuff, 1],
[keyBuff, certBuff, true],
[keyBuff, certBuff, [caCert, true], 1],
].forEach(([key, cert, ca, index]) => {
const val = index === undefined ? ca : ca[index];
assert.throws(() => {
tls.createServer({ key, cert, ca });
}, {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: 'The "options.ca" property must be of type string or an instance' +
' of Buffer, TypedArray, DataView, or BunFile.' +
common.invalidArgTypeHelper(val)
});
});
// Checks to ensure tls.createSecureContext works with false-y input
// Format ['key', 'cert', 'ca']
[
[null, null, null],
[false, false, false],
[undefined, undefined, undefined],
['', '', ''],
[0, 0, 0],
].forEach(([key, cert, ca]) => {
tls.createSecureContext({ key, cert, ca });
});