diff --git a/README.md b/README.md
index 7c1cadb9f..791fb53c5 100644
--- a/README.md
+++ b/README.md
@@ -106,7 +106,7 @@ Validator | Description
**isDecimal(str [, options])** | check if the string represents a decimal number, such as 0.1, .3, 1.1, 1.00003, 4.0, etc.
`options` is an object which defaults to `{force_decimal: false, decimal_digits: '1,', locale: 'en-US'}`.
`locale` determines the decimal separator and is one of `['ar', 'ar-AE', 'ar-BH', 'ar-DZ', 'ar-EG', 'ar-IQ', 'ar-JO', 'ar-KW', 'ar-LB', 'ar-LY', 'ar-MA', 'ar-QA', 'ar-QM', 'ar-SA', 'ar-SD', 'ar-SY', 'ar-TN', 'ar-YE', 'bg-BG', 'cs-CZ', 'da-DK', 'de-DE', 'el-GR', 'en-AU', 'en-GB', 'en-HK', 'en-IN', 'en-NZ', 'en-US', 'en-ZA', 'en-ZM', 'eo', 'es-ES', 'fa', 'fa-AF', 'fa-IR', 'fr-FR', 'fr-CA', 'hu-HU', 'id-ID', 'it-IT', 'ku-IQ', 'nb-NO', 'nl-NL', 'nn-NO', 'pl-PL', 'pl-Pl', 'pt-BR', 'pt-PT', 'ru-RU', 'sl-SI', 'sr-RS', 'sr-RS@latin', 'sv-SE', 'tr-TR', 'uk-UA', 'vi-VN']`.
**Note:** `decimal_digits` is given as a range like '1,3', a specific value like '3' or min like '1,'.
**isDivisibleBy(str, number)** | check if the string is a number that is divisible by another.
**isEAN(str)** | check if the string is an [EAN (European Article Number)][European Article Number].
-**isEmail(str [, options])** | check if the string is an email.
`options` is an object which defaults to `{ allow_display_name: false, require_display_name: false, allow_utf8_local_part: true, require_tld: true, allow_ip_domain: false, allow_underscores: false, domain_specific_validation: false, blacklisted_chars: '', host_blacklist: [] }`. If `allow_display_name` is set to true, the validator will also match `Display Name `. If `require_display_name` is set to true, the validator will reject strings without the format `Display Name `. If `allow_utf8_local_part` is set to false, the validator will not allow any non-English UTF8 character in email address' local part. If `require_tld` is set to false, email addresses without a TLD in their domain will also be matched. If `ignore_max_length` is set to true, the validator will not check for the standard max length of an email. If `allow_ip_domain` is set to true, the validator will allow IP addresses in the host part. If `domain_specific_validation` is true, some additional validation will be enabled, e.g. disallowing certain syntactically valid email addresses that are rejected by Gmail. If `blacklisted_chars` receives a string, then the validator will reject emails that include any of the characters in the string, in the name part. If `host_blacklist` is set to an array of strings and the part of the email after the `@` symbol matches one of the strings defined in it, the validation fails. If `host_whitelist` is set to an array of strings and the part of the email after the `@` symbol matches none of the strings defined in it, the validation fails.
+**isEmail(str [, options])** | check if the string is an email.
`options` is an object which defaults to `{ allow_display_name: false, require_display_name: false, allow_utf8_local_part: true, require_tld: true, allow_ip_domain: false, allow_underscores: false, domain_specific_validation: false, blacklisted_chars: '', host_blacklist: [] }`. If `allow_display_name` is set to true, the validator will also match `Display Name `. If `require_display_name` is set to true, the validator will reject strings without the format `Display Name `. If `allow_utf8_local_part` is set to false, the validator will not allow any non-English UTF8 character in email address' local part. If `require_tld` is set to false, email addresses without a TLD in their domain will also be matched. If `ignore_max_length` is set to true, the validator will not check for the standard max length of an email. If `allow_ip_domain` is set to true, the validator will allow IP addresses in the host part. If `domain_specific_validation` is true, some additional validation will be enabled, e.g. disallowing certain syntactically valid email addresses that are rejected by Gmail. If `blacklisted_chars` receives a string, then the validator will reject emails that include any of the characters in the string, in the name part. If `host_blacklist` is set to an array of strings or regexp, and the part of the email after the `@` symbol matches one of the strings defined in it, the validation fails. If `host_whitelist` is set to an array of strings or regexp, and the part of the email after the `@` symbol matches none of the strings defined in it, the validation fails.
**isEmpty(str [, options])** | check if the string has a length of zero.
`options` is an object which defaults to `{ ignore_whitespace: false }`.
**isEthereumAddress(str)** | check if the string is an [Ethereum][Ethereum] address. Does not validate address checksums.
**isFloat(str [, options])** | check if the string is a float.
`options` is an object which can contain the keys `min`, `max`, `gt`, and/or `lt` to validate the float is within boundaries (e.g. `{ min: 7.22, max: 9.55 }`) it also has `locale` as an option.
`min` and `max` are equivalent to 'greater or equal' and 'less or equal', respectively while `gt` and `lt` are their strict counterparts.
`locale` determines the decimal separator and is one of `['ar', 'ar-AE', 'ar-BH', 'ar-DZ', 'ar-EG', 'ar-IQ', 'ar-JO', 'ar-KW', 'ar-LB', 'ar-LY', 'ar-MA', 'ar-QA', 'ar-QM', 'ar-SA', 'ar-SD', 'ar-SY', 'ar-TN', 'ar-YE', 'bg-BG', 'cs-CZ', 'da-DK', 'de-DE', 'en-AU', 'en-GB', 'en-HK', 'en-IN', 'en-NZ', 'en-US', 'en-ZA', 'en-ZM', 'eo', 'es-ES', 'fr-CA', 'fr-FR', 'hu-HU', 'it-IT', 'nb-NO', 'nl-NL', 'nn-NO', 'pl-PL', 'pt-BR', 'pt-PT', 'ru-RU', 'sl-SI', 'sr-RS', 'sr-RS@latin', 'sv-SE', 'tr-TR', 'uk-UA']`. Locale list is `validator.isFloatLocales`.
@@ -139,7 +139,7 @@ Validator | Description
**isJSON(str [, options])** | check if the string is valid JSON (note: uses JSON.parse).
`options` is an object which defaults to `{ allow_primitives: false }`. If `allow_primitives` is true, the primitives 'true', 'false' and 'null' are accepted as valid JSON values.
**isJWT(str)** | check if the string is valid JWT token.
**isLatLong(str [, options])** | check if the string is a valid latitude-longitude coordinate in the format `lat,long` or `lat, long`.
`options` is an object that defaults to `{ checkDMS: false }`. Pass `checkDMS` as `true` to validate DMS(degrees, minutes, and seconds) latitude-longitude format.
-**isLength(str [, options])** | check if the string's length falls in a range.
`options` is an object which defaults to `{ min: 0, max: undefined }`. Note: this function takes into account surrogate pairs.
+**isLength(str [, options])** | check if the string's length falls in a range and equal to any of the integers of the `discreteLengths` array if provided.
`options` is an object which defaults to `{ min: 0, max: undefined, discreteLengths: undefined }`. Note: this function takes into account surrogate pairs.
**isLicensePlate(str, locale)** | check if the string matches the format of a country's license plate.
`locale` is one of `['cs-CZ', 'de-DE', 'de-LI', 'en-IN', 'en-SG', 'en-PK', 'es-AR', 'hu-HU', 'pt-BR', 'pt-PT', 'sq-AL', 'sv-SE']` or `'any'`.
**isLocale(str)** | check if the string is a locale.
**isLowercase(str)** | check if the string is lowercase.
diff --git a/src/lib/isDate.js b/src/lib/isDate.js
index ede3e33e6..3a1e4afd2 100644
--- a/src/lib/isDate.js
+++ b/src/lib/isDate.js
@@ -28,6 +28,7 @@ export default function isDate(input, options) {
options = merge(options, default_date_options);
}
if (typeof input === 'string' && isValidFormat(options.format)) {
+ if (options.strictMode && input.length !== options.format.length) return false;
const formatDelimiter = options.delimiters
.find(delimiter => options.format.indexOf(delimiter) !== -1);
const dateDelimiter = options.strictMode
diff --git a/src/lib/isEmail.js b/src/lib/isEmail.js
index 1aceca3cf..abe465052 100644
--- a/src/lib/isEmail.js
+++ b/src/lib/isEmail.js
@@ -1,4 +1,5 @@
import assertString from './util/assertString';
+import checkHost from './util/checkHost';
import isByteLength from './isByteLength';
import isFQDN from './isFQDN';
@@ -60,7 +61,6 @@ function validateDisplayName(display_name) {
return true;
}
-
export default function isEmail(str, options) {
assertString(str);
options = merge(options, default_email_options);
@@ -97,11 +97,11 @@ export default function isEmail(str, options) {
const domain = parts.pop();
const lower_domain = domain.toLowerCase();
- if (options.host_blacklist.includes(lower_domain)) {
+ if (options.host_blacklist.length > 0 && checkHost(lower_domain, options.host_blacklist)) {
return false;
}
- if (options.host_whitelist.length > 0 && !options.host_whitelist.includes(lower_domain)) {
+ if (options.host_whitelist.length > 0 && !checkHost(lower_domain, options.host_whitelist)) {
return false;
}
diff --git a/src/lib/isISO6346.js b/src/lib/isISO6346.js
index 0cb657e7c..2c28c1123 100644
--- a/src/lib/isISO6346.js
+++ b/src/lib/isISO6346.js
@@ -27,7 +27,8 @@ export function isISO6346(str) {
} else sum += str[i] * (2 ** i);
}
- const checkSumDigit = sum % 11;
+ let checkSumDigit = sum % 11;
+ if (checkSumDigit === 10) checkSumDigit = 0;
return Number(str[str.length - 1]) === checkSumDigit;
}
diff --git a/src/lib/isLength.js b/src/lib/isLength.js
index 4ef8b83eb..4d5d52546 100644
--- a/src/lib/isLength.js
+++ b/src/lib/isLength.js
@@ -5,6 +5,7 @@ export default function isLength(str, options) {
assertString(str);
let min;
let max;
+
if (typeof (options) === 'object') {
min = options.min || 0;
max = options.max;
@@ -12,8 +13,15 @@ export default function isLength(str, options) {
min = arguments[1] || 0;
max = arguments[2];
}
+
const presentationSequences = str.match(/(\uFE0F|\uFE0E)/g) || [];
const surrogatePairs = str.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g) || [];
const len = str.length - presentationSequences.length - surrogatePairs.length;
- return len >= min && (typeof max === 'undefined' || len <= max);
+ const isInsideRange = len >= min && (typeof max === 'undefined' || len <= max);
+
+ if (isInsideRange && Array.isArray(options?.discreteLengths)) {
+ return options.discreteLengths.some(discreteLen => discreteLen === len);
+ }
+
+ return isInsideRange;
}
diff --git a/src/lib/isMobilePhone.js b/src/lib/isMobilePhone.js
index 71afda7fb..8e7909418 100644
--- a/src/lib/isMobilePhone.js
+++ b/src/lib/isMobilePhone.js
@@ -43,7 +43,7 @@ const phones = {
'en-BS': /^(\+?1[-\s]?|0)?\(?242\)?[-\s]?\d{3}[-\s]?\d{4}$/,
'en-GB': /^(\+?44|0)7[1-9]\d{8}$/,
'en-GG': /^(\+?44|0)1481\d{6}$/,
- 'en-GH': /^(\+233|0)(20|50|24|54|27|57|26|56|23|28|55|59)\d{7}$/,
+ 'en-GH': /^(\+233|0)(20|50|24|54|27|57|26|56|23|53|28|55|59)\d{7}$/,
'en-GY': /^(\+592|0)6\d{6}$/,
'en-HK': /^(\+?852[-\s]?)?[456789]\d{3}[-\s]?\d{4}$/,
'en-MO': /^(\+?853[-\s]?)?[6]\d{3}[-\s]?\d{4}$/,
@@ -74,7 +74,7 @@ const phones = {
'en-US': /^((\+1|1)?( |-)?)?(\([2-9][0-9]{2}\)|[2-9][0-9]{2})( |-)?([2-9][0-9]{2}( |-)?[0-9]{4})$/,
'en-VU': /^(\+678)?[5]\d{5}$/,
'en-ZA': /^(\+?27|0)\d{9}$/,
- 'en-ZM': /^(\+?26)?09[567]\d{7}$/,
+ 'en-ZM': /^(\+?26)?0[79][567]\d{7}$/,
'en-ZW': /^(\+263)[0-9]{9}$/,
'en-BW': /^(\+?267)?(7[1-8]{1})\d{6}$/,
'es-AR': /^\+?549(11|[2368]\d)\d{8}$/,
@@ -125,7 +125,7 @@ const phones = {
'kk-KZ': /^(\+?7|8)?7\d{9}$/,
'kl-GL': /^(\+?299)?\s?\d{2}\s?\d{2}\s?\d{2}$/,
'ko-KR': /^((\+?82)[ \-]?)?0?1([0|1|6|7|8|9]{1})[ \-]?\d{3,4}[ \-]?\d{4}$/,
- 'ky-KG': /^(\+?7\s?\+?7|0)\s?\d{2}\s?\d{3}\s?\d{4}$/,
+ 'ky-KG': /^(\+996\s?)?(22[0-9]|50[0-9]|55[0-9]|70[0-9]|75[0-9]|77[0-9]|880|990|995|996|997|998)\s?\d{3}\s?\d{3}$/,
'lt-LT': /^(\+370|8)\d{8}$/,
'lv-LV': /^(\+?371)2\d{7}$/,
'mg-MG': /^((\+?261|0)(2|3)\d)?\d{7}$/,
diff --git a/src/lib/isPostalCode.js b/src/lib/isPostalCode.js
index 103656205..cadf39346 100644
--- a/src/lib/isPostalCode.js
+++ b/src/lib/isPostalCode.js
@@ -14,7 +14,7 @@ const patterns = {
BA: /^([7-8]\d{4}$)/,
BE: fourDigit,
BG: fourDigit,
- BR: /^\d{5}-\d{3}$/,
+ BR: /^\d{5}-?\d{3}$/,
BY: /^2[1-4]\d{4}$/,
CA: /^[ABCEGHJKLMNPRSTVXY]\d[ABCEGHJ-NPRSTV-Z][\s\-]?\d[ABCEGHJ-NPRSTV-Z]\d$/i,
CH: fourDigit,
diff --git a/src/lib/isURL.js b/src/lib/isURL.js
index 7529f4bde..9bfafbf0c 100644
--- a/src/lib/isURL.js
+++ b/src/lib/isURL.js
@@ -1,4 +1,5 @@
import assertString from './util/assertString';
+import checkHost from './util/checkHost';
import isFQDN from './isFQDN';
import isIP from './isIP';
@@ -38,20 +39,6 @@ const default_url_options = {
const wrapped_ipv6 = /^\[([^\]]+)\](?::([0-9]+))?$/;
-function isRegExp(obj) {
- return Object.prototype.toString.call(obj) === '[object RegExp]';
-}
-
-function checkHost(host, matches) {
- for (let i = 0; i < matches.length; i++) {
- let match = matches[i];
- if (host === match || (isRegExp(match) && match.test(host))) {
- return true;
- }
- }
- return false;
-}
-
export default function isURL(url, options) {
assertString(url);
if (!url || /[\s<>]/.test(url)) {
diff --git a/src/lib/util/checkHost.js b/src/lib/util/checkHost.js
new file mode 100644
index 000000000..ed1dddefe
--- /dev/null
+++ b/src/lib/util/checkHost.js
@@ -0,0 +1,13 @@
+function isRegExp(obj) {
+ return Object.prototype.toString.call(obj) === '[object RegExp]';
+}
+
+export default function checkHost(host, matches) {
+ for (let i = 0; i < matches.length; i++) {
+ let match = matches[i];
+ if (host === match || (isRegExp(match) && match.test(host))) {
+ return true;
+ }
+ }
+ return false;
+}
diff --git a/test/validators.test.js b/test/validators.test.js
index 3b6ede69a..aa13906b0 100644
--- a/test/validators.test.js
+++ b/test/validators.test.js
@@ -325,6 +325,25 @@ describe('Validators', () => {
});
});
+ it('should allow regular expressions in the host blacklist of isEmail', () => {
+ test({
+ validator: 'isEmail',
+ args: [{
+ host_blacklist: ['bar.com', 'foo.com', /\.foo\.com$/],
+ }],
+ valid: [
+ 'email@foobar.com',
+ 'email@foo.bar.com',
+ 'email@qux.com',
+ ],
+ invalid: [
+ 'email@bar.com',
+ 'email@foo.com',
+ 'email@a.b.c.foo.com',
+ ],
+ });
+ });
+
it('should validate only email addresses with whitelisted domains', () => {
test({
validator: 'isEmail',
@@ -341,6 +360,25 @@ describe('Validators', () => {
});
});
+ it('should allow regular expressions in the host whitelist of isEmail', () => {
+ test({
+ validator: 'isEmail',
+ args: [{
+ host_whitelist: ['bar.com', 'foo.com', /\.foo\.com$/],
+ }],
+ valid: [
+ 'email@bar.com',
+ 'email@foo.com',
+ 'email@a.b.c.foo.com',
+ ],
+ invalid: [
+ 'email@foobar.com',
+ 'email@foo.bar.com',
+ 'email@qux.com',
+ ],
+ });
+ });
+
it('should validate URLs', () => {
test({
validator: 'isURL',
@@ -5394,12 +5432,42 @@ describe('Validators', () => {
valid: ['abc', 'de', 'a', ''],
invalid: ['abcd'],
});
+ test({
+ validator: 'isLength',
+ args: [{ max: 6, discreteLengths: 5 }],
+ valid: ['abcd', 'vfd', 'ff', '', 'k'],
+ invalid: ['abcdefgh', 'hfjdksks'],
+ });
+ test({
+ validator: 'isLength',
+ args: [{ min: 2, max: 6, discreteLengths: 5 }],
+ valid: ['bsa', 'vfvd', 'ff'],
+ invalid: ['', ' ', 'hfskdunvc'],
+ });
+ test({
+ validator: 'isLength',
+ args: [{ min: 1, discreteLengths: 2 }],
+ valid: [' ', 'hello', 'bsa'],
+ invalid: [''],
+ });
test({
validator: 'isLength',
args: [{ max: 0 }],
valid: [''],
invalid: ['a', 'ab'],
});
+ test({
+ validator: 'isLength',
+ args: [{ min: 5, max: 10, discreteLengths: [2, 6, 8, 9] }],
+ valid: ['helloguy', 'shopping', 'validator', 'length'],
+ invalid: ['abcde', 'abcdefg'],
+ });
+ test({
+ validator: 'isLength',
+ args: [{ discreteLengths: '9' }],
+ valid: ['a', 'abcd', 'abcdefghijkl'],
+ invalid: [],
+ });
test({
validator: 'isLength',
valid: ['a', '', 'asds'],
@@ -8023,6 +8091,7 @@ describe('Validators', () => {
'0502345671',
'0242345671',
'0542345671',
+ '0532345671',
'0272345671',
'0572345671',
'0262345671',
@@ -8033,6 +8102,7 @@ describe('Validators', () => {
'+233502345671',
'+233242345671',
'+233542345671',
+ '+233532345671',
'+233272345671',
'+233572345671',
'+233262345671',
@@ -8747,6 +8817,8 @@ describe('Validators', () => {
'+260966684590',
'+260976684590',
'260976684590',
+ '+260779493521',
+ '+260760010936',
],
invalid: [
'12345',
@@ -8754,6 +8826,7 @@ describe('Validators', () => {
'Vml2YW11cyBmZXJtZtesting123',
'010-38238383',
'966684590',
+ '760010936',
],
},
{
@@ -9536,15 +9609,44 @@ describe('Validators', () => {
{
locale: 'ky-KG',
valid: [
- '+7 727 123 4567',
- '+7 714 2396102',
- '77271234567',
- '0271234567',
- ],
- invalid: [
- '02188565377',
- '09386932778',
- '0938693277vadggjdsaasdgj8',
+ '+996553033300',
+ '+996 222 123456',
+ '+996 500 987654',
+ '+996 555 111222',
+ '+996 700 333444',
+ '+996 770 555666',
+ '+996 880 777888',
+ '+996 990 999000',
+ '+996 995 555666',
+ '+996 996 555666',
+ '+996 997 555666',
+ '+996 998 555666',
+ ],
+ invalid: [
+ '+996 201 123456',
+ '+996 312 123456',
+ '+996 3960 12345',
+ '+996 3961 12345',
+ '+996 3962 12345',
+ '+996 3963 12345',
+ '+996 3964 12345',
+ '+996 3965 12345',
+ '+996 3966 12345',
+ '+996 3967 12345',
+ '+996 3968 12345',
+ '+996 511 123456',
+ '+996 522 123456',
+ '+996 561 123456',
+ '+996 571 123456',
+ '+996 624 123456',
+ '+996 623 123456',
+ '+996 622 123456',
+ '+996 609 123456',
+ '+996 100 12345',
+ '+996 100 1234567',
+ '996 100 123456',
+ '0 100 123456',
+ '0 100 123abc',
],
},
{
@@ -12678,6 +12780,9 @@ describe('Validators', () => {
'39100-000',
'22040-020',
'39400-152',
+ '39100000',
+ '22040020',
+ '39400152',
],
invalid: [
'79800A12',
@@ -13004,6 +13109,55 @@ describe('Validators', () => {
});
});
+ it('should validate ISO6346 shipping container IDs with checksum digit 10 represented as 0', () => {
+ test({
+ validator: 'isISO6346',
+ valid: [
+ 'APZU3789870',
+ 'TEMU1002030',
+ 'DFSU1704420',
+ 'CMAU2221480',
+ 'SEGU5060260',
+ 'FCIU8939320',
+ 'TRHU3495670',
+ 'MEDU3871410',
+ 'CMAU2184010',
+ 'TCLU2265970',
+ ],
+ invalid: [
+ 'APZU3789871', // Incorrect check digit
+ 'TEMU1002031',
+ 'DFSU1704421',
+ 'CMAU2221481',
+ 'SEGU5060261',
+ ],
+ });
+ });
+ it('should validate ISO6346 shipping container IDs with checksum digit 10 represented as 0', () => {
+ test({
+ validator: 'isFreightContainerID',
+ valid: [
+ 'APZU3789870',
+ 'TEMU1002030',
+ 'DFSU1704420',
+ 'CMAU2221480',
+ 'SEGU5060260',
+ 'FCIU8939320',
+ 'TRHU3495670',
+ 'MEDU3871410',
+ 'CMAU2184010',
+ 'TCLU2265970',
+ ],
+ invalid: [
+ 'APZU3789871', // Incorrect check digit
+ 'TEMU1002031',
+ 'DFSU1704421',
+ 'CMAU2221481',
+ 'SEGU5060261',
+ ],
+ });
+ });
+
// EU-UK valid numbers sourced from https://ec.europa.eu/taxation_customs/tin/specs/FS-TIN%20Algorithms-Public.docx or constructed by @tplessas.
it('should validate taxID', () => {
test({
@@ -13939,6 +14093,7 @@ describe('Validators', () => {
new Date([2014, 2, 15]),
new Date('2014-03-15'),
'29.02.2020',
+ '02.29.2020.20',
'2024-',
'2024-05',
'2024-05-',