• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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