UNPKG

@kodepandai/node-input-validator

Version:

validation library for nodejs, inspired by laravel.

1,637 lines (1,596 loc) 87.6 kB
import * as mimeTypes from 'mime-types'; import fileType from 'file-type'; import readChunk from 'read-chunk'; import fs from 'fs'; import util from 'util'; import { URL } from 'url'; var Langs; (function (Langs) { Langs["en_US"] = "en_US"; Langs["pb"] = "pb"; Langs["fa"] = "fa"; Langs["pt_BR"] = "pt_BR"; })(Langs || (Langs = {})); const after$1 = 'The :attr must be a date after :arg0.'; const afterOrEqual = 'The :attr must be a date after or equal :arg0.'; const before$1 = 'The :attr must be a date before :arg0.'; const beforeOrEqual = 'The :attr must be a date before or equal to :date.'; const boolean$1 = 'The :attr field must be boolean.'; const dateISO$1 = 'The :attr must be a valid ISO-8601 date.'; const messages$1 = { after: after$1, afterOrEqual, before: before$1, beforeOrEqual, accepted: 'The :attr must be accepted.', acceptedIf: 'The :attr should be accepted if the parameter :arg0 is :arg1.', acceptedNotIf: 'The :attr can\'t be accepted if the parameter :arg0 is :arg1.', activeUrl: 'The :attr is not a valid url.', alpha: 'The :attr can only contain alphabets.', alphaDash: 'The :attr can only contain letters, numbers, and dashes.', alphaNumeric: 'The :attr can only contain letters and numbers.', alphaHyphen: 'The :attr can only contain letters and hyphen (-).', alphaNumericDash: 'The :attr can only contain letters, numbers and dashes.', arrayLen: 'The :attr must be an array of length :arg0.', arrayLenRange: (params) => { if (params.ruleArgs.length == 1) { return 'The :attr must be an array max length :arg0.'; } return 'The :attr must be an array of length between :arg1 - :arg0.'; }, arrayLenMin: 'The :attr must be an array with minimum length :arg0.', arrayLenMax: 'The :attr must be an array with maximum length :arg0.', array: 'The :attr must be an array.', arrayUnique: 'The :attr must be an array of unique values.', arrayUniqueObjects: 'The :attr must be an array of unique :args attributes of object.', ascii: 'The :attr can only contains valid ascii characters.', base64: 'The :attr must be a valid base64 string.', between: 'The :attr must be between :arg0 and :arg1', boolean: boolean$1, booleanStrict: boolean$1, booleanStr: boolean$1, booleanInt: boolean$1, confirmed: 'The :attr confirmation does not match.', contains: 'The :attr must contains :arg0.', creditCard: 'The :attr value must be a valid card number.', date: 'The :attr must be a valid date.', dateAfter: after$1, dateAfterToday: 'The :attr must be a date after :arg0 :arg1.', dateDaysAfterToday: 'The :attr must be a date after :arg0 days.', dateYearsAfterToday: 'The :attr must be a date after :arg0 years.', dateDaysBeforeToday: 'The :attr must be a date before :arg0 days.', dateYearsBeforeToday: 'The :attr must be a date before :arg0 years.', dateBefore: before$1, dateBeforeToday: 'The :attr must be a date before :arg0 :arg1.', dateFormat: 'The :attr does not match the date format :arg0.', datetime: 'The :attr must be a valid datetime(YYYY-MM-DD HH:mm:ss).', dateISO: dateISO$1, dateiso: dateISO$1, decimal: 'The :attr must be a valid decimal value.', different: 'The :attr and :arg0 must be different.', digits: 'The :attr must be of :arg0 digits.', digitsBetween: 'The :attr must be between :arg0 and :arg1.', dimensions: 'The :attr must meet dimension constraints as :args.', domain: 'The :attr must be a valid domain.', email: 'The :attr must be a valid email address.', equals: 'The :attr must be a equals :arg0.', gt: 'The :attr must be greater then :args.', gte: 'The :attr must be greater then or equals to :args.', length: 'The :attr length is not acceptable.', lt: 'The :attr must be less then :args', lte: 'The :attr must be less then or equals :args', hash: 'The :attr must be a valid :arg0 hash.', hex: 'The :attr must be a valid hex.', hexColor: 'The :attr must be a valid hex color.', in: 'The selected :attr is invalid.', integer: 'The :attr must be an integer.', ip: 'The :attr must be a valid IP address.', ipv4: 'The :attr must be a valid IPv4 address.', ipv6: 'The :attr must be a valid IPv6 address.', iso8601: 'The :attr must be a valid ISO8601 string.', json: 'The :attr must be a valid JSON string.', latLong: 'The :attr must be a valid comma seperated lat and long without spaces.', lengthBetween: 'The :attr length must be between :arg0 - :arg1.', macAddress: 'The :attr must be a valid mac address.', max: 'The :attr can not be greater than :arg0.', maxLength: 'The :attr can not be greater than :arg0.', mime: 'The :attr must be a file of type: :args.', min: 'The :attr must be at least :arg0.', minLength: 'The :attr can not be less than :arg0.', mongoId: 'The :attr must be a valid mongo id.', notContains: 'The :attr may not contains :arg0.', notIn: 'The selected :attr is invalid.', nullable: 'The :attr is required.', numeric: 'The :attr must be a number.', object: 'The :attr must be an object.', phoneNumber: 'The :attr must be a valid phone number.', regex: 'The :attr format is invalid.', required: 'The :attr field is required.', requiredIf: 'The :attr field is required.', requiredNotIf: 'The :attr field is required.', requiredWith: 'The :attr field is required.', requiredWithout: 'The :attr field is required.', requiredWithoutAll: 'The :attr field is required.', same: 'The :attr and :arg0 must match.', size: 'The :attr must be :arg0.', sometimes: 'The :attr is required.', string: 'The :attr must be a string.', timezone: 'The :attr must be a valid zone.', unique: 'The :attr has already been taken.', url: 'The :attr format is invalid.', // validator: (params: any) => { // if (!params.ruleArgs || !params.ruleArgs[0]) { // // throw params.ruleArgs; // return messages.$default; // } // const rule = params.ruleArgs[0].replace('is', '').toLowerCase(); // // throw rule; // // @ts-ignore // return messages[rule] || messages.$default; // }, any: 'At least one of :attr fields must be provided', $niceNames: {}, $custom: { // customAttributeName: 'Message goes here.', // 'customAttributeName.ruleName': 'Message goes here.', }, $default: "The :attr validation failed under rule :rule against value :value.", }; var enUS_messages = /*#__PURE__*/Object.freeze({ __proto__: null, messages: messages$1 }); let config = { wildcardIterations: 1000, wildcardSeperator: '.', lang: Langs.en_US, implicitRules: [ "required", "requiredIf", "requiredNotIf", "requiredWith", "requiredWithout", "accepted", "sometimes", "nullable", ], }; function set(customConfig) { config = Object.assign(Object.assign({}, config), customConfig); } function get(key, defaultValue = null) { if (key) { return config[key] || defaultValue; } return config; } function modify(key, value) { config[key] = value; } function addImplicitRule$1(ruleName) { var _a; (_a = config.implicitRules) === null || _a === void 0 ? void 0 : _a.push(ruleName); } function getKeyValue(key) { return (obj) => obj[key]; } function getValueByStringNotation(object, notation) { const notationArr = notation.split("."); let value; notationArr.map((item) => { if (value === undefined) { value = object[item]; } else { value = value[item]; } return value; }); return value; } function isIterable(object) { return object != null && (typeof object === "object" || Array.isArray(object)); } function getValuesByWildCardStringNotation(iterable, rules = {}, options = {}) { const currentConfig = get(); const { prefix, iterations, seperator } = Object.assign({ prefix: [], iterations: currentConfig.wildcardIterations, seperator: currentConfig.wildcardSeperator }, options); const notationsVals = {}; const notationMap = {}; let iterationsCount = 1; const parse = (data, prefix) => { iterationsCount++; if (iterationsCount > iterations) { // eslint-disable-next-line no-console throw new Error(`Max(${iterations}) repetation was reached.`); } Object.keys(data).forEach((key, index) => { const v = data[key]; const notationKey = `${[...prefix, key].join(seperator)}`; const notationMapKey = notationKey.replace(/\.[0-9+]\./g, ".*.").replace(/^[0-9+]\./g, "*."); notationMap[notationMapKey] = notationMap[notationMapKey] || []; notationMap[notationMapKey].push(notationKey); if (isIterable(v)) { parse(v, [...prefix, key]); notationsVals[notationKey] = v; } else { notationsVals[notationKey] = v; } }); }; parse(iterable, [...prefix]); return { notationsVals, notationMap }; } const DEFAULT_LANG = Langs.en_US; const MessagesRef = {}; function messagesRefByLang(lang) { let messages = getKeyValue(lang.toString())(MessagesRef); if (typeof messages === "undefined") { // @ts-ignore MessagesRef[lang] = { messages: {} }; messages = getKeyValue(lang.toString())(MessagesRef); } return messages; } function extend$1(newMessages, lang = Langs.en_US) { const messages = messagesRefByLang(lang); Object.assign(messages, newMessages); } function addCustomMessages(customMessages, lang = Langs.en_US) { const messages = messagesRefByLang(lang); if (!messages.$custom) { messages.$custom = {}; } Object.assign(messages.$custom, customMessages); } function addNiceNames(attributesNiceNames, lang = Langs.en_US) { const messages = messagesRefByLang(lang); if (!messages.$niceNames) { messages.$niceNames = {}; } Object.assign(messages.$niceNames, attributesNiceNames); } extend$1(messages$1, Langs.en_US); var messages = /*#__PURE__*/Object.freeze({ __proto__: null, en_US: enUS_messages }); var index = /*#__PURE__*/Object.freeze({ __proto__: null, Messages: messages, DEFAULT_LANG: DEFAULT_LANG, messagesRefByLang: messagesRefByLang, extend: extend$1, addCustomMessages: addCustomMessages, addNiceNames: addNiceNames }); function reallyEmpty(value) { const str = (value === undefined || value === null ? "" : value) + ""; return str.length === 0; } function fillMissingSpots(target, key) { // convert key to an array or clone is already an array const segments = Array.isArray(key) ? [...key] : key.split('.'); // pull the first key from array const segment = segments.shift(); if (segment === '*') { if (!Array.isArray(target)) { target = []; } if (segments.length) { if (!target.length) { const targetVal = {}; targetVal[segments[0]] = null; target.push(targetVal); console.log('create new target', target); } target.forEach((childTarget) => { fillMissingSpots(childTarget, segments); }); } } else if (typeof target === 'object') { if (segments.length) { if (segment && !target[segment]) { target[segment] = segments[0] === '*' ? [] : {}; } if (target && segment && target[segment]) { fillMissingSpots(target[segment], segments); } } } } /** * The field under validation must be yes, on, 1, or true. * This is useful for validating "Terms of Service" acceptance. * @param {Array<string>} args seeds */ /** * Usage Example * * ```js * async (req,res) => { * const v = new niv.Validator(req.body, { tandc: 'accepted' }) * const passed = await v.validate(); * console.log(passed) // output: true/false, depends on input * } * ``` */ function accepted(args = ["true", "1", "yes", "on"]) { return { name: "accepted", handler: (value) => { if (args.indexOf(value) >= 0) { return true; } return false; }, }; } /** * The field under validation must be yes, on, 1, or true if the attribute given in the seed present and has value given value. * This is useful for validating "Terms of Service" acceptance of some service that is optional and user only have to agree, if user has enabled that service. * @param args seeds * @param acceptedValues */ /** * Usage Example * * ```js * async (req,res) => { * const v = new niv.Validator(req.body, { tandc: 'acceptedIf:newsletter,yes' }) * const passed = await v.validate(); * console.log(passed) // output: true/false, depends on input * } * ``` */ function acceptedIf(args, acceptedValues = ["true", "1", "yes", "on"]) { const argsLen = args.length; if (!args || argsLen < 2 || argsLen % 2 !== 0) { throw new Error('Invalid number of arguments.'); } return { name: "acceptedIf", handler: (value, v) => { let required = true; let i = 0; for (i; i < argsLen; i += 2) { const attrName = args[i]; const requiredAttrVal = args[i + 1]; const actualValue = v.attributeValue(attrName); if (reallyEmpty(actualValue) || String(requiredAttrVal) !== String(actualValue)) { required = false; break; } } return (required && acceptedValues.indexOf(value) < 0) ? false : true; }, }; } /** * The field under validation must no be yes, on, 1, or true if the attribute given in the seed present and has value given value. * This is useful for validating "Terms of Service" acceptance of some service that user should not accept if user has disabled that service. * @param args seeds * @param acceptedValues */ /** * Usage Example * * ```js * async (req,res) => { * const v = new niv.Validator(req.body, { tandc: 'acceptedNotIf:newsletter,no' }) * const passed = await v.validate(); * console.log(passed) // output: true/false, depends on input * } * ``` */ function acceptedNotIf(args, acceptedValues = ["true", "1", "yes", "on"]) { const argsLen = args.length; if (!args || argsLen < 2 || argsLen % 2 !== 0) { throw new Error('Invalid number of arguments.'); } return { name: "acceptedNotIf", handler: (value, v) => { let required = true; let i = 0; for (i; i < argsLen; i += 2) { const attrName = args[i]; const requiredAttrVal = args[i + 1]; const actualValue = v.attributeValue(attrName); if (reallyEmpty(actualValue) || String(requiredAttrVal) === String(actualValue)) { required = false; break; } } return (required === false && acceptedValues.indexOf(value) >= 0) ? false : true; }, }; } const REGEX_ALPHA = /^[A-Z]+$/i; const REGEX_ALPHA_DASH = /^[A-Z_-]+$/i; const REGEX_ALPHA_HYPHEN = /^[A-Z-]+$/i; const REGEX_ALPHA_NUMERIC = /^[0-9A-Z]+$/i; const REGEX_ALPHA_NUMERIC_DASH = /^[A-Z0-9_-]+$/i; /** * The field under validation must be entirely alphabetic characters. */ function alpha() { return { name: "alpha", handler: (value) => { if (typeof value !== 'string') { return false; } return REGEX_ALPHA.test(value); }, }; } /** * The field under validation may have alpha characters, as well as hyphens and underscores. */ function alphaDash() { return { name: "alphaDash", handler: (value) => { if (typeof value !== 'string') { return false; } return REGEX_ALPHA_DASH.test(value); }, }; } /** * The field under validation may have alpha characters, as well as hyphens. */ function alphaHyphen() { return { name: "alphaHyphen", handler: (value) => { if (typeof value !== 'string') { return false; } return REGEX_ALPHA_HYPHEN.test(value); }, }; } /** * The field under validation must contains alpha-numeric characters. */ function alphaNumeric() { return { name: "alphaNumeric", handler: (value) => { if (typeof value !== 'string') { return false; } return REGEX_ALPHA_NUMERIC.test(value); }, }; } /** * The field under validation may have alpha-numeric characters, as well as hyphens and underscores. */ function alphaNumericDash() { return { name: "alphaNumericDash", handler: (value) => { if (typeof value !== 'string') { return false; } return REGEX_ALPHA_NUMERIC_DASH.test(value); }, }; } // This file is the perfect example of when to place multiple rules in same file. // All rules in this file have common check ie. isArray and on top of that // 2 of them have unique check. /** * The field under validation must be array. */ function array() { return { name: "array", handler: (value) => { return Array.isArray(value); }, }; } /** * The field under validation must be array of unique values. * No need to use array rule. This rule will take care of that. */ function arrayUnique() { return { name: "arrayUnique", handler: (value) => { if (!Array.isArray(value)) { return false; } return (new Set(value)).size === value.length; }, }; } /** * The field under validation must be array and should have objects with unique attributes as per seed. * No need to use array rule. This rule will take care of that. * @param args seeds */ function arrayUniqueObjects(args) { return { name: "arrayUniqueObjects", handler: (value) => { if (!Array.isArray(value)) { return false; } const result = new Set(value.map((o) => { let output = ""; for (const attr of args) { output += o[attr]; } return output; })); return result.size === value.length; }, }; } /** * @since: v5 * The field under validation must be array of length as per seed. * @param args seeds */ function arrayLen(args) { if (args.length !== 1) { throw new Error('Invalid number of arguments.'); } const len = parseInt(args[0], 10); return { name: "arrayLen", handler: (value) => { return (Array.isArray(value) && value.length === len); }, }; } /** * @since: v5 * The field under validation must be array and has length range as per seed. * @param args seeds */ function arrayLenRange(args) { if (args.length < 1 || args.length > 2) { throw new Error('Invalid number of arguments.'); } const max = parseInt(args[0], 10); const min = parseInt(args[1] || '0', 10); return { name: "arrayLenRange", handler: (value) => { const len = value.length; return (Array.isArray(value) && len <= max && (!min || len >= min)); }, }; } /** * @since: v5 * The field under validation must be array and has minumun length as per seed. * @param args seeds */ function arrayLenMin(args) { if (args.length !== 1) { throw new Error('Invalid number of arguments.'); } const min = parseInt(args[0], 10); return { name: "arrayLenMin", handler: (value) => { const len = value.length; return (Array.isArray(value) && len >= min); }, }; } /** * @since: v5 * The field under validation must be array and has maximum length as per seed. * @param args seeds */ function arrayLenMax(args) { if (args.length !== 1) { throw new Error('Invalid number of arguments.'); } const max = parseInt(args[0], 10); return { name: "arrayLenMax", handler: (value) => { const len = value.length; return (Array.isArray(value) && len <= max); }, }; } function isInt(value, allowLeadingZero = false) { if (allowLeadingZero) { return /^[-+]?[0-9]+$/.test(value); } return /^(?:[-+]?(?:0|[1-9][0-9]*))$/.test(value); } function isNumeric(value) { return /^[+-]?([0-9]*[.])?[0-9]+$/.test(value); } function isDecimal(value, strict = false, decimalDigits = 1, seperator = '.') { const r = new RegExp(`^[-+]?([0-9]+)?(\\${seperator}[0-9]{${decimalDigits},})${strict ? '' : '?'}$`); return r.test(value); } /** * The field under validation must be between min and max seed. * This will work with number as well as array. * In case of array, array values must be numbers between min and max seed. * @param args seeds */ function between(args) { if (args.length !== 2) { throw new Error('Invalid number of arguments.'); } if (!isNumeric(args[0]) || !isNumeric(args[0])) { throw new TypeError('Seeds must be number.'); } const min = Number(args[0]); const max = Number(args[1]); if (min >= max) { throw new RangeError('Seed min must be less then max.'); } return { name: 'between', handler: (value) => { if (Array.isArray(value)) { let i = 0; const len = value.length; for (i; i < len; i++) { const v = String(value[i]); if (!isNumeric(v)) { return false; } } const minV = Math.min(...value); const maxV = Math.max(...value); if (minV < min || maxV > max) { return false; } return true; } const v = String(value); if (!isNumeric(v)) { return false; } const valNum = Number(value); if (valNum < min || valNum > max) { return false; } return true; }, }; } function booleanStr() { const args = ["true", "false"]; return { name: "booleanStr", handler: (value) => { if (args.indexOf(value) >= 0) { return true; } return false; }, }; } function booleanInt() { const args = [0, 1]; return { name: "booleanInt", handler: (value) => { if (args.indexOf(value) >= 0) { return true; } return false; }, }; } function booleanStrict() { const args = [true, false]; return { name: "booleanStrict", handler: (value) => { if (args.indexOf(value) >= 0) { return true; } return false; }, }; } // /** // * @deprecated Since version 5. // * Use booleanStrict,booleanStr,booleanInt instead. // * @param args // */ function boolean(args = [true, false, 0, 1, "true", "false", "0", "1"]) { // console.warn("Rule boolean has be deprecated, please use booleanStrict,booleanStr,booleanInt instead."); return { name: "boolean", handler: (value) => { if (args.indexOf(value) >= 0) { return true; } return false; }, }; } function confirmed(args = []) { return { name: "confirmed", handler: (value, v, attrName) => { if (value === v.attributeValue(`${attrName}Confirmation`)) { return true; } return false; }, }; } function contains(args) { if (args.length < 1 || args.length > 2) { throw new Error('Invalid number of arguments.'); } const [find, modifier] = args; if (modifier && modifier !== 'i') { throw new Error('Only support modifier is insensitive (i).'); } return { name: "contains", handler: (value) => { if (typeof value !== 'string') { return false; } if (modifier === 'i') { return value.toLowerCase().indexOf(find.toLowerCase()) >= 0; } return value.indexOf(find) >= 0; }, }; } function notContains(args) { const containsHandler = contains(args).handler; return { name: "notContains", handler: (value) => { return !containsHandler(value); }, }; } // credits: https://github.com/validatorjs/validator.js/blob/master/src/lib/isFQDN.js function isDomain(value) { const parts = value.split('.'); const tld = parts[parts.length - 1]; // disallow fqdns without tld if (parts.length < 2) { return false; } if (!/^([a-z\u00a1-\uffff]{2,}|xn[a-z0-9-]{2,})$/i.test(tld)) { return false; } // disallow spaces && special characers if (/[\s\u2002-\u200B\u202F\u205F\u3000\uFEFF\uDB40\uDC20\u00A9\uFFFD]/.test(tld)) { return false; } if (/^\d+$/.test(tld)) { return false; } return parts.every((part) => { if (part.length > 63) { return false; } if (!/^[a-z_\u00a1-\uffff0-9-]+$/i.test(part)) { return false; } // disallow full-width chars if (/[\uff01-\uff5e]/.test(part)) { return false; } // disallow parts starting or ending with hyphen if (/^-|-$/.test(part)) { return false; } if (/_/.test(part)) { return false; } return true; }); } function isByteLength(str, options) { let min = 0; let max; if (typeof (options) === 'object') { min = options.min || 0; max = options.max; } const len = encodeURI(str).split(/%..|./).length - 1; return len >= min && (typeof max === 'undefined' || len <= max); } // credits: https://github.com/validatorjs/validator.js/blob/master/src/lib/isEmail.js const emailUserUtf8Part = /^[a-z\d!#\$%&'\*\+\-\/=\?\^_`{\|}~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+$/i; const quotedEmailUserUtf8 = /^([\s\x01-\x08\x0b\x0c\x0e-\x1f\x7f\x21\x23-\x5b\x5d-\x7e\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|(\\[\x01-\x09\x0b\x0c\x0d-\x7f\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))*$/i; const defaultMaxEmailLength = 254; function isEmail(str) { if (str.length > defaultMaxEmailLength) { return false; } const parts = str.split('@'); const domain = parts.pop() || ''; let user = parts.join('@'); domain.toLowerCase(); if (!isByteLength(user, { max: 64 }) || !isByteLength(domain, { max: 254 })) { return false; } if (!isDomain(domain)) { return false; } if (user[0] === '"') { user = user.slice(1, user.length - 1); return quotedEmailUserUtf8.test(user); } const pattern = emailUserUtf8Part; const user_parts = user.split('.'); for (let i = 0; i < user_parts.length; i++) { if (!pattern.test(user_parts[i])) { return false; } } return true; } const ipv4Maybe = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/; const ipv6Block = /^[0-9A-F]{1,4}$/i; function isIp(str, version = '') { if (!version) { return isIp(str, '4') || isIp(str, '6'); } if (version === '4') { if (!ipv4Maybe.test(str)) { return false; } // @ts-ignore const parts = str.split('.').sort((a, b) => a - b); // @ts-ignore return parts[3] <= 255; } if (version === '6') { let addressAndZone = [str]; // ipv6 addresses could have scoped architecture // according to https://tools.ietf.org/html/rfc4007#section-11 if (str.includes('%')) { addressAndZone = str.split('%'); if (addressAndZone.length !== 2) { // it must be just two parts return false; } if (!addressAndZone[0].includes(':')) { // the first part must be the address return false; } if (addressAndZone[1] === '') { // the second part must not be empty return false; } } const blocks = addressAndZone[0].split(':'); let foundOmissionBlock = false; // marker to indicate :: // At least some OS accept the last 32 bits of an IPv6 address // (i.e. 2 of the blocks) in IPv4 notation, and RFC 3493 says // that '::ffff:a.b.c.d' is valid for IPv4-mapped IPv6 addresses, // and '::a.b.c.d' is deprecated, but also valid. const foundIPv4TransitionBlock = isIp(blocks[blocks.length - 1], '4'); const expectedNumberOfBlocks = foundIPv4TransitionBlock ? 7 : 8; if (blocks.length > expectedNumberOfBlocks) { return false; } // initial or final :: if (str === '::') { return true; } else if (str.substr(0, 2) === '::') { blocks.shift(); blocks.shift(); foundOmissionBlock = true; } else if (str.substr(str.length - 2) === '::') { blocks.pop(); blocks.pop(); foundOmissionBlock = true; } for (let i = 0; i < blocks.length; ++i) { // test for a :: which can not be at the string start/end // since those cases have been handled above if (blocks[i] === '' && i > 0 && i < blocks.length - 1) { if (foundOmissionBlock) { return false; // multiple :: in address } foundOmissionBlock = true; } else if (foundIPv4TransitionBlock && i === blocks.length - 1) ; else if (!ipv6Block.test(blocks[i])) { return false; } } if (foundOmissionBlock) { return blocks.length >= 1; } return blocks.length === expectedNumberOfBlocks; } return false; } // credits: https://github.com/validatorjs/validator.js/blob/master/src/lib/isCreditCard.js const creditCard$1 = /^(?:4[0-9]{12}(?:[0-9]{3,6})?|5[1-5][0-9]{14}|(222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}|6(?:011|5[0-9][0-9])[0-9]{12,15}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11}|6[27][0-9]{14})$/; function isCreditCard(str) { const sanitized = str.replace(/[- ]+/g, ''); if (!creditCard$1.test(sanitized)) { return false; } let sum = 0; let digit; let tmpNum; let shouldDouble; for (let i = sanitized.length - 1; i >= 0; i--) { digit = sanitized.substring(i, (i + 1)); tmpNum = parseInt(digit, 10); if (shouldDouble) { tmpNum *= 2; if (tmpNum >= 10) { sum += ((tmpNum % 10) + 1); } else { sum += tmpNum; } } else { sum += tmpNum; } shouldDouble = !shouldDouble; } return !!((sum % 10) === 0 ? sanitized : false); } function creditCard() { return { name: "creditCard", handler: (value) => { if (typeof value !== 'string') { return false; } return isCreditCard(value); }, }; } class DateAdapter { constructor(dateLib) { this.dateLib = dateLib; // https://www.unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table // readonly FORMAT_yyyy_MM_dd: string = 'yyyy-MM-dd'; this.FORMAT_DATE = 'yyyy-MM-dd'; this.FORMAT_DATETIME = 'yyyy-MM-dd HH:mm:ss'; } } class DateFnsAdapter extends DateAdapter { constructor(dateLib) { super(dateLib); this.FORMAT_DATE = 'yyyy-MM-dd'; this.FORMAT_DATETIME = 'yyyy-MM-dd HH:mm:ss'; } isAfter(format, date, dateToCompare) { date = this.parseDate(date, format); dateToCompare = this.parseDate(dateToCompare, format); return this.dateLib.isAfter(date, dateToCompare); } isBefore(format, date, dateToCompare) { date = this.parseDate(date, format); dateToCompare = this.parseDate(dateToCompare, format); return this.dateLib.isBefore(date, dateToCompare); } addDays(date, days) { return this.dateLib.addDays(date, days); } subDays(date, days) { return this.dateLib.subDays(date, days); } parseDate(date, format) { if (!(date instanceof Date)) { date = this.dateLib.parse(date, format, new Date); } // @ts-ignore let d = date; d.setHours(0); d.setMinutes(0); d.setSeconds(0); d.setMilliseconds(0); return d; } parse(date, format, referenceDate) { return this.dateLib.parse(date, format, referenceDate); } isValidDateFormat(date, format) { return this.dateLib.isValid(this.parse(date, format, new Date)); } isValidIsoDateFormat(date) { return this.dateLib.isValid(this.dateLib.parseISO(date)); } format(date, format) { return this.dateLib.format(date, format); } } class MomentAdapter extends DateAdapter { constructor(dateLib) { super(dateLib); this.FORMAT_DATE = 'YYYY-MM-DD'; this.FORMAT_DATETIME = 'YYYY-MM-DD HH:mm:ss'; } isAfter(format, date, dateToCompare) { return this.dateLib(date, format).isAfter(this.format(dateToCompare, format)); } isBefore(format, date, dateToCompare) { return this.dateLib(date, format).isBefore(this.format(dateToCompare, format)); } addDays(date, days) { return this.dateLib(date).add(days, 'days').toDate(); } subDays(date, days) { return this.dateLib(date).subtract(days, 'days').toDate(); } parseDate(date, format) { return this.dateLib(date, format).set({ h: 0, m: 0, s: 0, ms: 0, }).toDate(); } parse(date, format, referenceDate) { return this.dateLib(date, format).toDate(); } isValidDateFormat(date, format) { return this.dateLib(date, format, true).isValid(); } isValidIsoDateFormat(date) { return this.dateLib(date, this.dateLib.ISO_8601).isValid(); } format(date, format) { return this.dateLib(date).format(format); } } function dateAdapter() { const adapterInstance = get('dateAdapter'); if (!adapterInstance) { throw new Error('Please set date adapter to use date rules.'); } return adapterInstance; } function dateAfter(args) { if (args.length !== 2) { throw new Error('Rule accepts 2 argument (format,date).'); } const adapter = dateAdapter(); const [format, dateToCompare] = args; if (typeof format !== 'string') { throw new TypeError('First element must be date format.'); } return { name: "dateAfter", handler: (value) => { return adapter.isAfter(format, value, dateToCompare || new Date); }, }; } function after(args) { if (!args.length || args.length > 2) { throw new Error('Rule accepts 2 argument (format,[date optional]).'); } const adapter = dateAdapter(); const [format, dateToCompare] = args; if (typeof format !== 'string') { throw new TypeError('First element must be date format.'); } return { name: "after", handler: (value) => { return adapter.isAfter(format, value, dateToCompare || new Date()); }, }; } function dateAfterToday(args) { if (args.length !== 1) { throw new Error('Rule accepts only 1 argument (format).'); } const adapter = dateAdapter(); const [format] = args; return { name: "dateAfterToday", handler: (value) => { return adapter.isAfter(format, value, new Date); }, }; } function dateBefore(args) { if (args.length !== 2) { throw new Error('Rule accepts 2 argument (format,date).'); } const adapter = dateAdapter(); const [format, dateToCompare] = args; if (typeof format !== 'string') { throw new TypeError('First element must be date format.'); } return { name: "dateBefore", handler: (value) => { return adapter.isBefore(format, value, dateToCompare); }, }; } function before(args) { if (!args.length || args.length > 2) { throw new Error('Rule accepts 2 argument (format,[date optional]).'); } const adapter = dateAdapter(); const [format, dateToCompare] = args; if (typeof format !== 'string') { throw new TypeError('First element must be date format.'); } return { name: "before", handler: (value) => { return adapter.isBefore(format, value, dateToCompare || new Date()); }, }; } function dateBeforeToday(args) { if (args.length !== 1) { throw new Error('Rule accepts only 1 argument (format).'); } const adapter = dateAdapter(); const [format] = args; return { name: "dateBeforeToday", handler: (value) => { return adapter.isBefore(format, value, new Date); }, }; } function dateFormat(args) { if (args.length !== 1) { throw new Error('Rule accepts only 1 argument (format).'); } const [format] = args; const adapter = dateAdapter(); return { name: "dateFormat", handler: (value) => { return adapter.isValidDateFormat(value, format); }, }; } function dateISO() { const adapter = dateAdapter(); return { name: "dateISO", handler: (value) => { return adapter.isValidIsoDateFormat(value); }, }; } // export { dateISO as dateiso, dateISO as iso8601 }; function datetime() { const adapter = dateAdapter(); return { name: 'datetime', handler: (value) => { return adapter.isValidDateFormat(value, adapter.FORMAT_DATETIME); } }; } function date(args = []) { const adapter = dateAdapter(); const [format = adapter.FORMAT_DATE] = args; return { name: 'date', handler: (value) => { return adapter.isValidDateFormat(value, format); } }; } const iso8601Regex = /^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-3])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/; function dateiso() { return { name: "dateiso", handler: (value) => { if (typeof value !== 'string') { return false; } return iso8601Regex.test(value); }, }; } function iso8601() { return dateiso(); } function different(args) { if (!args.length) { throw new Error("Invalid number of arguments."); } return { name: "different", handler: (value, v) => { const [otherInput] = args; const otherValue = v.attributeValue(otherInput); if (otherValue === value) { return false; } return true; }, }; } function digits(args = []) { if (args.length !== 1) { throw new Error('Invalid number of arguments.'); } const [lenStr] = args; const len = parseInt(lenStr, 10); if (!len) { throw new TypeError('Seed must be number, greater then 0.'); } return { name: "digits", handler: (value) => { const v = String(value); if (isInt(v, true) && v.length === len) { return true; } return false; }, }; } function digitsBetween(args = []) { if (args.length !== 2) { throw new Error('Invalid number of arguments.'); } const [min, max] = args; const minInt = parseInt(min, 10); const maxInt = parseInt(max, 10); if (!minInt || !maxInt) { throw new TypeError('Seeds must be number, greater then 0.'); } if (min > max) { throw new RangeError('Seed min must be less then max.'); } return { name: "digitsBetween", handler: (value) => { const v = String(value); const len = v.length; if (isInt(v, true) && len >= minInt && len <= maxInt) { return true; } return false; }, }; } function domain() { return { name: "domain", handler: (value) => { if (typeof value !== 'string') { return false; } return isDomain(value); }, }; } function email() { return { name: "email", handler: (value) => { if (typeof value !== 'string') { return false; } return isEmail(value); }, }; } const asciiRegex = /^[\x00-\x7F]+$/; function ascii() { return { name: "ascii", handler: (value) => { if (typeof value !== 'string') { return false; } return asciiRegex.test(value); }, }; } function json() { return { name: "json", handler: (value) => { try { const obj = JSON.parse(value); return !!obj && typeof obj === 'object'; } catch (e) { /* ignore */ } return false; }, }; } const notBase64 = /[^A-Z0-9+\/=]/i; const urlSafeBase64 = /^[A-Z0-9_\-]*$/i; function base64(args = []) { return { name: "base64", handler: (value) => { const len = value.length; if (args && args[0] === 'urlsafe') { return urlSafeBase64.test(value); } if (len % 4 !== 0 || notBase64.test(value)) { return false; } const firstPaddingChar = value.indexOf('='); return firstPaddingChar === -1 || firstPaddingChar === len - 1 || (firstPaddingChar === len - 2 && value[len - 1] === '='); }, }; } function equals(args) { if (args.length !== 1) { throw new Error('Invalid number of arguments.'); } const [otherValue] = args; return { name: "equals", handler: (value) => { return value === otherValue; }, }; } function gt(args) { if (args.length !== 1) { throw new Error('Invalid number of arguments.'); } const [anotherAttr] = args; return { name: "gt", handler: (value, v) => { const anotherAttrVal = v.attributeValue(anotherAttr); return Number(value) > Number(anotherAttrVal); }, }; } function gte(args) { if (args.length !== 1) { throw new Error('Invalid number of arguments.'); } return { name: "gte", handler: (value, v) => { const [anotherAttr] = args; const anotherAttrVal = v.attributeValue(anotherAttr); return Number(value) >= Number(anotherAttrVal); }, }; } const lengths = { md5: 32, md4: 32, sha1: 40, sha256: 64, sha384: 96, sha512: 128, ripemd128: 32, ripemd160: 40, tiger128: 32, tiger160: 40, tiger192: 48, crc32: 8, crc32b: 8, }; function hash(args) { if (args.length !== 1) { throw new Error('Invalid number of arguments.'); } const [algo] = args; const len = lengths[algo]; if (!len) { throw new Error(`Algo ${algo} not supported.`); } return { name: "hash", handler: (value) => { const hash = new RegExp(`^[a-fA-F0-9]{${len}}$`); return hash.test(value); }, }; } function camelCaseToSentance(str) { return str.replace(/([A-Z]+)/g, " $1").trimLeft().toLowerCase(); } function snakeCaseToSentance(str) { return str.replace(/_/g, " "); } function sizeToBytes(inputSize) { const size = inputSize.toLowerCase(); /* istanbul ignore next */ if (size.includes('gb') || size.includes('g')) { return parseInt(size.replace('gb', '').replace('g', '')) * 1024 * 1024 * 1024; } /* istanbul ignore next */ if (size.includes('mb') || size.includes('m')) { return parseInt(size.replace('mb', '').replace('m', '')) * 1024 * 1024; } /* istanbul ignore next */ if (size.includes('kb') || size.includes('k')) { return parseInt(size.replace('kb', '').replace('k', '')) * 1024; } /* istanbul ignore next */ if (size.includes('b')) { return parseInt(size.replace('b', '')); } /* istanbul ignore next */ return parseInt(size) * 1024; } const hexadecimal = /^(0x|0h)?[0-9A-F]+$/i; function isHexadecimal(value) { return hexadecimal.test(value); } const hexcolor = /^#?([0-9A-F]{3}|[0-9A-F]{4}|[0-9A-F]{6}|[0-9A-F]{8})$/i; function hex() { return { name: "hex", handler: (value) => { if (typeof value !== 'string') { return false; } return isHexadecimal(value); }, }; } function hexColor() { return { name: "hexColor", handler: (value) => { if (typeof value !== 'string') { return false; } return hexcolor.test(value); }, }; } function _in(args) { if (!args || !args.length) { throw new Error('Invalid number of arguments.'); } return { name: "in", handler: (value) => { return !(args.indexOf(value) < 0); }, }; } function notIn(args) { if (!args.length) { throw new Error('Invalid number of arguments.'); } return { name: "notIn", handler: (value) => { return !_in(args).handler(value); }, }; } function ip(args = []) { return { name: "ip", handler: (value) => { if (typeof value !== 'string') { return false; } return isIp(value, args[0] || ''); }, }; } const lat = /^\(?[+-]?(90(\.0+)?|[1-8]?\d(\.\d+)?)$/; const long = /^\s?[+-]?(180(\.0+)?|1[0-7]\d(\.\d+)?|\d{1,2}(\.\d+)?)\)?$/; const latDMS = /^(([1-8]?\d)\D+([1-5]?\d|60)\D+([1-5]?\d|60)(\.\d+)?|90\D+0\D+0)\D+[NSns]?$/i; const longDMS = /^\s*([1-7]?\d{1,2}\D+([1-5]?\d|60)\D+([1-5]?\d|60)(\.\d+)?|180\D+0\D+0)\D+[EWew]?$/i; function latLong(args = []) { const checkDMS = args && args[0] === 'dms'; return { name: "latLong", handler: (value) => { if (typeof value !== 'string') { return false; } if (!value.includes(',')) { return false; } const pair = value.split(','); if ((pair[0].startsWith('(') && !pair[1].endsWith(')')) || (pair[1].endsWith(')') && !pair[0].startsWith('('))) return false; if (checkDMS) { return latDMS.test(pair[0]) && longDMS.test(pair[1]); } return lat.test(pair[0]) && long.test(pair[1]); }, }; } function maxLength(args) { if (args.length !== 1) { throw new Error('Invalid number of arguments.'); } const maxNum = parseInt(args[0], 10); return { name: "maxLength", handler: (value) => { return value.toString().length <= maxNum; }, }; } function minLength(args) { if (args.length !== 1) { throw new Error('Invalid number of arguments.'); } const maxNum = parseInt(args[0], 10); return { name: "minLength", handler: (value) => { return (value.toString().length >= maxNum); }, }; } function length(args) { let min; if (args.length < 1 || args.length > 2) { throw new Error('Invalid number of arguments.'); } const max = parseInt(args[0], 10); if (args[1]) { min = parseInt(args[1], 10); } return { name: 'length', handler: (value) => { const len = value.length; if (len <= max) { if (min && len < min) { return false; } return true; } return false; }, }; } function lengthBetween(args) { if (args.length !== 2) { throw new Error('Invalid number of arguments.'); } const min = parseInt(args[0], 10); const max = parseInt(args[1], 10); if (min >= max) { throw new RangeError('Seed min must be less then max.'); } return { name: 'lengthBetween',