Upload Kmake

This commit is contained in:
Gorochu
2026-05-26 23:36:42 -07:00
parent ba051b2f74
commit 555ec72358
41615 changed files with 13344630 additions and 1 deletions

547
lib/_http_agent.js Normal file
View File

@ -0,0 +1,547 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const {
NumberParseInt,
ObjectKeys,
ObjectSetPrototypeOf,
ObjectValues,
Symbol,
} = primordials;
const net = require('net');
const EventEmitter = require('events');
let debug = require('internal/util/debuglog').debuglog('http', (fn) => {
debug = fn;
});
const { AsyncResource } = require('async_hooks');
const { async_id_symbol } = require('internal/async_hooks').symbols;
const {
kEmptyObject,
once,
} = require('internal/util');
const {
validateNumber,
validateOneOf,
validateString,
} = require('internal/validators');
const kOnKeylog = Symbol('onkeylog');
const kRequestOptions = Symbol('requestOptions');
const kRequestAsyncResource = Symbol('requestAsyncResource');
// TODO(jazelly): make this configurable
const HTTP_AGENT_KEEP_ALIVE_TIMEOUT_BUFFER = 1000;
// New Agent code.
// The largest departure from the previous implementation is that
// an Agent instance holds connections for a variable number of host:ports.
// Surprisingly, this is still API compatible as far as third parties are
// concerned. The only code that really notices the difference is the
// request object.
// Another departure is that all code related to HTTP parsing is in
// ClientRequest.onSocket(). The Agent is now *strictly*
// concerned with managing a connection pool.
class ReusedHandle {
constructor(type, handle) {
this.type = type;
this.handle = handle;
}
}
function freeSocketErrorListener(err) {
const socket = this;
debug('SOCKET ERROR on FREE socket:', err.message, err.stack);
socket.destroy();
socket.emit('agentRemove');
}
function Agent(options) {
if (!(this instanceof Agent))
return new Agent(options);
EventEmitter.call(this);
this.defaultPort = 80;
this.protocol = 'http:';
this.options = { __proto__: null, ...options };
if (this.options.noDelay === undefined)
this.options.noDelay = true;
// Don't confuse net and make it think that we're connecting to a pipe
this.options.path = null;
this.requests = { __proto__: null };
this.sockets = { __proto__: null };
this.freeSockets = { __proto__: null };
this.keepAliveMsecs = this.options.keepAliveMsecs || 1000;
this.keepAlive = this.options.keepAlive || false;
this.maxSockets = this.options.maxSockets || Agent.defaultMaxSockets;
this.maxFreeSockets = this.options.maxFreeSockets || 256;
this.scheduling = this.options.scheduling || 'lifo';
this.maxTotalSockets = this.options.maxTotalSockets;
this.totalSocketCount = 0;
validateOneOf(this.scheduling, 'scheduling', ['fifo', 'lifo']);
if (this.maxTotalSockets !== undefined) {
validateNumber(this.maxTotalSockets, 'maxTotalSockets', 1);
} else {
this.maxTotalSockets = Infinity;
}
this.on('free', (socket, options) => {
const name = this.getName(options);
debug('agent.on(free)', name);
// TODO(ronag): socket.destroy(err) might have been called
// before coming here and have an 'error' scheduled. In the
// case of socket.destroy() below this 'error' has no handler
// and could cause unhandled exception.
if (!socket.writable) {
socket.destroy();
return;
}
const requests = this.requests[name];
if (requests?.length) {
const req = requests.shift();
const reqAsyncRes = req[kRequestAsyncResource];
if (reqAsyncRes) {
// Run request within the original async context.
reqAsyncRes.runInAsyncScope(() => {
asyncResetHandle(socket);
setRequestSocket(this, req, socket);
});
req[kRequestAsyncResource] = null;
} else {
setRequestSocket(this, req, socket);
}
if (requests.length === 0) {
delete this.requests[name];
}
return;
}
// If there are no pending requests, then put it in
// the freeSockets pool, but only if we're allowed to do so.
const req = socket._httpMessage;
if (!req || !req.shouldKeepAlive || !this.keepAlive) {
socket.destroy();
return;
}
const freeSockets = this.freeSockets[name] || [];
const freeLen = freeSockets.length;
let count = freeLen;
if (this.sockets[name])
count += this.sockets[name].length;
if (this.totalSocketCount > this.maxTotalSockets ||
count > this.maxSockets ||
freeLen >= this.maxFreeSockets ||
!this.keepSocketAlive(socket)) {
socket.destroy();
return;
}
this.freeSockets[name] = freeSockets;
socket[async_id_symbol] = -1;
socket._httpMessage = null;
this.removeSocket(socket, options);
socket.once('error', freeSocketErrorListener);
freeSockets.push(socket);
});
// Don't emit keylog events unless there is a listener for them.
this.on('newListener', maybeEnableKeylog);
}
ObjectSetPrototypeOf(Agent.prototype, EventEmitter.prototype);
ObjectSetPrototypeOf(Agent, EventEmitter);
function maybeEnableKeylog(eventName) {
if (eventName === 'keylog') {
this.removeListener('newListener', maybeEnableKeylog);
// Future sockets will listen on keylog at creation.
const agent = this;
this[kOnKeylog] = function onkeylog(keylog) {
agent.emit('keylog', keylog, this);
};
// Existing sockets will start listening on keylog now.
const sockets = ObjectValues(this.sockets);
for (let i = 0; i < sockets.length; i++) {
sockets[i].on('keylog', this[kOnKeylog]);
}
}
}
Agent.defaultMaxSockets = Infinity;
Agent.prototype.createConnection = net.createConnection;
// Get the key for a given set of request options
Agent.prototype.getName = function getName(options = kEmptyObject) {
let name = options.host || 'localhost';
name += ':';
if (options.port)
name += options.port;
name += ':';
if (options.localAddress)
name += options.localAddress;
// Pacify parallel/test-http-agent-getname by only appending
// the ':' when options.family is set.
if (options.family === 4 || options.family === 6)
name += `:${options.family}`;
if (options.socketPath)
name += `:${options.socketPath}`;
return name;
};
Agent.prototype.addRequest = function addRequest(req, options, port/* legacy */,
localAddress/* legacy */) {
// Legacy API: addRequest(req, host, port, localAddress)
if (typeof options === 'string') {
options = {
__proto__: null,
host: options,
port,
localAddress,
};
}
options = { __proto__: null, ...options, ...this.options };
if (options.socketPath)
options.path = options.socketPath;
normalizeServerName(options, req);
const name = this.getName(options);
this.sockets[name] ||= [];
const freeSockets = this.freeSockets[name];
let socket;
if (freeSockets) {
while (freeSockets.length && freeSockets[0].destroyed) {
freeSockets.shift();
}
socket = this.scheduling === 'fifo' ?
freeSockets.shift() :
freeSockets.pop();
if (!freeSockets.length)
delete this.freeSockets[name];
}
const freeLen = freeSockets ? freeSockets.length : 0;
const sockLen = freeLen + this.sockets[name].length;
if (socket) {
asyncResetHandle(socket);
this.reuseSocket(socket, req);
setRequestSocket(this, req, socket);
this.sockets[name].push(socket);
} else if (sockLen < this.maxSockets &&
this.totalSocketCount < this.maxTotalSockets) {
debug('call onSocket', sockLen, freeLen);
// If we are under maxSockets create a new one.
this.createSocket(req, options, (err, socket) => {
if (err)
req.onSocket(socket, err);
else
setRequestSocket(this, req, socket);
});
} else {
debug('wait for socket');
// We are over limit so we'll add it to the queue.
this.requests[name] ||= [];
// Used to create sockets for pending requests from different origin
req[kRequestOptions] = options;
// Used to capture the original async context.
req[kRequestAsyncResource] = new AsyncResource('QueuedRequest');
this.requests[name].push(req);
}
};
Agent.prototype.createSocket = function createSocket(req, options, cb) {
options = { __proto__: null, ...options, ...this.options };
if (options.socketPath)
options.path = options.socketPath;
normalizeServerName(options, req);
const name = this.getName(options);
options._agentKey = name;
debug('createConnection', name, options);
options.encoding = null;
const oncreate = once((err, s) => {
if (err)
return cb(err);
this.sockets[name] ||= [];
this.sockets[name].push(s);
this.totalSocketCount++;
debug('sockets', name, this.sockets[name].length, this.totalSocketCount);
installListeners(this, s, options);
cb(null, s);
});
// When keepAlive is true, pass the related options to createConnection
if (this.keepAlive) {
options.keepAlive = this.keepAlive;
options.keepAliveInitialDelay = this.keepAliveMsecs;
}
const newSocket = this.createConnection(options, oncreate);
if (newSocket)
oncreate(null, newSocket);
};
function normalizeServerName(options, req) {
if (!options.servername && options.servername !== '')
options.servername = calculateServerName(options, req);
}
function calculateServerName(options, req) {
let servername = options.host;
const hostHeader = req.getHeader('host');
if (hostHeader) {
validateString(hostHeader, 'options.headers.host');
// abc => abc
// abc:123 => abc
// [::1] => ::1
// [::1]:123 => ::1
if (hostHeader[0] === '[') {
const index = hostHeader.indexOf(']');
if (index === -1) {
// Leading '[', but no ']'. Need to do something...
servername = hostHeader;
} else {
servername = hostHeader.substring(1, index);
}
} else {
servername = hostHeader.split(':', 1)[0];
}
}
// Don't implicitly set invalid (IP) servernames.
if (net.isIP(servername))
servername = '';
return servername;
}
function installListeners(agent, s, options) {
function onFree() {
debug('CLIENT socket onFree');
agent.emit('free', s, options);
}
s.on('free', onFree);
function onClose(err) {
debug('CLIENT socket onClose');
// This is the only place where sockets get removed from the Agent.
// If you want to remove a socket from the pool, just close it.
// All socket errors end in a close event anyway.
agent.totalSocketCount--;
agent.removeSocket(s, options);
}
s.on('close', onClose);
function onTimeout() {
debug('CLIENT socket onTimeout');
// Destroy if in free list.
// TODO(ronag): Always destroy, even if not in free list.
const sockets = agent.freeSockets;
if (ObjectKeys(sockets).some((name) => sockets[name].includes(s))) {
return s.destroy();
}
}
s.on('timeout', onTimeout);
function onRemove() {
// We need this function for cases like HTTP 'upgrade'
// (defined by WebSockets) where we need to remove a socket from the
// pool because it'll be locked up indefinitely
debug('CLIENT socket onRemove');
agent.totalSocketCount--;
agent.removeSocket(s, options);
s.removeListener('close', onClose);
s.removeListener('free', onFree);
s.removeListener('timeout', onTimeout);
s.removeListener('agentRemove', onRemove);
}
s.on('agentRemove', onRemove);
if (agent[kOnKeylog]) {
s.on('keylog', agent[kOnKeylog]);
}
}
Agent.prototype.removeSocket = function removeSocket(s, options) {
const name = this.getName(options);
debug('removeSocket', name, 'writable:', s.writable);
const sets = [this.sockets];
// If the socket was destroyed, remove it from the free buffers too.
if (!s.writable)
sets.push(this.freeSockets);
for (let sk = 0; sk < sets.length; sk++) {
const sockets = sets[sk];
if (sockets[name]) {
const index = sockets[name].indexOf(s);
if (index !== -1) {
sockets[name].splice(index, 1);
// Don't leak
if (sockets[name].length === 0)
delete sockets[name];
}
}
}
let req;
if (this.requests[name]?.length) {
debug('removeSocket, have a request, make a socket');
req = this.requests[name][0];
} else {
// TODO(rickyes): this logic will not be FIFO across origins.
// There might be older requests in a different origin, but
// if the origin which releases the socket has pending requests
// that will be prioritized.
const keys = ObjectKeys(this.requests);
for (let i = 0; i < keys.length; i++) {
const prop = keys[i];
// Check whether this specific origin is already at maxSockets
if (this.sockets[prop]?.length) break;
debug('removeSocket, have a request with different origin,' +
' make a socket');
req = this.requests[prop][0];
options = req[kRequestOptions];
break;
}
}
if (req && options) {
req[kRequestOptions] = undefined;
// If we have pending requests and a socket gets closed make a new one
this.createSocket(req, options, (err, socket) => {
if (err)
req.onSocket(socket, err);
else
socket.emit('free');
});
}
};
Agent.prototype.keepSocketAlive = function keepSocketAlive(socket) {
socket.setKeepAlive(true, this.keepAliveMsecs);
socket.unref();
let agentTimeout = this.options.timeout || 0;
let canKeepSocketAlive = true;
if (socket._httpMessage?.res) {
const keepAliveHint = socket._httpMessage.res.headers['keep-alive'];
if (keepAliveHint) {
const hint = /^timeout=(\d+)/.exec(keepAliveHint)?.[1];
if (hint) {
// Let the timer expire before the announced timeout to reduce
// the likelihood of ECONNRESET errors
let serverHintTimeout = (NumberParseInt(hint) * 1000) - HTTP_AGENT_KEEP_ALIVE_TIMEOUT_BUFFER;
serverHintTimeout = serverHintTimeout > 0 ? serverHintTimeout : 0;
if (serverHintTimeout === 0) {
// Cannot safely reuse the socket because the server timeout is
// too short
canKeepSocketAlive = false;
} else if (serverHintTimeout < agentTimeout) {
agentTimeout = serverHintTimeout;
}
}
}
}
if (socket.timeout !== agentTimeout) {
socket.setTimeout(agentTimeout);
}
return canKeepSocketAlive;
};
Agent.prototype.reuseSocket = function reuseSocket(socket, req) {
debug('have free socket');
socket.removeListener('error', freeSocketErrorListener);
req.reusedSocket = true;
socket.ref();
};
Agent.prototype.destroy = function destroy() {
const sets = [this.freeSockets, this.sockets];
for (let s = 0; s < sets.length; s++) {
const set = sets[s];
const keys = ObjectKeys(set);
for (let v = 0; v < keys.length; v++) {
const setName = set[keys[v]];
for (let n = 0; n < setName.length; n++) {
setName[n].destroy();
}
}
}
};
function setRequestSocket(agent, req, socket) {
req.onSocket(socket);
const agentTimeout = agent.options.timeout || 0;
if (req.timeout === undefined || req.timeout === agentTimeout) {
return;
}
socket.setTimeout(req.timeout);
}
function asyncResetHandle(socket) {
// Guard against an uninitialized or user supplied Socket.
const handle = socket._handle;
if (handle && typeof handle.asyncReset === 'function') {
// Assign the handle a new asyncId and run any destroy()/init() hooks.
handle.asyncReset(new ReusedHandle(handle.getProviderType(), handle));
socket[async_id_symbol] = handle.getAsyncId();
}
}
module.exports = {
Agent,
globalAgent: new Agent({ keepAlive: true, scheduling: 'lifo', timeout: 5000 }),
};

996
lib/_http_client.js Normal file
View File

@ -0,0 +1,996 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const {
ArrayIsArray,
Boolean,
Error,
NumberIsFinite,
ObjectAssign,
ObjectKeys,
ObjectSetPrototypeOf,
ReflectApply,
String,
Symbol,
} = primordials;
const net = require('net');
const assert = require('internal/assert');
const {
kEmptyObject,
once,
} = require('internal/util');
const {
_checkIsHttpToken: checkIsHttpToken,
freeParser,
parsers,
HTTPParser,
isLenient,
prepareError,
} = require('_http_common');
const {
kUniqueHeaders,
parseUniqueHeadersOption,
OutgoingMessage,
} = require('_http_outgoing');
const Agent = require('_http_agent');
const { Buffer } = require('buffer');
const { defaultTriggerAsyncIdScope } = require('internal/async_hooks');
const { URL, urlToHttpOptions, isURL } = require('internal/url');
const {
kOutHeaders,
kNeedDrain,
isTraceHTTPEnabled,
traceBegin,
traceEnd,
getNextTraceEventId,
} = require('internal/http');
const {
ConnResetException,
codes: {
ERR_HTTP_HEADERS_SENT,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_HTTP_TOKEN,
ERR_INVALID_PROTOCOL,
ERR_UNESCAPED_CHARACTERS,
},
} = require('internal/errors');
const {
validateInteger,
validateBoolean,
} = require('internal/validators');
const { getTimerDuration } = require('internal/timers');
const {
hasObserver,
startPerf,
stopPerf,
} = require('internal/perf/observe');
const kClientRequestStatistics = Symbol('ClientRequestStatistics');
const dc = require('diagnostics_channel');
const onClientRequestCreatedChannel = dc.channel('http.client.request.created');
const onClientRequestStartChannel = dc.channel('http.client.request.start');
const onClientRequestErrorChannel = dc.channel('http.client.request.error');
const onClientResponseFinishChannel = dc.channel('http.client.response.finish');
function emitErrorEvent(request, error) {
if (onClientRequestErrorChannel.hasSubscribers) {
onClientRequestErrorChannel.publish({
request,
error,
});
}
request.emit('error', error);
}
const { addAbortSignal, finished } = require('stream');
let debug = require('internal/util/debuglog').debuglog('http', (fn) => {
debug = fn;
});
const INVALID_PATH_REGEX = /[^\u0021-\u00ff]/;
const kError = Symbol('kError');
const kLenientAll = HTTPParser.kLenientAll | 0;
const kLenientNone = HTTPParser.kLenientNone | 0;
const HTTP_CLIENT_TRACE_EVENT_NAME = 'http.client.request';
function validateHost(host, name) {
if (host !== null && host !== undefined && typeof host !== 'string') {
throw new ERR_INVALID_ARG_TYPE(`options.${name}`,
['string', 'undefined', 'null'],
host);
}
return host;
}
class HTTPClientAsyncResource {
constructor(type, req) {
this.type = type;
this.req = req;
}
}
function ClientRequest(input, options, cb) {
OutgoingMessage.call(this);
if (typeof input === 'string') {
const urlStr = input;
input = urlToHttpOptions(new URL(urlStr));
} else if (isURL(input)) {
// url.URL instance
input = urlToHttpOptions(input);
} else {
cb = options;
options = input;
input = null;
}
if (typeof options === 'function') {
cb = options;
options = input || kEmptyObject;
} else {
options = ObjectAssign(input || {}, options);
}
let agent = options.agent;
const defaultAgent = options._defaultAgent || Agent.globalAgent;
if (agent === false) {
agent = new defaultAgent.constructor();
} else if (agent === null || agent === undefined) {
if (typeof options.createConnection !== 'function') {
agent = defaultAgent;
}
// Explicitly pass through this statement as agent will not be used
// when createConnection is provided.
} else if (typeof agent.addRequest !== 'function') {
throw new ERR_INVALID_ARG_TYPE('options.agent',
['Agent-like Object', 'undefined', 'false'],
agent);
}
this.agent = agent;
const protocol = options.protocol || defaultAgent.protocol;
let expectedProtocol = defaultAgent.protocol;
if (this.agent?.protocol)
expectedProtocol = this.agent.protocol;
if (options.path) {
const path = String(options.path);
if (INVALID_PATH_REGEX.test(path)) {
debug('Path contains unescaped characters: "%s"', path);
throw new ERR_UNESCAPED_CHARACTERS('Request path');
}
}
if (protocol !== expectedProtocol) {
throw new ERR_INVALID_PROTOCOL(protocol, expectedProtocol);
}
const defaultPort = options.defaultPort ||
(this.agent?.defaultPort);
const optsWithoutSignal = { __proto__: null, ...options };
const port = optsWithoutSignal.port = options.port || defaultPort || 80;
const host = optsWithoutSignal.host = validateHost(options.hostname, 'hostname') ||
validateHost(options.host, 'host') || 'localhost';
const setHost = options.setHost !== undefined ?
Boolean(options.setHost) :
options.setDefaultHeaders !== false;
this._removedConnection = options.setDefaultHeaders === false;
this._removedContLen = options.setDefaultHeaders === false;
this._removedTE = options.setDefaultHeaders === false;
this.socketPath = options.socketPath;
if (options.timeout !== undefined)
this.timeout = getTimerDuration(options.timeout, 'timeout');
const signal = options.signal;
if (signal) {
addAbortSignal(signal, this);
delete optsWithoutSignal.signal;
}
let method = options.method;
const methodIsString = (typeof method === 'string');
if (method !== null && method !== undefined && !methodIsString) {
throw new ERR_INVALID_ARG_TYPE('options.method', 'string', method);
}
if (methodIsString && method) {
if (!checkIsHttpToken(method)) {
throw new ERR_INVALID_HTTP_TOKEN('Method', method);
}
method = this.method = method.toUpperCase();
} else {
method = this.method = 'GET';
}
const maxHeaderSize = options.maxHeaderSize;
if (maxHeaderSize !== undefined)
validateInteger(maxHeaderSize, 'maxHeaderSize', 0);
this.maxHeaderSize = maxHeaderSize;
const insecureHTTPParser = options.insecureHTTPParser;
if (insecureHTTPParser !== undefined) {
validateBoolean(insecureHTTPParser, 'options.insecureHTTPParser');
}
this.insecureHTTPParser = insecureHTTPParser;
if (options.joinDuplicateHeaders !== undefined) {
validateBoolean(options.joinDuplicateHeaders, 'options.joinDuplicateHeaders');
}
this.joinDuplicateHeaders = options.joinDuplicateHeaders;
this.path = options.path || '/';
if (cb) {
this.once('response', cb);
}
if (method === 'GET' ||
method === 'HEAD' ||
method === 'DELETE' ||
method === 'OPTIONS' ||
method === 'TRACE' ||
method === 'CONNECT') {
this.useChunkedEncodingByDefault = false;
} else {
this.useChunkedEncodingByDefault = true;
}
this._ended = false;
this.res = null;
this.aborted = false;
this.timeoutCb = null;
this.upgradeOrConnect = false;
this.parser = null;
this.maxHeadersCount = null;
this.reusedSocket = false;
this.host = host;
this.protocol = protocol;
if (this.agent) {
// If there is an agent we should default to Connection:keep-alive,
// but only if the Agent will actually reuse the connection!
// If it's not a keepAlive agent, and the maxSockets==Infinity, then
// there's never a case where this socket will actually be reused
if (!this.agent.keepAlive && !NumberIsFinite(this.agent.maxSockets)) {
this._last = true;
this.shouldKeepAlive = false;
} else {
this._last = false;
this.shouldKeepAlive = true;
}
}
const headersArray = ArrayIsArray(options.headers);
if (!headersArray) {
if (options.headers) {
const keys = ObjectKeys(options.headers);
// Retain for(;;) loop for performance reasons
// Refs: https://github.com/nodejs/node/pull/30958
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
this.setHeader(key, options.headers[key]);
}
}
if (host && !this.getHeader('host') && setHost) {
let hostHeader = host;
// For the Host header, ensure that IPv6 addresses are enclosed
// in square brackets, as defined by URI formatting
// https://tools.ietf.org/html/rfc3986#section-3.2.2
const posColon = hostHeader.indexOf(':');
if (posColon !== -1 &&
hostHeader.includes(':', posColon + 1) &&
hostHeader.charCodeAt(0) !== 91/* '[' */) {
hostHeader = `[${hostHeader}]`;
}
if (port && +port !== defaultPort) {
hostHeader += ':' + port;
}
this.setHeader('Host', hostHeader);
}
if (options.auth && !this.getHeader('Authorization')) {
this.setHeader('Authorization', 'Basic ' +
Buffer.from(options.auth).toString('base64'));
}
if (this.getHeader('expect')) {
if (this._header) {
throw new ERR_HTTP_HEADERS_SENT('render');
}
this._storeHeader(this.method + ' ' + this.path + ' HTTP/1.1\r\n',
this[kOutHeaders]);
}
} else {
this._storeHeader(this.method + ' ' + this.path + ' HTTP/1.1\r\n',
options.headers);
}
this[kUniqueHeaders] = parseUniqueHeadersOption(options.uniqueHeaders);
// initiate connection
if (this.agent) {
this.agent.addRequest(this, optsWithoutSignal);
} else {
// No agent, default to Connection:close.
this._last = true;
this.shouldKeepAlive = false;
let opts = optsWithoutSignal;
if (opts.path || opts.socketPath) {
opts = { ...optsWithoutSignal };
if (opts.socketPath) {
opts.path = opts.socketPath;
} else {
opts.path &&= undefined;
}
}
if (typeof opts.createConnection === 'function') {
const oncreate = once((err, socket) => {
if (err) {
process.nextTick(() => emitErrorEvent(this, err));
} else {
this.onSocket(socket);
}
});
try {
const newSocket = opts.createConnection(opts, oncreate);
if (newSocket) {
oncreate(null, newSocket);
}
} catch (err) {
oncreate(err);
}
} else {
debug('CLIENT use net.createConnection', opts);
this.onSocket(net.createConnection(opts));
}
}
if (onClientRequestCreatedChannel.hasSubscribers) {
onClientRequestCreatedChannel.publish({
request: this,
});
}
}
ObjectSetPrototypeOf(ClientRequest.prototype, OutgoingMessage.prototype);
ObjectSetPrototypeOf(ClientRequest, OutgoingMessage);
ClientRequest.prototype._finish = function _finish() {
OutgoingMessage.prototype._finish.call(this);
if (hasObserver('http')) {
startPerf(this, kClientRequestStatistics, {
type: 'http',
name: 'HttpClient',
detail: {
req: {
method: this.method,
url: `${this.protocol}//${this.host}${this.path}`,
headers: typeof this.getHeaders === 'function' ? this.getHeaders() : {},
},
},
});
}
if (onClientRequestStartChannel.hasSubscribers) {
onClientRequestStartChannel.publish({
request: this,
});
}
if (isTraceHTTPEnabled()) {
this._traceEventId = getNextTraceEventId();
traceBegin(HTTP_CLIENT_TRACE_EVENT_NAME, this._traceEventId);
}
};
ClientRequest.prototype._implicitHeader = function _implicitHeader() {
if (this._header) {
throw new ERR_HTTP_HEADERS_SENT('render');
}
this._storeHeader(this.method + ' ' + this.path + ' HTTP/1.1\r\n',
this[kOutHeaders]);
};
ClientRequest.prototype.abort = function abort() {
if (this.aborted) {
return;
}
this.aborted = true;
process.nextTick(emitAbortNT, this);
this.destroy();
};
ClientRequest.prototype.destroy = function destroy(err) {
if (this.destroyed) {
return this;
}
this.destroyed = true;
// If we're aborting, we don't care about any more response data.
if (this.res) {
this.res._dump();
}
this[kError] = err;
this.socket?.destroy(err);
return this;
};
function emitAbortNT(req) {
req.emit('abort');
}
function ondrain() {
const msg = this._httpMessage;
if (msg && !msg.finished && msg[kNeedDrain]) {
msg[kNeedDrain] = false;
msg.emit('drain');
}
}
function socketCloseListener() {
const socket = this;
const req = socket._httpMessage;
debug('HTTP socket close');
// NOTE: It's important to get parser here, because it could be freed by
// the `socketOnData`.
const parser = socket.parser;
const res = req.res;
req.destroyed = true;
if (res) {
// Socket closed before we emitted 'end' below.
if (!res.complete) {
res.destroy(new ConnResetException('aborted'));
}
req._closed = true;
req.emit('close');
if (!res.aborted && res.readable) {
res.push(null);
}
} else {
if (!req.socket._hadError) {
// This socket error fired before we started to
// receive a response. The error needs to
// fire on the request.
req.socket._hadError = true;
emitErrorEvent(req, new ConnResetException('socket hang up'));
}
req._closed = true;
req.emit('close');
}
// Too bad. That output wasn't getting written.
// This is pretty terrible that it doesn't raise an error.
// Fixed better in v0.10
if (req.outputData)
req.outputData.length = 0;
if (parser) {
parser.finish();
freeParser(parser, req, socket);
}
}
function socketErrorListener(err) {
const socket = this;
const req = socket._httpMessage;
debug('SOCKET ERROR:', err.message, err.stack);
if (req) {
// For Safety. Some additional errors might fire later on
// and we need to make sure we don't double-fire the error event.
req.socket._hadError = true;
emitErrorEvent(req, err);
}
const parser = socket.parser;
if (parser) {
parser.finish();
freeParser(parser, req, socket);
}
// Ensure that no further data will come out of the socket
socket.removeListener('data', socketOnData);
socket.removeListener('end', socketOnEnd);
socket.destroy();
}
function socketOnEnd() {
const socket = this;
const req = this._httpMessage;
const parser = this.parser;
if (!req.res && !req.socket._hadError) {
// If we don't have a response then we know that the socket
// ended prematurely and we need to emit an error on the request.
req.socket._hadError = true;
emitErrorEvent(req, new ConnResetException('socket hang up'));
}
if (parser) {
parser.finish();
freeParser(parser, req, socket);
}
socket.destroy();
}
function socketOnData(d) {
const socket = this;
const req = this._httpMessage;
const parser = this.parser;
assert(parser && parser.socket === socket);
const ret = parser.execute(d);
if (ret instanceof Error) {
prepareError(ret, parser, d);
debug('parse error', ret);
freeParser(parser, req, socket);
socket.removeListener('data', socketOnData);
socket.removeListener('end', socketOnEnd);
socket.destroy();
req.socket._hadError = true;
emitErrorEvent(req, ret);
} else if (parser.incoming?.upgrade) {
// Upgrade (if status code 101) or CONNECT
const bytesParsed = ret;
const res = parser.incoming;
req.res = res;
socket.removeListener('data', socketOnData);
socket.removeListener('end', socketOnEnd);
socket.removeListener('drain', ondrain);
if (req.timeoutCb) socket.removeListener('timeout', req.timeoutCb);
socket.removeListener('timeout', responseOnTimeout);
parser.finish();
freeParser(parser, req, socket);
const bodyHead = d.slice(bytesParsed, d.length);
const eventName = req.method === 'CONNECT' ? 'connect' : 'upgrade';
if (req.listenerCount(eventName) > 0) {
req.upgradeOrConnect = true;
// detach the socket
socket.emit('agentRemove');
socket.removeListener('close', socketCloseListener);
socket.removeListener('error', socketErrorListener);
socket._httpMessage = null;
socket.readableFlowing = null;
req.emit(eventName, res, socket, bodyHead);
req.destroyed = true;
req._closed = true;
req.emit('close');
} else {
// Requested Upgrade or used CONNECT method, but have no handler.
socket.destroy();
}
} else if (parser.incoming?.complete &&
// When the status code is informational (100, 102-199),
// the server will send a final response after this client
// sends a request body, so we must not free the parser.
// 101 (Switching Protocols) and all other status codes
// should be processed normally.
!statusIsInformational(parser.incoming.statusCode)) {
socket.removeListener('data', socketOnData);
socket.removeListener('end', socketOnEnd);
socket.removeListener('drain', ondrain);
freeParser(parser, req, socket);
}
}
function statusIsInformational(status) {
// 100 (Continue) RFC7231 Section 6.2.1
// 102 (Processing) RFC2518
// 103 (Early Hints) RFC8297
// 104-199 (Unassigned)
return (status < 200 && status >= 100 && status !== 101);
}
// client
function parserOnIncomingClient(res, shouldKeepAlive) {
const socket = this.socket;
const req = socket._httpMessage;
debug('AGENT incoming response!');
if (req.res) {
// We already have a response object, this means the server
// sent a double response.
socket.destroy();
return 0; // No special treatment.
}
req.res = res;
// Skip body and treat as Upgrade.
if (res.upgrade)
return 2;
// Responses to CONNECT request is handled as Upgrade.
const method = req.method;
if (method === 'CONNECT') {
res.upgrade = true;
return 2; // Skip body and treat as Upgrade.
}
if (statusIsInformational(res.statusCode)) {
// Restart the parser, as this is a 1xx informational message.
req.res = null; // Clear res so that we don't hit double-responses.
// Maintain compatibility by sending 100-specific events
if (res.statusCode === 100) {
req.emit('continue');
}
// Send information events to all 1xx responses except 101 Upgrade.
req.emit('information', {
statusCode: res.statusCode,
statusMessage: res.statusMessage,
httpVersion: res.httpVersion,
httpVersionMajor: res.httpVersionMajor,
httpVersionMinor: res.httpVersionMinor,
headers: res.headers,
rawHeaders: res.rawHeaders,
});
return 1; // Skip body but don't treat as Upgrade.
}
if (req.shouldKeepAlive && !shouldKeepAlive && !req.upgradeOrConnect) {
// Server MUST respond with Connection:keep-alive for us to enable it.
// If we've been upgraded (via WebSockets) we also shouldn't try to
// keep the connection open.
req.shouldKeepAlive = false;
}
if (req[kClientRequestStatistics] && hasObserver('http')) {
stopPerf(req, kClientRequestStatistics, {
detail: {
res: {
statusCode: res.statusCode,
statusMessage: res.statusMessage,
headers: res.headers,
},
},
});
}
if (onClientResponseFinishChannel.hasSubscribers) {
onClientResponseFinishChannel.publish({
request: req,
response: res,
});
}
if (isTraceHTTPEnabled() && typeof req._traceEventId === 'number') {
traceEnd(HTTP_CLIENT_TRACE_EVENT_NAME, req._traceEventId, {
path: req.path,
statusCode: res.statusCode,
});
}
req.res = res;
res.req = req;
// Add our listener first, so that we guarantee socket cleanup
res.on('end', responseOnEnd);
req.on('finish', requestOnFinish);
socket.on('timeout', responseOnTimeout);
// If the user did not listen for the 'response' event, then they
// can't possibly read the data, so we ._dump() it into the void
// so that the socket doesn't hang there in a paused state.
if (req.aborted || !req.emit('response', res))
res._dump();
if (method === 'HEAD')
return 1; // Skip body but don't treat as Upgrade.
if (res.statusCode === 304) {
res.complete = true;
return 1; // Skip body as there won't be any
}
return 0; // No special treatment.
}
// client
function responseKeepAlive(req) {
const socket = req.socket;
debug('AGENT socket keep-alive');
if (req.timeoutCb) {
socket.setTimeout(0, req.timeoutCb);
req.timeoutCb = null;
}
socket.removeListener('close', socketCloseListener);
socket.removeListener('error', socketErrorListener);
socket.removeListener('data', socketOnData);
socket.removeListener('end', socketOnEnd);
// TODO(ronag): Between here and emitFreeNT the socket
// has no 'error' handler.
// There are cases where _handle === null. Avoid those. Passing undefined to
// nextTick() will call getDefaultTriggerAsyncId() to retrieve the id.
const asyncId = socket._handle ? socket._handle.getAsyncId() : undefined;
// Mark this socket as available, AFTER user-added end
// handlers have a chance to run.
defaultTriggerAsyncIdScope(asyncId, process.nextTick, emitFreeNT, req);
req.destroyed = true;
if (req.res) {
// Detach socket from IncomingMessage to avoid destroying the freed
// socket in IncomingMessage.destroy().
req.res.socket = null;
}
}
function responseOnEnd() {
const req = this.req;
const socket = req.socket;
if (socket) {
if (req.timeoutCb) socket.removeListener('timeout', emitRequestTimeout);
socket.removeListener('timeout', responseOnTimeout);
}
req._ended = true;
if (!req.shouldKeepAlive) {
if (socket.writable) {
debug('AGENT socket.destroySoon()');
if (typeof socket.destroySoon === 'function')
socket.destroySoon();
else
socket.end();
}
assert(!socket.writable);
} else if (req.writableFinished && !this.aborted) {
assert(req.finished);
// We can assume `req.finished` means all data has been written since:
// - `'responseOnEnd'` means we have been assigned a socket.
// - when we have a socket we write directly to it without buffering.
// - `req.finished` means `end()` has been called and no further data.
// can be written
// In addition, `req.writableFinished` means all data written has been
// accepted by the kernel. (i.e. the `req.socket` is drained).Without
// this constraint, we may assign a non drained socket to a request.
responseKeepAlive(req);
}
}
function responseOnTimeout() {
const req = this._httpMessage;
if (!req) return;
const res = req.res;
if (!res) return;
res.emit('timeout');
}
// This function is necessary in the case where we receive the entire response
// from the server before we finish sending out the request.
function requestOnFinish() {
const req = this;
if (req.shouldKeepAlive && req._ended)
responseKeepAlive(req);
}
function emitFreeNT(req) {
req._closed = true;
req.emit('close');
if (req.socket) {
req.socket.emit('free');
}
}
function tickOnSocket(req, socket) {
const parser = parsers.alloc();
req.socket = socket;
const lenient = req.insecureHTTPParser === undefined ?
isLenient() : req.insecureHTTPParser;
parser.initialize(HTTPParser.RESPONSE,
new HTTPClientAsyncResource('HTTPINCOMINGMESSAGE', req),
req.maxHeaderSize || 0,
lenient ? kLenientAll : kLenientNone);
parser.socket = socket;
parser.outgoing = req;
req.parser = parser;
socket.parser = parser;
socket._httpMessage = req;
// Propagate headers limit from request object to parser
if (typeof req.maxHeadersCount === 'number') {
parser.maxHeaderPairs = req.maxHeadersCount << 1;
}
parser.joinDuplicateHeaders = req.joinDuplicateHeaders;
parser.onIncoming = parserOnIncomingClient;
socket.on('error', socketErrorListener);
socket.on('data', socketOnData);
socket.on('end', socketOnEnd);
socket.on('close', socketCloseListener);
socket.on('drain', ondrain);
if (
req.timeout !== undefined ||
(req.agent?.options?.timeout)
) {
listenSocketTimeout(req);
}
req.emit('socket', socket);
}
function emitRequestTimeout() {
const req = this._httpMessage;
if (req) {
req.emit('timeout');
}
}
function listenSocketTimeout(req) {
if (req.timeoutCb) {
return;
}
// Set timeoutCb so it will get cleaned up on request end.
req.timeoutCb = emitRequestTimeout;
// Delegate socket timeout event.
if (req.socket) {
req.socket.once('timeout', emitRequestTimeout);
} else {
req.on('socket', (socket) => {
socket.once('timeout', emitRequestTimeout);
});
}
}
ClientRequest.prototype.onSocket = function onSocket(socket, err) {
// TODO(ronag): Between here and onSocketNT the socket
// has no 'error' handler.
process.nextTick(onSocketNT, this, socket, err);
};
function onSocketNT(req, socket, err) {
if (req.destroyed || err) {
req.destroyed = true;
function _destroy(req, err) {
if (!req.aborted && !err) {
err = new ConnResetException('socket hang up');
}
if (err) {
emitErrorEvent(req, err);
}
req._closed = true;
req.emit('close');
}
if (socket) {
if (!err && req.agent && !socket.destroyed) {
socket.emit('free');
} else {
finished(socket.destroy(err || req[kError]), (er) => {
if (er?.code === 'ERR_STREAM_PREMATURE_CLOSE') {
er = null;
}
_destroy(req, er || err);
});
return;
}
}
_destroy(req, err || req[kError]);
} else {
tickOnSocket(req, socket);
req._flush();
}
}
ClientRequest.prototype._deferToConnect = _deferToConnect;
function _deferToConnect(method, arguments_) {
// This function is for calls that need to happen once the socket is
// assigned to this request and writable. It's an important promisy
// thing for all the socket calls that happen either now
// (when a socket is assigned) or in the future (when a socket gets
// assigned out of the pool and is eventually writable).
const callSocketMethod = () => {
if (method)
ReflectApply(this.socket[method], this.socket, arguments_);
};
const onSocket = () => {
if (this.socket.writable) {
callSocketMethod();
} else {
this.socket.once('connect', callSocketMethod);
}
};
if (!this.socket) {
this.once('socket', onSocket);
} else {
onSocket();
}
}
ClientRequest.prototype.setTimeout = function setTimeout(msecs, callback) {
if (this._ended) {
return this;
}
listenSocketTimeout(this);
msecs = getTimerDuration(msecs, 'msecs');
if (callback) this.once('timeout', callback);
if (this.socket) {
setSocketTimeout(this.socket, msecs);
} else {
this.once('socket', (sock) => setSocketTimeout(sock, msecs));
}
return this;
};
function setSocketTimeout(sock, msecs) {
if (sock.connecting) {
sock.once('connect', function() {
sock.setTimeout(msecs);
});
} else {
sock.setTimeout(msecs);
}
}
ClientRequest.prototype.setNoDelay = function setNoDelay(noDelay) {
this._deferToConnect('setNoDelay', [noDelay]);
};
ClientRequest.prototype.setSocketKeepAlive =
function setSocketKeepAlive(enable, initialDelay) {
this._deferToConnect('setKeepAlive', [enable, initialDelay]);
};
ClientRequest.prototype.clearTimeout = function clearTimeout(cb) {
this.setTimeout(0, cb);
};
module.exports = {
ClientRequest,
};

269
lib/_http_common.js Normal file
View File

@ -0,0 +1,269 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const {
MathMin,
Symbol,
} = primordials;
const { setImmediate } = require('timers');
const { methods, allMethods, HTTPParser } = internalBinding('http_parser');
const { getOptionValue } = require('internal/options');
const insecureHTTPParser = getOptionValue('--insecure-http-parser');
const FreeList = require('internal/freelist');
const incoming = require('_http_incoming');
const {
IncomingMessage,
readStart,
readStop,
} = incoming;
const kIncomingMessage = Symbol('IncomingMessage');
const kOnMessageBegin = HTTPParser.kOnMessageBegin | 0;
const kOnHeaders = HTTPParser.kOnHeaders | 0;
const kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0;
const kOnBody = HTTPParser.kOnBody | 0;
const kOnMessageComplete = HTTPParser.kOnMessageComplete | 0;
const kOnExecute = HTTPParser.kOnExecute | 0;
const kOnTimeout = HTTPParser.kOnTimeout | 0;
const MAX_HEADER_PAIRS = 2000;
// Only called in the slow case where slow means
// that the request headers were either fragmented
// across multiple TCP packets or too large to be
// processed in a single run. This method is also
// called to process trailing HTTP headers.
function parserOnHeaders(headers, url) {
// Once we exceeded headers limit - stop collecting them
if (this.maxHeaderPairs <= 0 ||
this._headers.length < this.maxHeaderPairs) {
this._headers.push(...headers);
}
this._url += url;
}
// `headers` and `url` are set only if .onHeaders() has not been called for
// this request.
// `url` is not set for response parsers but that's not applicable here since
// all our parsers are request parsers.
function parserOnHeadersComplete(versionMajor, versionMinor, headers, method,
url, statusCode, statusMessage, upgrade,
shouldKeepAlive) {
const parser = this;
const { socket } = parser;
if (headers === undefined) {
headers = parser._headers;
parser._headers = [];
}
if (url === undefined) {
url = parser._url;
parser._url = '';
}
// Parser is also used by http client
const ParserIncomingMessage = (socket?.server?.[kIncomingMessage]) ||
IncomingMessage;
const incoming = parser.incoming = new ParserIncomingMessage(socket);
incoming.httpVersionMajor = versionMajor;
incoming.httpVersionMinor = versionMinor;
incoming.httpVersion = `${versionMajor}.${versionMinor}`;
incoming.joinDuplicateHeaders = socket?.server?.joinDuplicateHeaders ||
parser.joinDuplicateHeaders;
incoming.url = url;
incoming.upgrade = upgrade;
let n = headers.length;
// If parser.maxHeaderPairs <= 0 assume that there's no limit.
if (parser.maxHeaderPairs > 0)
n = MathMin(n, parser.maxHeaderPairs);
incoming._addHeaderLines(headers, n);
if (typeof method === 'number') {
// server only
incoming.method = allMethods[method];
} else {
// client only
incoming.statusCode = statusCode;
incoming.statusMessage = statusMessage;
}
return parser.onIncoming(incoming, shouldKeepAlive);
}
function parserOnBody(b) {
const stream = this.incoming;
// If the stream has already been removed, then drop it.
if (stream === null)
return;
// Pretend this was the result of a stream._read call.
if (!stream._dumped) {
const ret = stream.push(b);
if (!ret)
readStop(this.socket);
}
}
function parserOnMessageComplete() {
const parser = this;
const stream = parser.incoming;
if (stream !== null) {
stream.complete = true;
// Emit any trailing headers.
const headers = parser._headers;
if (headers.length) {
stream._addHeaderLines(headers, headers.length);
parser._headers = [];
parser._url = '';
}
// For emit end event
stream.push(null);
}
// Force to read the next incoming message
readStart(parser.socket);
}
const parsers = new FreeList('parsers', 1000, function parsersCb() {
const parser = new HTTPParser();
cleanParser(parser);
parser[kOnHeaders] = parserOnHeaders;
parser[kOnHeadersComplete] = parserOnHeadersComplete;
parser[kOnBody] = parserOnBody;
parser[kOnMessageComplete] = parserOnMessageComplete;
return parser;
});
function closeParserInstance(parser) { parser.close(); }
// Free the parser and also break any links that it
// might have to any other things.
// TODO: All parser data should be attached to a
// single object, so that it can be easily cleaned
// up by doing `parser.data = {}`, which should
// be done in FreeList.free. `parsers.free(parser)`
// should be all that is needed.
function freeParser(parser, req, socket) {
if (parser) {
if (parser._consumed)
parser.unconsume();
cleanParser(parser);
parser.remove();
if (parsers.free(parser) === false) {
// Make sure the parser's stack has unwound before deleting the
// corresponding C++ object through .close().
setImmediate(closeParserInstance, parser);
} else {
// Since the Parser destructor isn't going to run the destroy() callbacks
// it needs to be triggered manually.
parser.free();
}
}
if (req) {
req.parser = null;
}
if (socket) {
socket.parser = null;
}
}
const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/;
/**
* Verifies that the given val is a valid HTTP token
* per the rules defined in RFC 7230
* See https://tools.ietf.org/html/rfc7230#section-3.2.6
*/
function checkIsHttpToken(val) {
return tokenRegExp.test(val);
}
const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/;
/**
* True if val contains an invalid field-vchar
* field-value = *( field-content / obs-fold )
* field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
* field-vchar = VCHAR / obs-text
*/
function checkInvalidHeaderChar(val) {
return headerCharRegex.test(val);
}
function cleanParser(parser) {
parser._headers = [];
parser._url = '';
parser.socket = null;
parser.incoming = null;
parser.outgoing = null;
parser.maxHeaderPairs = MAX_HEADER_PAIRS;
parser[kOnMessageBegin] = null;
parser[kOnExecute] = null;
parser[kOnTimeout] = null;
parser._consumed = false;
parser.onIncoming = null;
parser.joinDuplicateHeaders = null;
}
function prepareError(err, parser, rawPacket) {
err.rawPacket = rawPacket || parser.getCurrentBuffer();
if (typeof err.reason === 'string')
err.message = `Parse Error: ${err.reason}`;
}
let warnedLenient = false;
function isLenient() {
if (insecureHTTPParser && !warnedLenient) {
warnedLenient = true;
process.emitWarning('Using insecure HTTP parsing');
}
return insecureHTTPParser;
}
module.exports = {
_checkInvalidHeaderChar: checkInvalidHeaderChar,
_checkIsHttpToken: checkIsHttpToken,
chunkExpression: /(?:^|\W)chunked(?:$|\W)/i,
continueExpression: /(?:^|\W)100-continue(?:$|\W)/i,
CRLF: '\r\n', // TODO: Deprecate this.
freeParser,
methods,
parsers,
kIncomingMessage,
HTTPParser,
isLenient,
prepareError,
};

453
lib/_http_incoming.js Normal file
View File

@ -0,0 +1,453 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const {
ObjectDefineProperty,
ObjectSetPrototypeOf,
Symbol,
} = primordials;
const { Readable, finished } = require('stream');
const kHeaders = Symbol('kHeaders');
const kHeadersDistinct = Symbol('kHeadersDistinct');
const kHeadersCount = Symbol('kHeadersCount');
const kTrailers = Symbol('kTrailers');
const kTrailersDistinct = Symbol('kTrailersDistinct');
const kTrailersCount = Symbol('kTrailersCount');
function readStart(socket) {
if (socket && !socket._paused && socket.readable)
socket.resume();
}
function readStop(socket) {
if (socket)
socket.pause();
}
/* Abstract base class for ServerRequest and ClientResponse. */
function IncomingMessage(socket) {
let streamOptions;
if (socket) {
streamOptions = {
highWaterMark: socket.readableHighWaterMark,
};
}
Readable.call(this, streamOptions);
this._readableState.readingMore = true;
this.socket = socket;
this.httpVersionMajor = null;
this.httpVersionMinor = null;
this.httpVersion = null;
this.complete = false;
this[kHeaders] = null;
this[kHeadersCount] = 0;
this.rawHeaders = [];
this[kTrailers] = null;
this[kTrailersCount] = 0;
this.rawTrailers = [];
this.joinDuplicateHeaders = false;
this.aborted = false;
this.upgrade = null;
// request (server) only
this.url = '';
this.method = null;
// response (client) only
this.statusCode = null;
this.statusMessage = null;
this.client = socket;
this._consuming = false;
// Flag for when we decide that this message cannot possibly be
// read by the user, so there's no point continuing to handle it.
this._dumped = false;
}
ObjectSetPrototypeOf(IncomingMessage.prototype, Readable.prototype);
ObjectSetPrototypeOf(IncomingMessage, Readable);
ObjectDefineProperty(IncomingMessage.prototype, 'connection', {
__proto__: null,
get: function() {
return this.socket;
},
set: function(val) {
this.socket = val;
},
});
ObjectDefineProperty(IncomingMessage.prototype, 'headers', {
__proto__: null,
get: function() {
if (!this[kHeaders]) {
this[kHeaders] = {};
const src = this.rawHeaders;
const dst = this[kHeaders];
for (let n = 0; n < this[kHeadersCount]; n += 2) {
this._addHeaderLine(src[n + 0], src[n + 1], dst);
}
}
return this[kHeaders];
},
set: function(val) {
this[kHeaders] = val;
},
});
ObjectDefineProperty(IncomingMessage.prototype, 'headersDistinct', {
__proto__: null,
get: function() {
if (!this[kHeadersDistinct]) {
this[kHeadersDistinct] = {};
const src = this.rawHeaders;
const dst = this[kHeadersDistinct];
for (let n = 0; n < this[kHeadersCount]; n += 2) {
this._addHeaderLineDistinct(src[n + 0], src[n + 1], dst);
}
}
return this[kHeadersDistinct];
},
set: function(val) {
this[kHeadersDistinct] = val;
},
});
ObjectDefineProperty(IncomingMessage.prototype, 'trailers', {
__proto__: null,
get: function() {
if (!this[kTrailers]) {
this[kTrailers] = {};
const src = this.rawTrailers;
const dst = this[kTrailers];
for (let n = 0; n < this[kTrailersCount]; n += 2) {
this._addHeaderLine(src[n + 0], src[n + 1], dst);
}
}
return this[kTrailers];
},
set: function(val) {
this[kTrailers] = val;
},
});
ObjectDefineProperty(IncomingMessage.prototype, 'trailersDistinct', {
__proto__: null,
get: function() {
if (!this[kTrailersDistinct]) {
this[kTrailersDistinct] = {};
const src = this.rawTrailers;
const dst = this[kTrailersDistinct];
for (let n = 0; n < this[kTrailersCount]; n += 2) {
this._addHeaderLineDistinct(src[n + 0], src[n + 1], dst);
}
}
return this[kTrailersDistinct];
},
set: function(val) {
this[kTrailersDistinct] = val;
},
});
IncomingMessage.prototype.setTimeout = function setTimeout(msecs, callback) {
if (callback)
this.on('timeout', callback);
this.socket.setTimeout(msecs);
return this;
};
// Argument n cannot be factored out due to the overhead of
// argument adaptor frame creation inside V8 in case that number of actual
// arguments is different from expected arguments.
// Ref: https://bugs.chromium.org/p/v8/issues/detail?id=10201
// NOTE: Argument adapt frame issue might be solved in V8 engine v8.9.
// Refactoring `n` out might be possible when V8 is upgraded to that
// version.
// Ref: https://v8.dev/blog/v8-release-89
IncomingMessage.prototype._read = function _read(n) {
if (!this._consuming) {
this._readableState.readingMore = false;
this._consuming = true;
}
// We actually do almost nothing here, because the parserOnBody
// function fills up our internal buffer directly. However, we
// do need to unpause the underlying socket so that it flows.
if (this.socket.readable)
readStart(this.socket);
};
// It's possible that the socket will be destroyed, and removed from
// any messages, before ever calling this. In that case, just skip
// it, since something else is destroying this connection anyway.
IncomingMessage.prototype._destroy = function _destroy(err, cb) {
if (!this.readableEnded || !this.complete) {
this.aborted = true;
this.emit('aborted');
}
// If aborted and the underlying socket is not already destroyed,
// destroy it.
// We have to check if the socket is already destroyed because finished
// does not call the callback when this method is invoked from `_http_client`
// in `test/parallel/test-http-client-spurious-aborted.js`
if (this.socket && !this.socket.destroyed && this.aborted) {
this.socket.destroy(err);
const cleanup = finished(this.socket, (e) => {
if (e?.code === 'ERR_STREAM_PREMATURE_CLOSE') {
e = null;
}
cleanup();
process.nextTick(onError, this, e || err, cb);
});
} else {
process.nextTick(onError, this, err, cb);
}
};
IncomingMessage.prototype._addHeaderLines = _addHeaderLines;
function _addHeaderLines(headers, n) {
if (headers?.length) {
let dest;
if (this.complete) {
this.rawTrailers = headers;
this[kTrailersCount] = n;
dest = this[kTrailers];
} else {
this.rawHeaders = headers;
this[kHeadersCount] = n;
dest = this[kHeaders];
}
if (dest) {
for (let i = 0; i < n; i += 2) {
this._addHeaderLine(headers[i], headers[i + 1], dest);
}
}
}
}
// This function is used to help avoid the lowercasing of a field name if it
// matches a 'traditional cased' version of a field name. It then returns the
// lowercased name to both avoid calling toLowerCase() a second time and to
// indicate whether the field was a 'no duplicates' field. If a field is not a
// 'no duplicates' field, a `0` byte is prepended as a flag. The one exception
// to this is the Set-Cookie header which is indicated by a `1` byte flag, since
// it is an 'array' field and thus is treated differently in _addHeaderLines().
// TODO: perhaps http_parser could be returning both raw and lowercased versions
// of known header names to avoid us having to call toLowerCase() for those
// headers.
function matchKnownFields(field, lowercased) {
switch (field.length) {
case 3:
if (field === 'Age' || field === 'age') return 'age';
break;
case 4:
if (field === 'Host' || field === 'host') return 'host';
if (field === 'From' || field === 'from') return 'from';
if (field === 'ETag' || field === 'etag') return 'etag';
if (field === 'Date' || field === 'date') return '\u0000date';
if (field === 'Vary' || field === 'vary') return '\u0000vary';
break;
case 6:
if (field === 'Server' || field === 'server') return 'server';
if (field === 'Cookie' || field === 'cookie') return '\u0002cookie';
if (field === 'Origin' || field === 'origin') return '\u0000origin';
if (field === 'Expect' || field === 'expect') return '\u0000expect';
if (field === 'Accept' || field === 'accept') return '\u0000accept';
break;
case 7:
if (field === 'Referer' || field === 'referer') return 'referer';
if (field === 'Expires' || field === 'expires') return 'expires';
if (field === 'Upgrade' || field === 'upgrade') return '\u0000upgrade';
break;
case 8:
if (field === 'Location' || field === 'location')
return 'location';
if (field === 'If-Match' || field === 'if-match')
return '\u0000if-match';
break;
case 10:
if (field === 'User-Agent' || field === 'user-agent')
return 'user-agent';
if (field === 'Set-Cookie' || field === 'set-cookie')
return '\u0001';
if (field === 'Connection' || field === 'connection')
return '\u0000connection';
break;
case 11:
if (field === 'Retry-After' || field === 'retry-after')
return 'retry-after';
break;
case 12:
if (field === 'Content-Type' || field === 'content-type')
return 'content-type';
if (field === 'Max-Forwards' || field === 'max-forwards')
return 'max-forwards';
break;
case 13:
if (field === 'Authorization' || field === 'authorization')
return 'authorization';
if (field === 'Last-Modified' || field === 'last-modified')
return 'last-modified';
if (field === 'Cache-Control' || field === 'cache-control')
return '\u0000cache-control';
if (field === 'If-None-Match' || field === 'if-none-match')
return '\u0000if-none-match';
break;
case 14:
if (field === 'Content-Length' || field === 'content-length')
return 'content-length';
break;
case 15:
if (field === 'Accept-Encoding' || field === 'accept-encoding')
return '\u0000accept-encoding';
if (field === 'Accept-Language' || field === 'accept-language')
return '\u0000accept-language';
if (field === 'X-Forwarded-For' || field === 'x-forwarded-for')
return '\u0000x-forwarded-for';
break;
case 16:
if (field === 'Content-Encoding' || field === 'content-encoding')
return '\u0000content-encoding';
if (field === 'X-Forwarded-Host' || field === 'x-forwarded-host')
return '\u0000x-forwarded-host';
break;
case 17:
if (field === 'If-Modified-Since' || field === 'if-modified-since')
return 'if-modified-since';
if (field === 'Transfer-Encoding' || field === 'transfer-encoding')
return '\u0000transfer-encoding';
if (field === 'X-Forwarded-Proto' || field === 'x-forwarded-proto')
return '\u0000x-forwarded-proto';
break;
case 19:
if (field === 'Proxy-Authorization' || field === 'proxy-authorization')
return 'proxy-authorization';
if (field === 'If-Unmodified-Since' || field === 'if-unmodified-since')
return 'if-unmodified-since';
break;
}
if (lowercased) {
return '\u0000' + field;
}
return matchKnownFields(field.toLowerCase(), true);
}
// Add the given (field, value) pair to the message
//
// Per RFC2616, section 4.2 it is acceptable to join multiple instances of the
// same header with a ', ' if the header in question supports specification of
// multiple values this way. The one exception to this is the Cookie header,
// which has multiple values joined with a '; ' instead. If a header's values
// cannot be joined in either of these ways, we declare the first instance the
// winner and drop the second. Extended header fields (those beginning with
// 'x-') are always joined.
IncomingMessage.prototype._addHeaderLine = _addHeaderLine;
function _addHeaderLine(field, value, dest) {
field = matchKnownFields(field);
const flag = field.charCodeAt(0);
if (flag === 0 || flag === 2) {
field = field.slice(1);
// Make a delimited list
if (typeof dest[field] === 'string') {
dest[field] += (flag === 0 ? ', ' : '; ') + value;
} else {
dest[field] = value;
}
} else if (flag === 1) {
// Array header -- only Set-Cookie at the moment
if (dest['set-cookie'] !== undefined) {
dest['set-cookie'].push(value);
} else {
dest['set-cookie'] = [value];
}
} else if (this.joinDuplicateHeaders) {
// RFC 9110 https://www.rfc-editor.org/rfc/rfc9110#section-5.2
// https://github.com/nodejs/node/issues/45699
// allow authorization multiple fields
// Make a delimited list
if (dest[field] === undefined) {
dest[field] = value;
} else {
dest[field] += ', ' + value;
}
} else if (dest[field] === undefined) {
// Drop duplicates
dest[field] = value;
}
}
IncomingMessage.prototype._addHeaderLineDistinct = _addHeaderLineDistinct;
function _addHeaderLineDistinct(field, value, dest) {
field = field.toLowerCase();
if (!dest[field]) {
dest[field] = [value];
} else {
dest[field].push(value);
}
}
// Call this instead of resume() if we want to just
// dump all the data to /dev/null
IncomingMessage.prototype._dump = function _dump() {
if (!this._dumped) {
this._dumped = true;
// If there is buffered data, it may trigger 'data' events.
// Remove 'data' event listeners explicitly.
this.removeAllListeners('data');
this.resume();
}
};
function onError(self, error, cb) {
// This is to keep backward compatible behavior.
// An error is emitted only if there are listeners attached to the event.
if (self.listenerCount('error') === 0) {
cb();
} else {
cb(error);
}
}
module.exports = {
IncomingMessage,
readStart,
readStop,
};

1204
lib/_http_outgoing.js Normal file

File diff suppressed because it is too large Load Diff

1232
lib/_http_server.js Normal file

File diff suppressed because it is too large Load Diff

5
lib/_stream_duplex.js Normal file
View File

@ -0,0 +1,5 @@
'use strict';
// Keep this file as an alias for the full stream module.
module.exports = require('stream').Duplex;

View File

@ -0,0 +1,5 @@
'use strict';
// Keep this file as an alias for the full stream module.
module.exports = require('stream').PassThrough;

5
lib/_stream_readable.js Normal file
View File

@ -0,0 +1,5 @@
'use strict';
// Keep this file as an alias for the full stream module.
module.exports = require('stream').Readable;

5
lib/_stream_transform.js Normal file
View File

@ -0,0 +1,5 @@
'use strict';
// Keep this file as an alias for the full stream module.
module.exports = require('stream').Transform;

5
lib/_stream_wrap.js Normal file
View File

@ -0,0 +1,5 @@
'use strict';
module.exports = require('internal/js_stream_socket');
process.emitWarning('The _stream_wrap module is deprecated.',
'DeprecationWarning', 'DEP0125');

5
lib/_stream_writable.js Normal file
View File

@ -0,0 +1,5 @@
'use strict';
// Keep this file as an alias for the full stream module.
module.exports = require('stream').Writable;

156
lib/_tls_common.js Normal file
View File

@ -0,0 +1,156 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const {
JSONParse,
} = primordials;
const tls = require('tls');
const {
codes: {
ERR_TLS_INVALID_PROTOCOL_VERSION,
ERR_TLS_PROTOCOL_VERSION_CONFLICT,
},
} = require('internal/errors');
const {
crypto: {
SSL_OP_CIPHER_SERVER_PREFERENCE,
TLS1_VERSION,
TLS1_1_VERSION,
TLS1_2_VERSION,
TLS1_3_VERSION,
},
} = internalBinding('constants');
const {
kEmptyObject,
} = require('internal/util');
const {
validateInteger,
} = require('internal/validators');
const {
configSecureContext,
} = require('internal/tls/secure-context');
function toV(which, v, def) {
v ??= def;
if (v === 'TLSv1') return TLS1_VERSION;
if (v === 'TLSv1.1') return TLS1_1_VERSION;
if (v === 'TLSv1.2') return TLS1_2_VERSION;
if (v === 'TLSv1.3') return TLS1_3_VERSION;
throw new ERR_TLS_INVALID_PROTOCOL_VERSION(v, which);
}
const {
SecureContext: NativeSecureContext,
} = internalBinding('crypto');
function SecureContext(secureProtocol, secureOptions, minVersion, maxVersion) {
if (!(this instanceof SecureContext)) {
return new SecureContext(secureProtocol, secureOptions, minVersion,
maxVersion);
}
if (secureProtocol) {
if (minVersion != null)
throw new ERR_TLS_PROTOCOL_VERSION_CONFLICT(minVersion, secureProtocol);
if (maxVersion != null)
throw new ERR_TLS_PROTOCOL_VERSION_CONFLICT(maxVersion, secureProtocol);
}
this.context = new NativeSecureContext();
this.context.init(secureProtocol,
toV('minimum', minVersion, tls.DEFAULT_MIN_VERSION),
toV('maximum', maxVersion, tls.DEFAULT_MAX_VERSION));
if (secureOptions) {
validateInteger(secureOptions, 'secureOptions');
this.context.setOptions(secureOptions);
}
}
function createSecureContext(options) {
options ||= kEmptyObject;
const {
honorCipherOrder,
minVersion,
maxVersion,
secureProtocol,
} = options;
let { secureOptions } = options;
if (honorCipherOrder)
secureOptions |= SSL_OP_CIPHER_SERVER_PREFERENCE;
const c = new SecureContext(secureProtocol, secureOptions,
minVersion, maxVersion);
configSecureContext(c.context, options);
return c;
}
// Translate some fields from the handle's C-friendly format into more idiomatic
// javascript object representations before passing them back to the user. Can
// be used on any cert object, but changing the name would be semver-major.
function translatePeerCertificate(c) {
if (!c)
return null;
if (c.issuerCertificate != null && c.issuerCertificate !== c) {
c.issuerCertificate = translatePeerCertificate(c.issuerCertificate);
}
if (c.infoAccess != null) {
const info = c.infoAccess;
c.infoAccess = { __proto__: null };
// XXX: More key validation?
info.replace(/([^\n:]*):([^\n]*)(?:\n|$)/g,
(all, key, val) => {
if (val.charCodeAt(0) === 0x22) {
// The translatePeerCertificate function is only
// used on internally created legacy certificate
// objects, and any value that contains a quote
// will always be a valid JSON string literal,
// so this should never throw.
val = JSONParse(val);
}
if (key in c.infoAccess)
c.infoAccess[key].push(val);
else
c.infoAccess[key] = [val];
});
}
return c;
}
module.exports = {
SecureContext,
createSecureContext,
translatePeerCertificate,
};

1782
lib/_tls_wrap.js Normal file

File diff suppressed because it is too large Load Diff

844
lib/assert.js Normal file
View File

@ -0,0 +1,844 @@
// Originally from narwhal.js (http://narwhaljs.org)
// Copyright (c) 2009 Thomas Robinson <280north.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the 'Software'), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const {
ArrayPrototypeIndexOf,
ArrayPrototypeJoin,
ArrayPrototypePush,
ArrayPrototypeSlice,
Error,
NumberIsNaN,
ObjectAssign,
ObjectIs,
ObjectKeys,
ObjectPrototypeIsPrototypeOf,
ReflectApply,
RegExpPrototypeExec,
String,
StringPrototypeIndexOf,
StringPrototypeSlice,
StringPrototypeSplit,
} = primordials;
const {
codes: {
ERR_AMBIGUOUS_ARGUMENT,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
ERR_INVALID_RETURN_VALUE,
ERR_MISSING_ARGS,
},
} = require('internal/errors');
const AssertionError = require('internal/assert/assertion_error');
const { inspect } = require('internal/util/inspect');
const {
isPromise,
isRegExp,
} = require('internal/util/types');
const { isError, deprecate } = require('internal/util');
const { innerOk } = require('internal/assert/utils');
const CallTracker = require('internal/assert/calltracker');
const {
validateFunction,
} = require('internal/validators');
let isDeepEqual;
let isDeepStrictEqual;
let isPartialStrictEqual;
function lazyLoadComparison() {
const comparison = require('internal/util/comparisons');
isDeepEqual = comparison.isDeepEqual;
isDeepStrictEqual = comparison.isDeepStrictEqual;
isPartialStrictEqual = comparison.isPartialStrictEqual;
}
let warned = false;
// The assert module provides functions that throw
// AssertionError's when particular conditions are not met. The
// assert module must conform to the following interface.
const assert = module.exports = ok;
const NO_EXCEPTION_SENTINEL = {};
// All of the following functions must throw an AssertionError
// when a corresponding condition is not met, with a message that
// may be undefined if not provided. All assertion methods provide
// both the actual and expected values to the assertion error for
// display purposes.
function innerFail(obj) {
if (obj.message instanceof Error) throw obj.message;
throw new AssertionError(obj);
}
/**
* @param {any} actual
* @param {any} expected
* @param {string | Error} [message]
* @param {string} [operator]
* @param {Function} [stackStartFn]
*/
function fail(actual, expected, message, operator, stackStartFn) {
const argsLen = arguments.length;
let internalMessage = false;
if (actual == null && argsLen <= 1) {
internalMessage = true;
message = 'Failed';
} else if (argsLen === 1) {
message = actual;
actual = undefined;
} else {
if (warned === false) {
warned = true;
process.emitWarning(
'assert.fail() with more than one argument is deprecated. ' +
'Please use assert.strictEqual() instead or only pass a message.',
'DeprecationWarning',
'DEP0094',
);
}
if (argsLen === 2)
operator = '!=';
}
if (message instanceof Error) throw message;
const errArgs = {
actual,
expected,
operator: operator === undefined ? 'fail' : operator,
stackStartFn: stackStartFn || fail,
message,
};
const err = new AssertionError(errArgs);
if (internalMessage) {
err.generatedMessage = true;
}
throw err;
}
assert.fail = fail;
// The AssertionError is defined in internal/error.
assert.AssertionError = AssertionError;
/**
* Pure assertion tests whether a value is truthy, as determined
* by !!value.
* @param {...any} args
* @returns {void}
*/
function ok(...args) {
innerOk(ok, args.length, ...args);
}
assert.ok = ok;
/**
* The equality assertion tests shallow, coercive equality with ==.
* @param {any} actual
* @param {any} expected
* @param {string | Error} [message]
* @returns {void}
*/
/* eslint-disable no-restricted-properties */
assert.equal = function equal(actual, expected, message) {
if (arguments.length < 2) {
throw new ERR_MISSING_ARGS('actual', 'expected');
}
// eslint-disable-next-line eqeqeq
if (actual != expected && (!NumberIsNaN(actual) || !NumberIsNaN(expected))) {
innerFail({
actual,
expected,
message,
operator: '==',
stackStartFn: equal,
});
}
};
/**
* The non-equality assertion tests for whether two objects are not
* equal with !=.
* @param {any} actual
* @param {any} expected
* @param {string | Error} [message]
* @returns {void}
*/
assert.notEqual = function notEqual(actual, expected, message) {
if (arguments.length < 2) {
throw new ERR_MISSING_ARGS('actual', 'expected');
}
// eslint-disable-next-line eqeqeq
if (actual == expected || (NumberIsNaN(actual) && NumberIsNaN(expected))) {
innerFail({
actual,
expected,
message,
operator: '!=',
stackStartFn: notEqual,
});
}
};
/**
* The deep equivalence assertion tests a deep equality relation.
* @param {any} actual
* @param {any} expected
* @param {string | Error} [message]
* @returns {void}
*/
assert.deepEqual = function deepEqual(actual, expected, message) {
if (arguments.length < 2) {
throw new ERR_MISSING_ARGS('actual', 'expected');
}
if (isDeepEqual === undefined) lazyLoadComparison();
if (!isDeepEqual(actual, expected)) {
innerFail({
actual,
expected,
message,
operator: 'deepEqual',
stackStartFn: deepEqual,
});
}
};
/**
* The deep non-equivalence assertion tests for any deep inequality.
* @param {any} actual
* @param {any} expected
* @param {string | Error} [message]
* @returns {void}
*/
assert.notDeepEqual = function notDeepEqual(actual, expected, message) {
if (arguments.length < 2) {
throw new ERR_MISSING_ARGS('actual', 'expected');
}
if (isDeepEqual === undefined) lazyLoadComparison();
if (isDeepEqual(actual, expected)) {
innerFail({
actual,
expected,
message,
operator: 'notDeepEqual',
stackStartFn: notDeepEqual,
});
}
};
/* eslint-enable */
/**
* The deep strict equivalence assertion tests a deep strict equality
* relation.
* @param {any} actual
* @param {any} expected
* @param {string | Error} [message]
* @returns {void}
*/
assert.deepStrictEqual = function deepStrictEqual(actual, expected, message) {
if (arguments.length < 2) {
throw new ERR_MISSING_ARGS('actual', 'expected');
}
if (isDeepEqual === undefined) lazyLoadComparison();
if (!isDeepStrictEqual(actual, expected)) {
innerFail({
actual,
expected,
message,
operator: 'deepStrictEqual',
stackStartFn: deepStrictEqual,
});
}
};
/**
* The deep strict non-equivalence assertion tests for any deep strict
* inequality.
* @param {any} actual
* @param {any} expected
* @param {string | Error} [message]
* @returns {void}
*/
assert.notDeepStrictEqual = notDeepStrictEqual;
function notDeepStrictEqual(actual, expected, message) {
if (arguments.length < 2) {
throw new ERR_MISSING_ARGS('actual', 'expected');
}
if (isDeepEqual === undefined) lazyLoadComparison();
if (isDeepStrictEqual(actual, expected)) {
innerFail({
actual,
expected,
message,
operator: 'notDeepStrictEqual',
stackStartFn: notDeepStrictEqual,
});
}
}
/**
* The strict equivalence assertion tests a strict equality relation.
* @param {any} actual
* @param {any} expected
* @param {string | Error} [message]
* @returns {void}
*/
assert.strictEqual = function strictEqual(actual, expected, message) {
if (arguments.length < 2) {
throw new ERR_MISSING_ARGS('actual', 'expected');
}
if (!ObjectIs(actual, expected)) {
innerFail({
actual,
expected,
message,
operator: 'strictEqual',
stackStartFn: strictEqual,
});
}
};
/**
* The strict non-equivalence assertion tests for any strict inequality.
* @param {any} actual
* @param {any} expected
* @param {string | Error} [message]
* @returns {void}
*/
assert.notStrictEqual = function notStrictEqual(actual, expected, message) {
if (arguments.length < 2) {
throw new ERR_MISSING_ARGS('actual', 'expected');
}
if (ObjectIs(actual, expected)) {
innerFail({
actual,
expected,
message,
operator: 'notStrictEqual',
stackStartFn: notStrictEqual,
});
}
};
/**
* The strict equivalence assertion test between two objects
* @param {any} actual
* @param {any} expected
* @param {string | Error} [message]
* @returns {void}
*/
assert.partialDeepStrictEqual = function partialDeepStrictEqual(
actual,
expected,
message,
) {
if (arguments.length < 2) {
throw new ERR_MISSING_ARGS('actual', 'expected');
}
if (isDeepEqual === undefined) lazyLoadComparison();
if (!isPartialStrictEqual(actual, expected)) {
innerFail({
actual,
expected,
message,
operator: 'partialDeepStrictEqual',
stackStartFn: partialDeepStrictEqual,
});
}
};
class Comparison {
constructor(obj, keys, actual) {
for (const key of keys) {
if (key in obj) {
if (actual !== undefined &&
typeof actual[key] === 'string' &&
isRegExp(obj[key]) &&
RegExpPrototypeExec(obj[key], actual[key]) !== null) {
this[key] = actual[key];
} else {
this[key] = obj[key];
}
}
}
}
}
function compareExceptionKey(actual, expected, key, message, keys, fn) {
if (!(key in actual) || !isDeepStrictEqual(actual[key], expected[key])) {
if (!message) {
// Create placeholder objects to create a nice output.
const a = new Comparison(actual, keys);
const b = new Comparison(expected, keys, actual);
const err = new AssertionError({
actual: a,
expected: b,
operator: 'deepStrictEqual',
stackStartFn: fn,
});
err.actual = actual;
err.expected = expected;
err.operator = fn.name;
throw err;
}
innerFail({
actual,
expected,
message,
operator: fn.name,
stackStartFn: fn,
});
}
}
function expectedException(actual, expected, message, fn) {
let generatedMessage = false;
let throwError = false;
if (typeof expected !== 'function') {
// Handle regular expressions.
if (isRegExp(expected)) {
const str = String(actual);
if (RegExpPrototypeExec(expected, str) !== null)
return;
if (!message) {
generatedMessage = true;
message = 'The input did not match the regular expression ' +
`${inspect(expected)}. Input:\n\n${inspect(str)}\n`;
}
throwError = true;
// Handle primitives properly.
} else if (typeof actual !== 'object' || actual === null) {
const err = new AssertionError({
actual,
expected,
message,
operator: 'deepStrictEqual',
stackStartFn: fn,
});
err.operator = fn.name;
throw err;
} else {
// Handle validation objects.
const keys = ObjectKeys(expected);
// Special handle errors to make sure the name and the message are
// compared as well.
if (expected instanceof Error) {
ArrayPrototypePush(keys, 'name', 'message');
} else if (keys.length === 0) {
throw new ERR_INVALID_ARG_VALUE('error',
expected, 'may not be an empty object');
}
if (isDeepEqual === undefined) lazyLoadComparison();
for (const key of keys) {
if (typeof actual[key] === 'string' &&
isRegExp(expected[key]) &&
RegExpPrototypeExec(expected[key], actual[key]) !== null) {
continue;
}
compareExceptionKey(actual, expected, key, message, keys, fn);
}
return;
}
// Guard instanceof against arrow functions as they don't have a prototype.
// Check for matching Error classes.
} else if (expected.prototype !== undefined && actual instanceof expected) {
return;
} else if (ObjectPrototypeIsPrototypeOf(Error, expected)) {
if (!message) {
generatedMessage = true;
message = 'The error is expected to be an instance of ' +
`"${expected.name}". Received `;
if (isError(actual)) {
const name = (actual.constructor?.name) ||
actual.name;
if (expected.name === name) {
message += 'an error with identical name but a different prototype.';
} else {
message += `"${name}"`;
}
if (actual.message) {
message += `\n\nError message:\n\n${actual.message}`;
}
} else {
message += `"${inspect(actual, { depth: -1 })}"`;
}
}
throwError = true;
} else {
// Check validation functions return value.
const res = ReflectApply(expected, {}, [actual]);
if (res !== true) {
if (!message) {
generatedMessage = true;
const name = expected.name ? `"${expected.name}" ` : '';
message = `The ${name}validation function is expected to return` +
` "true". Received ${inspect(res)}`;
if (isError(actual)) {
message += `\n\nCaught error:\n\n${actual}`;
}
}
throwError = true;
}
}
if (throwError) {
const err = new AssertionError({
actual,
expected,
message,
operator: fn.name,
stackStartFn: fn,
});
err.generatedMessage = generatedMessage;
throw err;
}
}
function getActual(fn) {
validateFunction(fn, 'fn');
try {
fn();
} catch (e) {
return e;
}
return NO_EXCEPTION_SENTINEL;
}
function checkIsPromise(obj) {
// Accept native ES6 promises and promises that are implemented in a similar
// way. Do not accept thenables that use a function as `obj` and that have no
// `catch` handler.
return isPromise(obj) ||
(obj !== null && typeof obj === 'object' &&
typeof obj.then === 'function' &&
typeof obj.catch === 'function');
}
async function waitForActual(promiseFn) {
let resultPromise;
if (typeof promiseFn === 'function') {
// Return a rejected promise if `promiseFn` throws synchronously.
resultPromise = promiseFn();
// Fail in case no promise is returned.
if (!checkIsPromise(resultPromise)) {
throw new ERR_INVALID_RETURN_VALUE('instance of Promise',
'promiseFn', resultPromise);
}
} else if (checkIsPromise(promiseFn)) {
resultPromise = promiseFn;
} else {
throw new ERR_INVALID_ARG_TYPE(
'promiseFn', ['Function', 'Promise'], promiseFn);
}
try {
await resultPromise;
} catch (e) {
return e;
}
return NO_EXCEPTION_SENTINEL;
}
function expectsError(stackStartFn, actual, error, message) {
if (typeof error === 'string') {
if (arguments.length === 4) {
throw new ERR_INVALID_ARG_TYPE('error',
['Object', 'Error', 'Function', 'RegExp'],
error);
}
if (typeof actual === 'object' && actual !== null) {
if (actual.message === error) {
throw new ERR_AMBIGUOUS_ARGUMENT(
'error/message',
`The error message "${actual.message}" is identical to the message.`,
);
}
} else if (actual === error) {
throw new ERR_AMBIGUOUS_ARGUMENT(
'error/message',
`The error "${actual}" is identical to the message.`,
);
}
message = error;
error = undefined;
} else if (error != null &&
typeof error !== 'object' &&
typeof error !== 'function') {
throw new ERR_INVALID_ARG_TYPE('error',
['Object', 'Error', 'Function', 'RegExp'],
error);
}
if (actual === NO_EXCEPTION_SENTINEL) {
let details = '';
if (error?.name) {
details += ` (${error.name})`;
}
details += message ? `: ${message}` : '.';
const fnType = stackStartFn === assert.rejects ? 'rejection' : 'exception';
innerFail({
actual: undefined,
expected: error,
operator: stackStartFn.name,
message: `Missing expected ${fnType}${details}`,
stackStartFn,
});
}
if (!error)
return;
expectedException(actual, error, message, stackStartFn);
}
function hasMatchingError(actual, expected) {
if (typeof expected !== 'function') {
if (isRegExp(expected)) {
const str = String(actual);
return RegExpPrototypeExec(expected, str) !== null;
}
throw new ERR_INVALID_ARG_TYPE(
'expected', ['Function', 'RegExp'], expected,
);
}
// Guard instanceof against arrow functions as they don't have a prototype.
if (expected.prototype !== undefined && actual instanceof expected) {
return true;
}
if (ObjectPrototypeIsPrototypeOf(Error, expected)) {
return false;
}
return ReflectApply(expected, {}, [actual]) === true;
}
function expectsNoError(stackStartFn, actual, error, message) {
if (actual === NO_EXCEPTION_SENTINEL)
return;
if (typeof error === 'string') {
message = error;
error = undefined;
}
if (!error || hasMatchingError(actual, error)) {
const details = message ? `: ${message}` : '.';
const fnType = stackStartFn === assert.doesNotReject ?
'rejection' : 'exception';
innerFail({
actual,
expected: error,
operator: stackStartFn.name,
message: `Got unwanted ${fnType}${details}\n` +
`Actual message: "${actual?.message}"`,
stackStartFn,
});
}
throw actual;
}
/**
* Expects the function `promiseFn` to throw an error.
* @param {() => any} promiseFn
* @param {...any} [args]
* @returns {void}
*/
assert.throws = function throws(promiseFn, ...args) {
expectsError(throws, getActual(promiseFn), ...args);
};
/**
* Expects `promiseFn` function or its value to reject.
* @param {() => Promise<any>} promiseFn
* @param {...any} [args]
* @returns {Promise<void>}
*/
assert.rejects = async function rejects(promiseFn, ...args) {
expectsError(rejects, await waitForActual(promiseFn), ...args);
};
/**
* Asserts that the function `fn` does not throw an error.
* @param {() => any} fn
* @param {...any} [args]
* @returns {void}
*/
assert.doesNotThrow = function doesNotThrow(fn, ...args) {
expectsNoError(doesNotThrow, getActual(fn), ...args);
};
/**
* Expects `fn` or its value to not reject.
* @param {() => Promise<any>} fn
* @param {...any} [args]
* @returns {Promise<void>}
*/
assert.doesNotReject = async function doesNotReject(fn, ...args) {
expectsNoError(doesNotReject, await waitForActual(fn), ...args);
};
/**
* Throws `value` if the value is not `null` or `undefined`.
* @param {any} err
* @returns {void}
*/
assert.ifError = function ifError(err) {
if (err !== null && err !== undefined) {
let message = 'ifError got unwanted exception: ';
if (typeof err === 'object' && typeof err.message === 'string') {
if (err.message.length === 0 && err.constructor) {
message += err.constructor.name;
} else {
message += err.message;
}
} else {
message += inspect(err);
}
const newErr = new AssertionError({
actual: err,
expected: null,
operator: 'ifError',
message,
stackStartFn: ifError,
});
// Make sure we actually have a stack trace!
const origStack = err.stack;
if (typeof origStack === 'string') {
// This will remove any duplicated frames from the error frames taken
// from within `ifError` and add the original error frames to the newly
// created ones.
const origStackStart = StringPrototypeIndexOf(origStack, '\n at');
if (origStackStart !== -1) {
const originalFrames = StringPrototypeSplit(
StringPrototypeSlice(origStack, origStackStart + 1),
'\n',
);
// Filter all frames existing in err.stack.
let newFrames = StringPrototypeSplit(newErr.stack, '\n');
for (const errFrame of originalFrames) {
// Find the first occurrence of the frame.
const pos = ArrayPrototypeIndexOf(newFrames, errFrame);
if (pos !== -1) {
// Only keep new frames.
newFrames = ArrayPrototypeSlice(newFrames, 0, pos);
break;
}
}
const stackStart = ArrayPrototypeJoin(newFrames, '\n');
const stackEnd = ArrayPrototypeJoin(originalFrames, '\n');
newErr.stack = `${stackStart}\n${stackEnd}`;
}
}
throw newErr;
}
};
function internalMatch(string, regexp, message, fn) {
if (!isRegExp(regexp)) {
throw new ERR_INVALID_ARG_TYPE(
'regexp', 'RegExp', regexp,
);
}
const match = fn === assert.match;
if (typeof string !== 'string' ||
RegExpPrototypeExec(regexp, string) !== null !== match) {
if (message instanceof Error) {
throw message;
}
const generatedMessage = !message;
// 'The input was expected to not match the regular expression ' +
message ||= (typeof string !== 'string' ?
'The "string" argument must be of type string. Received type ' +
`${typeof string} (${inspect(string)})` :
(match ?
'The input did not match the regular expression ' :
'The input was expected to not match the regular expression ') +
`${inspect(regexp)}. Input:\n\n${inspect(string)}\n`);
const err = new AssertionError({
actual: string,
expected: regexp,
message,
operator: fn.name,
stackStartFn: fn,
});
err.generatedMessage = generatedMessage;
throw err;
}
}
/**
* Expects the `string` input to match the regular expression.
* @param {string} string
* @param {RegExp} regexp
* @param {string | Error} [message]
* @returns {void}
*/
assert.match = function match(string, regexp, message) {
internalMatch(string, regexp, message, match);
};
/**
* Expects the `string` input not to match the regular expression.
* @param {string} string
* @param {RegExp} regexp
* @param {string | Error} [message]
* @returns {void}
*/
assert.doesNotMatch = function doesNotMatch(string, regexp, message) {
internalMatch(string, regexp, message, doesNotMatch);
};
assert.CallTracker = deprecate(CallTracker, 'assert.CallTracker is deprecated.', 'DEP0173');
/**
* Expose a strict only variant of assert.
* @param {...any} args
* @returns {void}
*/
function strict(...args) {
innerOk(strict, args.length, ...args);
}
assert.strict = ObjectAssign(strict, assert, {
equal: assert.strictEqual,
deepEqual: assert.deepStrictEqual,
notEqual: assert.notStrictEqual,
notDeepEqual: assert.notDeepStrictEqual,
});
assert.strict.strict = assert.strict;

3
lib/assert/strict.js Normal file
View File

@ -0,0 +1,3 @@
'use strict';
module.exports = require('assert').strict;

296
lib/async_hooks.js Normal file
View File

@ -0,0 +1,296 @@
'use strict';
const {
ArrayPrototypeIncludes,
ArrayPrototypeIndexOf,
ArrayPrototypePush,
ArrayPrototypeSplice,
ArrayPrototypeUnshift,
FunctionPrototypeBind,
NumberIsSafeInteger,
ObjectDefineProperties,
ObjectFreeze,
ReflectApply,
Symbol,
} = primordials;
const {
ERR_ASYNC_CALLBACK,
ERR_ASYNC_TYPE,
ERR_INVALID_ASYNC_ID,
} = require('internal/errors').codes;
const {
deprecate,
kEmptyObject,
} = require('internal/util');
const {
validateFunction,
validateString,
} = require('internal/validators');
const internal_async_hooks = require('internal/async_hooks');
const AsyncContextFrame = require('internal/async_context_frame');
// Get functions
// For userland AsyncResources, make sure to emit a destroy event when the
// resource gets gced.
const { registerDestroyHook, kNoPromiseHook } = internal_async_hooks;
const {
asyncWrap,
executionAsyncId,
triggerAsyncId,
// Private API
hasAsyncIdStack,
getHookArrays,
enableHooks,
disableHooks,
updatePromiseHookMode,
executionAsyncResource,
// Internal Embedder API
newAsyncId,
getDefaultTriggerAsyncId,
emitInit,
emitBefore,
emitAfter,
emitDestroy,
enabledHooksExist,
initHooksExist,
destroyHooksExist,
} = internal_async_hooks;
// Get symbols
const {
async_id_symbol, trigger_async_id_symbol,
init_symbol, before_symbol, after_symbol, destroy_symbol,
promise_resolve_symbol,
} = internal_async_hooks.symbols;
// Get constants
const {
kInit, kBefore, kAfter, kDestroy, kTotals, kPromiseResolve,
} = internal_async_hooks.constants;
// Listener API //
class AsyncHook {
constructor({ init, before, after, destroy, promiseResolve }) {
if (init !== undefined && typeof init !== 'function')
throw new ERR_ASYNC_CALLBACK('hook.init');
if (before !== undefined && typeof before !== 'function')
throw new ERR_ASYNC_CALLBACK('hook.before');
if (after !== undefined && typeof after !== 'function')
throw new ERR_ASYNC_CALLBACK('hook.after');
if (destroy !== undefined && typeof destroy !== 'function')
throw new ERR_ASYNC_CALLBACK('hook.destroy');
if (promiseResolve !== undefined && typeof promiseResolve !== 'function')
throw new ERR_ASYNC_CALLBACK('hook.promiseResolve');
this[init_symbol] = init;
this[before_symbol] = before;
this[after_symbol] = after;
this[destroy_symbol] = destroy;
this[promise_resolve_symbol] = promiseResolve;
this[kNoPromiseHook] = false;
}
enable() {
// The set of callbacks for a hook should be the same regardless of whether
// enable()/disable() are run during their execution. The following
// references are reassigned to the tmp arrays if a hook is currently being
// processed.
const { 0: hooks_array, 1: hook_fields } = getHookArrays();
// Each hook is only allowed to be added once.
if (ArrayPrototypeIncludes(hooks_array, this))
return this;
const prev_kTotals = hook_fields[kTotals];
// createHook() has already enforced that the callbacks are all functions,
// so here simply increment the count of whether each callbacks exists or
// not.
hook_fields[kTotals] = hook_fields[kInit] += +!!this[init_symbol];
hook_fields[kTotals] += hook_fields[kBefore] += +!!this[before_symbol];
hook_fields[kTotals] += hook_fields[kAfter] += +!!this[after_symbol];
hook_fields[kTotals] += hook_fields[kDestroy] += +!!this[destroy_symbol];
hook_fields[kTotals] +=
hook_fields[kPromiseResolve] += +!!this[promise_resolve_symbol];
ArrayPrototypePush(hooks_array, this);
if (prev_kTotals === 0 && hook_fields[kTotals] > 0) {
enableHooks();
}
if (!this[kNoPromiseHook]) {
updatePromiseHookMode();
}
return this;
}
disable() {
const { 0: hooks_array, 1: hook_fields } = getHookArrays();
const index = ArrayPrototypeIndexOf(hooks_array, this);
if (index === -1)
return this;
const prev_kTotals = hook_fields[kTotals];
hook_fields[kTotals] = hook_fields[kInit] -= +!!this[init_symbol];
hook_fields[kTotals] += hook_fields[kBefore] -= +!!this[before_symbol];
hook_fields[kTotals] += hook_fields[kAfter] -= +!!this[after_symbol];
hook_fields[kTotals] += hook_fields[kDestroy] -= +!!this[destroy_symbol];
hook_fields[kTotals] +=
hook_fields[kPromiseResolve] -= +!!this[promise_resolve_symbol];
ArrayPrototypeSplice(hooks_array, index, 1);
if (prev_kTotals > 0 && hook_fields[kTotals] === 0) {
disableHooks();
}
return this;
}
}
function createHook(fns) {
return new AsyncHook(fns);
}
// Embedder API //
const destroyedSymbol = Symbol('destroyed');
const contextFrameSymbol = Symbol('context_frame');
class AsyncResource {
constructor(type, opts = kEmptyObject) {
validateString(type, 'type');
let triggerAsyncId = opts;
let requireManualDestroy = false;
if (typeof opts !== 'number') {
triggerAsyncId = opts.triggerAsyncId === undefined ?
getDefaultTriggerAsyncId() : opts.triggerAsyncId;
requireManualDestroy = !!opts.requireManualDestroy;
}
// Unlike emitInitScript, AsyncResource doesn't supports null as the
// triggerAsyncId.
if (!NumberIsSafeInteger(triggerAsyncId) || triggerAsyncId < -1) {
throw new ERR_INVALID_ASYNC_ID('triggerAsyncId', triggerAsyncId);
}
this[contextFrameSymbol] = AsyncContextFrame.current();
const asyncId = newAsyncId();
this[async_id_symbol] = asyncId;
this[trigger_async_id_symbol] = triggerAsyncId;
if (initHooksExist()) {
if (enabledHooksExist() && type.length === 0) {
throw new ERR_ASYNC_TYPE(type);
}
emitInit(asyncId, type, triggerAsyncId, this);
}
if (!requireManualDestroy && destroyHooksExist()) {
// This prop name (destroyed) has to be synchronized with C++
const destroyed = { destroyed: false };
this[destroyedSymbol] = destroyed;
registerDestroyHook(this, asyncId, destroyed);
}
}
runInAsyncScope(fn, thisArg, ...args) {
const asyncId = this[async_id_symbol];
emitBefore(asyncId, this[trigger_async_id_symbol], this);
const contextFrame = this[contextFrameSymbol];
const prior = AsyncContextFrame.exchange(contextFrame);
try {
return ReflectApply(fn, thisArg, args);
} finally {
AsyncContextFrame.set(prior);
if (hasAsyncIdStack())
emitAfter(asyncId);
}
}
emitDestroy() {
if (this[destroyedSymbol] !== undefined) {
this[destroyedSymbol].destroyed = true;
}
emitDestroy(this[async_id_symbol]);
return this;
}
asyncId() {
return this[async_id_symbol];
}
triggerAsyncId() {
return this[trigger_async_id_symbol];
}
bind(fn, thisArg) {
validateFunction(fn, 'fn');
let bound;
if (thisArg === undefined) {
const resource = this;
bound = function(...args) {
ArrayPrototypeUnshift(args, fn, this);
return ReflectApply(resource.runInAsyncScope, resource, args);
};
} else {
bound = FunctionPrototypeBind(this.runInAsyncScope, this, fn, thisArg);
}
let self = this;
ObjectDefineProperties(bound, {
'length': {
__proto__: null,
configurable: true,
enumerable: false,
value: fn.length,
writable: false,
},
'asyncResource': {
__proto__: null,
configurable: true,
enumerable: true,
get: deprecate(function() {
return self;
}, 'The asyncResource property on bound functions is deprecated', 'DEP0172'),
set: deprecate(function(val) {
self = val;
}, 'The asyncResource property on bound functions is deprecated', 'DEP0172'),
},
});
return bound;
}
static bind(fn, type, thisArg) {
type ||= fn.name;
return (new AsyncResource(type || 'bound-anonymous-fn')).bind(fn, thisArg);
}
}
// Placing all exports down here because the exported classes won't export
// otherwise.
module.exports = {
// Public API
get AsyncLocalStorage() {
return AsyncContextFrame.enabled ?
require('internal/async_local_storage/async_context_frame') :
require('internal/async_local_storage/async_hooks');
},
createHook,
executionAsyncId,
triggerAsyncId,
executionAsyncResource,
asyncWrapProviders: ObjectFreeze({ __proto__: null, ...asyncWrap.Providers }),
// Embedder API
AsyncResource,
};

1377
lib/buffer.js Normal file

File diff suppressed because it is too large Load Diff

1023
lib/child_process.js Normal file

File diff suppressed because it is too large Load Diff

29
lib/cluster.js Normal file
View File

@ -0,0 +1,29 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const {
ObjectPrototypeHasOwnProperty: ObjectHasOwn,
} = primordials;
const childOrPrimary = ObjectHasOwn(process.env, 'NODE_UNIQUE_ID') ? 'child' : 'primary';
module.exports = require(`internal/cluster/${childOrPrimary}`);

24
lib/console.js Normal file
View File

@ -0,0 +1,24 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
module.exports = require('internal/console/global');

41
lib/constants.js Normal file
View File

@ -0,0 +1,41 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const {
ObjectAssign,
ObjectFreeze,
} = primordials;
// This module is deprecated in documentation only. Users should be directed
// towards using the specific constants exposed by the individual modules on
// which they are most relevant.
// Deprecation Code: DEP0008
const constants = internalBinding('constants');
ObjectAssign(exports,
constants.os.dlopen,
constants.os.errno,
constants.os.priority,
constants.os.signals,
constants.fs,
constants.crypto);
ObjectFreeze(exports);

372
lib/crypto.js Normal file
View File

@ -0,0 +1,372 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
// Note: In 0.8 and before, crypto functions all defaulted to using
// binary-encoded strings rather than buffers.
'use strict';
const {
ObjectDefineProperties,
ObjectDefineProperty,
} = primordials;
const {
assertCrypto,
deprecate,
} = require('internal/util');
assertCrypto();
const {
ERR_CRYPTO_FIPS_FORCED,
ERR_WORKER_UNSUPPORTED_OPERATION,
} = require('internal/errors').codes;
const constants = internalBinding('constants').crypto;
const { getOptionValue } = require('internal/options');
const {
getFipsCrypto,
setFipsCrypto,
timingSafeEqual,
} = internalBinding('crypto');
const {
checkPrime,
checkPrimeSync,
generatePrime,
generatePrimeSync,
randomBytes,
randomFill,
randomFillSync,
randomInt,
randomUUID,
} = require('internal/crypto/random');
const {
pbkdf2,
pbkdf2Sync,
} = require('internal/crypto/pbkdf2');
const {
scrypt,
scryptSync,
} = require('internal/crypto/scrypt');
const {
hkdf,
hkdfSync,
} = require('internal/crypto/hkdf');
const {
generateKeyPair,
generateKeyPairSync,
generateKey,
generateKeySync,
} = require('internal/crypto/keygen');
const {
createSecretKey,
createPublicKey,
createPrivateKey,
KeyObject,
} = require('internal/crypto/keys');
const {
DiffieHellman,
DiffieHellmanGroup,
ECDH,
diffieHellman,
} = require('internal/crypto/diffiehellman');
const {
Cipheriv,
Decipheriv,
privateDecrypt,
privateEncrypt,
publicDecrypt,
publicEncrypt,
getCipherInfo,
} = require('internal/crypto/cipher');
const {
Sign,
signOneShot,
Verify,
verifyOneShot,
} = require('internal/crypto/sig');
const {
Hash,
Hmac,
hash,
} = require('internal/crypto/hash');
const {
X509Certificate,
} = require('internal/crypto/x509');
const {
getCiphers,
getCurves,
getHashes,
setEngine,
secureHeapUsed,
} = require('internal/crypto/util');
const Certificate = require('internal/crypto/certificate');
let webcrypto;
function lazyWebCrypto() {
webcrypto ??= require('internal/crypto/webcrypto');
return webcrypto;
}
let ownsProcessState;
function lazyOwnsProcessState() {
ownsProcessState ??= require('internal/worker').ownsProcessState;
return ownsProcessState;
}
// These helper functions are needed because the constructors can
// use new, in which case V8 cannot inline the recursive constructor call
function createHash(algorithm, options) {
return new Hash(algorithm, options);
}
function createCipheriv(cipher, key, iv, options) {
return new Cipheriv(cipher, key, iv, options);
}
function createDecipheriv(cipher, key, iv, options) {
return new Decipheriv(cipher, key, iv, options);
}
function createDiffieHellman(sizeOrKey, keyEncoding, generator, genEncoding) {
return new DiffieHellman(sizeOrKey, keyEncoding, generator, genEncoding);
}
function createDiffieHellmanGroup(name) {
return new DiffieHellmanGroup(name);
}
function createECDH(curve) {
return new ECDH(curve);
}
function createHmac(hmac, key, options) {
return new Hmac(hmac, key, options);
}
function createSign(algorithm, options) {
return new Sign(algorithm, options);
}
function createVerify(algorithm, options) {
return new Verify(algorithm, options);
}
module.exports = {
// Methods
checkPrime,
checkPrimeSync,
createCipheriv,
createDecipheriv,
createDiffieHellman,
createDiffieHellmanGroup,
createECDH,
createHash,
createHmac,
createPrivateKey,
createPublicKey,
createSecretKey,
createSign,
createVerify,
diffieHellman,
generatePrime,
generatePrimeSync,
getCiphers,
getCipherInfo,
getCurves,
getDiffieHellman: createDiffieHellmanGroup,
getHashes,
hkdf,
hkdfSync,
pbkdf2,
pbkdf2Sync,
generateKeyPair,
generateKeyPairSync,
generateKey,
generateKeySync,
privateDecrypt,
privateEncrypt,
publicDecrypt,
publicEncrypt,
randomBytes,
randomFill,
randomFillSync,
randomInt,
randomUUID,
scrypt,
scryptSync,
sign: signOneShot,
setEngine,
timingSafeEqual,
getFips,
setFips,
verify: verifyOneShot,
hash,
// Classes
Certificate,
Cipheriv,
Decipheriv,
DiffieHellman,
DiffieHellmanGroup,
ECDH,
Hash: deprecate(Hash, 'crypto.Hash constructor is deprecated.', 'DEP0179'),
Hmac: deprecate(Hmac, 'crypto.Hmac constructor is deprecated.', 'DEP0181'),
KeyObject,
Sign,
Verify,
X509Certificate,
secureHeapUsed,
};
function getFips() {
return getOptionValue('--force-fips') ? 1 : getFipsCrypto();
}
function setFips(val) {
if (getOptionValue('--force-fips')) {
if (val) return;
throw new ERR_CRYPTO_FIPS_FORCED();
} else {
if (!lazyOwnsProcessState()) {
throw new ERR_WORKER_UNSUPPORTED_OPERATION('Calling crypto.setFips()');
}
setFipsCrypto(val);
}
}
function getRandomValues(array) {
return lazyWebCrypto().crypto.getRandomValues(array);
}
ObjectDefineProperty(constants, 'defaultCipherList', {
__proto__: null,
get() {
const value = getOptionValue('--tls-cipher-list');
ObjectDefineProperty(this, 'defaultCipherList', {
__proto__: null,
writable: true,
configurable: true,
enumerable: true,
value,
});
return value;
},
set(val) {
ObjectDefineProperty(this, 'defaultCipherList', {
__proto__: null,
writable: true,
configurable: true,
enumerable: true,
value: val,
});
},
configurable: true,
enumerable: true,
});
function getRandomBytesAlias(key) {
return {
enumerable: false,
configurable: true,
get() {
let value;
if (getOptionValue('--pending-deprecation')) {
value = deprecate(
randomBytes,
`crypto.${key} is deprecated.`,
'DEP0115');
} else {
value = randomBytes;
}
ObjectDefineProperty(
this,
key,
{
__proto__: null,
enumerable: false,
configurable: true,
writable: true,
value: value,
},
);
return value;
},
set(value) {
ObjectDefineProperty(
this,
key,
{
__proto__: null,
enumerable: true,
configurable: true,
writable: true,
value,
},
);
},
};
}
ObjectDefineProperties(module.exports, {
fips: {
__proto__: null,
get: deprecate(getFips, 'The crypto.fips is deprecated. ' +
'Please use crypto.getFips()', 'DEP0093'),
set: deprecate(setFips, 'The crypto.fips is deprecated. ' +
'Please use crypto.setFips()', 'DEP0093'),
},
constants: {
__proto__: null,
configurable: false,
enumerable: true,
value: constants,
},
webcrypto: {
__proto__: null,
configurable: false,
enumerable: true,
get() { return lazyWebCrypto().crypto; },
set: undefined,
},
subtle: {
__proto__: null,
configurable: false,
enumerable: true,
get() { return lazyWebCrypto().crypto.subtle; },
set: undefined,
},
getRandomValues: {
__proto__: null,
configurable: false,
enumerable: true,
get: () => getRandomValues,
set: undefined,
},
// Aliases for randomBytes are deprecated.
// The ecosystem needs those to exist for backwards compatibility.
prng: getRandomBytesAlias('prng'),
pseudoRandomBytes: getRandomBytesAlias('pseudoRandomBytes'),
rng: getRandomBytesAlias('rng'),
});

1130
lib/dgram.js Normal file

File diff suppressed because it is too large Load Diff

438
lib/diagnostics_channel.js Normal file
View File

@ -0,0 +1,438 @@
'use strict';
const {
ArrayPrototypeAt,
ArrayPrototypeIndexOf,
ArrayPrototypePush,
ArrayPrototypePushApply,
ArrayPrototypeSlice,
ArrayPrototypeSplice,
ObjectDefineProperty,
ObjectGetPrototypeOf,
ObjectSetPrototypeOf,
Promise,
PromisePrototypeThen,
PromiseReject,
PromiseResolve,
ReflectApply,
SafeFinalizationRegistry,
SafeMap,
SymbolHasInstance,
} = primordials;
const {
codes: {
ERR_INVALID_ARG_TYPE,
},
} = require('internal/errors');
const {
validateFunction,
} = require('internal/validators');
const { triggerUncaughtException } = internalBinding('errors');
const { WeakReference } = require('internal/util');
// Can't delete when weakref count reaches 0 as it could increment again.
// Only GC can be used as a valid time to clean up the channels map.
class WeakRefMap extends SafeMap {
#finalizers = new SafeFinalizationRegistry((key) => {
this.delete(key);
});
set(key, value) {
this.#finalizers.register(value, key);
return super.set(key, new WeakReference(value));
}
get(key) {
return super.get(key)?.get();
}
incRef(key) {
return super.get(key)?.incRef();
}
decRef(key) {
return super.get(key)?.decRef();
}
}
function markActive(channel) {
// eslint-disable-next-line no-use-before-define
ObjectSetPrototypeOf(channel, ActiveChannel.prototype);
channel._subscribers = [];
channel._stores = new SafeMap();
}
function maybeMarkInactive(channel) {
// When there are no more active subscribers or bound, restore to fast prototype.
if (!channel._subscribers.length && !channel._stores.size) {
// eslint-disable-next-line no-use-before-define
ObjectSetPrototypeOf(channel, Channel.prototype);
channel._subscribers = undefined;
channel._stores = undefined;
}
}
function defaultTransform(data) {
return data;
}
function wrapStoreRun(store, data, next, transform = defaultTransform) {
return () => {
let context;
try {
context = transform(data);
} catch (err) {
process.nextTick(() => {
triggerUncaughtException(err, false);
});
return next();
}
return store.run(context, next);
};
}
// TODO(qard): should there be a C++ channel interface?
class ActiveChannel {
subscribe(subscription) {
validateFunction(subscription, 'subscription');
this._subscribers = ArrayPrototypeSlice(this._subscribers);
ArrayPrototypePush(this._subscribers, subscription);
channels.incRef(this.name);
}
unsubscribe(subscription) {
const index = ArrayPrototypeIndexOf(this._subscribers, subscription);
if (index === -1) return false;
const before = ArrayPrototypeSlice(this._subscribers, 0, index);
const after = ArrayPrototypeSlice(this._subscribers, index + 1);
this._subscribers = before;
ArrayPrototypePushApply(this._subscribers, after);
channels.decRef(this.name);
maybeMarkInactive(this);
return true;
}
bindStore(store, transform) {
const replacing = this._stores.has(store);
if (!replacing) channels.incRef(this.name);
this._stores.set(store, transform);
}
unbindStore(store) {
if (!this._stores.has(store)) {
return false;
}
this._stores.delete(store);
channels.decRef(this.name);
maybeMarkInactive(this);
return true;
}
get hasSubscribers() {
return true;
}
publish(data) {
const subscribers = this._subscribers;
for (let i = 0; i < (subscribers?.length || 0); i++) {
try {
const onMessage = subscribers[i];
onMessage(data, this.name);
} catch (err) {
process.nextTick(() => {
triggerUncaughtException(err, false);
});
}
}
}
runStores(data, fn, thisArg, ...args) {
let run = () => {
this.publish(data);
return ReflectApply(fn, thisArg, args);
};
for (const entry of this._stores.entries()) {
const store = entry[0];
const transform = entry[1];
run = wrapStoreRun(store, data, run, transform);
}
return run();
}
}
class Channel {
constructor(name) {
this._subscribers = undefined;
this._stores = undefined;
this.name = name;
channels.set(name, this);
}
static [SymbolHasInstance](instance) {
const prototype = ObjectGetPrototypeOf(instance);
return prototype === Channel.prototype ||
prototype === ActiveChannel.prototype;
}
subscribe(subscription) {
markActive(this);
this.subscribe(subscription);
}
unsubscribe() {
return false;
}
bindStore(store, transform) {
markActive(this);
this.bindStore(store, transform);
}
unbindStore() {
return false;
}
get hasSubscribers() {
return false;
}
publish() {}
runStores(data, fn, thisArg, ...args) {
return ReflectApply(fn, thisArg, args);
}
}
const channels = new WeakRefMap();
function channel(name) {
const channel = channels.get(name);
if (channel) return channel;
if (typeof name !== 'string' && typeof name !== 'symbol') {
throw new ERR_INVALID_ARG_TYPE('channel', ['string', 'symbol'], name);
}
return new Channel(name);
}
function subscribe(name, subscription) {
return channel(name).subscribe(subscription);
}
function unsubscribe(name, subscription) {
return channel(name).unsubscribe(subscription);
}
function hasSubscribers(name) {
const channel = channels.get(name);
if (!channel) return false;
return channel.hasSubscribers;
}
const traceEvents = [
'start',
'end',
'asyncStart',
'asyncEnd',
'error',
];
function assertChannel(value, name) {
if (!(value instanceof Channel)) {
throw new ERR_INVALID_ARG_TYPE(name, ['Channel'], value);
}
}
function tracingChannelFrom(nameOrChannels, name) {
if (typeof nameOrChannels === 'string') {
return channel(`tracing:${nameOrChannels}:${name}`);
}
if (typeof nameOrChannels === 'object' && nameOrChannels !== null) {
const channel = nameOrChannels[name];
assertChannel(channel, `nameOrChannels.${name}`);
return channel;
}
throw new ERR_INVALID_ARG_TYPE('nameOrChannels',
['string', 'object', 'TracingChannel'],
nameOrChannels);
}
class TracingChannel {
constructor(nameOrChannels) {
for (let i = 0; i < traceEvents.length; ++i) {
const eventName = traceEvents[i];
ObjectDefineProperty(this, eventName, {
__proto__: null,
value: tracingChannelFrom(nameOrChannels, eventName),
});
}
}
get hasSubscribers() {
return this.start?.hasSubscribers ||
this.end?.hasSubscribers ||
this.asyncStart?.hasSubscribers ||
this.asyncEnd?.hasSubscribers ||
this.error?.hasSubscribers;
}
subscribe(handlers) {
for (let i = 0; i < traceEvents.length; ++i) {
const name = traceEvents[i];
if (!handlers[name]) continue;
this[name]?.subscribe(handlers[name]);
}
}
unsubscribe(handlers) {
let done = true;
for (let i = 0; i < traceEvents.length; ++i) {
const name = traceEvents[i];
if (!handlers[name]) continue;
if (!this[name]?.unsubscribe(handlers[name])) {
done = false;
}
}
return done;
}
traceSync(fn, context = {}, thisArg, ...args) {
if (!this.hasSubscribers) {
return ReflectApply(fn, thisArg, args);
}
const { start, end, error } = this;
return start.runStores(context, () => {
try {
const result = ReflectApply(fn, thisArg, args);
context.result = result;
return result;
} catch (err) {
context.error = err;
error.publish(context);
throw err;
} finally {
end.publish(context);
}
});
}
tracePromise(fn, context = {}, thisArg, ...args) {
if (!this.hasSubscribers) {
return ReflectApply(fn, thisArg, args);
}
const { start, end, asyncStart, asyncEnd, error } = this;
function reject(err) {
context.error = err;
error.publish(context);
asyncStart.publish(context);
// TODO: Is there a way to have asyncEnd _after_ the continuation?
asyncEnd.publish(context);
return PromiseReject(err);
}
function resolve(result) {
context.result = result;
asyncStart.publish(context);
// TODO: Is there a way to have asyncEnd _after_ the continuation?
asyncEnd.publish(context);
return result;
}
return start.runStores(context, () => {
try {
let promise = ReflectApply(fn, thisArg, args);
// Convert thenables to native promises
if (!(promise instanceof Promise)) {
promise = PromiseResolve(promise);
}
return PromisePrototypeThen(promise, resolve, reject);
} catch (err) {
context.error = err;
error.publish(context);
throw err;
} finally {
end.publish(context);
}
});
}
traceCallback(fn, position = -1, context = {}, thisArg, ...args) {
if (!this.hasSubscribers) {
return ReflectApply(fn, thisArg, args);
}
const { start, end, asyncStart, asyncEnd, error } = this;
function wrappedCallback(err, res) {
if (err) {
context.error = err;
error.publish(context);
} else {
context.result = res;
}
// Using runStores here enables manual context failure recovery
asyncStart.runStores(context, () => {
try {
return ReflectApply(callback, this, arguments);
} finally {
asyncEnd.publish(context);
}
});
}
const callback = ArrayPrototypeAt(args, position);
validateFunction(callback, 'callback');
ArrayPrototypeSplice(args, position, 1, wrappedCallback);
return start.runStores(context, () => {
try {
return ReflectApply(fn, thisArg, args);
} catch (err) {
context.error = err;
error.publish(context);
throw err;
} finally {
end.publish(context);
}
});
}
}
function tracingChannel(nameOrChannels) {
return new TracingChannel(nameOrChannels);
}
module.exports = {
channel,
hasSubscribers,
subscribe,
tracingChannel,
unsubscribe,
Channel,
};

375
lib/dns.js Normal file
View File

@ -0,0 +1,375 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const {
ObjectDefineProperties,
ObjectDefineProperty,
Symbol,
} = primordials;
const cares = internalBinding('cares_wrap');
const { isIP } = require('internal/net');
const { customPromisifyArgs } = require('internal/util');
const {
DNSException,
codes: {
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
ERR_MISSING_ARGS,
},
} = require('internal/errors');
const {
bindDefaultResolver,
setDefaultResolver,
validateHints,
emitInvalidHostnameWarning,
getDefaultResultOrder,
setDefaultResultOrder,
errorCodes: dnsErrorCodes,
validDnsOrders,
validFamilies,
} = require('internal/dns/utils');
const {
Resolver,
} = require('internal/dns/callback_resolver');
const {
NODATA,
FORMERR,
SERVFAIL,
NOTFOUND,
NOTIMP,
REFUSED,
BADQUERY,
BADNAME,
BADFAMILY,
BADRESP,
CONNREFUSED,
TIMEOUT,
EOF,
FILE,
NOMEM,
DESTRUCTION,
BADSTR,
BADFLAGS,
NONAME,
BADHINTS,
NOTINITIALIZED,
LOADIPHLPAPI,
ADDRGETNETWORKPARAMS,
CANCELLED,
} = dnsErrorCodes;
const {
validateBoolean,
validateFunction,
validateNumber,
validateOneOf,
validatePort,
validateString,
} = require('internal/validators');
const {
GetAddrInfoReqWrap,
GetNameInfoReqWrap,
DNS_ORDER_VERBATIM,
DNS_ORDER_IPV4_FIRST,
DNS_ORDER_IPV6_FIRST,
} = cares;
const kPerfHooksDnsLookupContext = Symbol('kPerfHooksDnsLookupContext');
const kPerfHooksDnsLookupServiceContext = Symbol('kPerfHooksDnsLookupServiceContext');
const {
hasObserver,
startPerf,
stopPerf,
} = require('internal/perf/observe');
let promises = null; // Lazy loaded
function onlookup(err, addresses) {
if (err) {
return this.callback(new DNSException(err, 'getaddrinfo', this.hostname));
}
this.callback(null, addresses[0], this.family || isIP(addresses[0]));
if (this[kPerfHooksDnsLookupContext] && hasObserver('dns')) {
stopPerf(this, kPerfHooksDnsLookupContext, { detail: { addresses } });
}
}
function onlookupall(err, addresses) {
if (err) {
return this.callback(new DNSException(err, 'getaddrinfo', this.hostname));
}
const family = this.family;
for (let i = 0; i < addresses.length; i++) {
const addr = addresses[i];
addresses[i] = {
address: addr,
family: family || isIP(addr),
};
}
this.callback(null, addresses);
if (this[kPerfHooksDnsLookupContext] && hasObserver('dns')) {
stopPerf(this, kPerfHooksDnsLookupContext, { detail: { addresses } });
}
}
// Easy DNS A/AAAA look up
// lookup(hostname, [options,] callback)
function lookup(hostname, options, callback) {
let hints = 0;
let family = 0;
let all = false;
let dnsOrder = getDefaultResultOrder();
// Parse arguments
if (hostname) {
validateString(hostname, 'hostname');
}
if (typeof options === 'function') {
callback = options;
family = 0;
} else if (typeof options === 'number') {
validateFunction(callback, 'callback');
validateOneOf(options, 'family', validFamilies);
family = options;
} else if (options !== undefined && typeof options !== 'object') {
validateFunction(arguments.length === 2 ? options : callback, 'callback');
throw new ERR_INVALID_ARG_TYPE('options', ['integer', 'object'], options);
} else {
validateFunction(callback, 'callback');
if (options?.hints != null) {
validateNumber(options.hints, 'options.hints');
hints = options.hints >>> 0;
validateHints(hints);
}
if (options?.family != null) {
switch (options.family) {
case 'IPv4':
family = 4;
break;
case 'IPv6':
family = 6;
break;
default:
validateOneOf(options.family, 'options.family', validFamilies);
family = options.family;
break;
}
}
if (options?.all != null) {
validateBoolean(options.all, 'options.all');
all = options.all;
}
if (options?.verbatim != null) {
validateBoolean(options.verbatim, 'options.verbatim');
dnsOrder = options.verbatim ? 'verbatim' : 'ipv4first';
}
if (options?.order != null) {
validateOneOf(options.order, 'options.order', validDnsOrders);
dnsOrder = options.order;
}
}
if (!hostname) {
emitInvalidHostnameWarning(hostname);
if (all) {
process.nextTick(callback, null, []);
} else {
process.nextTick(callback, null, null, family === 6 ? 6 : 4);
}
return {};
}
const matchedFamily = isIP(hostname);
if (matchedFamily) {
if (all) {
process.nextTick(
callback, null, [{ address: hostname, family: matchedFamily }]);
} else {
process.nextTick(callback, null, hostname, matchedFamily);
}
return {};
}
const req = new GetAddrInfoReqWrap();
req.callback = callback;
req.family = family;
req.hostname = hostname;
req.oncomplete = all ? onlookupall : onlookup;
let order = DNS_ORDER_VERBATIM;
if (dnsOrder === 'ipv4first') {
order = DNS_ORDER_IPV4_FIRST;
} else if (dnsOrder === 'ipv6first') {
order = DNS_ORDER_IPV6_FIRST;
}
const err = cares.getaddrinfo(
req, hostname, family, hints, order,
);
if (err) {
process.nextTick(callback, new DNSException(err, 'getaddrinfo', hostname));
return {};
}
if (hasObserver('dns')) {
const detail = {
hostname,
family,
hints,
verbatim: order === DNS_ORDER_VERBATIM,
order: dnsOrder,
};
startPerf(req, kPerfHooksDnsLookupContext, { type: 'dns', name: 'lookup', detail });
}
return req;
}
ObjectDefineProperty(lookup, customPromisifyArgs,
{ __proto__: null, value: ['address', 'family'], enumerable: false });
function onlookupservice(err, hostname, service) {
if (err)
return this.callback(new DNSException(err, 'getnameinfo', this.hostname));
this.callback(null, hostname, service);
if (this[kPerfHooksDnsLookupServiceContext] && hasObserver('dns')) {
stopPerf(this, kPerfHooksDnsLookupServiceContext, { detail: { hostname, service } });
}
}
function lookupService(address, port, callback) {
if (arguments.length !== 3)
throw new ERR_MISSING_ARGS('address', 'port', 'callback');
if (isIP(address) === 0)
throw new ERR_INVALID_ARG_VALUE('address', address);
validatePort(port);
validateFunction(callback, 'callback');
port = +port;
const req = new GetNameInfoReqWrap();
req.callback = callback;
req.hostname = address;
req.port = port;
req.oncomplete = onlookupservice;
const err = cares.getnameinfo(req, address, port);
if (err) throw new DNSException(err, 'getnameinfo', address);
if (hasObserver('dns')) {
startPerf(req, kPerfHooksDnsLookupServiceContext, {
type: 'dns',
name: 'lookupService',
detail: {
host: address,
port,
},
});
}
return req;
}
ObjectDefineProperty(lookupService, customPromisifyArgs,
{ __proto__: null, value: ['hostname', 'service'], enumerable: false });
function defaultResolverSetServers(servers) {
const resolver = new Resolver();
resolver.setServers(servers);
setDefaultResolver(resolver);
bindDefaultResolver(module.exports, Resolver.prototype);
if (promises !== null)
bindDefaultResolver(promises, promises.Resolver.prototype);
}
module.exports = {
lookup,
lookupService,
Resolver,
getDefaultResultOrder,
setDefaultResultOrder,
setServers: defaultResolverSetServers,
// uv_getaddrinfo flags
ADDRCONFIG: cares.AI_ADDRCONFIG,
ALL: cares.AI_ALL,
V4MAPPED: cares.AI_V4MAPPED,
// ERROR CODES
NODATA,
FORMERR,
SERVFAIL,
NOTFOUND,
NOTIMP,
REFUSED,
BADQUERY,
BADNAME,
BADFAMILY,
BADRESP,
CONNREFUSED,
TIMEOUT,
EOF,
FILE,
NOMEM,
DESTRUCTION,
BADSTR,
BADFLAGS,
NONAME,
BADHINTS,
NOTINITIALIZED,
LOADIPHLPAPI,
ADDRGETNETWORKPARAMS,
CANCELLED,
};
bindDefaultResolver(module.exports, Resolver.prototype);
ObjectDefineProperties(module.exports, {
promises: {
__proto__: null,
configurable: true,
enumerable: true,
get() {
if (promises === null) {
promises = require('internal/dns/promises');
}
return promises;
},
},
});

3
lib/dns/promises.js Normal file
View File

@ -0,0 +1,3 @@
'use strict';
module.exports = require('internal/dns/promises');

556
lib/domain.js Normal file
View File

@ -0,0 +1,556 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
// WARNING: THIS MODULE IS PENDING DEPRECATION.
//
// No new pull requests targeting this module will be accepted
// unless they address existing, critical bugs.
const {
ArrayPrototypeEvery,
ArrayPrototypeIndexOf,
ArrayPrototypeLastIndexOf,
ArrayPrototypePush,
ArrayPrototypeSlice,
ArrayPrototypeSplice,
Error,
FunctionPrototypeCall,
ObjectDefineProperty,
Promise,
ReflectApply,
SafeMap,
SafeWeakMap,
StringPrototypeRepeat,
Symbol,
} = primordials;
const EventEmitter = require('events');
const {
ERR_DOMAIN_CALLBACK_NOT_AVAILABLE,
ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE,
ERR_UNHANDLED_ERROR,
} = require('internal/errors').codes;
const { createHook } = require('async_hooks');
const { useDomainTrampoline } = require('internal/async_hooks');
const kWeak = Symbol('kWeak');
const { WeakReference } = require('internal/util');
// Overwrite process.domain with a getter/setter that will allow for more
// effective optimizations
const _domain = [null];
ObjectDefineProperty(process, 'domain', {
__proto__: null,
enumerable: true,
get: function() {
return _domain[0];
},
set: function(arg) {
return _domain[0] = arg;
},
});
const vmPromises = new SafeWeakMap();
const pairing = new SafeMap();
const asyncHook = createHook({
init(asyncId, type, triggerAsyncId, resource) {
if (process.domain !== null && process.domain !== undefined) {
// If this operation is created while in a domain, let's mark it
pairing.set(asyncId, process.domain[kWeak]);
// Promises from other contexts, such as with the VM module, should not
// have a domain property as it can be used to escape the sandbox.
if (type !== 'PROMISE' || resource instanceof Promise) {
ObjectDefineProperty(resource, 'domain', {
__proto__: null,
configurable: true,
enumerable: false,
value: process.domain,
writable: true,
});
// Because promises from other contexts don't get a domain field,
// the domain needs to be held alive another way. Stuffing it in a
// weakmap connected to the promise lifetime can fix that.
} else {
vmPromises.set(resource, process.domain);
}
}
},
before(asyncId) {
const current = pairing.get(asyncId);
if (current !== undefined) { // Enter domain for this cb
// We will get the domain through current.get(), because the resource
// object's .domain property makes sure it is not garbage collected.
// However, we do need to make the reference to the domain non-weak,
// so that it cannot be garbage collected before the after() hook.
current.incRef();
current.get().enter();
}
},
after(asyncId) {
const current = pairing.get(asyncId);
if (current !== undefined) { // Exit domain for this cb
const domain = current.get();
current.decRef();
domain.exit();
}
},
destroy(asyncId) {
pairing.delete(asyncId); // cleaning up
},
});
// When domains are in use, they claim full ownership of the
// uncaught exception capture callback.
if (process.hasUncaughtExceptionCaptureCallback()) {
throw new ERR_DOMAIN_CALLBACK_NOT_AVAILABLE();
}
// Get the stack trace at the point where `domain` was required.
// eslint-disable-next-line no-restricted-syntax
const domainRequireStack = new Error('require(`domain`) at this point').stack;
const { setUncaughtExceptionCaptureCallback } = process;
process.setUncaughtExceptionCaptureCallback = function(fn) {
const err = new ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE();
err.stack += `\n${StringPrototypeRepeat('-', 40)}\n${domainRequireStack}`;
throw err;
};
let sendMakeCallbackDeprecation = false;
function emitMakeCallbackDeprecation({ target, method }) {
if (!sendMakeCallbackDeprecation) {
process.emitWarning(
'Using a domain property in MakeCallback is deprecated. Use the ' +
'async_context variant of MakeCallback or the AsyncResource class ' +
'instead. ' +
`(Triggered by calling ${method?.name || '<anonymous>'} ` +
`on ${target?.constructor?.name}.)`,
'DeprecationWarning', 'DEP0097');
sendMakeCallbackDeprecation = true;
}
}
function topLevelDomainCallback(cb, ...args) {
const domain = this.domain;
if (exports.active && domain)
emitMakeCallbackDeprecation({ target: this, method: cb });
if (domain)
domain.enter();
const ret = ReflectApply(cb, this, args);
if (domain)
domain.exit();
return ret;
}
// It's possible to enter one domain while already inside
// another one. The stack is each entered domain.
let stack = [];
exports._stack = stack;
useDomainTrampoline(topLevelDomainCallback);
function updateExceptionCapture() {
if (ArrayPrototypeEvery(stack,
(domain) => domain.listenerCount('error') === 0)) {
setUncaughtExceptionCaptureCallback(null);
} else {
setUncaughtExceptionCaptureCallback(null);
setUncaughtExceptionCaptureCallback((er) => {
return process.domain._errorHandler(er);
});
}
}
process.on('newListener', (name, listener) => {
if (name === 'uncaughtException' &&
listener !== domainUncaughtExceptionClear) {
// Make sure the first listener for `uncaughtException` always clears
// the domain stack.
process.removeListener(name, domainUncaughtExceptionClear);
process.prependListener(name, domainUncaughtExceptionClear);
}
});
process.on('removeListener', (name, listener) => {
if (name === 'uncaughtException' &&
listener !== domainUncaughtExceptionClear) {
// If the domain listener would be the only remaining one, remove it.
const listeners = process.listeners('uncaughtException');
if (listeners.length === 1 && listeners[0] === domainUncaughtExceptionClear)
process.removeListener(name, domainUncaughtExceptionClear);
}
});
function domainUncaughtExceptionClear() {
stack.length = 0;
exports.active = process.domain = null;
updateExceptionCapture();
}
class Domain extends EventEmitter {
constructor() {
super();
this.members = [];
this[kWeak] = new WeakReference(this);
asyncHook.enable();
this.on('removeListener', updateExceptionCapture);
this.on('newListener', updateExceptionCapture);
}
}
exports.Domain = Domain;
exports.create = exports.createDomain = function createDomain() {
return new Domain();
};
// The active domain is always the one that we're currently in.
exports.active = null;
Domain.prototype.members = undefined;
// Called by process._fatalException in case an error was thrown.
Domain.prototype._errorHandler = function(er) {
let caught = false;
if ((typeof er === 'object' && er !== null) || typeof er === 'function') {
ObjectDefineProperty(er, 'domain', {
__proto__: null,
configurable: true,
enumerable: false,
value: this,
writable: true,
});
er.domainThrown = true;
}
// Pop all adjacent duplicates of the currently active domain from the stack.
// This is done to prevent a domain's error handler to run within the context
// of itself, and re-entering itself recursively handler as a result of an
// exception thrown in its context.
while (exports.active === this) {
this.exit();
}
// The top-level domain-handler is handled separately.
//
// The reason is that if V8 was passed a command line option
// asking it to abort on an uncaught exception (currently
// "--abort-on-uncaught-exception"), we want an uncaught exception
// in the top-level domain error handler to make the
// process abort. Using try/catch here would always make V8 think
// that these exceptions are caught, and thus would prevent it from
// aborting in these cases.
if (stack.length === 0) {
// If there's no error handler, do not emit an 'error' event
// as this would throw an error, make the process exit, and thus
// prevent the process 'uncaughtException' event from being emitted
// if a listener is set.
if (EventEmitter.listenerCount(this, 'error') > 0) {
// Clear the uncaughtExceptionCaptureCallback so that we know that, since
// the top-level domain is not active anymore, it would be ok to abort on
// an uncaught exception at this point
setUncaughtExceptionCaptureCallback(null);
try {
caught = this.emit('error', er);
} finally {
updateExceptionCapture();
}
}
} else {
// Wrap this in a try/catch so we don't get infinite throwing
try {
// One of three things will happen here.
//
// 1. There is a handler, caught = true
// 2. There is no handler, caught = false
// 3. It throws, caught = false
//
// If caught is false after this, then there's no need to exit()
// the domain, because we're going to crash the process anyway.
caught = this.emit('error', er);
} catch (er2) {
// The domain error handler threw! oh no!
// See if another domain can catch THIS error,
// or else crash on the original one.
updateExceptionCapture();
if (stack.length) {
exports.active = process.domain = stack[stack.length - 1];
caught = process.domain._errorHandler(er2);
} else {
// Pass on to the next exception handler.
throw er2;
}
}
}
// Exit all domains on the stack. Uncaught exceptions end the
// current tick and no domains should be left on the stack
// between ticks.
domainUncaughtExceptionClear();
return caught;
};
Domain.prototype.enter = function() {
// Note that this might be a no-op, but we still need
// to push it onto the stack so that we can pop it later.
exports.active = process.domain = this;
ArrayPrototypePush(stack, this);
updateExceptionCapture();
};
Domain.prototype.exit = function() {
// Don't do anything if this domain is not on the stack.
const index = ArrayPrototypeLastIndexOf(stack, this);
if (index === -1) return;
// Exit all domains until this one.
ArrayPrototypeSplice(stack, index);
exports.active = stack.length === 0 ? undefined : stack[stack.length - 1];
process.domain = exports.active;
updateExceptionCapture();
};
// note: this works for timers as well.
Domain.prototype.add = function(ee) {
// If the domain is already added, then nothing left to do.
if (ee.domain === this)
return;
// Has a domain already - remove it first.
if (ee.domain)
ee.domain.remove(ee);
// Check for circular Domain->Domain links.
// They cause big issues.
//
// For example:
// var d = domain.create();
// var e = domain.create();
// d.add(e);
// e.add(d);
// e.emit('error', er); // RangeError, stack overflow!
if (this.domain && (ee instanceof Domain)) {
for (let d = this.domain; d; d = d.domain) {
if (ee === d) return;
}
}
ObjectDefineProperty(ee, 'domain', {
__proto__: null,
configurable: true,
enumerable: false,
value: this,
writable: true,
});
ArrayPrototypePush(this.members, ee);
};
Domain.prototype.remove = function(ee) {
ee.domain = null;
const index = ArrayPrototypeIndexOf(this.members, ee);
if (index !== -1)
ArrayPrototypeSplice(this.members, index, 1);
};
Domain.prototype.run = function(fn) {
this.enter();
const ret = ReflectApply(fn, this, ArrayPrototypeSlice(arguments, 1));
this.exit();
return ret;
};
function intercepted(_this, self, cb, fnargs) {
if (fnargs[0] && fnargs[0] instanceof Error) {
const er = fnargs[0];
er.domainBound = cb;
er.domainThrown = false;
ObjectDefineProperty(er, 'domain', {
__proto__: null,
configurable: true,
enumerable: false,
value: self,
writable: true,
});
self.emit('error', er);
return;
}
self.enter();
const ret = ReflectApply(cb, _this, ArrayPrototypeSlice(fnargs, 1));
self.exit();
return ret;
}
Domain.prototype.intercept = function(cb) {
const self = this;
function runIntercepted() {
return intercepted(this, self, cb, arguments);
}
return runIntercepted;
};
function bound(_this, self, cb, fnargs) {
self.enter();
const ret = ReflectApply(cb, _this, fnargs);
self.exit();
return ret;
}
Domain.prototype.bind = function(cb) {
const self = this;
function runBound() {
return bound(this, self, cb, arguments);
}
ObjectDefineProperty(runBound, 'domain', {
__proto__: null,
configurable: true,
enumerable: false,
value: this,
writable: true,
});
return runBound;
};
// Override EventEmitter methods to make it domain-aware.
EventEmitter.usingDomains = true;
const eventInit = EventEmitter.init;
EventEmitter.init = function(opts) {
ObjectDefineProperty(this, 'domain', {
__proto__: null,
configurable: true,
enumerable: false,
value: null,
writable: true,
});
if (exports.active && !(this instanceof exports.Domain)) {
this.domain = exports.active;
}
return FunctionPrototypeCall(eventInit, this, opts);
};
const eventEmit = EventEmitter.prototype.emit;
EventEmitter.prototype.emit = function emit(...args) {
const domain = this.domain;
const type = args[0];
const shouldEmitError = type === 'error' &&
this.listenerCount(type) > 0;
// Just call original `emit` if current EE instance has `error`
// handler, there's no active domain or this is process
if (shouldEmitError || domain === null || domain === undefined ||
this === process) {
return ReflectApply(eventEmit, this, args);
}
if (type === 'error') {
const er = args.length > 1 && args[1] ?
args[1] : new ERR_UNHANDLED_ERROR();
if (typeof er === 'object') {
er.domainEmitter = this;
ObjectDefineProperty(er, 'domain', {
__proto__: null,
configurable: true,
enumerable: false,
value: domain,
writable: true,
});
er.domainThrown = false;
}
// Remove the current domain (and its duplicates) from the domains stack and
// set the active domain to its parent (if any) so that the domain's error
// handler doesn't run in its own context. This prevents any event emitter
// created or any exception thrown in that error handler from recursively
// executing that error handler.
const origDomainsStack = ArrayPrototypeSlice(stack);
const origActiveDomain = process.domain;
// Travel the domains stack from top to bottom to find the first domain
// instance that is not a duplicate of the current active domain.
let idx = stack.length - 1;
while (idx > -1 && process.domain === stack[idx]) {
--idx;
}
// Change the stack to not contain the current active domain, and only the
// domains above it on the stack.
if (idx < 0) {
stack.length = 0;
} else {
ArrayPrototypeSplice(stack, idx + 1);
}
// Change the current active domain
if (stack.length > 0) {
exports.active = process.domain = stack[stack.length - 1];
} else {
exports.active = process.domain = null;
}
updateExceptionCapture();
domain.emit('error', er);
// Now that the domain's error handler has completed, restore the domains
// stack and the active domain to their original values.
exports._stack = stack = origDomainsStack;
exports.active = process.domain = origActiveDomain;
updateExceptionCapture();
return false;
}
domain.enter();
const ret = ReflectApply(eventEmit, this, args);
domain.exit();
return ret;
};

View File

@ -0,0 +1,528 @@
/* eslint-disable @stylistic/js/max-len */
import {
noRestrictedSyntaxCommonAll,
noRestrictedSyntaxCommonLib,
} from '../tools/eslint/eslint.config_utils.mjs';
const noRestrictedSyntax = [
'error',
...noRestrictedSyntaxCommonAll,
...noRestrictedSyntaxCommonLib,
{
selector: "CallExpression[callee.object.name='assert']:not([callee.property.name='ok']):not([callee.property.name='fail']):not([callee.property.name='ifError'])",
message: 'Only use simple assertions',
},
{
// Forbids usages of `btoa` that are not caught by no-restricted-globals, like:
// ```
// const { btoa } = internalBinding('buffer');
// btoa('...');
// ```
selector: "CallExpression[callee.property.name='btoa'], CallExpression[callee.name='btoa']",
message: "`btoa` supports only latin-1 charset, use Buffer.from(str).toString('base64') instead",
},
{
selector: 'NewExpression[callee.name=/Error$/]:not([callee.name=/^(AssertionError|NghttpError|AbortError|NodeAggregateError)$/])',
message: "Use an error exported by 'internal/errors' instead.",
},
{
selector: "CallExpression[callee.object.name='Error'][callee.property.name='captureStackTrace']",
message: "Use 'hideStackFrames' from 'internal/errors' instead.",
},
{
selector: "AssignmentExpression:matches([left.object.name='Error']):matches([left.name='prepareStackTrace'], [left.property.name='prepareStackTrace'])",
message: "Use 'overrideStackTrace' from 'internal/errors' instead.",
},
{
selector: "ThrowStatement > NewExpression[callee.name=/^ERR_[A-Z_]+$/] > ObjectExpression:first-child:not(:has([key.name='message']):has([key.name='code']):has([key.name='syscall']))",
message: 'The context passed into the SystemError constructor must include .code, .syscall, and .message properties.',
},
];
export default [
{
files: ['lib/**/*.js'],
languageOptions: {
globals: {
// Parameters passed to internal modules.
require: 'readonly',
process: 'readonly',
exports: 'readonly',
module: 'readonly',
internalBinding: 'readonly',
primordials: 'readonly',
},
},
rules: {
'prefer-object-spread': 'error',
'no-buffer-constructor': 'error',
'no-restricted-syntax': noRestrictedSyntax,
'no-restricted-globals': [
'error',
{
name: 'AbortController',
message: "Use `const { AbortController } = require('internal/abort_controller');` instead of the global.",
},
{
name: 'AbortSignal',
message: "Use `const { AbortSignal } = require('internal/abort_controller');` instead of the global.",
},
{
name: 'Blob',
message: "Use `const { Blob } = require('buffer');` instead of the global.",
},
{
name: 'BroadcastChannel',
message: "Use `const { BroadcastChannel } = require('internal/worker/io');` instead of the global.",
},
{
name: 'Buffer',
message: "Use `const { Buffer } = require('buffer');` instead of the global.",
},
{
name: 'ByteLengthQueuingStrategy',
message: "Use `const { ByteLengthQueuingStrategy } = require('internal/webstreams/queuingstrategies')` instead of the global.",
},
{
name: 'CloseEvent',
message: "Use `const { CloseEvent } = require('internal/deps/undici/undici');` instead of the global.",
},
{
name: 'CompressionStream',
message: "Use `const { CompressionStream } = require('internal/webstreams/compression')` instead of the global.",
},
{
name: 'CountQueuingStrategy',
message: "Use `const { CountQueuingStrategy } = require('internal/webstreams/queuingstrategies')` instead of the global.",
},
{
name: 'CustomEvent',
message: "Use `const { CustomEvent } = require('internal/event_target');` instead of the global.",
},
{
name: 'DecompressionStream',
message: "Use `const { DecompressionStream } = require('internal/webstreams/compression')` instead of the global.",
},
{
name: 'DOMException',
message: "Use lazy function `const { lazyDOMExceptionClass } = require('internal/util');` instead of the global.",
},
{
name: 'Event',
message: "Use `const { Event } = require('internal/event_target');` instead of the global.",
},
{
name: 'EventTarget',
message: "Use `const { EventTarget } = require('internal/event_target');` instead of the global.",
},
{
name: 'File',
message: "Use `const { File } = require('buffer');` instead of the global.",
},
{
name: 'FormData',
message: "Use `const { FormData } = require('internal/deps/undici/undici');` instead of the global.",
},
{
name: 'Headers',
message: "Use `const { Headers } = require('internal/deps/undici/undici');` instead of the global.",
},
// Intl is not available in primordials because it can be
// disabled with --without-intl build flag.
{
name: 'Intl',
message: 'Use `const { Intl } = globalThis;` instead of the global.',
},
{
name: 'Iterator',
message: 'Use `const { Iterator } = globalThis;` instead of the global.',
},
{
name: 'MessageChannel',
message: "Use `const { MessageChannel } = require('internal/worker/io');` instead of the global.",
},
{
name: 'MessageEvent',
message: "Use `const { MessageEvent } = require('internal/deps/undici/undici');` instead of the global.",
},
{
name: 'MessagePort',
message: "Use `const { MessagePort } = require('internal/worker/io');` instead of the global.",
},
{
name: 'Navigator',
message: "Use `const { Navigator } = require('internal/navigator');` instead of the global.",
},
{
name: 'navigator',
message: "Use `const { navigator } = require('internal/navigator');` instead of the global.",
},
{
name: 'PerformanceEntry',
message: "Use `const { PerformanceEntry } = require('perf_hooks');` instead of the global.",
},
{
name: 'PerformanceMark',
message: "Use `const { PerformanceMark } = require('perf_hooks');` instead of the global.",
},
{
name: 'PerformanceMeasure',
message: "Use `const { PerformanceMeasure } = require('perf_hooks');` instead of the global.",
},
{
name: 'PerformanceObserverEntryList',
message: "Use `const { PerformanceObserverEntryList } = require('perf_hooks');` instead of the global.",
},
{
name: 'PerformanceObserver',
message: "Use `const { PerformanceObserver } = require('perf_hooks');` instead of the global.",
},
{
name: 'PerformanceResourceTiming',
message: "Use `const { PerformanceResourceTiming } = require('perf_hooks');` instead of the global.",
},
{
name: 'ReadableStream',
message: "Use `const { ReadableStream } = require('internal/webstreams/readablestream')` instead of the global.",
},
{
name: 'ReadableStreamDefaultReader',
message: "Use `const { ReadableStreamDefaultReader } = require('internal/webstreams/readablestream')` instead of the global.",
},
{
name: 'ReadableStreamBYOBReader',
message: "Use `const { ReadableStreamBYOBReader } = require('internal/webstreams/readablestream')` instead of the global.",
},
{
name: 'ReadableStreamBYOBRequest',
message: "Use `const { ReadableStreamBYOBRequest } = require('internal/webstreams/readablestream')` instead of the global.",
},
{
name: 'ReadableByteStreamController',
message: "Use `const { ReadableByteStreamController } = require('internal/webstreams/readablestream')` instead of the global.",
},
{
name: 'ReadableStreamDefaultController',
message: "Use `const { ReadableStreamDefaultController } = require('internal/webstreams/readablestream')` instead of the global.",
},
{
name: 'Request',
message: "Use `const { Request } = require('internal/deps/undici/undici');` instead of the global.",
},
{
name: 'Response',
message: "Use `const { Response } = require('internal/deps/undici/undici');` instead of the global.",
},
// ShadowRealm is not available in primordials because it can be
// disabled with --no-harmony-shadow-realm CLI flag.
{
name: 'ShadowRealm',
message: 'Use `const { ShadowRealm } = globalThis;` instead of the global.',
},
// SharedArrayBuffer is not available in primordials because it can be
// disabled with --no-harmony-sharedarraybuffer CLI flag.
{
name: 'SharedArrayBuffer',
message: 'Use `const { SharedArrayBuffer } = globalThis;` instead of the global.',
},
{
name: 'TextDecoder',
message: "Use `const { TextDecoder } = require('internal/encoding');` instead of the global.",
},
{
name: 'TextDecoderStream',
message: "Use `const { TextDecoderStream } = require('internal/webstreams/encoding')` instead of the global.",
},
{
name: 'TextEncoder',
message: "Use `const { TextEncoder } = require('internal/encoding');` instead of the global.",
},
{
name: 'TextEncoderStream',
message: "Use `const { TextEncoderStream } = require('internal/webstreams/encoding')` instead of the global.",
},
{
name: 'TransformStream',
message: "Use `const { TransformStream } = require('internal/webstreams/transformstream')` instead of the global.",
},
{
name: 'TransformStreamDefaultController',
message: "Use `const { TransformStreamDefaultController } = require('internal/webstreams/transformstream')` instead of the global.",
},
{
name: 'URL',
message: "Use `const { URL } = require('internal/url');` instead of the global.",
},
{
name: 'URLSearchParams',
message: "Use `const { URLSearchParams } = require('internal/url');` instead of the global.",
},
// WebAssembly is not available in primordials because it can be
// disabled with --jitless CLI flag.
{
name: 'WebAssembly',
message: 'Use `const { WebAssembly } = globalThis;` instead of the global.',
},
{
name: 'WritableStream',
message: "Use `const { WritableStream } = require('internal/webstreams/writablestream')` instead of the global.",
},
{
name: 'WritableStreamDefaultWriter',
message: "Use `const { WritableStreamDefaultWriter } = require('internal/webstreams/writablestream')` instead of the global.",
},
{
name: 'WritableStreamDefaultController',
message: "Use `const { WritableStreamDefaultController } = require('internal/webstreams/writablestream')` instead of the global.",
},
{
name: 'atob',
message: "Use `const { atob } = require('buffer');` instead of the global.",
},
{
name: 'btoa',
message: "Use `const { btoa } = require('buffer');` instead of the global.",
},
{
name: 'clearImmediate',
message: "Use `const { clearImmediate } = require('timers');` instead of the global.",
},
{
name: 'clearInterval',
message: "Use `const { clearInterval } = require('timers');` instead of the global.",
},
{
name: 'clearTimeout',
message: "Use `const { clearTimeout } = require('timers');` instead of the global.",
},
{
name: 'console',
message: "Use `const console = require('internal/console/global');` instead of the global.",
},
{
name: 'crypto',
message: "Use `const { crypto } = require('internal/crypto/webcrypto');` instead of the global.",
},
{
name: 'Crypto',
message: "Use `const { Crypto } = require('internal/crypto/webcrypto');` instead of the global.",
},
{
name: 'CryptoKey',
message: "Use `const { CryptoKey } = require('internal/crypto/webcrypto');` instead of the global.",
},
{
name: 'EventSource',
message: "Use `const { EventSource } = require('internal/deps/undici/undici');` instead of the global.",
},
{
name: 'fetch',
message: "Use `const { fetch } = require('internal/deps/undici/undici');` instead of the global.",
},
{
name: 'global',
message: 'Use `const { globalThis } = primordials;` instead of `global`.',
},
{
name: 'globalThis',
message: 'Use `const { globalThis } = primordials;` instead of the global.',
},
{
name: 'performance',
message: "Use `const { performance } = require('perf_hooks');` instead of the global.",
},
{
name: 'queueMicrotask',
message: "Use `const { queueMicrotask } = require('internal/process/task_queues');` instead of the global.",
},
{
name: 'setImmediate',
message: "Use `const { setImmediate } = require('timers');` instead of the global.",
},
{
name: 'setInterval',
message: "Use `const { setInterval } = require('timers');` instead of the global.",
},
{
name: 'setTimeout',
message: "Use `const { setTimeout } = require('timers');` instead of the global.",
},
{
name: 'structuredClone',
message: "Use `const { structuredClone } = internalBinding('messaging');` instead of the global.",
},
{
name: 'SubtleCrypto',
message: "Use `const { SubtleCrypto } = require('internal/crypto/webcrypto');` instead of the global.",
},
// Float16Array is not available in primordials because it can be
// disabled with --no-js-float16array CLI flag.
{
name: 'Float16Array',
message: 'Use `const { Float16Array } = globalThis;` instead of the global.',
},
// DisposableStack and AsyncDisposableStack are not available in primordials because they can be
// disabled with --no-js-explicit-resource-management CLI flag.
{
name: 'DisposableStack',
message: 'Use `const { DisposableStack } = globalThis;` instead of the global.',
},
{
name: 'AsyncDisposableStack',
message: 'Use `const { AsyncDisposableStack } = globalThis;` instead of the global.',
},
],
'no-restricted-modules': [
'error',
{
name: 'url',
message: 'Require `internal/url` instead of `url`.',
},
],
// Custom rules in tools/eslint-rules.
'node-core/alphabetize-errors': 'error',
'node-core/alphabetize-primordials': 'error',
'node-core/avoid-prototype-pollution': 'error',
'node-core/lowercase-name-for-primitive': 'error',
'node-core/non-ascii-character': 'error',
'node-core/no-array-destructuring': 'error',
'node-core/prefer-primordials': [
'error',
{ name: 'AggregateError' },
{ name: 'Array' },
{ name: 'ArrayBuffer' },
{ name: 'Atomics' },
{ name: 'BigInt' },
{ name: 'BigInt64Array' },
{ name: 'BigUint64Array' },
{ name: 'Boolean' },
{ name: 'DataView' },
{ name: 'Date' },
{ name: 'decodeURI' },
{ name: 'decodeURIComponent' },
{ name: 'encodeURI' },
{ name: 'encodeURIComponent' },
{ name: 'escape' },
{ name: 'eval' },
{
name: 'Error',
ignore: [
'prepareStackTrace',
'stackTraceLimit',
],
},
{ name: 'EvalError' },
{
name: 'FinalizationRegistry',
into: 'Safe',
},
{ name: 'Float32Array' },
{ name: 'Float64Array' },
{ name: 'Function' },
{ name: 'Int16Array' },
{ name: 'Int32Array' },
{ name: 'Int8Array' },
{
name: 'isFinite',
into: 'Number',
},
{
name: 'isNaN',
into: 'Number',
},
{ name: 'JSON' },
{
name: 'Map',
into: 'Safe',
},
{ name: 'Math' },
{ name: 'Number' },
{ name: 'Object' },
{
name: 'parseFloat',
into: 'Number',
},
{
name: 'parseInt',
into: 'Number',
},
{ name: 'Proxy' },
{ name: 'Promise' },
{ name: 'RangeError' },
{ name: 'ReferenceError' },
{ name: 'Reflect' },
{ name: 'RegExp' },
{
name: 'Set',
into: 'Safe',
},
{ name: 'String' },
{ name: 'Symbol' },
{ name: 'SyntaxError' },
{ name: 'TypeError' },
{ name: 'Uint16Array' },
{ name: 'Uint32Array' },
{ name: 'Uint8Array' },
{ name: 'Uint8ClampedArray' },
{ name: 'unescape' },
{ name: 'URIError' },
{
name: 'WeakMap',
into: 'Safe',
},
{
name: 'WeakRef',
into: 'Safe',
},
{
name: 'WeakSet',
into: 'Safe',
},
],
},
},
{
files: ['lib/internal/modules/**/*.js'],
rules: {
'curly': 'error',
},
},
{
files: ['lib/internal/per_context/primordials.js'],
rules: {
'node-core/alphabetize-primordials': [
'error',
{ enforceTopPosition: false },
],
},
},
{
files: ['lib/internal/test_runner/**/*.js'],
rules: {
'node-core/set-proto-to-null-in-object': 'error',
},
},
{
files: [
'lib/_http_*.js',
'lib/_tls_*.js',
'lib/http.js',
'lib/http2.js',
'lib/internal/http.js',
'lib/internal/http2/*.js',
'lib/tls.js',
'lib/zlib.js',
],
rules: {
'no-restricted-syntax': [
...noRestrictedSyntax,
{
selector: 'VariableDeclarator:has(.init[name="primordials"]) Identifier[name=/Prototype[A-Z]/]:not([name=/^(Object|Reflect)(Get|Set)PrototypeOf$/])',
message: 'Do not use prototype primordials in this file.',
},
],
},
},
];

1212
lib/events.js Normal file

File diff suppressed because it is too large Load Diff

3407
lib/fs.js Normal file

File diff suppressed because it is too large Load Diff

3
lib/fs/promises.js Normal file
View File

@ -0,0 +1,3 @@
'use strict';
module.exports = require('internal/fs/promises').exports;

198
lib/http.js Normal file
View File

@ -0,0 +1,198 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const {
ObjectDefineProperty,
} = primordials;
const { validateInteger } = require('internal/validators');
const httpAgent = require('_http_agent');
const { ClientRequest } = require('_http_client');
const { methods, parsers } = require('_http_common');
const { IncomingMessage } = require('_http_incoming');
const {
validateHeaderName,
validateHeaderValue,
OutgoingMessage,
} = require('_http_outgoing');
const {
_connectionListener,
STATUS_CODES,
Server,
ServerResponse,
} = require('_http_server');
let maxHeaderSize;
let undici;
/**
* Returns a new instance of `http.Server`.
* @param {{
* IncomingMessage?: IncomingMessage;
* ServerResponse?: ServerResponse;
* insecureHTTPParser?: boolean;
* maxHeaderSize?: number;
* requireHostHeader?: boolean;
* joinDuplicateHeaders?: boolean;
* highWaterMark?: number;
* rejectNonStandardBodyWrites?: boolean;
* }} [opts]
* @param {Function} [requestListener]
* @returns {Server}
*/
function createServer(opts, requestListener) {
return new Server(opts, requestListener);
}
/**
* @typedef {object} HTTPRequestOptions
* @property {httpAgent.Agent | boolean} [agent] Controls Agent behavior.
* @property {string} [auth] Basic authentication ('user:password') to compute an Authorization header.
* @property {Function} [createConnection] Produces a socket/stream to use when the agent option is not used.
* @property {number} [defaultPort] Default port for the protocol.
* @property {number} [family] IP address family to use when resolving host or hostname.
* @property {object} [headers] An object containing request headers.
* @property {number} [hints] Optional dns.lookup() hints.
* @property {string} [host] A domain name or IP address of the server to issue the request to.
* @property {string} [hostname] Alias for host.
* @property {boolean} [insecureHTTPParser] Use an insecure HTTP parser that accepts invalid HTTP headers when true.
* @property {boolean} [joinDuplicateHeaders] Multiple header that joined with `,` field line values.
* @property {string} [localAddress] Local interface to bind for network connections.
* @property {number} [localPort] Local port to connect from.
* @property {Function} [lookup] Custom lookup function. Default: dns.lookup().
* @property {number} [maxHeaderSize] Overrides the --max-http-header-size value for responses received from the server.
* @property {string} [method] A string specifying the HTTP request method.
* @property {string} [path] Request path.
* @property {number} [port] Port of remote server.
* @property {string} [protocol] Protocol to use.
* @property {boolean} [setHost] Specifies whether or not to automatically add the Host header.
* @property {AbortSignal} [signal] An AbortSignal that may be used to abort an ongoing request.
* @property {string} [socketPath] Unix domain socket.
* @property {number} [timeout] A number specifying the socket timeout in milliseconds.
* @property {Array} [uniqueHeaders] A list of request headers that should be sent only once.
*/
/**
* Makes an HTTP request.
* @param {string | URL} url
* @param {HTTPRequestOptions} [options]
* @param {Function} [cb]
* @returns {ClientRequest}
*/
function request(url, options, cb) {
return new ClientRequest(url, options, cb);
}
/**
* Makes a `GET` HTTP request.
* @param {string | URL} url
* @param {HTTPRequestOptions} [options]
* @param {Function} [cb]
* @returns {ClientRequest}
*/
function get(url, options, cb) {
const req = request(url, options, cb);
req.end();
return req;
}
/**
* Lazy loads WebSocket, CloseEvent and MessageEvent classes from undici
* @returns {object} An object containing WebSocket, CloseEvent, and MessageEvent classes.
*/
function lazyUndici() {
return undici ??= require('internal/deps/undici/undici');
}
module.exports = {
_connectionListener,
METHODS: methods.toSorted(),
STATUS_CODES,
Agent: httpAgent.Agent,
ClientRequest,
IncomingMessage,
OutgoingMessage,
Server,
ServerResponse,
createServer,
validateHeaderName,
validateHeaderValue,
get,
request,
setMaxIdleHTTPParsers(max) {
validateInteger(max, 'max', 1);
parsers.max = max;
},
};
ObjectDefineProperty(module.exports, 'maxHeaderSize', {
__proto__: null,
configurable: true,
enumerable: true,
get() {
if (maxHeaderSize === undefined) {
const { getOptionValue } = require('internal/options');
maxHeaderSize = getOptionValue('--max-http-header-size');
}
return maxHeaderSize;
},
});
ObjectDefineProperty(module.exports, 'globalAgent', {
__proto__: null,
configurable: true,
enumerable: true,
get() {
return httpAgent.globalAgent;
},
set(value) {
httpAgent.globalAgent = value;
},
});
ObjectDefineProperty(module.exports, 'WebSocket', {
__proto__: null,
configurable: true,
enumerable: true,
get() {
return lazyUndici().WebSocket;
},
});
ObjectDefineProperty(module.exports, 'CloseEvent', {
__proto__: null,
configurable: true,
enumerable: true,
get() {
return lazyUndici().CloseEvent;
},
});
ObjectDefineProperty(module.exports, 'MessageEvent', {
__proto__: null,
configurable: true,
enumerable: true,
get() {
return lazyUndici().MessageEvent;
},
});

29
lib/http2.js Normal file
View File

@ -0,0 +1,29 @@
'use strict';
const {
connect,
constants,
createServer,
createSecureServer,
getDefaultSettings,
getPackedSettings,
getUnpackedSettings,
performServerHandshake,
sensitiveHeaders,
Http2ServerRequest,
Http2ServerResponse,
} = require('internal/http2/core');
module.exports = {
connect,
constants,
createServer,
createSecureServer,
getDefaultSettings,
getPackedSettings,
getUnpackedSettings,
performServerHandshake,
sensitiveHeaders,
Http2ServerRequest,
Http2ServerResponse,
};

429
lib/https.js Normal file
View File

@ -0,0 +1,429 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const {
ArrayPrototypeIndexOf,
ArrayPrototypePush,
ArrayPrototypeShift,
ArrayPrototypeSplice,
ArrayPrototypeUnshift,
FunctionPrototypeCall,
JSONStringify,
ObjectAssign,
ObjectSetPrototypeOf,
ReflectApply,
ReflectConstruct,
SymbolAsyncDispose,
} = primordials;
const {
assertCrypto,
kEmptyObject,
promisify,
} = require('internal/util');
assertCrypto();
const tls = require('tls');
const { Agent: HttpAgent } = require('_http_agent');
const {
httpServerPreClose,
Server: HttpServer,
setupConnectionsTracking,
storeHTTPOptions,
_connectionListener,
} = require('_http_server');
const { ClientRequest } = require('_http_client');
let debug = require('internal/util/debuglog').debuglog('https', (fn) => {
debug = fn;
});
const { URL, urlToHttpOptions, isURL } = require('internal/url');
const { validateObject } = require('internal/validators');
function Server(opts, requestListener) {
if (!(this instanceof Server)) return new Server(opts, requestListener);
let ALPNProtocols = ['http/1.1'];
if (typeof opts === 'function') {
requestListener = opts;
opts = kEmptyObject;
} else if (opts == null) {
opts = kEmptyObject;
} else {
validateObject(opts, 'options');
// Only one of ALPNProtocols and ALPNCallback can be set, so make sure we
// only set a default ALPNProtocols if the caller has not set either of them
if (opts.ALPNProtocols || opts.ALPNCallback)
ALPNProtocols = undefined;
}
FunctionPrototypeCall(storeHTTPOptions, this, opts);
FunctionPrototypeCall(tls.Server, this,
{
noDelay: true,
ALPNProtocols,
...opts,
},
_connectionListener);
this.httpAllowHalfOpen = false;
if (requestListener) {
this.addListener('request', requestListener);
}
this.addListener('tlsClientError', function addListener(err, conn) {
if (!this.emit('clientError', err, conn))
conn.destroy(err);
});
this.timeout = 0;
this.maxHeadersCount = null;
this.on('listening', setupConnectionsTracking);
}
ObjectSetPrototypeOf(Server.prototype, tls.Server.prototype);
ObjectSetPrototypeOf(Server, tls.Server);
Server.prototype.closeAllConnections = HttpServer.prototype.closeAllConnections;
Server.prototype.closeIdleConnections = HttpServer.prototype.closeIdleConnections;
Server.prototype.setTimeout = HttpServer.prototype.setTimeout;
Server.prototype.close = function close() {
httpServerPreClose(this);
ReflectApply(tls.Server.prototype.close, this, arguments);
return this;
};
Server.prototype[SymbolAsyncDispose] = async function() {
await FunctionPrototypeCall(promisify(this.close), this);
};
/**
* Creates a new `https.Server` instance.
* @param {{
* IncomingMessage?: IncomingMessage;
* ServerResponse?: ServerResponse;
* insecureHTTPParser?: boolean;
* maxHeaderSize?: number;
* }} [opts]
* @param {Function} [requestListener]
* @returns {Server}
*/
function createServer(opts, requestListener) {
return new Server(opts, requestListener);
}
// HTTPS agents.
function createConnection(port, host, options) {
if (port !== null && typeof port === 'object') {
options = port;
} else if (host !== null && typeof host === 'object') {
options = { ...host };
} else if (options === null || typeof options !== 'object') {
options = {};
} else {
options = { ...options };
}
if (typeof port === 'number') {
options.port = port;
}
if (typeof host === 'string') {
options.host = host;
}
debug('createConnection', options);
if (options._agentKey) {
const session = this._getSession(options._agentKey);
if (session) {
debug('reuse session for %j', options._agentKey);
options = {
session,
...options,
};
}
}
const socket = tls.connect(options);
if (options._agentKey) {
// Cache new session for reuse
socket.on('session', (session) => {
this._cacheSession(options._agentKey, session);
});
// Evict session on error
socket.once('close', (err) => {
if (err)
this._evictSession(options._agentKey);
});
}
return socket;
}
/**
* Creates a new `HttpAgent` instance.
* @param {{
* keepAlive?: boolean;
* keepAliveMsecs?: number;
* maxSockets?: number;
* maxTotalSockets?: number;
* maxFreeSockets?: number;
* scheduling?: string;
* timeout?: number;
* maxCachedSessions?: number;
* servername?: string;
* }} [options]
* @constructor
*/
function Agent(options) {
if (!(this instanceof Agent))
return new Agent(options);
FunctionPrototypeCall(HttpAgent, this, options);
this.defaultPort = 443;
this.protocol = 'https:';
this.maxCachedSessions = this.options.maxCachedSessions;
if (this.maxCachedSessions === undefined)
this.maxCachedSessions = 100;
this._sessionCache = {
map: {},
list: [],
};
}
ObjectSetPrototypeOf(Agent.prototype, HttpAgent.prototype);
ObjectSetPrototypeOf(Agent, HttpAgent);
Agent.prototype.createConnection = createConnection;
/**
* Gets a unique name for a set of options.
* @param {{
* host: string;
* port: number;
* localAddress: string;
* family: number;
* }} [options]
* @returns {string}
*/
Agent.prototype.getName = function getName(options = kEmptyObject) {
let name = FunctionPrototypeCall(HttpAgent.prototype.getName, this, options);
name += ':';
if (options.ca)
name += options.ca;
name += ':';
if (options.cert)
name += options.cert;
name += ':';
if (options.clientCertEngine)
name += options.clientCertEngine;
name += ':';
if (options.ciphers)
name += options.ciphers;
name += ':';
if (options.key)
name += options.key;
name += ':';
if (options.pfx)
name += options.pfx;
name += ':';
if (options.rejectUnauthorized !== undefined)
name += options.rejectUnauthorized;
name += ':';
if (options.servername && options.servername !== options.host)
name += options.servername;
name += ':';
if (options.minVersion)
name += options.minVersion;
name += ':';
if (options.maxVersion)
name += options.maxVersion;
name += ':';
if (options.secureProtocol)
name += options.secureProtocol;
name += ':';
if (options.crl)
name += options.crl;
name += ':';
if (options.honorCipherOrder !== undefined)
name += options.honorCipherOrder;
name += ':';
if (options.ecdhCurve)
name += options.ecdhCurve;
name += ':';
if (options.dhparam)
name += options.dhparam;
name += ':';
if (options.secureOptions !== undefined)
name += options.secureOptions;
name += ':';
if (options.sessionIdContext)
name += options.sessionIdContext;
name += ':';
if (options.sigalgs)
name += JSONStringify(options.sigalgs);
name += ':';
if (options.privateKeyIdentifier)
name += options.privateKeyIdentifier;
name += ':';
if (options.privateKeyEngine)
name += options.privateKeyEngine;
return name;
};
Agent.prototype._getSession = function _getSession(key) {
return this._sessionCache.map[key];
};
Agent.prototype._cacheSession = function _cacheSession(key, session) {
// Cache is disabled
if (this.maxCachedSessions === 0)
return;
// Fast case - update existing entry
if (this._sessionCache.map[key]) {
this._sessionCache.map[key] = session;
return;
}
// Put new entry
if (this._sessionCache.list.length >= this.maxCachedSessions) {
const oldKey = ArrayPrototypeShift(this._sessionCache.list);
debug('evicting %j', oldKey);
delete this._sessionCache.map[oldKey];
}
ArrayPrototypePush(this._sessionCache.list, key);
this._sessionCache.map[key] = session;
};
Agent.prototype._evictSession = function _evictSession(key) {
const index = ArrayPrototypeIndexOf(this._sessionCache.list, key);
if (index === -1)
return;
ArrayPrototypeSplice(this._sessionCache.list, index, 1);
delete this._sessionCache.map[key];
};
const globalAgent = new Agent({ keepAlive: true, scheduling: 'lifo', timeout: 5000 });
/**
* Makes a request to a secure web server.
* @param {...any} args
* @returns {ClientRequest}
*/
function request(...args) {
let options = {};
if (typeof args[0] === 'string') {
const urlStr = ArrayPrototypeShift(args);
options = urlToHttpOptions(new URL(urlStr));
} else if (isURL(args[0])) {
options = urlToHttpOptions(ArrayPrototypeShift(args));
}
if (args[0] && typeof args[0] !== 'function') {
ObjectAssign(options, ArrayPrototypeShift(args));
}
options._defaultAgent = module.exports.globalAgent;
ArrayPrototypeUnshift(args, options);
return ReflectConstruct(ClientRequest, args);
}
/**
* Makes a GET request to a secure web server.
* @param {string | URL} input
* @param {{
* agent?: Agent | boolean;
* auth?: string;
* createConnection?: Function;
* defaultPort?: number;
* family?: number;
* headers?: object;
* hints?: number;
* host?: string;
* hostname?: string;
* insecureHTTPParser?: boolean;
* joinDuplicateHeaders?: boolean;
* localAddress?: string;
* localPort?: number;
* lookup?: Function;
* maxHeaderSize?: number;
* method?: string;
* path?: string;
* port?: number;
* protocol?: string;
* setHost?: boolean;
* socketPath?: string;
* timeout?: number;
* signal?: AbortSignal;
* uniqueHeaders?: Array;
* } | string | URL} [options]
* @param {Function} [cb]
* @returns {ClientRequest}
*/
function get(input, options, cb) {
const req = request(input, options, cb);
req.end();
return req;
}
module.exports = {
Agent,
globalAgent,
Server,
createServer,
get,
request,
};

228
lib/inspector.js Normal file
View File

@ -0,0 +1,228 @@
'use strict';
const {
JSONParse,
JSONStringify,
SafeMap,
SymbolDispose,
} = primordials;
const {
ERR_INSPECTOR_ALREADY_ACTIVATED,
ERR_INSPECTOR_ALREADY_CONNECTED,
ERR_INSPECTOR_CLOSED,
ERR_INSPECTOR_COMMAND,
ERR_INSPECTOR_NOT_AVAILABLE,
ERR_INSPECTOR_NOT_CONNECTED,
ERR_INSPECTOR_NOT_ACTIVE,
ERR_INSPECTOR_NOT_WORKER,
} = require('internal/errors').codes;
const { isLoopback } = require('internal/net');
const { hasInspector } = internalBinding('config');
if (!hasInspector)
throw new ERR_INSPECTOR_NOT_AVAILABLE();
const EventEmitter = require('events');
const { queueMicrotask } = require('internal/process/task_queues');
const { kEmptyObject } = require('internal/util');
const {
isUint32,
validateFunction,
validateInt32,
validateObject,
validateString,
} = require('internal/validators');
const { isMainThread } = require('worker_threads');
const { _debugEnd } = internalBinding('process_methods');
const {
Connection,
MainThreadConnection,
open,
url,
isEnabled,
waitForDebugger,
console,
emitProtocolEvent,
} = internalBinding('inspector');
class Session extends EventEmitter {
#connection = null;
#nextId = 1;
#messageCallbacks = new SafeMap();
/**
* Connects the session to the inspector back-end.
* @returns {void}
*/
connect() {
if (this.#connection)
throw new ERR_INSPECTOR_ALREADY_CONNECTED('The inspector session');
this.#connection = new Connection((message) => this.#onMessage(message));
}
/**
* Connects the session to the main thread
* inspector back-end.
* @returns {void}
*/
connectToMainThread() {
if (isMainThread)
throw new ERR_INSPECTOR_NOT_WORKER();
if (this.#connection)
throw new ERR_INSPECTOR_ALREADY_CONNECTED('The inspector session');
this.#connection =
new MainThreadConnection(
(message) => queueMicrotask(() => this.#onMessage(message)));
}
#onMessage(message) {
const parsed = JSONParse(message);
try {
if (parsed.id) {
const callback = this.#messageCallbacks.get(parsed.id);
this.#messageCallbacks.delete(parsed.id);
if (callback) {
if (parsed.error) {
return callback(
new ERR_INSPECTOR_COMMAND(parsed.error.code, parsed.error.message),
);
}
callback(null, parsed.result);
}
} else {
this.emit(parsed.method, parsed);
this.emit('inspectorNotification', parsed);
}
} catch (error) {
process.emitWarning(error);
}
}
/**
* Posts a message to the inspector back-end.
* @param {string} method
* @param {Record<unknown, unknown>} [params]
* @param {Function} [callback]
* @returns {void}
*/
post(method, params, callback) {
validateString(method, 'method');
if (!callback && typeof params === 'function') {
callback = params;
params = null;
}
if (params) {
validateObject(params, 'params');
}
if (callback) {
validateFunction(callback, 'callback');
}
if (!this.#connection) {
throw new ERR_INSPECTOR_NOT_CONNECTED();
}
const id = this.#nextId++;
const message = { id, method };
if (params) {
message.params = params;
}
if (callback) {
this.#messageCallbacks.set(id, callback);
}
this.#connection.dispatch(JSONStringify(message));
}
/**
* Immediately closes the session, all pending
* message callbacks will be called with an
* error.
* @returns {void}
*/
disconnect() {
if (!this.#connection)
return;
this.#connection.disconnect();
this.#connection = null;
const remainingCallbacks = this.#messageCallbacks.values();
for (const callback of remainingCallbacks) {
process.nextTick(callback, new ERR_INSPECTOR_CLOSED());
}
this.#messageCallbacks.clear();
this.#nextId = 1;
}
}
/**
* Activates inspector on host and port.
* @param {number} [port]
* @param {string} [host]
* @param {boolean} [wait]
* @returns {void}
*/
function inspectorOpen(port, host, wait) {
if (isEnabled()) {
throw new ERR_INSPECTOR_ALREADY_ACTIVATED();
}
// inspectorOpen() currently does not typecheck its arguments and adding
// such checks would be a potentially breaking change. However, the native
// open() function requires the port to fit into a 16-bit unsigned integer,
// causing an integer overflow otherwise, so we at least need to prevent that.
if (isUint32(port)) {
validateInt32(port, 'port', 0, 65535);
}
if (host && !isLoopback(host)) {
process.emitWarning(
'Binding the inspector to a public IP with an open port is insecure, ' +
'as it allows external hosts to connect to the inspector ' +
'and perform a remote code execution attack. ' +
'Documentation can be found at ' +
'https://nodejs.org/api/cli.html#--inspecthostport',
'SecurityWarning',
);
}
open(port, host);
if (wait)
waitForDebugger();
return { __proto__: null, [SymbolDispose]() { _debugEnd(); } };
}
/**
* Blocks until a client (existing or connected later)
* has sent the `Runtime.runIfWaitingForDebugger`
* command.
* @returns {void}
*/
function inspectorWaitForDebugger() {
if (!waitForDebugger())
throw new ERR_INSPECTOR_NOT_ACTIVE();
}
function broadcastToFrontend(eventName, params = kEmptyObject) {
validateString(eventName, 'eventName');
validateObject(params, 'params');
emitProtocolEvent(eventName, params);
}
const Network = {
requestWillBeSent: (params) => broadcastToFrontend('Network.requestWillBeSent', params),
responseReceived: (params) => broadcastToFrontend('Network.responseReceived', params),
loadingFinished: (params) => broadcastToFrontend('Network.loadingFinished', params),
loadingFailed: (params) => broadcastToFrontend('Network.loadingFailed', params),
dataReceived: (params) => broadcastToFrontend('Network.dataReceived', params),
};
module.exports = {
open: inspectorOpen,
close: _debugEnd,
url,
waitForDebugger: inspectorWaitForDebugger,
console,
Session,
Network,
};

14
lib/inspector/promises.js Normal file
View File

@ -0,0 +1,14 @@
'use strict';
const inspector = require('inspector');
const { promisify } = require('internal/util');
class Session extends inspector.Session {
constructor() { super(); } // eslint-disable-line no-useless-constructor
}
Session.prototype.post = promisify(inspector.Session.prototype.post);
module.exports = {
...inspector,
Session,
};

13
lib/internal/README.md Normal file
View File

@ -0,0 +1,13 @@
# Internal Modules
The modules located in `lib/internal` directory are exclusively meant
for internal usage within the Node.js core. They are not intended to
be accessed via user modules `require()`. These modules may change at
any point in time. Relying on these internal modules outside the core
is not supported and can lead to unpredictable behavior.
In certain scenarios, accessing these internal modules for debugging or
experimental purposes might be necessary. Node.js provides the `--expose-internals`
flag to expose these modules to userland code. This flag only exists to
assist Node.js maintainers with debugging internals. It is not meant for
use outside the project.

View File

@ -0,0 +1,578 @@
'use strict';
// Modeled very closely on the AbortController implementation
// in https://github.com/mysticatea/abort-controller (MIT license)
const {
ArrayPrototypePush,
ObjectAssign,
ObjectDefineProperties,
ObjectDefineProperty,
ObjectSetPrototypeOf,
PromiseResolve,
PromiseWithResolvers,
SafeFinalizationRegistry,
SafeSet,
SafeWeakRef,
Symbol,
SymbolToStringTag,
} = primordials;
const {
defineEventHandler,
EventTarget,
Event,
kTrustEvent,
kNewListener,
kRemoveListener,
kResistStopPropagation,
kWeakHandler,
} = require('internal/event_target');
const { kMaxEventTargetListeners } = require('events');
const {
customInspectSymbol,
kEmptyObject,
kEnumerableProperty,
} = require('internal/util');
const { inspect } = require('internal/util/inspect');
const {
codes: {
ERR_ILLEGAL_CONSTRUCTOR,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_THIS,
},
} = require('internal/errors');
const {
converters,
createInterfaceConverter,
createSequenceConverter,
} = require('internal/webidl');
const {
validateObject,
validateUint32,
kValidateObjectAllowObjects,
} = require('internal/validators');
const {
DOMException,
} = internalBinding('messaging');
const {
clearTimeout,
setTimeout,
} = require('timers');
const assert = require('internal/assert');
const {
kDeserialize,
kTransfer,
kTransferList,
markTransferMode,
} = require('internal/worker/js_transferable');
let _MessageChannel;
const kDontThrowSymbol = Symbol('kDontThrowSymbol');
// Loading the MessageChannel and markTransferable have to be done lazily
// because otherwise we'll end up with a require cycle that ends up with
// an incomplete initialization of abort_controller.
function lazyMessageChannel() {
_MessageChannel ??= require('internal/worker/io').MessageChannel;
return new _MessageChannel();
}
const clearTimeoutRegistry = new SafeFinalizationRegistry(clearTimeout);
const dependantSignalsCleanupRegistry = new SafeFinalizationRegistry((signalWeakRef) => {
const signal = signalWeakRef.deref();
if (signal === undefined) {
return;
}
signal[kDependantSignals].forEach((ref) => {
if (ref.deref() === undefined) {
signal[kDependantSignals].delete(ref);
}
});
});
const gcPersistentSignals = new SafeSet();
const sourceSignalsCleanupRegistry = new SafeFinalizationRegistry(({ sourceSignalRef, composedSignalRef }) => {
const composedSignal = composedSignalRef.deref();
if (composedSignal !== undefined) {
composedSignal[kSourceSignals].delete(sourceSignalRef);
if (composedSignal[kSourceSignals].size === 0) {
// This signal will no longer abort. There's no need to keep it in the gcPersistentSignals set.
gcPersistentSignals.delete(composedSignal);
}
}
});
const kAborted = Symbol('kAborted');
const kReason = Symbol('kReason');
const kCloneData = Symbol('kCloneData');
const kTimeout = Symbol('kTimeout');
const kMakeTransferable = Symbol('kMakeTransferable');
const kComposite = Symbol('kComposite');
const kSourceSignals = Symbol('kSourceSignals');
const kDependantSignals = Symbol('kDependantSignals');
function customInspect(self, obj, depth, options) {
if (depth < 0)
return self;
const opts = ObjectAssign({}, options, {
depth: options.depth === null ? null : options.depth - 1,
});
return `${self.constructor.name} ${inspect(obj, opts)}`;
}
function validateThisAbortSignal(obj) {
if (obj?.[kAborted] === undefined)
throw new ERR_INVALID_THIS('AbortSignal');
}
// Because the AbortSignal timeout cannot be canceled, we don't want the
// presence of the timer alone to keep the AbortSignal from being garbage
// collected if it otherwise no longer accessible. We also don't want the
// timer to keep the Node.js process open on it's own. Therefore, we wrap
// the AbortSignal in a WeakRef and have the setTimeout callback close
// over the WeakRef rather than directly over the AbortSignal, and we unref
// the created timer object. Separately, we add the signal to a
// FinalizerRegistry that will clear the timeout when the signal is gc'd.
function setWeakAbortSignalTimeout(weakRef, delay) {
const timeout = setTimeout(() => {
const signal = weakRef.deref();
if (signal !== undefined) {
gcPersistentSignals.delete(signal);
abortSignal(
signal,
new DOMException(
'The operation was aborted due to timeout',
'TimeoutError'));
}
}, delay);
timeout.unref();
return timeout;
}
class AbortSignal extends EventTarget {
/**
* @param {symbol | undefined} dontThrowSymbol
* @param {{
* aborted? : boolean,
* reason? : any,
* transferable? : boolean,
* composite? : boolean,
* }} [init]
* @private
*/
constructor(dontThrowSymbol = undefined, init = kEmptyObject) {
if (dontThrowSymbol !== kDontThrowSymbol) {
throw new ERR_ILLEGAL_CONSTRUCTOR();
}
super();
this[kMaxEventTargetListeners] = 0;
const {
aborted = false,
reason = undefined,
transferable = false,
composite = false,
} = init;
this[kAborted] = aborted;
this[kReason] = reason;
this[kComposite] = composite;
if (transferable) {
markTransferMode(this, false, true);
}
}
/**
* @type {boolean}
*/
get aborted() {
validateThisAbortSignal(this);
return !!this[kAborted];
}
/**
* @type {any}
*/
get reason() {
validateThisAbortSignal(this);
return this[kReason];
}
throwIfAborted() {
validateThisAbortSignal(this);
if (this[kAborted]) {
throw this[kReason];
}
}
[customInspectSymbol](depth, options) {
return customInspect(this, {
aborted: this.aborted,
}, depth, options);
}
/**
* @param {any} [reason]
* @returns {AbortSignal}
*/
static abort(
reason = new DOMException('This operation was aborted', 'AbortError')) {
return new AbortSignal(kDontThrowSymbol, { aborted: true, reason });
}
/**
* @param {number} delay
* @returns {AbortSignal}
*/
static timeout(delay) {
validateUint32(delay, 'delay', false);
const signal = new AbortSignal(kDontThrowSymbol);
signal[kTimeout] = true;
clearTimeoutRegistry.register(
signal,
setWeakAbortSignalTimeout(new SafeWeakRef(signal), delay));
return signal;
}
/**
* @param {AbortSignal[]} signals
* @returns {AbortSignal}
*/
static any(signals) {
const signalsArray = converters['sequence<AbortSignal>'](
signals,
{ __proto__: null, context: 'signals' },
);
const resultSignal = new AbortSignal(kDontThrowSymbol, { composite: true });
if (!signalsArray.length) {
return resultSignal;
}
const resultSignalWeakRef = new SafeWeakRef(resultSignal);
resultSignal[kSourceSignals] = new SafeSet();
// Track if we have any timeout signals
let hasTimeoutSignals = false;
for (let i = 0; i < signalsArray.length; i++) {
const signal = signalsArray[i];
// Check if this is a timeout signal
if (signal[kTimeout]) {
hasTimeoutSignals = true;
// Add the timeout signal to gcPersistentSignals to keep it alive
// This is what the kNewListener method would do when adding abort listeners
gcPersistentSignals.add(signal);
}
if (signal.aborted) {
abortSignal(resultSignal, signal.reason);
return resultSignal;
}
signal[kDependantSignals] ??= new SafeSet();
if (!signal[kComposite]) {
const signalWeakRef = new SafeWeakRef(signal);
resultSignal[kSourceSignals].add(signalWeakRef);
signal[kDependantSignals].add(resultSignalWeakRef);
dependantSignalsCleanupRegistry.register(resultSignal, signalWeakRef);
sourceSignalsCleanupRegistry.register(signal, {
sourceSignalRef: signalWeakRef,
composedSignalRef: resultSignalWeakRef,
});
} else if (!signal[kSourceSignals]) {
continue;
} else {
for (const sourceSignalWeakRef of signal[kSourceSignals]) {
const sourceSignal = sourceSignalWeakRef.deref();
if (!sourceSignal) {
continue;
}
assert(!sourceSignal.aborted);
assert(!sourceSignal[kComposite]);
if (resultSignal[kSourceSignals].has(sourceSignalWeakRef)) {
continue;
}
resultSignal[kSourceSignals].add(sourceSignalWeakRef);
sourceSignal[kDependantSignals].add(resultSignalWeakRef);
dependantSignalsCleanupRegistry.register(resultSignal, sourceSignalWeakRef);
sourceSignalsCleanupRegistry.register(signal, {
sourceSignalRef: sourceSignalWeakRef,
composedSignalRef: resultSignalWeakRef,
});
}
}
}
// If we have any timeout signals, add the composite signal to gcPersistentSignals
if (hasTimeoutSignals && resultSignal[kSourceSignals].size > 0) {
gcPersistentSignals.add(resultSignal);
}
return resultSignal;
}
[kNewListener](size, type, listener, once, capture, passive, weak) {
super[kNewListener](size, type, listener, once, capture, passive, weak);
const isTimeoutOrNonEmptyCompositeSignal = this[kTimeout] || (this[kComposite] && this[kSourceSignals]?.size);
if (isTimeoutOrNonEmptyCompositeSignal &&
type === 'abort' &&
!this.aborted &&
!weak &&
size === 1) {
// If this is a timeout signal, or a non-empty composite signal, and we're adding a non-weak abort
// listener, then we don't want it to be gc'd while the listener
// is attached and the timer still hasn't fired. So, we retain a
// strong ref that is held for as long as the listener is registered.
gcPersistentSignals.add(this);
}
}
[kRemoveListener](size, type, listener, capture) {
super[kRemoveListener](size, type, listener, capture);
const isTimeoutOrNonEmptyCompositeSignal = this[kTimeout] || (this[kComposite] && this[kSourceSignals]?.size);
if (isTimeoutOrNonEmptyCompositeSignal && type === 'abort' && size === 0) {
gcPersistentSignals.delete(this);
}
}
[kTransfer]() {
validateThisAbortSignal(this);
const aborted = this.aborted;
if (aborted) {
const reason = this.reason;
return {
data: { aborted, reason },
deserializeInfo: 'internal/abort_controller:ClonedAbortSignal',
};
}
const { port1, port2 } = this[kCloneData];
this[kCloneData] = undefined;
this.addEventListener('abort', () => {
port1.postMessage(this.reason);
port1.close();
}, { once: true });
return {
data: { port: port2 },
deserializeInfo: 'internal/abort_controller:ClonedAbortSignal',
};
}
[kTransferList]() {
if (!this.aborted) {
const { port1, port2 } = lazyMessageChannel();
port1.unref();
port2.unref();
this[kCloneData] = {
port1,
port2,
};
return [port2];
}
return [];
}
[kDeserialize]({ aborted, reason, port }) {
if (aborted) {
this[kAborted] = aborted;
this[kReason] = reason;
return;
}
port.onmessage = ({ data }) => {
abortSignal(this, data);
port.close();
port.onmessage = undefined;
};
// The receiving port, by itself, should never keep the event loop open.
// The unref() has to be called *after* setting the onmessage handler.
port.unref();
}
}
converters.AbortSignal = createInterfaceConverter('AbortSignal', AbortSignal.prototype);
converters['sequence<AbortSignal>'] = createSequenceConverter(converters.AbortSignal);
function ClonedAbortSignal() {
return new AbortSignal(kDontThrowSymbol, { transferable: true });
}
ClonedAbortSignal.prototype[kDeserialize] = () => {};
ObjectDefineProperties(AbortSignal.prototype, {
aborted: kEnumerableProperty,
});
ObjectDefineProperty(AbortSignal.prototype, SymbolToStringTag, {
__proto__: null,
writable: false,
enumerable: false,
configurable: true,
value: 'AbortSignal',
});
defineEventHandler(AbortSignal.prototype, 'abort');
// https://dom.spec.whatwg.org/#dom-abortsignal-abort
function abortSignal(signal, reason) {
// 1. If signal is aborted, then return.
if (signal[kAborted]) return;
// 2. Set signal's abort reason to reason if it is given;
// otherwise to a new "AbortError" DOMException.
signal[kAborted] = true;
signal[kReason] = reason;
// 3. Let dependentSignalsToAbort be a new list.
const dependentSignalsToAbort = ObjectSetPrototypeOf([], null);
// 4. For each dependentSignal of signal's dependent signals:
signal[kDependantSignals]?.forEach((s) => {
const dependentSignal = s.deref();
// 1. If dependentSignal is not aborted, then:
if (dependentSignal && !dependentSignal[kAborted]) {
// 1. Set dependentSignal's abort reason to signal's abort reason.
dependentSignal[kReason] = reason;
dependentSignal[kAborted] = true;
// 2. Append dependentSignal to dependentSignalsToAbort.
ArrayPrototypePush(dependentSignalsToAbort, dependentSignal);
}
});
// 5. Run the abort steps for signal
runAbort(signal);
// 6. For each dependentSignal of dependentSignalsToAbort,
// run the abort steps for dependentSignal.
for (let i = 0; i < dependentSignalsToAbort.length; i++) {
const dependentSignal = dependentSignalsToAbort[i];
runAbort(dependentSignal);
}
// Clean up the signal from gcPersistentSignals
gcPersistentSignals.delete(signal);
// If this is a composite signal, also remove all of its source signals from gcPersistentSignals
// when they get dereferenced from the signal's kSourceSignals set
if (signal[kComposite] && signal[kSourceSignals]) {
signal[kSourceSignals].forEach((sourceWeakRef) => {
const sourceSignal = sourceWeakRef.deref();
if (sourceSignal) {
gcPersistentSignals.delete(sourceSignal);
}
});
}
}
// To run the abort steps for an AbortSignal signal
function runAbort(signal) {
const event = new Event('abort', {
[kTrustEvent]: true,
});
signal.dispatchEvent(event);
}
class AbortController {
#signal;
/**
* @type {AbortSignal}
*/
get signal() {
this.#signal ??= new AbortSignal(kDontThrowSymbol);
return this.#signal;
}
/**
* @param {any} [reason]
*/
abort(reason = new DOMException('This operation was aborted', 'AbortError')) {
abortSignal(this.#signal ??= new AbortSignal(kDontThrowSymbol), reason);
}
[customInspectSymbol](depth, options) {
return customInspect(this, {
signal: this.signal,
}, depth, options);
}
static [kMakeTransferable]() {
const controller = new AbortController();
controller.#signal = new AbortSignal(kDontThrowSymbol, { transferable: true });
return controller;
}
}
/**
* Enables the AbortSignal to be transferable using structuredClone/postMessage.
* @param {AbortSignal} signal
* @returns {AbortSignal}
*/
function transferableAbortSignal(signal) {
if (signal?.[kAborted] === undefined)
throw new ERR_INVALID_ARG_TYPE('signal', 'AbortSignal', signal);
markTransferMode(signal, false, true);
return signal;
}
/**
* Creates an AbortController with a transferable AbortSignal
*/
function transferableAbortController() {
return AbortController[kMakeTransferable]();
}
/**
* @param {AbortSignal} signal
* @param {any} resource
* @returns {Promise<void>}
*/
async function aborted(signal, resource) {
converters.AbortSignal(signal, { __proto__: null, context: 'signal' });
validateObject(resource, 'resource', kValidateObjectAllowObjects);
if (signal.aborted)
return PromiseResolve();
const abortPromise = PromiseWithResolvers();
const opts = { __proto__: null, [kWeakHandler]: resource, once: true, [kResistStopPropagation]: true };
signal.addEventListener('abort', abortPromise.resolve, opts);
return abortPromise.promise;
}
ObjectDefineProperties(AbortController.prototype, {
signal: kEnumerableProperty,
abort: kEnumerableProperty,
});
ObjectDefineProperty(AbortController.prototype, SymbolToStringTag, {
__proto__: null,
writable: false,
enumerable: false,
configurable: true,
value: 'AbortController',
});
module.exports = {
AbortController,
AbortSignal,
ClonedAbortSignal,
aborted,
transferableAbortSignal,
transferableAbortController,
};

22
lib/internal/assert.js Normal file
View File

@ -0,0 +1,22 @@
'use strict';
let error;
function lazyError() {
return error ??= require('internal/errors').codes.ERR_INTERNAL_ASSERTION;
}
function assert(value, message) {
if (!value) {
const ERR_INTERNAL_ASSERTION = lazyError();
throw new ERR_INTERNAL_ASSERTION(message);
}
}
function fail(message) {
const ERR_INTERNAL_ASSERTION = lazyError();
throw new ERR_INTERNAL_ASSERTION(message);
}
assert.fail = fail;
module.exports = assert;

View File

@ -0,0 +1,417 @@
'use strict';
const {
ArrayPrototypeJoin,
ArrayPrototypePop,
ArrayPrototypeSlice,
Error,
ErrorCaptureStackTrace,
ObjectAssign,
ObjectDefineProperty,
ObjectGetPrototypeOf,
ObjectPrototypeHasOwnProperty,
String,
StringPrototypeRepeat,
StringPrototypeSlice,
StringPrototypeSplit,
} = primordials;
const { isError } = require('internal/util');
const { inspect } = require('internal/util/inspect');
const colors = require('internal/util/colors');
const { validateObject } = require('internal/validators');
const { isErrorStackTraceLimitWritable } = require('internal/errors');
const { myersDiff, printMyersDiff, printSimpleMyersDiff } = require('internal/assert/myers_diff');
const kReadableOperator = {
deepStrictEqual: 'Expected values to be strictly deep-equal:',
partialDeepStrictEqual: 'Expected values to be partially and strictly deep-equal:',
strictEqual: 'Expected values to be strictly equal:',
strictEqualObject: 'Expected "actual" to be reference-equal to "expected":',
deepEqual: 'Expected values to be loosely deep-equal:',
notDeepStrictEqual: 'Expected "actual" not to be strictly deep-equal to:',
notStrictEqual: 'Expected "actual" to be strictly unequal to:',
notStrictEqualObject:
'Expected "actual" not to be reference-equal to "expected":',
notDeepEqual: 'Expected "actual" not to be loosely deep-equal to:',
notIdentical: 'Values have same structure but are not reference-equal:',
notDeepEqualUnequal: 'Expected values not to be loosely deep-equal:',
};
const kMaxShortStringLength = 12;
const kMaxLongStringLength = 512;
const kMethodsWithCustomMessageDiff = ['deepStrictEqual', 'strictEqual', 'partialDeepStrictEqual'];
function copyError(source) {
const target = ObjectAssign(
{ __proto__: ObjectGetPrototypeOf(source) },
source,
);
ObjectDefineProperty(target, 'message', {
__proto__: null,
value: source.message,
});
if (ObjectPrototypeHasOwnProperty(source, 'cause')) {
let { cause } = source;
if (isError(cause)) {
cause = copyError(cause);
}
ObjectDefineProperty(target, 'cause', { __proto__: null, value: cause });
}
return target;
}
function inspectValue(val) {
// The util.inspect default values could be changed. This makes sure the
// error messages contain the necessary information nevertheless.
return inspect(val, {
compact: false,
customInspect: false,
depth: 1000,
maxArrayLength: Infinity,
// Assert compares only enumerable properties (with a few exceptions).
showHidden: false,
// Assert does not detect proxies currently.
showProxy: false,
sorted: true,
// Inspect getters as we also check them when comparing entries.
getters: true,
});
}
function getErrorMessage(operator, message) {
return message || kReadableOperator[operator];
}
function checkOperator(actual, expected, operator) {
// In case both values are objects or functions explicitly mark them as not
// reference equal for the `strictEqual` operator.
if (
operator === 'strictEqual' &&
((typeof actual === 'object' &&
actual !== null &&
typeof expected === 'object' &&
expected !== null) ||
(typeof actual === 'function' && typeof expected === 'function'))
) {
operator = 'strictEqualObject';
}
return operator;
}
function getColoredMyersDiff(actual, expected) {
const header = `${colors.green}actual${colors.white} ${colors.red}expected${colors.white}`;
const skipped = false;
const diff = myersDiff(StringPrototypeSplit(actual, ''), StringPrototypeSplit(expected, ''));
let message = printSimpleMyersDiff(diff);
if (skipped) {
message += '...';
}
return { message, header, skipped };
}
function getStackedDiff(actual, expected) {
const isStringComparison = typeof actual === 'string' && typeof expected === 'string';
let message = `\n${colors.green}+${colors.white} ${actual}\n${colors.red}- ${colors.white}${expected}`;
const stringsLen = actual.length + expected.length;
const maxTerminalLength = process.stderr.isTTY ? process.stderr.columns : 80;
const showIndicator = isStringComparison && (stringsLen <= maxTerminalLength);
if (showIndicator) {
let indicatorIdx = -1;
for (let i = 0; i < actual.length; i++) {
if (actual[i] !== expected[i]) {
// Skip the indicator for the first 2 characters because the diff is immediately apparent
// It is 3 instead of 2 to account for the quotes
if (i >= 3) {
indicatorIdx = i;
}
break;
}
}
if (indicatorIdx !== -1) {
message += `\n${StringPrototypeRepeat(' ', indicatorIdx + 2)}^`;
}
}
return { message };
}
function getSimpleDiff(originalActual, actual, originalExpected, expected) {
let stringsLen = actual.length + expected.length;
// Accounting for the quotes wrapping strings
if (typeof originalActual === 'string') {
stringsLen -= 2;
}
if (typeof originalExpected === 'string') {
stringsLen -= 2;
}
if (stringsLen <= kMaxShortStringLength && (originalActual !== 0 || originalExpected !== 0)) {
return { message: `${actual} !== ${expected}`, header: '' };
}
const isStringComparison = typeof originalActual === 'string' && typeof originalExpected === 'string';
// colored myers diff
if (isStringComparison && colors.hasColors) {
return getColoredMyersDiff(actual, expected);
}
return getStackedDiff(actual, expected);
}
function isSimpleDiff(actual, inspectedActual, expected, inspectedExpected) {
if (inspectedActual.length > 1 || inspectedExpected.length > 1) {
return false;
}
return typeof actual !== 'object' || actual === null || typeof expected !== 'object' || expected === null;
}
function createErrDiff(actual, expected, operator, customMessage) {
operator = checkOperator(actual, expected, operator);
let skipped = false;
let message = '';
const inspectedActual = inspectValue(actual);
const inspectedExpected = inspectValue(expected);
const inspectedSplitActual = StringPrototypeSplit(inspectedActual, '\n');
const inspectedSplitExpected = StringPrototypeSplit(inspectedExpected, '\n');
const showSimpleDiff = isSimpleDiff(actual, inspectedSplitActual, expected, inspectedSplitExpected);
let header = `${colors.green}+ actual${colors.white} ${colors.red}- expected${colors.white}`;
if (showSimpleDiff) {
const simpleDiff = getSimpleDiff(actual, inspectedSplitActual[0], expected, inspectedSplitExpected[0]);
message = simpleDiff.message;
if (typeof simpleDiff.header !== 'undefined') {
header = simpleDiff.header;
}
if (simpleDiff.skipped) {
skipped = true;
}
} else if (inspectedActual === inspectedExpected) {
// Handles the case where the objects are structurally the same but different references
operator = 'notIdentical';
if (inspectedSplitActual.length > 50) {
message = `${ArrayPrototypeJoin(ArrayPrototypeSlice(inspectedSplitActual, 0, 50), '\n')}\n...}`;
skipped = true;
} else {
message = ArrayPrototypeJoin(inspectedSplitActual, '\n');
}
header = '';
} else {
const checkCommaDisparity = actual != null && typeof actual === 'object';
const diff = myersDiff(inspectedSplitActual, inspectedSplitExpected, checkCommaDisparity);
const myersDiffMessage = printMyersDiff(diff, operator);
message = myersDiffMessage.message;
if (operator === 'partialDeepStrictEqual') {
header = `${colors.gray}${colors.hasColors ? '' : '+ '}actual${colors.white} ${colors.red}- expected${colors.white}`;
}
if (myersDiffMessage.skipped) {
skipped = true;
}
}
const headerMessage = `${getErrorMessage(operator, customMessage)}\n${header}`;
const skippedMessage = skipped ? '\n... Skipped lines' : '';
return `${headerMessage}${skippedMessage}\n${message}\n`;
}
function addEllipsis(string) {
const lines = StringPrototypeSplit(string, '\n', 11);
if (lines.length > 10) {
lines.length = 10;
return `${ArrayPrototypeJoin(lines, '\n')}\n...`;
} else if (string.length > kMaxLongStringLength) {
return `${StringPrototypeSlice(string, kMaxLongStringLength)}...`;
}
return string;
}
class AssertionError extends Error {
constructor(options) {
validateObject(options, 'options');
const {
message,
operator,
stackStartFn,
details,
// Compatibility with older versions.
stackStartFunction,
} = options;
let {
actual,
expected,
} = options;
const limit = Error.stackTraceLimit;
if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0;
if (message != null) {
if (kMethodsWithCustomMessageDiff.includes(operator)) {
super(createErrDiff(actual, expected, operator, message));
} else {
super(String(message));
}
} else {
// Reset colors on each call to make sure we handle dynamically set environment
// variables correct.
colors.refresh();
// Prevent the error stack from being visible by duplicating the error
// in a very close way to the original in case both sides are actually
// instances of Error.
if (typeof actual === 'object' && actual !== null &&
typeof expected === 'object' && expected !== null &&
'stack' in actual && actual instanceof Error &&
'stack' in expected && expected instanceof Error) {
actual = copyError(actual);
expected = copyError(expected);
}
if (kMethodsWithCustomMessageDiff.includes(operator)) {
super(createErrDiff(actual, expected, operator, message));
} else if (operator === 'notDeepStrictEqual' ||
operator === 'notStrictEqual') {
// In case the objects are equal but the operator requires unequal, show
// the first object and say A equals B
let base = kReadableOperator[operator];
const res = StringPrototypeSplit(inspectValue(actual), '\n');
// In case "actual" is an object or a function, it should not be
// reference equal.
if (operator === 'notStrictEqual' &&
((typeof actual === 'object' && actual !== null) ||
typeof actual === 'function')) {
base = kReadableOperator.notStrictEqualObject;
}
// Only remove lines in case it makes sense to collapse those.
// TODO: Accept env to always show the full error.
if (res.length > 50) {
res[46] = `${colors.blue}...${colors.white}`;
while (res.length > 47) {
ArrayPrototypePop(res);
}
}
// Only print a single input.
if (res.length === 1) {
super(`${base}${res[0].length > 5 ? '\n\n' : ' '}${res[0]}`);
} else {
super(`${base}\n\n${ArrayPrototypeJoin(res, '\n')}\n`);
}
} else {
let res = inspectValue(actual);
let other = inspectValue(expected);
const knownOperator = kReadableOperator[operator];
if (operator === 'notDeepEqual' && res === other) {
res = `${knownOperator}\n\n${res}`;
if (res.length > 1024) {
res = `${StringPrototypeSlice(res, 0, 1021)}...`;
}
super(res);
} else {
if (res.length > kMaxLongStringLength) {
res = `${StringPrototypeSlice(res, 0, 509)}...`;
}
if (other.length > kMaxLongStringLength) {
other = `${StringPrototypeSlice(other, 0, 509)}...`;
}
if (operator === 'deepEqual') {
res = `${knownOperator}\n\n${res}\n\nshould loosely deep-equal\n\n`;
} else {
const newOp = kReadableOperator[`${operator}Unequal`];
if (newOp) {
res = `${newOp}\n\n${res}\n\nshould not loosely deep-equal\n\n`;
} else {
other = ` ${operator} ${other}`;
}
}
super(`${res}${other}`);
}
}
}
if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = limit;
this.generatedMessage = !message;
ObjectDefineProperty(this, 'name', {
__proto__: null,
value: 'AssertionError [ERR_ASSERTION]',
enumerable: false,
writable: true,
configurable: true,
});
this.code = 'ERR_ASSERTION';
if (details) {
this.actual = undefined;
this.expected = undefined;
this.operator = undefined;
for (let i = 0; i < details.length; i++) {
this['message ' + i] = details[i].message;
this['actual ' + i] = details[i].actual;
this['expected ' + i] = details[i].expected;
this['operator ' + i] = details[i].operator;
this['stack trace ' + i] = details[i].stack;
}
} else {
this.actual = actual;
this.expected = expected;
this.operator = operator;
}
ErrorCaptureStackTrace(this, stackStartFn || stackStartFunction);
// Create error message including the error code in the name.
this.stack; // eslint-disable-line no-unused-expressions
// Reset the name.
this.name = 'AssertionError';
}
toString() {
return `${this.name} [${this.code}]: ${this.message}`;
}
[inspect.custom](recurseTimes, ctx) {
// Long strings should not be fully inspected.
const tmpActual = this.actual;
const tmpExpected = this.expected;
if (typeof this.actual === 'string') {
this.actual = addEllipsis(this.actual);
}
if (typeof this.expected === 'string') {
this.expected = addEllipsis(this.expected);
}
// This limits the `actual` and `expected` property default inspection to
// the minimum depth. Otherwise those values would be too verbose compared
// to the actual error message which contains a combined view of these two
// input values.
const result = inspect(this, {
...ctx,
customInspect: false,
depth: 0,
});
// Reset the properties after inspection.
this.actual = tmpActual;
this.expected = tmpExpected;
return result;
}
}
module.exports = AssertionError;

View File

@ -0,0 +1,153 @@
'use strict';
const {
ArrayPrototypePush,
ArrayPrototypeSlice,
Error,
FunctionPrototype,
ObjectFreeze,
Proxy,
ReflectApply,
SafeSet,
SafeWeakMap,
} = primordials;
const {
codes: {
ERR_INVALID_ARG_VALUE,
ERR_UNAVAILABLE_DURING_EXIT,
},
} = require('internal/errors');
const AssertionError = require('internal/assert/assertion_error');
const {
validateUint32,
} = require('internal/validators');
const noop = FunctionPrototype;
class CallTrackerContext {
#expected;
#calls;
#name;
#stackTrace;
constructor({ expected, stackTrace, name }) {
this.#calls = [];
this.#expected = expected;
this.#stackTrace = stackTrace;
this.#name = name;
}
track(thisArg, args) {
const argsClone = ObjectFreeze(ArrayPrototypeSlice(args));
ArrayPrototypePush(this.#calls, ObjectFreeze({ thisArg, arguments: argsClone }));
}
get delta() {
return this.#calls.length - this.#expected;
}
reset() {
this.#calls = [];
}
getCalls() {
return ObjectFreeze(ArrayPrototypeSlice(this.#calls));
}
report() {
if (this.delta !== 0) {
const message = `Expected the ${this.#name} function to be ` +
`executed ${this.#expected} time(s) but was ` +
`executed ${this.#calls.length} time(s).`;
return {
message,
actual: this.#calls.length,
expected: this.#expected,
operator: this.#name,
stack: this.#stackTrace,
};
}
}
}
class CallTracker {
#callChecks = new SafeSet();
#trackedFunctions = new SafeWeakMap();
#getTrackedFunction(tracked) {
if (!this.#trackedFunctions.has(tracked)) {
throw new ERR_INVALID_ARG_VALUE('tracked', tracked, 'is not a tracked function');
}
return this.#trackedFunctions.get(tracked);
}
reset(tracked) {
if (tracked === undefined) {
this.#callChecks.forEach((check) => check.reset());
return;
}
this.#getTrackedFunction(tracked).reset();
}
getCalls(tracked) {
return this.#getTrackedFunction(tracked).getCalls();
}
calls(fn, expected = 1) {
if (process._exiting)
throw new ERR_UNAVAILABLE_DURING_EXIT();
if (typeof fn === 'number') {
expected = fn;
fn = noop;
} else if (fn === undefined) {
fn = noop;
}
validateUint32(expected, 'expected', true);
const context = new CallTrackerContext({
expected,
// eslint-disable-next-line no-restricted-syntax
stackTrace: new Error(),
name: fn.name || 'calls',
});
const tracked = new Proxy(fn, {
__proto__: null,
apply(fn, thisArg, argList) {
context.track(thisArg, argList);
return ReflectApply(fn, thisArg, argList);
},
});
this.#callChecks.add(context);
this.#trackedFunctions.set(tracked, context);
return tracked;
}
report() {
const errors = [];
for (const context of this.#callChecks) {
const message = context.report();
if (message !== undefined) {
ArrayPrototypePush(errors, message);
}
}
return errors;
}
verify() {
const errors = this.report();
if (errors.length === 0) {
return;
}
const message = errors.length === 1 ?
errors[0].message :
'Functions were not called the expected number of times';
throw new AssertionError({
message,
details: errors,
});
}
}
module.exports = CallTracker;

View File

@ -0,0 +1,177 @@
'use strict';
const {
ArrayPrototypePush,
Int32Array,
StringPrototypeEndsWith,
} = primordials;
const colors = require('internal/util/colors');
const kNopLinesToCollapse = 5;
const kOperations = {
DELETE: -1,
NOP: 0,
INSERT: 1,
};
function areLinesEqual(actual, expected, checkCommaDisparity) {
if (actual === expected) {
return true;
}
if (checkCommaDisparity) {
return (actual + ',') === expected || actual === (expected + ',');
}
return false;
}
function myersDiff(actual, expected, checkCommaDisparity = false) {
const actualLength = actual.length;
const expectedLength = expected.length;
const max = actualLength + expectedLength;
// TODO(BridgeAR): Cap the input in case the values go beyond the limit of 2^31 - 1.
const v = new Int32Array(2 * max + 1);
const trace = [];
for (let diffLevel = 0; diffLevel <= max; diffLevel++) {
ArrayPrototypePush(trace, new Int32Array(v)); // Clone the current state of `v`
for (let diagonalIndex = -diffLevel; diagonalIndex <= diffLevel; diagonalIndex += 2) {
const offset = diagonalIndex + max;
const previousOffset = v[offset - 1];
const nextOffset = v[offset + 1];
let x = diagonalIndex === -diffLevel || (diagonalIndex !== diffLevel && previousOffset < nextOffset) ?
nextOffset :
previousOffset + 1;
let y = x - diagonalIndex;
while (
x < actualLength &&
y < expectedLength &&
areLinesEqual(actual[x], expected[y], checkCommaDisparity)
) {
x++;
y++;
}
v[offset] = x;
if (x >= actualLength && y >= expectedLength) {
return backtrack(trace, actual, expected, checkCommaDisparity);
}
}
}
}
function backtrack(trace, actual, expected, checkCommaDisparity) {
const actualLength = actual.length;
const expectedLength = expected.length;
const max = actualLength + expectedLength;
let x = actualLength;
let y = expectedLength;
const result = [];
for (let diffLevel = trace.length - 1; diffLevel >= 0; diffLevel--) {
const v = trace[diffLevel];
const diagonalIndex = x - y;
const offset = diagonalIndex + max;
let prevDiagonalIndex;
if (
diagonalIndex === -diffLevel ||
(diagonalIndex !== diffLevel && v[offset - 1] < v[offset + 1])
) {
prevDiagonalIndex = diagonalIndex + 1;
} else {
prevDiagonalIndex = diagonalIndex - 1;
}
const prevX = v[prevDiagonalIndex + max];
const prevY = prevX - prevDiagonalIndex;
while (x > prevX && y > prevY) {
const actualItem = actual[x - 1];
const value = checkCommaDisparity && !StringPrototypeEndsWith(actualItem, ',') ? expected[y - 1] : actualItem;
ArrayPrototypePush(result, [ kOperations.NOP, value ]);
x--;
y--;
}
if (diffLevel > 0) {
if (x > prevX) {
ArrayPrototypePush(result, [ kOperations.INSERT, actual[--x] ]);
} else {
ArrayPrototypePush(result, [ kOperations.DELETE, expected[--y] ]);
}
}
}
return result;
}
function printSimpleMyersDiff(diff) {
let message = '';
for (let diffIdx = diff.length - 1; diffIdx >= 0; diffIdx--) {
const { 0: operation, 1: value } = diff[diffIdx];
let color = colors.white;
if (operation === kOperations.INSERT) {
color = colors.green;
} else if (operation === kOperations.DELETE) {
color = colors.red;
}
message += `${color}${value}${colors.white}`;
}
return `\n${message}`;
}
function printMyersDiff(diff, operator) {
let message = '';
let skipped = false;
let nopCount = 0;
for (let diffIdx = diff.length - 1; diffIdx >= 0; diffIdx--) {
const { 0: operation, 1: value } = diff[diffIdx];
const previousOperation = diffIdx < diff.length - 1 ? diff[diffIdx + 1][0] : null;
// Avoid grouping if only one line would have been grouped otherwise
if (previousOperation === kOperations.NOP && operation !== previousOperation) {
if (nopCount === kNopLinesToCollapse + 1) {
message += `${colors.white} ${diff[diffIdx + 1][1]}\n`;
} else if (nopCount === kNopLinesToCollapse + 2) {
message += `${colors.white} ${diff[diffIdx + 2][1]}\n`;
message += `${colors.white} ${diff[diffIdx + 1][1]}\n`;
} else if (nopCount >= kNopLinesToCollapse + 3) {
message += `${colors.blue}...${colors.white}\n`;
message += `${colors.white} ${diff[diffIdx + 1][1]}\n`;
skipped = true;
}
nopCount = 0;
}
if (operation === kOperations.INSERT) {
if (operator === 'partialDeepStrictEqual') {
message += `${colors.gray}${colors.hasColors ? ' ' : '+'} ${value}${colors.white}\n`;
} else {
message += `${colors.green}+${colors.white} ${value}\n`;
}
} else if (operation === kOperations.DELETE) {
message += `${colors.red}-${colors.white} ${value}\n`;
} else if (operation === kOperations.NOP) {
if (nopCount < kNopLinesToCollapse) {
message += `${colors.white} ${value}\n`;
}
nopCount++;
}
}
message = message.trimEnd();
return { message: `\n${message}`, skipped };
}
module.exports = { myersDiff, printMyersDiff, printSimpleMyersDiff };

View File

@ -0,0 +1,287 @@
'use strict';
const {
ArrayPrototypeShift,
Error,
ErrorCaptureStackTrace,
FunctionPrototypeBind,
RegExpPrototypeSymbolReplace,
SafeMap,
StringPrototypeCharCodeAt,
StringPrototypeIncludes,
StringPrototypeIndexOf,
StringPrototypeReplace,
StringPrototypeSlice,
StringPrototypeSplit,
StringPrototypeStartsWith,
} = primordials;
const { Buffer } = require('buffer');
const {
isErrorStackTraceLimitWritable,
overrideStackTrace,
} = require('internal/errors');
const AssertionError = require('internal/assert/assertion_error');
const { openSync, closeSync, readSync } = require('fs');
const { EOL } = require('internal/constants');
const { BuiltinModule } = require('internal/bootstrap/realm');
const { isError } = require('internal/util');
const errorCache = new SafeMap();
const { fileURLToPath } = require('internal/url');
let parseExpressionAt;
let findNodeAround;
let tokenizer;
let decoder;
// Escape control characters but not \n and \t to keep the line breaks and
// indentation intact.
// eslint-disable-next-line no-control-regex
const escapeSequencesRegExp = /[\x00-\x08\x0b\x0c\x0e-\x1f]/g;
const meta = [
'\\u0000', '\\u0001', '\\u0002', '\\u0003', '\\u0004',
'\\u0005', '\\u0006', '\\u0007', '\\b', '',
'', '\\u000b', '\\f', '', '\\u000e',
'\\u000f', '\\u0010', '\\u0011', '\\u0012', '\\u0013',
'\\u0014', '\\u0015', '\\u0016', '\\u0017', '\\u0018',
'\\u0019', '\\u001a', '\\u001b', '\\u001c', '\\u001d',
'\\u001e', '\\u001f',
];
const escapeFn = (str) => meta[StringPrototypeCharCodeAt(str, 0)];
function findColumn(fd, column, code) {
if (code.length > column + 100) {
try {
return parseCode(code, column);
} catch {
// End recursion in case no code could be parsed. The expression should
// have been found after 2500 characters, so stop trying.
if (code.length - column > 2500) {
// eslint-disable-next-line no-throw-literal
throw null;
}
}
}
// Read up to 2500 bytes more than necessary in columns. That way we address
// multi byte characters and read enough data to parse the code.
const bytesToRead = column - code.length + 2500;
const buffer = Buffer.allocUnsafe(bytesToRead);
const bytesRead = readSync(fd, buffer, 0, bytesToRead);
code += decoder.write(buffer.slice(0, bytesRead));
// EOF: fast path.
if (bytesRead < bytesToRead) {
return parseCode(code, column);
}
// Read potentially missing code.
return findColumn(fd, column, code);
}
function getCode(fd, line, column) {
let bytesRead = 0;
if (line === 0) {
// Special handle line number one. This is more efficient and simplifies the
// rest of the algorithm. Read more than the regular column number in bytes
// to prevent multiple reads in case multi byte characters are used.
return findColumn(fd, column, '');
}
let lines = 0;
// Prevent blocking the event loop by limiting the maximum amount of
// data that may be read.
let maxReads = 32; // bytesPerRead * maxReads = 512 KiB
const bytesPerRead = 16384;
// Use a single buffer up front that is reused until the call site is found.
let buffer = Buffer.allocUnsafe(bytesPerRead);
while (maxReads-- !== 0) {
// Only allocate a new buffer in case the needed line is found. All data
// before that can be discarded.
buffer = lines < line ? buffer : Buffer.allocUnsafe(bytesPerRead);
bytesRead = readSync(fd, buffer, 0, bytesPerRead);
// Read the buffer until the required code line is found.
for (let i = 0; i < bytesRead; i++) {
if (buffer[i] === 10 && ++lines === line) {
// If the end of file is reached, directly parse the code and return.
if (bytesRead < bytesPerRead) {
return parseCode(buffer.toString('utf8', i + 1, bytesRead), column);
}
// Check if the read code is sufficient or read more until the whole
// expression is read. Make sure multi byte characters are preserved
// properly by using the decoder.
const code = decoder.write(buffer.slice(i + 1, bytesRead));
return findColumn(fd, column, code);
}
}
}
}
function parseCode(code, offset) {
// Lazy load acorn.
if (parseExpressionAt === undefined) {
const Parser = require('internal/deps/acorn/acorn/dist/acorn').Parser;
({ findNodeAround } = require('internal/deps/acorn/acorn-walk/dist/walk'));
parseExpressionAt = FunctionPrototypeBind(Parser.parseExpressionAt, Parser);
tokenizer = FunctionPrototypeBind(Parser.tokenizer, Parser);
}
let node;
let start;
// Parse the read code until the correct expression is found.
for (const token of tokenizer(code, { ecmaVersion: 'latest' })) {
start = token.start;
if (start > offset) {
// No matching expression found. This could happen if the assert
// expression is bigger than the provided buffer.
break;
}
try {
node = parseExpressionAt(code, start, { ecmaVersion: 'latest' });
// Find the CallExpression in the tree.
node = findNodeAround(node, offset, 'CallExpression');
if (node?.node.end >= offset) {
return [
node.node.start,
StringPrototypeReplace(StringPrototypeSlice(code,
node.node.start, node.node.end),
escapeSequencesRegExp, escapeFn),
];
}
// eslint-disable-next-line no-unused-vars
} catch (err) {
continue;
}
}
// eslint-disable-next-line no-throw-literal
throw null;
}
function getErrMessage(message, fn) {
const tmpLimit = Error.stackTraceLimit;
const errorStackTraceLimitIsWritable = isErrorStackTraceLimitWritable();
// Make sure the limit is set to 1. Otherwise it could fail (<= 0) or it
// does to much work.
if (errorStackTraceLimitIsWritable) Error.stackTraceLimit = 1;
// We only need the stack trace. To minimize the overhead use an object
// instead of an error.
const err = {};
ErrorCaptureStackTrace(err, fn);
if (errorStackTraceLimitIsWritable) Error.stackTraceLimit = tmpLimit;
overrideStackTrace.set(err, (_, stack) => stack);
const call = err.stack[0];
let filename = call.getFileName();
const line = call.getLineNumber() - 1;
let column = call.getColumnNumber() - 1;
let identifier;
let code;
if (filename) {
identifier = `${filename}${line}${column}`;
// Skip Node.js modules!
if (StringPrototypeStartsWith(filename, 'node:') &&
BuiltinModule.exists(StringPrototypeSlice(filename, 5))) {
errorCache.set(identifier, undefined);
return;
}
} else {
return message;
}
if (errorCache.has(identifier)) {
return errorCache.get(identifier);
}
let fd;
try {
// Set the stack trace limit to zero. This makes sure unexpected token
// errors are handled faster.
if (errorStackTraceLimitIsWritable) Error.stackTraceLimit = 0;
if (filename) {
if (decoder === undefined) {
const { StringDecoder } = require('string_decoder');
decoder = new StringDecoder('utf8');
}
// ESM file prop is a file proto. Convert that to path.
// This ensure opensync will not throw ENOENT for ESM files.
const fileProtoPrefix = 'file://';
if (StringPrototypeStartsWith(filename, fileProtoPrefix)) {
filename = fileURLToPath(filename);
}
fd = openSync(filename, 'r', 0o666);
// Reset column and message.
({ 0: column, 1: message } = getCode(fd, line, column));
// Flush unfinished multi byte characters.
decoder.end();
} else {
for (let i = 0; i < line; i++) {
code = StringPrototypeSlice(code,
StringPrototypeIndexOf(code, '\n') + 1);
}
({ 0: column, 1: message } = parseCode(code, column));
}
// Always normalize indentation, otherwise the message could look weird.
if (StringPrototypeIncludes(message, '\n')) {
if (EOL === '\r\n') {
message = RegExpPrototypeSymbolReplace(/\r\n/g, message, '\n');
}
const frames = StringPrototypeSplit(message, '\n');
message = ArrayPrototypeShift(frames);
for (const frame of frames) {
let pos = 0;
while (pos < column && (frame[pos] === ' ' || frame[pos] === '\t')) {
pos++;
}
message += `\n ${StringPrototypeSlice(frame, pos)}`;
}
}
message = `The expression evaluated to a falsy value:\n\n ${message}\n`;
// Make sure to always set the cache! No matter if the message is
// undefined or not
errorCache.set(identifier, message);
return message;
} catch {
// Invalidate cache to prevent trying to read this part again.
errorCache.set(identifier, undefined);
} finally {
// Reset limit.
if (errorStackTraceLimitIsWritable) Error.stackTraceLimit = tmpLimit;
if (fd !== undefined)
closeSync(fd);
}
}
function innerOk(fn, argLen, value, message) {
if (!value) {
let generatedMessage = false;
if (argLen === 0) {
generatedMessage = true;
message = 'No value argument passed to `assert.ok()`';
} else if (message == null) {
generatedMessage = true;
message = getErrMessage(message, fn);
} else if (isError(message)) {
throw message;
}
const err = new AssertionError({
actual: value,
expected: true,
message,
operator: '==',
stackStartFn: fn,
});
err.generatedMessage = generatedMessage;
throw err;
}
}
module.exports = {
innerOk,
};

View File

@ -0,0 +1,77 @@
'use strict';
const {
ObjectSetPrototypeOf,
SafeMap,
} = primordials;
const {
getContinuationPreservedEmbedderData,
setContinuationPreservedEmbedderData,
} = internalBinding('async_context_frame');
let enabled_;
class ActiveAsyncContextFrame extends SafeMap {
static get enabled() {
return true;
}
static current() {
return getContinuationPreservedEmbedderData();
}
static set(frame) {
setContinuationPreservedEmbedderData(frame);
}
static exchange(frame) {
const prior = this.current();
this.set(frame);
return prior;
}
static disable(store) {
const frame = this.current();
frame?.disable(store);
}
}
function checkEnabled() {
const enabled = require('internal/options')
.getOptionValue('--async-context-frame');
// If enabled, swap to active prototype so we don't need to check status
// on every interaction with the async context frame.
if (enabled) {
// eslint-disable-next-line no-use-before-define
ObjectSetPrototypeOf(AsyncContextFrame, ActiveAsyncContextFrame);
}
return enabled;
}
class InactiveAsyncContextFrame extends SafeMap {
static get enabled() {
enabled_ ??= checkEnabled();
return enabled_;
}
static current() {}
static set(frame) {}
static exchange(frame) {}
static disable(store) {}
}
class AsyncContextFrame extends InactiveAsyncContextFrame {
constructor(store, data) {
super(AsyncContextFrame.current());
this.set(store, data);
}
disable(store) {
this.delete(store);
}
}
module.exports = AsyncContextFrame;

629
lib/internal/async_hooks.js Normal file
View File

@ -0,0 +1,629 @@
'use strict';
const {
ArrayPrototypeSlice,
ErrorCaptureStackTrace,
ObjectDefineProperty,
ObjectPrototypeHasOwnProperty,
Symbol,
} = primordials;
const { exitCodes: { kGenericUserError } } = internalBinding('errors');
const async_wrap = internalBinding('async_wrap');
const { setCallbackTrampoline } = async_wrap;
/* async_hook_fields is a Uint32Array wrapping the uint32_t array of
* Environment::AsyncHooks::fields_[]. Each index tracks the number of active
* hooks for each type.
*
* async_id_fields is a Float64Array wrapping the double array of
* Environment::AsyncHooks::async_id_fields_[]. Each index contains the ids for
* the various asynchronous states of the application. These are:
* kExecutionAsyncId: The async_id assigned to the resource responsible for the
* current execution stack.
* kTriggerAsyncId: The async_id of the resource that caused (or 'triggered')
* the resource corresponding to the current execution stack.
* kAsyncIdCounter: Incremental counter tracking the next assigned async_id.
* kDefaultTriggerAsyncId: Written immediately before a resource's constructor
* that sets the value of the init()'s triggerAsyncId. The precedence order
* of retrieving the triggerAsyncId value is:
* 1. the value passed directly to the constructor
* 2. value set in kDefaultTriggerAsyncId
* 3. executionAsyncId of the current resource.
*
* async_ids_stack is a Float64Array that contains part of the async ID
* stack. Each pushAsyncContext() call adds two doubles to it, and each
* popAsyncContext() call removes two doubles from it.
* It has a fixed size, so if that is exceeded, calls to the native
* side are used instead in pushAsyncContext() and popAsyncContext().
*/
const {
async_hook_fields,
async_id_fields,
execution_async_resources,
} = async_wrap;
// Store the pair executionAsyncId and triggerAsyncId in a AliasedFloat64Array
// in Environment::AsyncHooks::async_ids_stack_ which tracks the resource
// responsible for the current execution stack. This is unwound as each resource
// exits. In the case of a fatal exception this stack is emptied after calling
// each hook's after() callback.
const {
pushAsyncContext: pushAsyncContext_,
popAsyncContext: popAsyncContext_,
executionAsyncResource: executionAsyncResource_,
clearAsyncIdStack,
} = async_wrap;
// Properties in active_hooks are used to keep track of the set of hooks being
// executed in case another hook is enabled/disabled. The new set of hooks is
// then restored once the active set of hooks is finished executing.
const active_hooks = {
// Array of all AsyncHooks that will be iterated whenever an async event
// fires. Using var instead of (preferably const) in order to assign
// active_hooks.tmp_array if a hook is enabled/disabled during hook
// execution.
array: [],
// Use a counter to track nested calls of async hook callbacks and make sure
// the active_hooks.array isn't altered mid execution.
call_depth: 0,
// Use to temporarily store and updated active_hooks.array if the user
// enables or disables a hook while hooks are being processed. If a hook is
// enabled() or disabled() during hook execution then the current set of
// active hooks is duplicated and set equal to active_hooks.tmp_array. Any
// subsequent changes are on the duplicated array. When all hooks have
// completed executing active_hooks.tmp_array is assigned to
// active_hooks.array.
tmp_array: null,
// Keep track of the field counts held in active_hooks.tmp_array. Because the
// async_hook_fields can't be reassigned, store each uint32 in an array that
// is written back to async_hook_fields when active_hooks.array is restored.
tmp_fields: null,
};
const { registerDestroyHook } = async_wrap;
const { enqueueMicrotask } = internalBinding('task_queue');
const { resource_symbol, owner_symbol } = internalBinding('symbols');
// Each constant tracks how many callbacks there are for any given step of
// async execution. These are tracked so if the user didn't include callbacks
// for a given step, that step can bail out early.
const {
kInit, kBefore, kAfter, kDestroy, kTotals, kPromiseResolve,
kCheck, kExecutionAsyncId, kAsyncIdCounter, kTriggerAsyncId,
kDefaultTriggerAsyncId, kStackLength, kUsesExecutionAsyncResource,
} = async_wrap.constants;
const { async_id_symbol,
trigger_async_id_symbol } = internalBinding('symbols');
// Lazy load of internal/util/inspect;
let inspect;
// Used in AsyncHook and AsyncResource.
const init_symbol = Symbol('init');
const before_symbol = Symbol('before');
const after_symbol = Symbol('after');
const destroy_symbol = Symbol('destroy');
const promise_resolve_symbol = Symbol('promiseResolve');
const emitBeforeNative = emitHookFactory(before_symbol, 'emitBeforeNative');
const emitAfterNative = emitHookFactory(after_symbol, 'emitAfterNative');
const emitDestroyNative = emitHookFactory(destroy_symbol, 'emitDestroyNative');
const emitPromiseResolveNative =
emitHookFactory(promise_resolve_symbol, 'emitPromiseResolveNative');
let domain_cb;
function useDomainTrampoline(fn) {
domain_cb = fn;
}
function callbackTrampoline(asyncId, resource, cb, ...args) {
const index = async_hook_fields[kStackLength] - 1;
execution_async_resources[index] = resource;
if (asyncId !== 0 && hasHooks(kBefore))
emitBeforeNative(asyncId);
let result;
if (asyncId === 0 && typeof domain_cb === 'function') {
args.unshift(cb);
result = domain_cb.apply(this, args);
} else {
result = cb.apply(this, args);
}
if (asyncId !== 0 && hasHooks(kAfter))
emitAfterNative(asyncId);
execution_async_resources.pop();
return result;
}
const topLevelResource = {};
function executionAsyncResource() {
// Indicate to the native layer that this function is likely to be used,
// in which case it will inform JS about the current async resource via
// the trampoline above.
async_hook_fields[kUsesExecutionAsyncResource] = 1;
const index = async_hook_fields[kStackLength] - 1;
if (index === -1) return topLevelResource;
const resource = execution_async_resources[index] ||
executionAsyncResource_(index);
return lookupPublicResource(resource);
}
function inspectExceptionValue(e) {
inspect ??= require('internal/util/inspect').inspect;
return { message: inspect(e) };
}
// Used to fatally abort the process if a callback throws.
function fatalError(e) {
if (typeof e?.stack === 'string') {
process._rawDebug(e.stack);
} else {
const o = inspectExceptionValue(e);
ErrorCaptureStackTrace(o, fatalError);
process._rawDebug(o.stack);
}
const { getOptionValue } = require('internal/options');
if (getOptionValue('--abort-on-uncaught-exception')) {
process.abort();
}
process.exit(kGenericUserError);
}
function lookupPublicResource(resource) {
if (typeof resource !== 'object' || resource === null) return resource;
// TODO(addaleax): Merge this with owner_symbol and use it across all
// AsyncWrap instances.
const publicResource = resource[resource_symbol];
if (publicResource !== undefined)
return publicResource;
return resource;
}
// Emit From Native //
// Used by C++ to call all init() callbacks. Because some state can be setup
// from C++ there's no need to perform all the same operations as in
// emitInitScript.
function emitInitNative(asyncId, type, triggerAsyncId, resource) {
active_hooks.call_depth += 1;
resource = lookupPublicResource(resource);
// Use a single try/catch for all hooks to avoid setting up one per iteration.
try {
// Using var here instead of let because "for (var ...)" is faster than let.
// Refs: https://github.com/nodejs/node/pull/30380#issuecomment-552948364
// eslint-disable-next-line no-var
for (var i = 0; i < active_hooks.array.length; i++) {
if (typeof active_hooks.array[i][init_symbol] === 'function') {
active_hooks.array[i][init_symbol](
asyncId, type, triggerAsyncId,
resource,
);
}
}
} catch (e) {
fatalError(e);
} finally {
active_hooks.call_depth -= 1;
}
// Hooks can only be restored if there have been no recursive hook calls.
// Also the active hooks do not need to be restored if enable()/disable()
// weren't called during hook execution, in which case active_hooks.tmp_array
// will be null.
if (active_hooks.call_depth === 0 && active_hooks.tmp_array !== null) {
restoreActiveHooks();
}
}
// Called from native. The asyncId stack handling is taken care of there
// before this is called.
function emitHook(symbol, asyncId) {
active_hooks.call_depth += 1;
// Use a single try/catch for all hook to avoid setting up one per
// iteration.
try {
// Using var here instead of let because "for (var ...)" is faster than let.
// Refs: https://github.com/nodejs/node/pull/30380#issuecomment-552948364
// eslint-disable-next-line no-var
for (var i = 0; i < active_hooks.array.length; i++) {
if (typeof active_hooks.array[i][symbol] === 'function') {
active_hooks.array[i][symbol](asyncId);
}
}
} catch (e) {
fatalError(e);
} finally {
active_hooks.call_depth -= 1;
}
// Hooks can only be restored if there have been no recursive hook calls.
// Also the active hooks do not need to be restored if enable()/disable()
// weren't called during hook execution, in which case
// active_hooks.tmp_array will be null.
if (active_hooks.call_depth === 0 && active_hooks.tmp_array !== null) {
restoreActiveHooks();
}
}
function emitHookFactory(symbol, name) {
const fn = emitHook.bind(undefined, symbol);
// Set the name property of the function as it looks good in the stack trace.
ObjectDefineProperty(fn, 'name', {
__proto__: null,
value: name,
});
return fn;
}
// Manage Active Hooks //
function getHookArrays() {
if (active_hooks.call_depth === 0)
return [active_hooks.array, async_hook_fields];
// If this hook is being enabled while in the middle of processing the array
// of currently active hooks then duplicate the current set of active hooks
// and store this there. This shouldn't fire until the next time hooks are
// processed.
if (active_hooks.tmp_array === null)
storeActiveHooks();
return [active_hooks.tmp_array, active_hooks.tmp_fields];
}
function storeActiveHooks() {
active_hooks.tmp_array = ArrayPrototypeSlice(active_hooks.array);
// Don't want to make the assumption that kInit to kDestroy are indexes 0 to
// 4. So do this the long way.
active_hooks.tmp_fields = [];
copyHooks(active_hooks.tmp_fields, async_hook_fields);
}
function copyHooks(destination, source) {
destination[kInit] = source[kInit];
destination[kBefore] = source[kBefore];
destination[kAfter] = source[kAfter];
destination[kDestroy] = source[kDestroy];
destination[kPromiseResolve] = source[kPromiseResolve];
}
// Then restore the correct hooks array in case any hooks were added/removed
// during hook callback execution.
function restoreActiveHooks() {
active_hooks.array = active_hooks.tmp_array;
copyHooks(async_hook_fields, active_hooks.tmp_fields);
active_hooks.tmp_array = null;
active_hooks.tmp_fields = null;
}
function trackPromise(promise, parent) {
if (promise[async_id_symbol]) {
return;
}
// Get trigger id from parent async id before making the async id of the
// child so if a new one must be made it will be lower than the child.
const triggerAsyncId = parent ? getOrSetAsyncId(parent) :
getDefaultTriggerAsyncId();
promise[async_id_symbol] = newAsyncId();
promise[trigger_async_id_symbol] = triggerAsyncId;
}
function promiseInitHook(promise, parent) {
trackPromise(promise, parent);
const asyncId = promise[async_id_symbol];
const triggerAsyncId = promise[trigger_async_id_symbol];
emitInitScript(asyncId, 'PROMISE', triggerAsyncId, promise);
}
function promiseInitHookWithDestroyTracking(promise, parent) {
promiseInitHook(promise, parent);
destroyTracking(promise, parent);
}
function destroyTracking(promise, parent) {
trackPromise(promise, parent);
const asyncId = promise[async_id_symbol];
registerDestroyHook(promise, asyncId);
}
function promiseBeforeHook(promise) {
trackPromise(promise);
const asyncId = promise[async_id_symbol];
const triggerId = promise[trigger_async_id_symbol];
emitBeforeScript(asyncId, triggerId, promise);
}
function promiseAfterHook(promise) {
trackPromise(promise);
const asyncId = promise[async_id_symbol];
if (hasHooks(kAfter)) {
emitAfterNative(asyncId);
}
if (asyncId === executionAsyncId()) {
// This condition might not be true if async_hooks was enabled during
// the promise callback execution.
// Popping it off the stack can be skipped in that case, because it is
// known that it would correspond to exactly one call with
// PromiseHookType::kBefore that was not witnessed by the PromiseHook.
popAsyncContext(asyncId);
}
}
function promiseResolveHook(promise) {
trackPromise(promise);
const asyncId = promise[async_id_symbol];
emitPromiseResolveNative(asyncId);
}
let wantPromiseHook = false;
function enableHooks() {
async_hook_fields[kCheck] += 1;
setCallbackTrampoline(callbackTrampoline);
}
let stopPromiseHook;
function updatePromiseHookMode() {
wantPromiseHook = true;
let initHook;
if (initHooksExist()) {
initHook = destroyHooksExist() ? promiseInitHookWithDestroyTracking :
promiseInitHook;
} else if (destroyHooksExist()) {
initHook = destroyTracking;
}
if (stopPromiseHook) stopPromiseHook();
const promiseHooks = require('internal/promise_hooks');
stopPromiseHook = promiseHooks.createHook({
init: initHook,
before: promiseBeforeHook,
after: promiseAfterHook,
settled: promiseResolveHooksExist() ? promiseResolveHook : undefined,
});
}
function disableHooks() {
async_hook_fields[kCheck] -= 1;
wantPromiseHook = false;
setCallbackTrampoline();
// Delay the call to `disablePromiseHook()` because we might currently be
// between the `before` and `after` calls of a Promise.
enqueueMicrotask(disablePromiseHookIfNecessary);
}
function disablePromiseHookIfNecessary() {
if (!wantPromiseHook && stopPromiseHook) {
stopPromiseHook();
}
}
// Internal Embedder API //
// Increment the internal id counter and return the value. Important that the
// counter increment first. Since it's done the same way in
// Environment::new_async_uid()
function newAsyncId() {
return ++async_id_fields[kAsyncIdCounter];
}
function getOrSetAsyncId(object) {
if (ObjectPrototypeHasOwnProperty(object, async_id_symbol)) {
return object[async_id_symbol];
}
return object[async_id_symbol] = newAsyncId();
}
// Return the triggerAsyncId meant for the constructor calling it. It's up to
// the user to safeguard this call and make sure it's zero'd out when the
// constructor is complete.
function getDefaultTriggerAsyncId() {
const defaultTriggerAsyncId = async_id_fields[kDefaultTriggerAsyncId];
// If defaultTriggerAsyncId isn't set, use the executionAsyncId
if (defaultTriggerAsyncId < 0)
return async_id_fields[kExecutionAsyncId];
return defaultTriggerAsyncId;
}
function clearDefaultTriggerAsyncId() {
async_id_fields[kDefaultTriggerAsyncId] = -1;
}
/**
* Sets a default top level trigger ID to be used
* @template {Array<unknown>} T
* @template {unknown} R
* @param {number} triggerAsyncId
* @param { (...T: args) => R } block
* @param {T} args
* @returns {R}
*/
function defaultTriggerAsyncIdScope(triggerAsyncId, block, ...args) {
if (triggerAsyncId === undefined)
return block.apply(null, args);
// CHECK(NumberIsSafeInteger(triggerAsyncId))
// CHECK(triggerAsyncId > 0)
const oldDefaultTriggerAsyncId = async_id_fields[kDefaultTriggerAsyncId];
async_id_fields[kDefaultTriggerAsyncId] = triggerAsyncId;
try {
return block.apply(null, args);
} finally {
async_id_fields[kDefaultTriggerAsyncId] = oldDefaultTriggerAsyncId;
}
}
function hasHooks(key) {
return async_hook_fields[key] > 0;
}
function enabledHooksExist() {
return hasHooks(kCheck);
}
function initHooksExist() {
return hasHooks(kInit);
}
function afterHooksExist() {
return hasHooks(kAfter);
}
function destroyHooksExist() {
return hasHooks(kDestroy);
}
function promiseResolveHooksExist() {
return hasHooks(kPromiseResolve);
}
function emitInitScript(asyncId, type, triggerAsyncId, resource) {
// Short circuit all checks for the common case. Which is that no hooks have
// been set. Do this to remove performance impact for embedders (and core).
if (!hasHooks(kInit))
return;
if (triggerAsyncId === null) {
triggerAsyncId = getDefaultTriggerAsyncId();
}
emitInitNative(asyncId, type, triggerAsyncId, resource);
}
function emitBeforeScript(asyncId, triggerAsyncId, resource) {
pushAsyncContext(asyncId, triggerAsyncId, resource);
if (hasHooks(kBefore))
emitBeforeNative(asyncId);
}
function emitAfterScript(asyncId) {
if (hasHooks(kAfter))
emitAfterNative(asyncId);
popAsyncContext(asyncId);
}
function emitDestroyScript(asyncId) {
// Return early if there are no destroy callbacks, or invalid asyncId.
if (!hasHooks(kDestroy) || asyncId <= 0)
return;
async_wrap.queueDestroyAsyncId(asyncId);
}
function hasAsyncIdStack() {
return hasHooks(kStackLength);
}
// This is the equivalent of the native push_async_ids() call.
function pushAsyncContext(asyncId, triggerAsyncId, resource) {
const offset = async_hook_fields[kStackLength];
execution_async_resources[offset] = resource;
if (offset * 2 >= async_wrap.async_ids_stack.length)
return pushAsyncContext_(asyncId, triggerAsyncId);
async_wrap.async_ids_stack[offset * 2] = async_id_fields[kExecutionAsyncId];
async_wrap.async_ids_stack[offset * 2 + 1] = async_id_fields[kTriggerAsyncId];
async_hook_fields[kStackLength]++;
async_id_fields[kExecutionAsyncId] = asyncId;
async_id_fields[kTriggerAsyncId] = triggerAsyncId;
}
// This is the equivalent of the native pop_async_ids() call.
function popAsyncContext(asyncId) {
const stackLength = async_hook_fields[kStackLength];
if (stackLength === 0) return false;
if (enabledHooksExist() && async_id_fields[kExecutionAsyncId] !== asyncId) {
// Do the same thing as the native code (i.e. crash hard).
return popAsyncContext_(asyncId);
}
const offset = stackLength - 1;
async_id_fields[kExecutionAsyncId] = async_wrap.async_ids_stack[2 * offset];
async_id_fields[kTriggerAsyncId] = async_wrap.async_ids_stack[2 * offset + 1];
execution_async_resources.pop();
async_hook_fields[kStackLength] = offset;
return offset > 0;
}
function executionAsyncId() {
return async_id_fields[kExecutionAsyncId];
}
function triggerAsyncId() {
return async_id_fields[kTriggerAsyncId];
}
const kNoPromiseHook = Symbol('kNoPromiseHook');
module.exports = {
executionAsyncId,
triggerAsyncId,
// Private API
getHookArrays,
symbols: {
async_id_symbol, trigger_async_id_symbol,
init_symbol, before_symbol, after_symbol, destroy_symbol,
promise_resolve_symbol, owner_symbol,
},
constants: {
kInit, kBefore, kAfter, kDestroy, kTotals, kPromiseResolve,
},
enableHooks,
disableHooks,
updatePromiseHookMode,
clearDefaultTriggerAsyncId,
clearAsyncIdStack,
hasAsyncIdStack,
executionAsyncResource,
// Internal Embedder API
newAsyncId,
getOrSetAsyncId,
getDefaultTriggerAsyncId,
defaultTriggerAsyncIdScope,
enabledHooksExist,
initHooksExist,
afterHooksExist,
destroyHooksExist,
emitInit: emitInitScript,
emitBefore: emitBeforeScript,
emitAfter: emitAfterScript,
emitDestroy: emitDestroyScript,
pushAsyncContext,
popAsyncContext,
registerDestroyHook,
useDomainTrampoline,
nativeHooks: {
init: emitInitNative,
before: emitBeforeNative,
after: emitAfterNative,
destroy: emitDestroyNative,
promise_resolve: emitPromiseResolveNative,
},
asyncWrap: {
Providers: async_wrap.Providers,
},
kNoPromiseHook,
};

View File

@ -0,0 +1,82 @@
'use strict';
const {
ObjectIs,
ReflectApply,
} = primordials;
const {
validateObject,
} = require('internal/validators');
const AsyncContextFrame = require('internal/async_context_frame');
const { AsyncResource } = require('async_hooks');
class AsyncLocalStorage {
#defaultValue = undefined;
#name = undefined;
/**
* @typedef {object} AsyncLocalStorageOptions
* @property {any} [defaultValue] - The default value to use when no value is set.
* @property {string} [name] - The name of the storage.
*/
/**
* @param {AsyncLocalStorageOptions} [options]
*/
constructor(options = {}) {
validateObject(options, 'options');
this.#defaultValue = options.defaultValue;
if (options.name !== undefined) {
this.#name = `${options.name}`;
}
}
/** @type {string} */
get name() { return this.#name || ''; }
static bind(fn) {
return AsyncResource.bind(fn);
}
static snapshot() {
return AsyncLocalStorage.bind((cb, ...args) => cb(...args));
}
disable() {
AsyncContextFrame.disable(this);
}
enterWith(data) {
const frame = new AsyncContextFrame(this, data);
AsyncContextFrame.set(frame);
}
run(data, fn, ...args) {
const prior = this.getStore();
if (ObjectIs(prior, data)) {
return ReflectApply(fn, null, args);
}
this.enterWith(data);
try {
return ReflectApply(fn, null, args);
} finally {
this.enterWith(prior);
}
}
exit(fn, ...args) {
return this.run(undefined, fn, ...args);
}
getStore() {
const frame = AsyncContextFrame.current();
if (!frame?.has(this)) {
return this.#defaultValue;
}
return frame?.get(this);
}
}
module.exports = AsyncLocalStorage;

View File

@ -0,0 +1,147 @@
'use strict';
const {
ArrayPrototypeIndexOf,
ArrayPrototypePush,
ArrayPrototypeSplice,
ObjectIs,
ReflectApply,
Symbol,
} = primordials;
const {
validateObject,
} = require('internal/validators');
const {
AsyncResource,
createHook,
executionAsyncResource,
} = require('async_hooks');
const storageList = [];
const storageHook = createHook({
init(asyncId, type, triggerAsyncId, resource) {
const currentResource = executionAsyncResource();
// Value of currentResource is always a non null object
for (let i = 0; i < storageList.length; ++i) {
storageList[i]._propagate(resource, currentResource, type);
}
},
});
class AsyncLocalStorage {
#defaultValue = undefined;
#name = undefined;
/**
* @typedef {object} AsyncLocalStorageOptions
* @property {any} [defaultValue] - The default value to use when no value is set.
* @property {string} [name] - The name of the storage.
*/
/**
* @param {AsyncLocalStorageOptions} [options]
*/
constructor(options = {}) {
this.kResourceStore = Symbol('kResourceStore');
this.enabled = false;
validateObject(options, 'options');
this.#defaultValue = options.defaultValue;
if (options.name !== undefined) {
this.#name = `${options.name}`;
}
this._enable();
}
/** @type {string} */
get name() { return this.#name || ''; }
static bind(fn) {
return AsyncResource.bind(fn);
}
static snapshot() {
return AsyncLocalStorage.bind((cb, ...args) => cb(...args));
}
disable() {
if (this.enabled) {
this.enabled = false;
// If this.enabled, the instance must be in storageList
const index = ArrayPrototypeIndexOf(storageList, this);
ArrayPrototypeSplice(storageList, index, 1);
if (storageList.length === 0) {
storageHook.disable();
}
}
}
_enable() {
if (!this.enabled) {
this.enabled = true;
ArrayPrototypePush(storageList, this);
storageHook.enable();
}
}
// Propagate the context from a parent resource to a child one
_propagate(resource, triggerResource, type) {
const store = triggerResource[this.kResourceStore];
if (this.enabled) {
resource[this.kResourceStore] = store;
}
}
enterWith(store) {
this._enable();
const resource = executionAsyncResource();
resource[this.kResourceStore] = store;
}
run(store, callback, ...args) {
// Avoid creation of an AsyncResource if store is already active
if (ObjectIs(store, this.getStore())) {
return ReflectApply(callback, null, args);
}
this._enable();
const resource = executionAsyncResource();
const oldStore = resource[this.kResourceStore];
resource[this.kResourceStore] = store;
try {
return ReflectApply(callback, null, args);
} finally {
resource[this.kResourceStore] = oldStore;
}
}
exit(callback, ...args) {
if (!this.enabled) {
return ReflectApply(callback, null, args);
}
this.disable();
try {
return ReflectApply(callback, null, args);
} finally {
this._enable();
}
}
getStore() {
if (this.enabled) {
const resource = executionAsyncResource();
if (!(this.kResourceStore in resource)) {
return this.#defaultValue;
}
return resource[this.kResourceStore];
}
return this.#defaultValue;
}
}
module.exports = AsyncLocalStorage;

522
lib/internal/blob.js Normal file
View File

@ -0,0 +1,522 @@
'use strict';
const {
ArrayFrom,
MathMax,
MathMin,
ObjectDefineProperties,
ObjectDefineProperty,
ObjectSetPrototypeOf,
PromisePrototypeThen,
PromiseReject,
PromiseWithResolvers,
RegExpPrototypeExec,
RegExpPrototypeSymbolReplace,
StringPrototypeSplit,
StringPrototypeToLowerCase,
Symbol,
SymbolIterator,
SymbolToStringTag,
Uint8Array,
} = primordials;
const {
createBlob: _createBlob,
createBlobFromFilePath: _createBlobFromFilePath,
concat,
getDataObject,
} = internalBinding('blob');
const {
kMaxLength,
} = internalBinding('buffer');
const {
TextDecoder,
TextEncoder,
} = require('internal/encoding');
const { URL } = require('internal/url');
const {
markTransferMode,
kClone,
kDeserialize,
} = require('internal/worker/js_transferable');
const {
isAnyArrayBuffer,
isArrayBufferView,
} = require('internal/util/types');
const {
customInspectSymbol: kInspect,
kEmptyObject,
kEnumerableProperty,
lazyDOMException,
} = require('internal/util');
const { inspect } = require('internal/util/inspect');
const { convertToInt } = require('internal/webidl');
const {
codes: {
ERR_BUFFER_TOO_LARGE,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
ERR_INVALID_STATE,
ERR_INVALID_THIS,
},
} = require('internal/errors');
const {
validateDictionary,
} = require('internal/validators');
const {
setImmediate,
} = require('timers');
const { queueMicrotask } = require('internal/process/task_queues');
const kHandle = Symbol('kHandle');
const kType = Symbol('kType');
const kLength = Symbol('kLength');
const kNotCloneable = Symbol('kNotCloneable');
const disallowedTypeCharacters = /[^\u{0020}-\u{007E}]/u;
let ReadableStream;
const enc = new TextEncoder();
let dec;
// Yes, lazy loading is annoying but because of circular
// references between the url, internal/blob, and buffer
// modules, lazy loading here makes sure that things work.
function lazyReadableStream(options) {
// eslint-disable-next-line no-global-assign
ReadableStream ??=
require('internal/webstreams/readablestream').ReadableStream;
return new ReadableStream(options);
}
const { EOL } = require('internal/constants');
function isBlob(object) {
return object?.[kHandle] !== undefined;
}
function getSource(source, endings) {
if (isBlob(source))
return [source.size, source[kHandle]];
if (isAnyArrayBuffer(source)) {
source = new Uint8Array(source);
} else if (!isArrayBufferView(source)) {
source = `${source}`;
if (endings === 'native')
source = RegExpPrototypeSymbolReplace(/\n|\r\n/g, source, EOL);
source = enc.encode(source);
}
// We copy into a new Uint8Array because the underlying
// BackingStores are going to be detached and owned by
// the Blob.
const { buffer, byteOffset, byteLength } = source;
const slice = buffer.slice(byteOffset, byteOffset + byteLength);
return [byteLength, new Uint8Array(slice)];
}
class Blob {
/**
* @typedef {string|ArrayBuffer|ArrayBufferView|Blob} SourcePart
*/
/**
* @param {SourcePart[]} [sources]
* @param {{
* endings? : string,
* type? : string,
* }} [options]
* @constructs {Blob}
*/
constructor(sources = [], options) {
markTransferMode(this, true, false);
if (sources === null ||
typeof sources[SymbolIterator] !== 'function' ||
typeof sources === 'string') {
throw new ERR_INVALID_ARG_TYPE('sources', 'a sequence', sources);
}
validateDictionary(options, 'options');
let {
type = '',
endings = 'transparent',
} = options ?? kEmptyObject;
endings = `${endings}`;
if (endings !== 'transparent' && endings !== 'native')
throw new ERR_INVALID_ARG_VALUE('options.endings', endings);
let length = 0;
const sources_ = ArrayFrom(sources, (source) => {
const { 0: len, 1: src } = getSource(source, endings);
length += len;
return src;
});
if (length > kMaxLength)
throw new ERR_BUFFER_TOO_LARGE(kMaxLength);
this[kHandle] = _createBlob(sources_, length);
this[kLength] = length;
type = `${type}`;
this[kType] = RegExpPrototypeExec(disallowedTypeCharacters, type) !== null ?
'' : StringPrototypeToLowerCase(type);
}
[kInspect](depth, options) {
if (depth < 0)
return this;
const opts = {
...options,
depth: options.depth == null ? null : options.depth - 1,
};
return `Blob ${inspect({
size: this.size,
type: this.type,
}, opts)}`;
}
[kClone]() {
if (this[kNotCloneable]) {
// We do not currently allow file-backed Blobs to be cloned or passed across
// worker threads.
throw new ERR_INVALID_STATE.TypeError('File-backed Blobs are not cloneable');
}
const handle = this[kHandle];
const type = this[kType];
const length = this[kLength];
return {
data: { handle, type, length },
deserializeInfo: 'internal/blob:Blob',
};
}
[kDeserialize]({ handle, type, length }) {
this[kHandle] = handle;
this[kType] = type;
this[kLength] = length;
}
/**
* @readonly
* @type {string}
*/
get type() {
if (!isBlob(this))
throw new ERR_INVALID_THIS('Blob');
return this[kType];
}
/**
* @readonly
* @type {number}
*/
get size() {
if (!isBlob(this))
throw new ERR_INVALID_THIS('Blob');
return this[kLength];
}
/**
* @param {number} [start]
* @param {number} [end]
* @param {string} [contentType]
* @returns {Blob}
*/
slice(start = 0, end = this[kLength], contentType = '') {
if (!isBlob(this))
throw new ERR_INVALID_THIS('Blob');
// Coerce values to int
const opts = { __proto__: null, signed: true };
start = convertToInt('start', start, 64, opts);
end = convertToInt('end', end, 64, opts);
if (start < 0) {
start = MathMax(this[kLength] + start, 0);
} else {
start = MathMin(start, this[kLength]);
}
if (end < 0) {
end = MathMax(this[kLength] + end, 0);
} else {
end = MathMin(end, this[kLength]);
}
contentType = `${contentType}`;
if (RegExpPrototypeExec(disallowedTypeCharacters, contentType) !== null) {
contentType = '';
} else {
contentType = StringPrototypeToLowerCase(contentType);
}
const span = MathMax(end - start, 0);
return createBlob(
this[kHandle].slice(start, start + span),
span,
contentType);
}
/**
* @returns {Promise<ArrayBuffer>}
*/
arrayBuffer() {
if (!isBlob(this))
return PromiseReject(new ERR_INVALID_THIS('Blob'));
return arrayBuffer(this);
}
/**
* @returns {Promise<string>}
*/
text() {
if (!isBlob(this))
return PromiseReject(new ERR_INVALID_THIS('Blob'));
dec ??= new TextDecoder();
return PromisePrototypeThen(
arrayBuffer(this),
(buffer) => dec.decode(buffer));
}
/**
* @returns {Promise<Uint8Array>}
*/
bytes() {
if (!isBlob(this))
return PromiseReject(new ERR_INVALID_THIS('Blob'));
return PromisePrototypeThen(
arrayBuffer(this),
(buffer) => new Uint8Array(buffer));
}
/**
* @returns {ReadableStream}
*/
stream() {
if (!isBlob(this))
throw new ERR_INVALID_THIS('Blob');
return createBlobReaderStream(this[kHandle].getReader());
}
}
function TransferableBlob(handle, length, type = '') {
ObjectSetPrototypeOf(this, Blob.prototype);
markTransferMode(this, true, false);
this[kHandle] = handle;
this[kType] = type;
this[kLength] = length;
}
ObjectSetPrototypeOf(TransferableBlob.prototype, Blob.prototype);
ObjectSetPrototypeOf(TransferableBlob, Blob);
function createBlob(handle, length, type = '') {
const transferredBlob = new TransferableBlob(handle, length, type);
// Fix issues like: https://github.com/nodejs/node/pull/49730#discussion_r1331720053
transferredBlob.constructor = Blob;
return transferredBlob;
}
ObjectDefineProperty(Blob.prototype, SymbolToStringTag, {
__proto__: null,
configurable: true,
value: 'Blob',
});
ObjectDefineProperties(Blob.prototype, {
size: kEnumerableProperty,
type: kEnumerableProperty,
slice: kEnumerableProperty,
stream: kEnumerableProperty,
text: kEnumerableProperty,
arrayBuffer: kEnumerableProperty,
bytes: kEnumerableProperty,
});
function resolveObjectURL(url) {
url = `${url}`;
try {
const parsed = new URL(url);
const split = StringPrototypeSplit(parsed.pathname, ':', 3);
if (split.length !== 2)
return;
const {
0: base,
1: id,
} = split;
if (base !== 'nodedata')
return;
const ret = getDataObject(id);
if (ret === undefined)
return;
const {
0: handle,
1: length,
2: type,
} = ret;
if (handle !== undefined)
return createBlob(handle, length, type);
} catch {
// If there's an error, it's ignored and nothing is returned
}
}
// TODO(@jasnell): Now that the File class exists, we might consider having
// this return a `File` instead of a `Blob`.
function createBlobFromFilePath(path, options) {
const maybeBlob = _createBlobFromFilePath(path);
if (maybeBlob === undefined) {
return lazyDOMException('The blob could not be read', 'NotReadableError');
}
const { 0: blob, 1: length } = maybeBlob;
const res = createBlob(blob, length, options?.type);
res[kNotCloneable] = true;
return res;
}
function arrayBuffer(blob) {
const { promise, resolve, reject } = PromiseWithResolvers();
const reader = blob[kHandle].getReader();
const buffers = [];
const readNext = () => {
reader.pull((status, buffer) => {
if (status === 0) {
// EOS, concat & resolve
// buffer should be undefined here
resolve(concat(buffers));
return;
} else if (status < 0) {
// The read could fail for many different reasons when reading
// from a non-memory resident blob part (e.g. file-backed blob).
// The error details the system error code.
const error = lazyDOMException('The blob could not be read', 'NotReadableError');
reject(error);
return;
}
if (buffer !== undefined)
buffers.push(buffer);
queueMicrotask(() => readNext());
});
};
readNext();
return promise;
}
function createBlobReaderStream(reader) {
return new lazyReadableStream({
type: 'bytes',
start(c) {
// There really should only be one read at a time so using an
// array here is purely defensive.
this.pendingPulls = [];
},
pull(c) {
const { promise, resolve, reject } = PromiseWithResolvers();
this.pendingPulls.push({ resolve, reject });
const readNext = () => {
reader.pull((status, buffer) => {
// If pendingPulls is empty here, the stream had to have
// been canceled, and we don't really care about the result.
// We can simply exit.
if (this.pendingPulls.length === 0) {
return;
}
if (status === 0) {
// EOS
c.close();
// This is to signal the end for byob readers
// see https://streams.spec.whatwg.org/#example-rbs-pull
c.byobRequest?.respond(0);
const pending = this.pendingPulls.shift();
pending.resolve();
return;
} else if (status < 0) {
// The read could fail for many different reasons when reading
// from a non-memory resident blob part (e.g. file-backed blob).
// The error details the system error code.
const error = lazyDOMException('The blob could not be read', 'NotReadableError');
const pending = this.pendingPulls.shift();
c.error(error);
pending.reject(error);
return;
}
// ReadableByteStreamController.enqueue errors if we submit a 0-length
// buffer. We need to check for that here.
if (buffer !== undefined && buffer.byteLength !== 0) {
c.enqueue(new Uint8Array(buffer));
}
// We keep reading until we either reach EOS, some error, or we
// hit the flow rate of the stream (c.desiredSize).
// We use set immediate here because we have to allow the event
// loop to turn in order to proecss any pending i/o. Using
// queueMicrotask won't allow the event loop to turn.
setImmediate(() => {
if (c.desiredSize < 0) {
// A manual backpressure check.
if (this.pendingPulls.length !== 0) {
// A case of waiting pull finished (= not yet canceled)
const pending = this.pendingPulls.shift();
pending.resolve();
}
return;
}
readNext();
});
});
};
readNext();
return promise;
},
cancel(reason) {
// Reject any currently pending pulls here.
for (const pending of this.pendingPulls) {
pending.reject(reason);
}
this.pendingPulls = [];
},
// We set the highWaterMark to 0 because we do not want the stream to
// start reading immediately on creation. We want it to wait until read
// is called.
}, { highWaterMark: 0 });
}
module.exports = {
Blob,
createBlob,
createBlobFromFilePath,
isBlob,
kHandle,
resolveObjectURL,
TransferableBlob,
createBlobReaderStream,
};

175
lib/internal/blocklist.js Normal file
View File

@ -0,0 +1,175 @@
'use strict';
const {
Boolean,
ObjectSetPrototypeOf,
Symbol,
} = primordials;
const {
BlockList: BlockListHandle,
} = internalBinding('block_list');
const {
customInspectSymbol: kInspect,
} = require('internal/util');
const {
SocketAddress,
kHandle: kSocketAddressHandle,
} = require('internal/socketaddress');
const {
markTransferMode,
kClone,
kDeserialize,
} = require('internal/worker/js_transferable');
const { inspect } = require('internal/util/inspect');
const kHandle = Symbol('kHandle');
const { owner_symbol } = internalBinding('symbols');
const {
ERR_INVALID_ARG_VALUE,
} = require('internal/errors').codes;
const { validateInt32, validateString } = require('internal/validators');
class BlockList {
constructor() {
markTransferMode(this, true, false);
this[kHandle] = new BlockListHandle();
this[kHandle][owner_symbol] = this;
}
/**
* Returns true if the value is a BlockList
* @param {any} value
* @returns {boolean}
*/
static isBlockList(value) {
return value?.[kHandle] !== undefined;
}
[kInspect](depth, options) {
if (depth < 0)
return this;
const opts = {
...options,
depth: options.depth == null ? null : options.depth - 1,
};
return `BlockList ${inspect({
rules: this.rules,
}, opts)}`;
}
addAddress(address, family = 'ipv4') {
if (!SocketAddress.isSocketAddress(address)) {
validateString(address, 'address');
validateString(family, 'family');
address = new SocketAddress({
address,
family,
});
}
this[kHandle].addAddress(address[kSocketAddressHandle]);
}
addRange(start, end, family = 'ipv4') {
if (!SocketAddress.isSocketAddress(start)) {
validateString(start, 'start');
validateString(family, 'family');
start = new SocketAddress({
address: start,
family,
});
}
if (!SocketAddress.isSocketAddress(end)) {
validateString(end, 'end');
validateString(family, 'family');
end = new SocketAddress({
address: end,
family,
});
}
const ret = this[kHandle].addRange(
start[kSocketAddressHandle],
end[kSocketAddressHandle]);
if (ret === false)
throw new ERR_INVALID_ARG_VALUE('start', start, 'must come before end');
}
addSubnet(network, prefix, family = 'ipv4') {
if (!SocketAddress.isSocketAddress(network)) {
validateString(network, 'network');
validateString(family, 'family');
network = new SocketAddress({
address: network,
family,
});
}
switch (network.family) {
case 'ipv4':
validateInt32(prefix, 'prefix', 0, 32);
break;
case 'ipv6':
validateInt32(prefix, 'prefix', 0, 128);
break;
}
this[kHandle].addSubnet(network[kSocketAddressHandle], prefix);
}
check(address, family = 'ipv4') {
if (!SocketAddress.isSocketAddress(address)) {
validateString(address, 'address');
validateString(family, 'family');
try {
address = new SocketAddress({
address,
family,
});
} catch {
// Ignore the error. If it's not a valid address, return false.
return false;
}
}
return Boolean(this[kHandle].check(address[kSocketAddressHandle]));
}
get rules() {
return this[kHandle].getRules();
}
[kClone]() {
const handle = this[kHandle];
return {
data: { handle },
deserializeInfo: 'internal/blocklist:InternalBlockList',
};
}
[kDeserialize]({ handle }) {
this[kHandle] = handle;
this[kHandle][owner_symbol] = this;
}
}
class InternalBlockList {
constructor(handle) {
markTransferMode(this, true, false);
this[kHandle] = handle;
if (handle !== undefined)
handle[owner_symbol] = this;
}
}
InternalBlockList.prototype.constructor = BlockList.prototype.constructor;
ObjectSetPrototypeOf(InternalBlockList.prototype, BlockList.prototype);
module.exports = {
BlockList,
InternalBlockList,
};

View File

@ -0,0 +1,475 @@
// Hello, and welcome to hacking node.js!
//
// This file is invoked by `Realm::BootstrapRealm()` in `src/node_realm.cc`,
// and is responsible for setting up Node.js core before main scripts
// under `lib/internal/main/` are executed.
//
// By default, Node.js binaries come with an embedded V8 startup snapshot
// that is generated at build-time with a `node_mksnapshot` executable.
// The snapshot generation code can be found in `SnapshotBuilder::Generate()`
// from `src/node_snapshotable.cc`.
// This snapshot captures the V8 heap initialized by scripts under
// `lib/internal/bootstrap/`, including this file. When initializing the main
// thread, Node.js deserializes the heap from the snapshot, instead of actually
// running this script and others in `lib/internal/bootstrap/`. To disable this
// behavior, pass `--no-node-snapshot` when starting the process so that
// Node.js actually runs this script to initialize the heap.
//
// This script is expected not to perform any asynchronous operations itself
// when being executed - those should be done in either
// `lib/internal/process/pre_execution.js` or in main scripts. It should not
// query any run-time states (e.g. command line arguments, environment
// variables) when being executed - functions in this script that are invoked
// at a later time can, however, query those states lazily.
// The majority of the code here focuses on setting up the global object and
// the process object in a synchronous, environment-independent manner.
//
// Scripts run before this file:
// - `lib/internal/per_context/primordials.js`: this saves copies of JavaScript
// builtins that won't be affected by user land monkey-patching for internal
// modules to use.
// - `lib/internal/per_context/domexception.js`: implementation of the
// `DOMException` class.
// - `lib/internal/per_context/messageport.js`: JS-side components of the
// `MessagePort` implementation.
// - `lib/internal/bootstrap/realm.js`: this sets up internal binding and
// module loaders, including `process.binding()`, `process._linkedBinding()`,
// `internalBinding()` and `BuiltinModule`, and per-realm internal states
// and bindings, including `prepare_stack_trace_callback`.
//
// The initialization done in this script is included in both the main thread
// and the worker threads. After this, further initialization is done based
// on the configuration of the Node.js instance by executing the scripts in
// `lib/internal/bootstrap/switches/`.
//
// Then, depending on how the Node.js instance is launched, one of the main
// scripts in `lib/internal/main` will be selected by C++ to start the actual
// execution. They may run additional setups exported by
// `lib/internal/process/pre_execution.js` depending on the run-time states.
'use strict';
// This file is compiled as if it's wrapped in a function with arguments
// passed by `BuiltinLoader::CompileAndCall()`.
/* global process, require, internalBinding, primordials */
const {
FunctionPrototypeCall,
JSONParse,
Number,
NumberIsNaN,
ObjectDefineProperty,
ObjectFreeze,
ObjectGetPrototypeOf,
ObjectSetPrototypeOf,
SymbolToStringTag,
globalThis,
} = primordials;
const config = internalBinding('config');
const internalTimers = require('internal/timers');
const { defineOperation } = require('internal/util');
const {
validateInteger,
} = require('internal/validators');
const {
constants: {
kExitCode,
kExiting,
kHasExitCode,
},
privateSymbols: {
exit_info_private_symbol,
},
} = internalBinding('util');
setupProcessObject();
setupGlobalProxy();
setupBuffer();
process.domain = null;
// process._exiting and process.exitCode
{
const fields = process[exit_info_private_symbol];
ObjectDefineProperty(process, '_exiting', {
__proto__: null,
get() {
return fields[kExiting] === 1;
},
set(value) {
fields[kExiting] = value ? 1 : 0;
},
enumerable: true,
configurable: true,
});
ObjectDefineProperty(process, 'exitCode', {
__proto__: null,
get() {
return fields[kHasExitCode] ? fields[kExitCode] : undefined;
},
set(code) {
if (code !== null && code !== undefined) {
let value = code;
if (typeof code === 'string' && code !== '' &&
NumberIsNaN((value = Number(code)))) {
value = code;
}
validateInteger(value, 'code');
fields[kExitCode] = value;
fields[kHasExitCode] = 1;
} else {
fields[kHasExitCode] = 0;
}
},
enumerable: true,
configurable: false,
});
}
process._exiting = false;
// process.config is serialized config.gypi
const binding = internalBinding('builtins');
const processConfig = JSONParse(binding.config, (_key, value) => {
// The `reviver` argument of the JSONParse method will visit all the values of
// the parsed config, including the "root" object, so there is no need to
// explicitly freeze the config outside of this method
return ObjectFreeze(value);
});
ObjectDefineProperty(process, 'config', {
__proto__: null,
enumerable: true,
configurable: true,
value: processConfig,
});
require('internal/worker/js_transferable').setup();
// Bootstrappers for all threads, including worker threads and main thread
const perThreadSetup = require('internal/process/per_thread');
const rawMethods = internalBinding('process_methods');
// Set up methods on the process object for all threads
{
process.dlopen = rawMethods.dlopen;
process.uptime = rawMethods.uptime;
// TODO(joyeecheung): either remove them or make them public
process._getActiveRequests = rawMethods._getActiveRequests;
process._getActiveHandles = rawMethods._getActiveHandles;
process.getActiveResourcesInfo = rawMethods.getActiveResourcesInfo;
// TODO(joyeecheung): remove these
process.reallyExit = rawMethods.reallyExit;
process._kill = rawMethods._kill;
const wrapped = perThreadSetup.wrapProcessMethods(rawMethods);
process.loadEnvFile = wrapped.loadEnvFile;
process._rawDebug = wrapped._rawDebug;
process.cpuUsage = wrapped.cpuUsage;
process.threadCpuUsage = wrapped.threadCpuUsage;
process.resourceUsage = wrapped.resourceUsage;
process.memoryUsage = wrapped.memoryUsage;
process.constrainedMemory = rawMethods.constrainedMemory;
process.availableMemory = rawMethods.availableMemory;
process.kill = wrapped.kill;
process.exit = wrapped.exit;
process.execve = wrapped.execve;
process.ref = perThreadSetup.ref;
process.unref = perThreadSetup.unref;
let finalizationMod;
ObjectDefineProperty(process, 'finalization', {
__proto__: null,
get() {
if (finalizationMod !== undefined) {
return finalizationMod;
}
const { createFinalization } = require('internal/process/finalization');
finalizationMod = createFinalization();
return finalizationMod;
},
set(value) {
finalizationMod = value;
},
enumerable: true,
configurable: true,
});
process.hrtime = perThreadSetup.hrtime;
process.hrtime.bigint = perThreadSetup.hrtimeBigInt;
process.openStdin = function() {
process.stdin.resume();
return process.stdin;
};
}
const credentials = internalBinding('credentials');
if (credentials.implementsPosixCredentials) {
process.getuid = credentials.getuid;
process.geteuid = credentials.geteuid;
process.getgid = credentials.getgid;
process.getegid = credentials.getegid;
process.getgroups = credentials.getgroups;
}
// Setup the callbacks that node::AsyncWrap will call when there are hooks to
// process. They use the same functions as the JS embedder API. These callbacks
// are setup immediately to prevent async_wrap.setupHooks() from being hijacked
// and the cost of doing so is negligible.
const { nativeHooks } = require('internal/async_hooks');
internalBinding('async_wrap').setupHooks(nativeHooks);
const {
setupTaskQueue,
} = require('internal/process/task_queues');
const timers = require('timers');
// Non-standard extensions:
defineOperation(globalThis, 'clearImmediate', timers.clearImmediate);
defineOperation(globalThis, 'setImmediate', timers.setImmediate);
// Set the per-Environment callback that will be called
// when the TrackingTraceStateObserver updates trace state.
// Note that when NODE_USE_V8_PLATFORM is true, the observer is
// attached to the per-process TracingController.
const { setTraceCategoryStateUpdateHandler } = internalBinding('trace_events');
setTraceCategoryStateUpdateHandler(perThreadSetup.toggleTraceCategoryState);
// process.allowedNodeEnvironmentFlags
ObjectDefineProperty(process, 'allowedNodeEnvironmentFlags', {
__proto__: null,
get() {
const flags = perThreadSetup.buildAllowedFlags();
process.allowedNodeEnvironmentFlags = flags;
return process.allowedNodeEnvironmentFlags;
},
// If the user tries to set this to another value, override
// this completely to that value.
set(value) {
ObjectDefineProperty(this, 'allowedNodeEnvironmentFlags', {
__proto__: null,
value,
configurable: true,
enumerable: true,
writable: true,
});
},
enumerable: true,
configurable: true,
});
// TODO(joyeecheung): this property has not been well-maintained, should we
// deprecate it in favor of a better API?
const { isDebugBuild, hasOpenSSL, openSSLIsBoringSSL, hasInspector } = config;
const features = {
inspector: hasInspector,
debug: isDebugBuild,
uv: true,
ipv6: true, // TODO(bnoordhuis) ping libuv
tls_alpn: hasOpenSSL,
tls_sni: hasOpenSSL,
tls_ocsp: hasOpenSSL,
tls: hasOpenSSL,
openssl_is_boringssl: openSSLIsBoringSSL,
// This needs to be dynamic because --no-node-snapshot disables the
// code cache even if the binary is built with embedded code cache.
get cached_builtins() {
return binding.hasCachedBuiltins();
},
get require_module() {
return getOptionValue('--experimental-require-module');
},
};
ObjectDefineProperty(process, 'features', {
__proto__: null,
enumerable: true,
writable: false,
configurable: false,
value: features,
});
{
const {
onGlobalUncaughtException,
setUncaughtExceptionCaptureCallback,
hasUncaughtExceptionCaptureCallback,
} = require('internal/process/execution');
// For legacy reasons this is still called `_fatalException`, even
// though it is now a global uncaught exception handler.
// The C++ land node::errors::TriggerUncaughtException grabs it
// from the process object because it has been monkey-patchable.
// TODO(joyeecheung): investigate whether process._fatalException
// can be deprecated.
process._fatalException = onGlobalUncaughtException;
process.setUncaughtExceptionCaptureCallback =
setUncaughtExceptionCaptureCallback;
process.hasUncaughtExceptionCaptureCallback =
hasUncaughtExceptionCaptureCallback;
}
const { emitWarning, emitWarningSync } = require('internal/process/warning');
const { getOptionValue } = require('internal/options');
let kTypeStrippingMode = process.config.variables.node_use_amaro ? null : false;
// This must be a getter, as getOptionValue does not work
// before bootstrapping.
ObjectDefineProperty(process.features, 'typescript', {
__proto__: null,
get() {
if (kTypeStrippingMode === null) {
if (getOptionValue('--experimental-transform-types')) {
kTypeStrippingMode = 'transform';
} else if (getOptionValue('--experimental-strip-types')) {
kTypeStrippingMode = 'strip';
} else {
kTypeStrippingMode = false;
}
}
return kTypeStrippingMode;
},
configurable: true,
enumerable: true,
});
process.emitWarning = emitWarning;
internalBinding('process_methods').setEmitWarningSync(emitWarningSync);
// We initialize the tick callbacks and the timer callbacks last during
// bootstrap to make sure that any operation done before this are synchronous.
// If any ticks or timers are scheduled before this they are unlikely to work.
{
const { nextTick, runNextTicks } = setupTaskQueue();
process.nextTick = nextTick;
// Used to emulate a tick manually in the JS land.
// A better name for this function would be `runNextTicks` but
// it has been exposed to the process object so we keep this legacy name
// TODO(joyeecheung): either remove it or make it public
process._tickCallback = runNextTicks;
const { setupTimers } = internalBinding('timers');
const {
processImmediate,
processTimers,
} = internalTimers.getTimerCallbacks(runNextTicks);
// Sets two per-Environment callbacks that will be run from libuv:
// - processImmediate will be run in the callback of the per-Environment
// check handle.
// - processTimers will be run in the callback of the per-Environment timer.
setupTimers(processImmediate, processTimers);
// Note: only after this point are the timers effective
}
{
const {
getSourceMapsSupport,
setSourceMapsSupport,
maybeCacheGeneratedSourceMap,
} = require('internal/source_map/source_map_cache');
const {
setMaybeCacheGeneratedSourceMap,
} = internalBinding('errors');
ObjectDefineProperty(process, 'sourceMapsEnabled', {
__proto__: null,
enumerable: true,
configurable: true,
get() {
return getSourceMapsSupport().enabled;
},
});
process.setSourceMapsEnabled = function setSourceMapsEnabled(val) {
setSourceMapsSupport(val, {
__proto__: null,
// TODO(legendecas): In order to smoothly improve the source map support,
// skip source maps in node_modules and generated code with
// `process.setSourceMapsEnabled(true)` in a semver major version.
nodeModules: val,
generatedCode: val,
});
};
// The C++ land calls back to maybeCacheGeneratedSourceMap()
// when code is generated by user with eval() or new Function()
// to cache the source maps from the evaluated code, if any.
setMaybeCacheGeneratedSourceMap(maybeCacheGeneratedSourceMap);
}
{
const { getBuiltinModule } = require('internal/modules/helpers');
process.getBuiltinModule = getBuiltinModule;
}
function setupProcessObject() {
const EventEmitter = require('events');
const origProcProto = ObjectGetPrototypeOf(process);
ObjectSetPrototypeOf(origProcProto, EventEmitter.prototype);
FunctionPrototypeCall(EventEmitter, process);
ObjectDefineProperty(process, SymbolToStringTag, {
__proto__: null,
enumerable: false,
writable: true,
configurable: false,
value: 'process',
});
// Create global.process as getters so that we have a
// deprecation path for these in ES Modules.
// See https://github.com/nodejs/node/pull/26334.
let _process = process;
ObjectDefineProperty(globalThis, 'process', {
__proto__: null,
get() {
return _process;
},
set(value) {
_process = value;
},
enumerable: false,
configurable: true,
});
}
function setupGlobalProxy() {
ObjectDefineProperty(globalThis, SymbolToStringTag, {
__proto__: null,
value: 'global',
writable: false,
enumerable: false,
configurable: true,
});
globalThis.global = globalThis;
}
function setupBuffer() {
const {
Buffer,
} = require('buffer');
const bufferBinding = internalBinding('buffer');
// Only after this point can C++ use Buffer::New()
bufferBinding.setBufferPrototype(Buffer.prototype);
delete bufferBinding.setBufferPrototype;
// Create global.Buffer as getters so that we have a
// deprecation path for these in ES Modules.
// See https://github.com/nodejs/node/pull/26334.
let _Buffer = Buffer;
ObjectDefineProperty(globalThis, 'Buffer', {
__proto__: null,
get() {
return _Buffer;
},
set(value) {
_Buffer = value;
},
enumerable: false,
configurable: true,
});
}

View File

@ -0,0 +1,475 @@
// This file is executed in every realm that is created by Node.js, including
// the context of main thread, worker threads, and ShadowRealms.
// Only per-realm internal states and bindings should be bootstrapped in this
// file and no globals should be exposed to the user code.
//
// This file creates the internal module & binding loaders used by built-in
// modules. In contrast, user land modules are loaded using
// lib/internal/modules/cjs/loader.js (CommonJS Modules) or
// lib/internal/modules/esm/* (ES Modules).
//
// This file is compiled and run by node.cc before bootstrap/node.js
// was called, therefore the loaders are bootstrapped before we start to
// actually bootstrap Node.js. It creates the following objects:
//
// C++ binding loaders:
// - process.binding(): the legacy C++ binding loader, accessible from user land
// because it is an object attached to the global process object.
// These C++ bindings are created using NODE_BUILTIN_MODULE_CONTEXT_AWARE()
// and have their nm_flags set to NM_F_BUILTIN. We do not make any guarantees
// about the stability of these bindings, but still have to take care of
// compatibility issues caused by them from time to time.
// - process._linkedBinding(): intended to be used by embedders to add
// additional C++ bindings in their applications. These C++ bindings
// can be created using NODE_BINDING_CONTEXT_AWARE_CPP() with the flag
// NM_F_LINKED.
// - internalBinding(): the private internal C++ binding loader, inaccessible
// from user land unless through `require('internal/test/binding')`.
// These C++ bindings are created using NODE_BINDING_CONTEXT_AWARE_INTERNAL()
// and have their nm_flags set to NM_F_INTERNAL.
//
// Internal JavaScript module loader:
// - BuiltinModule: a minimal module system used to load the JavaScript core
// modules found in lib/**/*.js and deps/**/*.js. All core modules are
// compiled into the node binary via node_javascript.cc generated by js2c.cc,
// so they can be loaded faster without the cost of I/O. This class makes the
// lib/internal/*, deps/internal/* modules and internalBinding() available by
// default to core modules, and lets the core modules require itself via
// require('internal/bootstrap/realm') even when this file is not written in
// CommonJS style.
//
// Other objects:
// - process.moduleLoadList: an array recording the bindings and the modules
// loaded in the process and the order in which they are loaded.
'use strict';
// This file is compiled as if it's wrapped in a function with arguments
// passed by node::RunBootstrapping()
/* global process, getLinkedBinding, getInternalBinding, primordials */
const {
ArrayFrom,
ArrayPrototypeFilter,
ArrayPrototypeIncludes,
ArrayPrototypeMap,
ArrayPrototypePush,
ArrayPrototypePushApply,
ArrayPrototypeSlice,
Error,
ObjectDefineProperty,
ObjectKeys,
ObjectPrototypeHasOwnProperty,
ObjectSetPrototypeOf,
ReflectGet,
SafeMap,
SafeSet,
String,
StringPrototypeSlice,
StringPrototypeStartsWith,
TypeError,
} = primordials;
// Set up process.moduleLoadList.
const moduleLoadList = [];
ObjectDefineProperty(process, 'moduleLoadList', {
__proto__: null,
value: moduleLoadList,
configurable: true,
enumerable: true,
writable: false,
});
// processBindingAllowList contains the name of bindings that are allowed
// for access via process.binding(). This is used to provide a transition
// path for modules that are being moved over to internalBinding.
// Certain bindings may not actually correspond to an internalBinding any
// more, we just implement them as legacy wrappers instead. See the
// legacyWrapperList.
const processBindingAllowList = new SafeSet([
'buffer',
'cares_wrap',
'config',
'constants',
'contextify',
'fs',
'fs_event_wrap',
'icu',
'inspector',
'js_stream',
'os',
'pipe_wrap',
'process_wrap',
'spawn_sync',
'stream_wrap',
'tcp_wrap',
'tls_wrap',
'tty_wrap',
'udp_wrap',
'uv',
'zlib',
]);
const runtimeDeprecatedList = new SafeSet([
// The list of runtime-deprecated bindings is currently empty.
]);
const legacyWrapperList = new SafeSet([
'natives',
'util',
]);
// The code bellow assumes that the two lists must not contain any modules
// beginning with "internal/".
// Modules that can only be imported via the node: scheme.
const schemelessBlockList = new SafeSet([
'sea',
'sqlite',
'quic',
'test',
'test/reporters',
]);
// Modules that will only be enabled at run time.
const experimentalModuleList = new SafeSet(['sqlite', 'quic']);
// Set up process.binding() and process._linkedBinding().
{
const bindingObj = { __proto__: null };
process.binding = function binding(module) {
module = String(module);
const mod = bindingObj[module];
if (typeof mod === 'object') {
return mod;
}
// Deprecated specific process.binding() modules, but not all, allow
// selective fallback to internalBinding for the deprecated ones.
if (runtimeDeprecatedList.has(module)) {
process.emitWarning(
`Access to process.binding('${module}') is deprecated.`,
'DeprecationWarning',
'DEP0111');
return internalBinding(module);
}
if (legacyWrapperList.has(module)) {
return requireBuiltin('internal/legacy/processbinding')[module]();
}
if (processBindingAllowList.has(module)) {
return internalBinding(module);
}
// eslint-disable-next-line no-restricted-syntax
throw new Error(`No such module: ${module}`);
};
process._linkedBinding = function _linkedBinding(module) {
module = String(module);
let mod = bindingObj[module];
if (typeof mod !== 'object')
mod = bindingObj[module] = getLinkedBinding(module);
return mod;
};
}
/**
* Set up internalBinding() in the closure.
* @type {import('typings/globals').internalBinding}
*/
let internalBinding;
{
const bindingObj = { __proto__: null };
// eslint-disable-next-line no-global-assign
internalBinding = function internalBinding(module) {
let mod = bindingObj[module];
if (typeof mod !== 'object') {
mod = bindingObj[module] = getInternalBinding(module);
ArrayPrototypePush(moduleLoadList, `Internal Binding ${module}`);
}
return mod;
};
}
const selfId = 'internal/bootstrap/realm';
const {
builtinIds,
compileFunction,
setInternalLoaders,
} = internalBinding('builtins');
const { ModuleWrap } = internalBinding('module_wrap');
ObjectSetPrototypeOf(ModuleWrap.prototype, null);
const getOwn = (target, property, receiver) => {
return ObjectPrototypeHasOwnProperty(target, property) ?
ReflectGet(target, property, receiver) :
undefined;
};
const publicBuiltinIds = builtinIds
.filter((id) =>
!StringPrototypeStartsWith(id, 'internal/') &&
!experimentalModuleList.has(id),
);
// Do not expose the loaders to user land even with --expose-internals.
const internalBuiltinIds = builtinIds
.filter((id) => StringPrototypeStartsWith(id, 'internal/') && id !== selfId);
// When --expose-internals is on we'll add the internal builtin ids to these.
let canBeRequiredByUsersList = new SafeSet(publicBuiltinIds);
let canBeRequiredByUsersWithoutSchemeList =
new SafeSet(publicBuiltinIds.filter((id) => !schemelessBlockList.has(id)));
/**
* An internal abstraction for the built-in JavaScript modules of Node.js.
* Be careful not to expose this to user land unless --expose-internals is
* used, in which case there is no compatibility guarantee about this class.
*/
class BuiltinModule {
/**
* A map from the module IDs to the module instances.
* @type {Map<string, BuiltinModule>}
*/
static map = new SafeMap(
ArrayPrototypeMap(builtinIds, (id) => [id, new BuiltinModule(id)]),
);
constructor(id) {
this.filename = `${id}.js`;
this.id = id;
// The CJS exports object of the module.
this.exports = {};
// States used to work around circular dependencies.
this.loaded = false;
this.loading = false;
// The following properties are used by the ESM implementation and only
// initialized when the built-in module is loaded by users.
/**
* The C++ ModuleWrap binding used to interface with the ESM implementation.
* @type {ModuleWrap|undefined}
*/
this.module = undefined;
/**
* Exported names for the ESM imports.
* @type {string[]|undefined}
*/
this.exportKeys = undefined;
}
static allowRequireByUsers(id) {
if (id === selfId) {
// No code because this is an assertion against bugs.
// eslint-disable-next-line no-restricted-syntax
throw new Error(`Should not allow ${id}`);
}
canBeRequiredByUsersList.add(id);
if (!schemelessBlockList.has(id)) {
canBeRequiredByUsersWithoutSchemeList.add(id);
}
}
static setRealmAllowRequireByUsers(ids) {
canBeRequiredByUsersList =
new SafeSet(ArrayPrototypeFilter(ids, (id) => ArrayPrototypeIncludes(publicBuiltinIds, id)));
canBeRequiredByUsersWithoutSchemeList =
new SafeSet(ArrayPrototypeFilter(ids, (id) => !schemelessBlockList.has(id)));
}
// To be called during pre-execution when --expose-internals is on.
// Enables the user-land module loader to access internal modules.
static exposeInternals() {
for (let i = 0; i < internalBuiltinIds.length; ++i) {
BuiltinModule.allowRequireByUsers(internalBuiltinIds[i]);
}
}
static exists(id) {
return BuiltinModule.map.has(id);
}
static canBeRequiredByUsers(id) {
return canBeRequiredByUsersList.has(id);
}
static canBeRequiredWithoutScheme(id) {
return canBeRequiredByUsersWithoutSchemeList.has(id);
}
static normalizeRequirableId(id) {
if (StringPrototypeStartsWith(id, 'node:')) {
const normalizedId = StringPrototypeSlice(id, 5);
if (BuiltinModule.canBeRequiredByUsers(normalizedId)) {
return normalizedId;
}
} else if (BuiltinModule.canBeRequiredWithoutScheme(id)) {
return id;
}
return undefined;
}
static isBuiltin(id) {
return BuiltinModule.canBeRequiredWithoutScheme(id) || (
typeof id === 'string' &&
StringPrototypeStartsWith(id, 'node:') &&
BuiltinModule.canBeRequiredByUsers(StringPrototypeSlice(id, 5))
);
}
static getSchemeOnlyModuleNames() {
return ArrayFrom(schemelessBlockList);
}
static getAllBuiltinModuleIds() {
const allBuiltins = ArrayFrom(canBeRequiredByUsersWithoutSchemeList);
ArrayPrototypePushApply(allBuiltins, ArrayFrom(schemelessBlockList, (x) => `node:${x}`));
return allBuiltins;
}
// Used by user-land module loaders to compile and load builtins.
compileForPublicLoader() {
if (!BuiltinModule.canBeRequiredByUsers(this.id)) {
// No code because this is an assertion against bugs
// eslint-disable-next-line no-restricted-syntax
throw new Error(`Should not compile ${this.id} for public use`);
}
this.compileForInternalLoader();
if (!this.exportKeys) {
// When using --expose-internals, we do not want to reflect the named
// exports from core modules as this can trigger unnecessary getters.
const internal = StringPrototypeStartsWith(this.id, 'internal/');
this.exportKeys = internal ? [] : ObjectKeys(this.exports);
}
return this.exports;
}
getESMFacade() {
if (this.module) return this.module;
const url = `node:${this.id}`;
const builtin = this;
const exportsKeys = ArrayPrototypeSlice(this.exportKeys);
if (!ArrayPrototypeIncludes(exportsKeys, 'default')) {
ArrayPrototypePush(exportsKeys, 'default');
}
this.module = new ModuleWrap(
url, undefined, exportsKeys,
function() {
builtin.syncExports();
this.setExport('default', builtin.exports);
});
// Ensure immediate sync execution to capture exports now
this.module.instantiate();
this.module.evaluate(-1, false);
return this.module;
}
// Provide named exports for all builtin libraries so that the libraries
// may be imported in a nicer way for ESM users. The default export is left
// as the entire namespace (module.exports) and updates when this function is
// called so that APMs and other behavior are supported.
syncExports() {
const names = this.exportKeys;
if (this.module) {
for (let i = 0; i < names.length; i++) {
const exportName = names[i];
if (exportName === 'default') continue;
this.module.setExport(exportName,
getOwn(this.exports, exportName, this.exports));
}
}
}
compileForInternalLoader() {
if (this.loaded || this.loading) {
return this.exports;
}
const id = this.id;
this.loading = true;
try {
const requireFn = StringPrototypeStartsWith(this.id, 'internal/deps/') ?
requireWithFallbackInDeps : requireBuiltin;
const fn = compileFunction(id);
// Arguments must match the parameters specified in
// BuiltinLoader::LookupAndCompile().
fn(this.exports, requireFn, this, process, internalBinding, primordials);
this.loaded = true;
} finally {
this.loading = false;
}
// "NativeModule" is a legacy name of "BuiltinModule". We keep it
// here to avoid breaking users who parse process.moduleLoadList.
ArrayPrototypePush(moduleLoadList, `NativeModule ${id}`);
return this.exports;
}
}
// Think of this as module.exports in this file even though it is not
// written in CommonJS style.
const loaderExports = {
internalBinding,
BuiltinModule,
require: requireBuiltin,
};
function requireBuiltin(id) {
if (id === selfId) {
return loaderExports;
}
const mod = BuiltinModule.map.get(id);
// Can't load the internal errors module from here, have to use a raw error.
// eslint-disable-next-line no-restricted-syntax
if (!mod) throw new TypeError(`Missing internal module '${id}'`);
return mod.compileForInternalLoader();
}
// Allow internal modules from dependencies to require
// other modules from dependencies by providing fallbacks.
function requireWithFallbackInDeps(request) {
if (StringPrototypeStartsWith(request, 'node:')) {
request = StringPrototypeSlice(request, 5);
} else if (!BuiltinModule.map.has(request)) {
request = `internal/deps/${request}`;
}
return requireBuiltin(request);
}
function setupPrepareStackTrace() {
const {
setEnhanceStackForFatalException,
setPrepareStackTraceCallback,
} = internalBinding('errors');
const {
prepareStackTraceCallback,
ErrorPrepareStackTrace,
fatalExceptionStackEnhancers: {
beforeInspector,
afterInspector,
},
} = requireBuiltin('internal/errors');
// Tell our PrepareStackTraceCallback passed to the V8 API
// to call prepareStackTrace().
setPrepareStackTraceCallback(prepareStackTraceCallback);
// Set the function used to enhance the error stack for printing
setEnhanceStackForFatalException(beforeInspector, afterInspector);
// Setup the default Error.prepareStackTrace.
ObjectDefineProperty(Error, 'prepareStackTrace', {
__proto__: null,
writable: true,
enumerable: false,
configurable: true,
value: ErrorPrepareStackTrace,
});
}
// Store the internal loaders in C++.
setInternalLoaders(internalBinding, requireBuiltin);
// Setup per-realm bindings.
setupPrepareStackTrace();

View File

@ -0,0 +1,21 @@
'use strict';
// This script sets up the context for shadow realms.
const {
prepareShadowRealmExecution,
} = require('internal/process/pre_execution');
const {
BuiltinModule,
} = require('internal/bootstrap/realm');
BuiltinModule.setRealmAllowRequireByUsers([
/**
* The built-in modules exposed in the ShadowRealm must each be providing
* platform capabilities with no authority to cause side effects such as
* I/O or mutation of values that are shared across different realms within
* the same Node.js environment.
*/
]);
prepareShadowRealmExecution();

View File

@ -0,0 +1,38 @@
'use strict';
const credentials = internalBinding('credentials');
const rawMethods = internalBinding('process_methods');
// TODO: this should be detached from ERR_WORKER_UNSUPPORTED_OPERATION
const { unavailable } = require('internal/process/worker_thread_only');
process.abort = unavailable('process.abort()');
process.chdir = unavailable('process.chdir()');
process.umask = wrappedUmask;
process.cwd = rawMethods.cwd;
if (credentials.implementsPosixCredentials) {
process.initgroups = unavailable('process.initgroups()');
process.setgroups = unavailable('process.setgroups()');
process.setegid = unavailable('process.setegid()');
process.seteuid = unavailable('process.seteuid()');
process.setgid = unavailable('process.setgid()');
process.setuid = unavailable('process.setuid()');
}
// ---- keep the attachment of the wrappers above so that it's easier to ----
// ---- compare the setups side-by-side -----
const {
codes: {
ERR_WORKER_UNSUPPORTED_OPERATION,
},
} = require('internal/errors');
function wrappedUmask(mask) {
// process.umask() is a read-only operation in workers.
if (mask !== undefined) {
throw new ERR_WORKER_UNSUPPORTED_OPERATION('Setting process.umask()');
}
return rawMethods.umask(mask);
}

View File

@ -0,0 +1,144 @@
'use strict';
const credentials = internalBinding('credentials');
const rawMethods = internalBinding('process_methods');
const {
namespace: {
addDeserializeCallback,
addSerializeCallback,
isBuildingSnapshot,
},
} = require('internal/v8/startup_snapshot');
process.abort = rawMethods.abort;
process.umask = wrappedUmask;
process.chdir = wrappedChdir;
process.cwd = wrappedCwd;
if (credentials.implementsPosixCredentials) {
const wrapped = wrapPosixCredentialSetters(credentials);
process.initgroups = wrapped.initgroups;
process.setgroups = wrapped.setgroups;
process.setegid = wrapped.setegid;
process.seteuid = wrapped.seteuid;
process.setgid = wrapped.setgid;
process.setuid = wrapped.setuid;
}
// ---- keep the attachment of the wrappers above so that it's easier to ----
// ---- compare the setups side-by-side -----
const {
parseFileMode,
validateArray,
validateString,
validateUint32,
} = require('internal/validators');
function wrapPosixCredentialSetters(credentials) {
const {
codes: {
ERR_INVALID_ARG_TYPE,
ERR_UNKNOWN_CREDENTIAL,
},
} = require('internal/errors');
const {
initgroups: _initgroups,
setgroups: _setgroups,
setegid: _setegid,
seteuid: _seteuid,
setgid: _setgid,
setuid: _setuid,
} = credentials;
function initgroups(user, extraGroup) {
validateId(user, 'user');
validateId(extraGroup, 'extraGroup');
// Result is 0 on success, 1 if user is unknown, 2 if group is unknown.
const result = _initgroups(user, extraGroup);
if (result === 1) {
throw new ERR_UNKNOWN_CREDENTIAL('User', user);
} else if (result === 2) {
throw new ERR_UNKNOWN_CREDENTIAL('Group', extraGroup);
}
}
function setgroups(groups) {
validateArray(groups, 'groups');
for (let i = 0; i < groups.length; i++) {
validateId(groups[i], `groups[${i}]`);
}
// Result is 0 on success. A positive integer indicates that the
// corresponding group was not found.
const result = _setgroups(groups);
if (result > 0) {
throw new ERR_UNKNOWN_CREDENTIAL('Group', groups[result - 1]);
}
}
function wrapIdSetter(type, method) {
return function(id) {
validateId(id, 'id');
if (typeof id === 'number') id >>>= 0;
// Result is 0 on success, 1 if credential is unknown.
const result = method(id);
if (result === 1) {
throw new ERR_UNKNOWN_CREDENTIAL(type, id);
}
};
}
function validateId(id, name) {
if (typeof id === 'number') {
validateUint32(id, name);
} else if (typeof id !== 'string') {
throw new ERR_INVALID_ARG_TYPE(name, ['number', 'string'], id);
}
}
return {
initgroups,
setgroups,
setegid: wrapIdSetter('Group', _setegid),
seteuid: wrapIdSetter('User', _seteuid),
setgid: wrapIdSetter('Group', _setgid),
setuid: wrapIdSetter('User', _setuid),
};
}
// Cache the working directory to prevent lots of lookups. If the working
// directory is changed by `chdir`, it'll be updated.
let cachedCwd = '';
if (isBuildingSnapshot()) {
// Reset the cwd on both serialization and deserialization so it's fine
// for process.cwd() to be accessed inside of serialization callbacks.
addSerializeCallback(() => {
cachedCwd = '';
addDeserializeCallback(() => {
cachedCwd = '';
});
});
}
function wrappedChdir(directory) {
validateString(directory, 'directory');
rawMethods.chdir(directory);
// Mark cache that it requires an update.
cachedCwd = '';
}
function wrappedUmask(mask) {
if (mask !== undefined) {
mask = parseFileMode(mask, 'mask');
}
return rawMethods.umask(mask);
}
function wrappedCwd() {
if (cachedCwd === '')
cachedCwd = rawMethods.cwd();
return cachedCwd;
}

View File

@ -0,0 +1,318 @@
'use strict';
const {
ObjectDefineProperty,
} = primordials;
const rawMethods = internalBinding('process_methods');
const {
namespace: {
addSerializeCallback,
isBuildingSnapshot,
},
} = require('internal/v8/startup_snapshot');
// TODO(joyeecheung): deprecate and remove these underscore methods
process._debugProcess = rawMethods._debugProcess;
process._debugEnd = rawMethods._debugEnd;
// See the discussion in https://github.com/nodejs/node/issues/19009 and
// https://github.com/nodejs/node/pull/34010 for why these are no-ops.
// Five word summary: they were broken beyond repair.
process._startProfilerIdleNotifier = () => {};
process._stopProfilerIdleNotifier = () => {};
function defineStream(name, getter) {
ObjectDefineProperty(process, name, {
__proto__: null,
configurable: true,
enumerable: true,
get: getter,
});
}
defineStream('stdout', getStdout);
defineStream('stdin', getStdin);
defineStream('stderr', getStderr);
// Worker threads don't receive signals.
const {
startListeningIfSignal,
stopListeningIfSignal,
} = require('internal/process/signal');
process.on('newListener', startListeningIfSignal);
process.on('removeListener', stopListeningIfSignal);
// ---- keep the attachment of the wrappers above so that it's easier to ----
// ---- compare the setups side-by-side -----
const { guessHandleType } = require('internal/util');
function createWritableStdioStream(fd) {
let stream;
// Note stream._type is used for test-module-load-list.js
switch (guessHandleType(fd)) {
case 'TTY': {
const tty = require('tty');
stream = new tty.WriteStream(fd);
stream._type = 'tty';
break;
}
case 'FILE': {
const SyncWriteStream = require('internal/fs/sync_write_stream');
stream = new SyncWriteStream(fd, { autoClose: false });
stream._type = 'fs';
break;
}
case 'PIPE':
case 'TCP': {
const net = require('net');
// If fd is already being used for the IPC channel, libuv will return
// an error when trying to use it again. In that case, create the socket
// using the existing handle instead of the fd.
if (process.channel && process.channel.fd === fd) {
const { kChannelHandle } = require('internal/child_process');
stream = new net.Socket({
handle: process[kChannelHandle],
readable: false,
writable: true,
});
} else {
stream = new net.Socket({
fd,
readable: false,
writable: true,
});
}
stream._type = 'pipe';
break;
}
default: {
// Provide a dummy black-hole output for e.g. non-console
// Windows applications.
const { Writable } = require('stream');
stream = new Writable({
write(buf, enc, cb) {
cb();
},
});
}
}
// For supporting legacy API we put the FD here.
stream.fd = fd;
stream._isStdio = true;
return stream;
}
function dummyDestroy(err, cb) {
cb(err);
this._undestroy();
// We need to emit 'close' anyway so that the closing
// of the stream is observable. We just make sure we
// are not going to do it twice.
// The 'close' event is needed so that finished and
// pipeline work correctly.
if (!this._writableState.emitClose) {
process.nextTick(() => {
this.emit('close');
});
}
}
let stdin;
let stdout;
let stderr;
let stdoutDestroy;
let stderrDestroy;
function refreshStdoutOnSigWinch() {
stdout._refreshSize();
}
function refreshStderrOnSigWinch() {
stderr._refreshSize();
}
function addCleanup(fn) {
if (isBuildingSnapshot()) {
addSerializeCallback(fn);
}
}
function getStdout() {
if (stdout) return stdout;
stdout = createWritableStdioStream(1);
stdout.destroySoon = stdout.destroy;
// Override _destroy so that the fd is never actually closed.
stdoutDestroy = stdout._destroy;
stdout._destroy = dummyDestroy;
if (stdout.isTTY) {
process.on('SIGWINCH', refreshStdoutOnSigWinch);
}
addCleanup(function cleanupStdout() {
stdout._destroy = stdoutDestroy;
stdout.destroy();
process.removeListener('SIGWINCH', refreshStdoutOnSigWinch);
stdout = undefined;
});
// No need to add deserialize callback because stdout = undefined above
// causes the stream to be lazily initialized again later.
return stdout;
}
function getStderr() {
if (stderr) return stderr;
stderr = createWritableStdioStream(2);
stderr.destroySoon = stderr.destroy;
stderrDestroy = stderr._destroy;
// Override _destroy so that the fd is never actually closed.
stderr._destroy = dummyDestroy;
if (stderr.isTTY) {
process.on('SIGWINCH', refreshStderrOnSigWinch);
}
addCleanup(function cleanupStderr() {
stderr._destroy = stderrDestroy;
stderr.destroy();
process.removeListener('SIGWINCH', refreshStderrOnSigWinch);
stderr = undefined;
});
// No need to add deserialize callback because stderr = undefined above
// causes the stream to be lazily initialized again later.
return stderr;
}
function getStdin() {
if (stdin) return stdin;
const fd = 0;
switch (guessHandleType(fd)) {
case 'TTY': {
const tty = require('tty');
stdin = new tty.ReadStream(fd);
break;
}
case 'FILE': {
const fs = require('fs');
stdin = new fs.ReadStream(null, { fd: fd, autoClose: false });
break;
}
case 'PIPE':
case 'TCP': {
const net = require('net');
// It could be that process has been started with an IPC channel
// sitting on fd=0, in such case the pipe for this fd is already
// present and creating a new one will lead to the assertion failure
// in libuv.
if (process.channel && process.channel.fd === fd) {
stdin = new net.Socket({
handle: process.channel,
readable: true,
writable: false,
manualStart: true,
});
} else {
stdin = new net.Socket({
fd: fd,
readable: true,
writable: false,
manualStart: true,
});
}
// Make sure the stdin can't be `.end()`-ed
stdin._writableState.ended = true;
break;
}
default: {
// Provide a dummy contentless input for e.g. non-console
// Windows applications.
const { Readable } = require('stream');
stdin = new Readable({ read() {} });
stdin.push(null);
}
}
// For supporting legacy API we put the FD here.
stdin.fd = fd;
// `stdin` starts out life in a paused state, but node doesn't
// know yet. Explicitly to readStop() it to put it in the
// not-reading state.
if (stdin._handle?.readStop) {
stdin._handle.reading = false;
stdin._readableState.reading = false;
stdin._handle.readStop();
}
// If the user calls stdin.pause(), then we need to stop reading
// once the stream implementation does so (one nextTick later),
// so that the process can close down.
stdin.on('pause', () => {
process.nextTick(onpause);
});
function onpause() {
if (!stdin._handle)
return;
if (stdin._handle.reading && !stdin.readableFlowing) {
stdin._readableState.reading = false;
stdin._handle.reading = false;
stdin._handle.readStop();
}
}
addCleanup(function cleanupStdin() {
stdin.destroy();
stdin = undefined;
});
// No need to add deserialize callback because stdin = undefined above
// causes the stream to be lazily initialized again later.
return stdin;
}
// Used by internal tests.
rawMethods.resetStdioForTesting = function() {
stdin = undefined;
stdout = undefined;
stderr = undefined;
};
// Needed by the module loader and generally needed everywhere.
require('fs');
require('util');
require('url'); // eslint-disable-line no-restricted-modules
internalBinding('module_wrap');
require('internal/modules/cjs/loader');
require('internal/modules/esm/utils');
// Needed to refresh the time origin.
require('internal/perf/utils');
// Needed to register the async hooks.
if (internalBinding('config').hasInspector) {
require('internal/inspector_async_hook');
}
// Needed to set the wasm web API callbacks.
internalBinding('wasm_web_api');
// Needed to detect whether it's on main thread.
internalBinding('worker');
// Needed by most execution modes.
require('internal/modules/run_main');
// Needed to refresh DNS configurations.
require('internal/dns/utils');
// Needed by almost all execution modes. It's fine to
// load them into the snapshot as long as we don't run
// any of the initialization.
require('internal/process/pre_execution');

View File

@ -0,0 +1,58 @@
'use strict';
const {
ObjectDefineProperty,
} = primordials;
delete process._debugProcess;
delete process._debugEnd;
function defineStream(name, getter) {
ObjectDefineProperty(process, name, {
__proto__: null,
configurable: true,
enumerable: true,
get: getter,
});
}
defineStream('stdout', getStdout);
defineStream('stdin', getStdin);
defineStream('stderr', getStderr);
// Worker threads don't receive signals.
const {
startListeningIfSignal,
stopListeningIfSignal,
} = require('internal/process/signal');
process.removeListener('newListener', startListeningIfSignal);
process.removeListener('removeListener', stopListeningIfSignal);
// ---- keep the attachment of the wrappers above so that it's easier to ----
// ---- compare the setups side-by-side -----
const {
createWorkerStdio,
kStdioWantsMoreDataCallback,
} = require('internal/worker/io');
let workerStdio;
function lazyWorkerStdio() {
if (workerStdio === undefined) {
workerStdio = createWorkerStdio();
process.on('exit', flushSync);
}
return workerStdio;
}
function flushSync() {
workerStdio.stdout[kStdioWantsMoreDataCallback]();
workerStdio.stderr[kStdioWantsMoreDataCallback]();
}
function getStdout() { return lazyWorkerStdio().stdout; }
function getStderr() { return lazyWorkerStdio().stderr; }
function getStdin() { return lazyWorkerStdio().stdin; }

View File

@ -0,0 +1,105 @@
'use strict';
/**
* This file exposes web interfaces that is defined with the WebIDL
* [Exposed=*] extended attribute.
* See more details at https://webidl.spec.whatwg.org/#Exposed.
*/
const {
globalThis,
} = primordials;
const {
exposeInterface,
exposeLazyInterfaces,
exposeNamespace,
} = require('internal/util');
const config = internalBinding('config');
const { exposeLazyDOMExceptionProperty } = internalBinding('messaging');
// https://console.spec.whatwg.org/#console-namespace
exposeNamespace(globalThis, 'console',
createGlobalConsole());
const { URL, URLSearchParams } = require('internal/url');
// https://url.spec.whatwg.org/#url
exposeInterface(globalThis, 'URL', URL);
// https://url.spec.whatwg.org/#urlsearchparams
exposeInterface(globalThis, 'URLSearchParams', URLSearchParams);
exposeLazyDOMExceptionProperty(globalThis);
// https://dom.spec.whatwg.org/#interface-abortcontroller
// Lazy ones.
exposeLazyInterfaces(globalThis, 'internal/abort_controller', [
'AbortController', 'AbortSignal',
]);
// https://dom.spec.whatwg.org/#interface-eventtarget
const {
EventTarget, Event,
} = require('internal/event_target');
exposeInterface(globalThis, 'Event', Event);
exposeInterface(globalThis, 'EventTarget', EventTarget);
exposeLazyInterfaces(globalThis, 'internal/event_target', ['CustomEvent']);
// https://encoding.spec.whatwg.org/#textencoder
// https://encoding.spec.whatwg.org/#textdecoder
exposeLazyInterfaces(globalThis,
'internal/encoding',
['TextEncoder', 'TextDecoder']);
function createGlobalConsole() {
const consoleFromNode =
require('internal/console/global');
if (config.hasInspector) {
const inspector = require('internal/util/inspector');
// TODO(joyeecheung): postpone this until the first time inspector
// is activated.
inspector.wrapConsole(consoleFromNode);
const { setConsoleExtensionInstaller } = internalBinding('inspector');
// Setup inspector command line API.
setConsoleExtensionInstaller(inspector.installConsoleExtensions);
}
return consoleFromNode;
}
// Web Streams API
exposeLazyInterfaces(
globalThis,
'internal/webstreams/transformstream',
['TransformStream', 'TransformStreamDefaultController']);
exposeLazyInterfaces(
globalThis,
'internal/webstreams/writablestream',
['WritableStream', 'WritableStreamDefaultController', 'WritableStreamDefaultWriter']);
exposeLazyInterfaces(
globalThis,
'internal/webstreams/readablestream',
[
'ReadableStream', 'ReadableStreamDefaultReader',
'ReadableStreamBYOBReader', 'ReadableStreamBYOBRequest',
'ReadableByteStreamController', 'ReadableStreamDefaultController',
]);
exposeLazyInterfaces(
globalThis,
'internal/webstreams/queuingstrategies',
[
'ByteLengthQueuingStrategy', 'CountQueuingStrategy',
]);
exposeLazyInterfaces(
globalThis,
'internal/webstreams/encoding',
[
'TextEncoderStream', 'TextDecoderStream',
]);
exposeLazyInterfaces(
globalThis,
'internal/webstreams/compression',
[
'CompressionStream', 'DecompressionStream',
]);

View File

@ -0,0 +1,129 @@
'use strict';
/**
* This file exposes web interfaces that is defined with the WebIDL
* Exposed=Window + Exposed=(Window,Worker) extended attribute or exposed in
* WindowOrWorkerGlobalScope mixin.
* See more details at https://webidl.spec.whatwg.org/#Exposed and
* https://html.spec.whatwg.org/multipage/webappapis.html#windoworworkerglobalscope.
*/
const {
ObjectDefineProperty,
ObjectGetOwnPropertyDescriptor,
globalThis,
} = primordials;
const {
defineOperation,
defineLazyProperties,
defineReplaceableLazyAttribute,
exposeLazyInterfaces,
exposeInterface,
} = require('internal/util');
const {
ERR_INVALID_THIS,
ERR_NO_CRYPTO,
} = require('internal/errors').codes;
// https://html.spec.whatwg.org/multipage/webappapis.html#windoworworkerglobalscope
const timers = require('timers');
defineOperation(globalThis, 'clearInterval', timers.clearInterval);
defineOperation(globalThis, 'clearTimeout', timers.clearTimeout);
defineOperation(globalThis, 'setInterval', timers.setInterval);
defineOperation(globalThis, 'setTimeout', timers.setTimeout);
const {
queueMicrotask,
} = require('internal/process/task_queues');
defineOperation(globalThis, 'queueMicrotask', queueMicrotask);
defineLazyProperties(
globalThis,
'internal/worker/js_transferable',
['structuredClone'],
);
defineLazyProperties(globalThis, 'buffer', ['atob', 'btoa']);
// https://html.spec.whatwg.org/multipage/web-messaging.html#broadcasting-to-other-browsing-contexts
exposeLazyInterfaces(globalThis, 'internal/worker/io', ['BroadcastChannel']);
exposeLazyInterfaces(globalThis, 'internal/worker/io', [
'MessageChannel', 'MessagePort',
]);
// https://www.w3.org/TR/FileAPI/#dfn-Blob
exposeLazyInterfaces(globalThis, 'internal/blob', ['Blob']);
// https://www.w3.org/TR/FileAPI/#dfn-file
exposeLazyInterfaces(globalThis, 'internal/file', ['File']);
// https://www.w3.org/TR/hr-time-2/#the-performance-attribute
exposeLazyInterfaces(globalThis, 'perf_hooks', [
'Performance', 'PerformanceEntry', 'PerformanceMark', 'PerformanceMeasure',
'PerformanceObserver', 'PerformanceObserverEntryList', 'PerformanceResourceTiming',
]);
defineReplaceableLazyAttribute(globalThis, 'perf_hooks', ['performance']);
// https://w3c.github.io/FileAPI/#creating-revoking
const { installObjectURLMethods, URLPattern } = require('internal/url');
installObjectURLMethods();
exposeInterface(globalThis, 'URLPattern', URLPattern);
let fetchImpl;
// https://fetch.spec.whatwg.org/#fetch-method
ObjectDefineProperty(globalThis, 'fetch', {
__proto__: null,
configurable: true,
enumerable: true,
writable: true,
value: function fetch(input, init = undefined) { // eslint-disable-line func-name-matching
if (!fetchImpl) { // Implement lazy loading of undici module for fetch function
const undiciModule = require('internal/deps/undici/undici');
fetchImpl = undiciModule.fetch;
}
return fetchImpl(input, init);
},
});
// https://xhr.spec.whatwg.org/#interface-formdata
// https://fetch.spec.whatwg.org/#headers-class
// https://fetch.spec.whatwg.org/#request-class
// https://fetch.spec.whatwg.org/#response-class
exposeLazyInterfaces(globalThis, 'internal/deps/undici/undici', [
'FormData', 'Headers', 'Request', 'Response', 'MessageEvent', 'CloseEvent',
]);
// https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events.org/
// https://websockets.spec.whatwg.org/
exposeLazyInterfaces(globalThis, 'internal/deps/undici/undici', ['EventSource', 'WebSocket']);
// The WebAssembly Web API which relies on Response.
// https:// webassembly.github.io/spec/web-api/#streaming-modules
internalBinding('wasm_web_api').setImplementation((streamState, source) => {
require('internal/wasm_web_api').wasmStreamingCallback(streamState, source);
});
// WebCryptoAPI
if (internalBinding('config').hasOpenSSL) {
defineReplaceableLazyAttribute(
globalThis,
'internal/crypto/webcrypto',
['crypto'],
false,
function cryptoThisCheck() {
if (this !== globalThis && this != null)
throw new ERR_INVALID_THIS(
'nullish or must be the global object');
},
);
exposeLazyInterfaces(
globalThis, 'internal/crypto/webcrypto',
['Crypto', 'CryptoKey', 'SubtleCrypto'],
);
} else {
ObjectDefineProperty(globalThis, 'crypto',
{ __proto__: null, ...ObjectGetOwnPropertyDescriptor({
get crypto() {
throw new ERR_NO_CRYPTO();
},
}, 'crypto') });
}

1128
lib/internal/buffer.js Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,169 @@
'use strict';
const {
ArrayPrototypePush,
JSONParse,
JSONStringify,
StringPrototypeSplit,
Symbol,
TypedArrayPrototypeSubarray,
} = primordials;
const { Buffer } = require('buffer');
const { StringDecoder } = require('string_decoder');
const v8 = require('v8');
const { isArrayBufferView } = require('internal/util/types');
const assert = require('internal/assert');
const { streamBaseState, kLastWriteWasAsync } = internalBinding('stream_wrap');
const kMessageBuffer = Symbol('kMessageBuffer');
const kMessageBufferSize = Symbol('kMessageBufferSize');
const kJSONBuffer = Symbol('kJSONBuffer');
const kStringDecoder = Symbol('kStringDecoder');
// Extend V8's serializer APIs to give more JSON-like behaviour in
// some cases; in particular, for native objects this serializes them the same
// way that JSON does rather than throwing an exception.
const kArrayBufferViewTag = 0;
const kNotArrayBufferViewTag = 1;
class ChildProcessSerializer extends v8.DefaultSerializer {
_writeHostObject(object) {
if (isArrayBufferView(object)) {
this.writeUint32(kArrayBufferViewTag);
return super._writeHostObject(object);
}
this.writeUint32(kNotArrayBufferViewTag);
this.writeValue({ ...object });
}
}
class ChildProcessDeserializer extends v8.DefaultDeserializer {
_readHostObject() {
const tag = this.readUint32();
if (tag === kArrayBufferViewTag)
return super._readHostObject();
assert(tag === kNotArrayBufferViewTag);
return this.readValue();
}
}
// Messages are parsed in either of the following formats:
// - Newline-delimited JSON, or
// - V8-serialized buffers, prefixed with their length as a big endian uint32
// (aka 'advanced')
const advanced = {
initMessageChannel(channel) {
channel[kMessageBuffer] = [];
channel[kMessageBufferSize] = 0;
channel.buffering = false;
},
*parseChannelMessages(channel, readData) {
if (readData.length === 0) return;
if (channel[kMessageBufferSize] && channel[kMessageBuffer][0].length < 4) {
// Message length split into two buffers, so let's concatenate it.
channel[kMessageBuffer][0] = Buffer.concat([channel[kMessageBuffer][0], readData]);
} else {
ArrayPrototypePush(channel[kMessageBuffer], readData);
}
channel[kMessageBufferSize] += readData.length;
// Index 0 should always be present because we just pushed data into it.
let messageBufferHead = channel[kMessageBuffer][0];
while (messageBufferHead.length >= 4) {
// We call `readUInt32BE` manually here, because this is faster than first converting
// it to a buffer and using `readUInt32BE` on that.
const fullMessageSize = (
messageBufferHead[0] << 24 |
messageBufferHead[1] << 16 |
messageBufferHead[2] << 8 |
messageBufferHead[3]
) + 4;
if (channel[kMessageBufferSize] < fullMessageSize) break;
const concatenatedBuffer = channel[kMessageBuffer].length === 1 ?
channel[kMessageBuffer][0] :
Buffer.concat(
channel[kMessageBuffer],
channel[kMessageBufferSize],
);
const deserializer = new ChildProcessDeserializer(
TypedArrayPrototypeSubarray(concatenatedBuffer, 4, fullMessageSize),
);
messageBufferHead = TypedArrayPrototypeSubarray(concatenatedBuffer, fullMessageSize);
channel[kMessageBufferSize] = messageBufferHead.length;
channel[kMessageBuffer] =
channel[kMessageBufferSize] !== 0 ? [messageBufferHead] : [];
deserializer.readHeader();
yield deserializer.readValue();
}
channel.buffering = channel[kMessageBufferSize] > 0;
},
writeChannelMessage(channel, req, message, handle) {
const ser = new ChildProcessSerializer();
// Add 4 bytes, to later populate with message length
ser.writeRawBytes(Buffer.allocUnsafe(4));
ser.writeHeader();
ser.writeValue(message);
const serializedMessage = ser.releaseBuffer();
const serializedMessageLength = serializedMessage.length - 4;
serializedMessage.set([
serializedMessageLength >> 24 & 0xFF,
serializedMessageLength >> 16 & 0xFF,
serializedMessageLength >> 8 & 0xFF,
serializedMessageLength & 0xFF,
], 0);
const result = channel.writeBuffer(req, serializedMessage, handle);
// Mirror what stream_base_commons.js does for Buffer retention.
if (streamBaseState[kLastWriteWasAsync])
req.buffer = serializedMessage;
return result;
},
};
const json = {
initMessageChannel(channel) {
channel[kJSONBuffer] = '';
channel[kStringDecoder] = undefined;
},
*parseChannelMessages(channel, readData) {
if (readData.length === 0) return;
if (channel[kStringDecoder] === undefined)
channel[kStringDecoder] = new StringDecoder('utf8');
const chunks =
StringPrototypeSplit(channel[kStringDecoder].write(readData), '\n');
const numCompleteChunks = chunks.length - 1;
// Last line does not have trailing linebreak
const incompleteChunk = chunks[numCompleteChunks];
if (numCompleteChunks === 0) {
channel[kJSONBuffer] += incompleteChunk;
} else {
chunks[0] = channel[kJSONBuffer] + chunks[0];
for (let i = 0; i < numCompleteChunks; i++)
yield JSONParse(chunks[i]);
channel[kJSONBuffer] = incompleteChunk;
}
channel.buffering = channel[kJSONBuffer].length !== 0;
},
writeChannelMessage(channel, req, message, handle) {
const string = JSONStringify(message) + '\n';
return channel.writeUtf8String(req, string, handle);
},
};
module.exports = { advanced, json };

94
lib/internal/cli_table.js Normal file
View File

@ -0,0 +1,94 @@
'use strict';
const {
ArrayPrototypeJoin,
ArrayPrototypeMap,
MathCeil,
MathMax,
MathMaxApply,
ObjectPrototypeHasOwnProperty,
StringPrototypeRepeat,
} = primordials;
const { getStringWidth } = require('internal/util/inspect');
// The use of Unicode characters below is the only non-comment use of non-ASCII
// Unicode characters in Node.js built-in modules. If they are ever removed or
// rewritten with \u escapes, then a test will need to be (re-)added to Node.js
// core to verify that Unicode characters work in built-ins.
// Refs: https://github.com/nodejs/node/issues/10673
const tableChars = {
/* eslint-disable node-core/non-ascii-character */
middleMiddle: '─',
rowMiddle: '┼',
topRight: '┐',
topLeft: '┌',
leftMiddle: '├',
topMiddle: '┬',
bottomRight: '┘',
bottomLeft: '└',
bottomMiddle: '┴',
rightMiddle: '┤',
left: '│ ',
right: ' │',
middle: ' │ ',
/* eslint-enable node-core/non-ascii-character */
};
const renderRow = (row, columnWidths) => {
let out = tableChars.left;
for (let i = 0; i < row.length; i++) {
const cell = row[i];
const len = getStringWidth(cell);
const needed = (columnWidths[i] - len);
// round(needed) + ceil(needed) will always add up to the amount
// of spaces we need while also left justifying the output.
out += cell + StringPrototypeRepeat(' ', MathCeil(needed));
if (i !== row.length - 1)
out += tableChars.middle;
}
out += tableChars.right;
return out;
};
const table = (head, columns) => {
const rows = [];
const columnWidths = ArrayPrototypeMap(head, (h) => getStringWidth(h));
const longestColumn = MathMaxApply(ArrayPrototypeMap(columns, (a) =>
a.length));
for (let i = 0; i < head.length; i++) {
const column = columns[i];
for (let j = 0; j < longestColumn; j++) {
if (rows[j] === undefined)
rows[j] = [];
const value = rows[j][i] =
ObjectPrototypeHasOwnProperty(column, j) ? column[j] : '';
const width = columnWidths[i] || 0;
const counted = getStringWidth(value);
columnWidths[i] = MathMax(width, counted);
}
}
const divider = ArrayPrototypeMap(columnWidths, (i) =>
StringPrototypeRepeat(tableChars.middleMiddle, i + 2));
let result = tableChars.topLeft +
ArrayPrototypeJoin(divider, tableChars.topMiddle) +
tableChars.topRight + '\n' +
renderRow(head, columnWidths) + '\n' +
tableChars.leftMiddle +
ArrayPrototypeJoin(divider, tableChars.rowMiddle) +
tableChars.rightMiddle + '\n';
for (const row of rows)
result += `${renderRow(row, columnWidths)}\n`;
result += tableChars.bottomLeft +
ArrayPrototypeJoin(divider, tableChars.bottomMiddle) +
tableChars.bottomRight;
return result;
};
module.exports = table;

View File

@ -0,0 +1,308 @@
'use strict';
const {
ArrayPrototypeJoin,
FunctionPrototype,
ObjectAssign,
ReflectApply,
SafeMap,
SafeSet,
} = primordials;
const assert = require('internal/assert');
const path = require('path');
const EventEmitter = require('events');
const { owner_symbol } = require('internal/async_hooks').symbols;
const Worker = require('internal/cluster/worker');
const { internal, sendHelper } = require('internal/cluster/utils');
const { exitCodes: { kNoFailure } } = internalBinding('errors');
const { TIMEOUT_MAX } = require('internal/timers');
const { setInterval, clearInterval } = require('timers');
const cluster = new EventEmitter();
const handles = new SafeMap();
const indexes = new SafeMap();
const noop = FunctionPrototype;
module.exports = cluster;
cluster.isWorker = true;
cluster.isMaster = false; // Deprecated alias. Must be same as isPrimary.
cluster.isPrimary = false;
cluster.worker = null;
cluster.Worker = Worker;
cluster._setupWorker = function() {
const worker = new Worker({
id: +process.env.NODE_UNIQUE_ID | 0,
process: process,
state: 'online',
});
cluster.worker = worker;
process.once('disconnect', () => {
worker.emit('disconnect');
if (!worker.exitedAfterDisconnect) {
// Unexpected disconnect, primary exited, or some such nastiness, so
// worker exits immediately.
process.exit(kNoFailure);
}
});
process.on('internalMessage', internal(worker, onmessage));
send({ act: 'online' });
function onmessage(message, handle) {
if (message.act === 'newconn')
onconnection(message, handle);
else if (message.act === 'disconnect')
ReflectApply(_disconnect, worker, [true]);
}
};
// `obj` is a net#Server or a dgram#Socket object.
cluster._getServer = function(obj, options, cb) {
let address = options.address;
// Resolve unix socket paths to absolute paths
if (options.port < 0 && typeof address === 'string' &&
process.platform !== 'win32')
address = path.resolve(address);
const indexesKey = ArrayPrototypeJoin(
[
address,
options.port,
options.addressType,
options.fd,
], ':');
let indexSet = indexes.get(indexesKey);
if (indexSet === undefined) {
indexSet = { nextIndex: 0, set: new SafeSet() };
indexes.set(indexesKey, indexSet);
}
const index = indexSet.nextIndex++;
indexSet.set.add(index);
const message = {
act: 'queryServer',
index,
data: null,
...options,
};
message.address = address;
// Set custom data on handle (i.e. tls tickets key)
if (obj._getServerData)
message.data = obj._getServerData();
send(message, (reply, handle) => {
if (typeof obj._setServerData === 'function')
obj._setServerData(reply.data);
if (handle) {
// Shared listen socket
shared(reply, { handle, indexesKey, index }, cb);
} else {
// Round-robin.
rr(reply, { indexesKey, index }, cb);
}
});
obj.once('listening', () => {
// short-lived sockets might have been closed
if (!indexes.has(indexesKey)) {
return;
}
cluster.worker.state = 'listening';
const address = obj.address();
message.act = 'listening';
message.port = (address?.port) || options.port;
send(message);
});
};
function removeIndexesKey(indexesKey, index) {
const indexSet = indexes.get(indexesKey);
if (!indexSet) {
return;
}
indexSet.set.delete(index);
if (indexSet.set.size === 0) {
indexes.delete(indexesKey);
}
}
// Shared listen socket.
function shared(message, { handle, indexesKey, index }, cb) {
const key = message.key;
// Monkey-patch the close() method so we can keep track of when it's
// closed. Avoids resource leaks when the handle is short-lived.
const close = handle.close;
handle.close = function() {
send({ act: 'close', key });
handles.delete(key);
removeIndexesKey(indexesKey, index);
return ReflectApply(close, handle, arguments);
};
assert(handles.has(key) === false);
handles.set(key, handle);
cb(message.errno, handle);
}
// Round-robin. Master distributes handles across workers.
function rr(message, { indexesKey, index }, cb) {
if (message.errno)
return cb(message.errno, null);
let key = message.key;
let fakeHandle = null;
function ref() {
fakeHandle ||= setInterval(noop, TIMEOUT_MAX);
}
function unref() {
if (fakeHandle) {
clearInterval(fakeHandle);
fakeHandle = null;
}
}
function listen(backlog) {
// TODO(bnoordhuis) Send a message to the primary that tells it to
// update the backlog size. The actual backlog should probably be
// the largest requested size by any worker.
return 0;
}
function close() {
// lib/net.js treats server._handle.close() as effectively synchronous.
// That means there is a time window between the call to close() and
// the ack by the primary process in which we can still receive handles.
// onconnection() below handles that by sending those handles back to
// the primary.
if (key === undefined)
return;
unref();
// If the handle is the last handle in process,
// the parent process will delete the handle when worker process exits.
// So it is ok if the close message get lost.
// See the comments of https://github.com/nodejs/node/pull/46161
send({ act: 'close', key });
handles.delete(key);
removeIndexesKey(indexesKey, index);
key = undefined;
}
function getsockname(out) {
if (key)
ObjectAssign(out, message.sockname);
return 0;
}
// Faux handle. net.Server is not associated with handle,
// so we control its state(ref or unref) by setInterval.
const handle = { close, listen, ref, unref };
handle.ref();
if (message.sockname) {
handle.getsockname = getsockname; // TCP handles only.
}
assert(handles.has(key) === false);
handles.set(key, handle);
cb(0, handle);
}
// Round-robin connection.
function onconnection(message, handle) {
const key = message.key;
const server = handles.get(key);
let accepted = server !== undefined;
if (accepted && server[owner_symbol]) {
const self = server[owner_symbol];
if (self.maxConnections != null &&
self._connections >= self.maxConnections &&
!self.dropMaxConnection) {
accepted = false;
}
}
send({ ack: message.seq, accepted });
if (accepted)
server.onconnection(0, handle);
else
handle.close();
}
function send(message, cb) {
return sendHelper(process, message, null, cb);
}
function _disconnect(primaryInitiated) {
this.exitedAfterDisconnect = true;
let waitingCount = 1;
function checkWaitingCount() {
waitingCount--;
if (waitingCount === 0) {
// If disconnect is worker initiated, wait for ack to be sure
// exitedAfterDisconnect is properly set in the primary, otherwise, if
// it's primary initiated there's no need to send the
// exitedAfterDisconnect message
if (primaryInitiated) {
process.disconnect();
} else {
send({ act: 'exitedAfterDisconnect' }, () => process.disconnect());
}
}
}
for (const handle of handles.values()) {
waitingCount++;
if (handle[owner_symbol])
handle[owner_symbol].close(checkWaitingCount);
else
handle.close(checkWaitingCount);
}
handles.clear();
checkWaitingCount();
}
// Extend generic Worker with methods specific to worker processes.
Worker.prototype.disconnect = function() {
if (this.state !== 'disconnecting' && this.state !== 'destroying') {
this.state = 'disconnecting';
ReflectApply(_disconnect, this, []);
}
return this;
};
Worker.prototype.destroy = function() {
if (this.state === 'destroying')
return;
this.exitedAfterDisconnect = true;
if (!this.isConnected()) {
process.exit(kNoFailure);
} else {
this.state = 'destroying';
send({ act: 'exitedAfterDisconnect' }, () => process.disconnect());
process.once('disconnect', () => process.exit(kNoFailure));
}
};

View File

@ -0,0 +1,362 @@
'use strict';
const {
ArrayPrototypePush,
ArrayPrototypeSlice,
ArrayPrototypeSome,
ObjectKeys,
ObjectValues,
SafeMap,
StringPrototypeStartsWith,
} = primordials;
const {
codes: {
ERR_SOCKET_BAD_PORT,
},
} = require('internal/errors');
const assert = require('internal/assert');
const { fork } = require('child_process');
const path = require('path');
const EventEmitter = require('events');
const RoundRobinHandle = require('internal/cluster/round_robin_handle');
const SharedHandle = require('internal/cluster/shared_handle');
const Worker = require('internal/cluster/worker');
const { getInspectPort, isUsingInspector } = require('internal/util/inspector');
const { internal, sendHelper } = require('internal/cluster/utils');
const cluster = new EventEmitter();
const intercom = new EventEmitter();
const SCHED_NONE = 1;
const SCHED_RR = 2;
module.exports = cluster;
const handles = new SafeMap();
cluster.isWorker = false;
cluster.isMaster = true; // Deprecated alias. Must be same as isPrimary.
cluster.isPrimary = true;
cluster.Worker = Worker;
cluster.workers = {};
cluster.settings = {};
cluster.SCHED_NONE = SCHED_NONE; // Leave it to the operating system.
cluster.SCHED_RR = SCHED_RR; // Primary distributes connections.
let ids = 0;
let initialized = false;
// XXX(bnoordhuis) Fold cluster.schedulingPolicy into cluster.settings?
let schedulingPolicy = process.env.NODE_CLUSTER_SCHED_POLICY;
if (schedulingPolicy === 'rr')
schedulingPolicy = SCHED_RR;
else if (schedulingPolicy === 'none')
schedulingPolicy = SCHED_NONE;
else if (process.platform === 'win32') {
// Round-robin doesn't perform well on
// Windows due to the way IOCP is wired up.
schedulingPolicy = SCHED_NONE;
} else
schedulingPolicy = SCHED_RR;
cluster.schedulingPolicy = schedulingPolicy;
cluster.setupPrimary = function(options) {
const settings = {
args: ArrayPrototypeSlice(process.argv, 2),
exec: process.argv[1],
execArgv: process.execArgv,
silent: false,
...cluster.settings,
...options,
};
// Tell V8 to write profile data for each process to a separate file.
// Without --logfile=v8-%p.log, everything ends up in a single, unusable
// file. (Unusable because what V8 logs are memory addresses and each
// process has its own memory mappings.)
if (ArrayPrototypeSome(settings.execArgv,
(s) => StringPrototypeStartsWith(s, '--prof')) &&
!ArrayPrototypeSome(settings.execArgv,
(s) => StringPrototypeStartsWith(s, '--logfile='))) {
settings.execArgv = [...settings.execArgv, '--logfile=v8-%p.log'];
}
cluster.settings = settings;
if (initialized === true)
return process.nextTick(setupSettingsNT, settings);
initialized = true;
schedulingPolicy = cluster.schedulingPolicy; // Freeze policy.
assert(schedulingPolicy === SCHED_NONE || schedulingPolicy === SCHED_RR,
`Bad cluster.schedulingPolicy: ${schedulingPolicy}`);
process.nextTick(setupSettingsNT, settings);
process.on('internalMessage', (message) => {
if (message.cmd !== 'NODE_DEBUG_ENABLED')
return;
for (const worker of ObjectValues(cluster.workers)) {
if (worker.state === 'online' || worker.state === 'listening') {
process._debugProcess(worker.process.pid);
} else {
worker.once('online', function() {
process._debugProcess(this.process.pid);
});
}
}
});
};
// Deprecated alias must be same as setupPrimary
cluster.setupMaster = cluster.setupPrimary;
function setupSettingsNT(settings) {
cluster.emit('setup', settings);
}
function createWorkerProcess(id, env) {
const workerEnv = { ...process.env, ...env, NODE_UNIQUE_ID: `${id}` };
const execArgv = [...cluster.settings.execArgv];
if (cluster.settings.inspectPort === null) {
throw new ERR_SOCKET_BAD_PORT('Port', null, true);
}
if (isUsingInspector(cluster.settings.execArgv)) {
ArrayPrototypePush(execArgv, `--inspect-port=${getInspectPort(cluster.settings.inspectPort)}`);
}
return fork(cluster.settings.exec, cluster.settings.args, {
cwd: cluster.settings.cwd,
env: workerEnv,
serialization: cluster.settings.serialization,
silent: cluster.settings.silent,
windowsHide: cluster.settings.windowsHide,
execArgv: execArgv,
stdio: cluster.settings.stdio,
gid: cluster.settings.gid,
uid: cluster.settings.uid,
});
}
function removeWorker(worker) {
assert(worker);
delete cluster.workers[worker.id];
if (ObjectKeys(cluster.workers).length === 0) {
assert(handles.size === 0, 'Resource leak detected.');
intercom.emit('disconnect');
}
}
function removeHandlesForWorker(worker) {
assert(worker);
for (const { 0: key, 1: handle } of handles) {
if (handle.remove(worker))
handles.delete(key);
}
}
cluster.fork = function(env) {
cluster.setupPrimary();
const id = ++ids;
const workerProcess = createWorkerProcess(id, env);
const worker = new Worker({
id: id,
process: workerProcess,
});
worker.on('message', function(message, handle) {
cluster.emit('message', this, message, handle);
});
worker.process.once('exit', (exitCode, signalCode) => {
/*
* Remove the worker from the workers list only
* if it has disconnected, otherwise we might
* still want to access it.
*/
if (!worker.isConnected()) {
removeHandlesForWorker(worker);
removeWorker(worker);
}
worker.exitedAfterDisconnect = !!worker.exitedAfterDisconnect;
worker.state = 'dead';
worker.emit('exit', exitCode, signalCode);
cluster.emit('exit', worker, exitCode, signalCode);
});
worker.process.once('disconnect', () => {
/*
* Now is a good time to remove the handles
* associated with this worker because it is
* not connected to the primary anymore.
*/
removeHandlesForWorker(worker);
/*
* Remove the worker from the workers list only
* if its process has exited. Otherwise, we might
* still want to access it.
*/
if (worker.isDead())
removeWorker(worker);
worker.exitedAfterDisconnect = !!worker.exitedAfterDisconnect;
worker.state = 'disconnected';
worker.emit('disconnect');
cluster.emit('disconnect', worker);
});
worker.process.on('internalMessage', internal(worker, onmessage));
process.nextTick(emitForkNT, worker);
cluster.workers[worker.id] = worker;
return worker;
};
function emitForkNT(worker) {
cluster.emit('fork', worker);
}
cluster.disconnect = function(cb) {
const workers = ObjectValues(cluster.workers);
if (workers.length === 0) {
process.nextTick(() => intercom.emit('disconnect'));
} else {
for (const worker of workers) {
if (worker.isConnected()) {
worker.disconnect();
}
}
}
if (typeof cb === 'function')
intercom.once('disconnect', cb);
};
const methodMessageMapping = {
close,
exitedAfterDisconnect,
listening,
online,
queryServer,
};
function onmessage(message, handle) {
const worker = this;
const fn = methodMessageMapping[message.act];
if (typeof fn === 'function')
fn(worker, message);
}
function online(worker) {
worker.state = 'online';
worker.emit('online');
cluster.emit('online', worker);
}
function exitedAfterDisconnect(worker, message) {
worker.exitedAfterDisconnect = true;
send(worker, { ack: message.seq });
}
function queryServer(worker, message) {
// Stop processing if worker already disconnecting
if (worker.exitedAfterDisconnect)
return;
const key = `${message.address}:${message.port}:${message.addressType}:` +
`${message.fd}:${message.index}`;
let handle = handles.get(key);
if (handle === undefined) {
let address = message.address;
// Find shortest path for unix sockets because of the ~100 byte limit
if (message.port < 0 && typeof address === 'string' &&
process.platform !== 'win32') {
address = path.relative(process.cwd(), address);
if (message.address.length < address.length)
address = message.address;
}
// UDP is exempt from round-robin connection balancing for what should
// be obvious reasons: it's connectionless. There is nothing to send to
// the workers except raw datagrams and that's pointless.
if (schedulingPolicy !== SCHED_RR ||
message.addressType === 'udp4' ||
message.addressType === 'udp6') {
handle = new SharedHandle(key, address, message);
} else {
handle = new RoundRobinHandle(key, address, message);
}
handles.set(key, handle);
}
handle.data ||= message.data;
// Set custom server data
handle.add(worker, (errno, reply, handle) => {
const { data } = handles.get(key);
if (errno)
handles.delete(key); // Gives other workers a chance to retry.
send(worker, {
errno,
key,
ack: message.seq,
data,
...reply,
}, handle);
});
}
function listening(worker, message) {
const info = {
addressType: message.addressType,
address: message.address,
port: message.port,
fd: message.fd,
};
worker.state = 'listening';
worker.emit('listening', info);
cluster.emit('listening', worker, info);
}
// Server in worker is closing, remove from list. The handle may have been
// removed by a prior call to removeHandlesForWorker() so guard against that.
function close(worker, message) {
const key = message.key;
const handle = handles.get(key);
if (handle && handle.remove(worker))
handles.delete(key);
}
function send(worker, message, handle, cb) {
return sendHelper(worker.process, message, handle, cb);
}
// Extend generic Worker with methods specific to the primary process.
Worker.prototype.disconnect = function() {
this.exitedAfterDisconnect = true;
send(this, { act: 'disconnect' });
removeHandlesForWorker(this);
removeWorker(this);
return this;
};
Worker.prototype.destroy = function(signo) {
const signal = signo || 'SIGTERM';
this.process.kill(signal);
};

View File

@ -0,0 +1,139 @@
'use strict';
const {
ArrayIsArray,
Boolean,
SafeMap,
} = primordials;
const assert = require('internal/assert');
const net = require('net');
const { sendHelper } = require('internal/cluster/utils');
const { append, init, isEmpty, peek, remove } = require('internal/linkedlist');
const { constants } = internalBinding('tcp_wrap');
module.exports = RoundRobinHandle;
function RoundRobinHandle(key, address, { port, fd, flags, backlog, readableAll, writableAll }) {
this.key = key;
this.all = new SafeMap();
this.free = new SafeMap();
this.handles = init({ __proto__: null });
this.handle = null;
this.server = net.createServer(assert.fail);
if (fd >= 0)
this.server.listen({ fd, backlog });
else if (port >= 0) {
this.server.listen({
port,
host: address,
// Currently, net module only supports `ipv6Only` option in `flags`.
ipv6Only: Boolean(flags & constants.UV_TCP_IPV6ONLY),
backlog,
});
} else
this.server.listen({
path: address,
backlog,
readableAll,
writableAll,
}); // UNIX socket path.
this.server.once('listening', () => {
this.handle = this.server._handle;
this.handle.onconnection = (err, handle) => this.distribute(err, handle);
this.server._handle = null;
this.server = null;
});
}
RoundRobinHandle.prototype.add = function(worker, send) {
assert(this.all.has(worker.id) === false);
this.all.set(worker.id, worker);
const done = () => {
if (this.handle.getsockname) {
const out = {};
this.handle.getsockname(out);
// TODO(bnoordhuis) Check err.
send(null, { sockname: out }, null);
} else {
send(null, null, null); // UNIX socket.
}
this.handoff(worker); // In case there are connections pending.
};
if (this.server === null)
return done();
// Still busy binding.
this.server.once('listening', done);
this.server.once('error', (err) => {
send(err.errno, null);
});
};
RoundRobinHandle.prototype.remove = function(worker) {
const existed = this.all.delete(worker.id);
if (!existed)
return false;
this.free.delete(worker.id);
if (this.all.size !== 0)
return false;
while (!isEmpty(this.handles)) {
const handle = peek(this.handles);
handle.close();
remove(handle);
}
this.handle.close();
this.handle = null;
return true;
};
RoundRobinHandle.prototype.distribute = function(err, handle) {
// If `accept` fails just skip it (handle is undefined)
if (err) {
return;
}
append(this.handles, handle);
// eslint-disable-next-line node-core/no-array-destructuring
const [ workerEntry ] = this.free; // this.free is a SafeMap
if (ArrayIsArray(workerEntry)) {
const { 0: workerId, 1: worker } = workerEntry;
this.free.delete(workerId);
this.handoff(worker);
}
};
RoundRobinHandle.prototype.handoff = function(worker) {
if (!this.all.has(worker.id)) {
return; // Worker is closing (or has closed) the server.
}
const handle = peek(this.handles);
if (handle === null) {
this.free.set(worker.id, worker); // Add to ready queue again.
return;
}
remove(handle);
const message = { act: 'newconn', key: this.key };
sendHelper(worker.process, message, handle, (reply) => {
if (reply.accepted)
handle.close();
else
this.distribute(0, handle); // Worker is shutting down. Send to another.
this.handoff(worker);
});
};

View File

@ -0,0 +1,49 @@
'use strict';
const {
SafeMap,
} = primordials;
const assert = require('internal/assert');
const dgram = require('internal/dgram');
const net = require('net');
module.exports = SharedHandle;
function SharedHandle(key, address, { port, addressType, fd, flags }) {
this.key = key;
this.workers = new SafeMap();
this.handle = null;
this.errno = 0;
let rval;
if (addressType === 'udp4' || addressType === 'udp6')
rval = dgram._createSocketHandle(address, port, addressType, fd, flags);
else
rval = net._createServerHandle(address, port, addressType, fd, flags);
if (typeof rval === 'number')
this.errno = rval;
else
this.handle = rval;
}
SharedHandle.prototype.add = function(worker, send) {
assert(!this.workers.has(worker.id));
this.workers.set(worker.id, worker);
send(this.errno, null, this.handle);
};
SharedHandle.prototype.remove = function(worker) {
if (!this.workers.has(worker.id))
return false;
this.workers.delete(worker.id);
if (this.workers.size !== 0)
return false;
this.handle.close();
this.handle = null;
return true;
};

View File

@ -0,0 +1,51 @@
'use strict';
const {
ReflectApply,
SafeMap,
} = primordials;
module.exports = {
sendHelper,
internal,
};
const callbacks = new SafeMap();
let seq = 0;
function sendHelper(proc, message, handle, cb) {
if (!proc.connected)
return false;
// Mark message as internal. See INTERNAL_PREFIX
// in lib/internal/child_process.js
message = { cmd: 'NODE_CLUSTER', ...message, seq };
if (typeof cb === 'function')
callbacks.set(seq, cb);
seq += 1;
return proc.send(message, handle);
}
// Returns an internalMessage listener that hands off normal messages
// to the callback but intercepts and redirects ACK messages.
function internal(worker, cb) {
return function onInternalMessage(message, handle) {
if (message.cmd !== 'NODE_CLUSTER')
return;
let fn = cb;
if (message.ack !== undefined) {
const callback = callbacks.get(message.ack);
if (callback !== undefined) {
fn = callback;
callbacks.delete(message.ack);
}
}
ReflectApply(fn, worker, arguments);
};
}

View File

@ -0,0 +1,57 @@
'use strict';
const {
ObjectSetPrototypeOf,
ReflectApply,
} = primordials;
const EventEmitter = require('events');
const { kEmptyObject } = require('internal/util');
module.exports = Worker;
// Common Worker implementation shared between the cluster primary and workers.
function Worker(options) {
if (!(this instanceof Worker))
return new Worker(options);
ReflectApply(EventEmitter, this, []);
if (options === null || typeof options !== 'object')
options = kEmptyObject;
this.exitedAfterDisconnect = undefined;
this.state = options.state || 'none';
this.id = options.id | 0;
if (options.process) {
this.process = options.process;
this.process.on('error', (code, signal) =>
this.emit('error', code, signal),
);
this.process.on('message', (message, handle) =>
this.emit('message', message, handle),
);
}
}
ObjectSetPrototypeOf(Worker.prototype, EventEmitter.prototype);
ObjectSetPrototypeOf(Worker, EventEmitter);
Worker.prototype.kill = function() {
ReflectApply(this.destroy, this, arguments);
};
Worker.prototype.send = function() {
return ReflectApply(this.process.send, this.process, arguments);
};
Worker.prototype.isDead = function() {
return this.process.exitCode != null || this.process.signalCode != null;
};
Worker.prototype.isConnected = function() {
return this.process.connected;
};

View File

@ -0,0 +1,688 @@
'use strict';
// The Console constructor is not actually used to construct the global
// console. It's exported for backwards compatibility.
const {
ArrayFrom,
ArrayIsArray,
ArrayPrototypeForEach,
ArrayPrototypePush,
ArrayPrototypeUnshift,
Boolean,
ErrorCaptureStackTrace,
FunctionPrototypeBind,
ObjectDefineProperties,
ObjectDefineProperty,
ObjectKeys,
ObjectPrototypeHasOwnProperty,
ObjectValues,
ReflectApply,
ReflectConstruct,
ReflectOwnKeys,
RegExpPrototypeSymbolReplace,
SafeArrayIterator,
SafeMap,
SafeSet,
SafeWeakMap,
StringPrototypeIncludes,
StringPrototypeRepeat,
StringPrototypeSlice,
Symbol,
SymbolHasInstance,
SymbolToStringTag,
} = primordials;
const { trace } = internalBinding('trace_events');
const {
codes: {
ERR_CONSOLE_WRITABLE_STREAM,
ERR_INCOMPATIBLE_OPTION_PAIR,
},
isStackOverflowError,
} = require('internal/errors');
const {
validateArray,
validateInteger,
validateObject,
validateOneOf,
} = require('internal/validators');
const { previewEntries } = internalBinding('util');
const { Buffer: { isBuffer } } = require('buffer');
const {
inspect,
formatWithOptions,
} = require('internal/util/inspect');
const {
isTypedArray, isSet, isMap, isSetIterator, isMapIterator,
} = require('internal/util/types');
const {
CHAR_UPPERCASE_C: kTraceCount,
} = require('internal/constants');
const kCounts = Symbol('counts');
const { time, timeLog, timeEnd, kNone } = require('internal/util/debuglog');
const { channel } = require('diagnostics_channel');
const onLog = channel('console.log');
const onWarn = channel('console.warn');
const onError = channel('console.error');
const onInfo = channel('console.info');
const onDebug = channel('console.debug');
const kTraceConsoleCategory = 'node,node.console';
const kMaxGroupIndentation = 1000;
// Lazy loaded for startup performance.
let cliTable;
let utilColors;
function lazyUtilColors() {
utilColors ??= require('internal/util/colors');
return utilColors;
}
// Track amount of indentation required via `console.group()`.
const kGroupIndentationWidth = Symbol('kGroupIndentWidth');
const kFormatForStderr = Symbol('kFormatForStderr');
const kFormatForStdout = Symbol('kFormatForStdout');
const kGetInspectOptions = Symbol('kGetInspectOptions');
const kColorMode = Symbol('kColorMode');
const kIsConsole = Symbol('kIsConsole');
const kWriteToConsole = Symbol('kWriteToConsole');
const kBindProperties = Symbol('kBindProperties');
const kBindStreamsEager = Symbol('kBindStreamsEager');
const kBindStreamsLazy = Symbol('kBindStreamsLazy');
const kUseStdout = Symbol('kUseStdout');
const kUseStderr = Symbol('kUseStderr');
const optionsMap = new SafeWeakMap();
function Console(options /* or: stdout, stderr, ignoreErrors = true */) {
// We have to test new.target here to see if this function is called
// with new, because we need to define a custom instanceof to accommodate
// the global console.
if (new.target === undefined) {
return ReflectConstruct(Console, arguments);
}
if (!options || typeof options.write === 'function') {
options = {
stdout: options,
stderr: arguments[1],
ignoreErrors: arguments[2],
};
}
const {
stdout,
stderr = stdout,
ignoreErrors = true,
colorMode = 'auto',
inspectOptions,
groupIndentation,
} = options;
if (!stdout || typeof stdout.write !== 'function') {
throw new ERR_CONSOLE_WRITABLE_STREAM('stdout');
}
if (!stderr || typeof stderr.write !== 'function') {
throw new ERR_CONSOLE_WRITABLE_STREAM('stderr');
}
validateOneOf(colorMode, 'colorMode', ['auto', true, false]);
if (groupIndentation !== undefined) {
validateInteger(groupIndentation, 'groupIndentation',
0, kMaxGroupIndentation);
}
if (inspectOptions !== undefined) {
validateObject(inspectOptions, 'options.inspectOptions');
if (inspectOptions.colors !== undefined &&
options.colorMode !== undefined) {
throw new ERR_INCOMPATIBLE_OPTION_PAIR(
'options.inspectOptions.color', 'colorMode');
}
optionsMap.set(this, inspectOptions);
}
// Bind the prototype functions to this Console instance
ArrayPrototypeForEach(ObjectKeys(Console.prototype), (key) => {
// We have to bind the methods grabbed from the instance instead of from
// the prototype so that users extending the Console can override them
// from the prototype chain of the subclass.
this[key] = FunctionPrototypeBind(this[key], this);
ObjectDefineProperty(this[key], 'name', {
__proto__: null,
value: key,
});
});
this[kBindStreamsEager](stdout, stderr);
this[kBindProperties](ignoreErrors, colorMode, groupIndentation);
}
const consolePropAttributes = {
writable: true,
enumerable: false,
configurable: true,
};
// Fixup global.console instanceof global.console.Console
ObjectDefineProperty(Console, SymbolHasInstance, {
__proto__: null,
value(instance) {
return instance[kIsConsole];
},
});
const kColorInspectOptions = { colors: true };
const kNoColorInspectOptions = {};
const internalIndentationMap = new SafeWeakMap();
ObjectDefineProperties(Console.prototype, {
[kBindStreamsEager]: {
__proto__: null,
...consolePropAttributes,
// Eager version for the Console constructor
value: function(stdout, stderr) {
ObjectDefineProperties(this, {
'_stdout': { __proto__: null, ...consolePropAttributes, value: stdout },
'_stderr': { __proto__: null, ...consolePropAttributes, value: stderr },
});
},
},
[kBindStreamsLazy]: {
__proto__: null,
...consolePropAttributes,
// Lazily load the stdout and stderr from an object so we don't
// create the stdio streams when they are not even accessed
value: function(object) {
let stdout;
let stderr;
ObjectDefineProperties(this, {
'_stdout': {
__proto__: null,
enumerable: false,
configurable: true,
get() {
return stdout ||= object.stdout;
},
set(value) { stdout = value; },
},
'_stderr': {
__proto__: null,
enumerable: false,
configurable: true,
get() {
return stderr ||= object.stderr;
},
set(value) { stderr = value; },
},
});
},
},
[kBindProperties]: {
__proto__: null,
...consolePropAttributes,
value: function(ignoreErrors, colorMode, groupIndentation = 2) {
ObjectDefineProperties(this, {
'_stdoutErrorHandler': {
__proto__: null,
...consolePropAttributes,
value: createWriteErrorHandler(this, kUseStdout),
},
'_stderrErrorHandler': {
...consolePropAttributes,
__proto__: null,
value: createWriteErrorHandler(this, kUseStderr),
},
'_ignoreErrors': {
__proto__: null,
...consolePropAttributes,
value: Boolean(ignoreErrors),
},
'_times': { __proto__: null, ...consolePropAttributes, value: new SafeMap() },
// Corresponds to https://console.spec.whatwg.org/#count-map
[kCounts]: { __proto__: null, ...consolePropAttributes, value: new SafeMap() },
[kColorMode]: { __proto__: null, ...consolePropAttributes, value: colorMode },
[kIsConsole]: { __proto__: null, ...consolePropAttributes, value: true },
[kGroupIndentationWidth]: {
__proto__: null,
...consolePropAttributes,
value: groupIndentation,
},
[SymbolToStringTag]: {
__proto__: null,
writable: false,
enumerable: false,
configurable: true,
value: 'console',
},
});
},
},
[kWriteToConsole]: {
__proto__: null,
...consolePropAttributes,
value: function(streamSymbol, string) {
const ignoreErrors = this._ignoreErrors;
const groupIndent = internalIndentationMap.get(this) || '';
const useStdout = streamSymbol === kUseStdout;
const stream = useStdout ? this._stdout : this._stderr;
const errorHandler = useStdout ?
this._stdoutErrorHandler : this._stderrErrorHandler;
if (groupIndent.length !== 0) {
if (StringPrototypeIncludes(string, '\n')) {
string = RegExpPrototypeSymbolReplace(/\n/g, string, `\n${groupIndent}`);
}
string = groupIndent + string;
}
string += '\n';
if (ignoreErrors === false) return stream.write(string);
// There may be an error occurring synchronously (e.g. for files or TTYs
// on POSIX systems) or asynchronously (e.g. pipes on POSIX systems), so
// handle both situations.
try {
// Add and later remove a noop error handler to catch synchronous
// errors.
if (stream.listenerCount('error') === 0)
stream.once('error', noop);
stream.write(string, errorHandler);
} catch (e) {
// Console is a debugging utility, so it swallowing errors is not
// desirable even in edge cases such as low stack space.
if (isStackOverflowError(e))
throw e;
// Sorry, there's no proper way to pass along the error here.
} finally {
stream.removeListener('error', noop);
}
},
},
[kGetInspectOptions]: {
__proto__: null,
...consolePropAttributes,
value: function(stream) {
let color = this[kColorMode];
if (color === 'auto') {
color = lazyUtilColors().shouldColorize(stream);
}
const options = optionsMap.get(this);
if (options) {
if (options.colors === undefined) {
options.colors = color;
}
return options;
}
return color ? kColorInspectOptions : kNoColorInspectOptions;
},
},
[kFormatForStdout]: {
__proto__: null,
...consolePropAttributes,
value: function(args) {
const opts = this[kGetInspectOptions](this._stdout);
ArrayPrototypeUnshift(args, opts);
return ReflectApply(formatWithOptions, null, args);
},
},
[kFormatForStderr]: {
__proto__: null,
...consolePropAttributes,
value: function(args) {
const opts = this[kGetInspectOptions](this._stderr);
ArrayPrototypeUnshift(args, opts);
return ReflectApply(formatWithOptions, null, args);
},
},
});
// Make a function that can serve as the callback passed to `stream.write()`.
function createWriteErrorHandler(instance, streamSymbol) {
return (err) => {
// This conditional evaluates to true if and only if there was an error
// that was not already emitted (which happens when the _write callback
// is invoked asynchronously).
const stream = streamSymbol === kUseStdout ?
instance._stdout : instance._stderr;
if (err !== null && !stream._writableState.errorEmitted) {
// If there was an error, it will be emitted on `stream` as
// an `error` event. Adding a `once` listener will keep that error
// from becoming an uncaught exception, but since the handler is
// removed after the event, non-console.* writes won't be affected.
// we are only adding noop if there is no one else listening for 'error'
if (stream.listenerCount('error') === 0) {
stream.once('error', noop);
}
}
};
}
function timeLogImpl(consoleRef, label, formatted, args) {
if (args === undefined) {
consoleRef.log('%s: %s', label, formatted);
} else {
consoleRef.log('%s: %s', label, formatted, ...new SafeArrayIterator(args));
}
}
const consoleMethods = {
log(...args) {
if (onLog.hasSubscribers) {
onLog.publish(args);
}
this[kWriteToConsole](kUseStdout, this[kFormatForStdout](args));
},
info(...args) {
if (onInfo.hasSubscribers) {
onInfo.publish(args);
}
this[kWriteToConsole](kUseStdout, this[kFormatForStdout](args));
},
debug(...args) {
if (onDebug.hasSubscribers) {
onDebug.publish(args);
}
this[kWriteToConsole](kUseStdout, this[kFormatForStdout](args));
},
warn(...args) {
if (onWarn.hasSubscribers) {
onWarn.publish(args);
}
this[kWriteToConsole](kUseStderr, this[kFormatForStderr](args));
},
error(...args) {
if (onError.hasSubscribers) {
onError.publish(args);
}
this[kWriteToConsole](kUseStderr, this[kFormatForStderr](args));
},
dir(object, options) {
this[kWriteToConsole](kUseStdout, inspect(object, {
customInspect: false,
...this[kGetInspectOptions](this._stdout),
...options,
}));
},
time(label = 'default') {
time(this._times, kTraceConsoleCategory, 'console.time()', kNone, label, `time::${label}`);
},
timeEnd(label = 'default') {
timeEnd(this._times, kTraceConsoleCategory, 'console.timeEnd()', kNone, (label, formatted, args) => timeLogImpl(this, label, formatted, args), label, `time::${label}`);
},
timeLog(label = 'default', ...data) {
timeLog(this._times, kTraceConsoleCategory, 'console.timeLog()', kNone, (label, formatted, args) => timeLogImpl(this, label, formatted, args), label, `time::${label}`, data);
},
trace: function trace(...args) {
const err = {
name: 'Trace',
message: this[kFormatForStderr](args),
};
ErrorCaptureStackTrace(err, trace);
this.error(err.stack);
},
// Defined by: https://console.spec.whatwg.org/#assert
assert(expression, ...args) {
if (!expression) {
if (args.length && typeof args[0] === 'string') {
args[0] = `Assertion failed: ${args[0]}`;
} else {
ArrayPrototypeUnshift(args, 'Assertion failed');
}
// The arguments will be formatted in warn() again
ReflectApply(this.warn, this, args);
}
},
// Defined by: https://console.spec.whatwg.org/#clear
clear() {
// It only makes sense to clear if _stdout is a TTY.
// Otherwise, do nothing.
if (this._stdout.isTTY && process.env.TERM !== 'dumb') {
// The require is here intentionally to avoid readline being
// required too early when console is first loaded.
const {
cursorTo,
clearScreenDown,
} = require('internal/readline/callbacks');
cursorTo(this._stdout, 0, 0);
clearScreenDown(this._stdout);
}
},
// Defined by: https://console.spec.whatwg.org/#count
count(label = 'default') {
// Ensures that label is a string, and only things that can be
// coerced to strings. e.g. Symbol is not allowed
label = `${label}`;
const counts = this[kCounts];
let count = counts.get(label);
if (count === undefined)
count = 1;
else
count++;
counts.set(label, count);
trace(kTraceCount, kTraceConsoleCategory, `count::${label}`, 0, count);
this.log(`${label}: ${count}`);
},
// Defined by: https://console.spec.whatwg.org/#countreset
countReset(label = 'default') {
const counts = this[kCounts];
if (!counts.has(label)) {
process.emitWarning(`Count for '${label}' does not exist`);
return;
}
trace(kTraceCount, kTraceConsoleCategory, `count::${label}`, 0, 0);
counts.delete(`${label}`);
},
group(...data) {
if (data.length > 0) {
ReflectApply(this.log, this, data);
}
let currentIndentation = internalIndentationMap.get(this) || '';
currentIndentation += StringPrototypeRepeat(' ', this[kGroupIndentationWidth]);
internalIndentationMap.set(this, currentIndentation);
},
groupEnd() {
const currentIndentation = internalIndentationMap.get(this) || '';
const newIndentation = StringPrototypeSlice(
currentIndentation,
0,
currentIndentation.length - this[kGroupIndentationWidth],
);
internalIndentationMap.set(this, newIndentation);
},
// https://console.spec.whatwg.org/#table
table(tabularData, properties) {
if (properties !== undefined)
validateArray(properties, 'properties');
if (tabularData === null || typeof tabularData !== 'object')
return this.log(tabularData);
cliTable ??= require('internal/cli_table');
const final = (k, v) => this.log(cliTable(k, v));
const _inspect = (v) => {
const depth = v !== null &&
typeof v === 'object' &&
!isArray(v) &&
ObjectKeys(v).length > 2 ? -1 : 0;
const opt = {
depth,
maxArrayLength: 3,
breakLength: Infinity,
...this[kGetInspectOptions](this._stdout),
};
return inspect(v, opt);
};
const getIndexArray = (length) => ArrayFrom(
{ length }, (_, i) => _inspect(i));
const mapIter = isMapIterator(tabularData);
let isKeyValue = false;
let i = 0;
if (mapIter) {
const res = previewEntries(tabularData, true);
tabularData = res[0];
isKeyValue = res[1];
}
if (isKeyValue || isMap(tabularData)) {
const keys = [];
const values = [];
let length = 0;
if (mapIter) {
for (; i < tabularData.length / 2; ++i) {
ArrayPrototypePush(keys, _inspect(tabularData[i * 2]));
ArrayPrototypePush(values, _inspect(tabularData[i * 2 + 1]));
length++;
}
} else {
for (const { 0: k, 1: v } of tabularData) {
ArrayPrototypePush(keys, _inspect(k));
ArrayPrototypePush(values, _inspect(v));
length++;
}
}
return final([
iterKey, keyKey, valuesKey,
], [
getIndexArray(length),
keys,
values,
]);
}
const setIter = isSetIterator(tabularData);
if (setIter)
tabularData = previewEntries(tabularData);
const setlike = setIter || mapIter || isSet(tabularData);
if (setlike) {
const values = [];
let length = 0;
for (const v of tabularData) {
ArrayPrototypePush(values, _inspect(v));
length++;
}
return final([iterKey, valuesKey], [getIndexArray(length), values]);
}
const map = { __proto__: null };
let hasPrimitives = false;
const valuesKeyArray = [];
const indexKeyArray = ObjectKeys(tabularData);
for (; i < indexKeyArray.length; i++) {
const item = tabularData[indexKeyArray[i]];
const primitive = item === null ||
(typeof item !== 'function' && typeof item !== 'object');
if (properties === undefined && primitive) {
hasPrimitives = true;
valuesKeyArray[i] = _inspect(item);
} else {
const keys = properties || ObjectKeys(item);
for (const key of keys) {
map[key] ??= [];
if ((primitive && properties) ||
!ObjectPrototypeHasOwnProperty(item, key))
map[key][i] = '';
else
map[key][i] = _inspect(item[key]);
}
}
}
const keys = ObjectKeys(map);
const values = ObjectValues(map);
if (hasPrimitives) {
ArrayPrototypePush(keys, valuesKey);
ArrayPrototypePush(values, valuesKeyArray);
}
ArrayPrototypeUnshift(keys, indexKey);
ArrayPrototypeUnshift(values, indexKeyArray);
return final(keys, values);
},
};
const keyKey = 'Key';
const valuesKey = 'Values';
const indexKey = '(index)';
const iterKey = '(iteration index)';
const isArray = (v) => ArrayIsArray(v) || isTypedArray(v) || isBuffer(v);
function noop() {}
for (const method of ReflectOwnKeys(consoleMethods))
Console.prototype[method] = consoleMethods[method];
Console.prototype.dirxml = Console.prototype.log;
Console.prototype.groupCollapsed = Console.prototype.group;
function initializeGlobalConsole(globalConsole) {
globalConsole[kBindStreamsLazy](process);
const {
namespace: {
addSerializeCallback,
isBuildingSnapshot,
},
} = require('internal/v8/startup_snapshot');
if (!internalBinding('config').hasInspector || !isBuildingSnapshot()) {
return;
}
const { console: consoleFromVM } = internalBinding('inspector');
const nodeConsoleKeys = ObjectKeys(Console.prototype);
const vmConsoleKeys = ObjectKeys(consoleFromVM);
const originalKeys = new SafeSet(vmConsoleKeys.concat(nodeConsoleKeys));
const inspectorConsoleKeys = new SafeSet();
for (const key of ObjectKeys(globalConsole)) {
if (!originalKeys.has(key)) {
inspectorConsoleKeys.add(key);
}
}
// During deserialization these should be reinstalled to console by
// V8 when the inspector client is created.
addSerializeCallback(() => {
for (const key of inspectorConsoleKeys) {
globalConsole[key] = undefined;
}
});
}
module.exports = {
Console,
kBindStreamsLazy,
kBindProperties,
initializeGlobalConsole,
};

View File

@ -0,0 +1,51 @@
'use strict';
// See https://console.spec.whatwg.org/#console-namespace
// > For historical web-compatibility reasons, the namespace object
// > for console must have as its [[Prototype]] an empty object,
// > created as if by ObjectCreate(%ObjectPrototype%),
// > instead of %ObjectPrototype%.
// Since in Node.js, the Console constructor has been exposed through
// require('console'), we need to keep the Console constructor but
// we cannot actually use `new Console` to construct the global console.
// Therefore, the console.Console.prototype is not
// in the global console prototype chain anymore.
const {
FunctionPrototypeBind,
ReflectDefineProperty,
ReflectGetOwnPropertyDescriptor,
ReflectOwnKeys,
} = primordials;
const {
Console,
kBindProperties,
} = require('internal/console/constructor');
const globalConsole = { __proto__: {} };
// Since Console is not on the prototype chain of the global console,
// the symbol properties on Console.prototype have to be looked up from
// the global console itself. In addition, we need to make the global
// console a namespace by binding the console methods directly onto
// the global console with the receiver fixed.
for (const prop of ReflectOwnKeys(Console.prototype)) {
if (prop === 'constructor') { continue; }
const desc = ReflectGetOwnPropertyDescriptor(Console.prototype, prop);
if (typeof desc.value === 'function') { // fix the receiver
const name = desc.value.name;
desc.value = FunctionPrototypeBind(desc.value, globalConsole);
ReflectDefineProperty(desc.value, 'name', { __proto__: null, value: name });
}
ReflectDefineProperty(globalConsole, prop, desc);
}
globalConsole[kBindProperties](true, 'auto');
// This is a legacy feature - the Console constructor is exposed on
// the global console instance.
globalConsole.Console = Console;
module.exports = globalConsole;

56
lib/internal/constants.js Normal file
View File

@ -0,0 +1,56 @@
'use strict';
const isWindows = process.platform === 'win32';
module.exports = {
// Alphabet chars.
CHAR_UPPERCASE_A: 65, /* A */
CHAR_LOWERCASE_A: 97, /* a */
CHAR_UPPERCASE_Z: 90, /* Z */
CHAR_LOWERCASE_Z: 122, /* z */
CHAR_UPPERCASE_C: 67, /* C */
CHAR_LOWERCASE_B: 98, /* b */
CHAR_LOWERCASE_E: 101, /* e */
CHAR_LOWERCASE_N: 110, /* n */
// Non-alphabetic chars.
CHAR_DOT: 46, /* . */
CHAR_FORWARD_SLASH: 47, /* / */
CHAR_BACKWARD_SLASH: 92, /* \ */
CHAR_VERTICAL_LINE: 124, /* | */
CHAR_COLON: 58, /* : */
CHAR_QUESTION_MARK: 63, /* ? */
CHAR_UNDERSCORE: 95, /* _ */
CHAR_LINE_FEED: 10, /* \n */
CHAR_CARRIAGE_RETURN: 13, /* \r */
CHAR_TAB: 9, /* \t */
CHAR_FORM_FEED: 12, /* \f */
CHAR_EXCLAMATION_MARK: 33, /* ! */
CHAR_HASH: 35, /* # */
CHAR_SPACE: 32, /* */
CHAR_NO_BREAK_SPACE: 160, /* \u00A0 */
CHAR_ZERO_WIDTH_NOBREAK_SPACE: 65279, /* \uFEFF */
CHAR_LEFT_SQUARE_BRACKET: 91, /* [ */
CHAR_RIGHT_SQUARE_BRACKET: 93, /* ] */
CHAR_LEFT_ANGLE_BRACKET: 60, /* < */
CHAR_RIGHT_ANGLE_BRACKET: 62, /* > */
CHAR_LEFT_CURLY_BRACKET: 123, /* { */
CHAR_RIGHT_CURLY_BRACKET: 125, /* } */
CHAR_HYPHEN_MINUS: 45, /* - */
CHAR_PLUS: 43, /* + */
CHAR_DOUBLE_QUOTE: 34, /* " */
CHAR_SINGLE_QUOTE: 39, /* ' */
CHAR_PERCENT: 37, /* % */
CHAR_SEMICOLON: 59, /* ; */
CHAR_CIRCUMFLEX_ACCENT: 94, /* ^ */
CHAR_GRAVE_ACCENT: 96, /* ` */
CHAR_AT: 64, /* @ */
CHAR_AMPERSAND: 38, /* & */
CHAR_EQUAL: 61, /* = */
// Digits
CHAR_0: 48, /* 0 */
CHAR_9: 57, /* 9 */
EOL: isWindows ? '\r\n' : '\n',
};

310
lib/internal/crypto/aes.js Normal file
View File

@ -0,0 +1,310 @@
'use strict';
const {
ArrayBufferIsView,
ArrayBufferPrototypeSlice,
ArrayFrom,
ArrayPrototypePush,
PromiseReject,
SafeSet,
TypedArrayPrototypeSlice,
} = primordials;
const {
AESCipherJob,
KeyObjectHandle,
kCryptoJobAsync,
kKeyVariantAES_CTR_128,
kKeyVariantAES_CBC_128,
kKeyVariantAES_GCM_128,
kKeyVariantAES_KW_128,
kKeyVariantAES_CTR_192,
kKeyVariantAES_CBC_192,
kKeyVariantAES_GCM_192,
kKeyVariantAES_KW_192,
kKeyVariantAES_CTR_256,
kKeyVariantAES_CBC_256,
kKeyVariantAES_GCM_256,
kKeyVariantAES_KW_256,
kWebCryptoCipherDecrypt,
kWebCryptoCipherEncrypt,
} = internalBinding('crypto');
const {
hasAnyNotIn,
jobPromise,
validateKeyOps,
kHandle,
kKeyObject,
} = require('internal/crypto/util');
const {
lazyDOMException,
promisify,
} = require('internal/util');
const {
InternalCryptoKey,
SecretKeyObject,
createSecretKey,
} = require('internal/crypto/keys');
const {
generateKey: _generateKey,
} = require('internal/crypto/keygen');
const generateKey = promisify(_generateKey);
function getAlgorithmName(name, length) {
switch (name) {
case 'AES-CBC': return `A${length}CBC`;
case 'AES-CTR': return `A${length}CTR`;
case 'AES-GCM': return `A${length}GCM`;
case 'AES-KW': return `A${length}KW`;
}
}
function validateKeyLength(length) {
if (length !== 128 && length !== 192 && length !== 256)
throw lazyDOMException('Invalid key length', 'DataError');
}
function getVariant(name, length) {
switch (name) {
case 'AES-CBC':
switch (length) {
case 128: return kKeyVariantAES_CBC_128;
case 192: return kKeyVariantAES_CBC_192;
case 256: return kKeyVariantAES_CBC_256;
}
break;
case 'AES-CTR':
switch (length) {
case 128: return kKeyVariantAES_CTR_128;
case 192: return kKeyVariantAES_CTR_192;
case 256: return kKeyVariantAES_CTR_256;
}
break;
case 'AES-GCM':
switch (length) {
case 128: return kKeyVariantAES_GCM_128;
case 192: return kKeyVariantAES_GCM_192;
case 256: return kKeyVariantAES_GCM_256;
}
break;
case 'AES-KW':
switch (length) {
case 128: return kKeyVariantAES_KW_128;
case 192: return kKeyVariantAES_KW_192;
case 256: return kKeyVariantAES_KW_256;
}
break;
}
}
function asyncAesCtrCipher(mode, key, data, algorithm) {
return jobPromise(() => new AESCipherJob(
kCryptoJobAsync,
mode,
key[kKeyObject][kHandle],
data,
getVariant('AES-CTR', key.algorithm.length),
algorithm.counter,
algorithm.length));
}
function asyncAesCbcCipher(mode, key, data, algorithm) {
return jobPromise(() => new AESCipherJob(
kCryptoJobAsync,
mode,
key[kKeyObject][kHandle],
data,
getVariant('AES-CBC', key.algorithm.length),
algorithm.iv));
}
function asyncAesKwCipher(mode, key, data) {
return jobPromise(() => new AESCipherJob(
kCryptoJobAsync,
mode,
key[kKeyObject][kHandle],
data,
getVariant('AES-KW', key.algorithm.length)));
}
function asyncAesGcmCipher(mode, key, data, algorithm) {
const { tagLength = 128 } = algorithm;
const tagByteLength = tagLength / 8;
let tag;
switch (mode) {
case kWebCryptoCipherDecrypt: {
const slice = ArrayBufferIsView(data) ?
TypedArrayPrototypeSlice : ArrayBufferPrototypeSlice;
tag = slice(data, -tagByteLength);
// Refs: https://www.w3.org/TR/WebCryptoAPI/#aes-gcm-operations
//
// > If *plaintext* has a length less than *tagLength* bits, then `throw`
// > an `OperationError`.
if (tagByteLength > tag.byteLength) {
return PromiseReject(lazyDOMException(
'The provided data is too small.',
'OperationError'));
}
data = slice(data, 0, -tagByteLength);
break;
}
case kWebCryptoCipherEncrypt:
tag = tagByteLength;
break;
}
return jobPromise(() => new AESCipherJob(
kCryptoJobAsync,
mode,
key[kKeyObject][kHandle],
data,
getVariant('AES-GCM', key.algorithm.length),
algorithm.iv,
tag,
algorithm.additionalData));
}
function aesCipher(mode, key, data, algorithm) {
switch (algorithm.name) {
case 'AES-CTR': return asyncAesCtrCipher(mode, key, data, algorithm);
case 'AES-CBC': return asyncAesCbcCipher(mode, key, data, algorithm);
case 'AES-GCM': return asyncAesGcmCipher(mode, key, data, algorithm);
case 'AES-KW': return asyncAesKwCipher(mode, key, data);
}
}
async function aesGenerateKey(algorithm, extractable, keyUsages) {
const { name, length } = algorithm;
const checkUsages = ['wrapKey', 'unwrapKey'];
if (name !== 'AES-KW')
ArrayPrototypePush(checkUsages, 'encrypt', 'decrypt');
const usagesSet = new SafeSet(keyUsages);
if (hasAnyNotIn(usagesSet, checkUsages)) {
throw lazyDOMException(
'Unsupported key usage for an AES key',
'SyntaxError');
}
const key = await generateKey('aes', { length }).catch((err) => {
throw lazyDOMException(
'The operation failed for an operation-specific reason' +
`[${err.message}]`,
{ name: 'OperationError', cause: err });
});
return new InternalCryptoKey(
key,
{ name, length },
ArrayFrom(usagesSet),
extractable);
}
function aesImportKey(
algorithm,
format,
keyData,
extractable,
keyUsages) {
const { name } = algorithm;
const checkUsages = ['wrapKey', 'unwrapKey'];
if (name !== 'AES-KW')
ArrayPrototypePush(checkUsages, 'encrypt', 'decrypt');
const usagesSet = new SafeSet(keyUsages);
if (hasAnyNotIn(usagesSet, checkUsages)) {
throw lazyDOMException(
'Unsupported key usage for an AES key',
'SyntaxError');
}
let keyObject;
let length;
switch (format) {
case 'KeyObject': {
validateKeyLength(keyData.symmetricKeySize * 8);
keyObject = keyData;
break;
}
case 'raw': {
validateKeyLength(keyData.byteLength * 8);
keyObject = createSecretKey(keyData);
break;
}
case 'jwk': {
if (!keyData.kty)
throw lazyDOMException('Invalid keyData', 'DataError');
if (keyData.kty !== 'oct')
throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');
if (usagesSet.size > 0 &&
keyData.use !== undefined &&
keyData.use !== 'enc') {
throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError');
}
validateKeyOps(keyData.key_ops, usagesSet);
if (keyData.ext !== undefined &&
keyData.ext === false &&
extractable === true) {
throw lazyDOMException(
'JWK "ext" Parameter and extractable mismatch',
'DataError');
}
const handle = new KeyObjectHandle();
try {
handle.initJwk(keyData);
} catch (err) {
throw lazyDOMException(
'Invalid keyData', { name: 'DataError', cause: err });
}
({ length } = handle.keyDetail({ }));
validateKeyLength(length);
if (keyData.alg !== undefined) {
if (keyData.alg !== getAlgorithmName(algorithm.name, length))
throw lazyDOMException(
'JWK "alg" does not match the requested algorithm',
'DataError');
}
keyObject = new SecretKeyObject(handle);
break;
}
default:
throw lazyDOMException(
`Unable to import AES key with format ${format}`,
'NotSupportedError');
}
if (length === undefined) {
({ length } = keyObject[kHandle].keyDetail({ }));
validateKeyLength(length);
}
return new InternalCryptoKey(
keyObject,
{ name, length },
keyUsages,
extractable);
}
module.exports = {
aesCipher,
aesGenerateKey,
aesImportKey,
getAlgorithmName,
};

View File

@ -0,0 +1,53 @@
'use strict';
const {
certExportChallenge,
certExportPublicKey,
certVerifySpkac,
} = internalBinding('crypto');
const {
getArrayBufferOrView,
} = require('internal/crypto/util');
// The functions contained in this file cover the SPKAC format
// (also referred to as Netscape SPKI). A general description of
// the format can be found at https://en.wikipedia.org/wiki/SPKAC
function verifySpkac(spkac, encoding) {
return certVerifySpkac(
getArrayBufferOrView(spkac, 'spkac', encoding));
}
function exportPublicKey(spkac, encoding) {
return certExportPublicKey(
getArrayBufferOrView(spkac, 'spkac', encoding));
}
function exportChallenge(spkac, encoding) {
return certExportChallenge(
getArrayBufferOrView(spkac, 'spkac', encoding));
}
// The legacy implementation of this exposed the Certificate
// object and required that users create an instance before
// calling the member methods. This API pattern has been
// deprecated, however, as the method implementations do not
// rely on any object state.
// For backwards compatibility reasons, this cannot be converted into a
// ES6 Class.
function Certificate() {
if (!(this instanceof Certificate))
return new Certificate();
}
Certificate.prototype.verifySpkac = verifySpkac;
Certificate.prototype.exportPublicKey = exportPublicKey;
Certificate.prototype.exportChallenge = exportChallenge;
Certificate.exportChallenge = exportChallenge;
Certificate.exportPublicKey = exportPublicKey;
Certificate.verifySpkac = verifySpkac;
module.exports = Certificate;

367
lib/internal/crypto/cfrg.js Normal file
View File

@ -0,0 +1,367 @@
'use strict';
const {
SafeSet,
} = primordials;
const { Buffer } = require('buffer');
const {
ECKeyExportJob,
KeyObjectHandle,
SignJob,
kCryptoJobAsync,
kKeyTypePrivate,
kKeyTypePublic,
kSignJobModeSign,
kSignJobModeVerify,
} = internalBinding('crypto');
const {
codes: {
ERR_CRYPTO_INVALID_JWK,
},
} = require('internal/errors');
const {
getUsagesUnion,
hasAnyNotIn,
jobPromise,
validateKeyOps,
kHandle,
kKeyObject,
} = require('internal/crypto/util');
const {
lazyDOMException,
promisify,
} = require('internal/util');
const {
generateKeyPair: _generateKeyPair,
} = require('internal/crypto/keygen');
const {
InternalCryptoKey,
PrivateKeyObject,
PublicKeyObject,
createPrivateKey,
createPublicKey,
} = require('internal/crypto/keys');
const generateKeyPair = promisify(_generateKeyPair);
function verifyAcceptableCfrgKeyUse(name, isPublic, usages) {
let checkSet;
switch (name) {
case 'X25519':
// Fall through
case 'X448':
checkSet = isPublic ? [] : ['deriveKey', 'deriveBits'];
break;
case 'Ed25519':
// Fall through
case 'Ed448':
checkSet = isPublic ? ['verify'] : ['sign'];
break;
default:
throw lazyDOMException(
'The algorithm is not supported', 'NotSupportedError');
}
if (hasAnyNotIn(usages, checkSet)) {
throw lazyDOMException(
`Unsupported key usage for a ${name} key`,
'SyntaxError');
}
}
function createCFRGRawKey(name, keyData, isPublic) {
const handle = new KeyObjectHandle();
switch (name) {
case 'Ed25519':
case 'X25519':
if (keyData.byteLength !== 32) {
throw lazyDOMException(
`${name} raw keys must be exactly 32-bytes`, 'DataError');
}
break;
case 'Ed448':
if (keyData.byteLength !== 57) {
throw lazyDOMException(
`${name} raw keys must be exactly 57-bytes`, 'DataError');
}
break;
case 'X448':
if (keyData.byteLength !== 56) {
throw lazyDOMException(
`${name} raw keys must be exactly 56-bytes`, 'DataError');
}
break;
}
const keyType = isPublic ? kKeyTypePublic : kKeyTypePrivate;
if (!handle.initEDRaw(name, keyData, keyType)) {
throw lazyDOMException('Invalid keyData', 'DataError');
}
return isPublic ? new PublicKeyObject(handle) : new PrivateKeyObject(handle);
}
async function cfrgGenerateKey(algorithm, extractable, keyUsages) {
const { name } = algorithm;
const usageSet = new SafeSet(keyUsages);
switch (name) {
case 'Ed25519':
// Fall through
case 'Ed448':
if (hasAnyNotIn(usageSet, ['sign', 'verify'])) {
throw lazyDOMException(
`Unsupported key usage for an ${name} key`,
'SyntaxError');
}
break;
case 'X25519':
// Fall through
case 'X448':
if (hasAnyNotIn(usageSet, ['deriveKey', 'deriveBits'])) {
throw lazyDOMException(
`Unsupported key usage for an ${name} key`,
'SyntaxError');
}
break;
}
let genKeyType;
switch (name) {
case 'Ed25519':
genKeyType = 'ed25519';
break;
case 'Ed448':
genKeyType = 'ed448';
break;
case 'X25519':
genKeyType = 'x25519';
break;
case 'X448':
genKeyType = 'x448';
break;
}
const keyPair = await generateKeyPair(genKeyType).catch((err) => {
throw lazyDOMException(
'The operation failed for an operation-specific reason',
{ name: 'OperationError', cause: err });
});
let publicUsages;
let privateUsages;
switch (name) {
case 'Ed25519':
// Fall through
case 'Ed448':
publicUsages = getUsagesUnion(usageSet, 'verify');
privateUsages = getUsagesUnion(usageSet, 'sign');
break;
case 'X25519':
// Fall through
case 'X448':
publicUsages = [];
privateUsages = getUsagesUnion(usageSet, 'deriveKey', 'deriveBits');
break;
}
const keyAlgorithm = { name };
const publicKey =
new InternalCryptoKey(
keyPair.publicKey,
keyAlgorithm,
publicUsages,
true);
const privateKey =
new InternalCryptoKey(
keyPair.privateKey,
keyAlgorithm,
privateUsages,
extractable);
return { __proto__: null, privateKey, publicKey };
}
function cfrgExportKey(key, format) {
return jobPromise(() => new ECKeyExportJob(
kCryptoJobAsync,
format,
key[kKeyObject][kHandle]));
}
function cfrgImportKey(
format,
keyData,
algorithm,
extractable,
keyUsages) {
const { name } = algorithm;
let keyObject;
const usagesSet = new SafeSet(keyUsages);
switch (format) {
case 'KeyObject': {
verifyAcceptableCfrgKeyUse(name, keyData.type === 'public', usagesSet);
keyObject = keyData;
break;
}
case 'spki': {
verifyAcceptableCfrgKeyUse(name, true, usagesSet);
try {
keyObject = createPublicKey({
key: keyData,
format: 'der',
type: 'spki',
});
} catch (err) {
throw lazyDOMException(
'Invalid keyData', { name: 'DataError', cause: err });
}
break;
}
case 'pkcs8': {
verifyAcceptableCfrgKeyUse(name, false, usagesSet);
try {
keyObject = createPrivateKey({
key: keyData,
format: 'der',
type: 'pkcs8',
});
} catch (err) {
throw lazyDOMException(
'Invalid keyData', { name: 'DataError', cause: err });
}
break;
}
case 'jwk': {
if (!keyData.kty)
throw lazyDOMException('Invalid keyData', 'DataError');
if (keyData.kty !== 'OKP')
throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');
if (keyData.crv !== name)
throw lazyDOMException(
'JWK "crv" Parameter and algorithm name mismatch', 'DataError');
const isPublic = keyData.d === undefined;
if (usagesSet.size > 0 && keyData.use !== undefined) {
let checkUse;
switch (name) {
case 'Ed25519':
// Fall through
case 'Ed448':
checkUse = 'sig';
break;
case 'X25519':
// Fall through
case 'X448':
checkUse = 'enc';
break;
}
if (keyData.use !== checkUse)
throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError');
}
validateKeyOps(keyData.key_ops, usagesSet);
if (keyData.ext !== undefined &&
keyData.ext === false &&
extractable === true) {
throw lazyDOMException(
'JWK "ext" Parameter and extractable mismatch',
'DataError');
}
if (keyData.alg !== undefined && (name === 'Ed25519' || name === 'Ed448')) {
if (keyData.alg !== name && keyData.alg !== 'EdDSA') {
throw lazyDOMException(
'JWK "alg" does not match the requested algorithm',
'DataError');
}
}
if (!isPublic && typeof keyData.x !== 'string') {
throw lazyDOMException('Invalid JWK', 'DataError');
}
verifyAcceptableCfrgKeyUse(
name,
isPublic,
usagesSet);
try {
const publicKeyObject = createCFRGRawKey(
name,
Buffer.from(keyData.x, 'base64'),
true);
if (isPublic) {
keyObject = publicKeyObject;
} else {
keyObject = createCFRGRawKey(
name,
Buffer.from(keyData.d, 'base64'),
false);
if (!createPublicKey(keyObject).equals(publicKeyObject)) {
throw new ERR_CRYPTO_INVALID_JWK();
}
}
} catch (err) {
throw lazyDOMException('Invalid keyData', { name: 'DataError', cause: err });
}
break;
}
case 'raw': {
verifyAcceptableCfrgKeyUse(name, true, usagesSet);
keyObject = createCFRGRawKey(name, keyData, true);
break;
}
}
if (keyObject.asymmetricKeyType !== name.toLowerCase()) {
throw lazyDOMException('Invalid key type', 'DataError');
}
return new InternalCryptoKey(
keyObject,
{ name },
keyUsages,
extractable);
}
function eddsaSignVerify(key, data, algorithm, signature) {
const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify;
const type = mode === kSignJobModeSign ? 'private' : 'public';
if (key.type !== type)
throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError');
return jobPromise(() => new SignJob(
kCryptoJobAsync,
mode,
key[kKeyObject][kHandle],
undefined,
undefined,
undefined,
data,
undefined,
undefined,
undefined,
undefined,
signature));
}
module.exports = {
cfrgExportKey,
cfrgImportKey,
cfrgGenerateKey,
eddsaSignVerify,
};

View File

@ -0,0 +1,289 @@
'use strict';
const {
ObjectSetPrototypeOf,
ReflectApply,
StringPrototypeToLowerCase,
} = primordials;
const {
CipherBase,
privateDecrypt: _privateDecrypt,
privateEncrypt: _privateEncrypt,
publicDecrypt: _publicDecrypt,
publicEncrypt: _publicEncrypt,
getCipherInfo: _getCipherInfo,
} = internalBinding('crypto');
const {
crypto: {
RSA_PKCS1_OAEP_PADDING,
RSA_PKCS1_PADDING,
},
} = internalBinding('constants');
const {
codes: {
ERR_CRYPTO_INVALID_STATE,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
ERR_UNKNOWN_ENCODING,
},
} = require('internal/errors');
const {
validateEncoding,
validateInt32,
validateObject,
validateString,
} = require('internal/validators');
const {
preparePrivateKey,
preparePublicOrPrivateKey,
prepareSecretKey,
} = require('internal/crypto/keys');
const {
getArrayBufferOrView,
getStringOption,
kHandle,
} = require('internal/crypto/util');
const {
isArrayBufferView,
} = require('internal/util/types');
const assert = require('internal/assert');
const LazyTransform = require('internal/streams/lazy_transform');
const { normalizeEncoding } = require('internal/util');
const { StringDecoder } = require('string_decoder');
function rsaFunctionFor(method, defaultPadding, keyType) {
return (options, buffer) => {
const { format, type, data, passphrase } =
keyType === 'private' ?
preparePrivateKey(options) :
preparePublicOrPrivateKey(options);
const padding = options.padding || defaultPadding;
const { oaepHash, encoding } = options;
let { oaepLabel } = options;
if (oaepHash !== undefined)
validateString(oaepHash, 'key.oaepHash');
if (oaepLabel !== undefined)
oaepLabel = getArrayBufferOrView(oaepLabel, 'key.oaepLabel', encoding);
buffer = getArrayBufferOrView(buffer, 'buffer', encoding);
return method(data, format, type, passphrase, buffer, padding, oaepHash,
oaepLabel);
};
}
const publicEncrypt = rsaFunctionFor(_publicEncrypt, RSA_PKCS1_OAEP_PADDING,
'public');
const publicDecrypt = rsaFunctionFor(_publicDecrypt, RSA_PKCS1_PADDING,
'public');
const privateEncrypt = rsaFunctionFor(_privateEncrypt, RSA_PKCS1_PADDING,
'private');
const privateDecrypt = rsaFunctionFor(_privateDecrypt, RSA_PKCS1_OAEP_PADDING,
'private');
function getDecoder(decoder, encoding) {
const normalizedEncoding = normalizeEncoding(encoding);
decoder ||= new StringDecoder(encoding);
if (decoder.encoding !== normalizedEncoding) {
if (normalizedEncoding === undefined) {
throw new ERR_UNKNOWN_ENCODING(encoding);
}
assert(false, 'Cannot change encoding');
}
return decoder;
}
function getUIntOption(options, key) {
let value;
if (options && (value = options[key]) != null) {
if (value >>> 0 !== value)
throw new ERR_INVALID_ARG_VALUE(`options.${key}`, value);
return value;
}
return -1;
}
function createCipherBase(cipher, credential, options, isEncrypt, iv) {
const authTagLength = getUIntOption(options, 'authTagLength');
this[kHandle] = new CipherBase(isEncrypt, cipher, credential, iv, authTagLength);
this._decoder = null;
ReflectApply(LazyTransform, this, [options]);
}
function createCipherWithIV(cipher, key, options, isEncrypt, iv) {
validateString(cipher, 'cipher');
const encoding = getStringOption(options, 'encoding');
key = prepareSecretKey(key, encoding);
iv = iv === null ? null : getArrayBufferOrView(iv, 'iv');
ReflectApply(createCipherBase, this, [cipher, key, options, isEncrypt, iv]);
}
// The Cipher class is part of the legacy Node.js crypto API. It exposes
// a stream-based encryption/decryption model. For backwards compatibility
// the Cipher class is defined using the legacy function syntax rather than
// ES6 classes.
function _transform(chunk, encoding, callback) {
this.push(this[kHandle].update(chunk, encoding));
callback();
};
function _flush(callback) {
try {
this.push(this[kHandle].final());
} catch (e) {
callback(e);
return;
}
callback();
};
function update(data, inputEncoding, outputEncoding) {
if (typeof data === 'string') {
validateEncoding(data, inputEncoding);
} else if (!isArrayBufferView(data)) {
throw new ERR_INVALID_ARG_TYPE(
'data', ['string', 'Buffer', 'TypedArray', 'DataView'], data);
}
const ret = this[kHandle].update(data, inputEncoding);
if (outputEncoding && outputEncoding !== 'buffer') {
this._decoder = getDecoder(this._decoder, outputEncoding);
return this._decoder.write(ret);
}
return ret;
};
function final(outputEncoding) {
const ret = this[kHandle].final();
if (outputEncoding && outputEncoding !== 'buffer') {
this._decoder = getDecoder(this._decoder, outputEncoding);
return this._decoder.end(ret);
}
return ret;
};
function setAutoPadding(ap) {
if (!this[kHandle].setAutoPadding(!!ap))
throw new ERR_CRYPTO_INVALID_STATE('setAutoPadding');
return this;
};
function getAuthTag() {
const ret = this[kHandle].getAuthTag();
if (ret === undefined)
throw new ERR_CRYPTO_INVALID_STATE('getAuthTag');
return ret;
};
function setAuthTag(tagbuf, encoding) {
tagbuf = getArrayBufferOrView(tagbuf, 'buffer', encoding);
if (!this[kHandle].setAuthTag(tagbuf))
throw new ERR_CRYPTO_INVALID_STATE('setAuthTag');
return this;
}
function setAAD(aadbuf, options) {
const encoding = getStringOption(options, 'encoding');
const plaintextLength = getUIntOption(options, 'plaintextLength');
aadbuf = getArrayBufferOrView(aadbuf, 'aadbuf', encoding);
if (!this[kHandle].setAAD(aadbuf, plaintextLength))
throw new ERR_CRYPTO_INVALID_STATE('setAAD');
return this;
};
// The Cipheriv class is part of the legacy Node.js crypto API. It exposes
// a stream-based encryption/decryption model. For backwards compatibility
// the Cipheriv class is defined using the legacy function syntax rather than
// ES6 classes.
function Cipheriv(cipher, key, iv, options) {
if (!(this instanceof Cipheriv))
return new Cipheriv(cipher, key, iv, options);
ReflectApply(createCipherWithIV, this, [cipher, key, options, true, iv]);
}
function addCipherPrototypeFunctions(constructor) {
constructor.prototype._transform = _transform;
constructor.prototype._flush = _flush;
constructor.prototype.update = update;
constructor.prototype.final = final;
constructor.prototype.setAutoPadding = setAutoPadding;
if (constructor === Cipheriv) {
constructor.prototype.getAuthTag = getAuthTag;
} else {
constructor.prototype.setAuthTag = setAuthTag;
}
constructor.prototype.setAAD = setAAD;
}
ObjectSetPrototypeOf(Cipheriv.prototype, LazyTransform.prototype);
ObjectSetPrototypeOf(Cipheriv, LazyTransform);
addCipherPrototypeFunctions(Cipheriv);
// The Decipheriv class is part of the legacy Node.js crypto API. It exposes
// a stream-based encryption/decryption model. For backwards compatibility
// the Decipheriv class is defined using the legacy function syntax rather than
// ES6 classes.
function Decipheriv(cipher, key, iv, options) {
if (!(this instanceof Decipheriv))
return new Decipheriv(cipher, key, iv, options);
ReflectApply(createCipherWithIV, this, [cipher, key, options, false, iv]);
}
ObjectSetPrototypeOf(Decipheriv.prototype, LazyTransform.prototype);
ObjectSetPrototypeOf(Decipheriv, LazyTransform);
addCipherPrototypeFunctions(Decipheriv);
function getCipherInfo(nameOrNid, options) {
if (typeof nameOrNid !== 'string' && typeof nameOrNid !== 'number') {
throw new ERR_INVALID_ARG_TYPE(
'nameOrNid',
['string', 'number'],
nameOrNid);
}
if (typeof nameOrNid === 'number')
validateInt32(nameOrNid, 'nameOrNid');
let keyLength, ivLength;
if (options !== undefined) {
validateObject(options, 'options');
({ keyLength, ivLength } = options);
if (keyLength !== undefined)
validateInt32(keyLength, 'options.keyLength');
if (ivLength !== undefined)
validateInt32(ivLength, 'options.ivLength');
}
const ret = _getCipherInfo({}, nameOrNid, keyLength, ivLength);
if (ret !== undefined) {
ret.name &&= StringPrototypeToLowerCase(ret.name);
ret.type &&= StringPrototypeToLowerCase(ret.type);
}
return ret;
}
module.exports = {
Cipheriv,
Decipheriv,
privateDecrypt,
privateEncrypt,
publicDecrypt,
publicEncrypt,
getCipherInfo,
};

View File

@ -0,0 +1,384 @@
'use strict';
const {
ArrayBufferPrototypeSlice,
FunctionPrototypeCall,
MathCeil,
ObjectDefineProperty,
SafeSet,
Uint8Array,
} = primordials;
const { Buffer } = require('buffer');
const {
DHBitsJob,
DiffieHellman: _DiffieHellman,
DiffieHellmanGroup: _DiffieHellmanGroup,
ECDH: _ECDH,
ECDHBitsJob,
ECDHConvertKey: _ECDHConvertKey,
kCryptoJobAsync,
kCryptoJobSync,
} = internalBinding('crypto');
const {
codes: {
ERR_CRYPTO_ECDH_INVALID_FORMAT,
ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY,
ERR_CRYPTO_INCOMPATIBLE_KEY,
ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
},
} = require('internal/errors');
const {
validateFunction,
validateInt32,
validateObject,
validateString,
} = require('internal/validators');
const {
isArrayBufferView,
isAnyArrayBuffer,
} = require('internal/util/types');
const {
lazyDOMException,
} = require('internal/util');
const {
KeyObject,
} = require('internal/crypto/keys');
const {
getArrayBufferOrView,
jobPromise,
toBuf,
kHandle,
kKeyObject,
} = require('internal/crypto/util');
const {
crypto: {
POINT_CONVERSION_COMPRESSED,
POINT_CONVERSION_HYBRID,
POINT_CONVERSION_UNCOMPRESSED,
},
} = internalBinding('constants');
const DH_GENERATOR = 2;
function DiffieHellman(sizeOrKey, keyEncoding, generator, genEncoding) {
if (!(this instanceof DiffieHellman))
return new DiffieHellman(sizeOrKey, keyEncoding, generator, genEncoding);
if (typeof sizeOrKey !== 'number' &&
typeof sizeOrKey !== 'string' &&
!isArrayBufferView(sizeOrKey) &&
!isAnyArrayBuffer(sizeOrKey)) {
throw new ERR_INVALID_ARG_TYPE(
'sizeOrKey',
['number', 'string', 'ArrayBuffer', 'Buffer', 'TypedArray', 'DataView'],
sizeOrKey,
);
}
// Sizes < 0 don't make sense but they _are_ accepted (and subsequently
// rejected with ERR_OSSL_BN_BITS_TOO_SMALL) by OpenSSL. The glue code
// in node_crypto.cc accepts values that are IsInt32() for that reason
// and that's why we do that here too.
if (typeof sizeOrKey === 'number')
validateInt32(sizeOrKey, 'sizeOrKey');
if (keyEncoding && !Buffer.isEncoding(keyEncoding) &&
keyEncoding !== 'buffer') {
genEncoding = generator;
generator = keyEncoding;
keyEncoding = false;
}
if (typeof sizeOrKey !== 'number')
sizeOrKey = toBuf(sizeOrKey, keyEncoding);
if (!generator) {
generator = DH_GENERATOR;
} else if (typeof generator === 'number') {
validateInt32(generator, 'generator');
} else if (typeof generator === 'string') {
generator = toBuf(generator, genEncoding);
} else if (!isArrayBufferView(generator) && !isAnyArrayBuffer(generator)) {
throw new ERR_INVALID_ARG_TYPE(
'generator',
['number', 'string', 'ArrayBuffer', 'Buffer', 'TypedArray', 'DataView'],
generator,
);
}
this[kHandle] = new _DiffieHellman(sizeOrKey, generator);
ObjectDefineProperty(this, 'verifyError', {
__proto__: null,
enumerable: true,
value: this[kHandle].verifyError,
writable: false,
});
}
function DiffieHellmanGroup(name) {
if (!(this instanceof DiffieHellmanGroup))
return new DiffieHellmanGroup(name);
this[kHandle] = new _DiffieHellmanGroup(name);
ObjectDefineProperty(this, 'verifyError', {
__proto__: null,
enumerable: true,
value: this[kHandle].verifyError,
writable: false,
});
}
DiffieHellmanGroup.prototype.generateKeys =
DiffieHellman.prototype.generateKeys =
dhGenerateKeys;
function dhGenerateKeys(encoding) {
const keys = this[kHandle].generateKeys();
return encode(keys, encoding);
}
DiffieHellmanGroup.prototype.computeSecret =
DiffieHellman.prototype.computeSecret =
dhComputeSecret;
function dhComputeSecret(key, inEnc, outEnc) {
key = getArrayBufferOrView(key, 'key', inEnc);
const ret = this[kHandle].computeSecret(key);
if (typeof ret === 'string')
throw new ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY();
return encode(ret, outEnc);
}
DiffieHellmanGroup.prototype.getPrime =
DiffieHellman.prototype.getPrime =
dhGetPrime;
function dhGetPrime(encoding) {
const prime = this[kHandle].getPrime();
return encode(prime, encoding);
}
DiffieHellmanGroup.prototype.getGenerator =
DiffieHellman.prototype.getGenerator =
dhGetGenerator;
function dhGetGenerator(encoding) {
const generator = this[kHandle].getGenerator();
return encode(generator, encoding);
}
DiffieHellmanGroup.prototype.getPublicKey =
DiffieHellman.prototype.getPublicKey =
dhGetPublicKey;
function dhGetPublicKey(encoding) {
const key = this[kHandle].getPublicKey();
return encode(key, encoding);
}
DiffieHellmanGroup.prototype.getPrivateKey =
DiffieHellman.prototype.getPrivateKey =
dhGetPrivateKey;
function dhGetPrivateKey(encoding) {
const key = this[kHandle].getPrivateKey();
return encode(key, encoding);
}
DiffieHellman.prototype.setPublicKey = function setPublicKey(key, encoding) {
key = getArrayBufferOrView(key, 'key', encoding);
this[kHandle].setPublicKey(key);
return this;
};
DiffieHellman.prototype.setPrivateKey = function setPrivateKey(key, encoding) {
key = getArrayBufferOrView(key, 'key', encoding);
this[kHandle].setPrivateKey(key);
return this;
};
function ECDH(curve) {
if (!(this instanceof ECDH))
return new ECDH(curve);
validateString(curve, 'curve');
this[kHandle] = new _ECDH(curve);
}
ECDH.prototype.computeSecret = DiffieHellman.prototype.computeSecret;
ECDH.prototype.setPrivateKey = DiffieHellman.prototype.setPrivateKey;
ECDH.prototype.setPublicKey = DiffieHellman.prototype.setPublicKey;
ECDH.prototype.getPrivateKey = DiffieHellman.prototype.getPrivateKey;
ECDH.prototype.generateKeys = function generateKeys(encoding, format) {
this[kHandle].generateKeys();
return this.getPublicKey(encoding, format);
};
ECDH.prototype.getPublicKey = function getPublicKey(encoding, format) {
const f = getFormat(format);
const key = this[kHandle].getPublicKey(f);
return encode(key, encoding);
};
ECDH.convertKey = function convertKey(key, curve, inEnc, outEnc, format) {
validateString(curve, 'curve');
key = getArrayBufferOrView(key, 'key', inEnc);
const f = getFormat(format);
const convertedKey = _ECDHConvertKey(key, curve, f);
return encode(convertedKey, outEnc);
};
function encode(buffer, encoding) {
if (encoding && encoding !== 'buffer')
buffer = buffer.toString(encoding);
return buffer;
}
function getFormat(format) {
if (format) {
if (format === 'compressed')
return POINT_CONVERSION_COMPRESSED;
if (format === 'hybrid')
return POINT_CONVERSION_HYBRID;
if (format !== 'uncompressed')
throw new ERR_CRYPTO_ECDH_INVALID_FORMAT(format);
}
return POINT_CONVERSION_UNCOMPRESSED;
}
const dhEnabledKeyTypes = new SafeSet(['dh', 'ec', 'x448', 'x25519']);
function diffieHellman(options, callback) {
validateObject(options, 'options');
if (callback !== undefined)
validateFunction(callback, 'callback');
const { privateKey, publicKey } = options;
if (!(privateKey instanceof KeyObject))
throw new ERR_INVALID_ARG_VALUE('options.privateKey', privateKey);
if (!(publicKey instanceof KeyObject))
throw new ERR_INVALID_ARG_VALUE('options.publicKey', publicKey);
if (privateKey.type !== 'private')
throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(privateKey.type, 'private');
if (publicKey.type !== 'public' && publicKey.type !== 'private') {
throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(publicKey.type,
'private or public');
}
const privateType = privateKey.asymmetricKeyType;
const publicType = publicKey.asymmetricKeyType;
if (privateType !== publicType || !dhEnabledKeyTypes.has(privateType)) {
throw new ERR_CRYPTO_INCOMPATIBLE_KEY('key types for Diffie-Hellman',
`${privateType} and ${publicType}`);
}
const job = new DHBitsJob(
callback ? kCryptoJobAsync : kCryptoJobSync,
publicKey[kHandle],
privateKey[kHandle]);
if (!callback) {
const { 0: err, 1: secret } = job.run();
if (err !== undefined)
throw err;
return Buffer.from(secret);
}
job.ondone = (error, secret) => {
if (error) return FunctionPrototypeCall(callback, job, error);
FunctionPrototypeCall(callback, job, null, Buffer.from(secret));
};
job.run();
}
let masks;
// The ecdhDeriveBits function is part of the Web Crypto API and serves both
// deriveKeys and deriveBits functions.
async function ecdhDeriveBits(algorithm, baseKey, length) {
const { 'public': key } = algorithm;
if (baseKey.type !== 'private') {
throw lazyDOMException(
'baseKey must be a private key', 'InvalidAccessError');
}
if (key.algorithm.name !== baseKey.algorithm.name) {
throw lazyDOMException(
'The public and private keys must be of the same type',
'InvalidAccessError');
}
if (
key.algorithm.name === 'ECDH' &&
key.algorithm.namedCurve !== baseKey.algorithm.namedCurve
) {
throw lazyDOMException('Named curve mismatch', 'InvalidAccessError');
}
const bits = await jobPromise(() => new ECDHBitsJob(
kCryptoJobAsync,
key[kKeyObject][kHandle],
baseKey[kKeyObject][kHandle]));
// If a length is not specified, return the full derived secret
if (length === null)
return bits;
// If the length is not a multiple of 8 the nearest ceiled
// multiple of 8 is sliced.
const sliceLength = MathCeil(length / 8);
const { byteLength } = bits;
// If the length is larger than the derived secret, throw.
if (byteLength < sliceLength)
throw lazyDOMException('derived bit length is too small', 'OperationError');
const slice = ArrayBufferPrototypeSlice(bits, 0, sliceLength);
const mod = length % 8;
if (mod === 0)
return slice;
// eslint-disable-next-line no-sparse-arrays
masks ||= [, 0b10000000, 0b11000000, 0b11100000, 0b11110000, 0b11111000, 0b11111100, 0b11111110];
const masked = new Uint8Array(slice);
masked[sliceLength - 1] = masked[sliceLength - 1] & masks[mod];
return masked.buffer;
}
module.exports = {
DiffieHellman,
DiffieHellmanGroup,
ECDH,
diffieHellman,
ecdhDeriveBits,
};

310
lib/internal/crypto/ec.js Normal file
View File

@ -0,0 +1,310 @@
'use strict';
const {
SafeSet,
} = primordials;
const {
ECKeyExportJob,
KeyObjectHandle,
SignJob,
kCryptoJobAsync,
kKeyTypePrivate,
kSignJobModeSign,
kSignJobModeVerify,
kSigEncP1363,
} = internalBinding('crypto');
const {
getUsagesUnion,
hasAnyNotIn,
jobPromise,
normalizeHashName,
validateKeyOps,
kHandle,
kKeyObject,
kNamedCurveAliases,
} = require('internal/crypto/util');
const {
lazyDOMException,
promisify,
} = require('internal/util');
const {
generateKeyPair: _generateKeyPair,
} = require('internal/crypto/keygen');
const {
InternalCryptoKey,
PrivateKeyObject,
PublicKeyObject,
createPrivateKey,
createPublicKey,
} = require('internal/crypto/keys');
const generateKeyPair = promisify(_generateKeyPair);
function verifyAcceptableEcKeyUse(name, isPublic, usages) {
let checkSet;
switch (name) {
case 'ECDH':
checkSet = isPublic ? [] : ['deriveKey', 'deriveBits'];
break;
case 'ECDSA':
checkSet = isPublic ? ['verify'] : ['sign'];
break;
default:
throw lazyDOMException(
'The algorithm is not supported', 'NotSupportedError');
}
if (hasAnyNotIn(usages, checkSet)) {
throw lazyDOMException(
`Unsupported key usage for a ${name} key`,
'SyntaxError');
}
}
function createECPublicKeyRaw(namedCurve, keyData) {
const handle = new KeyObjectHandle();
if (!handle.initECRaw(kNamedCurveAliases[namedCurve], keyData)) {
throw lazyDOMException('Invalid keyData', 'DataError');
}
return new PublicKeyObject(handle);
}
async function ecGenerateKey(algorithm, extractable, keyUsages) {
const { name, namedCurve } = algorithm;
const usageSet = new SafeSet(keyUsages);
switch (name) {
case 'ECDSA':
if (hasAnyNotIn(usageSet, ['sign', 'verify'])) {
throw lazyDOMException(
'Unsupported key usage for an ECDSA key',
'SyntaxError');
}
break;
case 'ECDH':
if (hasAnyNotIn(usageSet, ['deriveKey', 'deriveBits'])) {
throw lazyDOMException(
'Unsupported key usage for an ECDH key',
'SyntaxError');
}
// Fall through
}
const keypair = await generateKeyPair('ec', { namedCurve }).catch((err) => {
throw lazyDOMException(
'The operation failed for an operation-specific reason',
{ name: 'OperationError', cause: err });
});
let publicUsages;
let privateUsages;
switch (name) {
case 'ECDSA':
publicUsages = getUsagesUnion(usageSet, 'verify');
privateUsages = getUsagesUnion(usageSet, 'sign');
break;
case 'ECDH':
publicUsages = [];
privateUsages = getUsagesUnion(usageSet, 'deriveKey', 'deriveBits');
break;
}
const keyAlgorithm = { name, namedCurve };
const publicKey =
new InternalCryptoKey(
keypair.publicKey,
keyAlgorithm,
publicUsages,
true);
const privateKey =
new InternalCryptoKey(
keypair.privateKey,
keyAlgorithm,
privateUsages,
extractable);
return { __proto__: null, publicKey, privateKey };
}
function ecExportKey(key, format) {
return jobPromise(() => new ECKeyExportJob(
kCryptoJobAsync,
format,
key[kKeyObject][kHandle]));
}
function ecImportKey(
format,
keyData,
algorithm,
extractable,
keyUsages,
) {
const { name, namedCurve } = algorithm;
let keyObject;
const usagesSet = new SafeSet(keyUsages);
switch (format) {
case 'KeyObject': {
verifyAcceptableEcKeyUse(name, keyData.type === 'public', usagesSet);
keyObject = keyData;
break;
}
case 'spki': {
verifyAcceptableEcKeyUse(name, true, usagesSet);
try {
keyObject = createPublicKey({
key: keyData,
format: 'der',
type: 'spki',
});
} catch (err) {
throw lazyDOMException(
'Invalid keyData', { name: 'DataError', cause: err });
}
break;
}
case 'pkcs8': {
verifyAcceptableEcKeyUse(name, false, usagesSet);
try {
keyObject = createPrivateKey({
key: keyData,
format: 'der',
type: 'pkcs8',
});
} catch (err) {
throw lazyDOMException(
'Invalid keyData', { name: 'DataError', cause: err });
}
break;
}
case 'jwk': {
if (!keyData.kty)
throw lazyDOMException('Invalid keyData', 'DataError');
if (keyData.kty !== 'EC')
throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');
if (keyData.crv !== namedCurve)
throw lazyDOMException(
'JWK "crv" does not match the requested algorithm',
'DataError');
verifyAcceptableEcKeyUse(
name,
keyData.d === undefined,
usagesSet);
if (usagesSet.size > 0 && keyData.use !== undefined) {
const checkUse = name === 'ECDH' ? 'enc' : 'sig';
if (keyData.use !== checkUse)
throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError');
}
validateKeyOps(keyData.key_ops, usagesSet);
if (keyData.ext !== undefined &&
keyData.ext === false &&
extractable === true) {
throw lazyDOMException(
'JWK "ext" Parameter and extractable mismatch',
'DataError');
}
if (algorithm.name === 'ECDSA' && keyData.alg !== undefined) {
let algNamedCurve;
switch (keyData.alg) {
case 'ES256': algNamedCurve = 'P-256'; break;
case 'ES384': algNamedCurve = 'P-384'; break;
case 'ES512': algNamedCurve = 'P-521'; break;
}
if (algNamedCurve !== namedCurve)
throw lazyDOMException(
'JWK "alg" does not match the requested algorithm',
'DataError');
}
const handle = new KeyObjectHandle();
let type;
try {
type = handle.initJwk(keyData, namedCurve);
} catch (err) {
throw lazyDOMException(
'Invalid keyData', { name: 'DataError', cause: err });
}
if (type === undefined)
throw lazyDOMException('Invalid keyData', 'DataError');
keyObject = type === kKeyTypePrivate ?
new PrivateKeyObject(handle) :
new PublicKeyObject(handle);
break;
}
case 'raw': {
verifyAcceptableEcKeyUse(name, true, usagesSet);
keyObject = createECPublicKeyRaw(namedCurve, keyData);
break;
}
}
switch (algorithm.name) {
case 'ECDSA':
// Fall through
case 'ECDH':
if (keyObject.asymmetricKeyType !== 'ec')
throw lazyDOMException('Invalid key type', 'DataError');
break;
}
if (!keyObject[kHandle].checkEcKeyData()) {
throw lazyDOMException('Invalid keyData', 'DataError');
}
const {
namedCurve: checkNamedCurve,
} = keyObject[kHandle].keyDetail({});
if (kNamedCurveAliases[namedCurve] !== checkNamedCurve)
throw lazyDOMException('Named curve mismatch', 'DataError');
return new InternalCryptoKey(
keyObject,
{ name, namedCurve },
keyUsages,
extractable);
}
function ecdsaSignVerify(key, data, { name, hash }, signature) {
const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify;
const type = mode === kSignJobModeSign ? 'private' : 'public';
if (key.type !== type)
throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError');
const hashname = normalizeHashName(hash.name);
return jobPromise(() => new SignJob(
kCryptoJobAsync,
mode,
key[kKeyObject][kHandle],
undefined,
undefined,
undefined,
data,
hashname,
undefined, // Salt length, not used with ECDSA
undefined, // PSS Padding, not used with ECDSA
kSigEncP1363,
signature));
}
module.exports = {
ecExportKey,
ecImportKey,
ecGenerateKey,
ecdsaSignVerify,
};

225
lib/internal/crypto/hash.js Normal file
View File

@ -0,0 +1,225 @@
'use strict';
const {
ObjectSetPrototypeOf,
ReflectApply,
StringPrototypeToLowerCase,
Symbol,
} = primordials;
const {
Hash: _Hash,
HashJob,
Hmac: _Hmac,
kCryptoJobAsync,
oneShotDigest,
} = internalBinding('crypto');
const {
getStringOption,
jobPromise,
normalizeHashName,
validateMaxBufferLength,
kHandle,
getCachedHashId,
getHashCache,
} = require('internal/crypto/util');
const {
prepareSecretKey,
} = require('internal/crypto/keys');
const {
lazyDOMException,
normalizeEncoding,
encodingsMap,
} = require('internal/util');
const {
Buffer,
} = require('buffer');
const {
codes: {
ERR_CRYPTO_HASH_FINALIZED,
ERR_CRYPTO_HASH_UPDATE_FAILED,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
},
} = require('internal/errors');
const {
validateEncoding,
validateString,
validateUint32,
} = require('internal/validators');
const {
isArrayBufferView,
} = require('internal/util/types');
const LazyTransform = require('internal/streams/lazy_transform');
const kState = Symbol('kState');
const kFinalized = Symbol('kFinalized');
function Hash(algorithm, options) {
if (!new.target)
return new Hash(algorithm, options);
const isCopy = algorithm instanceof _Hash;
if (!isCopy)
validateString(algorithm, 'algorithm');
const xofLen = typeof options === 'object' && options !== null ?
options.outputLength : undefined;
if (xofLen !== undefined)
validateUint32(xofLen, 'options.outputLength');
// Lookup the cached ID from JS land because it's faster than decoding
// the string in C++ land.
const algorithmId = isCopy ? -1 : getCachedHashId(algorithm);
this[kHandle] = new _Hash(algorithm, xofLen, algorithmId, getHashCache());
this[kState] = {
[kFinalized]: false,
};
ReflectApply(LazyTransform, this, [options]);
}
ObjectSetPrototypeOf(Hash.prototype, LazyTransform.prototype);
ObjectSetPrototypeOf(Hash, LazyTransform);
Hash.prototype.copy = function copy(options) {
const state = this[kState];
if (state[kFinalized])
throw new ERR_CRYPTO_HASH_FINALIZED();
return new Hash(this[kHandle], options);
};
Hash.prototype._transform = function _transform(chunk, encoding, callback) {
this[kHandle].update(chunk, encoding);
callback();
};
Hash.prototype._flush = function _flush(callback) {
this.push(this[kHandle].digest());
callback();
};
Hash.prototype.update = function update(data, encoding) {
const state = this[kState];
if (state[kFinalized])
throw new ERR_CRYPTO_HASH_FINALIZED();
if (typeof data === 'string') {
validateEncoding(data, encoding);
} else if (!isArrayBufferView(data)) {
throw new ERR_INVALID_ARG_TYPE(
'data', ['string', 'Buffer', 'TypedArray', 'DataView'], data);
}
if (!this[kHandle].update(data, encoding))
throw new ERR_CRYPTO_HASH_UPDATE_FAILED();
return this;
};
Hash.prototype.digest = function digest(outputEncoding) {
const state = this[kState];
if (state[kFinalized])
throw new ERR_CRYPTO_HASH_FINALIZED();
// Explicit conversion of truthy values for backward compatibility.
const ret = this[kHandle].digest(outputEncoding && `${outputEncoding}`);
state[kFinalized] = true;
return ret;
};
function Hmac(hmac, key, options) {
if (!(this instanceof Hmac))
return new Hmac(hmac, key, options);
validateString(hmac, 'hmac');
const encoding = getStringOption(options, 'encoding');
key = prepareSecretKey(key, encoding);
this[kHandle] = new _Hmac();
this[kHandle].init(hmac, key);
this[kState] = {
[kFinalized]: false,
};
ReflectApply(LazyTransform, this, [options]);
}
ObjectSetPrototypeOf(Hmac.prototype, LazyTransform.prototype);
ObjectSetPrototypeOf(Hmac, LazyTransform);
Hmac.prototype.update = Hash.prototype.update;
Hmac.prototype.digest = function digest(outputEncoding) {
const state = this[kState];
if (state[kFinalized]) {
const buf = Buffer.from('');
if (outputEncoding && outputEncoding !== 'buffer')
return buf.toString(outputEncoding);
return buf;
}
// Explicit conversion of truthy values for backward compatibility.
const ret = this[kHandle].digest(outputEncoding && `${outputEncoding}`);
state[kFinalized] = true;
return ret;
};
Hmac.prototype._flush = Hash.prototype._flush;
Hmac.prototype._transform = Hash.prototype._transform;
// Implementation for WebCrypto subtle.digest()
async function asyncDigest(algorithm, data) {
validateMaxBufferLength(data, 'data');
switch (algorithm.name) {
case 'SHA-1':
// Fall through
case 'SHA-256':
// Fall through
case 'SHA-384':
// Fall through
case 'SHA-512':
return jobPromise(() => new HashJob(
kCryptoJobAsync,
normalizeHashName(algorithm.name),
data));
}
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
}
function hash(algorithm, input, outputEncoding = 'hex') {
validateString(algorithm, 'algorithm');
if (typeof input !== 'string' && !isArrayBufferView(input)) {
throw new ERR_INVALID_ARG_TYPE('input', ['Buffer', 'TypedArray', 'DataView', 'string'], input);
}
let normalized = outputEncoding;
// Fast case: if it's 'hex', we don't need to validate it further.
if (outputEncoding !== 'hex') {
validateString(outputEncoding, 'outputEncoding');
normalized = normalizeEncoding(outputEncoding);
// If the encoding is invalid, normalizeEncoding() returns undefined.
if (normalized === undefined) {
// normalizeEncoding() doesn't handle 'buffer'.
if (StringPrototypeToLowerCase(outputEncoding) === 'buffer') {
normalized = 'buffer';
} else {
throw new ERR_INVALID_ARG_VALUE('outputEncoding', outputEncoding);
}
}
}
return oneShotDigest(algorithm, getCachedHashId(algorithm), getHashCache(),
input, normalized, encodingsMap[normalized]);
}
module.exports = {
Hash,
Hmac,
asyncDigest,
hash,
};

View File

@ -0,0 +1,79 @@
'use strict';
const {
ObjectKeys,
} = primordials;
const kHashContextNode = 1;
const kHashContextWebCrypto = 2;
const kHashContextJwkRsa = 3;
const kHashContextJwkRsaPss = 4;
const kHashContextJwkRsaOaep = 5;
const kHashContextJwkHmac = 6;
// WebCrypto and JWK use a bunch of different names for the
// standard set of SHA-* digest algorithms... which is ... fun.
// Here we provide a utility for mapping between them in order
// make it easier in the code.
const kHashNames = {
sha1: {
[kHashContextNode]: 'sha1',
[kHashContextWebCrypto]: 'SHA-1',
[kHashContextJwkRsa]: 'RS1',
[kHashContextJwkRsaPss]: 'PS1',
[kHashContextJwkRsaOaep]: 'RSA-OAEP',
[kHashContextJwkHmac]: 'HS1',
},
sha256: {
[kHashContextNode]: 'sha256',
[kHashContextWebCrypto]: 'SHA-256',
[kHashContextJwkRsa]: 'RS256',
[kHashContextJwkRsaPss]: 'PS256',
[kHashContextJwkRsaOaep]: 'RSA-OAEP-256',
[kHashContextJwkHmac]: 'HS256',
},
sha384: {
[kHashContextNode]: 'sha384',
[kHashContextWebCrypto]: 'SHA-384',
[kHashContextJwkRsa]: 'RS384',
[kHashContextJwkRsaPss]: 'PS384',
[kHashContextJwkRsaOaep]: 'RSA-OAEP-384',
[kHashContextJwkHmac]: 'HS384',
},
sha512: {
[kHashContextNode]: 'sha512',
[kHashContextWebCrypto]: 'SHA-512',
[kHashContextJwkRsa]: 'RS512',
[kHashContextJwkRsaPss]: 'PS512',
[kHashContextJwkRsaOaep]: 'RSA-OAEP-512',
[kHashContextJwkHmac]: 'HS512',
},
};
{
// Index the aliases
const keys = ObjectKeys(kHashNames);
for (let n = 0; n < keys.length; n++) {
const contexts = ObjectKeys(kHashNames[keys[n]]);
for (let i = 0; i < contexts.length; i++) {
const alias = kHashNames[keys[n]][contexts[i]];
if (kHashNames[alias] === undefined)
kHashNames[alias] = kHashNames[keys[n]];
}
}
}
function normalizeHashName(name, context = kHashContextNode) {
return kHashNames[name]?.[context];
}
normalizeHashName.kContextNode = kHashContextNode;
normalizeHashName.kContextWebCrypto = kHashContextWebCrypto;
normalizeHashName.kContextJwkRsa = kHashContextJwkRsa;
normalizeHashName.kContextJwkRsaPss = kHashContextJwkRsaPss;
normalizeHashName.kContextJwkRsaOaep = kHashContextJwkRsaOaep;
normalizeHashName.kContextJwkHmac = kHashContextJwkHmac;
module.exports = normalizeHashName;

173
lib/internal/crypto/hkdf.js Normal file
View File

@ -0,0 +1,173 @@
'use strict';
const {
ArrayBuffer,
FunctionPrototypeCall,
} = primordials;
const {
HKDFJob,
kCryptoJobAsync,
kCryptoJobSync,
} = internalBinding('crypto');
const {
validateFunction,
validateInteger,
validateString,
} = require('internal/validators');
const { kMaxLength } = require('buffer');
const {
normalizeHashName,
toBuf,
validateByteSource,
kKeyObject,
} = require('internal/crypto/util');
const {
createSecretKey,
isKeyObject,
} = require('internal/crypto/keys');
const {
lazyDOMException,
promisify,
} = require('internal/util');
const {
isAnyArrayBuffer,
isArrayBufferView,
} = require('internal/util/types');
const {
codes: {
ERR_INVALID_ARG_TYPE,
ERR_OUT_OF_RANGE,
},
hideStackFrames,
} = require('internal/errors');
const validateParameters = hideStackFrames((hash, key, salt, info, length) => {
validateString.withoutStackTrace(hash, 'digest');
key = prepareKey(key);
salt = validateByteSource.withoutStackTrace(salt, 'salt');
info = validateByteSource.withoutStackTrace(info, 'info');
validateInteger.withoutStackTrace(length, 'length', 0, kMaxLength);
if (info.byteLength > 1024) {
throw new ERR_OUT_OF_RANGE.HideStackFramesError(
'info',
'must not contain more than 1024 bytes',
info.byteLength);
}
return {
hash,
key,
salt,
info,
length,
};
});
function prepareKey(key) {
if (isKeyObject(key))
return key;
if (isAnyArrayBuffer(key))
return createSecretKey(key);
key = toBuf(key);
if (!isArrayBufferView(key)) {
throw new ERR_INVALID_ARG_TYPE(
'ikm',
[
'string',
'SecretKeyObject',
'ArrayBuffer',
'TypedArray',
'DataView',
'Buffer',
],
key);
}
return createSecretKey(key);
}
function hkdf(hash, key, salt, info, length, callback) {
({
hash,
key,
salt,
info,
length,
} = validateParameters(hash, key, salt, info, length));
validateFunction(callback, 'callback');
const job = new HKDFJob(kCryptoJobAsync, hash, key, salt, info, length);
job.ondone = (error, bits) => {
if (error) return FunctionPrototypeCall(callback, job, error);
FunctionPrototypeCall(callback, job, null, bits);
};
job.run();
}
function hkdfSync(hash, key, salt, info, length) {
({
hash,
key,
salt,
info,
length,
} = validateParameters(hash, key, salt, info, length));
const job = new HKDFJob(kCryptoJobSync, hash, key, salt, info, length);
const { 0: err, 1: bits } = job.run();
if (err !== undefined)
throw err;
return bits;
}
const hkdfPromise = promisify(hkdf);
function validateHkdfDeriveBitsLength(length) {
if (length === null)
throw lazyDOMException('length cannot be null', 'OperationError');
if (length % 8) {
throw lazyDOMException(
'length must be a multiple of 8',
'OperationError');
}
}
async function hkdfDeriveBits(algorithm, baseKey, length) {
validateHkdfDeriveBitsLength(length);
const { hash, salt, info } = algorithm;
if (length === 0)
return new ArrayBuffer(0);
try {
return await hkdfPromise(
normalizeHashName(hash.name), baseKey[kKeyObject], salt, info, length / 8,
);
} catch (err) {
throw lazyDOMException(
'The operation failed for an operation-specific reason',
{ name: 'OperationError', cause: err });
}
}
module.exports = {
hkdf,
hkdfSync,
hkdfDeriveBits,
};

View File

@ -0,0 +1,405 @@
'use strict';
const {
FunctionPrototypeCall,
ObjectDefineProperty,
SafeArrayIterator,
} = primordials;
const {
DhKeyPairGenJob,
DsaKeyPairGenJob,
EcKeyPairGenJob,
NidKeyPairGenJob,
RsaKeyPairGenJob,
SecretKeyGenJob,
kCryptoJobAsync,
kCryptoJobSync,
kKeyVariantRSA_PSS,
kKeyVariantRSA_SSA_PKCS1_v1_5,
EVP_PKEY_ED25519,
EVP_PKEY_ED448,
EVP_PKEY_X25519,
EVP_PKEY_X448,
OPENSSL_EC_NAMED_CURVE,
OPENSSL_EC_EXPLICIT_CURVE,
} = internalBinding('crypto');
const {
PublicKeyObject,
PrivateKeyObject,
SecretKeyObject,
parsePublicKeyEncoding,
parsePrivateKeyEncoding,
} = require('internal/crypto/keys');
const {
customPromisifyArgs,
kEmptyObject,
} = require('internal/util');
const {
validateFunction,
validateBuffer,
validateString,
validateInteger,
validateObject,
validateOneOf,
validateInt32,
validateUint32,
} = require('internal/validators');
const {
codes: {
ERR_INCOMPATIBLE_OPTION_PAIR,
ERR_INVALID_ARG_VALUE,
ERR_MISSING_OPTION,
},
} = require('internal/errors');
const { isArrayBufferView } = require('internal/util/types');
function isJwk(obj) {
return obj != null && obj.kty !== undefined;
}
function wrapKey(key, ctor) {
if (typeof key === 'string' ||
isArrayBufferView(key) ||
isJwk(key))
return key;
return new ctor(key);
}
function generateKeyPair(type, options, callback) {
if (typeof options === 'function') {
callback = options;
options = undefined;
}
validateFunction(callback, 'callback');
const job = createJob(kCryptoJobAsync, type, options);
job.ondone = (error, result) => {
if (error) return FunctionPrototypeCall(callback, job, error);
// If no encoding was chosen, return key objects instead.
let { 0: pubkey, 1: privkey } = result;
pubkey = wrapKey(pubkey, PublicKeyObject);
privkey = wrapKey(privkey, PrivateKeyObject);
FunctionPrototypeCall(callback, job, null, pubkey, privkey);
};
job.run();
}
ObjectDefineProperty(generateKeyPair, customPromisifyArgs, {
__proto__: null,
value: ['publicKey', 'privateKey'],
enumerable: false,
});
function generateKeyPairSync(type, options) {
return handleError(createJob(kCryptoJobSync, type, options).run());
}
function handleError(ret) {
if (ret == null)
return; // async
const { 0: err, 1: keys } = ret;
if (err !== undefined)
throw err;
const { 0: publicKey, 1: privateKey } = keys;
// If no encoding was chosen, return key objects instead.
return {
publicKey: wrapKey(publicKey, PublicKeyObject),
privateKey: wrapKey(privateKey, PrivateKeyObject),
};
}
function parseKeyEncoding(keyType, options = kEmptyObject) {
const { publicKeyEncoding, privateKeyEncoding } = options;
let publicFormat, publicType;
if (publicKeyEncoding == null) {
publicFormat = publicType = undefined;
} else if (typeof publicKeyEncoding === 'object') {
({
format: publicFormat,
type: publicType,
} = parsePublicKeyEncoding(publicKeyEncoding, keyType,
'publicKeyEncoding'));
} else {
throw new ERR_INVALID_ARG_VALUE('options.publicKeyEncoding',
publicKeyEncoding);
}
let privateFormat, privateType, cipher, passphrase;
if (privateKeyEncoding == null) {
privateFormat = privateType = undefined;
} else if (typeof privateKeyEncoding === 'object') {
({
format: privateFormat,
type: privateType,
cipher,
passphrase,
} = parsePrivateKeyEncoding(privateKeyEncoding, keyType,
'privateKeyEncoding'));
} else {
throw new ERR_INVALID_ARG_VALUE('options.privateKeyEncoding',
privateKeyEncoding);
}
return [
publicFormat,
publicType,
privateFormat,
privateType,
cipher,
passphrase,
];
}
function createJob(mode, type, options) {
validateString(type, 'type');
const encoding = new SafeArrayIterator(parseKeyEncoding(type, options));
if (options !== undefined)
validateObject(options, 'options');
switch (type) {
case 'rsa':
case 'rsa-pss':
{
validateObject(options, 'options');
const { modulusLength } = options;
validateUint32(modulusLength, 'options.modulusLength');
let { publicExponent } = options;
if (publicExponent == null) {
publicExponent = 0x10001;
} else {
validateUint32(publicExponent, 'options.publicExponent');
}
if (type === 'rsa') {
return new RsaKeyPairGenJob(
mode,
kKeyVariantRSA_SSA_PKCS1_v1_5, // Used also for RSA-OAEP
modulusLength,
publicExponent,
...encoding);
}
const {
hash, mgf1Hash, hashAlgorithm, mgf1HashAlgorithm, saltLength,
} = options;
if (saltLength !== undefined)
validateInt32(saltLength, 'options.saltLength', 0);
if (hashAlgorithm !== undefined)
validateString(hashAlgorithm, 'options.hashAlgorithm');
if (mgf1HashAlgorithm !== undefined)
validateString(mgf1HashAlgorithm, 'options.mgf1HashAlgorithm');
if (hash !== undefined) {
process.emitWarning(
'"options.hash" is deprecated, ' +
'use "options.hashAlgorithm" instead.',
'DeprecationWarning',
'DEP0154');
validateString(hash, 'options.hash');
if (hashAlgorithm && hash !== hashAlgorithm) {
throw new ERR_INVALID_ARG_VALUE('options.hash', hash);
}
}
if (mgf1Hash !== undefined) {
process.emitWarning(
'"options.mgf1Hash" is deprecated, ' +
'use "options.mgf1HashAlgorithm" instead.',
'DeprecationWarning',
'DEP0154');
validateString(mgf1Hash, 'options.mgf1Hash');
if (mgf1HashAlgorithm && mgf1Hash !== mgf1HashAlgorithm) {
throw new ERR_INVALID_ARG_VALUE('options.mgf1Hash', mgf1Hash);
}
}
return new RsaKeyPairGenJob(
mode,
kKeyVariantRSA_PSS,
modulusLength,
publicExponent,
hashAlgorithm || hash,
mgf1HashAlgorithm || mgf1Hash,
saltLength,
...encoding);
}
case 'dsa':
{
validateObject(options, 'options');
const { modulusLength } = options;
validateUint32(modulusLength, 'options.modulusLength');
let { divisorLength } = options;
if (divisorLength == null) {
divisorLength = -1;
} else
validateInt32(divisorLength, 'options.divisorLength', 0);
return new DsaKeyPairGenJob(
mode,
modulusLength,
divisorLength,
...encoding);
}
case 'ec':
{
validateObject(options, 'options');
const { namedCurve } = options;
validateString(namedCurve, 'options.namedCurve');
let { paramEncoding } = options;
if (paramEncoding == null || paramEncoding === 'named')
paramEncoding = OPENSSL_EC_NAMED_CURVE;
else if (paramEncoding === 'explicit')
paramEncoding = OPENSSL_EC_EXPLICIT_CURVE;
else
throw new ERR_INVALID_ARG_VALUE('options.paramEncoding', paramEncoding);
return new EcKeyPairGenJob(
mode,
namedCurve,
paramEncoding,
...encoding);
}
case 'ed25519':
case 'ed448':
case 'x25519':
case 'x448':
{
let id;
switch (type) {
case 'ed25519':
id = EVP_PKEY_ED25519;
break;
case 'ed448':
id = EVP_PKEY_ED448;
break;
case 'x25519':
id = EVP_PKEY_X25519;
break;
case 'x448':
id = EVP_PKEY_X448;
break;
}
return new NidKeyPairGenJob(mode, id, ...encoding);
}
case 'dh':
{
validateObject(options, 'options');
const { group, primeLength, prime, generator } = options;
if (group != null) {
if (prime != null)
throw new ERR_INCOMPATIBLE_OPTION_PAIR('group', 'prime');
if (primeLength != null)
throw new ERR_INCOMPATIBLE_OPTION_PAIR('group', 'primeLength');
if (generator != null)
throw new ERR_INCOMPATIBLE_OPTION_PAIR('group', 'generator');
validateString(group, 'options.group');
return new DhKeyPairGenJob(mode, group, ...encoding);
}
if (prime != null) {
if (primeLength != null)
throw new ERR_INCOMPATIBLE_OPTION_PAIR('prime', 'primeLength');
validateBuffer(prime, 'options.prime');
} else if (primeLength != null) {
validateInt32(primeLength, 'options.primeLength', 0);
} else {
throw new ERR_MISSING_OPTION(
'At least one of the group, prime, or primeLength options');
}
if (generator != null) {
validateInt32(generator, 'options.generator', 0);
}
return new DhKeyPairGenJob(
mode,
prime != null ? prime : primeLength,
generator == null ? 2 : generator,
...encoding);
}
default:
// Fall through
}
throw new ERR_INVALID_ARG_VALUE('type', type, 'must be a supported key type');
}
// Symmetric Key Generation
function generateKeyJob(mode, keyType, options) {
validateString(keyType, 'type');
validateObject(options, 'options');
const { length } = options;
switch (keyType) {
case 'hmac':
validateInteger(length, 'options.length', 8, 2 ** 31 - 1);
break;
case 'aes':
validateOneOf(length, 'options.length', [128, 192, 256]);
break;
default:
throw new ERR_INVALID_ARG_VALUE(
'type',
keyType,
'must be a supported key type');
}
return new SecretKeyGenJob(mode, length);
}
function handleGenerateKeyError(ret) {
if (ret === undefined)
return; // async
const { 0: err, 1: key } = ret;
if (err !== undefined)
throw err;
return wrapKey(key, SecretKeyObject);
}
function generateKey(type, options, callback) {
if (typeof options === 'function') {
callback = options;
options = undefined;
}
validateFunction(callback, 'callback');
const job = generateKeyJob(kCryptoJobAsync, type, options);
job.ondone = (error, key) => {
if (error) return FunctionPrototypeCall(callback, job, error);
FunctionPrototypeCall(callback, job, null, wrapKey(key, SecretKeyObject));
};
handleGenerateKeyError(job.run());
}
function generateKeySync(type, options) {
return handleGenerateKeyError(
generateKeyJob(kCryptoJobSync, type, options).run());
}
module.exports = {
generateKeyPair,
generateKeyPairSync,
generateKey,
generateKeySync,
};

958
lib/internal/crypto/keys.js Normal file
View File

@ -0,0 +1,958 @@
'use strict';
const {
ArrayPrototypeSlice,
ObjectDefineProperties,
ObjectDefineProperty,
ObjectSetPrototypeOf,
SafeSet,
Symbol,
SymbolToStringTag,
Uint8Array,
} = primordials;
const {
KeyObjectHandle,
createNativeKeyObjectClass,
kKeyTypeSecret,
kKeyTypePublic,
kKeyTypePrivate,
kKeyFormatPEM,
kKeyFormatDER,
kKeyFormatJWK,
kKeyEncodingPKCS1,
kKeyEncodingPKCS8,
kKeyEncodingSPKI,
kKeyEncodingSEC1,
} = internalBinding('crypto');
const {
validateObject,
validateOneOf,
validateString,
} = require('internal/validators');
const {
codes: {
ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS,
ERR_CRYPTO_INVALID_JWK,
ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE,
ERR_ILLEGAL_CONSTRUCTOR,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
ERR_INVALID_THIS,
},
} = require('internal/errors');
const {
kHandle,
kKeyObject,
getArrayBufferOrView,
bigIntArrayToUnsignedBigInt,
normalizeAlgorithm,
hasAnyNotIn,
} = require('internal/crypto/util');
const {
isAnyArrayBuffer,
isArrayBufferView,
} = require('internal/util/types');
const {
markTransferMode,
kClone,
kDeserialize,
} = require('internal/worker/js_transferable');
const {
customInspectSymbol: kInspect,
kEnumerableProperty,
lazyDOMException,
} = require('internal/util');
const { inspect } = require('internal/util/inspect');
const { Buffer } = require('buffer');
const kAlgorithm = Symbol('kAlgorithm');
const kExtractable = Symbol('kExtractable');
const kKeyType = Symbol('kKeyType');
const kKeyUsages = Symbol('kKeyUsages');
// Key input contexts.
const kConsumePublic = 0;
const kConsumePrivate = 1;
const kCreatePublic = 2;
const kCreatePrivate = 3;
const encodingNames = [];
for (const m of [[kKeyEncodingPKCS1, 'pkcs1'], [kKeyEncodingPKCS8, 'pkcs8'],
[kKeyEncodingSPKI, 'spki'], [kKeyEncodingSEC1, 'sec1']])
encodingNames[m[0]] = m[1];
// Creating the KeyObject class is a little complicated due to inheritance
// and the fact that KeyObjects should be transferable between threads,
// which requires the KeyObject base class to be implemented in C++.
// The creation requires a callback to make sure that the NativeKeyObject
// base class cannot exist without the other KeyObject implementations.
const {
0: KeyObject,
1: SecretKeyObject,
2: PublicKeyObject,
3: PrivateKeyObject,
} = createNativeKeyObjectClass((NativeKeyObject) => {
// Publicly visible KeyObject class.
class KeyObject extends NativeKeyObject {
constructor(type, handle) {
if (type !== 'secret' && type !== 'public' && type !== 'private')
throw new ERR_INVALID_ARG_VALUE('type', type);
if (typeof handle !== 'object' || !(handle instanceof KeyObjectHandle))
throw new ERR_INVALID_ARG_TYPE('handle', 'object', handle);
super(handle);
this[kKeyType] = type;
ObjectDefineProperty(this, kHandle, {
__proto__: null,
value: handle,
enumerable: false,
configurable: false,
writable: false,
});
}
get type() {
return this[kKeyType];
}
static from(key) {
if (!isCryptoKey(key))
throw new ERR_INVALID_ARG_TYPE('key', 'CryptoKey', key);
return key[kKeyObject];
}
equals(otherKeyObject) {
if (!isKeyObject(otherKeyObject)) {
throw new ERR_INVALID_ARG_TYPE(
'otherKeyObject', 'KeyObject', otherKeyObject);
}
return otherKeyObject.type === this.type &&
this[kHandle].equals(otherKeyObject[kHandle]);
}
}
ObjectDefineProperties(KeyObject.prototype, {
[SymbolToStringTag]: {
__proto__: null,
configurable: true,
value: 'KeyObject',
},
});
let webidl;
class SecretKeyObject extends KeyObject {
constructor(handle) {
super('secret', handle);
}
get symmetricKeySize() {
return this[kHandle].getSymmetricKeySize();
}
export(options) {
if (options !== undefined) {
validateObject(options, 'options');
validateOneOf(
options.format, 'options.format', [undefined, 'buffer', 'jwk']);
if (options.format === 'jwk') {
return this[kHandle].exportJwk({}, false);
}
}
return this[kHandle].export();
}
toCryptoKey(algorithm, extractable, keyUsages) {
webidl ??= require('internal/crypto/webidl');
algorithm = normalizeAlgorithm(webidl.converters.AlgorithmIdentifier(algorithm), 'importKey');
extractable = webidl.converters.boolean(extractable);
keyUsages = webidl.converters['sequence<KeyUsage>'](keyUsages);
let result;
switch (algorithm.name) {
case 'HMAC':
result = require('internal/crypto/mac')
.hmacImportKey('KeyObject', this, algorithm, extractable, keyUsages);
break;
case 'AES-CTR':
// Fall through
case 'AES-CBC':
// Fall through
case 'AES-GCM':
// Fall through
case 'AES-KW':
result = require('internal/crypto/aes')
.aesImportKey(algorithm, 'KeyObject', this, extractable, keyUsages);
break;
case 'HKDF':
// Fall through
case 'PBKDF2':
result = importGenericSecretKey(
algorithm,
'KeyObject',
this,
extractable,
keyUsages);
break;
default:
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
}
if (result.usages.length === 0) {
throw lazyDOMException(
`Usages cannot be empty when importing a ${result.type} key.`,
'SyntaxError');
}
return result;
}
}
const kAsymmetricKeyType = Symbol('kAsymmetricKeyType');
const kAsymmetricKeyDetails = Symbol('kAsymmetricKeyDetails');
function normalizeKeyDetails(details = {}) {
if (details.publicExponent !== undefined) {
return {
...details,
publicExponent:
bigIntArrayToUnsignedBigInt(new Uint8Array(details.publicExponent)),
};
}
return details;
}
class AsymmetricKeyObject extends KeyObject {
// eslint-disable-next-line no-useless-constructor
constructor(type, handle) {
super(type, handle);
}
get asymmetricKeyType() {
return this[kAsymmetricKeyType] ||= this[kHandle].getAsymmetricKeyType();
}
get asymmetricKeyDetails() {
switch (this.asymmetricKeyType) {
case 'rsa':
case 'rsa-pss':
case 'dsa':
case 'ec':
return this[kAsymmetricKeyDetails] ||= normalizeKeyDetails(
this[kHandle].keyDetail({}),
);
default:
return {};
}
}
toCryptoKey(algorithm, extractable, keyUsages) {
webidl ??= require('internal/crypto/webidl');
algorithm = normalizeAlgorithm(webidl.converters.AlgorithmIdentifier(algorithm), 'importKey');
extractable = webidl.converters.boolean(extractable);
keyUsages = webidl.converters['sequence<KeyUsage>'](keyUsages);
let result;
switch (algorithm.name) {
case 'RSASSA-PKCS1-v1_5':
// Fall through
case 'RSA-PSS':
// Fall through
case 'RSA-OAEP':
result = require('internal/crypto/rsa')
.rsaImportKey('KeyObject', this, algorithm, extractable, keyUsages);
break;
case 'ECDSA':
// Fall through
case 'ECDH':
result = require('internal/crypto/ec')
.ecImportKey('KeyObject', this, algorithm, extractable, keyUsages);
break;
case 'Ed25519':
// Fall through
case 'Ed448':
// Fall through
case 'X25519':
// Fall through
case 'X448':
result = require('internal/crypto/cfrg')
.cfrgImportKey('KeyObject', this, algorithm, extractable, keyUsages);
break;
default:
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
}
if (result.type === 'private' && result.usages.length === 0) {
throw lazyDOMException(
`Usages cannot be empty when importing a ${result.type} key.`,
'SyntaxError');
}
return result;
}
}
class PublicKeyObject extends AsymmetricKeyObject {
constructor(handle) {
super('public', handle);
}
export(options) {
if (options && options.format === 'jwk') {
return this[kHandle].exportJwk({}, false);
}
const {
format,
type,
} = parsePublicKeyEncoding(options, this.asymmetricKeyType);
return this[kHandle].export(format, type);
}
}
class PrivateKeyObject extends AsymmetricKeyObject {
constructor(handle) {
super('private', handle);
}
export(options) {
if (options && options.format === 'jwk') {
if (options.passphrase !== undefined) {
throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
'jwk', 'does not support encryption');
}
return this[kHandle].exportJwk({}, false);
}
const {
format,
type,
cipher,
passphrase,
} = parsePrivateKeyEncoding(options, this.asymmetricKeyType);
return this[kHandle].export(format, type, cipher, passphrase);
}
}
return [KeyObject, SecretKeyObject, PublicKeyObject, PrivateKeyObject];
});
function parseKeyFormat(formatStr, defaultFormat, optionName) {
if (formatStr === undefined && defaultFormat !== undefined)
return defaultFormat;
else if (formatStr === 'pem')
return kKeyFormatPEM;
else if (formatStr === 'der')
return kKeyFormatDER;
else if (formatStr === 'jwk')
return kKeyFormatJWK;
throw new ERR_INVALID_ARG_VALUE(optionName, formatStr);
}
function parseKeyType(typeStr, required, keyType, isPublic, optionName) {
if (typeStr === undefined && !required) {
return undefined;
} else if (typeStr === 'pkcs1') {
if (keyType !== undefined && keyType !== 'rsa') {
throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
typeStr, 'can only be used for RSA keys');
}
return kKeyEncodingPKCS1;
} else if (typeStr === 'spki' && isPublic !== false) {
return kKeyEncodingSPKI;
} else if (typeStr === 'pkcs8' && isPublic !== true) {
return kKeyEncodingPKCS8;
} else if (typeStr === 'sec1' && isPublic !== true) {
if (keyType !== undefined && keyType !== 'ec') {
throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
typeStr, 'can only be used for EC keys');
}
return kKeyEncodingSEC1;
}
throw new ERR_INVALID_ARG_VALUE(optionName, typeStr);
}
function option(name, objName) {
return objName === undefined ?
`options.${name}` : `options.${objName}.${name}`;
}
function parseKeyFormatAndType(enc, keyType, isPublic, objName) {
const { format: formatStr, type: typeStr } = enc;
const isInput = keyType === undefined;
const format = parseKeyFormat(formatStr,
isInput ? kKeyFormatPEM : undefined,
option('format', objName));
const isRequired = (!isInput ||
format === kKeyFormatDER) &&
format !== kKeyFormatJWK;
const type = parseKeyType(typeStr,
isRequired,
keyType,
isPublic,
option('type', objName));
return { format, type };
}
function isStringOrBuffer(val) {
return typeof val === 'string' ||
isArrayBufferView(val) ||
isAnyArrayBuffer(val);
}
function parseKeyEncoding(enc, keyType, isPublic, objName) {
validateObject(enc, 'options');
const isInput = keyType === undefined;
const {
format,
type,
} = parseKeyFormatAndType(enc, keyType, isPublic, objName);
let cipher, passphrase, encoding;
if (isPublic !== true) {
({ cipher, passphrase, encoding } = enc);
if (!isInput) {
if (cipher != null) {
if (typeof cipher !== 'string')
throw new ERR_INVALID_ARG_VALUE(option('cipher', objName), cipher);
if (format === kKeyFormatDER &&
(type === kKeyEncodingPKCS1 ||
type === kKeyEncodingSEC1)) {
throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
encodingNames[type], 'does not support encryption');
}
} else if (passphrase !== undefined) {
throw new ERR_INVALID_ARG_VALUE(option('cipher', objName), cipher);
}
}
if ((isInput && passphrase !== undefined &&
!isStringOrBuffer(passphrase)) ||
(!isInput && cipher != null && !isStringOrBuffer(passphrase))) {
throw new ERR_INVALID_ARG_VALUE(option('passphrase', objName),
passphrase);
}
}
if (passphrase !== undefined)
passphrase = getArrayBufferOrView(passphrase, 'key.passphrase', encoding);
return { format, type, cipher, passphrase };
}
// Parses the public key encoding based on an object. keyType must be undefined
// when this is used to parse an input encoding and must be a valid key type if
// used to parse an output encoding.
function parsePublicKeyEncoding(enc, keyType, objName) {
return parseKeyEncoding(enc, keyType, keyType ? true : undefined, objName);
}
// Parses the private key encoding based on an object. keyType must be undefined
// when this is used to parse an input encoding and must be a valid key type if
// used to parse an output encoding.
function parsePrivateKeyEncoding(enc, keyType, objName) {
return parseKeyEncoding(enc, keyType, false, objName);
}
function getKeyObjectHandle(key, ctx) {
if (ctx === kCreatePrivate) {
throw new ERR_INVALID_ARG_TYPE(
'key',
['string', 'ArrayBuffer', 'Buffer', 'TypedArray', 'DataView'],
key,
);
}
if (key.type !== 'private') {
if (ctx === kConsumePrivate || ctx === kCreatePublic)
throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, 'private');
if (key.type !== 'public') {
throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type,
'private or public');
}
}
return key[kHandle];
}
function getKeyTypes(allowKeyObject, bufferOnly = false) {
const types = [
'ArrayBuffer',
'Buffer',
'TypedArray',
'DataView',
'string', // Only if bufferOnly == false
'KeyObject', // Only if allowKeyObject == true && bufferOnly == false
'CryptoKey', // Only if allowKeyObject == true && bufferOnly == false
];
if (bufferOnly) {
return ArrayPrototypeSlice(types, 0, 4);
} else if (!allowKeyObject) {
return ArrayPrototypeSlice(types, 0, 5);
}
return types;
}
function getKeyObjectHandleFromJwk(key, ctx) {
validateObject(key, 'key');
validateOneOf(
key.kty, 'key.kty', ['RSA', 'EC', 'OKP']);
const isPublic = ctx === kConsumePublic || ctx === kCreatePublic;
if (key.kty === 'OKP') {
validateString(key.crv, 'key.crv');
validateOneOf(
key.crv, 'key.crv', ['Ed25519', 'Ed448', 'X25519', 'X448']);
validateString(key.x, 'key.x');
if (!isPublic)
validateString(key.d, 'key.d');
let keyData;
if (isPublic)
keyData = Buffer.from(key.x, 'base64');
else
keyData = Buffer.from(key.d, 'base64');
switch (key.crv) {
case 'Ed25519':
case 'X25519':
if (keyData.byteLength !== 32) {
throw new ERR_CRYPTO_INVALID_JWK();
}
break;
case 'Ed448':
if (keyData.byteLength !== 57) {
throw new ERR_CRYPTO_INVALID_JWK();
}
break;
case 'X448':
if (keyData.byteLength !== 56) {
throw new ERR_CRYPTO_INVALID_JWK();
}
break;
}
const handle = new KeyObjectHandle();
const keyType = isPublic ? kKeyTypePublic : kKeyTypePrivate;
if (!handle.initEDRaw(key.crv, keyData, keyType)) {
throw new ERR_CRYPTO_INVALID_JWK();
}
return handle;
}
if (key.kty === 'EC') {
validateString(key.crv, 'key.crv');
validateOneOf(
key.crv, 'key.crv', ['P-256', 'secp256k1', 'P-384', 'P-521']);
validateString(key.x, 'key.x');
validateString(key.y, 'key.y');
const jwk = {
kty: key.kty,
crv: key.crv,
x: key.x,
y: key.y,
};
if (!isPublic) {
validateString(key.d, 'key.d');
jwk.d = key.d;
}
const handle = new KeyObjectHandle();
const type = handle.initJwk(jwk, jwk.crv);
if (type === undefined)
throw new ERR_CRYPTO_INVALID_JWK();
return handle;
}
// RSA
validateString(key.n, 'key.n');
validateString(key.e, 'key.e');
const jwk = {
kty: key.kty,
n: key.n,
e: key.e,
};
if (!isPublic) {
validateString(key.d, 'key.d');
validateString(key.p, 'key.p');
validateString(key.q, 'key.q');
validateString(key.dp, 'key.dp');
validateString(key.dq, 'key.dq');
validateString(key.qi, 'key.qi');
jwk.d = key.d;
jwk.p = key.p;
jwk.q = key.q;
jwk.dp = key.dp;
jwk.dq = key.dq;
jwk.qi = key.qi;
}
const handle = new KeyObjectHandle();
const type = handle.initJwk(jwk);
if (type === undefined)
throw new ERR_CRYPTO_INVALID_JWK();
return handle;
}
function prepareAsymmetricKey(key, ctx) {
if (isKeyObject(key)) {
// Best case: A key object, as simple as that.
return { data: getKeyObjectHandle(key, ctx) };
} else if (isCryptoKey(key)) {
return { data: getKeyObjectHandle(key[kKeyObject], ctx) };
} else if (isStringOrBuffer(key)) {
// Expect PEM by default, mostly for backward compatibility.
return { format: kKeyFormatPEM, data: getArrayBufferOrView(key, 'key') };
} else if (typeof key === 'object') {
const { key: data, encoding, format } = key;
// The 'key' property can be a KeyObject as well to allow specifying
// additional options such as padding along with the key.
if (isKeyObject(data))
return { data: getKeyObjectHandle(data, ctx) };
else if (isCryptoKey(data))
return { data: getKeyObjectHandle(data[kKeyObject], ctx) };
else if (format === 'jwk') {
validateObject(data, 'key.key');
return { data: getKeyObjectHandleFromJwk(data, ctx), format: 'jwk' };
}
// Either PEM or DER using PKCS#1 or SPKI.
if (!isStringOrBuffer(data)) {
throw new ERR_INVALID_ARG_TYPE(
'key.key',
getKeyTypes(ctx !== kCreatePrivate),
data);
}
const isPublic =
(ctx === kConsumePrivate || ctx === kCreatePrivate) ? false : undefined;
return {
data: getArrayBufferOrView(data, 'key', encoding),
...parseKeyEncoding(key, undefined, isPublic),
};
}
throw new ERR_INVALID_ARG_TYPE(
'key',
getKeyTypes(ctx !== kCreatePrivate),
key);
}
function preparePrivateKey(key) {
return prepareAsymmetricKey(key, kConsumePrivate);
}
function preparePublicOrPrivateKey(key) {
return prepareAsymmetricKey(key, kConsumePublic);
}
function prepareSecretKey(key, encoding, bufferOnly = false) {
if (!bufferOnly) {
if (isKeyObject(key)) {
if (key.type !== 'secret')
throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, 'secret');
return key[kHandle];
} else if (isCryptoKey(key)) {
if (key.type !== 'secret')
throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, 'secret');
return key[kKeyObject][kHandle];
}
}
if (typeof key !== 'string' &&
!isArrayBufferView(key) &&
!isAnyArrayBuffer(key)) {
throw new ERR_INVALID_ARG_TYPE(
'key',
getKeyTypes(!bufferOnly, bufferOnly),
key);
}
return getArrayBufferOrView(key, 'key', encoding);
}
function createSecretKey(key, encoding) {
key = prepareSecretKey(key, encoding, true);
const handle = new KeyObjectHandle();
handle.init(kKeyTypeSecret, key);
return new SecretKeyObject(handle);
}
function createPublicKey(key) {
const { format, type, data, passphrase } =
prepareAsymmetricKey(key, kCreatePublic);
let handle;
if (format === 'jwk') {
handle = data;
} else {
handle = new KeyObjectHandle();
handle.init(kKeyTypePublic, data, format, type, passphrase);
}
return new PublicKeyObject(handle);
}
function createPrivateKey(key) {
const { format, type, data, passphrase } =
prepareAsymmetricKey(key, kCreatePrivate);
let handle;
if (format === 'jwk') {
handle = data;
} else {
handle = new KeyObjectHandle();
handle.init(kKeyTypePrivate, data, format, type, passphrase);
}
return new PrivateKeyObject(handle);
}
function isKeyObject(obj) {
return obj != null && obj[kKeyType] !== undefined;
}
// Our implementation of CryptoKey is a simple wrapper around a KeyObject
// that adapts it to the standard interface.
// TODO(@jasnell): Embedder environments like electron may have issues
// here similar to other things like URL. A chromium provided CryptoKey
// will not be recognized as a Node.js CryptoKey, and vice versa. It
// would be fantastic if we could find a way of making those interop.
class CryptoKey {
constructor() {
throw new ERR_ILLEGAL_CONSTRUCTOR();
}
[kInspect](depth, options) {
if (depth < 0)
return this;
const opts = {
...options,
depth: options.depth == null ? null : options.depth - 1,
};
return `CryptoKey ${inspect({
type: this.type,
extractable: this.extractable,
algorithm: this.algorithm,
usages: this.usages,
}, opts)}`;
}
get type() {
if (!(this instanceof CryptoKey))
throw new ERR_INVALID_THIS('CryptoKey');
return this[kKeyObject].type;
}
get extractable() {
if (!(this instanceof CryptoKey))
throw new ERR_INVALID_THIS('CryptoKey');
return this[kExtractable];
}
get algorithm() {
if (!(this instanceof CryptoKey))
throw new ERR_INVALID_THIS('CryptoKey');
return this[kAlgorithm];
}
get usages() {
if (!(this instanceof CryptoKey))
throw new ERR_INVALID_THIS('CryptoKey');
return this[kKeyUsages];
}
}
ObjectDefineProperties(CryptoKey.prototype, {
type: kEnumerableProperty,
extractable: kEnumerableProperty,
algorithm: kEnumerableProperty,
usages: kEnumerableProperty,
[SymbolToStringTag]: {
__proto__: null,
configurable: true,
value: 'CryptoKey',
},
});
/**
* @param {InternalCryptoKey} key
* @param {KeyObject} keyObject
* @param {object} algorithm
* @param {boolean} extractable
* @param {Set<string>} keyUsages
*/
function defineCryptoKeyProperties(
key,
keyObject,
algorithm,
extractable,
keyUsages,
) {
// Using symbol properties here currently instead of private
// properties because (for now) the performance penalty of
// private fields is still too high.
ObjectDefineProperties(key, {
[kKeyObject]: {
__proto__: null,
value: keyObject,
enumerable: false,
configurable: false,
writable: false,
},
[kAlgorithm]: {
__proto__: null,
value: algorithm,
enumerable: false,
configurable: false,
writable: false,
},
[kExtractable]: {
__proto__: null,
value: extractable,
enumerable: false,
configurable: false,
writable: false,
},
[kKeyUsages]: {
__proto__: null,
value: keyUsages,
enumerable: false,
configurable: false,
writable: false,
},
});
}
// All internal code must use new InternalCryptoKey to create
// CryptoKey instances. The CryptoKey class is exposed to end
// user code but is not permitted to be constructed directly.
// Using markTransferMode also allows the CryptoKey to be
// cloned to Workers.
class InternalCryptoKey {
constructor(keyObject, algorithm, keyUsages, extractable) {
markTransferMode(this, true, false);
// When constructed during transfer the properties get assigned
// in the kDeserialize call.
if (keyObject) {
defineCryptoKeyProperties(
this,
keyObject,
algorithm,
extractable,
keyUsages,
);
}
}
[kClone]() {
const keyObject = this[kKeyObject];
const algorithm = this[kAlgorithm];
const extractable = this[kExtractable];
const usages = this[kKeyUsages];
return {
data: {
keyObject,
algorithm,
usages,
extractable,
},
deserializeInfo: 'internal/crypto/keys:InternalCryptoKey',
};
}
[kDeserialize]({ keyObject, algorithm, usages, extractable }) {
defineCryptoKeyProperties(this, keyObject, algorithm, extractable, usages);
}
}
InternalCryptoKey.prototype.constructor = CryptoKey;
ObjectSetPrototypeOf(InternalCryptoKey.prototype, CryptoKey.prototype);
function isCryptoKey(obj) {
return obj != null && obj[kKeyObject] !== undefined;
}
function importGenericSecretKey(
algorithm,
format,
keyData,
extractable,
keyUsages,
) {
const usagesSet = new SafeSet(keyUsages);
const { name } = algorithm;
if (extractable)
throw lazyDOMException(`${name} keys are not extractable`, 'SyntaxError');
if (hasAnyNotIn(usagesSet, ['deriveKey', 'deriveBits'])) {
throw lazyDOMException(
`Unsupported key usage for a ${name} key`,
'SyntaxError');
}
let keyObject;
switch (format) {
case 'KeyObject': {
keyObject = keyData;
break;
}
case 'raw': {
keyObject = createSecretKey(keyData);
break;
}
}
if (keyObject) {
return new InternalCryptoKey(keyObject, { name }, keyUsages, false);
}
throw lazyDOMException(
`Unable to import ${name} key with format ${format}`,
'NotSupportedError');
}
module.exports = {
// Public API.
createSecretKey,
createPublicKey,
createPrivateKey,
KeyObject,
CryptoKey,
InternalCryptoKey,
// These are designed for internal use only and should not be exposed.
parsePublicKeyEncoding,
parsePrivateKeyEncoding,
parseKeyEncoding,
preparePrivateKey,
preparePublicOrPrivateKey,
prepareSecretKey,
SecretKeyObject,
PublicKeyObject,
PrivateKeyObject,
isKeyObject,
isCryptoKey,
importGenericSecretKey,
};

184
lib/internal/crypto/mac.js Normal file
View File

@ -0,0 +1,184 @@
'use strict';
const {
ArrayFrom,
SafeSet,
} = primordials;
const {
HmacJob,
KeyObjectHandle,
kCryptoJobAsync,
kSignJobModeSign,
kSignJobModeVerify,
} = internalBinding('crypto');
const {
getBlockSize,
hasAnyNotIn,
jobPromise,
normalizeHashName,
validateKeyOps,
kHandle,
kKeyObject,
} = require('internal/crypto/util');
const {
lazyDOMException,
promisify,
} = require('internal/util');
const {
generateKey: _generateKey,
} = require('internal/crypto/keygen');
const {
InternalCryptoKey,
SecretKeyObject,
createSecretKey,
} = require('internal/crypto/keys');
const generateKey = promisify(_generateKey);
async function hmacGenerateKey(algorithm, extractable, keyUsages) {
const { hash, name } = algorithm;
let { length } = algorithm;
if (length === undefined)
length = getBlockSize(hash.name);
const usageSet = new SafeSet(keyUsages);
if (hasAnyNotIn(usageSet, ['sign', 'verify'])) {
throw lazyDOMException(
'Unsupported key usage for an HMAC key',
'SyntaxError');
}
const key = await generateKey('hmac', { length }).catch((err) => {
throw lazyDOMException(
'The operation failed for an operation-specific reason',
{ name: 'OperationError', cause: err });
});
return new InternalCryptoKey(
key,
{ name, length, hash: { name: hash.name } },
ArrayFrom(usageSet),
extractable);
}
function getAlgorithmName(hash) {
switch (hash) {
case 'SHA-1': // Fall through
case 'SHA-256': // Fall through
case 'SHA-384': // Fall through
case 'SHA-512': // Fall through
return `HS${hash.slice(4)}`;
default:
throw lazyDOMException('Unsupported digest algorithm', 'DataError');
}
}
function hmacImportKey(
format,
keyData,
algorithm,
extractable,
keyUsages,
) {
const usagesSet = new SafeSet(keyUsages);
if (hasAnyNotIn(usagesSet, ['sign', 'verify'])) {
throw lazyDOMException(
'Unsupported key usage for an HMAC key',
'SyntaxError');
}
let keyObject;
switch (format) {
case 'KeyObject': {
keyObject = keyData;
break;
}
case 'raw': {
keyObject = createSecretKey(keyData);
break;
}
case 'jwk': {
if (!keyData.kty)
throw lazyDOMException('Invalid keyData', 'DataError');
if (keyData.kty !== 'oct')
throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');
if (usagesSet.size > 0 &&
keyData.use !== undefined &&
keyData.use !== 'sig') {
throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError');
}
validateKeyOps(keyData.key_ops, usagesSet);
if (keyData.ext !== undefined &&
keyData.ext === false &&
extractable === true) {
throw lazyDOMException(
'JWK "ext" Parameter and extractable mismatch',
'DataError');
}
if (keyData.alg !== undefined) {
if (keyData.alg !== getAlgorithmName(algorithm.hash.name))
throw lazyDOMException(
'JWK "alg" does not match the requested algorithm',
'DataError');
}
const handle = new KeyObjectHandle();
try {
handle.initJwk(keyData);
} catch (err) {
throw lazyDOMException(
'Invalid keyData', { name: 'DataError', cause: err });
}
keyObject = new SecretKeyObject(handle);
break;
}
default:
throw lazyDOMException(`Unable to import HMAC key with format ${format}`);
}
const { length } = keyObject[kHandle].keyDetail({});
if (length === 0)
throw lazyDOMException('Zero-length key is not supported', 'DataError');
if (algorithm.length !== undefined &&
algorithm.length !== length) {
throw lazyDOMException('Invalid key length', 'DataError');
}
return new InternalCryptoKey(
keyObject, {
name: 'HMAC',
hash: algorithm.hash,
length,
},
keyUsages,
extractable);
}
function hmacSignVerify(key, data, algorithm, signature) {
const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify;
return jobPromise(() => new HmacJob(
kCryptoJobAsync,
mode,
normalizeHashName(key.algorithm.hash.name),
key[kKeyObject][kHandle],
data,
signature));
}
module.exports = {
hmacImportKey,
hmacGenerateKey,
hmacSignVerify,
};

View File

@ -0,0 +1,131 @@
'use strict';
const {
ArrayBuffer,
FunctionPrototypeCall,
} = primordials;
const { Buffer } = require('buffer');
const {
PBKDF2Job,
kCryptoJobAsync,
kCryptoJobSync,
} = internalBinding('crypto');
const {
validateFunction,
validateInt32,
validateString,
} = require('internal/validators');
const {
getArrayBufferOrView,
normalizeHashName,
kKeyObject,
} = require('internal/crypto/util');
const {
lazyDOMException,
promisify,
} = require('internal/util');
function pbkdf2(password, salt, iterations, keylen, digest, callback) {
if (typeof digest === 'function') {
callback = digest;
digest = undefined;
}
({ password, salt, iterations, keylen, digest } =
check(password, salt, iterations, keylen, digest));
validateFunction(callback, 'callback');
const job = new PBKDF2Job(
kCryptoJobAsync,
password,
salt,
iterations,
keylen,
digest);
job.ondone = (err, result) => {
if (err !== undefined)
return FunctionPrototypeCall(callback, job, err);
const buf = Buffer.from(result);
return FunctionPrototypeCall(callback, job, null, buf);
};
job.run();
}
function pbkdf2Sync(password, salt, iterations, keylen, digest) {
({ password, salt, iterations, keylen, digest } =
check(password, salt, iterations, keylen, digest));
const job = new PBKDF2Job(
kCryptoJobSync,
password,
salt,
iterations,
keylen,
digest);
const { 0: err, 1: result } = job.run();
if (err !== undefined)
throw err;
return Buffer.from(result);
}
function check(password, salt, iterations, keylen, digest) {
validateString(digest, 'digest');
password = getArrayBufferOrView(password, 'password');
salt = getArrayBufferOrView(salt, 'salt');
// OpenSSL uses a signed int to represent these values, so we are restricted
// to the 31-bit range here (which is plenty).
validateInt32(iterations, 'iterations', 1);
validateInt32(keylen, 'keylen', 0);
return { password, salt, iterations, keylen, digest };
}
const pbkdf2Promise = promisify(pbkdf2);
function validatePbkdf2DeriveBitsLength(length) {
if (length === null)
throw lazyDOMException('length cannot be null', 'OperationError');
if (length % 8) {
throw lazyDOMException(
'length must be a multiple of 8',
'OperationError');
}
}
async function pbkdf2DeriveBits(algorithm, baseKey, length) {
validatePbkdf2DeriveBitsLength(length);
const { iterations, hash, salt } = algorithm;
if (length === 0)
return new ArrayBuffer(0);
let result;
try {
result = await pbkdf2Promise(
baseKey[kKeyObject].export(), salt, iterations, length / 8, normalizeHashName(hash.name),
);
} catch (err) {
throw lazyDOMException(
'The operation failed for an operation-specific reason',
{ name: 'OperationError', cause: err });
}
return result.buffer;
}
module.exports = {
pbkdf2,
pbkdf2Sync,
pbkdf2DeriveBits,
};

View File

@ -0,0 +1,615 @@
'use strict';
const {
Array,
ArrayBufferPrototypeGetByteLength,
ArrayPrototypeForEach,
ArrayPrototypePush,
ArrayPrototypeShift,
ArrayPrototypeSplice,
BigInt,
BigIntPrototypeToString,
DataView,
DataViewPrototypeGetUint8,
FunctionPrototypeBind,
FunctionPrototypeCall,
MathMin,
NumberIsNaN,
NumberIsSafeInteger,
NumberPrototypeToString,
StringFromCharCodeApply,
StringPrototypePadStart,
} = primordials;
const {
RandomBytesJob,
RandomPrimeJob,
CheckPrimeJob,
kCryptoJobAsync,
kCryptoJobSync,
secureBuffer,
} = internalBinding('crypto');
const {
kEmptyObject,
lazyDOMException,
} = require('internal/util');
const { Buffer, kMaxLength } = require('buffer');
const {
codes: {
ERR_INVALID_ARG_TYPE,
ERR_OPERATION_FAILED,
ERR_OUT_OF_RANGE,
},
} = require('internal/errors');
const {
validateNumber,
validateBoolean,
validateFunction,
validateInt32,
validateObject,
} = require('internal/validators');
const {
isArrayBufferView,
isAnyArrayBuffer,
isTypedArray,
isFloat16Array,
isFloat32Array,
isFloat64Array,
} = require('internal/util/types');
const { FastBuffer } = require('internal/buffer');
const kMaxInt32 = 2 ** 31 - 1;
const kMaxPossibleLength = MathMin(kMaxLength, kMaxInt32);
function assertOffset(offset, elementSize, length) {
validateNumber(offset, 'offset');
offset *= elementSize;
const maxLength = MathMin(length, kMaxPossibleLength);
if (NumberIsNaN(offset) || offset > maxLength || offset < 0) {
throw new ERR_OUT_OF_RANGE('offset', `>= 0 && <= ${maxLength}`, offset);
}
return offset >>> 0; // Convert to uint32.
}
function assertSize(size, elementSize, offset, length) {
validateNumber(size, 'size');
size *= elementSize;
if (NumberIsNaN(size) || size > kMaxPossibleLength || size < 0) {
throw new ERR_OUT_OF_RANGE('size',
`>= 0 && <= ${kMaxPossibleLength}`, size);
}
if (size + offset > length) {
throw new ERR_OUT_OF_RANGE('size + offset', `<= ${length}`, size + offset);
}
return size >>> 0; // Convert to uint32.
}
function randomBytes(size, callback) {
size = assertSize(size, 1, 0, Infinity);
if (callback !== undefined) {
validateFunction(callback, 'callback');
}
const buf = new FastBuffer(size);
if (callback === undefined) {
randomFillSync(buf.buffer, 0, size);
return buf;
}
// Keep the callback as a regular function so this is propagated.
randomFill(buf.buffer, 0, size, function(error) {
if (error) return FunctionPrototypeCall(callback, this, error);
FunctionPrototypeCall(callback, this, null, buf);
});
}
function randomFillSync(buf, offset = 0, size) {
if (!isAnyArrayBuffer(buf) && !isArrayBufferView(buf)) {
throw new ERR_INVALID_ARG_TYPE(
'buf',
['ArrayBuffer', 'ArrayBufferView'],
buf);
}
const elementSize = buf.BYTES_PER_ELEMENT || 1;
offset = assertOffset(offset, elementSize, buf.byteLength);
if (size === undefined) {
size = buf.byteLength - offset;
} else {
size = assertSize(size, elementSize, offset, buf.byteLength);
}
if (size === 0)
return buf;
const job = new RandomBytesJob(
kCryptoJobSync,
buf,
offset,
size);
const err = job.run()[0];
if (err)
throw err;
return buf;
}
function randomFill(buf, offset, size, callback) {
if (!isAnyArrayBuffer(buf) && !isArrayBufferView(buf)) {
throw new ERR_INVALID_ARG_TYPE(
'buf',
['ArrayBuffer', 'ArrayBufferView'],
buf);
}
const elementSize = buf.BYTES_PER_ELEMENT || 1;
if (typeof offset === 'function') {
callback = offset;
offset = 0;
// Size is a length here, assertSize() call turns it into a number of bytes
size = buf.length;
} else if (typeof size === 'function') {
callback = size;
size = buf.length - offset;
} else {
validateFunction(callback, 'callback');
}
offset = assertOffset(offset, elementSize, buf.byteLength);
if (size === undefined) {
size = buf.byteLength - offset;
} else {
size = assertSize(size, elementSize, offset, buf.byteLength);
}
if (size === 0) {
callback(null, buf);
return;
}
const job = new RandomBytesJob(
kCryptoJobAsync,
buf,
offset,
size);
job.ondone = FunctionPrototypeBind(onJobDone, job, buf, callback);
job.run();
}
// Largest integer we can read from a buffer.
// e.g.: Buffer.from("ff".repeat(6), "hex").readUIntBE(0, 6);
const RAND_MAX = 0xFFFF_FFFF_FFFF;
// Cache random data to use in randomInt. The cache size must be evenly
// divisible by 6 because each attempt to obtain a random int uses 6 bytes.
const randomCache = new FastBuffer(6 * 1024);
let randomCacheOffset = randomCache.length;
let asyncCacheFillInProgress = false;
const asyncCachePendingTasks = [];
// Generates an integer in [min, max) range where min is inclusive and max is
// exclusive.
function randomInt(min, max, callback) {
// Detect optional min syntax
// randomInt(max)
// randomInt(max, callback)
const minNotSpecified = typeof max === 'undefined' ||
typeof max === 'function';
if (minNotSpecified) {
callback = max;
max = min;
min = 0;
}
const isSync = typeof callback === 'undefined';
if (!isSync) {
validateFunction(callback, 'callback');
}
if (!NumberIsSafeInteger(min)) {
throw new ERR_INVALID_ARG_TYPE('min', 'a safe integer', min);
}
if (!NumberIsSafeInteger(max)) {
throw new ERR_INVALID_ARG_TYPE('max', 'a safe integer', max);
}
if (max <= min) {
throw new ERR_OUT_OF_RANGE(
'max', `greater than the value of "min" (${min})`, max,
);
}
// First we generate a random int between [0..range)
const range = max - min;
if (!(range <= RAND_MAX)) {
throw new ERR_OUT_OF_RANGE(`max${minNotSpecified ? '' : ' - min'}`,
`<= ${RAND_MAX}`, range);
}
// For (x % range) to produce an unbiased value greater than or equal to 0 and
// less than range, x must be drawn randomly from the set of integers greater
// than or equal to 0 and less than randLimit.
const randLimit = RAND_MAX - (RAND_MAX % range);
// If we don't have a callback, or if there is still data in the cache, we can
// do this synchronously, which is super fast.
while (isSync || (randomCacheOffset < randomCache.length)) {
if (randomCacheOffset === randomCache.length) {
// This might block the thread for a bit, but we are in sync mode.
randomFillSync(randomCache);
randomCacheOffset = 0;
}
const x = randomCache.readUIntBE(randomCacheOffset, 6);
randomCacheOffset += 6;
if (x < randLimit) {
const n = (x % range) + min;
if (isSync) return n;
process.nextTick(callback, undefined, n);
return;
}
}
// At this point, we are in async mode with no data in the cache. We cannot
// simply refill the cache, because another async call to randomInt might
// already be doing that. Instead, queue this call for when the cache has
// been refilled.
ArrayPrototypePush(asyncCachePendingTasks, { min, max, callback });
asyncRefillRandomIntCache();
}
function asyncRefillRandomIntCache() {
if (asyncCacheFillInProgress)
return;
asyncCacheFillInProgress = true;
randomFill(randomCache, (err) => {
asyncCacheFillInProgress = false;
const tasks = asyncCachePendingTasks;
const errorReceiver = err && ArrayPrototypeShift(tasks);
if (!err)
randomCacheOffset = 0;
// Restart all pending tasks. If an error occurred, we only notify a single
// callback (errorReceiver) about it. This way, every async call to
// randomInt has a chance of being successful, and it avoids complex
// exception handling here.
ArrayPrototypeForEach(ArrayPrototypeSplice(tasks, 0), (task) => {
randomInt(task.min, task.max, task.callback);
});
// This is the only call that might throw, and is therefore done at the end.
if (errorReceiver)
errorReceiver.callback(err);
});
}
function onJobDone(buf, callback, error) {
if (error) return FunctionPrototypeCall(callback, this, error);
FunctionPrototypeCall(callback, this, null, buf);
}
// Really just the Web Crypto API alternative
// to require('crypto').randomFillSync() with an
// additional limitation that the input buffer is
// not allowed to exceed 65536 bytes, and can only
// be an integer-type TypedArray.
function getRandomValues(data) {
if (!isTypedArray(data) ||
isFloat16Array(data) ||
isFloat32Array(data) ||
isFloat64Array(data)) {
// Ordinarily this would be an ERR_INVALID_ARG_TYPE. However,
// the Web Crypto API and web platform tests expect this to
// be a DOMException with type TypeMismatchError.
throw lazyDOMException(
'The data argument must be an integer-type TypedArray',
'TypeMismatchError');
}
if (data.byteLength > 65536) {
throw lazyDOMException(
'The requested length exceeds 65,536 bytes',
'QuotaExceededError');
}
randomFillSync(data, 0);
return data;
}
// Implements an RFC 4122 version 4 random UUID.
// To improve performance, random data is generated in batches
// large enough to cover kBatchSize UUID's at a time. The uuidData
// buffer is reused. Each call to randomUUID() consumes 16 bytes
// from the buffer.
const kBatchSize = 128;
let uuidData;
let uuidNotBuffered;
let uuidBatch = 0;
let hexBytesCache;
function getHexBytes() {
if (hexBytesCache === undefined) {
hexBytesCache = new Array(256);
for (let i = 0; i < hexBytesCache.length; i++) {
const hex = NumberPrototypeToString(i, 16);
hexBytesCache[i] = StringPrototypePadStart(hex, 2, '0');
}
}
return hexBytesCache;
}
function serializeUUID(buf, offset = 0) {
const kHexBytes = getHexBytes();
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
return kHexBytes[buf[offset]] +
kHexBytes[buf[offset + 1]] +
kHexBytes[buf[offset + 2]] +
kHexBytes[buf[offset + 3]] +
'-' +
kHexBytes[buf[offset + 4]] +
kHexBytes[buf[offset + 5]] +
'-' +
kHexBytes[(buf[offset + 6] & 0x0f) | 0x40] +
kHexBytes[buf[offset + 7]] +
'-' +
kHexBytes[(buf[offset + 8] & 0x3f) | 0x80] +
kHexBytes[buf[offset + 9]] +
'-' +
kHexBytes[buf[offset + 10]] +
kHexBytes[buf[offset + 11]] +
kHexBytes[buf[offset + 12]] +
kHexBytes[buf[offset + 13]] +
kHexBytes[buf[offset + 14]] +
kHexBytes[buf[offset + 15]];
}
function getBufferedUUID() {
uuidData ??= secureBuffer(16 * kBatchSize);
if (uuidData === undefined)
throw new ERR_OPERATION_FAILED('Out of memory');
if (uuidBatch === 0) randomFillSync(uuidData);
uuidBatch = (uuidBatch + 1) % kBatchSize;
return serializeUUID(uuidData, uuidBatch * 16);
}
function getUnbufferedUUID() {
uuidNotBuffered ??= secureBuffer(16);
if (uuidNotBuffered === undefined)
throw new ERR_OPERATION_FAILED('Out of memory');
randomFillSync(uuidNotBuffered);
return serializeUUID(uuidNotBuffered);
}
function randomUUID(options) {
if (options !== undefined)
validateObject(options, 'options');
const {
disableEntropyCache = false,
} = options || kEmptyObject;
validateBoolean(disableEntropyCache, 'options.disableEntropyCache');
return disableEntropyCache ? getUnbufferedUUID() : getBufferedUUID();
}
function createRandomPrimeJob(type, size, options) {
validateObject(options, 'options');
const {
safe = false,
bigint = false,
} = options;
let {
add,
rem,
} = options;
validateBoolean(safe, 'options.safe');
validateBoolean(bigint, 'options.bigint');
if (add !== undefined) {
if (typeof add === 'bigint') {
add = unsignedBigIntToBuffer(add, 'options.add');
} else if (!isAnyArrayBuffer(add) && !isArrayBufferView(add)) {
throw new ERR_INVALID_ARG_TYPE(
'options.add',
[
'ArrayBuffer',
'TypedArray',
'Buffer',
'DataView',
'bigint',
],
add);
}
}
if (rem !== undefined) {
if (typeof rem === 'bigint') {
rem = unsignedBigIntToBuffer(rem, 'options.rem');
} else if (!isAnyArrayBuffer(rem) && !isArrayBufferView(rem)) {
throw new ERR_INVALID_ARG_TYPE(
'options.rem',
[
'ArrayBuffer',
'TypedArray',
'Buffer',
'DataView',
'bigint',
],
rem);
}
}
const job = new RandomPrimeJob(type, size, safe, add, rem);
job.result = bigint ? arrayBufferToUnsignedBigInt : (p) => p;
return job;
}
function generatePrime(size, options, callback) {
validateInt32(size, 'size', 1);
if (typeof options === 'function') {
callback = options;
options = kEmptyObject;
}
validateFunction(callback, 'callback');
const job = createRandomPrimeJob(kCryptoJobAsync, size, options);
job.ondone = (err, prime) => {
if (err) {
callback(err);
return;
}
callback(
undefined,
job.result(prime));
};
job.run();
}
function generatePrimeSync(size, options = kEmptyObject) {
validateInt32(size, 'size', 1);
const job = createRandomPrimeJob(kCryptoJobSync, size, options);
const { 0: err, 1: prime } = job.run();
if (err)
throw err;
return job.result(prime);
}
/**
* 48 is the ASCII code for '0', 97 is the ASCII code for 'a'.
* @param {number} number An integer between 0 and 15.
* @returns {number} corresponding to the ASCII code of the hex representation
* of the parameter.
*/
const numberToHexCharCode = (number) => (number < 10 ? 48 : 87) + number;
/**
* @param {ArrayBuffer} buf An ArrayBuffer.
* @return {bigint}
*/
function arrayBufferToUnsignedBigInt(buf) {
const length = ArrayBufferPrototypeGetByteLength(buf);
const chars = Array(length * 2);
const view = new DataView(buf);
for (let i = 0; i < length; i++) {
const val = DataViewPrototypeGetUint8(view, i);
chars[2 * i] = numberToHexCharCode(val >> 4);
chars[2 * i + 1] = numberToHexCharCode(val & 0xf);
}
return BigInt(`0x${StringFromCharCodeApply(chars)}`);
}
function unsignedBigIntToBuffer(bigint, name) {
if (bigint < 0) {
throw new ERR_OUT_OF_RANGE(name, '>= 0', bigint);
}
const hex = BigIntPrototypeToString(bigint, 16);
const padded = StringPrototypePadStart(hex, hex.length + (hex.length % 2), 0);
return Buffer.from(padded, 'hex');
}
function checkPrime(candidate, options = kEmptyObject, callback) {
if (typeof candidate === 'bigint')
candidate = unsignedBigIntToBuffer(candidate, 'candidate');
if (!isAnyArrayBuffer(candidate) && !isArrayBufferView(candidate)) {
throw new ERR_INVALID_ARG_TYPE(
'candidate',
[
'ArrayBuffer',
'TypedArray',
'Buffer',
'DataView',
'bigint',
],
candidate,
);
}
if (typeof options === 'function') {
callback = options;
options = kEmptyObject;
}
validateFunction(callback, 'callback');
validateObject(options, 'options');
const {
checks = 0,
} = options;
// The checks option is unsigned but must fit into a signed C int for OpenSSL.
validateInt32(checks, 'options.checks', 0);
const job = new CheckPrimeJob(kCryptoJobAsync, candidate, checks);
job.ondone = callback;
job.run();
}
function checkPrimeSync(candidate, options = kEmptyObject) {
if (typeof candidate === 'bigint')
candidate = unsignedBigIntToBuffer(candidate, 'candidate');
if (!isAnyArrayBuffer(candidate) && !isArrayBufferView(candidate)) {
throw new ERR_INVALID_ARG_TYPE(
'candidate',
[
'ArrayBuffer',
'TypedArray',
'Buffer',
'DataView',
'bigint',
],
candidate,
);
}
validateObject(options, 'options');
const {
checks = 0,
} = options;
// The checks option is unsigned but must fit into a signed C int for OpenSSL.
validateInt32(checks, 'options.checks', 0);
const job = new CheckPrimeJob(kCryptoJobSync, candidate, checks);
const { 0: err, 1: result } = job.run();
if (err)
throw err;
return result;
}
module.exports = {
checkPrime,
checkPrimeSync,
randomBytes,
randomFill,
randomFillSync,
randomInt,
getRandomValues,
randomUUID,
generatePrime,
generatePrimeSync,
};

369
lib/internal/crypto/rsa.js Normal file
View File

@ -0,0 +1,369 @@
'use strict';
const {
MathCeil,
SafeSet,
Uint8Array,
} = primordials;
const {
KeyObjectHandle,
RSACipherJob,
RSAKeyExportJob,
SignJob,
kCryptoJobAsync,
kSignJobModeSign,
kSignJobModeVerify,
kKeyVariantRSA_SSA_PKCS1_v1_5,
kKeyVariantRSA_PSS,
kKeyVariantRSA_OAEP,
kKeyTypePrivate,
kWebCryptoCipherEncrypt,
RSA_PKCS1_PSS_PADDING,
} = internalBinding('crypto');
const {
validateInt32,
} = require('internal/validators');
const {
bigIntArrayToUnsignedInt,
getDigestSizeInBytes,
getUsagesUnion,
hasAnyNotIn,
jobPromise,
normalizeHashName,
validateKeyOps,
validateMaxBufferLength,
kHandle,
kKeyObject,
} = require('internal/crypto/util');
const {
lazyDOMException,
promisify,
} = require('internal/util');
const {
InternalCryptoKey,
PrivateKeyObject,
PublicKeyObject,
createPublicKey,
createPrivateKey,
} = require('internal/crypto/keys');
const {
generateKeyPair: _generateKeyPair,
} = require('internal/crypto/keygen');
const kRsaVariants = {
'RSASSA-PKCS1-v1_5': kKeyVariantRSA_SSA_PKCS1_v1_5,
'RSA-PSS': kKeyVariantRSA_PSS,
'RSA-OAEP': kKeyVariantRSA_OAEP,
};
const generateKeyPair = promisify(_generateKeyPair);
function verifyAcceptableRsaKeyUse(name, isPublic, usages) {
let checkSet;
switch (name) {
case 'RSA-OAEP':
checkSet = isPublic ? ['encrypt', 'wrapKey'] : ['decrypt', 'unwrapKey'];
break;
case 'RSA-PSS':
// Fall through
case 'RSASSA-PKCS1-v1_5':
checkSet = isPublic ? ['verify'] : ['sign'];
break;
default:
throw lazyDOMException(
'The algorithm is not supported', 'NotSupportedError');
}
if (hasAnyNotIn(usages, checkSet)) {
throw lazyDOMException(
`Unsupported key usage for an ${name} key`,
'SyntaxError');
}
}
function validateRsaOaepAlgorithm(algorithm) {
if (algorithm.label !== undefined) {
validateMaxBufferLength(algorithm.label, 'algorithm.label');
}
}
function rsaOaepCipher(mode, key, data, algorithm) {
validateRsaOaepAlgorithm(algorithm);
const type = mode === kWebCryptoCipherEncrypt ? 'public' : 'private';
if (key.type !== type) {
throw lazyDOMException(
'The requested operation is not valid for the provided key',
'InvalidAccessError');
}
return jobPromise(() => new RSACipherJob(
kCryptoJobAsync,
mode,
key[kKeyObject][kHandle],
data,
kKeyVariantRSA_OAEP,
normalizeHashName(key.algorithm.hash.name),
algorithm.label));
}
async function rsaKeyGenerate(
algorithm,
extractable,
keyUsages,
) {
const publicExponentConverted = bigIntArrayToUnsignedInt(algorithm.publicExponent);
if (publicExponentConverted === undefined) {
throw lazyDOMException(
'The publicExponent must be equivalent to an unsigned 32-bit value',
'OperationError');
}
const {
name,
modulusLength,
publicExponent,
hash,
} = algorithm;
const usageSet = new SafeSet(keyUsages);
switch (name) {
case 'RSA-OAEP':
if (hasAnyNotIn(usageSet,
['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'])) {
throw lazyDOMException(
'Unsupported key usage for a RSA key',
'SyntaxError');
}
break;
default:
if (hasAnyNotIn(usageSet, ['sign', 'verify'])) {
throw lazyDOMException(
'Unsupported key usage for a RSA key',
'SyntaxError');
}
}
const keypair = await generateKeyPair('rsa', {
modulusLength,
publicExponent: publicExponentConverted,
}).catch((err) => {
throw lazyDOMException(
'The operation failed for an operation-specific reason',
{ name: 'OperationError', cause: err });
});
const keyAlgorithm = {
name,
modulusLength,
publicExponent,
hash: { name: hash.name },
};
let publicUsages;
let privateUsages;
switch (name) {
case 'RSA-OAEP': {
publicUsages = getUsagesUnion(usageSet, 'encrypt', 'wrapKey');
privateUsages = getUsagesUnion(usageSet, 'decrypt', 'unwrapKey');
break;
}
default: {
publicUsages = getUsagesUnion(usageSet, 'verify');
privateUsages = getUsagesUnion(usageSet, 'sign');
break;
}
}
const publicKey =
new InternalCryptoKey(
keypair.publicKey,
keyAlgorithm,
publicUsages,
true);
const privateKey =
new InternalCryptoKey(
keypair.privateKey,
keyAlgorithm,
privateUsages,
extractable);
return { __proto__: null, publicKey, privateKey };
}
function rsaExportKey(key, format) {
return jobPromise(() => new RSAKeyExportJob(
kCryptoJobAsync,
format,
key[kKeyObject][kHandle],
kRsaVariants[key.algorithm.name]));
}
function rsaImportKey(
format,
keyData,
algorithm,
extractable,
keyUsages) {
const usagesSet = new SafeSet(keyUsages);
let keyObject;
switch (format) {
case 'KeyObject': {
verifyAcceptableRsaKeyUse(algorithm.name, keyData.type === 'public', usagesSet);
keyObject = keyData;
break;
}
case 'spki': {
verifyAcceptableRsaKeyUse(algorithm.name, true, usagesSet);
try {
keyObject = createPublicKey({
key: keyData,
format: 'der',
type: 'spki',
});
} catch (err) {
throw lazyDOMException(
'Invalid keyData', { name: 'DataError', cause: err });
}
break;
}
case 'pkcs8': {
verifyAcceptableRsaKeyUse(algorithm.name, false, usagesSet);
try {
keyObject = createPrivateKey({
key: keyData,
format: 'der',
type: 'pkcs8',
});
} catch (err) {
throw lazyDOMException(
'Invalid keyData', { name: 'DataError', cause: err });
}
break;
}
case 'jwk': {
if (!keyData.kty)
throw lazyDOMException('Invalid keyData', 'DataError');
if (keyData.kty !== 'RSA')
throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');
verifyAcceptableRsaKeyUse(
algorithm.name,
keyData.d === undefined,
usagesSet);
if (usagesSet.size > 0 && keyData.use !== undefined) {
const checkUse = algorithm.name === 'RSA-OAEP' ? 'enc' : 'sig';
if (keyData.use !== checkUse)
throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError');
}
validateKeyOps(keyData.key_ops, usagesSet);
if (keyData.ext !== undefined &&
keyData.ext === false &&
extractable === true) {
throw lazyDOMException(
'JWK "ext" Parameter and extractable mismatch',
'DataError');
}
if (keyData.alg !== undefined) {
const expected =
normalizeHashName(algorithm.hash.name,
algorithm.name === 'RSASSA-PKCS1-v1_5' ? normalizeHashName.kContextJwkRsa :
algorithm.name === 'RSA-PSS' ? normalizeHashName.kContextJwkRsaPss :
normalizeHashName.kContextJwkRsaOaep);
if (keyData.alg !== expected)
throw lazyDOMException(
'JWK "alg" does not match the requested algorithm',
'DataError');
}
const handle = new KeyObjectHandle();
let type;
try {
type = handle.initJwk(keyData);
} catch (err) {
throw lazyDOMException(
'Invalid keyData', { name: 'DataError', cause: err });
}
if (type === undefined)
throw lazyDOMException('Invalid keyData', 'DataError');
keyObject = type === kKeyTypePrivate ?
new PrivateKeyObject(handle) :
new PublicKeyObject(handle);
break;
}
default:
throw lazyDOMException(
`Unable to import RSA key with format ${format}`,
'NotSupportedError');
}
if (keyObject.asymmetricKeyType !== 'rsa') {
throw lazyDOMException('Invalid key type', 'DataError');
}
const {
modulusLength,
publicExponent,
} = keyObject[kHandle].keyDetail({});
return new InternalCryptoKey(keyObject, {
name: algorithm.name,
modulusLength,
publicExponent: new Uint8Array(publicExponent),
hash: algorithm.hash,
}, keyUsages, extractable);
}
function rsaSignVerify(key, data, { saltLength }, signature) {
const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify;
const type = mode === kSignJobModeSign ? 'private' : 'public';
if (key.type !== type)
throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError');
return jobPromise(() => {
if (key.algorithm.name === 'RSA-PSS') {
validateInt32(
saltLength,
'algorithm.saltLength',
0,
MathCeil((key.algorithm.modulusLength - 1) / 8) - getDigestSizeInBytes(key.algorithm.hash.name) - 2);
}
return new SignJob(
kCryptoJobAsync,
signature === undefined ? kSignJobModeSign : kSignJobModeVerify,
key[kKeyObject][kHandle],
undefined,
undefined,
undefined,
data,
normalizeHashName(key.algorithm.hash.name),
saltLength,
key.algorithm.name === 'RSA-PSS' ? RSA_PKCS1_PSS_PADDING : undefined,
undefined,
signature);
});
}
module.exports = {
rsaCipher: rsaOaepCipher,
rsaExportKey,
rsaImportKey,
rsaKeyGenerate,
rsaSignVerify,
};

View File

@ -0,0 +1,135 @@
'use strict';
const {
FunctionPrototypeCall,
} = primordials;
const { Buffer } = require('buffer');
const {
ScryptJob,
kCryptoJobAsync,
kCryptoJobSync,
} = internalBinding('crypto');
const {
validateFunction,
validateInteger,
validateInt32,
validateUint32,
} = require('internal/validators');
const {
codes: {
ERR_CRYPTO_SCRYPT_NOT_SUPPORTED,
ERR_INCOMPATIBLE_OPTION_PAIR,
},
} = require('internal/errors');
const {
getArrayBufferOrView,
} = require('internal/crypto/util');
const defaults = {
N: 16384,
r: 8,
p: 1,
maxmem: 32 << 20, // 32 MiB, matches SCRYPT_MAX_MEM.
};
function scrypt(password, salt, keylen, options, callback = defaults) {
if (callback === defaults) {
callback = options;
options = defaults;
}
options = check(password, salt, keylen, options);
const { N, r, p, maxmem } = options;
({ password, salt, keylen } = options);
validateFunction(callback, 'callback');
const job = new ScryptJob(
kCryptoJobAsync, password, salt, N, r, p, maxmem, keylen);
job.ondone = (error, result) => {
if (error !== undefined)
return FunctionPrototypeCall(callback, job, error);
const buf = Buffer.from(result);
return FunctionPrototypeCall(callback, job, null, buf);
};
job.run();
}
function scryptSync(password, salt, keylen, options = defaults) {
options = check(password, salt, keylen, options);
const { N, r, p, maxmem } = options;
({ password, salt, keylen } = options);
const job = new ScryptJob(
kCryptoJobSync, password, salt, N, r, p, maxmem, keylen);
const { 0: err, 1: result } = job.run();
if (err !== undefined)
throw err;
return Buffer.from(result);
}
function check(password, salt, keylen, options) {
if (ScryptJob === undefined)
throw new ERR_CRYPTO_SCRYPT_NOT_SUPPORTED();
password = getArrayBufferOrView(password, 'password');
salt = getArrayBufferOrView(salt, 'salt');
validateInt32(keylen, 'keylen', 0);
let { N, r, p, maxmem } = defaults;
if (options && options !== defaults) {
const has_N = options.N !== undefined;
if (has_N) {
N = options.N;
validateUint32(N, 'N');
}
if (options.cost !== undefined) {
if (has_N) throw new ERR_INCOMPATIBLE_OPTION_PAIR('N', 'cost');
N = options.cost;
validateUint32(N, 'cost');
}
const has_r = (options.r !== undefined);
if (has_r) {
r = options.r;
validateUint32(r, 'r');
}
if (options.blockSize !== undefined) {
if (has_r) throw new ERR_INCOMPATIBLE_OPTION_PAIR('r', 'blockSize');
r = options.blockSize;
validateUint32(r, 'blockSize');
}
const has_p = options.p !== undefined;
if (has_p) {
p = options.p;
validateUint32(p, 'p');
}
if (options.parallelization !== undefined) {
if (has_p) throw new ERR_INCOMPATIBLE_OPTION_PAIR('p', 'parallelization');
p = options.parallelization;
validateUint32(p, 'parallelization');
}
if (options.maxmem !== undefined) {
maxmem = options.maxmem;
validateInteger(maxmem, 'maxmem', 0);
}
if (N === 0) N = defaults.N;
if (r === 0) r = defaults.r;
if (p === 0) p = defaults.p;
if (maxmem === 0) maxmem = defaults.maxmem;
}
return { password, salt, keylen, N, r, p, maxmem };
}
module.exports = {
scrypt,
scryptSync,
};

301
lib/internal/crypto/sig.js Normal file
View File

@ -0,0 +1,301 @@
'use strict';
const {
FunctionPrototypeCall,
ObjectSetPrototypeOf,
ReflectApply,
} = primordials;
const {
codes: {
ERR_CRYPTO_SIGN_KEY_REQUIRED,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
},
} = require('internal/errors');
const {
validateFunction,
validateEncoding,
validateString,
} = require('internal/validators');
const {
Sign: _Sign,
SignJob,
Verify: _Verify,
kCryptoJobAsync,
kCryptoJobSync,
kSigEncDER,
kSigEncP1363,
kSignJobModeSign,
kSignJobModeVerify,
} = internalBinding('crypto');
const {
getArrayBufferOrView,
kHandle,
} = require('internal/crypto/util');
const {
preparePrivateKey,
preparePublicOrPrivateKey,
} = require('internal/crypto/keys');
const { Writable } = require('stream');
const { Buffer } = require('buffer');
const {
isArrayBufferView,
} = require('internal/util/types');
function Sign(algorithm, options) {
if (!(this instanceof Sign))
return new Sign(algorithm, options);
validateString(algorithm, 'algorithm');
this[kHandle] = new _Sign();
this[kHandle].init(algorithm);
ReflectApply(Writable, this, [options]);
}
ObjectSetPrototypeOf(Sign.prototype, Writable.prototype);
ObjectSetPrototypeOf(Sign, Writable);
Sign.prototype._write = function _write(chunk, encoding, callback) {
this.update(chunk, encoding);
callback();
};
Sign.prototype.update = function update(data, encoding) {
if (typeof data === 'string') {
validateEncoding(data, encoding);
} else if (!isArrayBufferView(data)) {
throw new ERR_INVALID_ARG_TYPE(
'data', ['string', 'Buffer', 'TypedArray', 'DataView'], data);
}
this[kHandle].update(data, encoding);
return this;
};
function getPadding(options) {
return getIntOption('padding', options);
}
function getSaltLength(options) {
return getIntOption('saltLength', options);
}
function getDSASignatureEncoding(options) {
if (typeof options === 'object') {
const { dsaEncoding = 'der' } = options;
if (dsaEncoding === 'der')
return kSigEncDER;
else if (dsaEncoding === 'ieee-p1363')
return kSigEncP1363;
throw new ERR_INVALID_ARG_VALUE('options.dsaEncoding', dsaEncoding);
}
return kSigEncDER;
}
function getIntOption(name, options) {
const value = options[name];
if (value !== undefined) {
if (value === value >> 0) {
return value;
}
throw new ERR_INVALID_ARG_VALUE(`options.${name}`, value);
}
return undefined;
}
Sign.prototype.sign = function sign(options, encoding) {
if (!options)
throw new ERR_CRYPTO_SIGN_KEY_REQUIRED();
const { data, format, type, passphrase } = preparePrivateKey(options, true);
// Options specific to RSA
const rsaPadding = getPadding(options);
const pssSaltLength = getSaltLength(options);
// Options specific to (EC)DSA
const dsaSigEnc = getDSASignatureEncoding(options);
const ret = this[kHandle].sign(data, format, type, passphrase, rsaPadding,
pssSaltLength, dsaSigEnc);
if (encoding && encoding !== 'buffer')
return ret.toString(encoding);
return ret;
};
function signOneShot(algorithm, data, key, callback) {
if (algorithm != null)
validateString(algorithm, 'algorithm');
if (callback !== undefined)
validateFunction(callback, 'callback');
data = getArrayBufferOrView(data, 'data');
if (!key)
throw new ERR_CRYPTO_SIGN_KEY_REQUIRED();
// Options specific to RSA
const rsaPadding = getPadding(key);
const pssSaltLength = getSaltLength(key);
// Options specific to (EC)DSA
const dsaSigEnc = getDSASignatureEncoding(key);
const {
data: keyData,
format: keyFormat,
type: keyType,
passphrase: keyPassphrase,
} = preparePrivateKey(key);
const job = new SignJob(
callback ? kCryptoJobAsync : kCryptoJobSync,
kSignJobModeSign,
keyData,
keyFormat,
keyType,
keyPassphrase,
data,
algorithm,
pssSaltLength,
rsaPadding,
dsaSigEnc);
if (!callback) {
const { 0: err, 1: signature } = job.run();
if (err !== undefined)
throw err;
return Buffer.from(signature);
}
job.ondone = (error, signature) => {
if (error) return FunctionPrototypeCall(callback, job, error);
FunctionPrototypeCall(callback, job, null, Buffer.from(signature));
};
job.run();
}
function Verify(algorithm, options) {
if (!(this instanceof Verify))
return new Verify(algorithm, options);
validateString(algorithm, 'algorithm');
this[kHandle] = new _Verify();
this[kHandle].init(algorithm);
ReflectApply(Writable, this, [options]);
}
ObjectSetPrototypeOf(Verify.prototype, Writable.prototype);
ObjectSetPrototypeOf(Verify, Writable);
Verify.prototype._write = Sign.prototype._write;
Verify.prototype.update = Sign.prototype.update;
Verify.prototype.verify = function verify(options, signature, sigEncoding) {
const {
data,
format,
type,
passphrase,
} = preparePublicOrPrivateKey(options, true);
// Options specific to RSA
const rsaPadding = getPadding(options);
const pssSaltLength = getSaltLength(options);
// Options specific to (EC)DSA
const dsaSigEnc = getDSASignatureEncoding(options);
signature = getArrayBufferOrView(signature, 'signature', sigEncoding);
return this[kHandle].verify(data, format, type, passphrase, signature,
rsaPadding, pssSaltLength, dsaSigEnc);
};
function verifyOneShot(algorithm, data, key, signature, callback) {
if (algorithm != null)
validateString(algorithm, 'algorithm');
if (callback !== undefined)
validateFunction(callback, 'callback');
data = getArrayBufferOrView(data, 'data');
if (!isArrayBufferView(data)) {
throw new ERR_INVALID_ARG_TYPE(
'data',
['Buffer', 'TypedArray', 'DataView'],
data,
);
}
// Options specific to RSA
const rsaPadding = getPadding(key);
const pssSaltLength = getSaltLength(key);
// Options specific to (EC)DSA
const dsaSigEnc = getDSASignatureEncoding(key);
if (!isArrayBufferView(signature)) {
throw new ERR_INVALID_ARG_TYPE(
'signature',
['Buffer', 'TypedArray', 'DataView'],
signature,
);
}
const {
data: keyData,
format: keyFormat,
type: keyType,
passphrase: keyPassphrase,
} = preparePublicOrPrivateKey(key);
const job = new SignJob(
callback ? kCryptoJobAsync : kCryptoJobSync,
kSignJobModeVerify,
keyData,
keyFormat,
keyType,
keyPassphrase,
data,
algorithm,
pssSaltLength,
rsaPadding,
dsaSigEnc,
signature);
if (!callback) {
const { 0: err, 1: result } = job.run();
if (err !== undefined)
throw err;
return result;
}
job.ondone = (error, result) => {
if (error) return FunctionPrototypeCall(callback, job, error);
FunctionPrototypeCall(callback, job, null, result);
};
job.run();
}
module.exports = {
Sign,
signOneShot,
Verify,
verifyOneShot,
};

607
lib/internal/crypto/util.js Normal file
View File

@ -0,0 +1,607 @@
'use strict';
const {
ArrayBufferIsView,
ArrayBufferPrototypeGetByteLength,
ArrayPrototypeIncludes,
ArrayPrototypePush,
BigInt,
DataViewPrototypeGetBuffer,
DataViewPrototypeGetByteLength,
DataViewPrototypeGetByteOffset,
FunctionPrototypeBind,
Number,
ObjectDefineProperty,
ObjectEntries,
ObjectKeys,
ObjectPrototypeHasOwnProperty,
Promise,
StringPrototypeToUpperCase,
Symbol,
TypedArrayPrototypeGetBuffer,
TypedArrayPrototypeGetByteLength,
TypedArrayPrototypeGetByteOffset,
TypedArrayPrototypeSlice,
Uint8Array,
} = primordials;
const {
getCiphers: _getCiphers,
getCurves: _getCurves,
getHashes: _getHashes,
setEngine: _setEngine,
secureHeapUsed: _secureHeapUsed,
getCachedAliases,
getOpenSSLSecLevelCrypto: getOpenSSLSecLevel,
} = internalBinding('crypto');
const { getOptionValue } = require('internal/options');
const {
crypto: {
ENGINE_METHOD_ALL,
},
} = internalBinding('constants');
const normalizeHashName = require('internal/crypto/hashnames');
const {
codes: {
ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED,
ERR_CRYPTO_ENGINE_UNKNOWN,
ERR_INVALID_ARG_TYPE,
},
hideStackFrames,
} = require('internal/errors');
const {
validateArray,
validateNumber,
validateString,
} = require('internal/validators');
const { Buffer } = require('buffer');
const {
cachedResult,
emitExperimentalWarning,
filterDuplicateStrings,
lazyDOMException,
} = require('internal/util');
const {
namespace: {
isBuildingSnapshot,
addSerializeCallback,
},
} = require('internal/v8/startup_snapshot');
const {
isDataView,
isArrayBufferView,
isAnyArrayBuffer,
} = require('internal/util/types');
const kHandle = Symbol('kHandle');
const kKeyObject = Symbol('kKeyObject');
// This is here because many functions accepted binary strings without
// any explicit encoding in older versions of node, and we don't want
// to break them unnecessarily.
function toBuf(val, encoding) {
if (typeof val === 'string') {
if (encoding === 'buffer')
encoding = 'utf8';
return Buffer.from(val, encoding);
}
return val;
}
let _hashCache;
function getHashCache() {
if (_hashCache === undefined) {
_hashCache = getCachedAliases();
if (isBuildingSnapshot()) {
// For dynamic linking, clear the map.
addSerializeCallback(() => { _hashCache = undefined; });
}
}
return _hashCache;
}
function getCachedHashId(algorithm) {
const result = getHashCache()[algorithm];
return result === undefined ? -1 : result;
}
const getCiphers = cachedResult(() => filterDuplicateStrings(_getCiphers()));
const getHashes = cachedResult(() => filterDuplicateStrings(_getHashes()));
const getCurves = cachedResult(() => filterDuplicateStrings(_getCurves()));
function setEngine(id, flags) {
validateString(id, 'id');
if (flags)
validateNumber(flags, 'flags');
flags = flags >>> 0;
// Use provided engine for everything by default
if (flags === 0)
flags = ENGINE_METHOD_ALL;
if (typeof _setEngine !== 'function')
throw new ERR_CRYPTO_CUSTOM_ENGINE_NOT_SUPPORTED();
if (!_setEngine(id, flags))
throw new ERR_CRYPTO_ENGINE_UNKNOWN(id);
}
const getArrayBufferOrView = hideStackFrames((buffer, name, encoding) => {
if (isAnyArrayBuffer(buffer))
return buffer;
if (typeof buffer === 'string') {
if (encoding === 'buffer')
encoding = 'utf8';
return Buffer.from(buffer, encoding);
}
if (!isArrayBufferView(buffer)) {
throw new ERR_INVALID_ARG_TYPE.HideStackFramesError(
name,
[
'string',
'ArrayBuffer',
'Buffer',
'TypedArray',
'DataView',
],
buffer,
);
}
return buffer;
});
// The maximum buffer size that we'll support in the WebCrypto impl
const kMaxBufferLength = (2 ** 31) - 1;
// The EC named curves that we currently support via the Web Crypto API.
const kNamedCurveAliases = {
'P-256': 'prime256v1',
'P-384': 'secp384r1',
'P-521': 'secp521r1',
};
const kSupportedAlgorithms = {
'digest': {
'SHA-1': null,
'SHA-256': null,
'SHA-384': null,
'SHA-512': null,
},
'generateKey': {
'RSASSA-PKCS1-v1_5': 'RsaHashedKeyGenParams',
'RSA-PSS': 'RsaHashedKeyGenParams',
'RSA-OAEP': 'RsaHashedKeyGenParams',
'ECDSA': 'EcKeyGenParams',
'ECDH': 'EcKeyGenParams',
'AES-CTR': 'AesKeyGenParams',
'AES-CBC': 'AesKeyGenParams',
'AES-GCM': 'AesKeyGenParams',
'AES-KW': 'AesKeyGenParams',
'HMAC': 'HmacKeyGenParams',
'Ed25519': null,
'X25519': null,
},
'sign': {
'RSASSA-PKCS1-v1_5': null,
'RSA-PSS': 'RsaPssParams',
'ECDSA': 'EcdsaParams',
'HMAC': null,
'Ed25519': null,
},
'verify': {
'RSASSA-PKCS1-v1_5': null,
'RSA-PSS': 'RsaPssParams',
'ECDSA': 'EcdsaParams',
'HMAC': null,
'Ed25519': null,
},
'importKey': {
'RSASSA-PKCS1-v1_5': 'RsaHashedImportParams',
'RSA-PSS': 'RsaHashedImportParams',
'RSA-OAEP': 'RsaHashedImportParams',
'ECDSA': 'EcKeyImportParams',
'ECDH': 'EcKeyImportParams',
'HMAC': 'HmacImportParams',
'HKDF': null,
'PBKDF2': null,
'AES-CTR': null,
'AES-CBC': null,
'AES-GCM': null,
'AES-KW': null,
'Ed25519': null,
'X25519': null,
},
'deriveBits': {
'HKDF': 'HkdfParams',
'PBKDF2': 'Pbkdf2Params',
'ECDH': 'EcdhKeyDeriveParams',
'X25519': 'EcdhKeyDeriveParams',
},
'encrypt': {
'RSA-OAEP': 'RsaOaepParams',
'AES-CBC': 'AesCbcParams',
'AES-GCM': 'AesGcmParams',
'AES-CTR': 'AesCtrParams',
},
'decrypt': {
'RSA-OAEP': 'RsaOaepParams',
'AES-CBC': 'AesCbcParams',
'AES-GCM': 'AesGcmParams',
'AES-CTR': 'AesCtrParams',
},
'get key length': {
'AES-CBC': 'AesDerivedKeyParams',
'AES-CTR': 'AesDerivedKeyParams',
'AES-GCM': 'AesDerivedKeyParams',
'AES-KW': 'AesDerivedKeyParams',
'HMAC': 'HmacImportParams',
'HKDF': null,
'PBKDF2': null,
},
'wrapKey': {
'AES-KW': null,
},
'unwrapKey': {
'AES-KW': null,
},
};
const experimentalAlgorithms = ObjectEntries({
'X448': {
generateKey: null,
importKey: null,
deriveBits: 'EcdhKeyDeriveParams',
},
'Ed448': {
generateKey: null,
sign: 'Ed448Params',
verify: 'Ed448Params',
importKey: null,
},
});
for (let i = 0; i < experimentalAlgorithms.length; i++) {
const name = experimentalAlgorithms[i][0];
const ops = ObjectEntries(experimentalAlgorithms[i][1]);
for (let j = 0; j < ops.length; j++) {
const { 0: op, 1: dict } = ops[j];
ObjectDefineProperty(kSupportedAlgorithms[op], name, {
get() {
emitExperimentalWarning(`The ${name} Web Crypto API algorithm`);
return dict;
},
__proto__: null,
enumerable: true,
});
}
}
const simpleAlgorithmDictionaries = {
AesGcmParams: { iv: 'BufferSource', additionalData: 'BufferSource' },
RsaHashedKeyGenParams: { hash: 'HashAlgorithmIdentifier' },
EcKeyGenParams: {},
HmacKeyGenParams: { hash: 'HashAlgorithmIdentifier' },
RsaPssParams: {},
EcdsaParams: { hash: 'HashAlgorithmIdentifier' },
HmacImportParams: { hash: 'HashAlgorithmIdentifier' },
HkdfParams: {
hash: 'HashAlgorithmIdentifier',
salt: 'BufferSource',
info: 'BufferSource',
},
Ed448Params: { context: 'BufferSource' },
Pbkdf2Params: { hash: 'HashAlgorithmIdentifier', salt: 'BufferSource' },
RsaOaepParams: { label: 'BufferSource' },
RsaHashedImportParams: { hash: 'HashAlgorithmIdentifier' },
EcKeyImportParams: {},
};
function validateMaxBufferLength(data, name) {
if (data.byteLength > kMaxBufferLength) {
throw lazyDOMException(
`${name} must be less than ${kMaxBufferLength + 1} bits`,
'OperationError');
}
}
let webidl;
// https://w3c.github.io/webcrypto/#algorithm-normalization-normalize-an-algorithm
// adapted for Node.js from Deno's implementation
// https://github.com/denoland/deno/blob/v1.29.1/ext/crypto/00_crypto.js#L195
function normalizeAlgorithm(algorithm, op) {
if (typeof algorithm === 'string')
return normalizeAlgorithm({ name: algorithm }, op);
webidl ??= require('internal/crypto/webidl');
// 1.
const registeredAlgorithms = kSupportedAlgorithms[op];
// 2. 3.
const initialAlg = webidl.converters.Algorithm(algorithm, {
prefix: 'Failed to normalize algorithm',
context: 'passed algorithm',
});
// 4.
let algName = initialAlg.name;
// 5.
let desiredType;
for (const key in registeredAlgorithms) {
if (!ObjectPrototypeHasOwnProperty(registeredAlgorithms, key)) {
continue;
}
if (
StringPrototypeToUpperCase(key) === StringPrototypeToUpperCase(algName)
) {
algName = key;
desiredType = registeredAlgorithms[key];
}
}
if (desiredType === undefined)
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');
// Fast path everything below if the registered dictionary is null
if (desiredType === null)
return { name: algName };
// 6.
const normalizedAlgorithm = webidl.converters[desiredType](algorithm, {
prefix: 'Failed to normalize algorithm',
context: 'passed algorithm',
});
// 7.
normalizedAlgorithm.name = algName;
// 9.
const dict = simpleAlgorithmDictionaries[desiredType];
// 10.
const dictKeys = dict ? ObjectKeys(dict) : [];
for (let i = 0; i < dictKeys.length; i++) {
const member = dictKeys[i];
if (!ObjectPrototypeHasOwnProperty(dict, member))
continue;
const idlType = dict[member];
const idlValue = normalizedAlgorithm[member];
// 3.
if (idlType === 'BufferSource' && idlValue) {
const isView = ArrayBufferIsView(idlValue);
normalizedAlgorithm[member] = TypedArrayPrototypeSlice(
new Uint8Array(
isView ? getDataViewOrTypedArrayBuffer(idlValue) : idlValue,
isView ? getDataViewOrTypedArrayByteOffset(idlValue) : 0,
isView ? getDataViewOrTypedArrayByteLength(idlValue) : ArrayBufferPrototypeGetByteLength(idlValue),
),
);
} else if (idlType === 'HashAlgorithmIdentifier') {
normalizedAlgorithm[member] = normalizeAlgorithm(idlValue, 'digest');
} else if (idlType === 'AlgorithmIdentifier') {
// This extension point is not used by any supported algorithm (yet?)
throw lazyDOMException('Not implemented.', 'NotSupportedError');
}
}
return normalizedAlgorithm;
}
function getDataViewOrTypedArrayBuffer(V) {
return isDataView(V) ?
DataViewPrototypeGetBuffer(V) : TypedArrayPrototypeGetBuffer(V);
}
function getDataViewOrTypedArrayByteOffset(V) {
return isDataView(V) ?
DataViewPrototypeGetByteOffset(V) : TypedArrayPrototypeGetByteOffset(V);
}
function getDataViewOrTypedArrayByteLength(V) {
return isDataView(V) ?
DataViewPrototypeGetByteLength(V) : TypedArrayPrototypeGetByteLength(V);
}
function hasAnyNotIn(set, checks) {
for (const s of set)
if (!ArrayPrototypeIncludes(checks, s))
return true;
return false;
}
const validateByteSource = hideStackFrames((val, name) => {
val = toBuf(val);
if (isAnyArrayBuffer(val) || isArrayBufferView(val))
return val;
throw new ERR_INVALID_ARG_TYPE.HideStackFramesError(
name,
[
'string',
'ArrayBuffer',
'TypedArray',
'DataView',
'Buffer',
],
val);
});
function onDone(resolve, reject, err, result) {
if (err) {
return reject(lazyDOMException(
'The operation failed for an operation-specific reason',
{ name: 'OperationError', cause: err }));
}
resolve(result);
}
function jobPromise(getJob) {
return new Promise((resolve, reject) => {
try {
const job = getJob();
job.ondone = FunctionPrototypeBind(onDone, job, resolve, reject);
job.run();
} catch (err) {
onDone(resolve, reject, err);
}
});
}
// In WebCrypto, the publicExponent option in RSA is represented as a
// WebIDL "BigInteger"... that is, a Uint8Array that allows an arbitrary
// number of leading zero bits. Our conventional APIs for reading
// an unsigned int from a Buffer are not adequate. The implementation
// here is adapted from the chromium implementation here:
// https://github.com/chromium/chromium/blob/HEAD/third_party/blink/public/platform/web_crypto_algorithm_params.h, but ported to JavaScript
// Returns undefined if the conversion was unsuccessful.
function bigIntArrayToUnsignedInt(input) {
let result = 0;
for (let n = 0; n < input.length; ++n) {
const n_reversed = input.length - n - 1;
if (n_reversed >= 4 && input[n])
return; // Too large
result |= input[n] << 8 * n_reversed;
}
return result;
}
function bigIntArrayToUnsignedBigInt(input) {
let result = 0n;
for (let n = 0; n < input.length; ++n) {
const n_reversed = input.length - n - 1;
result |= BigInt(input[n]) << 8n * BigInt(n_reversed);
}
return result;
}
function getStringOption(options, key) {
let value;
if (options && (value = options[key]) != null)
validateString(value, `options.${key}`);
return value;
}
function getUsagesUnion(usageSet, ...usages) {
const newset = [];
for (let n = 0; n < usages.length; n++) {
if (usageSet.has(usages[n]))
ArrayPrototypePush(newset, usages[n]);
}
return newset;
}
function getBlockSize(name) {
switch (name) {
case 'SHA-1':
// Fall through
case 'SHA-256':
return 512;
case 'SHA-384':
// Fall through
case 'SHA-512':
return 1024;
}
}
function getDigestSizeInBytes(name) {
switch (name) {
case 'SHA-1': return 20;
case 'SHA-256': return 32;
case 'SHA-384': return 48;
case 'SHA-512': return 64;
}
}
const kKeyOps = {
sign: 1,
verify: 2,
encrypt: 3,
decrypt: 4,
wrapKey: 5,
unwrapKey: 6,
deriveKey: 7,
deriveBits: 8,
};
function validateKeyOps(keyOps, usagesSet) {
if (keyOps === undefined) return;
validateArray(keyOps, 'keyData.key_ops');
let flags = 0;
for (let n = 0; n < keyOps.length; n++) {
const op = keyOps[n];
const op_flag = kKeyOps[op];
// Skipping unknown key ops
if (op_flag === undefined)
continue;
// Have we seen it already? if so, error
if (flags & (1 << op_flag))
throw lazyDOMException('Duplicate key operation', 'DataError');
flags |= (1 << op_flag);
// TODO(@jasnell): RFC7517 section 4.3 strong recommends validating
// key usage combinations. Specifically, it says that unrelated key
// ops SHOULD NOT be used together. We're not yet validating that here.
}
if (usagesSet !== undefined) {
for (const use of usagesSet) {
if (!ArrayPrototypeIncludes(keyOps, use)) {
throw lazyDOMException(
'Key operations and usage mismatch',
'DataError');
}
}
}
}
function secureHeapUsed() {
const val = _secureHeapUsed();
if (val === undefined)
return { total: 0, used: 0, utilization: 0, min: 0 };
const used = Number(_secureHeapUsed());
const total = Number(getOptionValue('--secure-heap'));
const min = Number(getOptionValue('--secure-heap-min'));
const utilization = used / total;
return { total, used, utilization, min };
}
module.exports = {
getArrayBufferOrView,
getCiphers,
getCurves,
getDataViewOrTypedArrayBuffer,
getHashes,
kHandle,
kKeyObject,
setEngine,
toBuf,
kNamedCurveAliases,
normalizeAlgorithm,
normalizeHashName,
hasAnyNotIn,
validateByteSource,
validateKeyOps,
jobPromise,
validateMaxBufferLength,
bigIntArrayToUnsignedBigInt,
bigIntArrayToUnsignedInt,
getBlockSize,
getDigestSizeInBytes,
getStringOption,
getUsagesUnion,
secureHeapUsed,
getCachedHashId,
getHashCache,
getOpenSSLSecLevel,
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,715 @@
'use strict';
// Adapted from the following sources
// - https://github.com/jsdom/webidl-conversions
// Copyright Domenic Denicola. Licensed under BSD-2-Clause License.
// Original license at https://github.com/jsdom/webidl-conversions/blob/master/LICENSE.md.
// - https://github.com/denoland/deno
// Copyright Deno authors. Licensed under MIT License.
// Original license at https://github.com/denoland/deno/blob/main/LICENSE.md.
// Changes include using primordials and stripping the code down to only what
// WebCryptoAPI needs.
const {
ArrayBufferIsView,
ArrayPrototypeIncludes,
ArrayPrototypePush,
ArrayPrototypeSort,
MathPow,
MathTrunc,
Number,
NumberIsFinite,
ObjectPrototypeHasOwnProperty,
ObjectPrototypeIsPrototypeOf,
SafeArrayIterator,
String,
TypedArrayPrototypeGetBuffer,
TypedArrayPrototypeGetSymbolToStringTag,
} = primordials;
const {
makeException,
createEnumConverter,
createSequenceConverter,
} = require('internal/webidl');
const {
lazyDOMException,
kEmptyObject,
setOwnProperty,
} = require('internal/util');
const { CryptoKey } = require('internal/crypto/webcrypto');
const {
getDataViewOrTypedArrayBuffer,
validateMaxBufferLength,
kNamedCurveAliases,
} = require('internal/crypto/util');
const { isArrayBuffer, isSharedArrayBuffer } = require('internal/util/types');
// https://tc39.es/ecma262/#sec-tonumber
function toNumber(value, opts = kEmptyObject) {
switch (typeof value) {
case 'number':
return value;
case 'bigint':
throw makeException(
'is a BigInt and cannot be converted to a number.',
opts);
case 'symbol':
throw makeException(
'is a Symbol and cannot be converted to a number.',
opts);
default:
return Number(value);
}
}
function type(V) {
if (V === null)
return 'Null';
switch (typeof V) {
case 'undefined':
return 'Undefined';
case 'boolean':
return 'Boolean';
case 'number':
return 'Number';
case 'string':
return 'String';
case 'symbol':
return 'Symbol';
case 'bigint':
return 'BigInt';
case 'object': // Fall through
case 'function': // Fall through
default:
// Per ES spec, typeof returns an implementation-defined value that is not
// any of the existing ones for uncallable non-standard exotic objects.
// Yet Type() which the Web IDL spec depends on returns Object for such
// cases. So treat the default case as an object.
return 'Object';
}
}
const integerPart = MathTrunc;
function validateByteLength(buf, name, target) {
if (buf.byteLength !== target) {
throw lazyDOMException(
`${name} must contain exactly ${target} bytes`,
'OperationError');
}
}
function AESLengthValidator(V, dict) {
if (V !== 128 && V !== 192 && V !== 256)
throw lazyDOMException(
'AES key length must be 128, 192, or 256 bits',
'OperationError');
}
function namedCurveValidator(V, dict) {
if (!ObjectPrototypeHasOwnProperty(kNamedCurveAliases, V))
throw lazyDOMException(
'Unrecognized namedCurve',
'NotSupportedError');
}
// This was updated to only consider bitlength up to 32 used by WebCryptoAPI
function createIntegerConversion(bitLength) {
const lowerBound = 0;
const upperBound = MathPow(2, bitLength) - 1;
const twoToTheBitLength = MathPow(2, bitLength);
return (V, opts = kEmptyObject) => {
let x = toNumber(V, opts);
if (opts.enforceRange) {
if (!NumberIsFinite(x)) {
throw makeException(
'is not a finite number.',
opts);
}
x = integerPart(x);
if (x < lowerBound || x > upperBound) {
throw makeException(
`is outside the expected range of ${lowerBound} to ${upperBound}.`,
{ __proto__: null, ...opts, code: 'ERR_OUT_OF_RANGE' },
);
}
return x;
}
if (!NumberIsFinite(x) || x === 0) {
return 0;
}
x = integerPart(x);
if (x >= lowerBound && x <= upperBound) {
return x;
}
x = x % twoToTheBitLength;
return x;
};
}
const converters = {};
converters.boolean = (val) => !!val;
converters.octet = createIntegerConversion(8);
converters['unsigned short'] = createIntegerConversion(16);
converters['unsigned long'] = createIntegerConversion(32);
converters.DOMString = function(V, opts = kEmptyObject) {
if (typeof V === 'string') {
return V;
} else if (typeof V === 'symbol') {
throw makeException(
'is a Symbol and cannot be converted to a string.',
opts);
}
return String(V);
};
converters.object = (V, opts) => {
if (type(V) !== 'Object') {
throw makeException(
'is not an object.',
opts);
}
return V;
};
const isNonSharedArrayBuffer = isArrayBuffer;
converters.Uint8Array = (V, opts = kEmptyObject) => {
if (!ArrayBufferIsView(V) ||
TypedArrayPrototypeGetSymbolToStringTag(V) !== 'Uint8Array') {
throw makeException(
'is not an Uint8Array object.',
opts);
}
if (isSharedArrayBuffer(TypedArrayPrototypeGetBuffer(V))) {
throw makeException(
'is a view on a SharedArrayBuffer, which is not allowed.',
opts);
}
return V;
};
converters.BufferSource = (V, opts = kEmptyObject) => {
if (ArrayBufferIsView(V)) {
if (isSharedArrayBuffer(getDataViewOrTypedArrayBuffer(V))) {
throw makeException(
'is a view on a SharedArrayBuffer, which is not allowed.',
opts);
}
return V;
}
if (!isNonSharedArrayBuffer(V)) {
throw makeException(
'is not instance of ArrayBuffer, Buffer, TypedArray, or DataView.',
opts);
}
return V;
};
converters['sequence<DOMString>'] = createSequenceConverter(
converters.DOMString);
function requiredArguments(length, required, opts = kEmptyObject) {
if (length < required) {
throw makeException(
`${required} argument${
required === 1 ? '' : 's'
} required, but only ${length} present.`,
{ __proto__: null, ...opts, context: '', code: 'ERR_MISSING_ARGS' });
}
}
function createDictionaryConverter(name, dictionaries) {
let hasRequiredKey = false;
const allMembers = [];
for (let i = 0; i < dictionaries.length; i++) {
const member = dictionaries[i];
if (member.required) {
hasRequiredKey = true;
}
ArrayPrototypePush(allMembers, member);
}
ArrayPrototypeSort(allMembers, (a, b) => {
if (a.key === b.key) {
return 0;
}
return a.key < b.key ? -1 : 1;
});
return function(V, opts = kEmptyObject) {
const typeV = type(V);
switch (typeV) {
case 'Undefined':
case 'Null':
case 'Object':
break;
default:
throw makeException(
'can not be converted to a dictionary',
opts);
}
const esDict = V;
const idlDict = {};
// Fast path null and undefined.
if (V == null && !hasRequiredKey) {
return idlDict;
}
for (const member of new SafeArrayIterator(allMembers)) {
const key = member.key;
let esMemberValue;
if (typeV === 'Undefined' || typeV === 'Null') {
esMemberValue = undefined;
} else {
esMemberValue = esDict[key];
}
if (esMemberValue !== undefined) {
const context = `'${key}' of '${name}'${
opts.context ? ` (${opts.context})` : ''
}`;
const { converter, validator } = member;
const idlMemberValue = converter(esMemberValue, {
__proto__: null,
...opts,
context,
});
validator?.(idlMemberValue, esDict);
setOwnProperty(idlDict, key, idlMemberValue);
} else if (member.required) {
throw makeException(
`can not be converted to '${name}' because '${key}' is required in '${name}'.`,
{ __proto__: null, ...opts, code: 'ERR_MISSING_OPTION' });
}
}
return idlDict;
};
}
function createInterfaceConverter(name, prototype) {
return (V, opts) => {
if (!ObjectPrototypeIsPrototypeOf(prototype, V)) {
throw makeException(
`is not of type ${name}.`,
opts);
}
return V;
};
}
converters.AlgorithmIdentifier = (V, opts) => {
// Union for (object or DOMString)
if (type(V) === 'Object') {
return converters.object(V, opts);
}
return converters.DOMString(V, opts);
};
converters.KeyFormat = createEnumConverter('KeyFormat', [
'raw',
'pkcs8',
'spki',
'jwk',
]);
converters.KeyUsage = createEnumConverter('KeyUsage', [
'encrypt',
'decrypt',
'sign',
'verify',
'deriveKey',
'deriveBits',
'wrapKey',
'unwrapKey',
]);
converters['sequence<KeyUsage>'] = createSequenceConverter(converters.KeyUsage);
converters.HashAlgorithmIdentifier = converters.AlgorithmIdentifier;
const dictAlgorithm = [
{
key: 'name',
converter: converters.DOMString,
required: true,
},
];
converters.Algorithm = createDictionaryConverter(
'Algorithm', dictAlgorithm);
converters.BigInteger = converters.Uint8Array;
const dictRsaKeyGenParams = [
...new SafeArrayIterator(dictAlgorithm),
{
key: 'modulusLength',
converter: (V, opts) =>
converters['unsigned long'](V, { ...opts, enforceRange: true }),
required: true,
},
{
key: 'publicExponent',
converter: converters.BigInteger,
required: true,
},
];
converters.RsaKeyGenParams = createDictionaryConverter(
'RsaKeyGenParams', dictRsaKeyGenParams);
converters.RsaHashedKeyGenParams = createDictionaryConverter(
'RsaHashedKeyGenParams', [
...new SafeArrayIterator(dictRsaKeyGenParams),
{
key: 'hash',
converter: converters.HashAlgorithmIdentifier,
required: true,
},
]);
converters.RsaHashedImportParams = createDictionaryConverter(
'RsaHashedImportParams', [
...new SafeArrayIterator(dictAlgorithm),
{
key: 'hash',
converter: converters.HashAlgorithmIdentifier,
required: true,
},
]);
converters.NamedCurve = converters.DOMString;
converters.EcKeyImportParams = createDictionaryConverter(
'EcKeyImportParams', [
...new SafeArrayIterator(dictAlgorithm),
{
key: 'namedCurve',
converter: converters.NamedCurve,
validator: namedCurveValidator,
required: true,
},
]);
converters.EcKeyGenParams = createDictionaryConverter(
'EcKeyGenParams', [
...new SafeArrayIterator(dictAlgorithm),
{
key: 'namedCurve',
converter: converters.NamedCurve,
validator: namedCurveValidator,
required: true,
},
]);
converters.AesKeyGenParams = createDictionaryConverter(
'AesKeyGenParams', [
...new SafeArrayIterator(dictAlgorithm),
{
key: 'length',
converter: (V, opts) =>
converters['unsigned short'](V, { ...opts, enforceRange: true }),
validator: AESLengthValidator,
required: true,
},
]);
converters.HmacKeyGenParams = createDictionaryConverter(
'HmacKeyGenParams', [
...new SafeArrayIterator(dictAlgorithm),
{
key: 'hash',
converter: converters.HashAlgorithmIdentifier,
required: true,
},
{
key: 'length',
converter: (V, opts) =>
converters['unsigned long'](V, { ...opts, enforceRange: true }),
validator: (V, dict) => validateHmacKeyAlgorithm(V),
},
]);
function validateHmacKeyAlgorithm(length) {
if (length === 0)
throw lazyDOMException('Zero-length key is not supported', 'DataError');
// The Web Crypto spec allows for key lengths that are not multiples of 8. We don't.
if (length % 8)
throw lazyDOMException('Unsupported algorithm.length', 'NotSupportedError');
}
converters.RsaPssParams = createDictionaryConverter(
'RsaPssParams', [
...new SafeArrayIterator(dictAlgorithm),
{
key: 'saltLength',
converter: (V, opts) =>
converters['unsigned long'](V, { ...opts, enforceRange: true }),
required: true,
},
]);
converters.RsaOaepParams = createDictionaryConverter(
'RsaOaepParams', [
...new SafeArrayIterator(dictAlgorithm),
{
key: 'label',
converter: converters.BufferSource,
},
]);
converters.EcdsaParams = createDictionaryConverter(
'EcdsaParams', [
...new SafeArrayIterator(dictAlgorithm),
{
key: 'hash',
converter: converters.HashAlgorithmIdentifier,
required: true,
},
]);
converters.HmacImportParams = createDictionaryConverter(
'HmacImportParams', [
...new SafeArrayIterator(dictAlgorithm),
{
key: 'hash',
converter: converters.HashAlgorithmIdentifier,
required: true,
},
{
key: 'length',
converter: (V, opts) =>
converters['unsigned long'](V, { ...opts, enforceRange: true }),
validator: (V, dict) => validateHmacKeyAlgorithm(V),
},
]);
const simpleDomStringKey = (key) => ({ key, converter: converters.DOMString });
converters.RsaOtherPrimesInfo = createDictionaryConverter(
'RsaOtherPrimesInfo', [
simpleDomStringKey('r'),
simpleDomStringKey('d'),
simpleDomStringKey('t'),
]);
converters['sequence<RsaOtherPrimesInfo>'] = createSequenceConverter(
converters.RsaOtherPrimesInfo);
converters.JsonWebKey = createDictionaryConverter(
'JsonWebKey', [
simpleDomStringKey('kty'),
simpleDomStringKey('use'),
{
key: 'key_ops',
converter: converters['sequence<DOMString>'],
},
simpleDomStringKey('alg'),
{
key: 'ext',
converter: converters.boolean,
},
simpleDomStringKey('crv'),
simpleDomStringKey('x'),
simpleDomStringKey('y'),
simpleDomStringKey('d'),
simpleDomStringKey('n'),
simpleDomStringKey('e'),
simpleDomStringKey('p'),
simpleDomStringKey('q'),
simpleDomStringKey('dp'),
simpleDomStringKey('dq'),
simpleDomStringKey('qi'),
{
key: 'oth',
converter: converters['sequence<RsaOtherPrimesInfo>'],
},
simpleDomStringKey('k'),
]);
converters.HkdfParams = createDictionaryConverter(
'HkdfParams', [
...new SafeArrayIterator(dictAlgorithm),
{
key: 'hash',
converter: converters.HashAlgorithmIdentifier,
required: true,
},
{
key: 'salt',
converter: converters.BufferSource,
required: true,
},
{
key: 'info',
converter: converters.BufferSource,
required: true,
},
]);
converters.Pbkdf2Params = createDictionaryConverter(
'Pbkdf2Params', [
...new SafeArrayIterator(dictAlgorithm),
{
key: 'hash',
converter: converters.HashAlgorithmIdentifier,
required: true,
},
{
key: 'iterations',
converter: (V, opts) =>
converters['unsigned long'](V, { ...opts, enforceRange: true }),
validator: (V, dict) => {
if (V === 0)
throw lazyDOMException('iterations cannot be zero', 'OperationError');
},
required: true,
},
{
key: 'salt',
converter: converters.BufferSource,
required: true,
},
]);
converters.AesDerivedKeyParams = createDictionaryConverter(
'AesDerivedKeyParams', [
...new SafeArrayIterator(dictAlgorithm),
{
key: 'length',
converter: (V, opts) =>
converters['unsigned short'](V, { ...opts, enforceRange: true }),
validator: AESLengthValidator,
required: true,
},
]);
converters.AesCbcParams = createDictionaryConverter(
'AesCbcParams', [
...new SafeArrayIterator(dictAlgorithm),
{
key: 'iv',
converter: converters.BufferSource,
validator: (V, dict) => validateByteLength(V, 'algorithm.iv', 16),
required: true,
},
]);
converters.AesGcmParams = createDictionaryConverter(
'AesGcmParams', [
...new SafeArrayIterator(dictAlgorithm),
{
key: 'iv',
converter: converters.BufferSource,
validator: (V, dict) => validateMaxBufferLength(V, 'algorithm.iv'),
required: true,
},
{
key: 'tagLength',
converter: (V, opts) =>
converters.octet(V, { ...opts, enforceRange: true }),
validator: (V, dict) => {
if (!ArrayPrototypeIncludes([32, 64, 96, 104, 112, 120, 128], V)) {
throw lazyDOMException(
`${V} is not a valid AES-GCM tag length`,
'OperationError');
}
},
},
{
key: 'additionalData',
converter: converters.BufferSource,
validator: (V, dict) => validateMaxBufferLength(V, 'algorithm.additionalData'),
},
]);
converters.AesCtrParams = createDictionaryConverter(
'AesCtrParams', [
...new SafeArrayIterator(dictAlgorithm),
{
key: 'counter',
converter: converters.BufferSource,
validator: (V, dict) => validateByteLength(V, 'algorithm.counter', 16),
required: true,
},
{
key: 'length',
converter: (V, opts) =>
converters.octet(V, { ...opts, enforceRange: true }),
validator: (V, dict) => {
if (V === 0 || V > 128)
throw lazyDOMException(
'AES-CTR algorithm.length must be between 1 and 128',
'OperationError');
},
required: true,
},
]);
converters.CryptoKey = createInterfaceConverter(
'CryptoKey', CryptoKey.prototype);
converters.EcdhKeyDeriveParams = createDictionaryConverter(
'EcdhKeyDeriveParams', [
...new SafeArrayIterator(dictAlgorithm),
{
key: 'public',
converter: converters.CryptoKey,
validator: (V, dict) => {
if (V.type !== 'public')
throw lazyDOMException(
'algorithm.public must be a public key', 'InvalidAccessError');
if (V.algorithm.name.toUpperCase() !== dict.name.toUpperCase())
throw lazyDOMException(
`algorithm.public must be an ${dict.name.toUpperCase()} key`,
'InvalidAccessError');
},
required: true,
},
]);
converters.Ed448Params = createDictionaryConverter(
'Ed448Params', [
...new SafeArrayIterator(dictAlgorithm),
{
key: 'context',
converter: converters.BufferSource,
validator: (V, dict) => {
if (V.byteLength)
throw lazyDOMException(
'Non zero-length context is not supported.', 'NotSupportedError');
},
required: false,
},
]);
module.exports = {
converters,
requiredArguments,
};

388
lib/internal/crypto/x509.js Normal file
View File

@ -0,0 +1,388 @@
'use strict';
const {
ObjectSetPrototypeOf,
SafeMap,
Symbol,
} = primordials;
const {
parseX509,
X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT,
X509_CHECK_FLAG_NEVER_CHECK_SUBJECT,
X509_CHECK_FLAG_NO_WILDCARDS,
X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS,
X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS,
X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS,
} = internalBinding('crypto');
const {
PublicKeyObject,
isKeyObject,
} = require('internal/crypto/keys');
const {
customInspectSymbol: kInspect,
kEmptyObject,
} = require('internal/util');
const {
validateBoolean,
validateObject,
validateString,
} = require('internal/validators');
const { inspect } = require('internal/util/inspect');
const { Buffer } = require('buffer');
const {
isArrayBufferView,
} = require('internal/util/types');
const {
codes: {
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
},
} = require('internal/errors');
const {
markTransferMode,
kClone,
kDeserialize,
} = require('internal/worker/js_transferable');
const {
kHandle,
} = require('internal/crypto/util');
let lazyTranslatePeerCertificate;
const kInternalState = Symbol('kInternalState');
function isX509Certificate(value) {
return value[kInternalState] !== undefined;
}
function getFlags(options = kEmptyObject) {
validateObject(options, 'options');
const {
subject = 'default', // Can be 'default', 'always', or 'never'
wildcards = true,
partialWildcards = true,
multiLabelWildcards = false,
singleLabelSubdomains = false,
} = { ...options };
let flags = 0;
validateString(subject, 'options.subject');
validateBoolean(wildcards, 'options.wildcards');
validateBoolean(partialWildcards, 'options.partialWildcards');
validateBoolean(multiLabelWildcards, 'options.multiLabelWildcards');
validateBoolean(singleLabelSubdomains, 'options.singleLabelSubdomains');
switch (subject) {
case 'default': /* Matches OpenSSL's default, no flags. */ break;
case 'always': flags |= X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT; break;
case 'never': flags |= X509_CHECK_FLAG_NEVER_CHECK_SUBJECT; break;
default:
throw new ERR_INVALID_ARG_VALUE('options.subject', subject);
}
if (!wildcards) flags |= X509_CHECK_FLAG_NO_WILDCARDS;
if (!partialWildcards) flags |= X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS;
if (multiLabelWildcards) flags |= X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS;
if (singleLabelSubdomains) flags |= X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS;
return flags;
}
class InternalX509Certificate {
[kInternalState] = new SafeMap();
constructor(handle) {
markTransferMode(this, true, false);
this[kHandle] = handle;
}
}
class X509Certificate {
[kInternalState] = new SafeMap();
constructor(buffer) {
if (typeof buffer === 'string')
buffer = Buffer.from(buffer);
if (!isArrayBufferView(buffer)) {
throw new ERR_INVALID_ARG_TYPE(
'buffer',
['string', 'Buffer', 'TypedArray', 'DataView'],
buffer);
}
markTransferMode(this, true, false);
this[kHandle] = parseX509(buffer);
}
[kInspect](depth, options) {
if (depth < 0)
return this;
const opts = {
...options,
depth: options.depth == null ? null : options.depth - 1,
};
return `X509Certificate ${inspect({
subject: this.subject,
subjectAltName: this.subjectAltName,
issuer: this.issuer,
infoAccess: this.infoAccess,
validFrom: this.validFrom,
validTo: this.validTo,
validFromDate: this.validFromDate,
validToDate: this.validToDate,
fingerprint: this.fingerprint,
fingerprint256: this.fingerprint256,
fingerprint512: this.fingerprint512,
keyUsage: this.keyUsage,
serialNumber: this.serialNumber,
}, opts)}`;
}
[kClone]() {
const handle = this[kHandle];
return {
data: { handle },
deserializeInfo: 'internal/crypto/x509:InternalX509Certificate',
};
}
[kDeserialize]({ handle }) {
this[kHandle] = handle;
}
get subject() {
let value = this[kInternalState].get('subject');
if (value === undefined) {
value = this[kHandle].subject();
this[kInternalState].set('subject', value);
}
return value;
}
get subjectAltName() {
let value = this[kInternalState].get('subjectAltName');
if (value === undefined) {
value = this[kHandle].subjectAltName();
this[kInternalState].set('subjectAltName', value);
}
return value;
}
get issuer() {
let value = this[kInternalState].get('issuer');
if (value === undefined) {
value = this[kHandle].issuer();
this[kInternalState].set('issuer', value);
}
return value;
}
get issuerCertificate() {
let value = this[kInternalState].get('issuerCertificate');
if (value === undefined) {
const cert = this[kHandle].getIssuerCert();
if (cert)
value = new InternalX509Certificate(this[kHandle].getIssuerCert());
this[kInternalState].set('issuerCertificate', value);
}
return value;
}
get infoAccess() {
let value = this[kInternalState].get('infoAccess');
if (value === undefined) {
value = this[kHandle].infoAccess();
this[kInternalState].set('infoAccess', value);
}
return value;
}
get validFrom() {
let value = this[kInternalState].get('validFrom');
if (value === undefined) {
value = this[kHandle].validFrom();
this[kInternalState].set('validFrom', value);
}
return value;
}
get validTo() {
let value = this[kInternalState].get('validTo');
if (value === undefined) {
value = this[kHandle].validTo();
this[kInternalState].set('validTo', value);
}
return value;
}
get validFromDate() {
let value = this[kInternalState].get('validFromDate');
if (value === undefined) {
value = this[kHandle].validFromDate();
this[kInternalState].set('validFromDate', value);
}
return value;
}
get validToDate() {
let value = this[kInternalState].get('validToDate');
if (value === undefined) {
value = this[kHandle].validToDate();
this[kInternalState].set('validToDate', value);
}
return value;
}
get fingerprint() {
let value = this[kInternalState].get('fingerprint');
if (value === undefined) {
value = this[kHandle].fingerprint();
this[kInternalState].set('fingerprint', value);
}
return value;
}
get fingerprint256() {
let value = this[kInternalState].get('fingerprint256');
if (value === undefined) {
value = this[kHandle].fingerprint256();
this[kInternalState].set('fingerprint256', value);
}
return value;
}
get fingerprint512() {
let value = this[kInternalState].get('fingerprint512');
if (value === undefined) {
value = this[kHandle].fingerprint512();
this[kInternalState].set('fingerprint512', value);
}
return value;
}
get keyUsage() {
let value = this[kInternalState].get('keyUsage');
if (value === undefined) {
value = this[kHandle].keyUsage();
this[kInternalState].set('keyUsage', value);
}
return value;
}
get serialNumber() {
let value = this[kInternalState].get('serialNumber');
if (value === undefined) {
value = this[kHandle].serialNumber();
this[kInternalState].set('serialNumber', value);
}
return value;
}
get raw() {
let value = this[kInternalState].get('raw');
if (value === undefined) {
value = this[kHandle].raw();
this[kInternalState].set('raw', value);
}
return value;
}
get publicKey() {
let value = this[kInternalState].get('publicKey');
if (value === undefined) {
value = new PublicKeyObject(this[kHandle].publicKey());
this[kInternalState].set('publicKey', value);
}
return value;
}
toString() {
let value = this[kInternalState].get('pem');
if (value === undefined) {
value = this[kHandle].pem();
this[kInternalState].set('pem', value);
}
return value;
}
// There's no standardized JSON encoding for X509 certs so we
// fallback to providing the PEM encoding as a string.
toJSON() { return this.toString(); }
get ca() {
let value = this[kInternalState].get('ca');
if (value === undefined) {
value = this[kHandle].checkCA();
this[kInternalState].set('ca', value);
}
return value;
}
checkHost(name, options) {
validateString(name, 'name');
return this[kHandle].checkHost(name, getFlags(options));
}
checkEmail(email, options) {
validateString(email, 'email');
return this[kHandle].checkEmail(email, getFlags(options));
}
checkIP(ip, options) {
validateString(ip, 'ip');
// The options argument is currently undocumented since none of the options
// have any effect on the behavior of this function. However, we still parse
// the options argument in case OpenSSL adds flags in the future that do
// affect the behavior of X509_check_ip. This ensures that no invalid values
// are passed as the second argument in the meantime.
return this[kHandle].checkIP(ip, getFlags(options));
}
checkIssued(otherCert) {
if (!isX509Certificate(otherCert))
throw new ERR_INVALID_ARG_TYPE('otherCert', 'X509Certificate', otherCert);
return this[kHandle].checkIssued(otherCert[kHandle]);
}
checkPrivateKey(pkey) {
if (!isKeyObject(pkey))
throw new ERR_INVALID_ARG_TYPE('pkey', 'KeyObject', pkey);
if (pkey.type !== 'private')
throw new ERR_INVALID_ARG_VALUE('pkey', pkey);
return this[kHandle].checkPrivateKey(pkey[kHandle]);
}
verify(pkey) {
if (!isKeyObject(pkey))
throw new ERR_INVALID_ARG_TYPE('pkey', 'KeyObject', pkey);
if (pkey.type !== 'public')
throw new ERR_INVALID_ARG_VALUE('pkey', pkey);
return this[kHandle].verify(pkey[kHandle]);
}
toLegacyObject() {
// TODO(tniessen): do not depend on translatePeerCertificate here, return
// the correct legacy representation from the binding
lazyTranslatePeerCertificate ??=
require('_tls_common').translatePeerCertificate;
return lazyTranslatePeerCertificate(this[kHandle].toLegacy());
}
}
InternalX509Certificate.prototype.constructor = X509Certificate;
ObjectSetPrototypeOf(
InternalX509Certificate.prototype,
X509Certificate.prototype);
module.exports = {
X509Certificate,
InternalX509Certificate,
isX509Certificate,
};

352
lib/internal/data_url.js Normal file
View File

@ -0,0 +1,352 @@
'use strict';
const {
RegExpPrototypeExec,
RegExpPrototypeSymbolReplace,
StringFromCharCodeApply,
StringPrototypeCharCodeAt,
StringPrototypeIndexOf,
StringPrototypeSlice,
TypedArrayPrototypeSubarray,
Uint8Array,
} = primordials;
const assert = require('internal/assert');
const { Buffer } = require('buffer');
const { MIMEType } = require('internal/mime');
let encoder;
function lazyEncoder() {
if (encoder === undefined) {
const { TextEncoder } = require('internal/encoding');
encoder = new TextEncoder();
}
return encoder;
}
const ASCII_WHITESPACE_REPLACE_REGEX = /[\u0009\u000A\u000C\u000D\u0020]/g // eslint-disable-line
// https://fetch.spec.whatwg.org/#data-url-processor
/** @param {URL} dataURL */
function dataURLProcessor(dataURL) {
// 1. Assert: dataURL's scheme is "data".
assert(dataURL.protocol === 'data:');
// 2. Let input be the result of running the URL
// serializer on dataURL with exclude fragment
// set to true.
let input = URLSerializer(dataURL, true);
// 3. Remove the leading "data:" string from input.
input = StringPrototypeSlice(input, 5);
// 4. Let position point at the start of input.
const position = { position: 0 };
// 5. Let mimeType be the result of collecting a
// sequence of code points that are not equal
// to U+002C (,), given position.
let mimeType = collectASequenceOfCodePointsFast(
',',
input,
position,
);
// 6. Strip leading and trailing ASCII whitespace
// from mimeType.
// Undici implementation note: we need to store the
// length because if the mimetype has spaces removed,
// the wrong amount will be sliced from the input in
// step #9
const mimeTypeLength = mimeType.length;
mimeType = removeASCIIWhitespace(mimeType, true, true);
// 7. If position is past the end of input, then
// return failure
if (position.position >= input.length) {
return 'failure';
}
// 8. Advance position by 1.
position.position++;
// 9. Let encodedBody be the remainder of input.
const encodedBody = StringPrototypeSlice(input, mimeTypeLength + 1);
// 10. Let body be the percent-decoding of encodedBody.
let body = stringPercentDecode(encodedBody);
// 11. If mimeType ends with U+003B (;), followed by
// zero or more U+0020 SPACE, followed by an ASCII
// case-insensitive match for "base64", then:
if (RegExpPrototypeExec(/;(\u0020){0,}base64$/i, mimeType) !== null) {
// 1. Let stringBody be the isomorphic decode of body.
const stringBody = isomorphicDecode(body);
// 2. Set body to the forgiving-base64 decode of
// stringBody.
body = forgivingBase64(stringBody);
// 3. If body is failure, then return failure.
if (body === 'failure') {
return 'failure';
}
// 4. Remove the last 6 code points from mimeType.
mimeType = StringPrototypeSlice(mimeType, 0, -6);
// 5. Remove trailing U+0020 SPACE code points from mimeType,
// if any.
mimeType = RegExpPrototypeSymbolReplace(/(\u0020)+$/, mimeType, '');
// 6. Remove the last U+003B (;) code point from mimeType.
mimeType = StringPrototypeSlice(mimeType, 0, -1);
}
// 12. If mimeType starts with U+003B (;), then prepend
// "text/plain" to mimeType.
if (mimeType[0] === ';') {
mimeType = 'text/plain' + mimeType;
}
// 13. Let mimeTypeRecord be the result of parsing
// mimeType.
// 14. If mimeTypeRecord is failure, then set
// mimeTypeRecord to text/plain;charset=US-ASCII.
let mimeTypeRecord;
try {
mimeTypeRecord = new MIMEType(mimeType);
} catch {
mimeTypeRecord = new MIMEType('text/plain;charset=US-ASCII');
}
// 15. Return a new data: URL struct whose MIME
// type is mimeTypeRecord and body is body.
// https://fetch.spec.whatwg.org/#data-url-struct
return { mimeType: mimeTypeRecord, body };
}
// https://url.spec.whatwg.org/#concept-url-serializer
/**
* @param {URL} url
* @param {boolean} excludeFragment
*/
function URLSerializer(url, excludeFragment = false) {
const { href } = url;
if (!excludeFragment) {
return href;
}
const hashLength = url.hash.length;
const serialized = hashLength === 0 ? href : StringPrototypeSlice(href, 0, href.length - hashLength);
if (!hashLength && href[href.length - 1] === '#') {
return StringPrototypeSlice(serialized, 0, -1);
}
return serialized;
}
/**
* A faster collectASequenceOfCodePoints that only works when comparing a single character.
* @param {string} char
* @param {string} input
* @param {{ position: number }} position
*/
function collectASequenceOfCodePointsFast(char, input, position) {
const idx = StringPrototypeIndexOf(input, char, position.position);
const start = position.position;
if (idx === -1) {
position.position = input.length;
return StringPrototypeSlice(input, start);
}
position.position = idx;
return StringPrototypeSlice(input, start, position.position);
}
// https://url.spec.whatwg.org/#string-percent-decode
/** @param {string} input */
function stringPercentDecode(input) {
// 1. Let bytes be the UTF-8 encoding of input.
const bytes = lazyEncoder().encode(input);
// 2. Return the percent-decoding of bytes.
return percentDecode(bytes);
}
/**
* @param {number} byte
*/
function isHexCharByte(byte) {
// 0-9 A-F a-f
return (byte >= 0x30 && byte <= 0x39) || (byte >= 0x41 && byte <= 0x46) || (byte >= 0x61 && byte <= 0x66);
}
/**
* @param {number} byte
*/
function hexByteToNumber(byte) {
return (
// 0-9
byte >= 0x30 && byte <= 0x39 ?
(byte - 48) :
// Convert to uppercase
// ((byte & 0xDF) - 65) + 10
((byte & 0xDF) - 55)
);
}
// https://url.spec.whatwg.org/#percent-decode
/** @param {Uint8Array} input */
function percentDecode(input) {
const length = input.length;
// 1. Let output be an empty byte sequence.
/** @type {Uint8Array} */
const output = new Uint8Array(length);
let j = 0;
// 2. For each byte byte in input:
for (let i = 0; i < length; ++i) {
const byte = input[i];
// 1. If byte is not 0x25 (%), then append byte to output.
if (byte !== 0x25) {
output[j++] = byte;
// 2. Otherwise, if byte is 0x25 (%) and the next two bytes
// after byte in input are not in the ranges
// 0x30 (0) to 0x39 (9), 0x41 (A) to 0x46 (F),
// and 0x61 (a) to 0x66 (f), all inclusive, append byte
// to output.
} else if (
byte === 0x25 &&
!(isHexCharByte(input[i + 1]) && isHexCharByte(input[i + 2]))
) {
output[j++] = 0x25;
// 3. Otherwise:
} else {
// 1. Let bytePoint be the two bytes after byte in input,
// decoded, and then interpreted as hexadecimal number.
// 2. Append a byte whose value is bytePoint to output.
output[j++] = (hexByteToNumber(input[i + 1]) << 4) | hexByteToNumber(input[i + 2]);
// 3. Skip the next two bytes in input.
i += 2;
}
}
// 3. Return output.
return length === j ? output : TypedArrayPrototypeSubarray(output, 0, j);
}
// https://infra.spec.whatwg.org/#forgiving-base64-decode
/** @param {string} data */
function forgivingBase64(data) {
// 1. Remove all ASCII whitespace from data.
data = RegExpPrototypeSymbolReplace(ASCII_WHITESPACE_REPLACE_REGEX, data, '');
let dataLength = data.length;
// 2. If data's code point length divides by 4 leaving
// no remainder, then:
if (dataLength % 4 === 0) {
// 1. If data ends with one or two U+003D (=) code points,
// then remove them from data.
if (data[dataLength - 1] === '=') {
--dataLength;
if (data[dataLength - 1] === '=') {
--dataLength;
}
}
}
// 3. If data's code point length divides by 4 leaving
// a remainder of 1, then return failure.
if (dataLength % 4 === 1) {
return 'failure';
}
// 4. If data contains a code point that is not one of
// U+002B (+)
// U+002F (/)
// ASCII alphanumeric
// then return failure.
if (RegExpPrototypeExec(/[^+/0-9A-Za-z]/, data.length === dataLength ? data : StringPrototypeSlice(data, 0, dataLength)) !== null) {
return 'failure';
}
const buffer = Buffer.from(data, 'base64');
return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
}
/**
* @see https://infra.spec.whatwg.org/#ascii-whitespace
* @param {number} char
*/
function isASCIIWhitespace(char) {
// "\r\n\t\f "
return char === 0x00d || char === 0x00a || char === 0x009 || char === 0x00c || char === 0x020;
}
/**
* @see https://infra.spec.whatwg.org/#strip-leading-and-trailing-ascii-whitespace
* @param {string} str
* @param {boolean} [leading=true]
* @param {boolean} [trailing=true]
*/
function removeASCIIWhitespace(str, leading = true, trailing = true) {
return removeChars(str, leading, trailing, isASCIIWhitespace);
}
/**
* @param {string} str
* @param {boolean} leading
* @param {boolean} trailing
* @param {(charCode: number) => boolean} predicate
*/
function removeChars(str, leading, trailing, predicate) {
let lead = 0;
let trail = str.length - 1;
if (leading) {
while (lead < str.length && predicate(StringPrototypeCharCodeAt(str, lead))) lead++;
}
if (trailing) {
while (trail > 0 && predicate(StringPrototypeCharCodeAt(str, trail))) trail--;
}
return lead === 0 && trail === str.length - 1 ? str : StringPrototypeSlice(str, lead, trail + 1);
}
/**
* @see https://infra.spec.whatwg.org/#isomorphic-decode
* @param {Uint8Array} input
* @returns {string}
*/
function isomorphicDecode(input) {
// 1. To isomorphic decode a byte sequence input, return a string whose code point
// length is equal to input's length and whose code points have the same values
// as the values of input's bytes, in the same order.
const length = input.length;
if ((2 << 15) - 1 > length) {
return StringFromCharCodeApply(input);
}
let result = ''; let i = 0;
let addition = (2 << 15) - 1;
while (i < length) {
if (i + addition > length) {
addition = length - i;
}
result += StringFromCharCodeApply(TypedArrayPrototypeSubarray(input, i, i += addition));
}
return result;
}
module.exports = {
dataURLProcessor,
};

View File

@ -0,0 +1,366 @@
'use strict';
const {
ArrayPrototypeForEach,
ArrayPrototypeJoin,
ArrayPrototypeMap,
ArrayPrototypePop,
ArrayPrototypePushApply,
ArrayPrototypeShift,
ArrayPrototypeSlice,
FunctionPrototypeBind,
Number,
Promise,
PromisePrototypeThen,
PromiseResolve,
Proxy,
RegExpPrototypeExec,
RegExpPrototypeSymbolSplit,
StringPrototypeEndsWith,
StringPrototypeSplit,
} = primordials;
const { spawn } = require('child_process');
const { EventEmitter } = require('events');
const net = require('net');
const util = require('util');
const {
setInterval: pSetInterval,
setTimeout: pSetTimeout,
} = require('timers/promises');
const {
AbortController,
} = require('internal/abort_controller');
const { 0: InspectClient, 1: createRepl } =
[
require('internal/debugger/inspect_client'),
require('internal/debugger/inspect_repl'),
];
const debuglog = util.debuglog('inspect');
const { ERR_DEBUGGER_STARTUP_ERROR } = require('internal/errors').codes;
const {
exitCodes: {
kGenericUserError,
kNoFailure,
kInvalidCommandLineArgument,
},
} = internalBinding('errors');
async function portIsFree(host, port, timeout = 3000) {
if (port === 0) return; // Binding to a random port.
const retryDelay = 150;
const ac = new AbortController();
const { signal } = ac;
pSetTimeout(timeout).then(() => ac.abort());
const asyncIterator = pSetInterval(retryDelay);
while (true) {
await asyncIterator.next();
if (signal.aborted) {
throw new ERR_DEBUGGER_STARTUP_ERROR(
`Timeout (${timeout}) waiting for ${host}:${port} to be free`);
}
const error = await new Promise((resolve) => {
const socket = net.connect(port, host);
socket.on('error', resolve);
socket.on('connect', () => {
socket.end();
resolve();
});
});
if (error?.code === 'ECONNREFUSED') {
return;
}
}
}
const debugRegex = /Debugger listening on ws:\/\/\[?(.+?)\]?:(\d+)\//;
async function runScript(script, scriptArgs, inspectHost, inspectPort,
childPrint) {
await portIsFree(inspectHost, inspectPort);
const args = [`--inspect-brk=${inspectPort}`, script];
ArrayPrototypePushApply(args, scriptArgs);
const child = spawn(process.execPath, args);
child.stdout.setEncoding('utf8');
child.stderr.setEncoding('utf8');
child.stdout.on('data', (chunk) => childPrint(chunk, 'stdout'));
child.stderr.on('data', (chunk) => childPrint(chunk, 'stderr'));
let output = '';
return new Promise((resolve) => {
function waitForListenHint(text) {
output += text;
const debug = RegExpPrototypeExec(debugRegex, output);
if (debug) {
const host = debug[1];
const port = Number(debug[2]);
child.stderr.removeListener('data', waitForListenHint);
resolve([child, port, host]);
}
}
child.stderr.on('data', waitForListenHint);
});
}
function createAgentProxy(domain, client) {
const agent = new EventEmitter();
agent.then = (then, _catch) => {
// TODO: potentially fetch the protocol and pretty-print it here.
const descriptor = {
[util.inspect.custom](depth, { stylize }) {
return stylize(`[Agent ${domain}]`, 'special');
},
};
return PromisePrototypeThen(PromiseResolve(descriptor), then, _catch);
};
return new Proxy(agent, {
__proto__: null,
get(target, name) {
if (name in target) return target[name];
return function callVirtualMethod(params) {
return client.callMethod(`${domain}.${name}`, params);
};
},
});
}
class NodeInspector {
constructor(options, stdin, stdout) {
this.options = options;
this.stdin = stdin;
this.stdout = stdout;
this.paused = true;
this.child = null;
if (options.script) {
this._runScript = FunctionPrototypeBind(
runScript, null,
options.script,
options.scriptArgs,
options.host,
options.port,
FunctionPrototypeBind(this.childPrint, this));
} else {
this._runScript =
() => PromiseResolve([null, options.port, options.host]);
}
this.client = new InspectClient();
this.domainNames = ['Debugger', 'HeapProfiler', 'Profiler', 'Runtime'];
ArrayPrototypeForEach(this.domainNames, (domain) => {
this[domain] = createAgentProxy(domain, this.client);
});
this.handleDebugEvent = (fullName, params) => {
const { 0: domain, 1: name } = StringPrototypeSplit(fullName, '.', 2);
if (domain in this) {
this[domain].emit(name, params);
}
};
this.client.on('debugEvent', this.handleDebugEvent);
const startRepl = createRepl(this);
// Handle all possible exits
process.on('exit', () => this.killChild());
const exitCodeZero = () => process.exit(kNoFailure);
process.once('SIGTERM', exitCodeZero);
process.once('SIGHUP', exitCodeZero);
(async () => {
try {
await this.run();
const repl = await startRepl();
this.repl = repl;
this.repl.on('exit', exitCodeZero);
this.paused = false;
} catch (error) {
process.nextTick(() => { throw error; });
}
})();
}
suspendReplWhile(fn) {
if (this.repl) {
this.repl.pause();
}
this.stdin.pause();
this.paused = true;
return (async () => {
try {
await fn();
this.paused = false;
if (this.repl) {
this.repl.resume();
this.repl.displayPrompt();
}
this.stdin.resume();
} catch (error) {
process.nextTick(() => { throw error; });
}
})();
}
killChild() {
this.client.reset();
if (this.child) {
this.child.kill();
this.child = null;
}
}
async run() {
this.killChild();
const { 0: child, 1: port, 2: host } = await this._runScript();
this.child = child;
this.print(`connecting to ${host}:${port} ..`, false);
for (let attempt = 0; attempt < 5; attempt++) {
debuglog('connection attempt #%d', attempt);
this.stdout.write('.');
try {
await this.client.connect(port, host);
debuglog('connection established');
this.stdout.write(' ok\n');
return;
} catch (error) {
debuglog('connect failed', error);
await pSetTimeout(1000);
}
}
this.stdout.write(' failed to connect, please retry\n');
process.exit(kGenericUserError);
}
clearLine() {
if (this.stdout.isTTY) {
this.stdout.cursorTo(0);
this.stdout.clearLine(1);
} else {
this.stdout.write('\b');
}
}
print(text, appendNewline = false) {
this.clearLine();
this.stdout.write(appendNewline ? `${text}\n` : text);
}
#stdioBuffers = { stdout: '', stderr: '' };
childPrint(text, which) {
const lines = RegExpPrototypeSymbolSplit(
/\r\n|\r|\n/g,
this.#stdioBuffers[which] + text);
this.#stdioBuffers[which] = '';
if (lines[lines.length - 1] !== '') {
this.#stdioBuffers[which] = ArrayPrototypePop(lines);
}
const textToPrint = ArrayPrototypeJoin(
ArrayPrototypeMap(lines, (chunk) => `< ${chunk}`),
'\n');
if (lines.length) {
this.print(textToPrint, true);
if (!this.paused) {
this.repl.displayPrompt(true);
}
}
if (StringPrototypeEndsWith(
textToPrint,
'Waiting for the debugger to disconnect...\n',
)) {
this.killChild();
}
}
}
function parseArgv(args) {
const target = ArrayPrototypeShift(args);
let host = '127.0.0.1';
let port = 9229;
let isRemote = false;
let script = target;
let scriptArgs = args;
const hostMatch = RegExpPrototypeExec(/^([^:]+):(\d+)$/, target);
const portMatch = RegExpPrototypeExec(/^--port=(\d+)$/, target);
if (hostMatch) {
// Connecting to remote debugger
host = hostMatch[1];
port = Number(hostMatch[2]);
isRemote = true;
script = null;
} else if (portMatch) {
// Start on custom port
port = Number(portMatch[1]);
script = args[0];
scriptArgs = ArrayPrototypeSlice(args, 1);
} else if (args.length === 1 && RegExpPrototypeExec(/^\d+$/, args[0]) !== null &&
target === '-p') {
// Start debugger against a given pid
const pid = Number(args[0]);
try {
process._debugProcess(pid);
} catch (e) {
if (e.code === 'ESRCH') {
process.stderr.write(`Target process: ${pid} doesn't exist.\n`);
process.exit(kGenericUserError);
}
throw e;
}
script = null;
isRemote = true;
}
return {
host, port, isRemote, script, scriptArgs,
};
}
function startInspect(argv = ArrayPrototypeSlice(process.argv, 2),
stdin = process.stdin,
stdout = process.stdout) {
if (argv.length < 1) {
const invokedAs = `${process.argv0} ${process.argv[1]}`;
process.stderr.write(`Usage: ${invokedAs} script.js\n` +
` ${invokedAs} <host>:<port>\n` +
` ${invokedAs} --port=<port> Use 0 for random port assignment\n` +
` ${invokedAs} -p <pid>\n`);
process.exit(kInvalidCommandLineArgument);
}
const options = parseArgv(argv);
const inspector = new NodeInspector(options, stdin, stdout);
stdin.resume();
function handleUnexpectedError(e) {
if (e.code !== 'ERR_DEBUGGER_STARTUP_ERROR') {
process.stderr.write('There was an internal error in Node.js. ' +
'Please report this bug.\n' +
`${e.message}\n${e.stack}\n`);
} else {
process.stderr.write(e.message);
process.stderr.write('\n');
}
if (inspector.child) inspector.child.kill();
process.exit(kGenericUserError);
}
process.on('uncaughtException', handleUnexpectedError);
}
exports.start = startInspect;

View File

@ -0,0 +1,356 @@
'use strict';
const {
ArrayPrototypePush,
ErrorCaptureStackTrace,
FunctionPrototypeBind,
JSONParse,
JSONStringify,
ObjectKeys,
Promise,
} = primordials;
const Buffer = require('buffer').Buffer;
const crypto = require('crypto');
const { ERR_DEBUGGER_ERROR } = require('internal/errors').codes;
const { EventEmitter } = require('events');
const http = require('http');
const { URL } = require('internal/url');
const debuglog = require('internal/util/debuglog').debuglog('inspect');
const kOpCodeText = 0x1;
const kOpCodeClose = 0x8;
const kFinalBit = 0x80;
const kReserved1Bit = 0x40;
const kReserved2Bit = 0x20;
const kReserved3Bit = 0x10;
const kOpCodeMask = 0xF;
const kMaskBit = 0x80;
const kPayloadLengthMask = 0x7F;
const kMaxSingleBytePayloadLength = 125;
const kMaxTwoBytePayloadLength = 0xFFFF;
const kTwoBytePayloadLengthField = 126;
const kEightBytePayloadLengthField = 127;
const kMaskingKeyWidthInBytes = 4;
// This guid is defined in the Websocket Protocol RFC
// https://tools.ietf.org/html/rfc6455#section-1.3
const WEBSOCKET_HANDSHAKE_GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
function unpackError({ code, message }) {
const err = new ERR_DEBUGGER_ERROR(`${message}`);
err.code = code;
ErrorCaptureStackTrace(err, unpackError);
return err;
}
function validateHandshake(requestKey, responseKey) {
const expectedResponseKeyBase = requestKey + WEBSOCKET_HANDSHAKE_GUID;
const shasum = crypto.createHash('sha1');
shasum.update(expectedResponseKeyBase);
const shabuf = shasum.digest();
if (shabuf.toString('base64') !== responseKey) {
throw new ERR_DEBUGGER_ERROR(
`WebSocket secret mismatch: ${requestKey} did not match ${responseKey}`,
);
}
}
function encodeFrameHybi17(payload) {
const dataLength = payload.length;
let singleByteLength;
let additionalLength;
if (dataLength > kMaxTwoBytePayloadLength) {
singleByteLength = kEightBytePayloadLengthField;
additionalLength = Buffer.alloc(8);
let remaining = dataLength;
for (let i = 0; i < 8; ++i) {
additionalLength[7 - i] = remaining & 0xFF;
remaining >>= 8;
}
} else if (dataLength > kMaxSingleBytePayloadLength) {
singleByteLength = kTwoBytePayloadLengthField;
additionalLength = Buffer.alloc(2);
additionalLength[0] = (dataLength & 0xFF00) >> 8;
additionalLength[1] = dataLength & 0xFF;
} else {
additionalLength = Buffer.alloc(0);
singleByteLength = dataLength;
}
const header = Buffer.from([
kFinalBit | kOpCodeText,
kMaskBit | singleByteLength,
]);
const mask = Buffer.alloc(4);
const masked = Buffer.alloc(dataLength);
for (let i = 0; i < dataLength; ++i) {
masked[i] = payload[i] ^ mask[i % kMaskingKeyWidthInBytes];
}
return Buffer.concat([header, additionalLength, mask, masked]);
}
function decodeFrameHybi17(data) {
const dataAvailable = data.length;
const notComplete = { closed: false, payload: null, rest: data };
let payloadOffset = 2;
if ((dataAvailable - payloadOffset) < 0) return notComplete;
const firstByte = data[0];
const secondByte = data[1];
const final = (firstByte & kFinalBit) !== 0;
const reserved1 = (firstByte & kReserved1Bit) !== 0;
const reserved2 = (firstByte & kReserved2Bit) !== 0;
const reserved3 = (firstByte & kReserved3Bit) !== 0;
const opCode = firstByte & kOpCodeMask;
const masked = (secondByte & kMaskBit) !== 0;
const compressed = reserved1;
if (compressed) {
throw new ERR_DEBUGGER_ERROR('Compressed frames not supported');
}
if (!final || reserved2 || reserved3) {
throw new ERR_DEBUGGER_ERROR('Only compression extension is supported');
}
if (masked) {
throw new ERR_DEBUGGER_ERROR('Masked server frame - not supported');
}
let closed = false;
switch (opCode) {
case kOpCodeClose:
closed = true;
break;
case kOpCodeText:
break;
default:
throw new ERR_DEBUGGER_ERROR(`Unsupported op code ${opCode}`);
}
let payloadLength = secondByte & kPayloadLengthMask;
switch (payloadLength) {
case kTwoBytePayloadLengthField:
payloadOffset += 2;
payloadLength = (data[2] << 8) + data[3];
break;
case kEightBytePayloadLengthField:
payloadOffset += 8;
payloadLength = 0;
for (let i = 0; i < 8; ++i) {
payloadLength <<= 8;
payloadLength |= data[2 + i];
}
break;
default:
// Nothing. We already have the right size.
}
if ((dataAvailable - payloadOffset - payloadLength) < 0) return notComplete;
const payloadEnd = payloadOffset + payloadLength;
return {
payload: data.slice(payloadOffset, payloadEnd),
rest: data.slice(payloadEnd),
closed,
};
}
class Client extends EventEmitter {
constructor() {
super();
this.handleChunk = FunctionPrototypeBind(this._handleChunk, this);
this._port = undefined;
this._host = undefined;
this.reset();
}
_handleChunk(chunk) {
this._unprocessed = Buffer.concat([this._unprocessed, chunk]);
while (this._unprocessed.length > 2) {
const {
closed,
payload: payloadBuffer,
rest,
} = decodeFrameHybi17(this._unprocessed);
this._unprocessed = rest;
if (closed) {
this.reset();
return;
}
if (payloadBuffer === null || payloadBuffer.length === 0) break;
const payloadStr = payloadBuffer.toString();
debuglog('< %s', payloadStr);
const lastChar = payloadStr[payloadStr.length - 1];
if (payloadStr[0] !== '{' || lastChar !== '}') {
throw new ERR_DEBUGGER_ERROR(`Payload does not look like JSON: ${payloadStr}`);
}
let payload;
try {
payload = JSONParse(payloadStr);
} catch (parseError) {
parseError.string = payloadStr;
throw parseError;
}
const { id, method, params, result, error } = payload;
if (id) {
const handler = this._pending[id];
if (handler) {
delete this._pending[id];
handler(error, result);
}
} else if (method) {
this.emit('debugEvent', method, params);
this.emit(method, params);
} else {
throw new ERR_DEBUGGER_ERROR(`Unsupported response: ${payloadStr}`);
}
}
}
reset() {
if (this._http) {
this._http.destroy();
}
if (this._socket) {
this._socket.destroy();
}
this._http = null;
this._lastId = 0;
this._socket = null;
this._pending = {};
this._unprocessed = Buffer.alloc(0);
}
callMethod(method, params) {
return new Promise((resolve, reject) => {
if (!this._socket) {
reject(new ERR_DEBUGGER_ERROR('Use `run` to start the app again.'));
return;
}
const data = { id: ++this._lastId, method, params };
this._pending[data.id] = (error, result) => {
if (error) reject(unpackError(error));
else resolve(ObjectKeys(result).length ? result : undefined);
};
const json = JSONStringify(data);
debuglog('> %s', json);
this._socket.write(encodeFrameHybi17(Buffer.from(json)));
});
}
_fetchJSON(urlPath) {
return new Promise((resolve, reject) => {
const httpReq = http.get({
host: this._host,
port: this._port,
path: urlPath,
});
const chunks = [];
function onResponse(httpRes) {
function parseChunks() {
const resBody = Buffer.concat(chunks).toString();
if (httpRes.statusCode !== 200) {
reject(new ERR_DEBUGGER_ERROR(`Unexpected ${httpRes.statusCode}: ${resBody}`));
return;
}
try {
resolve(JSONParse(resBody));
} catch {
reject(new ERR_DEBUGGER_ERROR(`Response didn't contain JSON: ${resBody}`));
}
}
httpRes.on('error', reject);
httpRes.on('data', (chunk) => ArrayPrototypePush(chunks, chunk));
httpRes.on('end', parseChunks);
}
httpReq.on('error', reject);
httpReq.on('response', onResponse);
});
}
async connect(port, host) {
this._port = port;
this._host = host;
const urlPath = await this._discoverWebsocketPath();
return this._connectWebsocket(urlPath);
}
async _discoverWebsocketPath() {
const { 0: { webSocketDebuggerUrl } } = await this._fetchJSON('/json');
const { pathname, search } = new URL(webSocketDebuggerUrl);
return `${pathname}${search}`;
}
_connectWebsocket(urlPath) {
this.reset();
const requestKey = crypto.randomBytes(16).toString('base64');
debuglog('request WebSocket', requestKey);
const httpReq = this._http = http.request({
host: this._host,
port: this._port,
path: urlPath,
headers: {
'Connection': 'Upgrade',
'Upgrade': 'websocket',
'Sec-WebSocket-Key': requestKey,
'Sec-WebSocket-Version': '13',
},
});
httpReq.on('error', (e) => {
this.emit('error', e);
});
httpReq.on('response', (httpRes) => {
if (httpRes.statusCode >= 400) {
process.stderr.write(`Unexpected HTTP code: ${httpRes.statusCode}\n`);
httpRes.pipe(process.stderr);
} else {
httpRes.pipe(process.stderr);
}
});
const handshakeListener = (res, socket) => {
validateHandshake(requestKey, res.headers['sec-websocket-accept']);
debuglog('websocket upgrade');
this._socket = socket;
socket.on('data', this.handleChunk);
socket.on('close', () => {
this.emit('close');
});
this.emit('ready');
};
return new Promise((resolve, reject) => {
this.once('error', reject);
this.once('ready', resolve);
httpReq.on('upgrade', handshakeListener);
httpReq.end();
});
}
}
module.exports = Client;

File diff suppressed because it is too large Load Diff

91
lib/internal/dgram.js Normal file
View File

@ -0,0 +1,91 @@
'use strict';
const {
FunctionPrototypeBind,
Symbol,
} = primordials;
const { codes: {
ERR_SOCKET_BAD_TYPE,
} } = require('internal/errors');
const { UDP } = internalBinding('udp_wrap');
const { guessHandleType } = require('internal/util');
const {
isInt32,
validateFunction,
} = require('internal/validators');
const { UV_EINVAL } = internalBinding('uv');
const kStateSymbol = Symbol('state symbol');
let dns; // Lazy load for startup performance.
function lookup4(lookup, address, callback) {
return lookup(address || '127.0.0.1', 4, callback);
}
function lookup6(lookup, address, callback) {
return lookup(address || '::1', 6, callback);
}
function newHandle(type, lookup) {
if (lookup === undefined) {
if (dns === undefined) {
dns = require('dns');
}
lookup = dns.lookup;
} else {
validateFunction(lookup, 'lookup');
}
if (type === 'udp4') {
const handle = new UDP();
handle.lookup = FunctionPrototypeBind(lookup4, handle, lookup);
return handle;
}
if (type === 'udp6') {
const handle = new UDP();
handle.lookup = FunctionPrototypeBind(lookup6, handle, lookup);
handle.bind = handle.bind6;
handle.connect = handle.connect6;
handle.send = handle.send6;
return handle;
}
throw new ERR_SOCKET_BAD_TYPE();
}
function _createSocketHandle(address, port, addressType, fd, flags) {
const handle = newHandle(addressType);
let err;
if (isInt32(fd) && fd > 0) {
const type = guessHandleType(fd);
if (type !== 'UDP') {
err = UV_EINVAL;
} else {
err = handle.open(fd);
}
} else if (port || address) {
err = handle.bind(address, port || 0, flags);
}
if (err) {
handle.close();
return err;
}
return handle;
}
module.exports = {
kStateSymbol,
_createSocketHandle,
newHandle,
};

View File

@ -0,0 +1,114 @@
'use strict';
const {
ArrayPrototypeMap,
ObjectDefineProperty,
ReflectApply,
Symbol,
} = primordials;
const {
DNSException,
codes: {
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
},
} = require('internal/errors');
const {
createResolverClass,
} = require('internal/dns/utils');
const {
validateFunction,
validateString,
} = require('internal/validators');
const {
QueryReqWrap,
} = internalBinding('cares_wrap');
const {
hasObserver,
startPerf,
stopPerf,
} = require('internal/perf/observe');
const kPerfHooksDnsLookupResolveContext = Symbol('kPerfHooksDnsLookupResolveContext');
function onresolve(err, result, ttls) {
if (ttls && this.ttl)
result = ArrayPrototypeMap(
result, (address, index) => ({ address, ttl: ttls[index] }));
if (err)
this.callback(new DNSException(err, this.bindingName, this.hostname));
else {
this.callback(null, result);
if (this[kPerfHooksDnsLookupResolveContext] && hasObserver('dns')) {
stopPerf(this, kPerfHooksDnsLookupResolveContext, { detail: { result } });
}
}
}
function resolver(bindingName) {
function query(name, /* options, */ callback) {
let options;
if (arguments.length > 2) {
options = callback;
callback = arguments[2];
}
validateString(name, 'name');
validateFunction(callback, 'callback');
const req = new QueryReqWrap();
req.bindingName = bindingName;
req.callback = callback;
req.hostname = name;
req.oncomplete = onresolve;
req.ttl = !!(options?.ttl);
const err = this._handle[bindingName](req, name);
if (err) throw new DNSException(err, bindingName, name);
if (hasObserver('dns')) {
startPerf(req, kPerfHooksDnsLookupResolveContext, {
type: 'dns',
name: bindingName,
detail: {
host: name,
ttl: req.ttl,
},
});
}
return req;
}
ObjectDefineProperty(query, 'name', { __proto__: null, value: bindingName });
return query;
}
// This is the callback-based resolver. There is another similar
// resolver in dns/promises.js with resolve methods that are based
// on promises instead.
const { Resolver, resolveMap } = createResolverClass(resolver);
Resolver.prototype.resolve = resolve;
function resolve(hostname, rrtype, callback) {
let resolver;
if (typeof rrtype === 'string') {
resolver = resolveMap[rrtype];
} else if (typeof rrtype === 'function') {
resolver = resolveMap.A;
callback = rrtype;
} else {
throw new ERR_INVALID_ARG_TYPE('rrtype', 'string', rrtype);
}
if (typeof resolver === 'function') {
return ReflectApply(resolver, this, [hostname, callback]);
}
throw new ERR_INVALID_ARG_VALUE('rrtype', rrtype);
}
module.exports = {
Resolver,
};

View File

@ -0,0 +1,411 @@
'use strict';
const {
ArrayPrototypeMap,
ObjectDefineProperty,
Promise,
ReflectApply,
Symbol,
} = primordials;
const {
bindDefaultResolver,
createResolverClass,
validateHints,
emitInvalidHostnameWarning,
errorCodes: dnsErrorCodes,
getDefaultResultOrder,
setDefaultResultOrder,
setDefaultResolver,
validDnsOrders,
validFamilies,
} = require('internal/dns/utils');
const {
NODATA,
FORMERR,
SERVFAIL,
NOTFOUND,
NOTIMP,
REFUSED,
BADQUERY,
BADNAME,
BADFAMILY,
BADRESP,
CONNREFUSED,
TIMEOUT,
EOF,
FILE,
NOMEM,
DESTRUCTION,
BADSTR,
BADFLAGS,
NONAME,
BADHINTS,
NOTINITIALIZED,
LOADIPHLPAPI,
ADDRGETNETWORKPARAMS,
CANCELLED,
} = dnsErrorCodes;
const {
DNSException,
codes: {
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
ERR_MISSING_ARGS,
},
} = require('internal/errors');
const { isIP } = require('internal/net');
const {
getaddrinfo,
getnameinfo,
GetAddrInfoReqWrap,
GetNameInfoReqWrap,
QueryReqWrap,
DNS_ORDER_VERBATIM,
DNS_ORDER_IPV4_FIRST,
DNS_ORDER_IPV6_FIRST,
} = internalBinding('cares_wrap');
const {
validateBoolean,
validateNumber,
validateOneOf,
validatePort,
validateString,
} = require('internal/validators');
const kPerfHooksDnsLookupContext = Symbol('kPerfHooksDnsLookupContext');
const kPerfHooksDnsLookupServiceContext = Symbol('kPerfHooksDnsLookupServiceContext');
const kPerfHooksDnsLookupResolveContext = Symbol('kPerfHooksDnsLookupResolveContext');
const {
hasObserver,
startPerf,
stopPerf,
} = require('internal/perf/observe');
function onlookup(err, addresses) {
if (err) {
this.reject(new DNSException(err, 'getaddrinfo', this.hostname));
return;
}
const family = this.family || isIP(addresses[0]);
this.resolve({ address: addresses[0], family });
if (this[kPerfHooksDnsLookupContext] && hasObserver('dns')) {
stopPerf(this, kPerfHooksDnsLookupContext, { detail: { addresses } });
}
}
function onlookupall(err, addresses) {
if (err) {
this.reject(new DNSException(err, 'getaddrinfo', this.hostname));
return;
}
const family = this.family;
for (let i = 0; i < addresses.length; i++) {
const address = addresses[i];
addresses[i] = {
address,
family: family || isIP(addresses[i]),
};
}
this.resolve(addresses);
if (this[kPerfHooksDnsLookupContext] && hasObserver('dns')) {
stopPerf(this, kPerfHooksDnsLookupContext, { detail: { addresses } });
}
}
/**
* Creates a promise that resolves with the IP address of the given hostname.
* @param {0 | 4 | 6} family - The IP address family (4 or 6, or 0 for both).
* @param {string} hostname - The hostname to resolve.
* @param {boolean} all - Whether to resolve with all IP addresses for the hostname.
* @param {number} hints - One or more supported getaddrinfo flags (supply multiple via
* bitwise OR).
* @param {number} dnsOrder - How to sort results. Must be `ipv4first`, `ipv6first` or `verbatim`.
* @returns {Promise<DNSLookupResult | DNSLookupResult[]>} The IP address(es) of the hostname.
* @typedef {object} DNSLookupResult
* @property {string} address - The IP address.
* @property {0 | 4 | 6} family - The IP address type. 4 for IPv4 or 6 for IPv6, or 0 (for both).
*/
function createLookupPromise(family, hostname, all, hints, dnsOrder) {
return new Promise((resolve, reject) => {
if (!hostname) {
emitInvalidHostnameWarning(hostname);
resolve(all ? [] : { address: null, family: family === 6 ? 6 : 4 });
return;
}
const matchedFamily = isIP(hostname);
if (matchedFamily !== 0) {
const result = { address: hostname, family: matchedFamily };
resolve(all ? [result] : result);
return;
}
const req = new GetAddrInfoReqWrap();
req.family = family;
req.hostname = hostname;
req.oncomplete = all ? onlookupall : onlookup;
req.resolve = resolve;
req.reject = reject;
let order = DNS_ORDER_VERBATIM;
if (dnsOrder === 'ipv4first') {
order = DNS_ORDER_IPV4_FIRST;
} else if (dnsOrder === 'ipv6first') {
order = DNS_ORDER_IPV6_FIRST;
}
const err = getaddrinfo(req, hostname, family, hints, order);
if (err) {
reject(new DNSException(err, 'getaddrinfo', hostname));
} else if (hasObserver('dns')) {
const detail = {
hostname,
family,
hints,
verbatim: order === DNS_ORDER_VERBATIM,
order: dnsOrder,
};
startPerf(req, kPerfHooksDnsLookupContext, { type: 'dns', name: 'lookup', detail });
}
});
}
/**
* Get the IP address for a given hostname.
* @param {string} hostname - The hostname to resolve (ex. 'nodejs.org').
* @param {object} [options] - Optional settings.
* @param {boolean} [options.all=false] - Whether to return all or just the first resolved address.
* @param {0 | 4 | 6} [options.family=0] - The record family. Must be 4, 6, or 0 (for both).
* @param {number} [options.hints] - One or more supported getaddrinfo flags (supply multiple via
* bitwise OR).
* @param {string} [options.order='verbatim'] - Return results in same order DNS resolved them;
* Must be `ipv4first`, `ipv6first` or `verbatim`.
* New code should supply `verbatim`.
*/
function lookup(hostname, options) {
let hints = 0;
let family = 0;
let all = false;
let dnsOrder = getDefaultResultOrder();
// Parse arguments
if (hostname) {
validateString(hostname, 'hostname');
}
if (typeof options === 'number') {
validateOneOf(options, 'family', validFamilies);
family = options;
} else if (options !== undefined && typeof options !== 'object') {
throw new ERR_INVALID_ARG_TYPE('options', ['integer', 'object'], options);
} else {
if (options?.hints != null) {
validateNumber(options.hints, 'options.hints');
hints = options.hints >>> 0;
validateHints(hints);
}
if (options?.family != null) {
validateOneOf(options.family, 'options.family', validFamilies);
family = options.family;
}
if (options?.all != null) {
validateBoolean(options.all, 'options.all');
all = options.all;
}
if (options?.verbatim != null) {
validateBoolean(options.verbatim, 'options.verbatim');
dnsOrder = options.verbatim ? 'verbatim' : 'ipv4first';
}
if (options?.order != null) {
validateOneOf(options.order, 'options.order', validDnsOrders);
dnsOrder = options.order;
}
}
return createLookupPromise(family, hostname, all, hints, dnsOrder);
}
function onlookupservice(err, hostname, service) {
if (err) {
this.reject(new DNSException(err, 'getnameinfo', this.host));
return;
}
this.resolve({ hostname, service });
if (this[kPerfHooksDnsLookupServiceContext] && hasObserver('dns')) {
stopPerf(this, kPerfHooksDnsLookupServiceContext, { detail: { hostname, service } });
}
}
function createLookupServicePromise(hostname, port) {
return new Promise((resolve, reject) => {
const req = new GetNameInfoReqWrap();
req.hostname = hostname;
req.port = port;
req.oncomplete = onlookupservice;
req.resolve = resolve;
req.reject = reject;
const err = getnameinfo(req, hostname, port);
if (err)
reject(new DNSException(err, 'getnameinfo', hostname));
else if (hasObserver('dns')) {
startPerf(req, kPerfHooksDnsLookupServiceContext, {
type: 'dns',
name: 'lookupService',
detail: {
host: hostname,
port,
},
});
}
});
}
function lookupService(address, port) {
if (arguments.length !== 2)
throw new ERR_MISSING_ARGS('address', 'port');
if (isIP(address) === 0)
throw new ERR_INVALID_ARG_VALUE('address', address);
validatePort(port);
return createLookupServicePromise(address, +port);
}
function onresolve(err, result, ttls) {
if (err) {
this.reject(new DNSException(err, this.bindingName, this.hostname));
return;
}
if (ttls && this.ttl)
result = ArrayPrototypeMap(
result, (address, index) => ({ address, ttl: ttls[index] }));
this.resolve(result);
if (this[kPerfHooksDnsLookupResolveContext] && hasObserver('dns')) {
stopPerf(this, kPerfHooksDnsLookupResolveContext, { detail: { result } });
}
}
function createResolverPromise(resolver, bindingName, hostname, ttl) {
return new Promise((resolve, reject) => {
const req = new QueryReqWrap();
req.bindingName = bindingName;
req.hostname = hostname;
req.oncomplete = onresolve;
req.resolve = resolve;
req.reject = reject;
req.ttl = ttl;
const err = resolver._handle[bindingName](req, hostname);
if (err)
reject(new DNSException(err, bindingName, hostname));
else if (hasObserver('dns')) {
startPerf(req, kPerfHooksDnsLookupResolveContext, {
type: 'dns',
name: bindingName,
detail: {
host: hostname,
ttl,
},
});
}
});
}
function resolver(bindingName) {
function query(name, options) {
validateString(name, 'name');
const ttl = !!(options?.ttl);
return createResolverPromise(this, bindingName, name, ttl);
}
ObjectDefineProperty(query, 'name', { __proto__: null, value: bindingName });
return query;
}
function resolve(hostname, rrtype) {
let resolver;
if (rrtype !== undefined) {
validateString(rrtype, 'rrtype');
resolver = resolveMap[rrtype];
if (typeof resolver !== 'function')
throw new ERR_INVALID_ARG_VALUE('rrtype', rrtype);
} else {
resolver = resolveMap.A;
}
return ReflectApply(resolver, this, [hostname]);
}
// Promise-based resolver.
const { Resolver, resolveMap } = createResolverClass(resolver);
Resolver.prototype.resolve = resolve;
function defaultResolverSetServers(servers) {
const resolver = new Resolver();
resolver.setServers(servers);
setDefaultResolver(resolver);
bindDefaultResolver(module.exports, Resolver.prototype);
}
module.exports = {
lookup,
lookupService,
Resolver,
getDefaultResultOrder,
setDefaultResultOrder,
setServers: defaultResolverSetServers,
// ERROR CODES
NODATA,
FORMERR,
SERVFAIL,
NOTFOUND,
NOTIMP,
REFUSED,
BADQUERY,
BADNAME,
BADFAMILY,
BADRESP,
CONNREFUSED,
TIMEOUT,
EOF,
FILE,
NOMEM,
DESTRUCTION,
BADSTR,
BADFLAGS,
NONAME,
BADHINTS,
NOTINITIALIZED,
LOADIPHLPAPI,
ADDRGETNETWORKPARAMS,
CANCELLED,
};
bindDefaultResolver(module.exports, Resolver.prototype);

Some files were not shown because too many files have changed in this diff Show More