1/* eslint jsdoc/require-jsdoc: "error" */ 2 3'use strict'; 4 5const { 6 ArrayIsArray, 7 ArrayPrototypeIncludes, 8 ArrayPrototypeJoin, 9 ArrayPrototypeMap, 10 NumberIsInteger, 11 NumberIsNaN, 12 NumberMAX_SAFE_INTEGER, 13 NumberMIN_SAFE_INTEGER, 14 NumberParseInt, 15 ObjectPrototypeHasOwnProperty, 16 RegExpPrototypeExec, 17 String, 18 StringPrototypeToUpperCase, 19 StringPrototypeTrim, 20} = primordials; 21 22const { 23 hideStackFrames, 24 codes: { 25 ERR_SOCKET_BAD_PORT, 26 ERR_INVALID_ARG_TYPE, 27 ERR_INVALID_ARG_VALUE, 28 ERR_OUT_OF_RANGE, 29 ERR_UNKNOWN_SIGNAL, 30 }, 31} = require('internal/errors'); 32const { normalizeEncoding } = require('internal/util'); 33const { 34 isAsyncFunction, 35 isArrayBufferView, 36} = require('internal/util/types'); 37const { signals } = internalBinding('constants').os; 38 39/** 40 * @param {*} value 41 * @returns {boolean} 42 */ 43function isInt32(value) { 44 return value === (value | 0); 45} 46 47/** 48 * @param {*} value 49 * @returns {boolean} 50 */ 51function isUint32(value) { 52 return value === (value >>> 0); 53} 54 55const octalReg = /^[0-7]+$/; 56const modeDesc = 'must be a 32-bit unsigned integer or an octal string'; 57 58/** 59 * Parse and validate values that will be converted into mode_t (the S_* 60 * constants). Only valid numbers and octal strings are allowed. They could be 61 * converted to 32-bit unsigned integers or non-negative signed integers in the 62 * C++ land, but any value higher than 0o777 will result in platform-specific 63 * behaviors. 64 * @param {*} value Values to be validated 65 * @param {string} name Name of the argument 66 * @param {number} [def] If specified, will be returned for invalid values 67 * @returns {number} 68 */ 69function parseFileMode(value, name, def) { 70 value ??= def; 71 if (typeof value === 'string') { 72 if (RegExpPrototypeExec(octalReg, value) === null) { 73 throw new ERR_INVALID_ARG_VALUE(name, value, modeDesc); 74 } 75 value = NumberParseInt(value, 8); 76 } 77 78 validateUint32(value, name); 79 return value; 80} 81 82/** 83 * @callback validateInteger 84 * @param {*} value 85 * @param {string} name 86 * @param {number} [min] 87 * @param {number} [max] 88 * @returns {asserts value is number} 89 */ 90 91/** @type {validateInteger} */ 92const validateInteger = hideStackFrames( 93 (value, name, min = NumberMIN_SAFE_INTEGER, max = NumberMAX_SAFE_INTEGER) => { 94 if (typeof value !== 'number') 95 throw new ERR_INVALID_ARG_TYPE(name, 'number', value); 96 if (!NumberIsInteger(value)) 97 throw new ERR_OUT_OF_RANGE(name, 'an integer', value); 98 if (value < min || value > max) 99 throw new ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value); 100 }, 101); 102 103/** 104 * @callback validateInt32 105 * @param {*} value 106 * @param {string} name 107 * @param {number} [min] 108 * @param {number} [max] 109 * @returns {asserts value is number} 110 */ 111 112/** @type {validateInt32} */ 113const validateInt32 = hideStackFrames( 114 (value, name, min = -2147483648, max = 2147483647) => { 115 // The defaults for min and max correspond to the limits of 32-bit integers. 116 if (typeof value !== 'number') { 117 throw new ERR_INVALID_ARG_TYPE(name, 'number', value); 118 } 119 if (!NumberIsInteger(value)) { 120 throw new ERR_OUT_OF_RANGE(name, 'an integer', value); 121 } 122 if (value < min || value > max) { 123 throw new ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value); 124 } 125 }, 126); 127 128/** 129 * @callback validateUint32 130 * @param {*} value 131 * @param {string} name 132 * @param {number|boolean} [positive=false] 133 * @returns {asserts value is number} 134 */ 135 136/** @type {validateUint32} */ 137const validateUint32 = hideStackFrames((value, name, positive = false) => { 138 if (typeof value !== 'number') { 139 throw new ERR_INVALID_ARG_TYPE(name, 'number', value); 140 } 141 if (!NumberIsInteger(value)) { 142 throw new ERR_OUT_OF_RANGE(name, 'an integer', value); 143 } 144 const min = positive ? 1 : 0; 145 // 2 ** 32 === 4294967296 146 const max = 4_294_967_295; 147 if (value < min || value > max) { 148 throw new ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value); 149 } 150}); 151 152/** 153 * @callback validateString 154 * @param {*} value 155 * @param {string} name 156 * @returns {asserts value is string} 157 */ 158 159/** @type {validateString} */ 160function validateString(value, name) { 161 if (typeof value !== 'string') 162 throw new ERR_INVALID_ARG_TYPE(name, 'string', value); 163} 164 165/** 166 * @callback validateNumber 167 * @param {*} value 168 * @param {string} name 169 * @param {number} [min] 170 * @param {number} [max] 171 * @returns {asserts value is number} 172 */ 173 174/** @type {validateNumber} */ 175function validateNumber(value, name, min = undefined, max) { 176 if (typeof value !== 'number') 177 throw new ERR_INVALID_ARG_TYPE(name, 'number', value); 178 179 if ((min != null && value < min) || (max != null && value > max) || 180 ((min != null || max != null) && NumberIsNaN(value))) { 181 throw new ERR_OUT_OF_RANGE( 182 name, 183 `${min != null ? `>= ${min}` : ''}${min != null && max != null ? ' && ' : ''}${max != null ? `<= ${max}` : ''}`, 184 value); 185 } 186} 187 188/** 189 * @callback validateOneOf 190 * @template T 191 * @param {T} value 192 * @param {string} name 193 * @param {T[]} oneOf 194 */ 195 196/** @type {validateOneOf} */ 197const validateOneOf = hideStackFrames((value, name, oneOf) => { 198 if (!ArrayPrototypeIncludes(oneOf, value)) { 199 const allowed = ArrayPrototypeJoin( 200 ArrayPrototypeMap(oneOf, (v) => 201 (typeof v === 'string' ? `'${v}'` : String(v))), 202 ', '); 203 const reason = 'must be one of: ' + allowed; 204 throw new ERR_INVALID_ARG_VALUE(name, value, reason); 205 } 206}); 207 208/** 209 * @callback validateBoolean 210 * @param {*} value 211 * @param {string} name 212 * @returns {asserts value is boolean} 213 */ 214 215/** @type {validateBoolean} */ 216function validateBoolean(value, name) { 217 if (typeof value !== 'boolean') 218 throw new ERR_INVALID_ARG_TYPE(name, 'boolean', value); 219} 220 221/** 222 * @param {any} options 223 * @param {string} key 224 * @param {boolean} defaultValue 225 * @returns {boolean} 226 */ 227function getOwnPropertyValueOrDefault(options, key, defaultValue) { 228 return options == null || !ObjectPrototypeHasOwnProperty(options, key) ? 229 defaultValue : 230 options[key]; 231} 232 233/** 234 * @callback validateObject 235 * @param {*} value 236 * @param {string} name 237 * @param {{ 238 * allowArray?: boolean, 239 * allowFunction?: boolean, 240 * nullable?: boolean 241 * }} [options] 242 */ 243 244/** @type {validateObject} */ 245const validateObject = hideStackFrames( 246 (value, name, options = null) => { 247 const allowArray = getOwnPropertyValueOrDefault(options, 'allowArray', false); 248 const allowFunction = getOwnPropertyValueOrDefault(options, 'allowFunction', false); 249 const nullable = getOwnPropertyValueOrDefault(options, 'nullable', false); 250 if ((!nullable && value === null) || 251 (!allowArray && ArrayIsArray(value)) || 252 (typeof value !== 'object' && ( 253 !allowFunction || typeof value !== 'function' 254 ))) { 255 throw new ERR_INVALID_ARG_TYPE(name, 'Object', value); 256 } 257 }); 258 259/** 260 * @callback validateDictionary - We are using the Web IDL Standard definition 261 * of "dictionary" here, which means any value 262 * whose Type is either Undefined, Null, or 263 * Object (which includes functions). 264 * @param {*} value 265 * @param {string} name 266 * @see https://webidl.spec.whatwg.org/#es-dictionary 267 * @see https://tc39.es/ecma262/#table-typeof-operator-results 268 */ 269 270/** @type {validateDictionary} */ 271const validateDictionary = hideStackFrames( 272 (value, name) => { 273 if (value != null && typeof value !== 'object' && typeof value !== 'function') { 274 throw new ERR_INVALID_ARG_TYPE(name, 'a dictionary', value); 275 } 276 }); 277 278/** 279 * @callback validateArray 280 * @param {*} value 281 * @param {string} name 282 * @param {number} [minLength] 283 * @returns {asserts value is any[]} 284 */ 285 286/** @type {validateArray} */ 287const validateArray = hideStackFrames((value, name, minLength = 0) => { 288 if (!ArrayIsArray(value)) { 289 throw new ERR_INVALID_ARG_TYPE(name, 'Array', value); 290 } 291 if (value.length < minLength) { 292 const reason = `must be longer than ${minLength}`; 293 throw new ERR_INVALID_ARG_VALUE(name, value, reason); 294 } 295}); 296 297/** 298 * @callback validateStringArray 299 * @param {*} value 300 * @param {string} name 301 * @returns {asserts value is string[]} 302 */ 303 304/** @type {validateStringArray} */ 305function validateStringArray(value, name) { 306 validateArray(value, name); 307 for (let i = 0; i < value.length; i++) { 308 validateString(value[i], `${name}[${i}]`); 309 } 310} 311 312/** 313 * @callback validateBooleanArray 314 * @param {*} value 315 * @param {string} name 316 * @returns {asserts value is boolean[]} 317 */ 318 319/** @type {validateBooleanArray} */ 320function validateBooleanArray(value, name) { 321 validateArray(value, name); 322 for (let i = 0; i < value.length; i++) { 323 validateBoolean(value[i], `${name}[${i}]`); 324 } 325} 326 327/** 328 * @callback validateAbortSignalArray 329 * @param {*} value 330 * @param {string} name 331 * @returns {asserts value is AbortSignal[]} 332 */ 333 334/** @type {validateAbortSignalArray} */ 335function validateAbortSignalArray(value, name) { 336 validateArray(value, name); 337 for (let i = 0; i < value.length; i++) { 338 const signal = value[i]; 339 const indexedName = `${name}[${i}]`; 340 if (signal == null) { 341 throw new ERR_INVALID_ARG_TYPE(indexedName, 'AbortSignal', signal); 342 } 343 validateAbortSignal(signal, indexedName); 344 } 345} 346 347/** 348 * @param {*} signal 349 * @param {string} [name='signal'] 350 * @returns {asserts signal is keyof signals} 351 */ 352function validateSignalName(signal, name = 'signal') { 353 validateString(signal, name); 354 355 if (signals[signal] === undefined) { 356 if (signals[StringPrototypeToUpperCase(signal)] !== undefined) { 357 throw new ERR_UNKNOWN_SIGNAL(signal + 358 ' (signals must use all capital letters)'); 359 } 360 361 throw new ERR_UNKNOWN_SIGNAL(signal); 362 } 363} 364 365/** 366 * @callback validateBuffer 367 * @param {*} buffer 368 * @param {string} [name='buffer'] 369 * @returns {asserts buffer is ArrayBufferView} 370 */ 371 372/** @type {validateBuffer} */ 373const validateBuffer = hideStackFrames((buffer, name = 'buffer') => { 374 if (!isArrayBufferView(buffer)) { 375 throw new ERR_INVALID_ARG_TYPE(name, 376 ['Buffer', 'TypedArray', 'DataView'], 377 buffer); 378 } 379}); 380 381/** 382 * @param {string} data 383 * @param {string} encoding 384 */ 385function validateEncoding(data, encoding) { 386 const normalizedEncoding = normalizeEncoding(encoding); 387 const length = data.length; 388 389 if (normalizedEncoding === 'hex' && length % 2 !== 0) { 390 throw new ERR_INVALID_ARG_VALUE('encoding', encoding, 391 `is invalid for data of length ${length}`); 392 } 393} 394 395/** 396 * Check that the port number is not NaN when coerced to a number, 397 * is an integer and that it falls within the legal range of port numbers. 398 * @param {*} port 399 * @param {string} [name='Port'] 400 * @param {boolean} [allowZero=true] 401 * @returns {number} 402 */ 403function validatePort(port, name = 'Port', allowZero = true) { 404 if ((typeof port !== 'number' && typeof port !== 'string') || 405 (typeof port === 'string' && StringPrototypeTrim(port).length === 0) || 406 +port !== (+port >>> 0) || 407 port > 0xFFFF || 408 (port === 0 && !allowZero)) { 409 throw new ERR_SOCKET_BAD_PORT(name, port, allowZero); 410 } 411 return port | 0; 412} 413 414/** 415 * @callback validateAbortSignal 416 * @param {*} signal 417 * @param {string} name 418 */ 419 420/** @type {validateAbortSignal} */ 421const validateAbortSignal = hideStackFrames((signal, name) => { 422 if (signal !== undefined && 423 (signal === null || 424 typeof signal !== 'object' || 425 !('aborted' in signal))) { 426 throw new ERR_INVALID_ARG_TYPE(name, 'AbortSignal', signal); 427 } 428}); 429 430/** 431 * @callback validateFunction 432 * @param {*} value 433 * @param {string} name 434 * @returns {asserts value is Function} 435 */ 436 437/** @type {validateFunction} */ 438const validateFunction = hideStackFrames((value, name) => { 439 if (typeof value !== 'function') 440 throw new ERR_INVALID_ARG_TYPE(name, 'Function', value); 441}); 442 443/** 444 * @callback validatePlainFunction 445 * @param {*} value 446 * @param {string} name 447 * @returns {asserts value is Function} 448 */ 449 450/** @type {validatePlainFunction} */ 451const validatePlainFunction = hideStackFrames((value, name) => { 452 if (typeof value !== 'function' || isAsyncFunction(value)) 453 throw new ERR_INVALID_ARG_TYPE(name, 'Function', value); 454}); 455 456/** 457 * @callback validateUndefined 458 * @param {*} value 459 * @param {string} name 460 * @returns {asserts value is undefined} 461 */ 462 463/** @type {validateUndefined} */ 464const validateUndefined = hideStackFrames((value, name) => { 465 if (value !== undefined) 466 throw new ERR_INVALID_ARG_TYPE(name, 'undefined', value); 467}); 468 469/** 470 * @template T 471 * @param {T} value 472 * @param {string} name 473 * @param {T[]} union 474 */ 475function validateUnion(value, name, union) { 476 if (!ArrayPrototypeIncludes(union, value)) { 477 throw new ERR_INVALID_ARG_TYPE(name, `('${ArrayPrototypeJoin(union, '|')}')`, value); 478 } 479} 480 481/* 482 The rules for the Link header field are described here: 483 https://www.rfc-editor.org/rfc/rfc8288.html#section-3 484 485 This regex validates any string surrounded by angle brackets 486 (not necessarily a valid URI reference) followed by zero or more 487 link-params separated by semicolons. 488*/ 489const linkValueRegExp = /^(?:<[^>]*>)(?:\s*;\s*[^;"\s]+(?:=(")?[^;"\s]*\1)?)*$/; 490 491/** 492 * @param {any} value 493 * @param {string} name 494 */ 495function validateLinkHeaderFormat(value, name) { 496 if ( 497 typeof value === 'undefined' || 498 !RegExpPrototypeExec(linkValueRegExp, value) 499 ) { 500 throw new ERR_INVALID_ARG_VALUE( 501 name, 502 value, 503 'must be an array or string of format "</styles.css>; rel=preload; as=style"', 504 ); 505 } 506} 507 508/** 509 * @param {any} hints 510 * @return {string} 511 */ 512function validateLinkHeaderValue(hints) { 513 if (typeof hints === 'string') { 514 validateLinkHeaderFormat(hints, 'hints'); 515 return hints; 516 } else if (ArrayIsArray(hints)) { 517 const hintsLength = hints.length; 518 let result = ''; 519 520 if (hintsLength === 0) { 521 return result; 522 } 523 524 for (let i = 0; i < hintsLength; i++) { 525 const link = hints[i]; 526 validateLinkHeaderFormat(link, 'hints'); 527 result += link; 528 529 if (i !== hintsLength - 1) { 530 result += ', '; 531 } 532 } 533 534 return result; 535 } 536 537 throw new ERR_INVALID_ARG_VALUE( 538 'hints', 539 hints, 540 'must be an array or string of format "</styles.css>; rel=preload; as=style"', 541 ); 542} 543 544module.exports = { 545 isInt32, 546 isUint32, 547 parseFileMode, 548 validateArray, 549 validateStringArray, 550 validateBooleanArray, 551 validateAbortSignalArray, 552 validateBoolean, 553 validateBuffer, 554 validateDictionary, 555 validateEncoding, 556 validateFunction, 557 validateInt32, 558 validateInteger, 559 validateNumber, 560 validateObject, 561 validateOneOf, 562 validatePlainFunction, 563 validatePort, 564 validateSignalName, 565 validateString, 566 validateUint32, 567 validateUndefined, 568 validateUnion, 569 validateAbortSignal, 570 validateLinkHeaderValue, 571}; 572