Repository: validatorjs/validator.js
Pull request #1892 (2022-01-04 15:13:50)

feat(isISO6391): add ISO 639-1 validator
<!--
Add a descriptive title textbox above, e.g.
feat(validatorName): brief title of what has been done
-->

This PR adds a validator which validates [ISO 639-1 languages codes](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes), such as `'af'`, `'nb'` and `'fr'`. 

I would like this feature because ISO 639-1 seems to be the most commonly used set of language codes when referring solely to a language and not a locale, and I will be using them for an upcoming project which uses `validator.js`.

This PR is essentially a cleaned up version of a previous PR which I suspect was closed partially due to the lack of cleanup: https://github.com/validatorjs/validator.js/pull/1103.

Thanks to @ezkemboi and @profnandaa for their contributions in earlier PRs and issues.

My contribution is the cleanup of the diff as well as rewritten test code and readme entry.

See related issue: https://github.com/validatorjs/validator.js/issues/989

<!--- briefly describe what you have done in this PR --->

## Checklist

- [x] PR contains only changes related; no stray files, etc.
- [x] README updated (where applicable)
- [x] Tests written (where applicable)


codecov[bot]: # [Codecov](https://codecov.io/gh/validatorjs/validator.js/pull/1892?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=validatorjs) Report
> Merging [#1892](https://codecov.io/gh/validatorjs/validator.js/pull/1892?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=validatorjs) (a4ea9ce) into [master](https://codecov.io/gh/validatorjs/validator.js/commit/f055c111d8720dd5fad720f218f1399ce357e81a?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=validatorjs) (f055c11) will **not change** coverage.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/validatorjs/validator.js/pull/1892/graphs/tree.svg?width=650&height=150&src=pr&token=YzR3kbN1m2&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=validatorjs)](https://codecov.io/gh/validatorjs/validator.js/pull/1892?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=validatorjs)

```diff
@@            Coverage Diff            @@
##            master     #1892   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          102       103    +1     
  Lines         2072      2078    +6     
  Branches       472       472           
=========================================
+ Hits          2072      2078    +6     
```


| [Impacted Files](https://codecov.io/gh/validatorjs/validator.js/pull/1892?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=validatorjs) | Coverage Δ | |
|---|---|---|
| [src/index.js](https://codecov.io/gh/validatorjs/validator.js/pull/1892/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=validatorjs#diff-c3JjL2luZGV4Lmpz) | `100.00% <100.00%> (ø)` | |
| [src/lib/isISO6391.js](https://codecov.io/gh/validatorjs/validator.js/pull/1892/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=validatorjs#diff-c3JjL2xpYi9pc0lTTzYzOTEuanM=) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/validatorjs/validator.js/pull/1892?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=validatorjs).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=validatorjs)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/validatorjs/validator.js/pull/1892?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=validatorjs). Last update [f055c11...a4ea9ce](https://codecov.io/gh/validatorjs/validator.js/pull/1892?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=validatorjs). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=validatorjs).

braaar: I understand the desire to stick to regex based validation, but ISO will never update this spec with breaking changes, as far as I understand it. This specific version of ISO 639 is not likely to be updated ever again. It was last updated in 2002 and has since [been reviewed three times without changes](https://www.iso.org/standard/22109.html).
braaar: I cleaned up the list of language codes so it's easier on the eyes, like the list in [the ISO3166 validator](https://github.com/validatorjs/validator.js/blob/master/src/lib/isISO31661Alpha2.js)
braaar: I suppose a more complete implementation of this could be `isISO639(str, iso639Versions)` where you can specify which versions of `ISO639` you wish to validate against (perhaps just 1 through 5, since version 6 was withdrawn). 

The default behaviour without any options specified should match any of the 5 official and current versions of the standard.

Here's how I would implement that:
```js
const sets = [isISO6391Set, isISO6392Set, isISO6393Set, isISO6394Set, isISO6395Set];

export default function isISO6391(str, versions = [1, 2, 3, 4, 5]) {
  assertString(str);

  return versions.reduce((result, version) => {
    const currentSet = sets[version - 1];
    return currentSet.has(str) || result;
  }, false);
}
```
WikiRik: That would be a more complete implementation, but I'm not sure how often that would be used. Especially with the large number of supported languages for ISO 639-3, it might be too much for this project.
Also, I am not sure if the default for matching any of the 5 would be smart for false positives. But I would like to hear the opinions of @profnandaa and @ezkemboi about this

Patch:
diff --git a/README.md b/README.md
index 0d2ed2b..9a10d8f 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,8 @@
 [![Downloads][downloads-image]][npm-url]
 [![Backers on Open Collective](https://opencollective.com/validatorjs/backers/badge.svg)](#backers)
 [![Sponsors on Open Collective](https://opencollective.com/validatorjs/sponsors/badge.svg)](#sponsors)
-[![Gitter](https://badges.gitter.im/validatorjs/community.svg)](https://gitter.im/validatorjs/community)
+[![Gitter][gitter-image]][gitter-url]
+[![Disclose a vulnerability][huntr-image]][huntr-url]
 
 A library of string validators and sanitizers.
 
@@ -94,7 +95,7 @@ Validator                               | Description
 **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.
 **isAscii(str)**                        | check if the string contains ASCII chars only.
-**isBase32(str)**                       | check if a string is base32 encoded.
+**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.
 **isBase64(str [, options])**          | check if a 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](https://base64.guru/standards/base64url)
 **isBefore(str [, date])**              | check if the string is a date that's before the specified date.
@@ -109,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.
 **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`.
@@ -129,6 +130,7 @@ Validator                               | Description
 **isIPRange(str [, version])**          | check if the string is an IP Range (version 4 or 6).
 **isISBN(str [, version])**             | check if the string is an ISBN (version 10 or 13).
 **isISIN(str)**                         | check if the string is an [ISIN][ISIN] (stock/security identifier).
+**isISO6391(str)**                      | check if the string is a valid [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) language code.
 **isISO8601(str [, options])**          | check if the string is a valid [ISO 8601](https://en.wikipedia.org/wiki/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.
 **isISO31661Alpha2(str)**               | check if the string is a valid [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) officially assigned country code.
 **isISO31661Alpha3(str)**               | check if the string is a valid [ISO 3166-1 alpha-3](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3) officially assigned country code.
@@ -139,7 +141,7 @@ 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-PT', 'sq-AL', 'pt-BR']` 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']` or `any`)
 **isLocale(str)**                       | check if the string is a locale
 **isLowercase(str)**                    | check if the string is lowercase.
 **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.
@@ -161,7 +163,7 @@ Validator                               | Description
 **isUppercase(str)**                    | check if the string is uppercase.
 **isSlug**                              | Check if the string is of type slug. `Options` allow a single hyphen between string. e.g. [`cn-cn`, `cn-c-c`]
 **isStrongPassword(str [, options])**   | Check if a password is strong or not. Allows for custom requirements or scoring rules. If `returnScore` is true, then the function returns an integer score for the password rather than a boolean.<br/>Default options: <br/>`{ minLength: 8, minLowercase: 1, minUppercase: 1, minNumbers: 1, minSymbols: 1, returnScore: false, pointsPerUnique: 1, pointsPerRepeat: 0.5, pointsForContainingLower: 10, pointsForContainingUpper: 10, pointsForContainingNumber: 10, pointsForContainingSymbol: 10 }`
-**isTaxID(str, locale)**                | Check if the given value is a valid Tax Identification    Number. Default locale is `en-US`.<br/><br/>More info about exact TIN support can be found in `src/lib/isTaxID.js`<br/><br/>Supported locales: `[ 'bg-BG', 'cs-CZ', 'de-AT', 'de-DE', 'dk-DK', 'el-CY', 'el-GR', 'en-GB', 'en-IE', 'en-US', 'es-ES', 'et-EE', 'fi-FI', 'fr-BE', 'fr-FR', 'fr-LU', 'hr-HR', 'hu-HU', 'it-IT', 'lb-LU', 'lt-LT', 'lv-LV' 'mt-MT', 'nl-BE', 'nl-NL', 'pl-PL', 'pt-BR', 'pt-PT', 'ro-RO', 'sk-SK', 'sl-SI', 'sv-SE' ]`
+**isTaxID(str, locale)**                | Check if the given value is a valid Tax Identification Number. Default locale is `en-US`.<br/><br/>More info about exact TIN support can be found in `src/lib/isTaxID.js`<br/><br/>Supported locales: `[ 'bg-BG', 'cs-CZ', 'de-AT', 'de-DE', 'dk-DK', 'el-CY', 'el-GR', 'en-CA', 'en-GB', 'en-IE', 'en-US', 'es-ES', 'et-EE', 'fi-FI', 'fr-BE', 'fr-CA', 'fr-FR', 'fr-LU', 'hr-HR', 'hu-HU', 'it-IT', 'lb-LU', 'lt-LT', 'lv-LV' 'mt-MT', 'nl-BE', 'nl-NL', 'pl-PL', 'pt-BR', 'pt-PT', 'ro-RO', 'sk-SK', 'sl-SI', 'sv-SE' ]`
 **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.
@@ -226,6 +228,8 @@ $ npm test
 
 - [chriso](https://github.com/chriso) - **Chris O'Hara** (author)
 - [profnandaa](https://github.com/profnandaa) - **Anthony Nandaa**
+- [ezkemboi](https://github.com/ezkemboi) - **Ezrqn Kemboi**
+- [tux-tn](https://github.com/tux-tn) - **Sarhan Aissi**
 
 ## Reading
 
@@ -267,6 +271,12 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 [ci-url]: https://github.com/validatorjs/validator.js/actions?query=workflow%3ACI
 [ci-image]: https://github.com/validatorjs/validator.js/workflows/CI/badge.svg?branch=master
 
+[gitter-url]: https://gitter.im/validatorjs/community
+[gitter-image]: https://badges.gitter.im/validatorjs/community.svg
+
+[huntr-url]: https://huntr.dev/bounties/disclose/?target=https://github.com/validatorjs/validator.js
+[huntr-image]: https://cdn.huntr.dev/huntr_security_badge_mono.svg
+
 [amd]: http://requirejs.org/docs/whyamd.html
 [bower]: http://bower.io/
 
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..72592f1
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,11 @@
+# Security Policy
+
+## Supported Versions
+
+In the case of a confirmed security issue, only the current version of validator is guaranteed to be patched.
+
+## Reporting a Vulnerability
+
+**Please don't disclose security-related issues publicly.**
+
+If you discover a vulnerability within validator, please use [huntr.dev disclosure form](https://huntr.dev/bounties/disclose/?target=https://github.com/validatorjs/validator.js). We will try to validate and respond to reports in a reasonable time. if the issue is confirmed, we will create a security advisory and a patch as soon as possible.
\ No newline at end of file
diff --git a/src/index.js b/src/index.js
index b8ad651..ec6fe52 100644
--- a/src/index.js
+++ b/src/index.js
@@ -86,6 +86,7 @@ import isCurrency from './lib/isCurrency';
 
 import isBtcAddress from './lib/isBtcAddress';
 
+import isISO6391 from './lib/isISO6391';
 import isISO8601 from './lib/isISO8601';
 import isRFC3339 from './lib/isRFC3339';
 import isISO31661Alpha2 from './lib/isISO31661Alpha2';
@@ -195,6 +196,7 @@ const validator = {
   isEthereumAddress,
   isCurrency,
   isBtcAddress,
+  isISO6391,
   isISO8601,
   isRFC3339,
   isISO31661Alpha2,
diff --git a/src/lib/isBase32.js b/src/lib/isBase32.js
index b2e9c8a..5e2969c 100644
--- a/src/lib/isBase32.js
+++ b/src/lib/isBase32.js
@@ -1,9 +1,21 @@
 import assertString from './util/assertString';
+import merge from './util/merge';
 
 const base32 = /^[A-Z2-7]+=*$/;
+const crockfordBase32 = /^[A-HJKMNP-TV-Z0-9]+$/;
 
-export default function isBase32(str) {
+const defaultBase32Options = {
+  crockford: false,
+};
+
+export default function isBase32(str, options) {
   assertString(str);
+  options = merge(options, defaultBase32Options);
+
+  if (options.crockford) {
+    return crockfordBase32.test(str);
+  }
+
   const len = str.length;
   if (len % 8 === 0 && base32.test(str)) {
     return true;
diff --git a/src/lib/isISO6391.js b/src/lib/isISO6391.js
new file mode 100644
index 0000000..eaa01c5
--- /dev/null
+++ b/src/lib/isISO6391.js
@@ -0,0 +1,35 @@
+import assertString from './util/assertString';
+
+const isISO6391Set = new Set([
+  'aa', 'ab', 'ae', 'af', 'ak', 'am', 'an', 'ar', 'as', 'av', 'ay', 'az', 'az',
+  'ba', 'be', 'bg', 'bh', 'bi', 'bm', 'bn', 'bo', 'br', 'bs',
+  'ca', 'ce', 'ch', 'co', 'cr', 'cs', 'cu', 'cv', 'cy',
+  'da', 'de', 'dv', 'dz',
+  'ee', 'el', 'en', 'eo', 'es', 'et', 'eu',
+  'fa', 'ff', 'fi', 'fj', 'fo', 'fr', 'fy',
+  'ga', 'gd', 'gl', 'gn', 'gu', 'gv',
+  'ha', 'he', 'hi', 'ho', 'hr', 'ht', 'hu', 'hy', 'hz',
+  'ia', 'id', 'ie', 'ig', 'ii', 'ik', 'io', 'is', 'it', 'iu',
+  'ja', 'jv',
+  'ka', 'kg', 'ki', 'kj', 'kk', 'kl', 'km', 'kn', 'ko', 'kr', 'ks', 'ku', 'kv', 'kw', 'ky',
+  'la', 'lb', 'lg', 'li', 'ln', 'lo', 'lt', 'lu', 'lv',
+  'mg', 'mh', 'mi', 'mk', 'ml', 'mn', 'mr', 'ms', 'mt', 'my',
+  'na', 'nb', 'nd', 'ne', 'ng', 'nl', 'nn', 'no', 'nr', 'nv', 'ny',
+  'oc', 'oj', 'om', 'or', 'os',
+  'pa', 'pi', 'pl', 'ps', 'pt',
+  'qu',
+  'rm', 'rn', 'ro', 'ru', 'rw',
+  'sa', 'sc', 'sd', 'se', 'sg', 'si', 'sk', 'sl', 'sm', 'sn', 'so', 'sq', 'sr', 'ss', 'st', 'su', 'sv', 'sw',
+  'ta', 'te', 'tg', 'th', 'ti', 'tk', 'tl', 'tn', 'to', 'tr', 'ts', 'tt', 'tw', 'ty',
+  'ug', 'uk', 'ur', 'uz',
+  've', 'vi', 'vo',
+  'wa', 'wo',
+  'xh',
+  'yi', 'yo',
+  'za', 'zh', 'zu',
+]);
+
+export default function isISO6391(str) {
+  assertString(str);
+  return isISO6391Set.has(str);
+}
diff --git a/src/lib/isLicensePlate.js b/src/lib/isLicensePlate.js
index d6b27a5..668064f 100644
--- a/src/lib/isLicensePlate.js
+++ b/src/lib/isLicensePlate.js
@@ -13,6 +13,8 @@ const validators = {
     /^[A-Z]{2}[- ]?((\d{3}[- ]?(([A-Z]{2})|T))|(R[- ]?\d{3}))$/.test(str),
   'pt-BR': str =>
     /^[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()),
 };
 
 export default function isLicensePlate(str, locale) {
diff --git a/src/lib/isTaxID.js b/src/lib/isTaxID.js
index ee66ca3..f43c61a 100644
--- a/src/lib/isTaxID.js
+++ b/src/lib/isTaxID.js
@@ -60,6 +60,36 @@ function bgBgCheck(tin) {
   return checksum === digits[9];
 }
 
+/**
+ * Check if an input is a valid Canadian SIN (Social Insurance Number)
+ *
+ * The Social Insurance Number (SIN) is a 9 digit number that
+ * you need to work in Canada or to have access to government programs and benefits.
+ *
+ * https://en.wikipedia.org/wiki/Social_Insurance_Number
+ * https://www.canada.ca/en/employment-social-development/services/sin.html
+ * https://www.codercrunch.com/challenge/819302488/sin-validator
+ *
+ * @param {string} input
+ * @return {boolean}
+ */
+function isCanadianSIN(input) {
+  const digitsArray = input.split('');
+  const even = digitsArray
+    .filter((_, idx) => idx % 2)
+    .map(i => Number(i) * 2)
+    .join('')
+    .split('');
+
+  const total = digitsArray
+    .filter((_, idx) => !(idx % 2))
+    .concat(even)
+    .map(i => Number(i))
+    .reduce((acc, cur) => acc + cur);
+
+  return (total % 10 === 0);
+}
+
 /*
  * cs-CZ validation function
  * (Rodné číslo (RČ), persons only)
@@ -1096,7 +1126,6 @@ function svSeCheck(tin) {
  * uppercase and lowercase letters are acceptable.
  */
 const taxIdFormat = {
-
   'bg-BG': /^\d{10}$/,
   'cs-CZ': /^\d{6}\/{0,1}\d{3,4}$/,
   'de-AT': /^\d{9}$/,
@@ -1104,6 +1133,7 @@ const taxIdFormat = {
   'dk-DK': /^\d{6}-{0,1}\d{4}$/,
   'el-CY': /^[09]\d{7}[A-Z]$/,
   'el-GR': /^([0-4]|[7-9])\d{8}$/,
+  'en-CA': /^\d{9}$/,
   'en-GB': /^\d{10}$|^(?!GB|NK|TN|ZZ)(?![DFIQUV])[A-Z](?![DFIQUVO])[A-Z]\d{6}[ABCD ]$/i,
   'en-IE': /^\d{7}[A-W][A-IW]{0,1}$/i,
   'en-US': /^\d{2}[- ]{0,1}\d{7}$/,
@@ -1126,16 +1156,15 @@ const taxIdFormat = {
   'sk-SK': /^\d{6}\/{0,1}\d{3,4}$/,
   'sl-SI': /^[1-9]\d{7}$/,
   'sv-SE': /^(\d{6}[-+]{0,1}\d{4}|(18|19|20)\d{6}[-+]{0,1}\d{4})$/,
-
 };
 // taxIdFormat locale aliases
 taxIdFormat['lb-LU'] = taxIdFormat['fr-LU'];
 taxIdFormat['lt-LT'] = taxIdFormat['et-EE'];
 taxIdFormat['nl-BE'] = taxIdFormat['fr-BE'];
+taxIdFormat['fr-CA'] = taxIdFormat['en-CA'];
 
 // Algorithmic tax id check functions for various locales
 const taxIdCheck = {
-
   'bg-BG': bgBgCheck,
   'cs-CZ': csCzCheck,
   'de-AT': deAtCheck,
@@ -1143,6 +1172,7 @@ const taxIdCheck = {
   'dk-DK': dkDkCheck,
   'el-CY': elCyCheck,
   'el-GR': elGrCheck,
+  'en-CA': isCanadianSIN,
   'en-IE': enIeCheck,
   'en-US': enUsCheck,
   'es-ES': esEsCheck,
@@ -1164,12 +1194,12 @@ const taxIdCheck = {
   'sk-SK': skSkCheck,
   'sl-SI': slSiCheck,
   'sv-SE': svSeCheck,
-
 };
 // taxIdCheck locale aliases
 taxIdCheck['lb-LU'] = taxIdCheck['fr-LU'];
 taxIdCheck['lt-LT'] = taxIdCheck['et-EE'];
 taxIdCheck['nl-BE'] = taxIdCheck['fr-BE'];
+taxIdCheck['fr-CA'] = taxIdCheck['en-CA'];
 
 // Regexes for locales where characters should be omitted before checking format
 const allsymbols = /[-\\\/!@#$%\^&\*\(\)\+\=\[\]]+/g;


Test patch:
diff --git a/test/validators.js b/test/validators.js
index 76ec3d9..d1f0d2d 100644
--- a/test/validators.js
+++ b/test/validators.js
@@ -5753,6 +5753,25 @@ describe('Validators', () => {
     });
   });
 
+  it('should validate base32 strings with crockford alternative', () => {
+    test({
+      validator: 'isBase32',
+      args: [{ crockford: true }],
+      valid: [
+        '91JPRV3F41BPYWKCCGGG',
+        '60',
+        '64',
+        'B5QQA833C5Q20S3F41MQ8',
+      ],
+      invalid: [
+        '91JPRV3F41BUPYWKCCGGG',
+        'B5QQA833C5Q20S3F41MQ8L',
+        '60I',
+        'B5QQA833OULIC5Q20S3F41MQ8',
+      ],
+    });
+  });
+
   it('should validate base58 strings', () => {
     test({
       validator: 'isBase58',
@@ -9773,6 +9792,14 @@ describe('Validators', () => {
     });
   });
 
+  it('should validate ISO 639-1 language codes', () => {
+    test({
+      validator: 'isISO6391',
+      valid: ['ay', 'az', 'ba', 'be', 'bg'],
+      invalid: ['aj', 'al', 'pe', 'pf', 'abc', '123', ''],
+    });
+  });
+
   const validISO8601 = [
     '2009-12T12:34',
     '2009',
@@ -10312,6 +10339,23 @@ describe('Validators', () => {
           'A1A 1A1',
           'X0A-0H0',
           'V5K 0A1',
+          'A1C 3S4',
+          'A1C3S4',
+          'a1c 3s4',
+          'V9A 7N2',
+          'B3K 5X5',
+          'K8N 5W6',
+          'K1A 0B1',
+          'B1Z 0B9',
+        ],
+        invalid: [
+          '        ',
+          'invalid value',
+          'a1a1a',
+          'A1A  1A1',
+          'K1A 0D1',
+          'W1A 0B1',
+          'Z1A 0B1',
         ],
       },
       {
@@ -10517,6 +10561,8 @@ describe('Validators', () => {
           '78-399',
           '39-490',
           '38-483',
+          '05-800',
+          '54-060',
         ],
       },
       {
@@ -10575,6 +10621,9 @@ describe('Validators', () => {
           '65000',
           '65080',
           '01000',
+          '51901',
+          '51909',
+          '49125',
         ],
       },
       {
@@ -10950,6 +10999,43 @@ describe('Validators', () => {
         '658426713',
         '558426713'],
     });
+    test({
+      validator: 'isTaxID',
+      args: ['en-CA'],
+      valid: [
+        '000000000',
+        '521719666',
+        '469317481',
+        '120217450',
+        '480534858',
+        '325268597',
+        '336475660',
+        '744797853',
+        '130692544',
+        '046454286',
+      ],
+      invalid: [
+        '        ',
+        'any value',
+        '012345678',
+        '111111111',
+        '999999999',
+        '657449110',
+        '74 47 978 53',
+        '744 797 853',
+        '744-797-853',
+        '981062432',
+        '267500713',
+        '2675o0713',
+        '70597312',
+        '7058973122',
+        '069437151',
+        '046454281',
+        '146452286',
+        '30x92544',
+        '30692544',
+      ],
+    });
     test({
       validator: 'isTaxID',
       args: ['en-GB'],
@@ -11767,6 +11853,44 @@ describe('Validators', () => {
         'FS AB 1234 A',
       ],
     });
+    test({
+      validator: 'isLicensePlate',
+      args: ['sv-SE'],
+      valid: [
+        'ABC 123',
+        'ABC 12A',
+        'ABC123',
+        'ABC12A',
+        'A WORD',
+        'WORD',
+        'ÅSNA',
+        'EN VARG',
+        'CERISE',
+        'AA',
+        'ABCDEFG',
+        'ÅÄÖ',
+        'ÅÄÖ ÅÄÖ',
+      ],
+      invalid: [
+        '',
+        '    ',
+        'IQV 123',
+        'IQV123',
+        'ABI 12Q',
+        'ÅÄÖ 123',
+        'ÅÄÖ 12A',
+        'AB1 A23',
+        'AB1 12A',
+        'lower',
+        'abc 123',
+        'abc 12A',
+        'abc 12a',
+        'AbC 12a',
+        'WORDLONGERTHANSEVENCHARACTERS',
+        'A',
+        'ABC-123',
+      ],
+    });
   });
   it('should validate VAT numbers', () => {
     test({