From 86eca3b4b402dac38ab08e8238a106872b78ff10 Mon Sep 17 00:00:00 2001
From: Wei Kang <weikangchia@gmail.com>
Date: Wed, 13 Nov 2024 16:41:13 +0800
Subject: [PATCH 1/5] enhance isEmail to have regexp for host_whitelist

---
 README.md               |  2 +-
 src/lib/isEmail.js      |  6 +++---
 test/validators.test.js | 19 +++++++++++++++++++
 3 files changed, 23 insertions(+), 4 deletions(-)

diff --git a/README.md b/README.md
index c7518fd41..260c1742d 100644
--- a/README.md
+++ b/README.md
@@ -106,7 +106,7 @@ Validator                               | Description
 **isDecimal(str [, options])**             | check if the string represents a decimal number, such as 0.1, .3, 1.1, 1.00003, 4.0, etc.<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 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`.
diff --git a/src/lib/isEmail.js b/src/lib/isEmail.js
index 1aceca3cf..ebaf2a11d 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);
@@ -101,8 +101,8 @@ export default function isEmail(str, options) {
     return false;
   }
 
-  if (options.host_whitelist.length > 0 && !options.host_whitelist.includes(lower_domain)) {
-    return false;
+  if (options.host_whitelist.length > 0) {
+    return checkHost(lower_domain, options.host_whitelist);
   }
 
   let user = parts.join('@');
diff --git a/test/validators.test.js b/test/validators.test.js
index adf5d2cfe..49bc813ea 100644
--- a/test/validators.test.js
+++ b/test/validators.test.js
@@ -341,6 +341,25 @@ describe('Validators', () => {
     });
   });
 
+  it('should allow regular expressions in the host whitelist', () => {
+    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',

From 765ce247e3735463f243b15059c3e6af5ec4acd6 Mon Sep 17 00:00:00 2001
From: Wei Kang <weikangchia@gmail.com>
Date: Wed, 13 Nov 2024 16:49:40 +0800
Subject: [PATCH 2/5] update README.md

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 260c1742d..791fb53c5 100644
--- a/README.md
+++ b/README.md
@@ -106,7 +106,7 @@ Validator                               | Description
 **isDecimal(str [, options])**             | check if the string represents a decimal number, such as 0.1, .3, 1.1, 1.00003, 4.0, etc.<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 or regexp 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`.

From f25e4ff6b0671f619b435ad8d2cb0743e3be2f59 Mon Sep 17 00:00:00 2001
From: Wei Kang <weikangchia@gmail.com>
Date: Wed, 13 Nov 2024 16:50:33 +0800
Subject: [PATCH 3/5] refactor code

---
 src/lib/isEmail.js        |  4 ++--
 src/lib/isURL.js          | 15 +--------------
 src/lib/util/checkHost.js | 13 +++++++++++++
 test/validators.test.js   | 19 +++++++++++++++++++
 4 files changed, 35 insertions(+), 16 deletions(-)
 create mode 100644 src/lib/util/checkHost.js

