Repository: validatorjs/validator.js
Pull request #2491 (2024-11-02 07:42:22)

feat(isBase64): improve base64 validation based on RFC4648
Add padding to the options list.
Update regexes to support validation with and without padding.
Update default options to maintain backward compatibility.
Add new tests to cover various scenarios.

In the previous version, only Base64 strings with padding and URL-safe Base64 strings without padding were validated. However, according to [RFC4648](https://datatracker.ietf.org/doc/html/rfc4648), both standard and URL-safe Base64 variants can be valid with or without padding. Therefore, this version supports four validation combinations—standard with padding, standard without padding, URL-safe with padding, and URL-safe without padding—while maintaining backward compatibility.

## Checklist

- [x] PR contains only changes related; no stray files, etc.
- [x] README updated (where applicable)
- [x] Tests written (where applicable)
- [x] References provided in PR (where applicable)



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 581908f..236fe7c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,57 @@
+# 13.15.0
+
+### New Features / Validators
+
+- [#2399](https://github.com/validatorjs/validator.js/pull/2399) `isISO31661Numeric` @RobinvanderVliet
+- [#2294](https://github.com/validatorjs/validator.js/pull/2294) `isULID` @arafatkn
+- [#2215](https://github.com/validatorjs/validator.js/pull/2215) `isISO15924` @xDivisionByZerox
+
+### Fixes, New Locales and Enhancements
+
+- `isMobilePhone`
+  - [#2395](https://github.com/validatorjs/validator.js/pull/2395) add `es-GT` locale @ignaciosuarezquilis
+  - [#1971](https://github.com/validatorjs/validator.js/pull/1971) improve `en-GB` locale @ihmpavel
+  - [#2359](https://github.com/validatorjs/validator.js/pull/2359) improve `uk-UA` locale @arttiger
+  - [#2350](https://github.com/validatorjs/validator.js/pull/2350) improve `ky-KG` locale @sadraliev
+  - [#2482](https://github.com/validatorjs/validator.js/pull/2482) improve `en-ZM` locale @sonikishan
+  - [#2362](https://github.com/validatorjs/validator.js/pull/2362) improve `en-GH` locale @NanaAb-116
+  - [#2500](https://github.com/validatorjs/validator.js/pull/2500) add `mk-MK` locale @eshward95 
+  - [#2534](https://github.com/validatorjs/validator.js/pull/2534) improve `sq-AL` locale @nichoola
+- [#2406](https://github.com/validatorjs/validator.js/pull/2406) `isBtcAddress` support all address formats and testnets @madoke
+- [#2339](https://github.com/validatorjs/validator.js/pull/2339) `isIBAN` improve `VG` regex @ST-DDT
+- [#2332](https://github.com/validatorjs/validator.js/pull/2332) `isISO4217` update currency codes @cbodtorf
+- [#2291](https://github.com/validatorjs/validator.js/pull/2291) `isIdentityCard` add `PK` locale @Daniyal-Qureshi
+- [#2414](https://github.com/validatorjs/validator.js/pull/2414) `isEmail` fix blacklist_chars @keshavlingala
+- [#2416](https://github.com/validatorjs/validator.js/pull/2416) `isInt`/`isFloat` handle undefined and null values @Daniyal-Qureshi
+- [#2415](https://github.com/validatorjs/validator.js/pull/2415) `isPostalCode` add `CO` locale @jorgevrgs
+- [#2404](https://github.com/validatorjs/validator.js/pull/2404) `isPassportNumber` export `passportNumberLocales` @derekparnell
+- [#2029](https://github.com/validatorjs/validator.js/pull/2029) `isRgbColor` add `allowSpaces` option @a-h-i
+- [#2421](https://github.com/validatorjs/validator.js/pull/2421) `isUUID` require valid variant field and require RFC9562 UUID in version `all` @broofa
+- [#2439](https://github.com/validatorjs/validator.js/pull/2439) `isURL` add `max_allowed_length` option @pinkiesky
+- [#2437](https://github.com/validatorjs/validator.js/pull/2437) `isEmail` reject starting with double quotes @code0emperor
+- [#2333](https://github.com/validatorjs/validator.js/pull/2333) `isLicensePlate` add `en-SG` locale @Sabarinathan07
+- [#2441](https://github.com/validatorjs/validator.js/pull/2441) `normalizeEmail` add `yandex_convert_yandexru` option @AayushGH
+- [#2443](https://github.com/validatorjs/validator.js/pull/2443) `isDate` return false instead of Error in certain cases @pano9000
+- [#2474](https://github.com/validatorjs/validator.js/pull/2474) `isLength` add `discreteLengths` option @Suven-p
+- [#2481](https://github.com/validatorjs/validator.js/pull/2481) `isDate` disallow mismatching length in `strictMode` @sonikishan
+- [#2492](https://github.com/validatorjs/validator.js/pull/2492) `isISO6346` set check digit to 0 if remainder is 10 @joelcuy
+- [#2493](https://github.com/validatorjs/validator.js/pull/2493) `isPostalCode` improve `BR` locale @ticmaisdev
+- [#2494](https://github.com/validatorjs/validator.js/pull/2494) `isEmail` allow regexp in `host_whitelist` and `host_blacklist` @weikangchia
+- [#2518](https://github.com/validatorjs/validator.js/pull/2518) `isIBAN` improve `IE`/`PS` regex @Tarasz57
+- **Doc fixes and others:**
+  - [#2402](https://github.com/validatorjs/validator.js/pull/2402) @BibhushanKarki
+  - [#2394](https://github.com/validatorjs/validator.js/pull/2394) @RobinvanderVliet
+  - [#1732](https://github.com/validatorjs/validator.js/pull/1732) @alguerocode
+  - [#2413](https://github.com/validatorjs/validator.js/pull/2413) @rubiin
+  - [#2408](https://github.com/validatorjs/validator.js/pull/2408) @profnandaa
+  - [#2411](https://github.com/validatorjs/validator.js/pull/2411) @rubiin
+  - [#2325](https://github.com/validatorjs/validator.js/pull/2325) @ovarn
+  - [#2418](https://github.com/validatorjs/validator.js/pull/2418) @ihmpavel
+  - [#2323](https://github.com/validatorjs/validator.js/pull/2323) @ovarn
+  - [#2423](https://github.com/validatorjs/validator.js/pull/2423) @rubiin
+  - [#2409](https://github.com/validatorjs/validator.js/pull/2409) @profnandaa
+  - [#2442](https://github.com/validatorjs/validator.js/pull/2442) @pano9000
+
 # 13.12.0
 
 ### New Features / Validators
diff --git a/README.md b/README.md
index c7518fd..27ae195 100644
--- a/README.md
+++ b/README.md
@@ -93,7 +93,7 @@ Validator                               | Description
 **isAscii(str)**                        | check if the string contains ASCII chars only.
 **isBase32(str [, options])**           | check if the 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][Crockford Base32].
 **isBase58(str)**                       | check if the string is base58 encoded.
-**isBase64(str [, options])**          | check if the string is base64 encoded. `options` is optional and defaults to `{ urlSafe: false }`<br/> when `urlSafe` is true it tests the given base64 encoded string is [url safe][Base64 URL Safe].
+**isBase64(str [, options])**          | check if the string is base64 encoded. `options` is optional and defaults to `{ urlSafe: false, padding: true }`<br/> when `urlSafe` is true default value for `padding` is false and it tests the given base64 encoded string is [url safe][Base64 URL Safe].
 **isBefore(str [, date])**              | check if the string is a date that is before the specified date.
 **isBIC(str)**                          | check if the string is a BIC (Bank Identification Code) or SWIFT code.
 **isBoolean(str [, options])**          | check if the string is a boolean.<br/>`options` is an object which defaults to `{ loose: false }`. If `loose` is set to false, the validator will strictly match ['true', 'false', '0', '1']. If `loose` is set to true, the validator will also match 'yes', 'no', and will match a valid boolean string of any case. (e.g.: ['true', 'True', 'TRUE']).
@@ -106,7 +106,7 @@ Validator                               | Description
 **isDecimal(str [, options])**             | check if the string represents a decimal number, such as 0.1, .3, 1.1, 1.00003, 4.0, etc.<br/><br/>`options` is an object which defaults to `{force_decimal: false, decimal_digits: '1,', locale: 'en-US'}`.<br/><br/>`locale` determines the decimal separator and is one of `['ar', 'ar-AE', 'ar-BH', 'ar-DZ', 'ar-EG', 'ar-IQ', 'ar-JO', 'ar-KW', 'ar-LB', 'ar-LY', 'ar-MA', 'ar-QA', 'ar-QM', 'ar-SA', 'ar-SD', 'ar-SY', 'ar-TN', 'ar-YE', 'bg-BG', 'cs-CZ', 'da-DK', 'de-DE', 'el-GR', 'en-AU', 'en-GB', 'en-HK', 'en-IN', 'en-NZ', 'en-US', 'en-ZA', 'en-ZM', 'eo', 'es-ES', 'fa', 'fa-AF', 'fa-IR', 'fr-FR', 'fr-CA', 'hu-HU', 'id-ID', 'it-IT', 'ku-IQ', 'nb-NO', 'nl-NL', 'nn-NO', 'pl-PL', 'pl-Pl', 'pt-BR', 'pt-PT', 'ru-RU', 'sl-SI', 'sr-RS', 'sr-RS@latin', 'sv-SE', 'tr-TR', 'uk-UA', 'vi-VN']`.<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 is divisible by another.
 **isEAN(str)**                          | check if the string is an [EAN (European Article Number)][European Article Number].
-**isEmail(str [, options])**            | check if the string is an email.<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, allow_underscores: false, domain_specific_validation: false, blacklisted_chars: '', host_blacklist: [] }`. If `allow_display_name` is set to true, the validator will also match `Display Name <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, email addresses without a TLD in their domain will also be matched. If `ignore_max_length` is set to true, the validator will not check for the standard max length of an email. If `allow_ip_domain` is set to true, the validator will allow IP addresses in the host part. If `domain_specific_validation` is true, some additional validation will be enabled, e.g. disallowing certain syntactically valid email addresses that are rejected by Gmail. If `blacklisted_chars` receives a string, then the validator will reject emails that include any of the characters in the string, in the name part. If `host_blacklist` is set to an array of strings and the part of the email after the `@` symbol matches one of the strings defined in it, the validation fails. If `host_whitelist` is set to an array of strings and the part of the email after the `@` symbol matches none of the strings defined in it, the validation fails.
+**isEmail(str [, options])**            | check if the string is an email.<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, allow_underscores: false, domain_specific_validation: false, blacklisted_chars: '', host_blacklist: [] }`. If `allow_display_name` is set to true, the validator will also match `Display Name <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, email addresses without a TLD in their domain will also be matched. If `ignore_max_length` is set to true, the validator will not check for the standard max length of an email. If `allow_ip_domain` is set to true, the validator will allow IP addresses in the host part. If `domain_specific_validation` is true, some additional validation will be enabled, e.g. disallowing certain syntactically valid email addresses that are rejected by Gmail. If `blacklisted_chars` receives a string, then the validator will reject emails that include any of the characters in the string, in the name part. If `host_blacklist` is set to an array of strings or regexp, and the part of the email after the `@` symbol matches one of the strings defined in it, the validation fails. If `host_whitelist` is set to an array of strings or regexp, and the part of the email after the `@` symbol matches none of the strings defined in it, the validation fails.
 **isEmpty(str [, options])**            | check if the string has a length of zero.<br/><br/>`options` is an object which defaults to `{ ignore_whitespace: false }`.
 **isEthereumAddress(str)**              | check if the string is an [Ethereum][Ethereum] address. Does not validate address checksums.
 **isFloat(str [, options])**            | check if the string is a float.<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` determines the decimal separator and is one of `['ar', 'ar-AE', 'ar-BH', 'ar-DZ', 'ar-EG', 'ar-IQ', 'ar-JO', 'ar-KW', 'ar-LB', 'ar-LY', 'ar-MA', 'ar-QA', 'ar-QM', 'ar-SA', 'ar-SD', 'ar-SY', 'ar-TN', 'ar-YE', 'bg-BG', 'cs-CZ', 'da-DK', 'de-DE', 'en-AU', 'en-GB', 'en-HK', 'en-IN', 'en-NZ', 'en-US', 'en-ZA', 'en-ZM', 'eo', 'es-ES', 'fr-CA', 'fr-FR', 'hu-HU', 'it-IT', 'nb-NO', 'nl-NL', 'nn-NO', 'pl-PL', 'pt-BR', 'pt-PT', 'ru-RU', 'sl-SI', 'sr-RS', 'sr-RS@latin', 'sv-SE', 'tr-TR', 'uk-UA']`. Locale list is `validator.isFloatLocales`.
@@ -123,13 +123,14 @@ Validator                               | Description
 **isIMEI(str [, options]))**                         | check if the string is a valid [IMEI number][IMEI]. IMEI should be of format `###############` or `##-######-######-#`.<br/><br/>`options` is an object which can contain the keys `allow_hyphens`. Defaults to first format. If `allow_hyphens` is set to true, the validator will validate the second format.
 **isIn(str, values)**                   | check if the string is in an array of allowed values.
 **isInt(str [, options])**              | check if the string is an integer.<br/><br/>`options` is an object which can contain the keys `min` and/or `max` to check the integer is within boundaries (e.g. `{ min: 10, max: 99 }`). `options` can also contain the key `allow_leading_zeroes`, which when set to false will disallow integer values with leading zeroes (e.g. `{ allow_leading_zeroes: false }`). Finally, `options` can contain the keys `gt` and/or `lt` which will enforce integers being greater than or less than, respectively, the value provided (e.g. `{gt: 1, lt: 4}` for a number between 1 and 4).
-**isIP(str [, version])**               | check if the string is an IP (version 4 or 6).
+**isIP(str [, options])**               | check if the string is an IP address (version 4 or 6).<br/><br/>`options` is an object that defaults to `{ version: '' }`.<br/><br/>**Options:**<br/>`version`: defines which IP version to compare to. Accepted values: `4`, `6`, `'4'`, `'6'`.
 **isIPRange(str [, version])**          | check if the string is an IP Range (version 4 or 6).
 **isISBN(str [, options])**             | check if the string is an [ISBN][ISBN].<br/><br/>`options` is an object that has no default.<br/>**Options:**<br/>`version`: ISBN version to compare to. Accepted values are '10' and '13'. If none provided, both will be tested.
 **isISIN(str)**                         | check if the string is an [ISIN][ISIN] (stock/security identifier).
 **isISO6346(str)**                      | check if the string is a valid [ISO 6346](https://en.wikipedia.org/wiki/ISO_6346) shipping container identification.
 **isISO6391(str)**                      | check if the string is a valid [ISO 639-1][ISO 639-1] language code.
 **isISO8601(str [, options])**          | check if the string is a valid [ISO 8601][ISO 8601] date. <br/>`options` is an object which defaults to `{ strict: false, strictSeparator: false }`. If `strict` is true, date strings with invalid dates like `2009-02-29` will be invalid. If `strictSeparator` is true, date strings with date and time separated by anything other than a T will be invalid.
+**isISO15924(str)**               | check if the string is a valid [ISO 15924][ISO 15924] officially assigned script code.
 **isISO31661Alpha2(str)**               | check if the string is a valid [ISO 3166-1 alpha-2][ISO 3166-1 alpha-2] officially assigned country code.
 **isISO31661Alpha3(str)**               | check if the string is a valid [ISO 3166-1 alpha-3][ISO 3166-1 alpha-3] officially assigned country code.
 **isISO31661Numeric(str)**              | check if the string is a valid [ISO 3166-1 numeric][ISO 3166-1 numeric] officially assigned country code.
@@ -149,7 +150,7 @@ Validator                               | Description
 **isMailtoURI(str, [, options])**                      | check if the string is a [Mailto URI format][Mailto URI Format].<br/><br/>`options` is an object of validating emails inside the URI (check `isEmail`s options for details).
 **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][MIME 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-EH', 'ar-IQ', 'ar-JO', 'ar-KW', 'ar-PS', 'ar-SA', 'ar-SD', 'ar-SY', 'ar-TN', 'ar-YE', 'az-AZ', 'az-LB', 'az-LY', 'be-BY', 'bg-BG', 'bn-BD', 'bs-BA', 'ca-AD', 'cs-CZ', 'da-DK', 'de-AT', 'de-CH', 'de-DE', 'de-LU', 'dv-MV', 'dz-BT', 'el-CY', 'el-GR', 'en-AG', 'en-AI', 'en-AU', 'en-BM', 'en-BS', 'en-BW', 'en-CA', 'en-GB', 'en-GG', 'en-GH', 'en-GY', 'en-HK', 'en-IE', 'en-IN', 'en-JM', 'en-KE', 'en-KI', 'en-KN', 'en-LS', 'en-MO', 'en-MT', 'en-MU', 'en-MW', 'en-NG', 'en-NZ', 'en-PG', 'en-PH', 'en-PK', 'en-RW', 'en-SG', 'en-SL', 'en-SS', 'en-TZ', 'en-UG', 'en-US', 'en-ZA', 'en-ZM', 'en-ZW', 'es-AR', 'es-BO', 'es-CL', 'es-CO', 'es-CR', 'es-CU', 'es-DO', 'es-EC', 'es-ES', 'es-GT','es-HN', 'es-MX', 'es-NI', 'es-PA', 'es-PE', 'es-PY', 'es-SV', 'es-UY', 'es-VE', 'et-EE', 'fa-AF', 'fa-IR', 'fi-FI', 'fj-FJ', 'fo-FO', 'fr-BE', 'fr-BF', 'fr-BJ', 'fr-CD', 'fr-CF', 'fr-FR', 'fr-GF', 'fr-GP', 'fr-MQ', 'fr-PF', 'fr-RE', 'fr-WF', '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', 'mn-MN', 'ms-MY', 'my-MM', 'mz-MZ', 'nb-NO', 'ne-NP', 'nl-AW', 'nl-BE', 'nl-NL', 'nn-NO', 'pl-PL', 'pt-AO', 'pt-BR', 'pt-PT', 'ro-Md', 'ro-RO', 'ru-RU', 'si-LK', 'sk-SK', 'sl-SI', 'so-SO', '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']` 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-SD', 'ar-SY', 'ar-TN', 'ar-YE', 'az-AZ', 'az-LB', 'az-LY', 'be-BY', 'bg-BG', 'bn-BD', 'bs-BA', 'ca-AD', 'cs-CZ', 'da-DK', 'de-AT', 'de-CH', 'de-DE', 'de-LU', 'dv-MV', 'dz-BT', 'el-CY', 'el-GR', 'en-AG', 'en-AI', 'en-AU', 'en-BM', 'en-BS', 'en-BW', 'en-CA', 'en-GB', 'en-GG', 'en-GH', 'en-GY', 'en-HK', 'en-IE', 'en-IN', 'en-JM', 'en-KE', 'en-KI', 'en-KN', 'en-LS', 'en-MO', 'en-MT', 'en-MU', 'en-MW', 'en-NG', 'en-NZ', 'en-PG', 'en-PH', 'en-PK', 'en-RW', 'en-SG', 'en-SL', 'en-SS', 'en-TZ', 'en-UG', 'en-US', 'en-ZA', 'en-ZM', 'en-ZW', 'es-AR', 'es-BO', 'es-CL', 'es-CO', 'es-CR', 'es-CU', 'es-DO', 'es-EC', 'es-ES', 'es-GT','es-HN', 'es-MX', 'es-NI', 'es-PA', 'es-PE', 'es-PY', 'es-SV', 'es-UY', 'es-VE', 'et-EE', 'fa-AF', 'fa-IR', 'fi-FI', 'fj-FJ', 'fo-FO', 'fr-BE', 'fr-BF', 'fr-BJ', 'fr-CD', 'fr-CF', 'fr-FR', 'fr-GF', 'fr-GP', 'fr-MQ', 'fr-PF', 'fr-RE', 'fr-WF', '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', 'mn-MN', 'mk-MK', 'ms-MY', 'my-MM', 'mz-MZ', 'nb-NO', 'ne-NP', 'nl-AW', 'nl-BE', 'nl-NL', 'nn-NO', 'pl-PL', 'pt-AO', 'pt-BR', 'pt-PT', 'ro-Md', 'ro-RO', 'ru-RU', 'si-LK', 'sk-SK', 'sl-SI', 'so-SO', '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']` 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` determines the decimal separator and is one of `['ar', 'ar-AE', 'ar-BH', 'ar-DZ', 'ar-EG', 'ar-IQ', 'ar-JO', 'ar-KW', 'ar-LB', 'ar-LY', 'ar-MA', 'ar-QA', 'ar-QM', 'ar-SA', 'ar-SD', 'ar-SY', 'ar-TN', 'ar-YE', 'bg-BG', 'cs-CZ', 'da-DK', 'de-DE', 'en-AU', 'en-GB', 'en-HK', 'en-IN', 'en-NZ', 'en-US', 'en-ZA', 'en-ZM', 'eo', 'es-ES', 'fr-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']`.
@@ -252,6 +253,7 @@ This project is licensed under the [MIT](LICENSE). See the [LICENSE](LICENSE) fi
 [ISIN]: https://en.wikipedia.org/wiki/International_Securities_Identification_Number
 [ISO 639-1]: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
 [ISO 8601]: https://en.wikipedia.org/wiki/ISO_8601
+[ISO 15924]: https://en.wikipedia.org/wiki/ISO_15924
 [ISO 3166-1 alpha-2]: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
 [ISO 3166-1 alpha-3]: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3
 [ISO 3166-1 numeric]: https://en.wikipedia.org/wiki/ISO_3166-1_numeric
diff --git a/package.json b/package.json
index 854657f..49ac0f8 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "validator",
   "description": "String validation and sanitization",
-  "version": "13.12.0",
+  "version": "13.15.0",
   "sideEffects": false,
   "homepage": "https://github.com/validatorjs/validator.js",
   "files": [
diff --git a/src/index.js b/src/index.js
index 1bc65a8..c476745 100644
--- a/src/index.js
+++ b/src/index.js
@@ -94,6 +94,7 @@ import { isISO6346, isFreightContainerID } from './lib/isISO6346';
 import isISO6391 from './lib/isISO6391';
 import isISO8601 from './lib/isISO8601';
 import isRFC3339 from './lib/isRFC3339';
+import isISO15924 from './lib/isISO15924';
 import isISO31661Alpha2 from './lib/isISO31661Alpha2';
 import isISO31661Alpha3 from './lib/isISO31661Alpha3';
 import isISO31661Numeric from './lib/isISO31661Numeric';
@@ -129,7 +130,7 @@ import isStrongPassword from './lib/isStrongPassword';
 
 import isVAT from './lib/isVAT';
 
-const version = '13.12.0';
+const version = '13.15.0';
 
 const validator = {
   version,
@@ -211,6 +212,7 @@ const validator = {
   isFreightContainerID,
   isISO6391,
   isISO8601,
+  isISO15924,
   isRFC3339,
   isISO31661Alpha2,
   isISO31661Alpha3,
diff --git a/src/lib/isBase64.js b/src/lib/isBase64.js
index 02dead0..7eb3a5b 100644
--- a/src/lib/isBase64.js
+++ b/src/lib/isBase64.js
@@ -1,28 +1,23 @@
 import assertString from './util/assertString';
 import merge from './util/merge';
 
-const notBase64 = /[^A-Z0-9+\/=]/i;
-const urlSafeBase64 = /^[A-Z0-9_\-]*$/i;
-
-const defaultBase64Options = {
-  urlSafe: false,
-};
+const base64WithPadding = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$/;
+const base64WithoutPadding = /^[A-Za-z0-9+/]+$/;
+const base64UrlWithPadding = /^(?:[A-Za-z0-9_-]{4})*(?:[A-Za-z0-9_-]{2}==|[A-Za-z0-9_-]{3}=|[A-Za-z0-9_-]{4})$/;
+const base64UrlWithoutPadding = /^[A-Za-z0-9_-]+$/;
 
 export default function isBase64(str, options) {
   assertString(str);
-  options = merge(options, defaultBase64Options);
-  const len = str.length;
+  options = merge(options, { urlSafe: false, padding: !options?.urlSafe });
 
-  if (options.urlSafe) {
-    return urlSafeBase64.test(str);
-  }
+  if (str === '') return true;
 
-  if (len % 4 !== 0 || notBase64.test(str)) {
-    return false;
+  let regex;
+  if (options.urlSafe) {
+    regex = options.padding ? base64UrlWithPadding : base64UrlWithoutPadding;
+  } else {
+    regex = options.padding ? base64WithPadding : base64WithoutPadding;
   }
 
-  const firstPaddingChar = str.indexOf('=');
-  return firstPaddingChar === -1 ||
-    firstPaddingChar === len - 1 ||
-    (firstPaddingChar === len - 2 && str[len - 1] === '=');
+  return (!options.padding || str.length % 4 === 0) && regex.test(str);
 }
diff --git a/src/lib/isEmail.js b/src/lib/isEmail.js
index 1aceca3..abe4650 100644
--- a/src/lib/isEmail.js
+++ b/src/lib/isEmail.js
@@ -1,4 +1,5 @@
 import assertString from './util/assertString';
+import checkHost from './util/checkHost';
 
 import isByteLength from './isByteLength';
 import isFQDN from './isFQDN';
@@ -60,7 +61,6 @@ function validateDisplayName(display_name) {
   return true;
 }
 
-
 export default function isEmail(str, options) {
   assertString(str);
   options = merge(options, default_email_options);
@@ -97,11 +97,11 @@ export default function isEmail(str, options) {
   const domain = parts.pop();
   const lower_domain = domain.toLowerCase();
 
-  if (options.host_blacklist.includes(lower_domain)) {
+  if (options.host_blacklist.length > 0 && checkHost(lower_domain, options.host_blacklist)) {
     return false;
   }
 
-  if (options.host_whitelist.length > 0 && !options.host_whitelist.includes(lower_domain)) {
+  if (options.host_whitelist.length > 0 && !checkHost(lower_domain, options.host_whitelist)) {
     return false;
   }
 
diff --git a/src/lib/isIBAN.js b/src/lib/isIBAN.js
index 28f39be..94ac67c 100644
--- a/src/lib/isIBAN.js
+++ b/src/lib/isIBAN.js
@@ -39,7 +39,7 @@ const ibanRegexThroughCountryCode = {
   GT: /^(GT[0-9]{2})[A-Z0-9]{4}[A-Z0-9]{20}$/,
   HR: /^(HR[0-9]{2})\d{17}$/,
   HU: /^(HU[0-9]{2})\d{24}$/,
-  IE: /^(IE[0-9]{2})[A-Z0-9]{4}\d{14}$/,
+  IE: /^(IE[0-9]{2})[A-Z]{4}\d{14}$/,
   IL: /^(IL[0-9]{2})\d{19}$/,
   IQ: /^(IQ[0-9]{2})[A-Z]{4}\d{15}$/,
   IR: /^(IR[0-9]{2})0\d{2}0\d{18}$/,
@@ -67,7 +67,7 @@ const ibanRegexThroughCountryCode = {
   NO: /^(NO[0-9]{2})\d{11}$/,
   PK: /^(PK[0-9]{2})[A-Z0-9]{4}\d{16}$/,
   PL: /^(PL[0-9]{2})\d{24}$/,
-  PS: /^(PS[0-9]{2})[A-Z0-9]{4}\d{21}$/,
+  PS: /^(PS[0-9]{2})[A-Z]{4}[A-Z0-9]{21}$/,
   PT: /^(PT[0-9]{2})\d{21}$/,
   QA: /^(QA[0-9]{2})[A-Z]{4}[A-Z0-9]{21}$/,
   RO: /^(RO[0-9]{2})[A-Z]{4}[A-Z0-9]{16}$/,
diff --git a/src/lib/isIP.js b/src/lib/isIP.js
index 40ca19a..dca52d2 100644
--- a/src/lib/isIP.js
+++ b/src/lib/isIP.js
@@ -44,17 +44,24 @@ const IPv6AddressRegExp = new RegExp('^(' +
   `(?::((?::${IPv6SegmentFormat}){0,5}:${IPv4AddressFormat}|(?::${IPv6SegmentFormat}){1,7}|:))` +
   ')(%[0-9a-zA-Z-.:]{1,})?$');
 
-export default function isIP(str, version = '') {
-  assertString(str);
-  version = String(version);
+export default function isIP(ipAddress, options = {}) {
+  assertString(ipAddress);
+
+  // accessing 'arguments' for backwards compatibility: isIP(ipAddress [, version])
+  // eslint-disable-next-line prefer-rest-params
+  const version = (typeof options === 'object' ? options.version : arguments[1]) || '';
+
   if (!version) {
-    return isIP(str, 4) || isIP(str, 6);
+    return isIP(ipAddress, { version: 4 }) || isIP(ipAddress, { version: 6 });
   }
-  if (version === '4') {
-    return IPv4AddressRegExp.test(str);
+
+  if (version.toString() === '4') {
+    return IPv4AddressRegExp.test(ipAddress);
   }
-  if (version === '6') {
-    return IPv6AddressRegExp.test(str);
+
+  if (version.toString() === '6') {
+    return IPv6AddressRegExp.test(ipAddress);
   }
+
   return false;
 }
diff --git a/src/lib/isISO15924.js b/src/lib/isISO15924.js
new file mode 100644
index 0000000..30f9e4a
--- /dev/null
+++ b/src/lib/isISO15924.js
@@ -0,0 +1,37 @@
+import assertString from './util/assertString';
+
+// from https://www.unicode.org/iso15924/iso15924-codes.html
+const validISO15924Codes = new Set([
+  'Adlm', 'Afak', 'Aghb', 'Ahom', 'Arab', 'Aran', 'Armi', 'Armn', 'Avst',
+  'Bali', 'Bamu', 'Bass', 'Batk', 'Beng', 'Bhks', 'Blis', 'Bopo', 'Brah', 'Brai', 'Bugi', 'Buhd',
+  'Cakm', 'Cans', 'Cari', 'Cham', 'Cher', 'Chis', 'Chrs', 'Cirt', 'Copt', 'Cpmn', 'Cprt', 'Cyrl', 'Cyrs',
+  'Deva', 'Diak', 'Dogr', 'Dsrt', 'Dupl',
+  'Egyd', 'Egyh', 'Egyp', 'Elba', 'Elym', 'Ethi',
+  'Gara', 'Geok', 'Geor', 'Glag', 'Gong', 'Gonm', 'Goth', 'Gran', 'Grek', 'Gujr', 'Gukh', 'Guru',
+  'Hanb', 'Hang', 'Hani', 'Hano', 'Hans', 'Hant', 'Hatr', 'Hebr', 'Hira', 'Hluw', 'Hmng', 'Hmnp', 'Hrkt', 'Hung',
+  'Inds', 'Ital',
+  'Jamo', 'Java', 'Jpan', 'Jurc',
+  'Kali', 'Kana', 'Kawi', 'Khar', 'Khmr', 'Khoj', 'Kitl', 'Kits', 'Knda', 'Kore', 'Kpel', 'Krai', 'Kthi',
+  'Lana', 'Laoo', 'Latf', 'Latg', 'Latn', 'Leke', 'Lepc', 'Limb', 'Lina', 'Linb', 'Lisu', 'Loma', 'Lyci', 'Lydi',
+  'Mahj', 'Maka', 'Mand', 'Mani', 'Marc', 'Maya', 'Medf', 'Mend', 'Merc', 'Mero', 'Mlym', 'Modi', 'Mong', 'Moon', 'Mroo', 'Mtei', 'Mult', 'Mymr',
+  'Nagm', 'Nand', 'Narb', 'Nbat', 'Newa', 'Nkdb', 'Nkgb', 'Nkoo', 'Nshu',
+  'Ogam', 'Olck', 'Onao', 'Orkh', 'Orya', 'Osge', 'Osma', 'Ougr',
+  'Palm', 'Pauc', 'Pcun', 'Pelm', 'Perm', 'Phag', 'Phli', 'Phlp', 'Phlv', 'Phnx', 'Plrd', 'Piqd', 'Prti', 'Psin',
+  'Qaaa', 'Qaab', 'Qaac', 'Qaad', 'Qaae', 'Qaaf', 'Qaag', 'Qaah', 'Qaai', 'Qaaj', 'Qaak', 'Qaal', 'Qaam', 'Qaan', 'Qaao', 'Qaap', 'Qaaq', 'Qaar', 'Qaas', 'Qaat', 'Qaau', 'Qaav', 'Qaaw', 'Qaax', 'Qaay', 'Qaaz', 'Qaba', 'Qabb', 'Qabc', 'Qabd', 'Qabe', 'Qabf', 'Qabg', 'Qabh', 'Qabi', 'Qabj', 'Qabk', 'Qabl', 'Qabm', 'Qabn', 'Qabo', 'Qabp', 'Qabq', 'Qabr', 'Qabs', 'Qabt', 'Qabu', 'Qabv', 'Qabw', 'Qabx',
+  'Ranj', 'Rjng', 'Rohg', 'Roro', 'Runr',
+  'Samr', 'Sara', 'Sarb', 'Saur', 'Sgnw', 'Shaw', 'Shrd', 'Shui', 'Sidd', 'Sidt', 'Sind', 'Sinh', 'Sogd', 'Sogo', 'Sora', 'Soyo', 'Sund', 'Sunu', 'Sylo', 'Syrc', 'Syre', 'Syrj', 'Syrn',
+  'Tagb', 'Takr', 'Tale', 'Talu', 'Taml', 'Tang', 'Tavt', 'Tayo', 'Telu', 'Teng', 'Tfng', 'Tglg', 'Thaa', 'Thai', 'Tibt', 'Tirh', 'Tnsa', 'Todr', 'Tols', 'Toto', 'Tutg',
+  'Ugar',
+  'Vaii', 'Visp', 'Vith',
+  'Wara', 'Wcho', 'Wole',
+  'Xpeo', 'Xsux',
+  'Yezi', 'Yiii',
+  'Zanb', 'Zinh', 'Zmth', 'Zsye', 'Zsym', 'Zxxx', 'Zyyy', 'Zzzz',
+]);
+
+export default function isISO15924(str) {
+  assertString(str);
+  return validISO15924Codes.has(str);
+}
+
+export const ScriptCodes = validISO15924Codes;
diff --git a/src/lib/isISO6346.js b/src/lib/isISO6346.js
index 0cb657e..2c28c11 100644
--- a/src/lib/isISO6346.js
+++ b/src/lib/isISO6346.js
@@ -27,7 +27,8 @@ export function isISO6346(str) {
       } else sum += str[i] * (2 ** i);
     }
 
-    const checkSumDigit = sum % 11;
+    let checkSumDigit = sum % 11;
+    if (checkSumDigit === 10) checkSumDigit = 0;
     return Number(str[str.length - 1]) === checkSumDigit;
   }
 
diff --git a/src/lib/isMobilePhone.js b/src/lib/isMobilePhone.js
index 53c0e37..b45bd64 100644
--- a/src/lib/isMobilePhone.js
+++ b/src/lib/isMobilePhone.js
@@ -41,7 +41,7 @@ const phones = {
   'en-BS': /^(\+?1[-\s]?|0)?\(?242\)?[-\s]?\d{3}[-\s]?\d{4}$/,
   'en-GB': /^(\+?44|0)7[1-9]\d{8}$/,
   'en-GG': /^(\+?44|0)1481\d{6}$/,
-  'en-GH': /^(\+233|0)(20|50|24|54|27|57|26|56|23|28|55|59)\d{7}$/,
+  'en-GH': /^(\+233|0)(20|50|24|54|27|57|26|56|23|53|28|55|59)\d{7}$/,
   'en-GY': /^(\+592|0)6\d{6}$/,
   'en-HK': /^(\+?852[-\s]?)?[456789]\d{3}[-\s]?\d{4}$/,
   'en-MO': /^(\+?853[-\s]?)?[6]\d{3}[-\s]?\d{4}$/,
@@ -144,7 +144,7 @@ const phones = {
   'sl-SI': /^(\+386\s?|0)(\d{1}\s?\d{3}\s?\d{2}\s?\d{2}|\d{2}\s?\d{3}\s?\d{3})$/,
   'sk-SK': /^(\+?421)? ?[1-9][0-9]{2} ?[0-9]{3} ?[0-9]{3}$/,
   'so-SO': /^(\+?252|0)((6[0-9])\d{7}|(7[1-9])\d{7})$/,
-  'sq-AL': /^(\+355|0)6[789]\d{6}$/,
+  'sq-AL': /^(\+355|0)6[2-9]\d{7}$/,
   'sr-RS': /^(\+3816|06)[- \d]{5,9}$/,
   'sv-SE': /^(\+?46|0)[\s\-]?7[\s\-]?[02369]([\s\-]?\d){7}$/,
   'tg-TJ': /^(\+?992)?[5][5]\d{7}$/,
@@ -160,6 +160,7 @@ const phones = {
   '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})$/,
+  'mk-MK': /^(\+?389|0)?((?:2[2-9]\d{6}|(?:3[1-4]|4[2-8])\d{6}|500\d{5}|5[2-9]\d{6}|7[0-9][2-9]\d{5}|8[1-9]\d{6}|800\d{5}|8009\d{4}))$/,
 };
 /* eslint-enable max-len */
 
diff --git a/src/lib/isPassportNumber.js b/src/lib/isPassportNumber.js
index 92282ac..33481e8 100644
--- a/src/lib/isPassportNumber.js
+++ b/src/lib/isPassportNumber.js
@@ -16,7 +16,7 @@ const passportRegexByCountryCode = {
   BG: /^\d{9}$/, // BULGARIA
   BR: /^[A-Z]{2}\d{6}$/, // BRAZIL
   BY: /^[A-Z]{2}\d{7}$/, // BELARUS
-  CA: /^[A-Z]{2}\d{6}$/, // CANADA
+  CA: /^[A-Z]{2}\d{6}$|^[A-Z]\d{6}[A-Z]{2}$/, // CANADA
   CH: /^[A-Z]\d{7}$/, // SWITZERLAND
   CN: /^G\d{8}$|^E(?![IO])[A-Z0-9]\d{7}$/, // CHINA [G=Ordinary, E=Electronic] followed by 8-digits, or E followed by any UPPERCASE letter (except I and O) followed by 7 digits
   CY: /^[A-Z](\d{6}|\d{8})$/, // CYPRUS
diff --git a/src/lib/isPostalCode.js b/src/lib/isPostalCode.js
index 1036562..cadf393 100644
--- a/src/lib/isPostalCode.js
+++ b/src/lib/isPostalCode.js
@@ -14,7 +14,7 @@ const patterns = {
   BA: /^([7-8]\d{4}$)/,
   BE: fourDigit,
   BG: fourDigit,
-  BR: /^\d{5}-\d{3}$/,
+  BR: /^\d{5}-?\d{3}$/,
   BY: /^2[1-4]\d{4}$/,
   CA: /^[ABCEGHJKLMNPRSTVXY]\d[ABCEGHJ-NPRSTV-Z][\s\-]?\d[ABCEGHJ-NPRSTV-Z]\d$/i,
   CH: fourDigit,
diff --git a/src/lib/isURL.js b/src/lib/isURL.js
index 7529f4b..9bfafbf 100644
--- a/src/lib/isURL.js
+++ b/src/lib/isURL.js
@@ -1,4 +1,5 @@
 import assertString from './util/assertString';
+import checkHost from './util/checkHost';
 
 import isFQDN from './isFQDN';
 import isIP from './isIP';
@@ -38,20 +39,6 @@ const default_url_options = {
 
 const wrapped_ipv6 = /^\[([^\]]+)\](?::([0-9]+))?$/;
 
-function isRegExp(obj) {
-  return Object.prototype.toString.call(obj) === '[object RegExp]';
-}
-
-function checkHost(host, matches) {
-  for (let i = 0; i < matches.length; i++) {
-    let match = matches[i];
-    if (host === match || (isRegExp(match) && match.test(host))) {
-      return true;
-    }
-  }
-  return false;
-}
-
 export default function isURL(url, options) {
   assertString(url);
   if (!url || /[\s<>]/.test(url)) {
diff --git a/src/lib/util/checkHost.js b/src/lib/util/checkHost.js
new file mode 100644
index 0000000..ed1ddde
--- /dev/null
+++ b/src/lib/util/checkHost.js
@@ -0,0 +1,13 @@
+function isRegExp(obj) {
+  return Object.prototype.toString.call(obj) === '[object RegExp]';
+}
+
+export default function checkHost(host, matches) {
+  for (let i = 0; i < matches.length; i++) {
+    let match = matches[i];
+    if (host === match || (isRegExp(match) && match.test(host))) {
+      return true;
+    }
+  }
+  return false;
+}


Test patch:
diff --git a/test/validators.test.js b/test/validators.test.js
index 08d76f8..ce2d41b 100644
--- a/test/validators.test.js
+++ b/test/validators.test.js
@@ -1,9 +1,7 @@
 import assert from 'assert';
 import fs from 'fs';
 import timezone_mock from 'timezone-mock';
-import { format } from 'util';
 import vm from 'vm';
-import validator from '../src/index';
 import test from './testFunctions';
 
 let validator_js = fs.readFileSync(require.resolve('../validator.js')).toString();
@@ -325,6 +323,25 @@ describe('Validators', () => {
     });
   });
 
+  it('should allow regular expressions in the host blacklist of isEmail', () => {
+    test({
+      validator: 'isEmail',
+      args: [{
+        host_blacklist: ['bar.com', 'foo.com', /\.foo\.com$/],
+      }],
+      valid: [
+        'email@foobar.com',
+        'email@foo.bar.com',
+        'email@qux.com',
+      ],
+      invalid: [
+        'email@bar.com',
+        'email@foo.com',
+        'email@a.b.c.foo.com',
+      ],
+    });
+  });
+
   it('should validate only email addresses with whitelisted domains', () => {
     test({
       validator: 'isEmail',
@@ -341,6 +358,25 @@ describe('Validators', () => {
     });
   });
 
+  it('should allow regular expressions in the host whitelist of isEmail', () => {
+    test({
+      validator: 'isEmail',
+      args: [{
+        host_whitelist: ['bar.com', 'foo.com', /\.foo\.com$/],
+      }],
+      valid: [
+        'email@bar.com',
+        'email@foo.com',
+        'email@a.b.c.foo.com',
+      ],
+      invalid: [
+        'email@foobar.com',
+        'email@foo.bar.com',
+        'email@qux.com',
+      ],
+    });
+  });
+
   it('should validate URLs', () => {
     test({
       validator: 'isURL',
@@ -1009,136 +1045,6 @@ describe('Validators', () => {
     });
   });
 
-  it('should validate IP addresses', () => {
-    test({
-      validator: 'isIP',
-      valid: [
-        '127.0.0.1',
-        '0.0.0.0',
-        '255.255.255.255',
-        '1.2.3.4',
-        '::1',
-        '2001:db8:0000:1:1:1:1:1',
-        '2001:db8:3:4::192.0.2.33',
-        '2001:41d0:2:a141::1',
-        '::ffff:127.0.0.1',
-        '::0000',
-        '0000::',
-        '1::',
-        '1111:1:1:1:1:1:1:1',
-        'fe80::a6db:30ff:fe98:e946',
-        '::',
-        '::8',
-        '::ffff:127.0.0.1',
-        '::ffff:255.255.255.255',
-        '::ffff:0:255.255.255.255',
-        '::2:3:4:5:6:7:8',
-        '::255.255.255.255',
-        '0:0:0:0:0:ffff:127.0.0.1',
-        '1:2:3:4:5:6:7::',
-        '1:2:3:4:5:6::8',
-        '1::7:8',
-        '1:2:3:4:5::7:8',
-        '1:2:3:4:5::8',
-        '1::6:7:8',
-        '1:2:3:4::6:7:8',
-        '1:2:3:4::8',
-        '1::5:6:7:8',
-        '1:2:3::5:6:7:8',
-        '1:2:3::8',
-        '1::4:5:6:7:8',
-        '1:2::4:5:6:7:8',
-        '1:2::8',
-        '1::3:4:5:6:7:8',
-        '1::8',
-        'fe80::7:8%eth0',
-        'fe80::7:8%1',
-        '64:ff9b::192.0.2.33',
-        '0:0:0:0:0:0:10.0.0.1',
-      ],
-      invalid: [
-        'abc',
-        '256.0.0.0',
-        '0.0.0.256',
-        '26.0.0.256',
-        '0200.200.200.200',
-        '200.0200.200.200',
-        '200.200.0200.200',
-        '200.200.200.0200',
-        '::banana',
-        'banana::',
-        '::1banana',
-        '::1::',
-        '1:',
-        ':1',
-        ':1:1:1::2',
-        '1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1',
-        '::11111',
-        '11111:1:1:1:1:1:1:1',
-        '2001:db8:0000:1:1:1:1::1',
-        '0:0:0:0:0:0:ffff:127.0.0.1',
-        '0:0:0:0:ffff:127.0.0.1',
-      ],
-    });
-    test({
-      validator: 'isIP',
-      args: [4],
-      valid: [
-        '127.0.0.1',
-        '0.0.0.0',
-        '255.255.255.255',
-        '1.2.3.4',
-        '255.0.0.1',
-        '0.0.1.1',
-      ],
-      invalid: [
-        '::1',
-        '2001:db8:0000:1:1:1:1:1',
-        '::ffff:127.0.0.1',
-        '137.132.10.01',
-        '0.256.0.256',
-        '255.256.255.256',
-      ],
-    });
-    test({
-      validator: 'isIP',
-      args: [6],
-      valid: [
-        '::1',
-        '2001:db8:0000:1:1:1:1:1',
-        '::ffff:127.0.0.1',
-        'fe80::1234%1',
-        'ff08::9abc%10',
-        'ff08::9abc%interface10',
-        'ff02::5678%pvc1.3',
-      ],
-      invalid: [
-        '127.0.0.1',
-        '0.0.0.0',
-        '255.255.255.255',
-        '1.2.3.4',
-        '::ffff:287.0.0.1',
-        '%',
-        'fe80::1234%',
-        'fe80::1234%1%3%4',
-        'fe80%fe80%',
-      ],
-    });
-    test({
-      validator: 'isIP',
-      args: [10],
-      valid: [],
-      invalid: [
-        '127.0.0.1',
-        '0.0.0.0',
-        '255.255.255.255',
-        '1.2.3.4',
-        '::1',
-        '2001:db8:0000:1:1:1:1:1',
-      ],
-    });
-  });
-
   it('should validate isIPRange', () => {
     test({
       validator: 'isIPRange',
@@ -1333,6 +1239,7 @@ describe('Validators', () => {
       ],
     });
   });
+
   it('should validate alpha strings', () => {
     test({
       validator: 'isAlpha',
@@ -3047,9 +2954,17 @@ describe('Validators', () => {
       valid: [
         'GA302922',
         'ZE000509',
+        'A123456AB',
+        'Z556378HG',
       ],
       invalid: [
         'AB0123456',
+        'AZ556378H',
+        '556378HCX',
+        '556378432',
+        '5563784',
+        '#B12345FD',
+        'A43F12354',
       ],
     });
 
@@ -5819,6 +5734,9 @@ describe('Validators', () => {
         'MA64011519000001205000534921',
         'VG96VPVG0000012345678901',
         'DZ580002100001113000000570',
+        'IE29AIBK93115212345678',
+        'PS92PALS000000000400123456702',
+        'PS92PALS00000000040012345670O',
       ],
       invalid: [
         'XX22YYY1234567890123',
@@ -5827,6 +5745,8 @@ describe('Validators', () => {
         'FR7630006000011234567890189😅',
         'FR763000600001123456!!🤨7890189@',
         'VG46H07Y0223060094359858',
+        'IE95TE8270900834048660',
+        'PS072435171802145240705922007',
       ],
     });
     test({
@@ -7103,76 +7023,6 @@ describe('Validators', () => {
     });
   });
 
-  it('should validate base64 strings', () => {
-    test({
-      validator: 'isBase64',
-      valid: [
-        '',
-        'Zg==',
-        'Zm8=',
-        'Zm9v',
-        'Zm9vYg==',
-        'Zm9vYmE=',
-        'Zm9vYmFy',
-        'TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdC4=',
-        'Vml2YW11cyBmZXJtZW50dW0gc2VtcGVyIHBvcnRhLg==',
-        'U3VzcGVuZGlzc2UgbGVjdHVzIGxlbw==',
-        'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuMPNS1Ufof9EW/M98FNw' +
-        'UAKrwflsqVxaxQjBQnHQmiI7Vac40t8x7pIb8gLGV6wL7sBTJiPovJ0V7y7oc0Ye' +
-        'rhKh0Rm4skP2z/jHwwZICgGzBvA0rH8xlhUiTvcwDCJ0kc+fh35hNt8srZQM4619' +
-        'FTgB66Xmp4EtVyhpQV+t02g6NzK72oZI0vnAvqhpkxLeLiMCyrI416wHm5Tkukhx' +
-        'QmcL2a6hNOyu0ixX/x2kSFXApEnVrJ+/IxGyfyw8kf4N2IZpW5nEP847lpfj0SZZ' +
-        'Fwrd1mnfnDbYohX2zRptLy2ZUn06Qo9pkG5ntvFEPo9bfZeULtjYzIl6K8gJ2uGZ' +
-        'HQIDAQAB',
-      ],
-      invalid: [
-        '12345',
-        'Vml2YW11cyBmZXJtZtesting123',
-        'Zg=',
-        'Z===',
-        'Zm=8',
-        '=m9vYg==',
-        'Zm9vYmFy====',
-      ],
-    });
-
-    test({
-      validator: 'isBase64',
-      args: [{ urlSafe: true }],
-      valid: [
-        '',
-        'bGFkaWVzIGFuZCBnZW50bGVtZW4sIHdlIGFyZSBmbG9hdGluZyBpbiBzcGFjZQ',
-        '1234',
-        'bXVtLW5ldmVyLXByb3Vk',
-        'PDw_Pz8-Pg',
-        'VGhpcyBpcyBhbiBlbmNvZGVkIHN0cmluZw',
-      ],
-      invalid: [
-        ' AA',
-        '\tAA',
-        '\rAA',
-        '\nAA',
-        'This+isa/bad+base64Url==',
-        '0K3RgtC+INC30LDQutC+0LTQuNGA0L7QstCw0L3QvdCw0Y8g0YHRgtGA0L7QutCw',
-      ],
-      error: [
-        null,
-        undefined,
-        {},
-        [],
-        42,
-      ],
-    });
-
-    for (let i = 0, str = '', encoded; i < 1000; i++) {
-      str += String.fromCharCode(Math.random() * 26 | 97); // eslint-disable-line no-bitwise
-      encoded = Buffer.from(str).toString('base64');
-      if (!validator.isBase64(encoded)) {
-        let msg = format('validator.isBase64() failed with "%s"', encoded);
-        throw new Error(msg);
-      }
-    }
-  });
 
   it('should validate hex-encoded MongoDB ObjectId', () => {
     test({
@@ -8053,6 +7903,7 @@ describe('Validators', () => {
           '0502345671',
           '0242345671',
           '0542345671',
+          '0532345671',
           '0272345671',
           '0572345671',
           '0262345671',
@@ -8063,6 +7914,7 @@ describe('Validators', () => {
           '+233502345671',
           '+233242345671',
           '+233542345671',
+          '+233532345671',
           '+233272345671',
           '+233572345671',
           '+233262345671',
@@ -10296,15 +10148,27 @@ describe('Validators', () => {
       {
         locale: 'sq-AL',
         valid: [
-          '067123456',
-          '+35567123456',
+          '0621234567',
+          '0661234567',
+          '0671234567',
+          '0681234567',
+          '0691234567',
+          '+355621234567',
+          '+355651234567',
+          '+355661234567',
+          '+355671234567',
+          '+355681234567',
+          '+355691234567',
         ],
         invalid: [
           '67123456',
           '06712345',
+          '067123456',
           '06712345678',
-          '065123456',
-          '057123456',
+          '0571234567',
+          '+3556712345',
+          '+35565123456',
+          '+35157123456',
           'NotANumber',
         ],
       },
@@ -10688,7 +10552,34 @@ describe('Validators', () => {
           'This should fail',
           '5021931234567',
           '+50281234567',
-
+        ],
+      },
+      {
+        locale: 'mk-MK',
+        valid: [
+          '+38923234567',
+          '38931234567',
+          '022123456',
+          '22234567',
+          '71234567',
+          '31234567',
+          '+38923091500',
+          '80091234',
+          '81123456',
+          '54123456',
+        ],
+        invalid: [
+          '38912345678',
+          '+389123456789',
+          '21234567',
+          '123456789',
+          '+3891234567',
+          '700012345',
+          '510123456',
+          'This should fail',
+          '+389123456',
+          '389123456',
+          '80912345',
         ],
       },
     ];
@@ -12059,6 +11950,28 @@ describe('Validators', () => {
     });
   });
 
+  it('should validate ISO 15924 script codes', () => {
+    test({
+      validator: 'isISO15924',
+      valid: [
+        'Adlm',
+        'Bass',
+        'Copt',
+        'Dsrt',
+        'Egyd',
+        'Latn',
+        'Zzzz',
+      ],
+      invalid: [
+        '',
+        'arab',
+        'zzzz',
+        'Qaby',
+        'Lati',
+      ],
+    });
+  });
+
   it('should validate RFC 3339 dates', () => {
     test({
       validator: 'isRFC3339',
@@ -12740,6 +12653,9 @@ describe('Validators', () => {
           '39100-000',
           '22040-020',
           '39400-152',
+          '39100000',
+          '22040020',
+          '39400152',
         ],
         invalid: [
           '79800A12',
@@ -13066,6 +12982,55 @@ describe('Validators', () => {
     });
   });
 
+  it('should validate ISO6346 shipping container IDs with checksum digit 10 represented as 0', () => {
+    test({
+      validator: 'isISO6346',
+      valid: [
+        'APZU3789870',
+        'TEMU1002030',
+        'DFSU1704420',
+        'CMAU2221480',
+        'SEGU5060260',
+        'FCIU8939320',
+        'TRHU3495670',
+        'MEDU3871410',
+        'CMAU2184010',
+        'TCLU2265970',
+      ],
+      invalid: [
+        'APZU3789871', // Incorrect check digit
+        'TEMU1002031',
+        'DFSU1704421',
+        'CMAU2221481',
+        'SEGU5060261',
+      ],
+    });
+  });
+  it('should validate ISO6346 shipping container IDs with checksum digit 10 represented as 0', () => {
+    test({
+      validator: 'isFreightContainerID',
+      valid: [
+        'APZU3789870',
+        'TEMU1002030',
+        'DFSU1704420',
+        'CMAU2221480',
+        'SEGU5060260',
+        'FCIU8939320',
+        'TRHU3495670',
+        'MEDU3871410',
+        'CMAU2184010',
+        'TCLU2265970',
+      ],
+      invalid: [
+        'APZU3789871', // Incorrect check digit
+        'TEMU1002031',
+        'DFSU1704421',
+        'CMAU2221481',
+        'SEGU5060261',
+      ],
+    });
+  });
+
   // EU-UK valid numbers sourced from https://ec.europa.eu/taxation_customs/tin/specs/FS-TIN%20Algorithms-Public.docx or constructed by @tplessas.
   it('should validate taxID', () => {
     test({
@@ -13703,37 +13668,6 @@ describe('Validators', () => {
     });
   });
 
-  it('should validate base64URL', () => {
-    test({
-      validator: 'isBase64',
-      args: [{ urlSafe: true }],
-      valid: [
-        '',
-        'bGFkaWVzIGFuZCBnZW50bGVtZW4sIHdlIGFyZSBmbG9hdGluZyBpbiBzcGFjZQ',
-        '1234',
-        'bXVtLW5ldmVyLXByb3Vk',
-        'PDw_Pz8-Pg',
-        'VGhpcyBpcyBhbiBlbmNvZGVkIHN0cmluZw',
-      ],
-      invalid: [
-        ' AA',
-        '\tAA',
-        '\rAA',
-        '\nAA',
-        '123=',
-        'This+isa/bad+base64Url==',
-        '0K3RgtC+INC30LDQutC+0LTQuNGA0L7QstCw0L3QvdCw0Y8g0YHRgtGA0L7QutCw',
-      ],
-      error: [
-        null,
-        undefined,
-        {},
-        [],
-        42,
-      ],
-    });
-  });
-
   it('should validate date', () => {
     test({
       validator: 'isDate',
diff --git a/test/validators/isBase64.test.js b/test/validators/isBase64.test.js
new file mode 100644
index 0000000..c007434
--- /dev/null
+++ b/test/validators/isBase64.test.js
@@ -0,0 +1,201 @@
+import { format } from 'util';
+import test from '../testFunctions';
+import validator from '../../src';
+
+describe('isBase64', () => {
+  it('should validate base64 strings with default options', () => {
+    test({
+      validator: 'isBase64',
+      valid: [
+        '',
+        'Zg==',
+        'Zm8=',
+        'Zm9v',
+        'Zm9vYg==',
+        'Zm9vYmE=',
+        'Zm9vYmFy',
+        'TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdC4=',
+        'Vml2YW11cyBmZXJtZW50dW0gc2VtcGVyIHBvcnRhLg==',
+        'U3VzcGVuZGlzc2UgbGVjdHVzIGxlbw==',
+        'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuMPNS1Ufof9EW/M98FNw' +
+        'UAKrwflsqVxaxQjBQnHQmiI7Vac40t8x7pIb8gLGV6wL7sBTJiPovJ0V7y7oc0Ye' +
+        'rhKh0Rm4skP2z/jHwwZICgGzBvA0rH8xlhUiTvcwDCJ0kc+fh35hNt8srZQM4619' +
+        'FTgB66Xmp4EtVyhpQV+t02g6NzK72oZI0vnAvqhpkxLeLiMCyrI416wHm5Tkukhx' +
+        'QmcL2a6hNOyu0ixX/x2kSFXApEnVrJ+/IxGyfyw8kf4N2IZpW5nEP847lpfj0SZZ' +
+        'Fwrd1mnfnDbYohX2zRptLy2ZUn06Qo9pkG5ntvFEPo9bfZeULtjYzIl6K8gJ2uGZ' +
+        'HQIDAQAB',
+      ],
+      invalid: [
+        '12345',
+        'Vml2YW11cyBmZXJtZtesting123',
+        'Zg=',
+        'Z===',
+        'Zm=8',
+        '=m9vYg==',
+        'Zm9vYmFy====',
+      ],
+    });
+
+    test({
+      validator: 'isBase64',
+      args: [{ urlSafe: true }],
+      valid: [
+        '',
+        'bGFkaWVzIGFuZCBnZW50bGVtZW4sIHdlIGFyZSBmbG9hdGluZyBpbiBzcGFjZQ',
+        '1234',
+        'bXVtLW5ldmVyLXByb3Vk',
+        'PDw_Pz8-Pg',
+        'VGhpcyBpcyBhbiBlbmNvZGVkIHN0cmluZw',
+      ],
+      invalid: [
+        ' AA',
+        '\tAA',
+        '\rAA',
+        '\nAA',
+        'This+isa/bad+base64Url==',
+        '0K3RgtC+INC30LDQutC+0LTQuNGA0L7QstCw0L3QvdCw0Y8g0YHRgtGA0L7QutCw',
+      ],
+      error: [
+        null,
+        undefined,
+        {},
+        [],
+        42,
+      ],
+    });
+
+    for (let i = 0, str = '', encoded; i < 1000; i++) {
+      str += String.fromCharCode(Math.random() * 26 | 97); // eslint-disable-line no-bitwise
+      encoded = Buffer.from(str).toString('base64');
+      if (!validator.isBase64(encoded)) {
+        let msg = format('validator.isBase64() failed with "%s"', encoded);
+        throw new Error(msg);
+      }
+    }
+  });
+
+  it('should validate standard Base64 with padding', () => {
+    test({
+      validator: 'isBase64',
+      args: [{ urlSafe: false, padding: true }],
+      valid: [
+        '',
+        'TWFu',
+        'TWE=',
+        'TQ==',
+        'SGVsbG8=',
+        'U29mdHdhcmU=',
+        'YW55IGNhcm5hbCBwbGVhc3VyZS4=',
+      ],
+      invalid: [
+        'TWF',
+        'TWE===',
+        'SGVsbG8@',
+        'SGVsbG8===',
+        'SGVsb G8=',
+        '====',
+      ],
+    });
+  });
+
+  it('should validate standard Base64 without padding', () => {
+    test({
+      validator: 'isBase64',
+      args: [{ urlSafe: false, padding: false }],
+      valid: [
+        '',
+        'TWFu',
+        'TWE',
+        'TQ',
+        'SGVsbG8',
+        'U29mdHdhcmU',
+        'YW55IGNhcm5hbCBwbGVhc3VyZS4',
+      ],
+      invalid: [
+        'TWE=',
+        'TQ===',
+        'SGVsbG8@',
+        'SGVsbG8===',
+        'SGVsb G8',
+        '====',
+      ],
+    });
+  });
+
+  it('should validate Base64url with padding', () => {
+    test({
+      validator: 'isBase64',
+      args: [{ urlSafe: true, padding: true }],
+      valid: [
+        '',
+        'SGVsbG8=',
+        'U29mdHdhcmU=',
+        'YW55IGNhcm5hbCBwbGVhc3VyZS4=',
+        'SGVsbG8-',
+        'SGVsbG8_',
+      ],
+      invalid: [
+        'SGVsbG8===',
+        'SGVsbG8@',
+        'SGVsb G8=',
+        '====',
+      ],
+    });
+  });
+
+  it('should validate Base64url without padding', () => {
+    test({
+      validator: 'isBase64',
+      args: [{ urlSafe: true, padding: false }],
+      valid: [
+        '',
+        'SGVsbG8',
+        'U29mdHdhcmU',
+        'YW55IGNhcm5hbCBwbGVhc3VyZS4',
+        'SGVsbG8-',
+        'SGVsbG8_',
+      ],
+      invalid: [
+        'SGVsbG8=',
+        'SGVsbG8===',
+        'SGVsbG8@',
+        'SGVsb G8',
+        '====',
+      ],
+    });
+  });
+
+  it('should handle mixed cases correctly', () => {
+    test({
+      validator: 'isBase64',
+      args: [{ urlSafe: false, padding: true }],
+      valid: [
+        '',
+        'TWFu',
+        'TWE=',
+        'TQ==',
+      ],
+      invalid: [
+        'TWE',
+        'TQ=',
+        'TQ===',
+      ],
+    });
+
+    test({
+      validator: 'isBase64',
+      args: [{ urlSafe: true, padding: false }],
+      valid: [
+        '',
+        'SGVsbG8',
+        'SGVsbG8-',
+        'SGVsbG8_',
+      ],
+      invalid: [
+        'SGVsbG8=',
+        'SGVsbG8@',
+        'SGVsb G8',
+      ],
+    });
+  });
+});
diff --git a/test/validators/isIP.test.js b/test/validators/isIP.test.js
new file mode 100644
index 0000000..9b01d02
--- /dev/null
+++ b/test/validators/isIP.test.js
@@ -0,0 +1,302 @@
+import test from '../testFunctions';
+
+describe('isIP', () => {
+  it('should validate IP addresses', () => {
+    test({
+      validator: 'isIP',
+      valid: [
+        '127.0.0.1',
+        '0.0.0.0',
+        '255.255.255.255',
+        '1.2.3.4',
+        '::1',
+        '2001:db8:0000:1:1:1:1:1',
+        '2001:db8:3:4::192.0.2.33',
+        '2001:41d0:2:a141::1',
+        '::ffff:127.0.0.1',
+        '::0000',
+        '0000::',
+        '1::',
+        '1111:1:1:1:1:1:1:1',
+        'fe80::a6db:30ff:fe98:e946',
+        '::',
+        '::8',
+        '::ffff:127.0.0.1',
+        '::ffff:255.255.255.255',
+        '::ffff:0:255.255.255.255',
+        '::2:3:4:5:6:7:8',
+        '::255.255.255.255',
+        '0:0:0:0:0:ffff:127.0.0.1',
+        '1:2:3:4:5:6:7::',
+        '1:2:3:4:5:6::8',
+        '1::7:8',
+        '1:2:3:4:5::7:8',
+        '1:2:3:4:5::8',
+        '1::6:7:8',
+        '1:2:3:4::6:7:8',
+        '1:2:3:4::8',
+        '1::5:6:7:8',
+        '1:2:3::5:6:7:8',
+        '1:2:3::8',
+        '1::4:5:6:7:8',
+        '1:2::4:5:6:7:8',
+        '1:2::8',
+        '1::3:4:5:6:7:8',
+        '1::8',
+        'fe80::7:8%eth0',
+        'fe80::7:8%1',
+        '64:ff9b::192.0.2.33',
+        '0:0:0:0:0:0:10.0.0.1',
+      ],
+      invalid: [
+        'abc',
+        '256.0.0.0',
+        '0.0.0.256',
+        '26.0.0.256',
+        '0200.200.200.200',
+        '200.0200.200.200',
+        '200.200.0200.200',
+        '200.200.200.0200',
+        '::banana',
+        'banana::',
+        '::1banana',
+        '::1::',
+        '1:',
+        ':1',
+        ':1:1:1::2',
+        '1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1',
+        '::11111',
+        '11111:1:1:1:1:1:1:1',
+        '2001:db8:0000:1:1:1:1::1',
+        '0:0:0:0:0:0:ffff:127.0.0.1',
+        '0:0:0:0:ffff:127.0.0.1',
+      ],
+    });
+
+    test({
+      validator: 'isIP',
+      args: [{ version: 'invalid version' }],
+      valid: [],
+      invalid: [
+        '127.0.0.1',
+        '0.0.0.0',
+        '255.255.255.255',
+        '1.2.3.4',
+      ],
+    });
+
+    test({
+      validator: 'isIP',
+      args: [{ version: null }],
+      valid: [
+        '127.0.0.1',
+        '0.0.0.0',
+        '255.255.255.255',
+        '1.2.3.4',
+      ],
+    });
+
+    test({
+      validator: 'isIP',
+      args: [{ version: undefined }],
+      valid: [
+        '127.0.0.1',
+        '0.0.0.0',
+        '255.255.255.255',
+        '1.2.3.4',
+      ],
+    });
+
+    test({
+      validator: 'isIP',
+      args: [{ version: 4 }],
+      valid: [
+        '127.0.0.1',
+        '0.0.0.0',
+        '255.255.255.255',
+        '1.2.3.4',
+        '255.0.0.1',
+        '0.0.1.1',
+      ],
+      invalid: [
+        '::1',
+        '2001:db8:0000:1:1:1:1:1',
+        '::ffff:127.0.0.1',
+        '137.132.10.01',
+        '0.256.0.256',
+        '255.256.255.256',
+      ],
+    });
+
+    test({
+      validator: 'isIP',
+      args: [{ version: 6 }],
+      valid: [
+        '::1',
+        '2001:db8:0000:1:1:1:1:1',
+        '::ffff:127.0.0.1',
+        'fe80::1234%1',
+        'ff08::9abc%10',
+        'ff08::9abc%interface10',
+        'ff02::5678%pvc1.3',
+      ],
+      invalid: [
+        '127.0.0.1',
+        '0.0.0.0',
+        '255.255.255.255',
+        '1.2.3.4',
+        '::ffff:287.0.0.1',
+        '%',
+        'fe80::1234%',
+        'fe80::1234%1%3%4',
+        'fe80%fe80%',
+      ],
+    });
+
+    test({
+      validator: 'isIP',
+      args: [{ version: 10 }],
+      valid: [],
+      invalid: [
+        '127.0.0.1',
+        '0.0.0.0',
+        '255.255.255.255',
+        '1.2.3.4',
+        '::1',
+        '2001:db8:0000:1:1:1:1:1',
+      ],
+    });
+  });
+
+  describe('legacy syntax', () => {
+    it('should validate IP addresses', () => {
+      test({
+        validator: 'isIP',
+        valid: [
+          '127.0.0.1',
+          '0.0.0.0',
+          '255.255.255.255',
+          '1.2.3.4',
+          '::1',
+          '2001:db8:0000:1:1:1:1:1',
+          '2001:db8:3:4::192.0.2.33',
+          '2001:41d0:2:a141::1',
+          '::ffff:127.0.0.1',
+          '::0000',
+          '0000::',
+          '1::',
+          '1111:1:1:1:1:1:1:1',
+          'fe80::a6db:30ff:fe98:e946',
+          '::',
+          '::8',
+          '::ffff:127.0.0.1',
+          '::ffff:255.255.255.255',
+          '::ffff:0:255.255.255.255',
+          '::2:3:4:5:6:7:8',
+          '::255.255.255.255',
+          '0:0:0:0:0:ffff:127.0.0.1',
+          '1:2:3:4:5:6:7::',
+          '1:2:3:4:5:6::8',
+          '1::7:8',
+          '1:2:3:4:5::7:8',
+          '1:2:3:4:5::8',
+          '1::6:7:8',
+          '1:2:3:4::6:7:8',
+          '1:2:3:4::8',
+          '1::5:6:7:8',
+          '1:2:3::5:6:7:8',
+          '1:2:3::8',
+          '1::4:5:6:7:8',
+          '1:2::4:5:6:7:8',
+          '1:2::8',
+          '1::3:4:5:6:7:8',
+          '1::8',
+          'fe80::7:8%eth0',
+          'fe80::7:8%1',
+          '64:ff9b::192.0.2.33',
+          '0:0:0:0:0:0:10.0.0.1',
+        ],
+        invalid: [
+          'abc',
+          '256.0.0.0',
+          '0.0.0.256',
+          '26.0.0.256',
+          '0200.200.200.200',
+          '200.0200.200.200',
+          '200.200.0200.200',
+          '200.200.200.0200',
+          '::banana',
+          'banana::',
+          '::1banana',
+          '::1::',
+          '1:',
+          ':1',
+          ':1:1:1::2',
+          '1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1',
+          '::11111',
+          '11111:1:1:1:1:1:1:1',
+          '2001:db8:0000:1:1:1:1::1',
+          '0:0:0:0:0:0:ffff:127.0.0.1',
+          '0:0:0:0:ffff:127.0.0.1',
+        ],
+      });
+      test({
+        validator: 'isIP',
+        args: [4],
+        valid: [
+          '127.0.0.1',
+          '0.0.0.0',
+          '255.255.255.255',
+          '1.2.3.4',
+          '255.0.0.1',
+          '0.0.1.1',
+        ],
+        invalid: [
+          '::1',
+          '2001:db8:0000:1:1:1:1:1',
+          '::ffff:127.0.0.1',
+          '137.132.10.01',
+          '0.256.0.256',
+          '255.256.255.256',
+        ],
+      });
+      test({
+        validator: 'isIP',
+        args: [6],
+        valid: [
+          '::1',
+          '2001:db8:0000:1:1:1:1:1',
+          '::ffff:127.0.0.1',
+          'fe80::1234%1',
+          'ff08::9abc%10',
+          'ff08::9abc%interface10',
+          'ff02::5678%pvc1.3',
+        ],
+        invalid: [
+          '127.0.0.1',
+          '0.0.0.0',
+          '255.255.255.255',
+          '1.2.3.4',
+          '::ffff:287.0.0.1',
+          '%',
+          'fe80::1234%',
+          'fe80::1234%1%3%4',
+          'fe80%fe80%',
+        ],
+      });
+      test({
+        validator: 'isIP',
+        args: [10],
+        valid: [],
+        invalid: [
+          '127.0.0.1',
+          '0.0.0.0',
+          '255.255.255.255',
+          '1.2.3.4',
+          '::1',
+          '2001:db8:0000:1:1:1:1:1',
+        ],
+      });
+    });
+  });
+});