/* eslint jsdoc/require-jsdoc: "error" */ 'use strict'; const { ArrayIsArray, ArrayPrototypeIncludes, ArrayPrototypeJoin, ArrayPrototypeMap, NumberIsInteger, NumberIsNaN, NumberMAX_SAFE_INTEGER, NumberMIN_SAFE_INTEGER, NumberParseInt, ObjectPrototypeHasOwnProperty, RegExpPrototypeExec, String, StringPrototypeToUpperCase, StringPrototypeTrim, } = primordials; const { hideStackFrames, codes: { ERR_SOCKET_BAD_PORT, ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE, ERR_OUT_OF_RANGE, ERR_UNKNOWN_SIGNAL, }, } = require('internal/errors'); const { normalizeEncoding } = require('internal/util'); const { isAsyncFunction, isArrayBufferView, } = require('internal/util/types'); const { signals } = internalBinding('constants').os; /** * @param {*} value * @returns {boolean} */ function isInt32(value) { return value === (value | 0); } /** * @param {*} value * @returns {boolean} */ function isUint32(value) { return value === (value >>> 0); } const octalReg = /^[0-7]+$/; const modeDesc = 'must be a 32-bit unsigned integer or an octal string'; /** * Parse and validate values that will be converted into mode_t (the S_* * constants). Only valid numbers and octal strings are allowed. They could be * converted to 32-bit unsigned integers or non-negative signed integers in the * C++ land, but any value higher than 0o777 will result in platform-specific * behaviors. * @param {*} value Values to be validated * @param {string} name Name of the argument * @param {number} [def] If specified, will be returned for invalid values * @returns {number} */ function parseFileMode(value, name, def) { value ??= def; if (typeof value === 'string') { if (RegExpPrototypeExec(octalReg, value) === null) { throw new ERR_INVALID_ARG_VALUE(name, value, modeDesc); } value = NumberParseInt(value, 8); } validateUint32(value, name); return value; } /** * @callback validateInteger * @param {*} value * @param {string} name * @param {number} [min] * @param {number} [max] * @returns {asserts value is number} */ /** @type {validateInteger} */ const validateInteger = hideStackFrames( (value, name, min = NumberMIN_SAFE_INTEGER, max = NumberMAX_SAFE_INTEGER) => { if (typeof value !== 'number') throw new ERR_INVALID_ARG_TYPE(name, 'number', value); if (!NumberIsInteger(value)) throw new ERR_OUT_OF_RANGE(name, 'an integer', value); if (value < min || value > max) throw new ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value); }, ); /** * @callback validateInt32 * @param {*} value * @param {string} name * @param {number} [min] * @param {number} [max] * @returns {asserts value is number} */ /** @type {validateInt32} */ const validateInt32 = hideStackFrames( (value, name, min = -2147483648, max = 2147483647) => { // The defaults for min and max correspond to the limits of 32-bit integers. if (typeof value !== 'number') { throw new ERR_INVALID_ARG_TYPE(name, 'number', value); } if (!NumberIsInteger(value)) { throw new ERR_OUT_OF_RANGE(name, 'an integer', value); } if (value < min || value > max) { throw new ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value); } }, ); /** * @callback validateUint32 * @param {*} value * @param {string} name * @param {number|boolean} [positive=false] * @returns {asserts value is number} */ /** @type {validateUint32} */ const validateUint32 = hideStackFrames((value, name, positive = false) => { if (typeof value !== 'number') { throw new ERR_INVALID_ARG_TYPE(name, 'number', value); } if (!NumberIsInteger(value)) { throw new ERR_OUT_OF_RANGE(name, 'an integer', value); } const min = positive ? 1 : 0; // 2 ** 32 === 4294967296 const max = 4_294_967_295; if (value < min || value > max) { throw new ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value); } }); /** * @callback validateString * @param {*} value * @param {string} name * @returns {asserts value is string} */ /** @type {validateString} */ function validateString(value, name) { if (typeof value !== 'string') throw new ERR_INVALID_ARG_TYPE(name, 'string', value); } /** * @callback validateNumber * @param {*} value * @param {string} name * @param {number} [min] * @param {number} [max] * @returns {asserts value is number} */ /** @type {validateNumber} */ function validateNumber(value, name, min = undefined, max) { if (typeof value !== 'number') throw new ERR_INVALID_ARG_TYPE(name, 'number', value); if ((min != null && value < min) || (max != null && value > max) || ((min != null || max != null) && NumberIsNaN(value))) { throw new ERR_OUT_OF_RANGE( name, `${min != null ? `>= ${min}` : ''}${min != null && max != null ? ' && ' : ''}${max != null ? `<= ${max}` : ''}`, value); } } /** * @callback validateOneOf * @template T * @param {T} value * @param {string} name * @param {T[]} oneOf */ /** @type {validateOneOf} */ const validateOneOf = hideStackFrames((value, name, oneOf) => { if (!ArrayPrototypeIncludes(oneOf, value)) { const allowed = ArrayPrototypeJoin( ArrayPrototypeMap(oneOf, (v) => (typeof v === 'string' ? `'${v}'` : String(v))), ', '); const reason = 'must be one of: ' + allowed; throw new ERR_INVALID_ARG_VALUE(name, value, reason); } }); /** * @callback validateBoolean * @param {*} value * @param {string} name * @returns {asserts value is boolean} */ /** @type {validateBoolean} */ function validateBoolean(value, name) { if (typeof value !== 'boolean') throw new ERR_INVALID_ARG_TYPE(name, 'boolean', value); } /** * @param {any} options * @param {string} key * @param {boolean} defaultValue * @returns {boolean} */ function getOwnPropertyValueOrDefault(options, key, defaultValue) { return options == null || !ObjectPrototypeHasOwnProperty(options, key) ? defaultValue : options[key]; } /** * @callback validateObject * @param {*} value * @param {string} name * @param {{ * allowArray?: boolean, * allowFunction?: boolean, * nullable?: boolean * }} [options] */ /** @type {validateObject} */ const validateObject = hideStackFrames( (value, name, options = null) => { const allowArray = getOwnPropertyValueOrDefault(options, 'allowArray', false); const allowFunction = getOwnPropertyValueOrDefault(options, 'allowFunction', false); const nullable = getOwnPropertyValueOrDefault(options, 'nullable', false); if ((!nullable && value === null) || (!allowArray && ArrayIsArray(value)) || (typeof value !== 'object' && ( !allowFunction || typeof value !== 'function' ))) { throw new ERR_INVALID_ARG_TYPE(name, 'Object', value); } }); /** * @callback validateDictionary - We are using the Web IDL Standard definition * of "dictionary" here, which means any value * whose Type is either Undefined, Null, or * Object (which includes functions). * @param {*} value * @param {string} name * @see https://webidl.spec.whatwg.org/#es-dictionary * @see https://tc39.es/ecma262/#table-typeof-operator-results */ /** @type {validateDictionary} */ const validateDictionary = hideStackFrames( (value, name) => { if (value != null && typeof value !== 'object' && typeof value !== 'function') { throw new ERR_INVALID_ARG_TYPE(name, 'a dictionary', value); } }); /** * @callback validateArray * @param {*} value * @param {string} name * @param {number} [minLength] * @returns {asserts value is any[]} */ /** @type {validateArray} */ const validateArray = hideStackFrames((value, name, minLength = 0) => { if (!ArrayIsArray(value)) { throw new ERR_INVALID_ARG_TYPE(name, 'Array', value); } if (value.length < minLength) { const reason = `must be longer than ${minLength}`; throw new ERR_INVALID_ARG_VALUE(name, value, reason); } }); /** * @callback validateStringArray * @param {*} value * @param {string} name * @returns {asserts value is string[]} */ /** @type {validateStringArray} */ function validateStringArray(value, name) { validateArray(value, name); for (let i = 0; i < value.length; i++) { validateString(value[i], `${name}[${i}]`); } } /** * @callback validateBooleanArray * @param {*} value * @param {string} name * @returns {asserts value is boolean[]} */ /** @type {validateBooleanArray} */ function validateBooleanArray(value, name) { validateArray(value, name); for (let i = 0; i < value.length; i++) { validateBoolean(value[i], `${name}[${i}]`); } } /** * @param {*} signal * @param {string} [name='signal'] * @returns {asserts signal is keyof signals} */ function validateSignalName(signal, name = 'signal') { validateString(signal, name); if (signals[signal] === undefined) { if (signals[StringPrototypeToUpperCase(signal)] !== undefined) { throw new ERR_UNKNOWN_SIGNAL(signal + ' (signals must use all capital letters)'); } throw new ERR_UNKNOWN_SIGNAL(signal); } } /** * @callback validateBuffer * @param {*} buffer * @param {string} [name='buffer'] * @returns {asserts buffer is ArrayBufferView} */ /** @type {validateBuffer} */ const validateBuffer = hideStackFrames((buffer, name = 'buffer') => { if (!isArrayBufferView(buffer)) { throw new ERR_INVALID_ARG_TYPE(name, ['Buffer', 'TypedArray', 'DataView'], buffer); } }); /** * @param {string} data * @param {string} encoding */ function validateEncoding(data, encoding) { const normalizedEncoding = normalizeEncoding(encoding); const length = data.length; if (normalizedEncoding === 'hex' && length % 2 !== 0) { throw new ERR_INVALID_ARG_VALUE('encoding', encoding, `is invalid for data of length ${length}`); } } /** * Check that the port number is not NaN when coerced to a number, * is an integer and that it falls within the legal range of port numbers. * @param {*} port * @param {string} [name='Port'] * @param {boolean} [allowZero=true] * @returns {number} */ function validatePort(port, name = 'Port', allowZero = true) { if ((typeof port !== 'number' && typeof port !== 'string') || (typeof port === 'string' && StringPrototypeTrim(port).length === 0) || +port !== (+port >>> 0) || port > 0xFFFF || (port === 0 && !allowZero)) { throw new ERR_SOCKET_BAD_PORT(name, port, allowZero); } return port | 0; } /** * @callback validateAbortSignal * @param {*} signal * @param {string} name */ /** @type {validateAbortSignal} */ const validateAbortSignal = hideStackFrames((signal, name) => { if (signal !== undefined && (signal === null || typeof signal !== 'object' || !('aborted' in signal))) { throw new ERR_INVALID_ARG_TYPE(name, 'AbortSignal', signal); } }); /** * @callback validateFunction * @param {*} value * @param {string} name * @returns {asserts value is Function} */ /** @type {validateFunction} */ const validateFunction = hideStackFrames((value, name) => { if (typeof value !== 'function') throw new ERR_INVALID_ARG_TYPE(name, 'Function', value); }); /** * @callback validatePlainFunction * @param {*} value * @param {string} name * @returns {asserts value is Function} */ /** @type {validatePlainFunction} */ const validatePlainFunction = hideStackFrames((value, name) => { if (typeof value !== 'function' || isAsyncFunction(value)) throw new ERR_INVALID_ARG_TYPE(name, 'Function', value); }); /** * @callback validateUndefined * @param {*} value * @param {string} name * @returns {asserts value is undefined} */ /** @type {validateUndefined} */ const validateUndefined = hideStackFrames((value, name) => { if (value !== undefined) throw new ERR_INVALID_ARG_TYPE(name, 'undefined', value); }); /** * @template T * @param {T} value * @param {string} name * @param {T[]} union */ function validateUnion(value, name, union) { if (!ArrayPrototypeIncludes(union, value)) { throw new ERR_INVALID_ARG_TYPE(name, `('${ArrayPrototypeJoin(union, '|')}')`, value); } } /* The rules for the Link header field are described here: https://www.rfc-editor.org/rfc/rfc8288.html#section-3 This regex validates any string surrounded by angle brackets (not necessarily a valid URI reference) followed by zero or more link-params separated by semicolons. */ const linkValueRegExp = /^(?:<[^>]*>)(?:\s*;\s*[^;"\s]+(?:=(")?[^;"\s]*\1)?)*$/; /** * @param {any} value * @param {string} name */ function validateLinkHeaderFormat(value, name) { if ( typeof value === 'undefined' || !RegExpPrototypeExec(linkValueRegExp, value) ) { throw new ERR_INVALID_ARG_VALUE( name, value, 'must be an array or string of format "; rel=preload; as=style"', ); } } const validateInternalField = hideStackFrames((object, fieldKey, className) => { if (typeof object !== 'object' || object === null || !ObjectPrototypeHasOwnProperty(object, fieldKey)) { throw new ERR_INVALID_ARG_TYPE('this', className, object); } }); /** * @param {any} hints * @return {string} */ function validateLinkHeaderValue(hints) { if (typeof hints === 'string') { validateLinkHeaderFormat(hints, 'hints'); return hints; } else if (ArrayIsArray(hints)) { const hintsLength = hints.length; let result = ''; if (hintsLength === 0) { return result; } for (let i = 0; i < hintsLength; i++) { const link = hints[i]; validateLinkHeaderFormat(link, 'hints'); result += link; if (i !== hintsLength - 1) { result += ', '; } } return result; } throw new ERR_INVALID_ARG_VALUE( 'hints', hints, 'must be an array or string of format "; rel=preload; as=style"', ); } module.exports = { isInt32, isUint32, parseFileMode, validateArray, validateStringArray, validateBooleanArray, validateBoolean, validateBuffer, validateDictionary, validateEncoding, validateFunction, validateInt32, validateInteger, validateNumber, validateObject, validateOneOf, validatePlainFunction, validatePort, validateSignalName, validateString, validateUint32, validateUndefined, validateUnion, validateAbortSignal, validateLinkHeaderValue, validateInternalField, };