diff --git a/src/lib/isEmail.js b/src/lib/isEmail.js
index ebaf2a11d..036e12c27 100644
--- a/src/lib/isEmail.js
+++ b/src/lib/isEmail.js
@@ -97,8 +97,8 @@ export default function isEmail(str, options) {
   const domain = parts.pop();
   const lower_domain = domain.toLowerCase();
 
-  if (options.host_blacklist.includes(lower_domain)) {
-    return false;
+  if (options.host_blacklist.length > 0) {
+    return !checkHost(lower_domain, options.host_blacklist);
   }
 
   if (options.host_whitelist.length > 0) {
diff --git a/src/lib/isURL.js b/src/lib/isURL.js
index 7529f4bde..9bfafbf0c 100644
--- a/src/lib/isURL.js
+++ b/src/lib/isURL.js
@@ -1,4 +1,5 @@
 import assertString from './util/assertString';
+import checkHost from './util/checkHost';
 
 import isFQDN from './isFQDN';
 import isIP from './isIP';
@@ -38,20 +39,6 @@ const default_url_options = {
 
 const wrapped_ipv6 = /^\[([^\]]+)\](?::([0-9]+))?$/;
 
-function isRegExp(obj) {
-  return Object.prototype.toString.call(obj) === '[object RegExp]';
-}
-
-function checkHost(host, matches) {
-  for (let i = 0; i < matches.length; i++) {
-    let match = matches[i];
-    if (host === match || (isRegExp(match) && match.test(host))) {
-      return true;
-    }
-  }
-  return false;
-}
-
 export default function isURL(url, options) {
   assertString(url);
   if (!url || /[\s<>]/.test(url)) {
diff --git a/src/lib/util/checkHost.js b/src/lib/util/checkHost.js
new file mode 100644
index 000000000..ed1dddefe
--- /dev/null
+++ b/src/lib/util/checkHost.js
@@ -0,0 +1,13 @@
+function isRegExp(obj) {
+  return Object.prototype.toString.call(obj) === '[object RegExp]';
+}
+
+export default function checkHost(host, matches) {
+  for (let i = 0; i < matches.length; i++) {
+    let match = matches[i];
+    if (host === match || (isRegExp(match) && match.test(host))) {
+      return true;
+    }
+  }
+  return false;
+}
diff --git a/test/validators.test.js b/test/validators.test.js
index 49bc813ea..1492ea167 100644
--- a/test/validators.test.js
+++ b/test/validators.test.js
@@ -325,6 +325,25 @@ describe('Validators', () => {
     });
   });
 
+  it('should allow regular expressions in the host blacklist', () => {
+    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',

From b5f78eb2be4981ac821051ed9a6896857ff95447 Mon Sep 17 00:00:00 2001
From: Wei Kang <weikangchia@gmail.com>
Date: Wed, 13 Nov 2024 17:06:45 +0800
Subject: [PATCH 4/5] update test name

---
 test/validators.test.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/test/validators.test.js b/test/validators.test.js
index 1492ea167..3ca067939 100644
--- a/test/validators.test.js
+++ b/test/validators.test.js
@@ -325,7 +325,7 @@ describe('Validators', () => {
     });
   });
 
-  it('should allow regular expressions in the host blacklist', () => {
+  it('should allow regular expressions in the host blacklist of isEmail', () => {
     test({
       validator: 'isEmail',
       args: [{
@@ -360,7 +360,7 @@ describe('Validators', () => {
     });
   });
 
-  it('should allow regular expressions in the host whitelist', () => {
+  it('should allow regular expressions in the host whitelist of isEmail', () => {
     test({
       validator: 'isEmail',
       args: [{

From 3842d1afec43c740ba34bd0f196364bc97aa61c1 Mon Sep 17 00:00:00 2001
From: Wei Kang <weikangchia@gmail.com>
Date: Wed, 13 Nov 2024 17:13:17 +0800
Subject: [PATCH 5/5] fix code logic for checkHost

---
 src/lib/isEmail.js | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/lib/isEmail.js b/src/lib/isEmail.js
index 036e12c27..abe465052 100644
--- a/src/lib/isEmail.js
+++ b/src/lib/isEmail.js
@@ -97,12 +97,12 @@ export default function isEmail(str, options) {
   const domain = parts.pop();
   const lower_domain = domain.toLowerCase();
 
-  if (options.host_blacklist.length > 0) {
-    return !checkHost(lower_domain, options.host_blacklist);
+  if (options.host_blacklist.length > 0 && checkHost(lower_domain, options.host_blacklist)) {
+    return false;
   }
 
-  if (options.host_whitelist.length > 0) {
-    return checkHost(lower_domain, options.host_whitelist);
+  if (options.host_whitelist.length > 0 && !checkHost(lower_domain, options.host_whitelist)) {
+    return false;
   }
 
   let user = parts.join('@');