| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530 |
- /* 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
- } = require('../ours/primordials')
- const {
- hideStackFrames,
- codes: { ERR_SOCKET_BAD_PORT, ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE, ERR_OUT_OF_RANGE, ERR_UNKNOWN_SIGNAL }
- } = require('../ours/errors')
- const { normalizeEncoding } = require('../ours/util')
- const { isAsyncFunction, isArrayBufferView } = require('../ours/util').types
- const signals = {}
- /**
- * @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) {
- if (typeof value === 'undefined') {
- 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 = 4294967295
- 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}]`)
- }
- }
- /**
- * @callback validateAbortSignalArray
- * @param {*} value
- * @param {string} name
- * @returns {asserts value is AbortSignal[]}
- */
- /** @type {validateAbortSignalArray} */
- function validateAbortSignalArray(value, name) {
- validateArray(value, name)
- for (let i = 0; i < value.length; i++) {
- const signal = value[i]
- const indexedName = `${name}[${i}]`
- if (signal == null) {
- throw new ERR_INVALID_ARG_TYPE(indexedName, 'AbortSignal', signal)
- }
- validateAbortSignal(signal, indexedName)
- }
- }
- /**
- * @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 "</styles.css>; rel=preload; as=style"'
- )
- }
- }
- /**
- * @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 "</styles.css>; rel=preload; as=style"'
- )
- }
- module.exports = {
- isInt32,
- isUint32,
- parseFileMode,
- validateArray,
- validateStringArray,
- validateBooleanArray,
- validateAbortSignalArray,
- validateBoolean,
- validateBuffer,
- validateDictionary,
- validateEncoding,
- validateFunction,
- validateInt32,
- validateInteger,
- validateNumber,
- validateObject,
- validateOneOf,
- validatePlainFunction,
- validatePort,
- validateSignalName,
- validateString,
- validateUint32,
- validateUndefined,
- validateUnion,
- validateAbortSignal,
- validateLinkHeaderValue
- }
|