Repository: validatorjs/validator.js
Pull request #1920 (2022-01-31 21:57:53)

Add whitelist host to isEmail validation.
Add host whitelist to is Email validation.
closes: #1912 

Add a new option (whitelist host) to isEmail options to check if the email domain is in it or not, if it was not in the whitelist host it fails.

## Checklist

- [x] PR contains only changes related; no stray files, etc.
- [x] README updated (where applicable)
- [x] Tests written (where applicable)



Patch:
diff --git a/.gitignore b/.gitignore
index 9c8fa47..86aaede 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,4 @@ yarn.lock
 /index.js
 validator.js
 validator.min.js
+
diff --git a/README.md b/README.md
index 9674841..453582f 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
 # validator.js
-
+# Changes made - 1707074
 [![NPM version][npm-image]][npm-url]
 [![CI][ci-image]][ci-url]
 [![Coverage][codecov-image]][codecov-url]
@@ -92,8 +92,8 @@ Validator                               | Description
 **contains(str, seed [, options ])**    | check if the string contains the seed.<br/><br/>`options` is an object that defaults to `{ ignoreCase: false, minOccurrences: 1 }`.<br />Options: <br/> `ignoreCase`: Ignore case when doing comparison, default false<br/>`minOccurences`: Minimum number of occurrences for the seed in the string. Defaults to 1.
 **equals(str, comparison)**             | check if the string matches the comparison.
 **isAfter(str [, date])**               | check if the string is a date that's after the specified date (defaults to now).
