@hapi/validate
Advanced tools
Comparing version 1.1.0 to 1.1.1
'use strict'; | ||
const Url = require('url'); | ||
const Assert = require('@hapi/hoek/lib/assert'); | ||
const Domain = require('@hapi/address/lib/domain'); | ||
const Email = require('@hapi/address/lib/email'); | ||
const Ip = require('@hapi/address/lib/ip'); | ||
const EscapeRegex = require('@hapi/hoek/lib/escapeRegex'); | ||
const Tlds = require('@hapi/address/lib/tlds'); | ||
const Uri = require('@hapi/address/lib/uri'); | ||
@@ -16,3 +13,2 @@ const Any = require('./any'); | ||
const internals = { | ||
tlds: Tlds instanceof Set ? { tlds: { allow: Tlds, deny: null } } : false, // $lab:coverage:ignore$ | ||
base64Regex: { | ||
@@ -32,3 +28,2 @@ // paddingRequired | ||
hexRegex: /^[a-f0-9]+$/i, | ||
ipRegex: Ip.regex().regex, | ||
isoDurationRegex: /^P(?!$)(\d+Y)?(\d+M)?(\d+W)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+S)?)?$/, | ||
@@ -49,3 +44,7 @@ | ||
cidrPresences: ['required', 'optional', 'forbidden'], | ||
normalizationForms: ['NFC', 'NFD', 'NFKC', 'NFKD'] | ||
normalizationForms: ['NFC', 'NFD', 'NFKC', 'NFKD'], | ||
domainControlRx: /[\x00-\x20@\:\/]/, // Control + space + separators | ||
tldSegmentRx: /^[a-zA-Z](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?$/, | ||
domainSegmentRx: /^[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?$/ | ||
}; | ||
@@ -260,51 +259,2 @@ | ||
domain: { | ||
method(options) { | ||
if (options) { | ||
Common.assertOptions(options, ['allowUnicode', 'maxDomainSegments', 'minDomainSegments', 'tlds']); | ||
} | ||
const address = internals.addressOptions(options); | ||
return this.$_addRule({ name: 'domain', args: { options }, address }); | ||
}, | ||
validate(value, helpers, args, { address }) { | ||
if (Domain.isValid(value, address)) { | ||
return value; | ||
} | ||
return helpers.error('string.domain'); | ||
} | ||
}, | ||
email: { | ||
method(options = {}) { | ||
Common.assertOptions(options, ['allowUnicode', 'ignoreLength', 'maxDomainSegments', 'minDomainSegments', 'multiple', 'separator', 'tlds']); | ||
Assert(options.multiple === undefined || typeof options.multiple === 'boolean', 'multiple option must be an boolean'); | ||
const address = internals.addressOptions(options); | ||
const regex = new RegExp(`\\s*[${options.separator ? EscapeRegex(options.separator) : ','}]\\s*`); | ||
return this.$_addRule({ name: 'email', args: { options }, regex, address }); | ||
}, | ||
validate(value, helpers, { options }, { regex, address }) { | ||
const emails = options.multiple ? value.split(regex) : [value]; | ||
const invalids = []; | ||
for (const email of emails) { | ||
if (!Email.isValid(email, address)) { | ||
invalids.push(email); | ||
} | ||
} | ||
if (!invalids.length) { | ||
return value; | ||
} | ||
return helpers.error('string.email', { value, invalids }); | ||
} | ||
}, | ||
guid: { | ||
@@ -396,3 +346,3 @@ alias: 'uuid', | ||
if (Domain.isValid(value, { minDomainSegments: 1 }) || | ||
if (internals.isDomainValid(value) || | ||
internals.ipRegex.test(value)) { | ||
@@ -414,25 +364,2 @@ | ||
ip: { | ||
method(options = {}) { | ||
Common.assertOptions(options, ['cidr', 'version']); | ||
const { cidr, versions, regex } = Ip.regex(options); | ||
const version = options.version ? versions : undefined; | ||
return this.$_addRule({ name: 'ip', args: { options: { cidr, version } }, regex }); | ||
}, | ||
validate(value, helpers, { options }, { regex }) { | ||
if (regex.test(value)) { | ||
return value; | ||
} | ||
if (options.version) { | ||
return helpers.error('string.ipVersion', { value, cidr: options.cidr, version: options.version }); | ||
} | ||
return helpers.error('string.ip', { value, cidr: options.cidr }); | ||
} | ||
}, | ||
isoDate: { | ||
@@ -634,45 +561,2 @@ method() { | ||
} | ||
}, | ||
uri: { | ||
method(options = {}) { | ||
Common.assertOptions(options, ['allowRelative', 'allowQuerySquareBrackets', 'domain', 'relativeOnly', 'scheme']); | ||
if (options.domain) { | ||
Common.assertOptions(options.domain, ['allowUnicode', 'maxDomainSegments', 'minDomainSegments', 'tlds']); | ||
} | ||
const { regex, scheme } = Uri.regex(options); | ||
const domain = options.domain ? internals.addressOptions(options.domain) : null; | ||
return this.$_addRule({ name: 'uri', args: { options }, regex, domain, scheme }); | ||
}, | ||
validate(value, helpers, { options }, { regex, domain, scheme }) { | ||
if (['http:/', 'https:/'].includes(value)) { // scheme:/ is technically valid but makes no sense | ||
return helpers.error('string.uri'); | ||
} | ||
const match = regex.exec(value); | ||
if (match) { | ||
if (domain) { | ||
const matched = match[1] || match[2]; | ||
if (!Domain.isValid(matched, domain)) { | ||
return helpers.error('string.domain', { value: matched }); | ||
} | ||
} | ||
return value; | ||
} | ||
if (options.relativeOnly) { | ||
return helpers.error('string.uriRelativeOnly'); | ||
} | ||
if (options.scheme) { | ||
return helpers.error('string.uriCustomScheme', { scheme, value }); | ||
} | ||
return helpers.error('string.uri'); | ||
} | ||
} | ||
@@ -687,4 +571,2 @@ }, | ||
'string.dataUri': '{{#label}} must be a valid dataUri string', | ||
'string.domain': '{{#label}} must contain a valid domain name', | ||
'string.email': '{{#label}} must be a valid email', | ||
'string.empty': '{{#label}} is not allowed to be empty', | ||
@@ -695,4 +577,2 @@ 'string.guid': '{{#label}} must be a valid GUID', | ||
'string.hostname': '{{#label}} must be a valid hostname', | ||
'string.ip': '{{#label}} must be a valid ip address with a {{#cidr}} CIDR', | ||
'string.ipVersion': '{{#label}} must be a valid ip address of one of the following versions {{#version}} with a {{#cidr}} CIDR', | ||
'string.isoDate': '{{#label}} must be in iso format', | ||
@@ -711,5 +591,2 @@ 'string.isoDuration': '{{#label}} must be a valid ISO 8601 duration', | ||
'string.trim': '{{#label}} must not have leading or trailing whitespace', | ||
'string.uri': '{{#label}} must be a valid uri', | ||
'string.uriCustomScheme': '{{#label}} must be a valid uri with a scheme matching the {{#scheme}} pattern', | ||
'string.uriRelativeOnly': '{{#label}} must be a valid relative uri', | ||
'string.uppercase': '{{#label}} must only contain uppercase characters' | ||
@@ -722,93 +599,188 @@ } | ||
internals.addressOptions = function (options) { | ||
internals.isoDate = function (value) { | ||
if (!options) { | ||
return options; | ||
if (!Common.isIsoDate(value)) { | ||
return null; | ||
} | ||
// minDomainSegments | ||
const date = new Date(value); | ||
if (isNaN(date.getTime())) { | ||
return null; | ||
} | ||
Assert(options.minDomainSegments === undefined || | ||
Number.isSafeInteger(options.minDomainSegments) && options.minDomainSegments > 0, 'minDomainSegments must be a positive integer'); | ||
return date.toISOString(); | ||
}; | ||
// maxDomainSegments | ||
Assert(options.maxDomainSegments === undefined || | ||
Number.isSafeInteger(options.maxDomainSegments) && options.maxDomainSegments > 0, 'maxDomainSegments must be a positive integer'); | ||
internals.length = function (schema, name, limit, operator, encoding) { | ||
// tlds | ||
Assert(!encoding || Buffer && Buffer.isEncoding(encoding), 'Invalid encoding:', encoding); // $lab:coverage:ignore$ | ||
if (options.tlds === false) { | ||
return options; | ||
return schema.$_addRule({ name, method: 'length', args: { limit, encoding }, operator }); | ||
}; | ||
internals.rfc3986 = function () { | ||
const rfc3986 = {}; | ||
const hexDigit = '\\dA-Fa-f'; // HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" | ||
const hexDigitOnly = '[' + hexDigit + ']'; | ||
const unreserved = '\\w-\\.~'; // unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" | ||
const subDelims = '!\\$&\'\\(\\)\\*\\+,;='; // sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" | ||
const decOctect = '(?:0{0,2}\\d|0?[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])'; // dec-octet = DIGIT / %x31-39 DIGIT / "1" 2DIGIT / "2" %x30-34 DIGIT / "25" %x30-35 ; 0-9 / 10-99 / 100-199 / 200-249 / 250-255 | ||
rfc3986.ipv4 = '(?:' + decOctect + '\\.){3}' + decOctect; // IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet | ||
/* | ||
h16 = 1*4HEXDIG ; 16 bits of address represented in hexadecimal | ||
ls32 = ( h16 ":" h16 ) / IPv4address ; least-significant 32 bits of address | ||
IPv6address = 6( h16 ":" ) ls32 | ||
/ "::" 5( h16 ":" ) ls32 | ||
/ [ h16 ] "::" 4( h16 ":" ) ls32 | ||
/ [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 | ||
/ [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 | ||
/ [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 | ||
/ [ *4( h16 ":" ) h16 ] "::" ls32 | ||
/ [ *5( h16 ":" ) h16 ] "::" h16 | ||
/ [ *6( h16 ":" ) h16 ] "::" | ||
*/ | ||
const h16 = hexDigitOnly + '{1,4}'; | ||
const ls32 = '(?:' + h16 + ':' + h16 + '|' + rfc3986.ipv4 + ')'; | ||
const IPv6SixHex = '(?:' + h16 + ':){6}' + ls32; | ||
const IPv6FiveHex = '::(?:' + h16 + ':){5}' + ls32; | ||
const IPv6FourHex = '(?:' + h16 + ')?::(?:' + h16 + ':){4}' + ls32; | ||
const IPv6ThreeHex = '(?:(?:' + h16 + ':){0,1}' + h16 + ')?::(?:' + h16 + ':){3}' + ls32; | ||
const IPv6TwoHex = '(?:(?:' + h16 + ':){0,2}' + h16 + ')?::(?:' + h16 + ':){2}' + ls32; | ||
const IPv6OneHex = '(?:(?:' + h16 + ':){0,3}' + h16 + ')?::' + h16 + ':' + ls32; | ||
const IPv6NoneHex = '(?:(?:' + h16 + ':){0,4}' + h16 + ')?::' + ls32; | ||
const IPv6NoneHex2 = '(?:(?:' + h16 + ':){0,5}' + h16 + ')?::' + h16; | ||
const IPv6NoneHex3 = '(?:(?:' + h16 + ':){0,6}' + h16 + ')?::'; | ||
rfc3986.v4Cidr = '(?:\\d|[1-2]\\d|3[0-2])'; // IPv4 cidr = DIGIT / %x31-32 DIGIT / "3" %x30-32 ; 0-9 / 10-29 / 30-32 | ||
rfc3986.v6Cidr = '(?:0{0,2}\\d|0?[1-9]\\d|1[01]\\d|12[0-8])'; // IPv6 cidr = DIGIT / %x31-39 DIGIT / "1" %x0-1 DIGIT / "12" %x0-8; 0-9 / 10-99 / 100-119 / 120-128 | ||
rfc3986.ipv6 = '(?:' + IPv6SixHex + '|' + IPv6FiveHex + '|' + IPv6FourHex + '|' + IPv6ThreeHex + '|' + IPv6TwoHex + '|' + IPv6OneHex + '|' + IPv6NoneHex + '|' + IPv6NoneHex2 + '|' + IPv6NoneHex3 + ')'; | ||
rfc3986.ipvfuture = 'v' + hexDigitOnly + '+\\.[' + unreserved + subDelims + ':]+'; // IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" ) | ||
return rfc3986; | ||
}; | ||
internals.ipRegex = function (options = {}) { | ||
// CIDR | ||
Assert(options.cidr === undefined || typeof options.cidr === 'string', 'options.cidr must be a string'); | ||
const cidr = options.cidr ? options.cidr.toLowerCase() : 'optional'; | ||
Assert(['required', 'optional', 'forbidden'].includes(cidr), 'options.cidr must be one of required, optional, forbidden'); | ||
// Versions | ||
Assert(options.version === undefined || typeof options.version === 'string' || Array.isArray(options.version), 'options.version must be a string or an array of string'); | ||
let versions = options.version || ['ipv4', 'ipv6', 'ipvfuture']; | ||
if (!Array.isArray(versions)) { | ||
versions = [versions]; | ||
} | ||
if (options.tlds === true || | ||
options.tlds === undefined) { | ||
Assert(versions.length >= 1, 'options.version must have at least 1 version specified'); | ||
Assert(internals.tlds, 'Built-in TLD list disabled'); | ||
return Object.assign({}, options, internals.tlds); | ||
for (let i = 0; i < versions.length; ++i) { | ||
Assert(typeof versions[i] === 'string', 'options.version must only contain strings'); | ||
versions[i] = versions[i].toLowerCase(); | ||
Assert(['ipv4', 'ipv6', 'ipvfuture'].includes(versions[i]), 'options.version contains unknown version ' + versions[i] + ' - must be one of ipv4, ipv6, ipvfuture'); | ||
} | ||
Assert(typeof options.tlds === 'object', 'tlds must be true, false, or an object'); | ||
versions = Array.from(new Set(versions)); | ||
const deny = options.tlds.deny; | ||
if (deny) { | ||
if (Array.isArray(deny)) { | ||
options = Object.assign({}, options, { tlds: { deny: new Set(deny) } }); | ||
// Regex | ||
const rfc3986 = internals.rfc3986(); | ||
const parts = versions.map((version) => { | ||
// Forbidden | ||
if (cidr === 'forbidden') { | ||
return rfc3986[version]; | ||
} | ||
Assert(options.tlds.deny instanceof Set, 'tlds.deny must be an array, Set, or boolean'); | ||
Assert(!options.tlds.allow, 'Cannot specify both tlds.allow and tlds.deny lists'); | ||
internals.validateTlds(options.tlds.deny, 'tlds.deny'); | ||
return options; | ||
} | ||
// Required | ||
const allow = options.tlds.allow; | ||
if (!allow) { | ||
return options; | ||
} | ||
const cidrpart = `\\/${version === 'ipv4' ? rfc3986.v4Cidr : rfc3986.v6Cidr}`; | ||
if (allow === true) { | ||
Assert(internals.tlds, 'Built-in TLD list disabled'); | ||
return Object.assign({}, options, internals.tlds); | ||
} | ||
if (cidr === 'required') { | ||
return `${rfc3986[version]}${cidrpart}`; | ||
} | ||
if (Array.isArray(allow)) { | ||
options = Object.assign({}, options, { tlds: { allow: new Set(allow) } }); | ||
} | ||
// Optional | ||
Assert(options.tlds.allow instanceof Set, 'tlds.allow must be an array, Set, or boolean'); | ||
internals.validateTlds(options.tlds.allow, 'tlds.allow'); | ||
return options; | ||
return `${rfc3986[version]}(?:${cidrpart})?`; | ||
}); | ||
const raw = `(?:${parts.join('|')})`; | ||
return new RegExp(`^${raw}$`); | ||
}; | ||
internals.ipRegex = internals.ipRegex(); | ||
internals.validateTlds = function (set, source) { | ||
for (const tld of set) { | ||
Assert(Domain.isValid(tld, { minDomainSegments: 1, maxDomainSegments: 1 }), `${source} must contain valid top level domain names`); | ||
internals.isDomainValid = function (domain) { | ||
if (typeof domain !== 'string') { | ||
return false; | ||
} | ||
}; | ||
if (!domain) { | ||
return false; | ||
} | ||
internals.isoDate = function (value) { | ||
if (domain.length > 256) { | ||
return false; | ||
} | ||
if (!Common.isIsoDate(value)) { | ||
return null; | ||
domain = domain.normalize('NFC'); | ||
if (internals.domainControlRx.test(domain)) { | ||
return false; | ||
} | ||
const date = new Date(value); | ||
if (isNaN(date.getTime())) { | ||
return null; | ||
domain = internals.punycode(domain); | ||
const segments = domain.split('.'); | ||
for (let i = 0; i < segments.length; ++i) { | ||
const segment = segments[i]; | ||
if (!segment.length) { | ||
return false; | ||
} | ||
if (segment.length > 63) { | ||
return false; | ||
} | ||
if (i < segments.length - 1) { | ||
if (!internals.domainSegmentRx.test(segment)) { | ||
return false; | ||
} | ||
} | ||
else { | ||
if (!internals.tldSegmentRx.test(segment)) { | ||
return false; | ||
} | ||
} | ||
} | ||
return date.toISOString(); | ||
return true; | ||
}; | ||
internals.length = function (schema, name, limit, operator, encoding) { | ||
internals.punycode = function (domain) { | ||
Assert(!encoding || Buffer && Buffer.isEncoding(encoding), 'Invalid encoding:', encoding); // $lab:coverage:ignore$ | ||
return schema.$_addRule({ name, method: 'length', args: { limit, encoding }, operator }); | ||
try { | ||
return new Url.URL(`http://${domain}`).host; | ||
} | ||
catch (err) { | ||
return domain; | ||
} | ||
}; |
{ | ||
"name": "@hapi/validate", | ||
"description": "Object schema validation", | ||
"version": "1.1.0", | ||
"version": "1.1.1", | ||
"repository": "git://github.com/hapijs/validate", | ||
@@ -11,3 +11,2 @@ "main": "lib/index.js", | ||
"dependencies": { | ||
"@hapi/address": "^4.1.0", | ||
"@hapi/hoek": "^9.0.0", | ||
@@ -14,0 +13,0 @@ "@hapi/topo": "^5.0.0" |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
2
0
216368
5609
- Removed@hapi/address@^4.1.0
- Removed@hapi/address@4.1.0(transitive)