validators.js 14 KB

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