-**isAlpha(str [, locale, options])**    | check if the string contains only letters (a-zA-Z).<br/><br/>Locale 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', 'es-ES', 'fa-IR', 'fi-FI', 'fr-CA', 'fr-FR', 'he', 'hi-IN', 'hu-HU', 'it-IT', 'ku-IQ', 'nb-NO', 'nl-NL', 'nn-NO', 'pl-PL', 'pt-BR', 'pt-PT', 'ru-RU', 'sl-SI', 'sk-SK', 'sr-RS', 'sr-RS@latin', 'sv-SE', 'tr-TR', 'uk-UA']`) and defaults to `en-US`. Locale list is `validator.isAlphaLocales`. options is an optional object that can be supplied with the following key(s): ignore which can either be a String or RegExp of characters to be ignored e.g. " -" will ignore spaces and -'s.
-**isAlphanumeric(str [, locale, options])**      | check if the string contains only letters and numbers (a-zA-Z0-9).<br/><br/>Locale 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', 'es-ES', 'fa-IR', 'fi-FI', 'fr-CA', 'fr-FR', 'he', 'hi-IN', 'hu-HU', 'it-IT', 'ku-IQ', 'nb-NO', 'nl-NL', 'nn-NO', 'pl-PL', 'pt-BR', 'pt-PT', 'ru-RU', 'sl-SI', 'sk-SK', 'sr-RS', 'sr-RS@latin', 'sv-SE', 'tr-TR', 'uk-UA']`) and defaults to `en-US`. Locale list is `validator.isAlphanumericLocales`. options is an optional object that can be supplied with the following key(s): ignore which can either be a String or RegExp of characters to be ignored e.g. " -" will ignore spaces and -'s.
+**isAlpha(str [, locale, options])**    | check if the string contains only letters (a-zA-Z).<br/><br/>Locale 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', 'bn', 'cs-CZ', 'da-DK', 'de-DE', 'el-GR', 'en-AU', 'en-GB', 'en-HK', 'en-IN', 'en-NZ', 'en-US', 'en-ZA', 'en-ZM', 'es-ES', 'fa-IR', 'fi-FI', 'fr-CA', 'fr-FR', 'he', 'hi-IN', 'hu-HU', 'it-IT', 'ku-IQ', 'nb-NO', 'nl-NL', 'nn-NO', 'pl-PL', 'pt-BR', 'pt-PT', 'ru-RU', 'si-LK', 'sl-SI', 'sk-SK', 'sr-RS', 'sr-RS@latin', 'sv-SE', 'tr-TR', 'uk-UA']`) and defaults to `en-US`. Locale list is `validator.isAlphaLocales`. options is an optional object that can be supplied with the following key(s): ignore which can either be a String or RegExp of characters to be ignored e.g. " -" will ignore spaces and -'s.
+**isAlphanumeric(str [, locale, options])**      | check if the string contains only letters and numbers (a-zA-Z0-9).<br/><br/>Locale 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', 'bn', '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', 'es-ES', 'fa-IR', 'fi-FI', 'fr-CA', 'fr-FR', 'he', 'hi-IN', 'hu-HU', 'it-IT', 'ku-IQ', 'nb-NO', 'nl-NL', 'nn-NO', 'pl-PL', 'pt-BR', 'pt-PT', 'ru-RU', 'si-LK', 'sl-SI', 'sk-SK', 'sr-RS', 'sr-RS@latin', 'sv-SE', 'tr-TR', 'uk-UA']`) and defaults to `en-US`. Locale list is `validator.isAlphanumericLocales`. options is an optional object that can be supplied with the following key(s): ignore which can either be a String or RegExp of characters to be ignored e.g. " -" will ignore spaces and -'s.
 **isAscii(str)**                        | check if the string contains ASCII chars only.
 **isBase32(str [, options])**           | check if a string is base32 encoded. `options` is optional and defaults to `{crockford: false}`.<br/> When `crockford` is true it tests the given base32 encoded string using [Crockford's base32 alternative](http://www.crockford.com/base32.html).
 **isBase58(str)**                       | check if a string is base58 encoded.
@@ -110,7 +110,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.<br/><br/>`options` is an object which defaults to `{force_decimal: false, decimal_digits: '1,', locale: 'en-US'}`<br/><br/>`locale` determine 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', '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']`.<br/>**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's divisible by another.
 **isEAN(str)**                          | check if the string is an EAN (European Article Number).
-**isEmail(str [, options])**            | check if the string is an email.<br/><br/>`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, domain_specific_validation: false, blacklisted_chars: '', host_blacklist: [] }`. If `allow_display_name` is set to true, the validator will also match `Display Name <email-address>`. If `require_display_name` is set to true, the validator will reject strings without the format `Display Name <email-address>`. 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, e-mail addresses without having 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.
+**isEmail(str [, options])**            | check if the string is an email.<br/><br/>`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, domain_specific_validation: false, blacklisted_chars: '', host_blacklist: [] }`. If `allow_display_name` is set to true, the validator will also match `Display Name <email-address>`. If `require_display_name` is set to true, the validator will reject strings without the format `Display Name <email-address>`. 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, e-mail addresses without having 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. 
 **isEmpty(str [, options])**            | check if the string has a length of zero.<br/><br/>`options` is an object which defaults to `{ ignore_whitespace:false }`.
 **isEthereumAddress(str)**              | check if the string is an [Ethereum](https://ethereum.org/) address using basic regex. Does not validate address checksums.
 **isFloat(str [, options])**            | check if the string is a float.<br/><br/>`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.<br/><br/>`min` and `max` are equivalent to 'greater or equal' and 'less or equal', respectively while `gt` and `lt` are their strict counterparts.<br/><br/>`locale` determine 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', '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`.
@@ -141,21 +141,22 @@ Validator                               | Description
 **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`.<br/><br/>`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.<br/><br/>`options` is an object which defaults to `{min:0, max: undefined}`. Note: this function takes into account surrogate pairs.
-**isLicensePlate(str [, locale])**     | check if string matches the format of a country's license plate.<br/><br/>(locale is one of `['cs-CZ', 'de-DE', 'de-LI', 'fi-FI', 'pt-BR', 'pt-PT', 'sq-AL', 'sv-SE']` or `any`)
+**isLicensePlate(str [, locale])**     | check if string matches the format of a country's license plate.<br/><br/>(locale is one of `['cs-CZ', 'de-DE', 'de-LI', 'fi-FI', 'pt-BR', 'pt-PT', 'sq-AL', 'sv-SE', 'en-IN', 'hi-IN', 'gu-IN', 'as-IN', 'bn-IN', 'kn-IN', 'ml-IN', 'mr-IN', 'or-IN', 'pa-IN', 'sa-IN', 'ta-IN', 'te-IN', 'kok-IN']` or `any`)
 **isLocale(str)**                       | check if the string is a locale
 **isLowercase(str)**                    | check if the string is lowercase.
+**isLuhnValid(str)**                    | check if the string passes the luhn check.
 **isMACAddress(str [, options])**                   | check if the string is a MAC address.<br/><br/>`options` is an object which defaults to `{no_separators: false}`. If `no_separators` is true, the validator will allow MAC addresses without separators. Also, it allows the use of hyphens, spaces or dots e.g  '01 02 03 04 05 ab', '01-02-03-04-05-ab' or '0102.0304.05ab'. The options also allow a `eui` property to specify if it needs to be validated against EUI-48 or EUI-64. The accepted values of `eui` are: 48, 64.
 **isMagnetURI(str)**                      | check if the string is a [magnet uri format](https://en.wikipedia.org/wiki/Magnet_URI_scheme).
 **isMD5(str)**                          | check if the string is a MD5 hash.<br/><br/>Please note that you can also use the `isHash(str, 'md5')` function. Keep in mind that MD5 has some collision weaknesses compared to other algorithms (e.g., SHA).
 **isMimeType(str)**                     | check if the string matches to a valid [MIME type](https://en.wikipedia.org/wiki/Media_type) format
-**isMobilePhone(str [, locale [, options]])**          | check if the string is a mobile phone number,<br/><br/>(locale is either an array of locales (e.g `['sk-SK', 'sr-RS']`) OR one of `['am-Am', 'ar-AE', 'ar-BH', 'ar-DZ', 'ar-EG', 'ar-IQ', ar-JO', 'ar-KW', 'ar-PS', 'ar-SA', 'ar-SY', 'ar-TN', 'az-AZ', 'az-LY', 'az-LB', 'bs-BA', 'be-BY', 'bg-BG', 'bn-BD', 'ca-AD', 'cs-CZ', 'da-DK', 'de-DE', 'de-AT', 'de-CH', 'de-LU', 'dv-MV', 'el-GR', 'en-AU', 'en-BM', 'en-BW', 'en-CA', 'en-GB', 'en-GG', 'en-GH', 'en-GY', 'en-HK', 'en-MO', 'en-IE', 'en-IN', 'en-LS', 'en-KE', 'en-KI', 'en-MT', 'en-MU', 'en-NG', 'en-NZ', 'en-PK', 'en-PH', 'en-RW', 'en-SG', 'en-SL', 'en-UG', 'en-US', 'en-TZ', 'en-ZA', 'en-ZM', 'en-ZW', 'es-AR', 'es-BO', 'es-CL', 'es-CO', 'es-CR', 'es-CU', 'es-DO', 'es-HN', 'es-PE', 'es-EC', 'es-ES', 'es-MX', 'es-PA', 'es-PY', 'es-SV', 'es-UY', 'es-VE', 'et-EE', 'fa-IR', 'fi-FI', 'fj-FJ', 'fo-FO', 'fr-BE', 'fr-BF', 'fr-FR', 'fr-GF', 'fr-GP', 'fr-MQ', 'fr-PF', 'fr-RE', 'ga-IE', 'he-IL', 'hu-HU', 'id-ID', 'it-IT', 'it-SM', 'ja-JP', 'ka-GE', 'kk-KZ', 'kl-GL', 'ko-KR', 'lt-LT', 'ms-MY', 'my-MM', 'mz-MZ', nb-NO', 'ne-NP', 'nl-BE', 'nl-NL', 'nn-NO', 'pl-PL', 'pt-BR', 'pt-PT', 'pt-AO', 'ro-RO', 'ru-RU', 'si-LK' 'sl-SI', 'sk-SK', 'sq-AL', 'sr-RS', 'sv-SE', 'tg-TJ', 'th-TH', 'tk-TM', 'tr-TR', 'uk-UA', 'uz-UZ', 'vi-VN', 'zh-CN', 'zh-HK', 'zh-MO', 'zh-TW', 'dz-BT']` OR defaults to 'any'. If 'any' or a falsey value is used, function will check if any of the locales match).<br/><br/>`options` is an optional object that can be supplied with the following keys: `strictMode`, if this is set to `true`, the mobile phone number must be supplied with the country code and therefore must start with `+`. Locale list is `validator.isMobilePhoneLocales`.
+**isMobilePhone(str [, locale [, options]])**          | check if the string is a mobile phone number,<br/><br/>(locale is either an array of locales (e.g `['sk-SK', 'sr-RS']`) OR one of `['am-Am', 'ar-AE', 'ar-BH', 'ar-DZ', 'ar-EG', 'ar-EH' , 'ar-IQ', ar-JO', 'ar-KW', 'ar-PS', 'ar-SA', 'ar-SY', 'ar-TN', 'ar-YE' , 'az-AZ', 'az-LY', 'az-LB', 'bs-BA', 'be-BY', 'bg-BG', 'bn-BD', 'ca-AD', 'cs-CZ', 'da-DK', 'de-DE', 'de-AT', 'de-CH', 'de-LU', 'dv-MV', 'el-GR', 'el-CY' ,'en-AU', 'en-AG','en-AI', 'en-BM', 'en-BW', 'en-BS', 'en-CA', 'en-GB', 'en-GG', 'en-GH', 'en-GY', 'en-HK', 'en-JM', 'en-MO', 'en-IE', 'en-IN', 'en-LS', 'en-KE', 'en-KI', 'en-MT', 'en-MU', 'en-NG', 'es-NI' , 'en-NZ', 'en-PG', 'en-PK', 'en-PH', 'en-RW', 'en-SG', 'en-SL', 'en-UG', 'en-US', 'en-TZ', 'en-ZA', 'en-ZM', 'en-ZW', 'es-AR', 'es-BO', 'es-CL', 'es-CO', 'es-CR', 'es-CU', 'es-DO', 'es-HN', 'es-PE', 'es-EC', 'es-ES', 'es-MX', 'es-PA', 'es-PY', 'es-SV', 'es-UY', 'es-VE', 'et-EE', 'fa-IR', 'fa-AF', 'fi-FI', 'fj-FJ', 'fo-FO', 'fr-BE', 'fr-BF','fr-BJ', 'fr-FR', 'fr-GF', 'fr-GP', 'fr-MQ', 'fr-PF', 'fr-RE', 'ga-IE', 'he-IL', 'hu-HU', 'id-ID', 'ir-IR' , 'it-IT', 'it-SM', 'ja-JP', 'ka-GE', 'kk-KZ', 'kl-GL', 'ko-KR', 'ky-KG', 'lt-LT', 'mg-MG', 'ms-MY', 'mn-MN', 'my-MM' , 'mz-MZ', nb-NO', 'ne-NP', 'nl-BE', 'nl-NL','nl-AW' 'nn-NO', 'pl-PL', 'pt-BR', 'pt-PT', 'pt-AO', 'ro-RO', 'ru-RU', 'si-LK' 'sl-SI', 'sk-SK', 'sq-AL', 'sr-RS', 'sv-SE', 'tg-TJ', 'th-TH', 'tk-TM', 'tr-TR', 'uk-UA', 'uz-UZ', 'vi-VN', 'zh-CN', 'zh-HK', 'zh-MO', 'zh-TW', 'dz-BT']` OR defaults to 'any'. If 'any' or a falsey value is used, function will check if any of the locales match).<br/><br/>`options` is an optional object that can be supplied with the following keys: `strictMode`, if this is set to `true`, the mobile phone number must be supplied with the country code and therefore must start with `+`. Locale list is `validator.isMobilePhoneLocales`.
 **isMongoId(str)**                      | check if the string is a valid hex-encoded representation of a [MongoDB ObjectId][mongoid].
 **isMultibyte(str)**                    | check if the string contains one or more multibyte chars.
 **isNumeric(str [, options])**                      | check if the string contains only numbers.<br/><br/>`options` is an object which defaults to `{no_symbols: false}` it also has locale as an option. If `no_symbols` is true, the validator will reject numeric strings that feature a symbol (e.g. `+`, `-`, or `.`).<br/><br/>`locale` determine 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', 'es-ES', 'fr-FR', 'fr-CA', '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']`.
 **isOctal(str)**                        | check if the string is a valid octal number.
-**isPassportNumber(str, countryCode)**    | check if the string is a valid passport number.<br/><br/>(countryCode is one of `[ 'AM', 'AR', 'AT', 'AU', 'BE', 'BG', 'BY', 'BR', 'CA', 'CH', 'CN', 'CY', 'CZ', 'DE', 'DK', 'DZ', 'EE', 'ES', 'FI', 'FR', 'GB', 'GR', 'HR', 'HU', 'IE' 'IN', 'IR', 'ID', 'IS', 'IT', 'JP', 'KR', 'LT', 'LU', 'LV', 'LY', 'MT', 'MY', 'MZ', 'NL', 'PL', 'PT', 'RO', 'RU', 'SE', 'SL', 'SK', 'TR', 'UA', 'US' ]`.
+**isPassportNumber(str, countryCode)**    | check if the string is a valid passport number.<br/><br/>(countryCode is one of `[ 'AM', 'AR', 'AT', 'AU', 'BE', 'BG', 'BY', 'BR', 'CA', 'CH', 'CN', 'CY', 'CZ', 'DE', 'DK', 'DZ', 'EE', 'ES', 'FI', 'FR', 'GB', 'GR', 'HR', 'HU', 'IE' 'IN', 'IR', 'ID', 'IS', 'IT', 'JP', 'KR', 'LT', 'LU', 'LV', 'LY', 'MT', 'MX', 'MY', 'MZ', 'NL', 'PL', 'PT', 'RO', 'RU', 'SE', 'SL', 'SK', 'TH', 'TR', 'UA', 'US' ]`.
 **isPort(str)**                         | check if the string is a valid port number.
-**isPostalCode(str, locale)**           | check if the string is a postal code,<br/><br/>(locale is one of `[ 'AD', 'AT', 'AU', 'AZ', 'BE', 'BG', 'BR', 'BY', 'CA', 'CH', 'CN', 'CZ', 'DE', 'DK', 'DO', 'DZ', 'EE', 'ES', 'FI', 'FR', 'GB', 'GR', 'HR', 'HT', 'HU', 'ID', 'IE' 'IL', 'IN', 'IR', 'IS', 'IT', 'JP', 'KE', 'KR', 'LI', 'LK', 'LT', 'LU', 'LV', 'MT', 'MX', 'MY', 'NL', 'NO', 'NP', 'NZ', 'PL', 'PR', 'PT', 'RO', 'RU', 'SA', 'SE', 'SG', 'SI', 'TH', 'TN', 'TW', 'UA', 'US', 'ZA', 'ZM' ]` OR 'any'. If 'any' is used, function will check if any of the locals match. Locale list is `validator.isPostalCodeLocales`.).
+**isPostalCode(str, locale)**           | check if the string is a postal code,<br/><br/>(locale is one of `[ 'AD', 'AT', 'AU', 'AZ', 'BA', 'BE', 'BG', 'BR', 'BY', 'CA', 'CH', 'CN', 'CZ', 'DE', 'DK', 'DO', 'DZ', 'EE', 'ES', 'FI', 'FR', 'GB', 'GR', 'HR', 'HT', 'HU', 'ID', 'IE' 'IL', 'IN', 'IR', 'IS', 'IT', 'JP', 'KE', 'KR', 'LI', 'LK', 'LT', 'LU', 'LV', 'MG', 'MT', 'MX', 'MY', 'NL', 'NO', 'NP', 'NZ', 'PL', 'PR', 'PT', 'RO', 'RU', 'SA', 'SE', 'SG', 'SI', 'SK', 'TH', 'TN', 'TW', 'UA', 'US', 'ZA', 'ZM' ]` OR 'any'. If 'any' is used, function will check if any of the locals match. Locale list is `validator.isPostalCodeLocales`.).
 **isRFC3339(str)**                      | check if the string is a valid [RFC 3339](https://tools.ietf.org/html/rfc3339) date.
 **isRgbColor(str [, includePercentValues])**                     | check if the string is a rgb or rgba color.<br/><br/>`includePercentValues` defaults to `true`. If you don't want to allow to set `rgb` or `rgba` values with percents, like `rgb(5%,5%,5%)`, or `rgba(90%,90%,90%,.3)`, then set it to false.
 **isSemVer(str)**                       | check if the string is a Semantic Versioning Specification (SemVer).
@@ -167,7 +168,7 @@ Validator                               | Description
 **isURL(str [, options])**              | check if the string is an URL.<br/><br/>`options` is an object which defaults to `{ protocols: ['http','https','ftp'], require_tld: true, require_protocol: false, require_host: true, require_port: false, require_valid_protocol: true, allow_underscores: false, host_whitelist: false, host_blacklist: false, allow_trailing_dot: false, allow_protocol_relative_urls: false, allow_fragments: true, allow_query_components: true, disallow_auth: false, validate_length: true }`.<br/><br/>require_protocol - if set as true isURL will return false if protocol is not present in the URL.<br/>require_valid_protocol - isURL will check if the URL's protocol is present in the protocols option.<br/>protocols - valid protocols can be modified with this option.<br/>require_host - if set as false isURL will not check if host is present in the URL.<br/>require_port - if set as true isURL will check if port is present in the URL.<br/>allow_protocol_relative_urls - if set as true protocol relative URLs will be allowed.<br/>allow_fragments - if set as false isURL will return false if fragments are present.<br/>allow_query_components - if set as false isURL will return false if query components are present.<br/>validate_length - if set as false isURL will skip string length validation (2083 characters is IE max URL length).
 **isUUID(str [, version])**             | check if the string is a UUID (version 1, 2, 3, 4 or 5).
 **isVariableWidth(str)**                | check if the string contains a mixture of full and half-width chars.
-**isVAT(str, countryCode)**                | checks that the string is a [valid VAT number](https://en.wikipedia.org/wiki/VAT_identification_number) if validation is available for the given country code matching [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2). <br/><br/>Available country codes: `[ 'GB', 'IT','NL' ]`.
+**isVAT(str, countryCode)**             | checks that the string is a [valid VAT number](https://en.wikipedia.org/wiki/VAT_identification_number) if validation is available for the given country code matching [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2). <br/><br/>Available country codes: `[ 'AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'EL', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE', 'AL', 'MK', 'AU', 'BY', 'CA', 'IS', 'IN', 'ID', 'IL', 'KZ', 'NZ', 'NG', 'NO', 'PH', 'RU', 'SM', 'SA', 'RS', 'CH', 'TR', 'UA', 'GB', 'UZ', 'AR', 'BO', 'BR', 'CL', 'CO', 'CR', 'EC', 'SV', 'GT', 'HN', 'MX', 'NI', 'PA', 'PY', 'PE', 'DO', 'UY', 'VE' ]`.
 **isWhitelisted(str, chars)**           | checks characters if they appear in the whitelist.
 **matches(str, pattern [, modifiers])** | check if string matches the pattern.<br/><br/>Either `matches('foo', /foo/i)` or `matches('foo', 'foo', 'i')`.
 
diff --git a/src/index.js b/src/index.js
index ec6fe52..42e1e8b 100644
--- a/src/index.js
+++ b/src/index.js
@@ -69,6 +69,7 @@ import isBefore from './lib/isBefore';
 
 import isIn from './lib/isIn';
 
+import isLuhnValid from './lib/isLuhnValid';
 import isCreditCard from './lib/isCreditCard';
 import isIdentityCard from './lib/isIdentityCard';
 
@@ -183,6 +184,7 @@ const validator = {
   isAfter,
   isBefore,
   isIn,
+  isLuhnValid,
   isCreditCard,
   isIdentityCard,
   isEAN,
diff --git a/src/lib/alpha.js b/src/lib/alpha.js
index d663ede..c941c9b 100644
--- a/src/lib/alpha.js
+++ b/src/lib/alpha.js
@@ -31,7 +31,9 @@ export const alpha = {
   ar: /^[ءآأؤإئابةتثجحخدذرزسشصضطظعغفقكلمنهوىيًٌٍَُِّْٰ]+$/,
   he: /^[א-ת]+$/,
   fa: /^['آاءأؤئبپتثجچحخدذرزژسشصضطظعغفقکگلمنوهةی']+$/i,
+  bn: /^['ঀঁংঃঅআইঈউঊঋঌএঐওঔকখগঘঙচছজঝঞটঠডঢণতথদধনপফবভমযরলশষসহ়ঽািীুূৃৄেৈোৌ্ৎৗড়ঢ়য়ৠৡৢৣৰৱ৲৳৴৵৶৷৸৹৺৻']+$/,
   'hi-IN': /^[\u0900-\u0961]+[\u0972-\u097F]*$/i,
+  'si-LK': /^[\u0D80-\u0DFF]+$/,
 };
 
 export const alphanumeric = {
@@ -66,7 +68,9 @@ export const alphanumeric = {
   ar: /^[٠١٢٣٤٥٦٧٨٩0-9ءآأؤإئابةتثجحخدذرزسشصضطظعغفقكلمنهوىيًٌٍَُِّْٰ]+$/,
   he: /^[0-9א-ת]+$/,
   fa: /^['0-9آاءأؤئبپتثجچحخدذرزژسشصضطظعغفقکگلمنوهةی۱۲۳۴۵۶۷۸۹۰']+$/i,
+  bn: /^['ঀঁংঃঅআইঈউঊঋঌএঐওঔকখগঘঙচছজঝঞটঠডঢণতথদধনপফবভমযরলশষসহ়ঽািীুূৃৄেৈোৌ্ৎৗড়ঢ়য়ৠৡৢৣ০১২৩৪৫৬৭৮৯ৰৱ৲৳৴৵৶৷৸৹৺৻']+$/,
   'hi-IN': /^[\u0900-\u0963]+[\u0966-\u097F]*$/i,
+  'si-LK': /^[0-9\u0D80-\u0DFF]+$/,
 };
 
 export const decimal = {
@@ -85,10 +89,8 @@ for (let locale, i = 0; i < englishLocales.length; i++) {
 }
 
 // Source: http://www.localeplanet.com/java/
-export const arabicLocales = [
-  'AE', 'BH', 'DZ', 'EG', 'IQ', 'JO', 'KW', 'LB', 'LY',
-  'MA', 'QM', 'QA', 'SA', 'SD', 'SY', 'TN', 'YE',
-];
+export const arabicLocales = ['AE', 'BH', 'DZ', 'EG', 'IQ', 'JO', 'KW', 'LB', 'LY',
+  'MA', 'QM', 'QA', 'SA', 'SD', 'SY', 'TN', 'YE'];
 
 for (let locale, i = 0; i < arabicLocales.length; i++) {
   locale = `ar-${arabicLocales[i]}`;
@@ -97,9 +99,7 @@ for (let locale, i = 0; i < arabicLocales.length; i++) {
   decimal[locale] = decimal.ar;
 }
 
-export const farsiLocales = [
-  'IR', 'AF',
-];
+export const farsiLocales = ['IR', 'AF'];
 
 for (let locale, i = 0; i < farsiLocales.length; i++) {
   locale = `fa-${farsiLocales[i]}`;
@@ -107,12 +107,21 @@ for (let locale, i = 0; i < farsiLocales.length; i++) {
   decimal[locale] = decimal.ar;
 }
 
+export const bengaliLocales = ['BD', 'IN'];
+
+for (let locale, i = 0; i < bengaliLocales.length; i++) {
+  locale = `bn-${bengaliLocales[i]}`;
+  alpha[locale] = alpha.bn;
+  alphanumeric[locale] = alphanumeric.bn;
+  decimal[locale] = decimal['en-US'];
+}
+
 // Source: https://en.wikipedia.org/wiki/Decimal_mark
 export const dotDecimal = ['ar-EG', 'ar-LB', 'ar-LY'];
 export const commaDecimal = [
   'bg-BG', 'cs-CZ', 'da-DK', 'de-DE', 'el-GR', 'en-ZM', 'es-ES', 'fr-CA', 'fr-FR',
   'id-ID', 'it-IT', 'ku-IQ', 'hi-IN', 'hu-HU', 'nb-NO', 'nn-NO', 'nl-NL', 'pl-PL', 'pt-PT',
-  'ru-RU', 'sl-SI', 'sr-RS@latin', 'sr-RS', 'sv-SE', 'tr-TR', 'uk-UA', 'vi-VN',
+  'ru-RU', 'si-LK', 'sl-SI', 'sr-RS@latin', 'sr-RS', 'sv-SE', 'tr-TR', 'uk-UA', 'vi-VN',
 ];
 
 for (let i = 0; i < dotDecimal.length; i++) {
diff --git a/src/lib/isCreditCard.js b/src/lib/isCreditCard.js
index b11c4cc..53c80eb 100644
--- a/src/lib/isCreditCard.js
+++ b/src/lib/isCreditCard.js
@@ -1,4 +1,5 @@
 import assertString from './util/assertString';
+import isLuhnValid from './isLuhnValid';
 
 /* eslint-disable max-len */
 const creditCard = /^(?: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}|^(81[0-9]{14,17}))$/;
@@ -10,24 +11,5 @@ export default function isCreditCard(str) {
   if (!creditCard.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);
+  return isLuhnValid(str);
 }
diff --git a/src/lib/isDataURI.js b/src/lib/isDataURI.js
index 966a6f3..0275d2c 100644
--- a/src/lib/isDataURI.js
+++ b/src/lib/isDataURI.js
@@ -14,10 +14,10 @@ export default function isDataURI(str) {
   }
   const attributes = data.shift().trim().split(';');
   const schemeAndMediaType = attributes.shift();
-  if (schemeAndMediaType.substr(0, 5) !== 'data:') {
+  if (schemeAndMediaType.slice(0, 5) !== 'data:') {
     return false;
   }
-  const mediaType = schemeAndMediaType.substr(5);
+  const mediaType = schemeAndMediaType.slice(5);
   if (mediaType !== '' && !validMediaType.test(mediaType)) {
     return false;
   }
diff --git a/src/lib/isEmail.js b/src/lib/isEmail.js
index f24f2da..6db0019 100644
--- a/src/lib/isEmail.js
+++ b/src/lib/isEmail.js
@@ -13,6 +13,7 @@ const default_email_options = {
   blacklisted_chars: '',
   ignore_max_length: false,
   host_blacklist: [],
+  host_whitelist: [],
 };
 
 /* eslint-disable max-len */
@@ -77,7 +78,7 @@ export default function isEmail(str, options) {
       // eg. myname <address@gmail.com>
       // the display name is `myname` instead of `myname `, so need to trim the last space
       if (display_name.endsWith(' ')) {
-        display_name = display_name.substr(0, display_name.length - 1);
+        display_name = display_name.slice(0, -1);
       }
 
       if (!validateDisplayName(display_name)) {
@@ -99,6 +100,10 @@ export default function isEmail(str, options) {
     return false;
   }
 
+  if (options.host_whitelist.length > 0 && !options.host_whitelist.includes(lower_domain)) {
+    return false;
+  }
+
   let user = parts.join('@');
 
   if (options.domain_specific_validation && (lower_domain === 'gmail.com' || lower_domain === 'googlemail.com')) {
@@ -144,7 +149,7 @@ export default function isEmail(str, options) {
         return false;
       }
 
-      let noBracketdomain = domain.substr(1, domain.length - 2);
+      let noBracketdomain = domain.slice(1, -1);
 
       if (noBracketdomain.length === 0 || !isIP(noBracketdomain)) {
         return false;
diff --git a/src/lib/isFQDN.js b/src/lib/isFQDN.js
index 4a04cf3..884d1dd 100644
--- a/src/lib/isFQDN.js
+++ b/src/lib/isFQDN.js
@@ -32,7 +32,7 @@ export default function isFQDN(str, options) {
       return false;
     }
 
-    if (!/^([a-z\u00A1-\u00A8\u00AA-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]{2,}|xn[a-z0-9-]{2,})$/i.test(tld)) {
+    if (!options.allow_numeric_tld && !/^([a-z\u00A1-\u00A8\u00AA-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]{2,}|xn[a-z0-9-]{2,})$/i.test(tld)) {
       return false;
     }
 
diff --git a/src/lib/isIP.js b/src/lib/isIP.js
index 25856ca..40ca19a 100644
--- a/src/lib/isIP.js
+++ b/src/lib/isIP.js
@@ -51,14 +51,10 @@ export default function isIP(str, version = '') {
     return isIP(str, 4) || isIP(str, 6);
   }
   if (version === '4') {
-    if (!IPv4AddressRegExp.test(str)) {
-      return false;
-    }
-    const parts = str.split('.').sort((a, b) => a - b);
-    return parts[3] <= 255;
+    return IPv4AddressRegExp.test(str);
   }
   if (version === '6') {
-    return !!IPv6AddressRegExp.test(str);
+    return IPv6AddressRegExp.test(str);
   }
   return false;
 }
diff --git a/src/lib/isIdentityCard.js b/src/lib/isIdentityCard.js
index 9dc1302..d34ddae 100644
--- a/src/lib/isIdentityCard.js
+++ b/src/lib/isIdentityCard.js
@@ -130,15 +130,15 @@ const validators = {
   },
   IR: (str) => {
     if (!str.match(/^\d{10}$/)) return false;
-    str = (`0000${str}`).substr(str.length - 6);
+    str = (`0000${str}`).slice(str.length - 6);
 
-    if (parseInt(str.substr(3, 6), 10) === 0) return false;
+    if (parseInt(str.slice(3, 9), 10) === 0) return false;
 
-    const lastNumber = parseInt(str.substr(9, 1), 10);
+    const lastNumber = parseInt(str.slice(9, 10), 10);
     let sum = 0;
 
     for (let i = 0; i < 9; i++) {
-      sum += parseInt(str.substr(i, 1), 10) * (10 - i);
+      sum += parseInt(str.slice(i, i + 1), 10) * (10 - i);
     }
 
     sum %= 11;
diff --git a/src/lib/isLength.js b/src/lib/isLength.js
index 2e7c75c..4ef8b83 100644
--- a/src/lib/isLength.js
+++ b/src/lib/isLength.js
@@ -12,7 +12,8 @@ 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 - surrogatePairs.length;
+  const len = str.length - presentationSequences.length - surrogatePairs.length;
   return len >= min && (typeof max === 'undefined' || len <= max);
 }
diff --git a/src/lib/isLicensePlate.js b/src/lib/isLicensePlate.js
index 668064f..e370e50 100644
--- a/src/lib/isLicensePlate.js
+++ b/src/lib/isLicensePlate.js
@@ -4,7 +4,7 @@ const validators = {
   'cs-CZ': str =>
     /^(([ABCDEFHKIJKLMNPRSTUVXYZ]|[0-9])-?){5,8}$/.test(str),
   'de-DE': str =>
-    /^((AW|UL|AK|GA|AÖ|LF|AZ|AM|AS|ZE|AN|AB|A|KG|KH|BA|EW|BZ|HY|KM|BT|HP|B|BC|BI|BO|FN|TT|ÜB|BN|AH|BS|FR|HB|ZZ|BB|BK|BÖ|OC|OK|CW|CE|C|CO|LH|CB|KW|LC|LN|DA|DI|DE|DH|SY|NÖ|DO|DD|DU|DN|D|EI|EA|EE|FI|EM|EL|EN|PF|ED|EF|ER|AU|ZP|E|ES|NT|EU|FL|FO|FT|FF|F|FS|FD|FÜ|GE|G|GI|GF|GS|ZR|GG|GP|GR|NY|ZI|GÖ|GZ|GT|HA|HH|HM|HU|WL|HZ|WR|RN|HK|HD|HN|HS|GK|HE|HF|RZ|HI|HG|HO|HX|IK|IL|IN|J|JL|KL|KA|KS|KF|KE|KI|KT|KO|KN|KR|KC|KU|K|LD|LL|LA|L|OP|LM|LI|LB|LU|LÖ|HL|LG|MD|GN|MZ|MA|ML|MR|MY|AT|DM|MC|NZ|RM|RG|MM|ME|MB|MI|FG|DL|HC|MW|RL|MK|MG|MÜ|WS|MH|M|MS|NU|NB|ND|NM|NK|NW|NR|NI|NF|DZ|EB|OZ|TG|TO|N|OA|GM|OB|CA|EH|FW|OF|OL|OE|OG|BH|LR|OS|AA|GD|OH|KY|NP|WK|PB|PA|PE|PI|PS|P|PM|PR|RA|RV|RE|R|H|SB|WN|RS|RD|RT|BM|NE|GV|RP|SU|GL|RO|GÜ|RH|EG|RW|PN|SK|MQ|RU|SZ|RI|SL|SM|SC|HR|FZ|VS|SW|SN|CR|SE|SI|SO|LP|SG|NH|SP|IZ|ST|BF|TE|HV|OD|SR|S|AC|DW|ZW|TF|TS|TR|TÜ|UM|PZ|TP|UE|UN|UH|MN|KK|VB|V|AE|PL|RC|VG|GW|PW|VR|VK|KB|WA|WT|BE|WM|WE|AP|MO|WW|FB|WZ|WI|WB|JE|WF|WO|W|WÜ|BL|Z|GC)[- ]?[A-Z]{1,2}[- ]?\d{1,4}|(AIC|FDB|ABG|SLN|SAW|KLZ|BUL|ESB|NAB|SUL|WST|ABI|AZE|BTF|KÖT|DKB|FEU|ROT|ALZ|SMÜ|WER|AUR|NOR|DÜW|BRK|HAB|TÖL|WOR|BAD|BAR|BER|BIW|EBS|KEM|MÜB|PEG|BGL|BGD|REI|WIL|BKS|BIR|WAT|BOR|BOH|BOT|BRB|BLK|HHM|NEB|NMB|WSF|LEO|HDL|WMS|WZL|BÜS|CHA|KÖZ|ROD|WÜM|CLP|NEC|COC|ZEL|COE|CUX|DAH|LDS|DEG|DEL|RSL|DLG|DGF|LAN|HEI|MED|DON|KIB|ROK|JÜL|MON|SLE|EBE|EIC|HIG|WBS|BIT|PRÜ|LIB|EMD|WIT|ERH|HÖS|ERZ|ANA|ASZ|MAB|MEK|STL|SZB|FDS|HCH|HOR|WOL|FRG|GRA|WOS|FRI|FFB|GAP|GER|BRL|CLZ|GTH|NOH|HGW|GRZ|LÖB|NOL|WSW|DUD|HMÜ|OHA|KRU|HAL|HAM|HBS|QLB|HVL|NAU|HAS|EBN|GEO|HOH|HDH|ERK|HER|WAN|HEF|ROF|HBN|ALF|HSK|USI|NAI|REH|SAN|KÜN|ÖHR|HOL|WAR|ARN|BRG|GNT|HOG|WOH|KEH|MAI|PAR|RID|ROL|KLE|GEL|KUS|KYF|ART|SDH|LDK|DIL|MAL|VIB|LER|BNA|GHA|GRM|MTL|WUR|LEV|LIF|STE|WEL|LIP|VAI|LUP|HGN|LBZ|LWL|PCH|STB|DAN|MKK|SLÜ|MSP|TBB|MGH|MTK|BIN|MSH|EIL|HET|SGH|BID|MYK|MSE|MST|MÜR|WRN|MEI|GRH|RIE|MZG|MIL|OBB|BED|FLÖ|MOL|FRW|SEE|SRB|AIB|MOS|BCH|ILL|SOB|NMS|NEA|SEF|UFF|NEW|VOH|NDH|TDO|NWM|GDB|GVM|WIS|NOM|EIN|GAN|LAU|HEB|OHV|OSL|SFB|ERB|LOS|BSK|KEL|BSB|MEL|WTL|OAL|FÜS|MOD|OHZ|OPR|BÜR|PAF|PLÖ|CAS|GLA|REG|VIT|ECK|SIM|GOA|EMS|DIZ|GOH|RÜD|SWA|NES|KÖN|MET|LRO|BÜZ|DBR|ROS|TET|HRO|ROW|BRV|HIP|PAN|GRI|SHK|EIS|SRO|SOK|LBS|SCZ|MER|QFT|SLF|SLS|HOM|SLK|ASL|BBG|SBK|SFT|SHG|MGN|MEG|ZIG|SAD|NEN|OVI|SHA|BLB|SIG|SON|SPN|FOR|GUB|SPB|IGB|WND|STD|STA|SDL|OBG|HST|BOG|SHL|PIR|FTL|SEB|SÖM|SÜW|TIR|SAB|TUT|ANG|SDT|LÜN|LSZ|MHL|VEC|VER|VIE|OVL|ANK|OVP|SBG|UEM|UER|WLG|GMN|NVP|RDG|RÜG|DAU|FKB|WAF|WAK|SLZ|WEN|SOG|APD|WUG|GUN|ESW|WIZ|WES|DIN|BRA|BÜD|WHV|HWI|GHC|WTM|WOB|WUN|MAK|SEL|OCH|HOT|WDA)[- ]?(([A-Z][- ]?\d{1,4})|([A-Z]{2}[- ]?\d{1,3})))[- ]?(E|H)?$/.test(str),
+    /^((A|AA|AB|AC|AE|AH|AK|AM|AN|AÖ|AP|AS|AT|AU|AW|AZ|B|BA|BB|BC|BE|BF|BH|BI|BK|BL|BM|BN|BO|BÖ|BS|BT|BZ|C|CA|CB|CE|CO|CR|CW|D|DA|DD|DE|DH|DI|DL|DM|DN|DO|DU|DW|DZ|E|EA|EB|ED|EE|EF|EG|EH|EI|EL|EM|EN|ER|ES|EU|EW|F|FB|FD|FF|FG|FI|FL|FN|FO|FR|FS|FT|FÜ|FW|FZ|G|GA|GC|GD|GE|GF|GG|GI|GK|GL|GM|GN|GÖ|GP|GR|GS|GT|GÜ|GV|GW|GZ|H|HA|HB|HC|HD|HE|HF|HG|HH|HI|HK|HL|HM|HN|HO|HP|HR|HS|HU|HV|HX|HY|HZ|IK|IL|IN|IZ|J|JE|JL|K|KA|KB|KC|KE|KF|KG|KH|KI|KK|KL|KM|KN|KO|KR|KS|KT|KU|KW|KY|L|LA|LB|LC|LD|LF|LG|LH|LI|LL|LM|LN|LÖ|LP|LR|LU|M|MA|MB|MC|MD|ME|MG|MH|MI|MK|ML|MM|MN|MO|MQ|MR|MS|MÜ|MW|MY|MZ|N|NB|ND|NE|NF|NH|NI|NK|NM|NÖ|NP|NR|NT|NU|NW|NY|NZ|OA|OB|OC|OD|OE|OF|OG|OH|OK|OL|OP|OS|OZ|P|PA|PB|PE|PF|PI|PL|PM|PN|PR|PS|PW|PZ|R|RA|RC|RD|RE|RG|RH|RI|RL|RM|RN|RO|RP|RS|RT|RU|RV|RW|RZ|S|SB|SC|SE|SG|SI|SK|SL|SM|SN|SO|SP|SR|ST|SU|SW|SY|SZ|TE|TF|TG|TO|TP|TR|TS|TT|TÜ|ÜB|UE|UH|UL|UM|UN|V|VB|VG|VK|VR|VS|W|WA|WB|WE|WF|WI|WK|WL|WM|WN|WO|WR|WS|WT|WÜ|WW|WZ|Z|ZE|ZI|ZP|ZR|ZW|ZZ)[- ]?[A-Z]{1,2}[- ]?\d{1,4}|(ABG|ABI|AIB|AIC|ALF|ALZ|ANA|ANG|ANK|APD|ARN|ART|ASL|ASZ|AUR|AZE|BAD|BAR|BBG|BCH|BED|BER|BGD|BGL|BID|BIN|BIR|BIT|BIW|BKS|BLB|BLK|BNA|BOG|BOH|BOR|BOT|BRA|BRB|BRG|BRK|BRL|BRV|BSB|BSK|BTF|BÜD|BUL|BÜR|BÜS|BÜZ|CAS|CHA|CLP|CLZ|COC|COE|CUX|DAH|DAN|DAU|DBR|DEG|DEL|DGF|DIL|DIN|DIZ|DKB|DLG|DON|DUD|DÜW|EBE|EBN|EBS|ECK|EIC|EIL|EIN|EIS|EMD|EMS|ERB|ERH|ERK|ERZ|ESB|ESW|FDB|FDS|FEU|FFB|FKB|FLÖ|FOR|FRG|FRI|FRW|FTL|FÜS|GAN|GAP|GDB|GEL|GEO|GER|GHA|GHC|GLA|GMN|GNT|GOA|GOH|GRA|GRH|GRI|GRM|GRZ|GTH|GUB|GUN|GVM|HAB|HAL|HAM|HAS|HBN|HBS|HCH|HDH|HDL|HEB|HEF|HEI|HER|HET|HGN|HGW|HHM|HIG|HIP|HMÜ|HOG|HOH|HOL|HOM|HOR|HÖS|HOT|HRO|HSK|HST|HVL|HWI|IGB|ILL|JÜL|KEH|KEL|KEM|KIB|KLE|KLZ|KÖN|KÖT|KÖZ|KRU|KÜN|KUS|KYF|LAN|LAU|LBS|LBZ|LDK|LDS|LEO|LER|LEV|LIB|LIF|LIP|LÖB|LOS|LRO|LSZ|LÜN|LUP|LWL|MAB|MAI|MAK|MAL|MED|MEG|MEI|MEK|MEL|MER|MET|MGH|MGN|MHL|MIL|MKK|MOD|MOL|MON|MOS|MSE|MSH|MSP|MST|MTK|MTL|MÜB|MÜR|MYK|MZG|NAB|NAI|NAU|NDH|NEA|NEB|NEC|NEN|NES|NEW|NMB|NMS|NOH|NOL|NOM|NOR|NVP|NWM|OAL|OBB|OBG|OCH|OHA|ÖHR|OHV|OHZ|OPR|OSL|OVI|OVL|OVP|PAF|PAN|PAR|PCH|PEG|PIR|PLÖ|PRÜ|QFT|QLB|RDG|REG|REH|REI|RID|RIE|ROD|ROF|ROK|ROL|ROS|ROT|ROW|RSL|RÜD|RÜG|SAB|SAD|SAN|SAW|SBG|SBK|SCZ|SDH|SDL|SDT|SEB|SEE|SEF|SEL|SFB|SFT|SGH|SHA|SHG|SHK|SHL|SIG|SIM|SLE|SLF|SLK|SLN|SLS|SLÜ|SLZ|SMÜ|SOB|SOG|SOK|SÖM|SON|SPB|SPN|SRB|SRO|STA|STB|STD|STE|STL|SUL|SÜW|SWA|SZB|TBB|TDO|TET|TIR|TÖL|TUT|UEM|UER|UFF|USI|VAI|VEC|VER|VIB|VIE|VIT|VOH|WAF|WAK|WAN|WAR|WAT|WBS|WDA|WEL|WEN|WER|WES|WHV|WIL|WIS|WIT|WIZ|WLG|WMS|WND|WOB|WOH|WOL|WOR|WOS|WRN|WSF|WST|WSW|WTL|WTM|WUG|WÜM|WUN|WUR|WZL|ZEL|ZIG)[- ]?(([A-Z][- ]?\d{1,4})|([A-Z]{2}[- ]?\d{1,3})))[- ]?(E|H)?$/.test(str),
   'de-LI': str => /^FL[- ]?\d{1,5}[UZ]?$/.test(str),
   'fi-FI': str => /^(?=.{4,7})(([A-Z]{1,3}|[0-9]{1,3})[\s-]?([A-Z]{1,3}|[0-9]{1,5}))$/.test(str),
   'pt-PT': str =>
@@ -15,8 +15,23 @@ const validators = {
     /^[A-Z]{3}[ -]?[0-9][A-Z][0-9]{2}|[A-Z]{3}[ -]?[0-9]{4}$/.test(str),
   'sv-SE': str =>
     /^[A-HJ-PR-UW-Z]{3} ?[\d]{2}[A-HJ-PR-UW-Z1-9]$|(^[A-ZÅÄÖ ]{2,7}$)/.test(str.trim()),
+  'en-IN': str => /^[A-Z]{2}[ -]?[0-9]{1,2}(?:[ -]?[A-Z])(?:[ -]?[A-Z]*)?[ -]?[0-9]{4}$/.test(str),
 };
 
+validators['hi-IN'] = validators['en-IN'];
+validators['gu-IN'] = validators['en-IN'];
+validators['as-IN'] = validators['en-IN'];
+validators['bn-IN'] = validators['en-IN'];
+validators['kn-IN'] = validators['en-IN'];
+validators['ml-IN'] = validators['en-IN'];
+validators['mr-IN'] = validators['en-IN'];
+validators['or-IN'] = validators['en-IN'];
+validators['pa-IN'] = validators['en-IN'];
+validators['sa-IN'] = validators['en-IN'];
+validators['ta-IN'] = validators['en-IN'];
+validators['te-IN'] = validators['en-IN'];
+validators['kok-IN'] = validators['en-IN'];
+
 export default function isLicensePlate(str, locale) {
   assertString(str);
   if (locale in validators) {
diff --git a/src/lib/isLuhnValid.js b/src/lib/isLuhnValid.js
new file mode 100644
index 0000000..da20527
--- /dev/null
+++ b/src/lib/isLuhnValid.js
@@ -0,0 +1,26 @@
+import assertString from './util/assertString';
+
+export default function isLuhnValid(str) {
+  assertString(str);
+  const sanitized = str.replace(/[- ]+/g, '');
+  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);
+}
diff --git a/src/lib/isMagnetURI.js b/src/lib/isMagnetURI.js
index 45b5c8e..e00ee3c 100644
--- a/src/lib/isMagnetURI.js
+++ b/src/lib/isMagnetURI.js
@@ -1,8 +1,13 @@
 import assertString from './util/assertString';
 
-const magnetURI = /^magnet:\?xt(?:\.1)?=urn:(?:aich|bitprint|btih|ed2k|ed2khash|kzhash|md5|sha1|tree:tiger):[a-z0-9]{32}(?:[a-z0-9]{8})?($|&)/i;
+const magnetURIComponent = /(?:^magnet:\?|[^?&]&)xt(?:\.1)?=urn:(?:(?:aich|bitprint|btih|ed2k|ed2khash|kzhash|md5|sha1|tree:tiger):[a-z0-9]{32}(?:[a-z0-9]{8})?|btmh:1220[a-z0-9]{64})(?:$|&)/i;
 
 export default function isMagnetURI(url) {
   assertString(url);
-  return magnetURI.test(url.trim());
+
+  if (url.indexOf('magnet:?') !== 0) {
+    return false;
+  }
+
+  return magnetURIComponent.test(url);
 }
diff --git a/src/lib/isMobilePhone.js b/src/lib/isMobilePhone.js
index e19184b..3c38d25 100644
--- a/src/lib/isMobilePhone.js
+++ b/src/lib/isMobilePhone.js
@@ -32,8 +32,12 @@ const phones = {
   'de-LU': /^(\+352)?((6\d1)\d{6})$/,
   'dv-MV': /^(\+?960)?(7[2-9]|91|9[3-9])\d{7}$/,
   'el-GR': /^(\+?30|0)?(69\d{8})$/,
+  'el-CY': /^(\+?357?)?(9(9|6)\d{6})$/,
+  'en-AI': /^(\+?1|0)264(?:2(35|92)|4(?:6[1-2]|76|97)|5(?:3[6-9]|8[1-4])|7(?:2(4|9)|72))\d{4}$/,
   'en-AU': /^(\+?61|0)4\d{8}$/,
+  'en-AG': /^(?:\+1|1)268(?:464|7(?:1[3-9]|[28]\d|3[0246]|64|7[0-689]))\d{4}$/,
   'en-BM': /^(\+?1)?441(((3|7)\d{6}$)|(5[0-3][0-9]\d{4}$)|(59\d{5}))/,
+  'en-BS': /^(\+?1[-\s]?|0)?\(?242\)?[-\s]?\d{3}[-\s]?\d{4}$/,
   'en-GB': /^(\+?44|0)7\d{9}$/,
   '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}$/,
@@ -42,6 +46,7 @@ const phones = {
   'en-MO': /^(\+?853[-\s]?)?[6]\d{3}[-\s]?\d{4}$/,
   'en-IE': /^(\+?353|0)8[356789]\d{7}$/,
   'en-IN': /^(\+?91|0)?[6789]\d{9}$/,
+  'en-JM': /^(\+?876)?\d{7}$/,
   'en-KE': /^(\+?254|0)(7|1)\d{8}$/,
   'en-KI': /^((\+686|686)?)?( )?((6|7)(2|3|8)[0-9]{6})$/,
   'en-LS': /^(\+?266)(22|28|57|58|59|27|52)\d{6}$/,
@@ -50,6 +55,7 @@ const phones = {
   'en-NA': /^(\+?264|0)(6|8)\d{7}$/,
   'en-NG': /^(\+?234|0)?[789]\d{9}$/,
   'en-NZ': /^(\+?64|0)[28]\d{7,9}$/,
+  'en-PG': /^(\+?675|0)?(7\d|8[18])\d{6}$/,
   'en-PK': /^((00|\+)?92|0)3[0-6]\d{8}$/,
   'en-PH': /^(09|\+639)\d{9}$/,
   'en-RW': /^(\+?250|0)?[7]\d{8}$/,
@@ -74,6 +80,7 @@ const phones = {
   'es-ES': /^(\+?34)?[6|7]\d{8}$/,
   'es-PE': /^(\+?51)?9\d{8}$/,
   'es-MX': /^(\+?52)?(1|01)?\d{10,11}$/,
+  'es-NI': /^(\+?505)\d{7,8}$/,
   'es-PA': /^(\+?507)\d{7,8}$/,
   'es-PY': /^(\+?595|0)9[9876]\d{7}$/,
   'es-SV': /^(\+?503)?[67]\d{7}$/,
@@ -81,10 +88,11 @@ const phones = {
   'es-VE': /^(\+?58)?(2|4)\d{9}$/,
   'et-EE': /^(\+?372)?\s?(5|8[1-4])\s?([0-9]\s?){6,7}$/,
   'fa-IR': /^(\+?98[\-\s]?|0)9[0-39]\d[\-\s]?\d{3}[\-\s]?\d{4}$/,
-  'fi-FI': /^(\+?358|0)\s?(4(0|1|2|4|5|6)?|50)\s?(\d\s?){4,8}\d$/,
+  'fi-FI': /^(\+?358|0)\s?(4[0-6]|50)\s?(\d\s?){4,8}$/,
   'fj-FJ': /^(\+?679)?\s?\d{3}\s?\d{4}$/,
   'fo-FO': /^(\+?298)?\s?\d{2}\s?\d{2}\s?\d{2}$/,
   'fr-BF': /^(\+226|0)[67]\d{7}$/,
+  'fr-BJ': /^(\+229)\d{8}$/,
   'fr-CM': /^(\+?237)6[0-9]{8}$/,
   'fr-FR': /^(\+?33|0)[67]\d{8}$/,
   'fr-GF': /^(\+?594|0|00594)[67]\d{8}$/,
@@ -95,15 +103,19 @@ const phones = {
   'he-IL': /^(\+972|0)([23489]|5[012345689]|77)[1-9]\d{6}$/,
   'hu-HU': /^(\+?36|06)(20|30|31|50|70)\d{7}$/,
   'id-ID': /^(\+?62|0)8(1[123456789]|2[1238]|3[1238]|5[12356789]|7[78]|9[56789]|8[123456789])([\s?|\d]{5,11})$/,
+  'ir-IR': /^(\+98|0)?9\d{9}$/,
   'it-IT': /^(\+?39)?\s?3\d{2} ?\d{6,7}$/,
   'it-SM': /^((\+378)|(0549)|(\+390549)|(\+3780549))?6\d{5,9}$/,
   'ja-JP': /^(\+81[ \-]?(\(0\))?|0)[6789]0[ \-]?\d{4}[ \-]?\d{4}$/,
-  'ka-GE': /^(\+?995)?(5|79)\d{7}$/,
+  'ka-GE': /^(\+?995)?(79\d{7}|5\d{8})$/,
   '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}$/,
   'lt-LT': /^(\+370|8)\d{8}$/,
   'lv-LV': /^(\+?371)2\d{7}$/,
+  'mg-MG': /^((\+?261|0)(2|3)\d)?\d{7}$/,
+  'mn-MN': /^(\+|00|011)?976(77|81|88|91|94|95|96|99)\d{6}$/,
   'my-MM': /^(\+?959|09|9)(2[5-7]|3[1-2]|4[0-5]|6[6-9]|7[5-9]|9[6-9])[0-9]{7}$/,
   'ms-MY': /^(\+?6?01){1}(([0145]{1}(\-|\s)?\d{7,8})|([236789]{1}(\s|\-)?\d{7}))$/,
   'mz-MZ': /^(\+?258)?8[234567]\d{7}$/,
@@ -111,9 +123,10 @@ const phones = {
   'ne-NP': /^(\+?977)?9[78]\d{8}$/,
   'nl-BE': /^(\+?32|0)4\d{8}$/,
   'nl-NL': /^(((\+|00)?31\(0\))|((\+|00)?31)|0)6{1}\d{8}$/,
+  'nl-AW': /^(\+)?297(56|59|64|73|74|99)\d{5}$/,
   'nn-NO': /^(\+?47)?[49]\d{7}$/,
   'pl-PL': /^(\+?48)? ?[5-8]\d ?\d{3} ?\d{2} ?\d{2}$/,
-  'pt-BR': /^((\+?55\ ?[1-9]{2}\ ?)|(\+?55\ ?\([1-9]{2}\)\ ?)|(0[1-9]{2}\ ?)|(\([1-9]{2}\)\ ?)|([1-9]{2}\ ?))((\d{4}\-?\d{4})|(9[2-9]{1}\d{3}\-?\d{4}))$/,
+  'pt-BR': /^((\+?55\ ?[1-9]{2}\ ?)|(\+?55\ ?\([1-9]{2}\)\ ?)|(0[1-9]{2}\ ?)|(\([1-9]{2}\)\ ?)|([1-9]{2}\ ?))((\d{4}\-?\d{4})|(9[1-9]{1}\d{3}\-?\d{4}))$/,
   'pt-PT': /^(\+?351)?9[1236]\d{7}$/,
   'pt-AO': /^(\+244)\d{9}$/,
   'ro-RO': /^(\+?4?0)\s?7\d{2}(\/|\s|\.|\-)?\d{3}(\s|\.|\-)?\d{3}$/,
@@ -134,6 +147,9 @@ const phones = {
   'zh-CN': /^((\+|00)86)?(1[3-9]|9[28])\d{9}$/,
   'zh-TW': /^(\+?886\-?|0)?9\d{8}$/,
   'dz-BT': /^(\+?975|0)?(17|16|77|02)\d{6}$/,
+  'ar-YE': /^(((\+|00)9677|0?7)[0137]\d{7}|((\+|00)967|0)[1-7]\d{6})$/,
+  'ar-EH': /^(\+?212|0)[\s\-]?(5288|5289)[\s\-]?\d{5}$/,
+  'fa-AF': /^(\+93|0)?(2{1}[0-8]{1}|[3-5]{1}[0-4]{1})(\d{7})$/,
 };
 /* eslint-enable max-len */
 
diff --git a/src/lib/isPassportNumber.js b/src/lib/isPassportNumber.js
index 4c38bfb..5b2ebfd 100644
--- a/src/lib/isPassportNumber.js
+++ b/src/lib/isPassportNumber.js
@@ -46,6 +46,7 @@ const passportRegexByCountryCode = {
   MT: /^\d{7}$/, // MALTA
   MZ: /^([A-Z]{2}\d{7})|(\d{2}[A-Z]{2}\d{5})$/, // MOZAMBIQUE
   MY: /^[AHK]\d{8}$/, // MALAYSIA
+  MX: /^\d{10,11}$/, // MEXICO
   NL: /^[A-Z]{2}[A-Z0-9]{6}\d$/, // NETHERLANDS
   PL: /^[A-Z]{2}\d{7}$/, // POLAND
   PT: /^[A-Z]\d{6}$/, // PORTUGAL
@@ -54,6 +55,7 @@ const passportRegexByCountryCode = {
   SE: /^\d{8}$/, // SWEDEN
   SL: /^(P)[A-Z]\d{7}$/, // SLOVANIA
   SK: /^[0-9A-Z]\d{7}$/, // SLOVAKIA
+  TH: /^[A-Z]{1,2}\d{6,7}$/, // THAILAND
   TR: /^[A-Z]\d{8}$/, // TURKEY
   UA: /^[A-Z]{2}\d{6}$/, // UKRAINE
   US: /^\d{9}$/, // UNITED STATES
diff --git a/src/lib/isPostalCode.js b/src/lib/isPostalCode.js
index 7ef17ab..cf5b50d 100644
--- a/src/lib/isPostalCode.js
+++ b/src/lib/isPostalCode.js
@@ -11,6 +11,7 @@ const patterns = {
   AT: fourDigit,
   AU: fourDigit,
   AZ: /^AZ\d{4}$/,
+  BA: /^([7-8]\d{4}$)/,
   BE: fourDigit,
   BG: fourDigit,
   BR: /^\d{5}-\d{3}$/,
@@ -47,6 +48,7 @@ const patterns = {
   LU: fourDigit,
   LV: /^LV\-\d{4}$/,
   LK: fiveDigit,
+  MG: threeDigit,
   MX: fiveDigit,
   MT: /^[A-Za-z]{3}\s{0,1}\d{4}$/,
   MY: fiveDigit,
diff --git a/src/lib/isTaxID.js b/src/lib/isTaxID.js
index f43c61a..933783f 100644
--- a/src/lib/isTaxID.js
+++ b/src/lib/isTaxID.js
@@ -373,7 +373,7 @@ function enUsGetPrefixes() {
  * Verify that the TIN starts with a valid IRS campus prefix
  */
 function enUsCheck(tin) {
-  return enUsGetPrefixes().indexOf(tin.substr(0, 2)) !== -1;
+  return enUsGetPrefixes().indexOf(tin.slice(0, 2)) !== -1;
 }
 
 /*
diff --git a/src/lib/isURL.js b/src/lib/isURL.js
index aa6222a..3d2b1df 100644
--- a/src/lib/isURL.js
+++ b/src/lib/isURL.js
@@ -87,11 +87,11 @@ export default function isURL(url, options) {
     }
   } else if (options.require_protocol) {
     return false;
-  } else if (url.substr(0, 2) === '//') {
+  } else if (url.slice(0, 2) === '//') {
     if (!options.allow_protocol_relative_urls) {
       return false;
     }
-    split[0] = url.substr(2);
+    split[0] = url.slice(2);
   }
   url = split.join('://');
 
@@ -152,6 +152,11 @@ export default function isURL(url, options) {
   if (options.host_whitelist) {
     return checkHost(host, options.host_whitelist);
   }
+
+  if (host === '' && !options.require_host) {
+    return true;
+  }
+
   if (!isIP(host) && !isFQDN(host, options) && (!ipv6 || !isIP(ipv6, 6))) {
     return false;
   }
diff --git a/src/lib/isVAT.js b/src/lib/isVAT.js
index 884b066..9559356 100644
--- a/src/lib/isVAT.js
+++ b/src/lib/isVAT.js
@@ -1,9 +1,101 @@
 import assertString from './util/assertString';
+import * as algorithms from './util/algorithms';
+
+const PT = (str) => {
+  const match = str.match(/^(PT)?(\d{9})$/);
+  if (!match) {
+    return false;
+  }
+
+  const tin = match[2];
+
+  const checksum = 11 - (algorithms.reverseMultiplyAndSum(tin.split('').slice(0, 8).map(a => parseInt(a, 10)), 9) % 11);
+  if (checksum > 9) {
+    return parseInt(tin[8], 10) === 0;
+  }
+  return checksum === parseInt(tin[8], 10);
+};
 
 export const vatMatchers = {
-  GB: /^GB((\d{3} \d{4} ([0-8][0-9]|9[0-6]))|(\d{9} \d{3})|(((GD[0-4])|(HA[5-9]))[0-9]{2}))$/,
-  IT: /^(IT)?[0-9]{11}$/,
-  NL: /^(NL)?[0-9]{9}B[0-9]{2}$/,
+  /**
+   * European Union VAT identification numbers
+   */
+  AT: str => /^(AT)?U\d{8}$/.test(str),
+  BE: str => /^(BE)?\d{10}$/.test(str),
+  BG: str => /^(BG)?\d{9,10}$/.test(str),
+  HR: str => /^(HR)?\d{11}$/.test(str),
+  CY: str => /^(CY)?\w{9}$/.test(str),
+  CZ: str => /^(CZ)?\d{8,10}$/.test(str),
+  DK: str => /^(DK)?\d{8}$/.test(str),
+  EE: str => /^(EE)?\d{9}$/.test(str),
+  FI: str => /^(FI)?\d{8}$/.test(str),
+  FR: str => /^(FR)?\w{2}\d{9}$/.test(str),
+  DE: str => /^(DE)?\d{9}$/.test(str),
+  EL: str => /^(EL)?\d{9}$/.test(str),
+  HU: str => /^(HU)?\d{8}$/.test(str),
+  IE: str => /^(IE)?\d{7}\w{1}(W)?$/.test(str),
+  IT: str => /^(IT)?\d{11}$/.test(str),
+  LV: str => /^(LV)?\d{11}$/.test(str),
+  LT: str => /^(LT)?\d{9,12}$/.test(str),
+  LU: str => /^(LU)?\d{8}$/.test(str),
+  MT: str => /^(MT)?\d{8}$/.test(str),
+  NL: str => /^(NL)?\d{9}B\d{2}$/.test(str),
+  PL: str => /^(PL)?(\d{10}|(\d{3}-\d{3}-\d{2}-\d{2})|(\d{3}-\d{2}-\d{2}-\d{3}))$/.test(str),
+  PT,
+  RO: str => /^(RO)?\d{2,10}$/.test(str),
+  SK: str => /^(SK)?\d{10}$/.test(str),
+  SI: str => /^(SI)?\d{8}$/.test(str),
+  ES: str => /^(ES)?\w\d{7}[A-Z]$/.test(str),
+  SE: str => /^(SE)?\d{12}$/.test(str),
+
+  /**
+   * VAT numbers of non-EU countries
+   */
+  AL: str => /^(AL)?\w{9}[A-Z]$/.test(str),
+  MK: str => /^(MK)?\d{13}$/.test(str),
+  AU: str => /^(AU)?\d{11}$/.test(str),
+  BY: str => /^(УНП )?\d{9}$/.test(str),
+  CA: str => /^(CA)?\d{9}$/.test(str),
+  IS: str => /^(IS)?\d{5,6}$/.test(str),
+  IN: str => /^(IN)?\d{15}$/.test(str),
+  ID: str => /^(ID)?(\d{15}|(\d{2}.\d{3}.\d{3}.\d{1}-\d{3}.\d{3}))$/.test(str),
+  IL: str => /^(IL)?\d{9}$/.test(str),
+  KZ: str => /^(KZ)?\d{9}$/.test(str),
+  NZ: str => /^(NZ)?\d{9}$/.test(str),
+  NG: str => /^(NG)?(\d{12}|(\d{8}-\d{4}))$/.test(str),
+  NO: str => /^(NO)?\d{9}MVA$/.test(str),
+  PH: str => /^(PH)?(\d{12}|\d{3} \d{3} \d{3} \d{3})$/.test(str),
+  RU: str => /^(RU)?(\d{10}|\d{12})$/.test(str),
+  SM: str => /^(SM)?\d{5}$/.test(str),
+  SA: str => /^(SA)?\d{15}$/.test(str),
+  RS: str => /^(RS)?\d{9}$/.test(str),
+  CH: str => /^(CH)?(\d{6}|\d{9}|(\d{3}.\d{3})|(\d{3}.\d{3}.\d{3}))(TVA|MWST|IVA)$/.test(str),
+  TR: str => /^(TR)?\d{10}$/.test(str),
+  UA: str => /^(UA)?\d{12}$/.test(str),
+  GB: str => /^GB((\d{3} \d{4} ([0-8][0-9]|9[0-6]))|(\d{9} \d{3})|(((GD[0-4])|(HA[5-9]))[0-9]{2}))$/.test(str),
+  UZ: str => /^(UZ)?\d{9}$/.test(str),
+
+  /**
+   * VAT numbers of Latin American countries
+   */
+  AR: str => /^(AR)?\d{11}$/.test(str),
+  BO: str => /^(BO)?\d{7}$/.test(str),
+  BR: str => /^(BR)?((\d{2}.\d{3}.\d{3}\/\d{4}-\d{2})|(\d{3}.\d{3}.\d{3}-\d{2}))$/.test(str),
+  CL: str => /^(CL)?\d{8}-\d{1}$/.test(str),
+  CO: str => /^(CO)?\d{10}$/.test(str),
+  CR: str => /^(CR)?\d{9,12}$/.test(str),
+  EC: str => /^(EC)?\d{13}$/.test(str),
+  SV: str => /^(SV)?\d{4}-\d{6}-\d{3}-\d{1}$/.test(str),
+  GT: str => /^(GT)?\d{7}-\d{1}$/.test(str),
+  HN: str => /^(HN)?$/.test(str),
+  MX: str => /^(MX)?\w{3,4}\d{6}\w{3}$/.test(str),
+  NI: str => /^(NI)?\d{3}-\d{6}-\d{4}\w{1}$/.test(str),
+  PA: str => /^(PA)?$/.test(str),
+  PY: str => /^(PY)?\d{6,8}-\d{1}$/.test(str),
+  PE: str => /^(PE)?\d{11}$/.test(str),
+  DO: str => /^(DO)?(\d{11}|(\d{3}-\d{7}-\d{1})|[1,4,5]{1}\d{8}|([1,4,5]{1})-\d{2}-\d{5}-\d{1})$/.test(str),
+  UY: str => /^(UY)?\d{12}$/.test(str),
+  VE: str => /^(VE)?[J,G,V,E]{1}-(\d{9}|(\d{8}-\d{1}))$/.test(str),
 };
 
 export default function isVAT(str, countryCode) {
@@ -11,7 +103,7 @@ export default function isVAT(str, countryCode) {
   assertString(countryCode);
 
   if (countryCode in vatMatchers) {
-    return vatMatchers[countryCode].test(str);
+    return vatMatchers[countryCode](str);
   }
   throw new Error(`Invalid country code: '${countryCode}'`);
 }
diff --git a/src/lib/matches.js b/src/lib/matches.js
index 5b435e2..9e23c2e 100644
--- a/src/lib/matches.js
+++ b/src/lib/matches.js
@@ -5,5 +5,5 @@ export default function matches(str, pattern, modifiers) {
   if (Object.prototype.toString.call(pattern) !== '[object RegExp]') {
     pattern = new RegExp(pattern, modifiers);
   }
-  return pattern.test(str);
+  return !!str.match(pattern);
 }


Test patch:
diff --git a/test/validators.js b/test/validators.js
index 16fb6b8..dc1f02d 100644
--- a/test/validators.js
+++ b/test/validators.js
@@ -344,6 +344,22 @@ describe('Validators', () => {
     });
   });
 
+  it('should validate only email addresses with whitelisted domains', () => {
+    test({
+      validator: 'isEmail',
+      args: [{ host_whitelist: ['gmail.com', 'foo.bar.com'] }],
+      valid: [
+        'email@gmail.com',
+        'test@foo.bar.com',
+      ],
+      invalid: [
+        'foo+bar@test.com',
+        'email@foo.com',
+        'email@bar.com',
+      ],
+    });
+  });
+
   it('should validate URLs', () => {
     test({
       validator: 'isURL',
@@ -472,6 +488,24 @@ describe('Validators', () => {
     });
   });
 
+  it('should validate postgres URLs without a host', () => {
+    test({
+      validator: 'isURL',
+      args: [{
+        protocols: ['postgres'],
+        require_host: false,
+      }],
+      valid: [
+        'postgres://user:pw@/test',
+      ],
+      invalid: [
+        'http://foobar.com',
+        'postgres://',
+      ],
+    });
+  });
+
+
   it('should validate URLs with any protocol', () => {
     test({
       validator: 'isURL',
@@ -1291,7 +1325,18 @@ describe('Validators', () => {
       ],
     });
   });
-
+  it('should validate FQDN with required allow_trailing_dot, allow_underscores and allow_numeric_tld options', () => {
+    test({
+      validator: 'isFQDN',
+      args: [
+        { allow_trailing_dot: true, allow_underscores: true, allow_numeric_tld: true },
+      ],
+      valid: [
+        'abc.efg.g1h.',
+        'as1s.sad3s.ssa2d.',
+      ],
+    });
+  });
   it('should validate alpha strings', () => {
     test({
       validator: 'isAlpha',
@@ -1395,6 +1440,26 @@ describe('Validators', () => {
     });
   });
 
+  it('should validate Bengali alpha strings', () => {
+    test({
+      validator: 'isAlpha',
+      args: ['bn-BD'],
+      valid: [
+        'অয়াওর',
+        'ফগফদ্রত',
+        'ফদ্ম্যতভ',
+        'বেরেওভচনভন',
+        'আমারবাসগা',
+      ],
+      invalid: [
+        'দাস২৩৪',
+        '  দ্গফহ্নভ  ',
+        '',
+        '(গফদ)',
+      ],
+    });
+  });
+
   it('should validate czech alpha strings', () => {
     test({
       validator: 'isAlpha',
@@ -1920,6 +1985,26 @@ describe('Validators', () => {
     });
   });
 
+  it('should validate Sinhala alpha strings', () => {
+    test({
+      validator: 'isAlpha',
+      args: ['si-LK'],
+      valid: [
+        'චතුර',
+        'කචටදබ',
+        'ඎඏදාෛපසුගො',
+      ],
+      invalid: [
+        'ஆஐअतක',
+        'කචට 12',
+        ' ඎ ',
+        'abc1',
+        'abc',
+        '',
+      ],
+    });
+  });
+
   it('should error on invalid locale', () => {
     test({
       validator: 'isAlpha',
@@ -2046,6 +2131,25 @@ describe('Validators', () => {
     });
   });
 
+  it('should validate Bengali alphanumeric strings', () => {
+    test({
+      validator: 'isAlphanumeric',
+      args: ['bn-BD'],
+      valid: [
+        'দ্গজ্ঞহ্রত্য১২৩',
+        'দ্গগফ৮৯০',
+        'চব৩৬৫ভবচ',
+        '১২৩৪',
+        '৩৪২৩৪দফজ্ঞদফ',
+      ],
+      invalid: [
+        ' ',
+        '১২৩  ',
+        'hel৩২0',
+      ],
+    });
+  });
+
   it('should validate czech alphanumeric strings', () => {
     test({
       validator: 'isAlphanumeric',
@@ -2507,6 +2611,27 @@ describe('Validators', () => {
     });
   });
 
+  it('should validate Sinhala alphanumeric strings', () => {
+    test({
+      validator: 'isAlphanumeric',
+      args: ['si-LK'],
+      valid: [
+        'චතුර',
+        'කචට12',
+        'ඎඏදාෛපසුගො2',
+        '1234',
+      ],
+      invalid: [
+        'ஆஐअतක',
+        'කචට 12',
+        ' ඎ ',
+        'a1234',
+        'abc',
+        '',
+      ],
+    });
+  });
+
   it('should error on invalid locale', () => {
     test({
       validator: 'isAlphanumeric',
@@ -3145,6 +3270,19 @@ describe('Validators', () => {
       ],
     });
 
+    test({
+      validator: 'isPassportNumber',
+      args: ['MX'],
+      valid: [
+        '43986369222',
+        '01234567890',
+      ],
+      invalid: [
+        'ABC34567890',
+        '34567890',
+      ],
+    });
+
     test({
       validator: 'isPassportNumber',
       args: ['NL'],
@@ -3252,6 +3390,22 @@ describe('Validators', () => {
       ],
     });
 
+    test({
+      validator: 'isPassportNumber',
+      args: ['TH'],
+      valid: [
+        'A123456',
+        'B1234567',
+        'CD123456',
+        'EF1234567',
+      ],
+      invalid: [
+        '123456',
+        '1234567',
+        '010485371AA',
+      ],
+    });
+
     test({
       validator: 'isPassportNumber',
       args: ['TR'],
@@ -4586,6 +4740,11 @@ describe('Validators', () => {
       validator: 'isLength',
       valid: ['a', '', 'asds'],
     });
+    test({
+      validator: 'isLength',
+      args: [{ max: 8 }],
+      valid: ['👩🦰👩👩👦👦🏳️🌈', '⏩︎⏩︎⏪︎⏪︎⏭︎⏭︎⏮︎⏮︎'],
+    });
   });
 
   it('should validate strings by byte length', () => {
@@ -4931,6 +5090,34 @@ describe('Validators', () => {
     });
   });
 
+  it('should validate luhn numbers', () => {
+    test({
+      validator: 'isLuhnValid',
+      valid: [
+        '0',
+        '5421',
+        '01234567897',
+        '0123456789012345678906',
+        '0123456789012345678901234567891',
+        '123456789012345678906',
+        '375556917985515',
+        '36050234196908',
+        '4716461583322103',
+        '4716-2210-5188-5662',
+        '4929 7226 5379 7141',
+      ],
+      invalid: [
+        '',
+        '1',
+        '5422',
+        'foo',
+        'prefix6234917882863855',
+        '623491788middle2863855',
+        '6234917882863855suffix',
+      ],
+    });
+  });
+
   it('should validate credit cards', () => {
     test({
       validator: 'isCreditCard',
@@ -6476,6 +6663,12 @@ describe('Validators', () => {
           '(22) 999567894',
           '(22) 99956-7894',
           '(11) 94123-4567',
+          '(11) 91431-4567',
+          '+55 (11) 91431-4567',
+          '+55 11 91431-4567',
+          '+551191431-4567',
+          '5511914314567',
+          '5511912345678',
         ],
         invalid: [
           '0819876543',
@@ -6484,12 +6677,12 @@ describe('Validators', () => {
           '5501599623874',
           '+55012962308',
           '+55 015 1234-3214',
-          '+55 11 91431-4567',
-          '+55 (11) 91431-4567',
-          '+551191431-4567',
-          '5511914314567',
-          '5511912345678',
-          '(11) 91431-4567',
+          '+55 11 90431-4567',
+          '+55 (11) 90431-4567',
+          '+551190431-4567',
+          '5511904314567',
+          '5511902345678',
+          '(11) 90431-4567',
         ],
       },
       {
@@ -6608,6 +6801,28 @@ describe('Validators', () => {
           '+1441897465',
         ],
       },
+      {
+        locale: 'en-BS',
+        valid: [
+          '+12421231234',
+          '2421231234',
+          '+1-2421231234',
+          '+1-242-123-1234',
+          '(242)-123-1234',
+          '+1 (242)-123-1234',
+          '242 123-1234',
+          '(242) 123 1234',
+        ],
+        invalid: [
+          '85763287',
+          '+1 242 12 12 12 12',
+          '+1424123123',
+          '+14418245567',
+          '+14416546789',
+          'not a number',
+          '',
+        ],
+      },
       {
         locale: 'en-ZA',
         valid: [
@@ -6786,6 +7001,22 @@ describe('Validators', () => {
           '353811234567',
         ],
       },
+      {
+        locale: 'en-JM',
+        valid: [
+          '+8761021234',
+          '8761211234',
+          '8763511274',
+          '+8764511274',
+        ],
+        invalid: [
+          '999',
+          '+876102123422',
+          '+8861021234',
+          '8761021212213',
+          '876102123',
+        ],
+      },
       {
         locale: 'en-KE',
         valid: [
@@ -6971,6 +7202,24 @@ describe('Validators', () => {
           '+22634523',
         ],
       },
+      {
+        locale: 'fr-BJ',
+        valid: [
+          '+22920215789',
+          '+22920293092',
+          '+22921307898',
+          '+22921736346',
+          '+22922416346',
+          '+22923836346',
+        ],
+        invalid: [
+          '0612457892',
+          '01122921737346',
+          '+22762457898',
+          '+226724578980',
+          '+22634523',
+        ],
+      },
       {
         locale: 'fr-CA',
         valid: ['19876543210', '8005552222', '+15673628910'],
@@ -7107,18 +7356,19 @@ describe('Validators', () => {
       {
         locale: 'ka-GE',
         valid: [
-          '+99550001111',
-          '+99551535213',
+          '+995500011111',
+          '+995515352134',
           '+995798526662',
           '798526662',
-          '50001111',
+          '500011119',
           '798526662',
           '+995799766525',
         ],
         invalid: [
-          '+995500011118',
+          '+99550001111',
           '+9957997665250',
-          '+995999766525',
+          '+9959997665251',
+          '+995780011111',
           '20000000000',
           '68129485729',
           '6589394827',
@@ -7142,6 +7392,31 @@ describe('Validators', () => {
           '298RI89572',
         ],
       },
+      {
+        locale: 'el-CY',
+        valid: [
+          '96546247',
+          '96978927',
+          '+35799837145',
+          '+35799646792',
+          '96056927',
+          '99629593',
+          '99849980',
+          '3599701619',
+          '+3599148725',
+          '96537247',
+          '3596676533',
+        ],
+        invalid: [
+          '',
+          'somechars',
+          '9697892',
+          '998499803',
+          '33799837145',
+          '+3799646792',
+          '93056927',
+        ],
+      },
       {
         locale: 'en-GB',
         valid: [
@@ -7686,6 +7961,27 @@ describe('Validators', () => {
           '+34754789321',
         ],
       },
+      {
+        locale: 'es-NI',
+        valid: [
+          '+5051234567',
+          '+50512345678',
+          '5051234567',
+          '50512345678',
+          '+50555555555',
+        ],
+        invalid: [
+          '1234',
+          '',
+          '1234567',
+          '12345678',
+          '+12345678',
+          '+505123456789',
+          '+50612345678',
+          '+50712345678',
+          '-50512345678',
+        ],
+      },
       {
         locale: 'es-PA',
         valid: [
@@ -7873,6 +8169,9 @@ describe('Validators', () => {
           '0457 123 45 67',
           '+358457 123 45 67',
           '+358 50 555 7171',
+          '0501234',
+          '+358501234',
+          '050 1234',
         ],
         invalid: [
           '12345',
@@ -7984,6 +8283,20 @@ describe('Validators', () => {
           '+820 11 7766 1234',
         ],
       },
+      {
+        locale: 'ky-KG',
+        valid: [
+          '+7 727 123 4567',
+          '+7 714 2396102',
+          '77271234567',
+          '0271234567',
+        ],
+        invalid: [
+          '02188565377',
+          '09386932778',
+          '0938693277vadggjdsaasdgj8',
+        ],
+      },
       {
         locale: 'ja-JP',
         valid: [
@@ -8023,6 +8336,22 @@ describe('Validators', () => {
           '90 1234 5678',
         ],
       },
+      {
+        locale: 'ir-IR',
+        valid: [
+          '09023818688',
+          '09123809999',
+          '+989023818688',
+          '+989103923523',
+        ],
+        invalid: [
+          '19023818688',
+          '323254',
+          '+903232323257',
+          '++3567868',
+          '0902381888832',
+        ],
+      },
       {
         locale: 'it-IT',
         valid: [
@@ -8116,6 +8445,35 @@ describe('Validators', () => {
           '310212345678',
         ],
       },
+      {
+        locale: 'nl-AW',
+        valid: [
+          '2975612345',
+          '2976412345',
+          '+2975612345',
+          '+2975912345',
+          '+2976412345',
+          '+2977312345',
+          '+2977412345',
+          '+2979912345',
+        ],
+        invalid: [
+          '12345',
+          '+2972345',
+          '2972345',
+          '06701234567',
+          '012345678',
+          '+2974701234567',
+          '2974701234567',
+          '0297345678',
+          '029734567',
+          '+2971234567',
+          '2971234567',
+          '+297212345678',
+          '297212345678',
+          'number',
+        ],
+      },
       {
         locale: 'ro-RO',
         valid: [
@@ -8604,6 +8962,46 @@ describe('Validators', () => {
           'NotANumber',
         ],
       },
+      {
+        locale: 'mg-MG',
+        valid: [
+          '+261204269174',
+          '261204269174',
+          '0204269174',
+          '0209269174',
+          '0374269174',
+          '4269174',
+        ],
+        invalid: [
+          '0261204269174',
+          '+261 20 4 269174',
+          '+261 20 4269174',
+          '020 4269174',
+          '204269174',
+          '0404269174',
+          'NotANumber',
+        ],
+      },
+      {
+        locale: 'mn-MN',
+        valid: [
+          '+97699112222',
+          '97696112222',
+          '97695112222',
+          '01197691112222',
+          '0097688112222',
+          '+97677112222',
+          '+97694112222',
+          '+97681112222',
+        ],
+        invalid: [
+          '+97888112222',
+          '+97977112222',
+          '+97094112222',
+          '+97281112222',
+          '02297681112222',
+        ],
+      },
       {
         locale: 'my-MM',
         valid: [
@@ -8634,6 +9032,77 @@ describe('Validators', () => {
           '09650000234',
         ],
       },
+      {
+        locale: 'en-PG',
+        valid: [
+          '+67570123456',
+          '67570123456',
+          '+67571123456',
+          '+67572123456',
+          '+67573123456',
+          '+67574123456',
+          '+67575123456',
+          '+67576123456',
+          '+67577123456',
+          '+67578123456',
+          '+67579123456',
+          '+67581123456',
+          '+67588123456',
+        ],
+        invalid: [
+          '',
+          'not a number',
+          '12345',
+          '+675123456789',
+          '+67580123456',
+          '+67569123456',
+          '+67582123456',
+          '+6757012345',
+        ],
+      },
+      {
+        locale: 'en-AG',
+        valid: [
+          '12687151234',
+          '+12687151234',
+          '+12684641234',
+          '12684641234',
+          '+12687211234',
+          '+12687302468',
+          '+12687642456',
+          '+12687763333',
+        ],
+        invalid: [
+          '2687151234',
+          '+12687773333',
+          '+126846412333',
+          '+12684641',
+          '+12687123456',
+          '+12687633456',
+        ],
+      },
+      {
+        locale: 'en-AI',
+        valid: [
+          '+12642351234',
+          '12642351234',
+          '+12644612222',
+          '+12645366326',
+          '+12645376326',
+          '+12647246326',
+          '+12647726326',
+        ],
+        invalid: [
+          '',
+          'not a number',
+          '+22642351234',
+          '+12902351234',
+          '+12642331234',
+          '+1264235',
+          '22642353456',
+          '+12352643456',
+        ],
+      },
       {
         locale: 'en-PK',
         valid: [
@@ -8692,31 +9161,91 @@ describe('Validators', () => {
           'NotANumber',
         ],
       },
-    ];
-
-    let allValid = [];
-
-    fixtures.forEach((fixture) => {
-      // to be used later on for validating 'any' locale
-      if (fixture.valid) allValid = allValid.concat(fixture.valid);
-
-      if (Array.isArray(fixture.locale)) {
-        test({
-          validator: 'isMobilePhone',
-          valid: fixture.valid,
-          invalid: fixture.invalid,
-          args: [fixture.locale],
-        });
-      } else {
-        test({
-          validator: 'isMobilePhone',
-          valid: fixture.valid,
-          invalid: fixture.invalid,
-          args: [fixture.locale],
-        });
-      }
-    });
-
+      {
+        locale: 'ar-YE',
+        valid: [
+          '737198225',
+          '733111355',
+          '+967700990270',
+        ],
+        invalid: [
+          '+5032136663',
+          '21346663',
+          '+50321366663',
+          '12345',
+          'Yemen',
+          'this should fail',
+          '+5032222',
+          '+503 1111 1111',
+          '00 +503 1234 5678',
+        ],
+      },
+      {
+        locale: 'ar-EH',
+        valid: [
+          '+212-5288-12312',
+          '+212-5288 12312',
+          '+212 5288 12312',
+          '212528912312',
+          '+212528912312',
+          '+212528812312',
+        ],
+        invalid: [
+          '212528812312123',
+          '+212-5290-12312',
+          '++212528812312',
+          '12345',
+          'Wester Sahara',
+          'this should fail',
+          '212  5288---12312',
+          '+503 1111 1111',
+          '00 +503 1234 5678',
+        ],
+      },
+      {
+        locale: 'fa-AF',
+        valid: [
+          '0511231231',
+          '+93511231231',
+          '+93281234567',
+        ],
+        invalid: [
+          '212528812312123',
+          '+212-5290-12312',
+          '++212528812312',
+          '12345',
+          'Afghanistan',
+          'this should fail',
+          '212  5288---12312',
+          '+503 1111 1111',
+          '00 +503 1234 5678',
+        ],
+      },
+    ];
+
+    let allValid = [];
+
+    fixtures.forEach((fixture) => {
+      // to be used later on for validating 'any' locale
+      if (fixture.valid) allValid = allValid.concat(fixture.valid);
+
+      if (Array.isArray(fixture.locale)) {
+        test({
+          validator: 'isMobilePhone',
+          valid: fixture.valid,
+          invalid: fixture.invalid,
+          args: [fixture.locale],
+        });
+      } else {
+        test({
+          validator: 'isMobilePhone',
+          valid: fixture.valid,
+          invalid: fixture.invalid,
+          args: [fixture.locale],
+        });
+      }
+    });
+
     test({
       validator: 'isMobilePhone',
       valid: allValid,
@@ -10230,6 +10759,8 @@ describe('Validators', () => {
         'magnet:?xt=urn:md5:ABCDEFGHIJKLMNOPQRSTUVWXYZ123456',
         'magnet:?xt=urn:tree:tiger:ABCDEFGHIJKLMNOPQRSTUVWXYZ123456',
         'magnet:?xt=urn:ed2k:ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678901234',
+        'magnet:?tr=udp://helloworld:1337/announce&xt=urn:btih:ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678901234',
+        'magnet:?xt=urn:btmh:1220caf1e1c30e81cb361b9ee167c4aa64228a7fa4fa9f6105232b28ad099f3a302e',
       ],
       invalid: [
         ':?xt=urn:btih:ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678901234',
@@ -10242,6 +10773,8 @@ describe('Validators', () => {
         'magnet:?xt:urn:nonexisting:ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678901234',
         'magnet:?xt.2=urn:btih:ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678901234',
         'magnet:?xt=urn:ed2k:ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678901234567890123456789ABCD',
+        'magnet:?xt=urn:btmh:1120caf1e1c30e81cb361b9ee167c4aa64228a7fa4fa9f6105232b28ad099f3a302e',
+        'magnet:?ttxt=urn:btmh:1220caf1e1c30e81cb361b9ee167c4aa64228a7fa4fa9f6105232b28ad099f3a302e',
       ],
     });
     /* eslint-enable max-len */
@@ -10674,6 +11207,15 @@ describe('Validators', () => {
           '4144',
         ],
       },
+      {
+        locale: 'MG',
+        valid: [
+          '101',
+          '303',
+          '407',
+          '512',
+        ],
+      },
       {
         locale: 'MT',
         valid: [
@@ -10795,6 +11337,23 @@ describe('Validators', () => {
           '982',
         ],
       },
+      {
+        locale: 'BA',
+        valid: [
+          '76300',
+          '71000',
+          '75412',
+          '76100',
+          '88202',
+          '88313',
+        ],
+        invalid: [
+          '1234',
+          '789389',
+          '98212',
+          '11000',
+        ],
+      },
     ];
 
     let allValid = [];
@@ -11750,6 +12309,16 @@ describe('Validators', () => {
         'FS AB 1234 E',
         'FSAB1234E',
         'FS-AB-1234-E',
+        'FS AB-1234-E',
+        'FSAB1234 E',
+        'FS AB1234E',
+        'LRO AB 123',
+        'LRO-AB-123-E',
+        'LRO-AB-123E',
+        'LRO-AB-123 E',
+        'LRO-AB-123-H',
+        'LRO-AB-123H',
+        'LRO-AB-123 H',
       ],
       invalid: [
         'YY AB 123',
@@ -11757,6 +12326,14 @@ describe('Validators', () => {
         'M ABC 123',
         'M AB 12345',
         'FS AB 1234 A',
+        'LRO-AB-1234',
+        'HRO ABC 123',
+        'HRO ABC 1234',
+        'LDK-AB-1234-E',
+        'ÖHR FA 123D',
+        'MZG-AB-123X',
+        'OBG-ABD-123',
+        'PAF-AB2-123',
       ],
     });
     test({
@@ -11913,35 +12490,194 @@ describe('Validators', () => {
         'ABC-123',
       ],
     });
+    test({
+      validator: 'isLicensePlate',
+      args: ['en-IN'],
+      valid: [
+        'MH 04 AD 0001',
+        'HR26DQ0001',
+        'WB-04-ZU-2001',
+        'KL 18 X 5800',
+        'DL 4 CAF 4856',
+        'KA-41CE-5289',
+        'GJ 04-AD 5822',
+      ],
+      invalid: ['mh04ad0045', 'invalidlicenseplate', '4578', '', 'GJ054GH4785'],
+    });
   });
   it('should validate VAT numbers', () => {
     test({
       validator: 'isVAT',
-      args: ['GB'],
+      args: ['AT'],
       valid: [
-        'GB999 9999 00',
-        'GB999 9999 96',
-        'GB999999999 999',
-        'GBGD000',
-        'GBGD499',
-        'GBHA500',
-        'GBHA999',
+        'ATU12345678',
+        'U12345678',
       ],
       invalid: [
-        'GB999999900',
-        'GB999999996',
-        'GB999 9999 97',
-        'GB999999999999',
-        'GB999999999 9999',
-        'GB9999999999 999',
-        'GBGD 000',
-        'GBGD 499',
-        'GBHA 500',
-        'GBHA 999',
-        'GBGD500',
-        'GBGD999',
-        'GBHA000',
-        'GBHA499',
+        'AT 12345678',
+        '12345678',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['BE'],
+      valid: [
+        'BE1234567890',
+        '1234567890',
+      ],
+      invalid: [
+        'BE 1234567890',
+        '123456789',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['BG'],
+      valid: [
+        'BG1234567890',
+        '1234567890',
+        'BG123456789',
+        '123456789',
+      ],
+      invalid: [
+        'BG 1234567890',
+        '12345678',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['HR'],
+      valid: [
+        'HR12345678901',
+        '12345678901',
+      ],
+      invalid: [
+        'HR 12345678901',
+        '1234567890',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['CY'],
+      valid: [
+        'CY123456789',
+        '123456789',
+      ],
+      invalid: [
+        'CY 123456789',
+        '12345678',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['CZ'],
+      valid: [
+        'CZ1234567890',
+        'CZ123456789',
+        'CZ12345678',
+        '1234567890',
+        '123456789',
+        '12345678',
+      ],
+      invalid: [
+        'CZ 123456789',
+        '1234567',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['DK'],
+      valid: [
+        'DK12345678',
+        '12345678',
+      ],
+      invalid: [
+        'DK 12345678',
+        '1234567',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['EE'],
+      valid: [
+        'EE123456789',
+        '123456789',
+      ],
+      invalid: [
+        'EE 123456789',
+        '12345678',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['FI'],
+      valid: [
+        'FI12345678',
+        '12345678',
+      ],
+      invalid: [
+        'FI 12345678',
+        '1234567',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['FR'],
+      valid: [
+        'FRAA123456789',
+        'AA123456789',
+      ],
+      invalid: [
+        'FR AA123456789',
+        '123456789',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['DE'],
+      valid: [
+        'DE123456789',
+        '123456789',
+      ],
+      invalid: [
+        'DE 123456789',
+        '12345678',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['EL'],
+      valid: [
+        'EL123456789',
+        '123456789',
+      ],
+      invalid: [
+        'EL 123456789',
+        '12345678',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['HU'],
+      valid: [
+        'HU12345678',
+        '12345678',
+      ],
+      invalid: [
+        'HU 12345678',
+        '1234567',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['IE'],
+      valid: [
+        'IE1234567AW',
+        '1234567AW',
+      ],
+      invalid: [
+        'IE 1234567',
+        '1234567',
       ],
     });
     test({
@@ -11961,17 +12697,696 @@ describe('Validators', () => {
     });
     test({
       validator: 'isVAT',
-      args: ['NL'],
+      args: ['LV'],
       valid: [
-        'NL123456789B10',
-        '123456789B10',
+        'LV12345678901',
+        '12345678901',
       ],
       invalid: [
-        'NL12345678 910',
-        'NL 123456789101',
-        'NL123456789B1',
-        'GB12345678910',
-        'NL123456789',
+        'LV 12345678901',
+        '1234567890',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['LT'],
+      valid: [
+        'LT123456789012',
+        '123456789012',
+        'LT12345678901',
+        '12345678901',
+        'LT1234567890',
+        '1234567890',
+        'LT123456789',
+        '123456789',
+      ],
+      invalid: [
+        'LT 123456789012',
+        '12345678',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['LU'],
+      valid: [
+        'LU12345678',
+        '12345678',
+      ],
+      invalid: [
+        'LU 12345678',
+        '1234567',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['MT'],
+      valid: [
+        'MT12345678',
+        '12345678',
+      ],
+      invalid: [
+        'MT 12345678',
+        '1234567',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['NL'],
+      valid: [
+        'NL123456789B10',
+        '123456789B10',
+      ],
+      invalid: [
+        'NL12345678 910',
+        'NL 123456789101',
+        'NL123456789B1',
+        'GB12345678910',
+        'NL123456789',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['PL'],
+      valid: [
+        'PL1234567890',
+        '1234567890',
+        'PL123-456-78-90',
+        '123-456-78-90',
+        'PL123-45-67-890',
+        '123-45-67-890',
+      ],
+      invalid: [
+        'PL 1234567890',
+        '123456789',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['PT'],
+      valid: [
+        'PT123456789',
+        '123456789',
+      ],
+      invalid: [
+        'PT 123456789',
+        '000000001',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['RO'],
+      valid: [
+        'RO1234567890',
+        '1234567890',
+        'RO12',
+        '12',
+      ],
+      invalid: [
+        'RO 12',
+        '1',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['SK'],
+      valid: [
+        'SK1234567890',
+        '1234567890',
+      ],
+      invalid: [
+        'SK 1234567890',
+        '123456789',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['SI'],
+      valid: [
+        'SI12345678',
+        '12345678',
+      ],
+      invalid: [
+        'SI 12345678',
+        '1234567',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['ES'],
+      valid: [
+        'ESA1234567A',
+        'A1234567A',
+      ],
+      invalid: [
+        'ES 1234567A',
+        '123456789',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['SE'],
+      valid: [
+        'SE123456789012',
+        '123456789012',
+      ],
+      invalid: [
+        'SE 123456789012',
+        '12345678901',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['AL'],
+      valid: [
+        'AL123456789A',
+        '123456789A',
+      ],
+      invalid: [
+        'AL 123456789A',
+        '123456789',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['MK'],
+      valid: [
+        'MK1234567890123',
+        '1234567890123',
+      ],
+      invalid: [
+        'MK 1234567890123',
+        '123456789012',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['AU'],
+      valid: [
+        'AU12345678901',
+        '12345678901',
+      ],
+      invalid: [
+        'AU 12345678901',
+        '1234567890',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['BY'],
+      valid: [
+        'УНП 123456789',
+        '123456789',
+      ],
+      invalid: [
+        'BY 123456789',
+        '12345678',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['CA'],
+      valid: [
+        'CA123456789',
+        '123456789',
+      ],
+      invalid: [
+        'CA 123456789',
+        '12345678',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['IS'],
+      valid: [
+        'IS123456',
+        '12345',
+      ],
+      invalid: [
+        'IS 12345',
+        '1234',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['IN'],
+      valid: [
+        'IN123456789012345',
+        '123456789012345',
+      ],
+      invalid: [
+        'IN 123456789012345',
+        '12345678901234',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['ID'],
+      valid: [
+        'ID123456789012345',
+        '123456789012345',
+        'ID12.345.678.9-012.345',
+        '12.345.678.9-012.345',
+      ],
+      invalid: [
+        'ID 123456789012345',
+        '12345678901234',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['IL'],
+      valid: [
+        'IL123456789',
+        '123456789',
+      ],
+      invalid: [
+        'IL 123456789',
+        '12345678',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['KZ'],
+      valid: [
+        'KZ123456789',
+        '123456789',
+      ],
+      invalid: [
+        'KZ 123456789',
+        '12345678',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['NZ'],
+      valid: [
+        'NZ123456789',
+        '123456789',
+      ],
+      invalid: [
+        'NZ 123456789',
+        '12345678',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['NG'],
+      valid: [
+        'NG123456789012',
+        '123456789012',
+        'NG12345678-9012',
+        '12345678-9012',
+      ],
+      invalid: [
+        'NG 123456789012',
+        '12345678901',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['NO'],
+      valid: [
+        'NO123456789MVA',
+        '123456789MVA',
+      ],
+      invalid: [
+        'NO 123456789MVA',
+        '123456789',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['PH'],
+      valid: [
+        'PH123456789012',
+        '123456789012',
+        'PH123 456 789 012',
+        '123 456 789 012',
+      ],
+      invalid: [
+        'PH 123456789012',
+        '12345678901',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['RU'],
+      valid: [
+        'RU1234567890',
+        '1234567890',
+        'RU123456789012',
+        '123456789012',
+      ],
+      invalid: [
+        'RU 123456789012',
+        '12345678901',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['SM'],
+      valid: [
+        'SM12345',
+        '12345',
+      ],
+      invalid: [
+        'SM 12345',
+        '1234',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['SA'],
+      valid: [
+        'SA123456789012345',
+        '123456789012345',
+      ],
+      invalid: [
+        'SA 123456789012345',
+        '12345678901234',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['RS'],
+      valid: [
+        'RS123456789',
+        '123456789',
+      ],
+      invalid: [
+        'RS 123456789',
+        '12345678',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['CH'],
+      valid: [
+        'CH123456TVA',
+        '123456TVA',
+        'CH123456789MWST',
+        '123456789MWST',
+        'CH123.456IVA',
+        '123.456IVA',
+        'CH123.456.789TVA',
+        '123.456.789TVA',
+      ],
+      invalid: [
+        'CH 123456',
+        '12345',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['TR'],
+      valid: [
+        'TR1234567890',
+        '1234567890',
+      ],
+      invalid: [
+        'TR 1234567890',
+        '123456789',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['UA'],
+      valid: [
+        'UA123456789012',
+        '123456789012',
+      ],
+      invalid: [
+        'UA 123456789012',
+        '12345678901',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['GB'],
+      valid: [
+        'GB999 9999 00',
+        'GB999 9999 96',
+        'GB999999999 999',
+        'GBGD000',
+        'GBGD499',
+        'GBHA500',
+        'GBHA999',
+      ],
+      invalid: [
+        'GB999999900',
+        'GB999999996',
+        'GB999 9999 97',
+        'GB999999999999',
+        'GB999999999 9999',
+        'GB9999999999 999',
+        'GBGD 000',
+        'GBGD 499',
+        'GBHA 500',
+        'GBHA 999',
+        'GBGD500',
+        'GBGD999',
+        'GBHA000',
+        'GBHA499',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['UZ'],
+      valid: [
+        'UZ123456789',
+        '123456789',
+      ],
+      invalid: [
+        'UZ 123456789',
+        '12345678',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['AR'],
+      valid: [
+        'AR12345678901',
+        '12345678901',
+      ],
+      invalid: [
+        'AR 12345678901',
+        '1234567890',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['BO'],
+      valid: [
+        'BO1234567',
+        '1234567',
+      ],
+      invalid: [
+        'BO 1234567',
+        '123456',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['BR'],
+      valid: [
+        'BR12.345.678/9012-34',
+        '12.345.678/9012-34',
+        'BR123.456.789-01',
+        '123.456.789-01',
+      ],
+      invalid: [
+        'BR 12.345.678/9012-34',
+        '12345678901234',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['CL'],
+      valid: [
+        'CL12345678-9',
+        '12345678-9',
+      ],
+      invalid: [
+        'CL 12345678-9',
+        '12345678',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['CO'],
+      valid: [
+        'CO1234567890',
+        '1234567890',
+      ],
+      invalid: [
+        'CO 1234567890',
+        '123456789',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['CR'],
+      valid: [
+        'CR123456789012',
+        '123456789012',
+        'CR123456789',
+        '123456789',
+      ],
+      invalid: [
+        'CR 123456789',
+        '12345678',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['EC'],
+      valid: [
+        'EC1234567890123',
+        '1234567890123',
+      ],
+      invalid: [
+        'EC 1234567890123',
+        '123456789012',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['SV'],
+      valid: [
+        'SV1234-567890-123-1',
+        '1234-567890-123-1',
+      ],
+      invalid: [
+        'SV 1234-567890-123-1',
+        '1234567890123',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['GT'],
+      valid: [
+        'GT1234567-8',
+        '1234567-8',
+      ],
+      invalid: [
+        'GT 1234567-8',
+        '1234567',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['HN'],
+      valid: [
+        'HN',
+      ],
+      invalid: [
+        'HN ',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['MX'],
+      valid: [
+        'MXABCD123456EFG',
+        'ABCD123456EFG',
+        'MXABC123456DEF',
+        'ABC123456DEF',
+      ],
+      invalid: [
+        'MX ABC123456EFG',
+        '123456',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['NI'],
+      valid: [
+        'NI123-456789-0123A',
+        '123-456789-0123A',
+      ],
+      invalid: [
+        'NI 123-456789-0123A',
+        '1234567890123',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['PA'],
+      valid: [
+        'PA',
+      ],
+      invalid: [
+        'PA ',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['PY'],
+      valid: [
+        'PY12345678-9',
+        '12345678-9',
+        'PY123456-7',
+        '123456-7',
+      ],
+      invalid: [
+        'PY 123456-7',
+        '123456',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['PE'],
+      valid: [
+        'PE12345678901',
+        '12345678901',
+      ],
+      invalid: [
+        'PE 12345678901',
+        '1234567890',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['DO'],
+      valid: [
+        'DO12345678901',
+        '12345678901',
+        'DO123-4567890-1',
+        '123-4567890-1',
+        'DO123456789',
+        '123456789',
+        'DO1-23-45678-9',
+        '1-23-45678-9',
+      ],
+      invalid: [
+        'DO 12345678901',
+        '1234567890',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['UY'],
+      valid: [
+        'UY123456789012',
+        '123456789012',
+      ],
+      invalid: [
+        'UY 123456789012',
+        '12345678901',
+      ],
+    });
+    test({
+      validator: 'isVAT',
+      args: ['VE'],
+      valid: [
+        'VEJ-123456789',
+        'J-123456789',
+        'VEJ-12345678-9',
+        'J-12345678-9',
+      ],
+      invalid: [
+        'VE J-123456789',
+        '12345678',
       ],
     });
     test({