mirror of
https://github.com/JasonYANG170/IOTConnect-Web.git
synced 2024-11-23 20:26:28 +00:00
531 lines
14 KiB
JavaScript
531 lines
14 KiB
JavaScript
|
/* 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
|
||
|
}
|