Repository: validatorjs/validator.js (js)

Issue:
Union pay cards with 81 range fails isCreditCard check
**Describe the bug**
Using the 81* range UNION Pay card numbers does not pass isCreditCard check


**Examples**
Sample Test Cards: https://docs.adyen.com/development-resources/test-cards/test-card-numbers#china-unionpay

``validator.isCreditCard('8171999900000000021')``
false

The problem is the initial regex in https://github.com/validatorjs/validator.js/blob/master/src/lib/isCreditCard.js#L4

**Additional context**
Validator.js version: 13.6.0
Node.js version: 14
OS platform:l inux


Pull request:
feat(isCreditCard): Fixed regex for Union Pay Credit cards with 81 range
Fixes #1680 

Implemented regex for Union Pay Cards with 81 range by using the regex: `^(81[0-9]{14,17}))$`

## Checklist

- [x] PR contains only changes related; no stray files, etc.
- [x] README updated (where applicable)
- [x] Tests written (where applicable)


Patch:
diff --git a/README.md b/README.md
index 25af675..fc12f70 100644
--- a/README.md
+++ b/README.md
@@ -126,6 +126,7 @@ Validator                               | Description
 **isISO8601(str)**                      | 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.
+**isISO4217(str)**                      | check if the string is a valid [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) officially assigned currency code.
 **isISRC(str)**                         | check if the string is a [ISRC](https://en.wikipedia.org/wiki/International_Standard_Recording_Code).
 **isISSN(str [, options])**             | check if the string is an [ISSN](https://en.wikipedia.org/wiki/International_Standard_Serial_Number).<br/><br/>`options` is an object which defaults to `{ case_sensitive: false, require_hyphen: false }`. If `case_sensitive` is true, ISSNs with a lowercase `'x'` as the check digit are rejected.
 **isJSON(str [, options])**             | check if the string is valid JSON (note: uses JSON.parse).<br/><br/>`options` is an object which defaults to `{ allow_primitives: false }`. If `allow_primitives` is true, the primitives 'true', 'false' and 'null' are accepted as valid JSON values.
diff --git a/src/index.js b/src/index.js
index 59b813c..a0e8aa6 100644
--- a/src/index.js
+++ b/src/index.js
@@ -90,6 +90,7 @@ import isISO8601 from './lib/isISO8601';
 import isRFC3339 from './lib/isRFC3339';
 import isISO31661Alpha2 from './lib/isISO31661Alpha2';
 import isISO31661Alpha3 from './lib/isISO31661Alpha3';
+import isISO4217 from './lib/isISO4217';
 
 import isBase32 from './lib/isBase32';
 import isBase58 from './lib/isBase58';
@@ -198,6 +199,7 @@ const validator = {
   isRFC3339,
   isISO31661Alpha2,
   isISO31661Alpha3,
+  isISO4217,
   isBase32,
   isBase58,
   isBase64,
diff --git a/src/lib/isBIC.js b/src/lib/isBIC.js
index 240bfe1..b5576b2 100644
--- a/src/lib/isBIC.js
+++ b/src/lib/isBIC.js
@@ -9,7 +9,7 @@ export default function isBIC(str) {
 
   // toUpperCase() should be removed when a new major version goes out that changes
   // the regex to [A-Z] (per the spec).
-  if (CountryCodes.indexOf(str.slice(4, 6).toUpperCase()) < 0) {
+  if (!CountryCodes.has(str.slice(4, 6).toUpperCase())) {
     return false;
   }
 
diff --git a/src/lib/isCreditCard.js b/src/lib/isCreditCard.js
index 2b6f2b2..b11c4cc 100644
--- a/src/lib/isCreditCard.js
+++ b/src/lib/isCreditCard.js
@@ -1,7 +1,7 @@
 import assertString from './util/assertString';
 
 /* eslint-disable max-len */
-const creditCard = /^(?:4[0-9]{12}(?:[0-9]{3,6})?|5[1-5][0-9]{14}|(222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}|6(?:011|5[0-9][0-9])[0-9]{12,15}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11}|6[27][0-9]{14})$/;
+const creditCard = /^(?:4[0-9]{12}(?:[0-9]{3,6})?|5[1-5][0-9]{14}|(222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}|6(?:011|5[0-9][0-9])[0-9]{12,15}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11}|6[27][0-9]{14}|^(81[0-9]{14,17}))$/;
 /* eslint-enable max-len */
 
 export default function isCreditCard(str) {
diff --git a/src/lib/isDate.js b/src/lib/isDate.js
index 110c219..8b7862e 100644
--- a/src/lib/isDate.js
+++ b/src/lib/isDate.js
@@ -7,7 +7,7 @@ const default_date_options = {
 };
 
 function isValidFormat(format) {
-  return /(^(y{4}|y{2})[\/-](m{1,2})[\/-](d{1,2})$)|(^(m{1,2})[\/-](d{1,2})[\/-]((y{4}|y{2})$))|(^(d{1,2})[\/-](m{1,2})[\/-]((y{4}|y{2})$))/gi.test(format);
+  return /(^(y{4}|y{2})[.\/-](m{1,2})[.\/-](d{1,2})$)|(^(m{1,2})[.\/-](d{1,2})[.\/-]((y{4}|y{2})$))|(^(d{1,2})[.\/-](m{1,2})[.\/-]((y{4}|y{2})$))/gi.test(format);
 }
 
 function zip(date, format) {
diff --git a/src/lib/isISO31661Alpha2.js b/src/lib/isISO31661Alpha2.js
index e91ae87..e67bb1e 100644
--- a/src/lib/isISO31661Alpha2.js
+++ b/src/lib/isISO31661Alpha2.js
@@ -1,7 +1,7 @@
 import assertString from './util/assertString';
 
 // from https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
-const validISO31661Alpha2CountriesCodes = [
+const validISO31661Alpha2CountriesCodes = new Set([
   'AD', 'AE', 'AF', 'AG', 'AI', 'AL', 'AM', 'AO', 'AQ', 'AR', 'AS', 'AT', 'AU', 'AW', 'AX', 'AZ',
   'BA', 'BB', 'BD', 'BE', 'BF', 'BG', 'BH', 'BI', 'BJ', 'BL', 'BM', 'BN', 'BO', 'BQ', 'BR', 'BS', 'BT', 'BV', 'BW', 'BY', 'BZ',
   'CA', 'CC', 'CD', 'CF', 'CG', 'CH', 'CI', 'CK', 'CL', 'CM', 'CN', 'CO', 'CR', 'CU', 'CV', 'CW', 'CX', 'CY', 'CZ',
@@ -27,11 +27,11 @@ const validISO31661Alpha2CountriesCodes = [
   'WF', 'WS',
   'YE', 'YT',
   'ZA', 'ZM', 'ZW',
-];
+]);
 
 export default function isISO31661Alpha2(str) {
   assertString(str);
-  return validISO31661Alpha2CountriesCodes.indexOf(str.toUpperCase()) >= 0;
+  return validISO31661Alpha2CountriesCodes.has(str.toUpperCase());
 }
 
 export const CountryCodes = validISO31661Alpha2CountriesCodes;
diff --git a/src/lib/isISO31661Alpha3.js b/src/lib/isISO31661Alpha3.js
index 00c3dfb..34e552c 100644
--- a/src/lib/isISO31661Alpha3.js
+++ b/src/lib/isISO31661Alpha3.js
@@ -1,8 +1,7 @@
 import assertString from './util/assertString';
-import includes from './util/includes';
 
 // from https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3
-const validISO31661Alpha3CountriesCodes = [
+const validISO31661Alpha3CountriesCodes = new Set([
   'AFG', 'ALA', 'ALB', 'DZA', 'ASM', 'AND', 'AGO', 'AIA', 'ATA', 'ATG', 'ARG', 'ARM', 'ABW', 'AUS', 'AUT', 'AZE',
   'BHS', 'BHR', 'BGD', 'BRB', 'BLR', 'BEL', 'BLZ', 'BEN', 'BMU', 'BTN', 'BOL', 'BES', 'BIH', 'BWA', 'BVT', 'BRA',
   'IOT', 'BRN', 'BGR', 'BFA', 'BDI', 'KHM', 'CMR', 'CAN', 'CPV', 'CYM', 'CAF', 'TCD', 'CHL', 'CHN', 'CXR', 'CCK',
@@ -19,9 +18,9 @@ const validISO31661Alpha3CountriesCodes = [
   'ESP', 'LKA', 'SDN', 'SUR', 'SJM', 'SWZ', 'SWE', 'CHE', 'SYR', 'TWN', 'TJK', 'TZA', 'THA', 'TLS', 'TGO', 'TKL',
   'TON', 'TTO', 'TUN', 'TUR', 'TKM', 'TCA', 'TUV', 'UGA', 'UKR', 'ARE', 'GBR', 'USA', 'UMI', 'URY', 'UZB', 'VUT',
   'VEN', 'VNM', 'VGB', 'VIR', 'WLF', 'ESH', 'YEM', 'ZMB', 'ZWE',
-];
+]);
 
 export default function isISO31661Alpha3(str) {
   assertString(str);
-  return includes(validISO31661Alpha3CountriesCodes, str.toUpperCase());
+  return validISO31661Alpha3CountriesCodes.has(str.toUpperCase());
 }
diff --git a/src/lib/isISO4217.js b/src/lib/isISO4217.js
new file mode 100644
index 0000000..0738614
--- /dev/null
+++ b/src/lib/isISO4217.js
@@ -0,0 +1,38 @@
+import assertString from './util/assertString';
+
+// from https://en.wikipedia.org/wiki/ISO_4217
+const validISO4217CurrencyCodes = new Set([
+  'AED', 'AFN', 'ALL', 'AMD', 'ANG', 'AOA', 'ARS', 'AUD', 'AWG', 'AZN',
+  'BAM', 'BBD', 'BDT', 'BGN', 'BHD', 'BIF', 'BMD', 'BND', 'BOB', 'BOV', 'BRL', 'BSD', 'BTN', 'BWP', 'BYN', 'BZD',
+  'CAD', 'CDF', 'CHE', 'CHF', 'CHW', 'CLF', 'CLP', 'CNY', 'COP', 'COU', 'CRC', 'CUC', 'CUP', 'CVE', 'CZK',
+  'DJF', 'DKK', 'DOP', 'DZD',
+  'EGP', 'ERN', 'ETB', 'EUR',
+  'FJD', 'FKP',
+  'GBP', 'GEL', 'GHS', 'GIP', 'GMD', 'GNF', 'GTQ', 'GYD',
+  'HKD', 'HNL', 'HRK', 'HTG', 'HUF',
+  'IDR', 'ILS', 'INR', 'IQD', 'IRR', 'ISK',
+  'JMD', 'JOD', 'JPY',
+  'KES', 'KGS', 'KHR', 'KMF', 'KPW', 'KRW', 'KWD', 'KYD', 'KZT',
+  'LAK', 'LBP', 'LKR', 'LRD', 'LSL', 'LYD',
+  'MAD', 'MDL', 'MGA', 'MKD', 'MMK', 'MNT', 'MOP', 'MRU', 'MUR', 'MVR', 'MWK', 'MXN', 'MXV', 'MYR', 'MZN',
+  'NAD', 'NGN', 'NIO', 'NOK', 'NPR', 'NZD',
+  'OMR',
+  'PAB', 'PEN', 'PGK', 'PHP', 'PKR', 'PLN', 'PYG',
+  'QAR',
+  'RON', 'RSD', 'RUB', 'RWF',
+  'SAR', 'SBD', 'SCR', 'SDG', 'SEK', 'SGD', 'SHP', 'SLL', 'SOS', 'SRD', 'SSP', 'STN', 'SVC', 'SYP', 'SZL',
+  'THB', 'TJS', 'TMT', 'TND', 'TOP', 'TRY', 'TTD', 'TWD', 'TZS',
+  'UAH', 'UGX', 'USD', 'USN', 'UYI', 'UYU', 'UYW', 'UZS',
+  'VES', 'VND', 'VUV',
+  'WST',
+  'XAF', 'XAG', 'XAU', 'XBA', 'XBB', 'XBC', 'XBD', 'XCD', 'XDR', 'XOF', 'XPD', 'XPF', 'XPT', 'XSU', 'XTS', 'XUA', 'XXX',
+  'YER',
+  'ZAR', 'ZMW', 'ZWL',
+]);
+
+export default function isISO4217(str) {
+  assertString(str);
+  return validISO4217CurrencyCodes.has(str.toUpperCase());
+}
+
+export const CurrencyCodes = validISO4217CurrencyCodes;
diff --git a/src/lib/isMagnetURI.js b/src/lib/isMagnetURI.js
index 54a3d66..45b5c8e 100644
--- a/src/lib/isMagnetURI.js
+++ b/src/lib/isMagnetURI.js
@@ -1,6 +1,6 @@
 import assertString from './util/assertString';
 
-const magnetURI = /^magnet:\?xt=urn:[a-z0-9]+:[a-z0-9]{32,40}&dn=.+&tr=.+$/i;
+const magnetURI = /^magnet:\?xt(?:\.1)?=urn:(?:aich|bitprint|btih|ed2k|ed2khash|kzhash|md5|sha1|tree:tiger):[a-z0-9]{32}(?:[a-z0-9]{8})?($|&)/i;
 
 export default function isMagnetURI(url) {
   assertString(url);
diff --git a/src/lib/isPassportNumber.js b/src/lib/isPassportNumber.js
index b283a27..76284b1 100644
--- a/src/lib/isPassportNumber.js
+++ b/src/lib/isPassportNumber.js
@@ -17,7 +17,7 @@ const passportRegexByCountryCode = {
   BY: /^[A-Z]{2}\d{7}$/, // BELARUS
   CA: /^[A-Z]{2}\d{6}$/, // CANADA
   CH: /^[A-Z]\d{7}$/, // SWITZERLAND
-  CN: /^[GE]\d{8}$/, // CHINA [G=Ordinary, E=Electronic] followed by 8-digits
+  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
   CZ: /^\d{8}$/, // CZECH REPUBLIC
   DE: /^[CFGHJKLMNPRTVWXYZ0-9]{9}$/, // GERMANY


Test patch:
diff --git a/test/validators.js b/test/validators.js
index 6bb6072..6270d64 100644
--- a/test/validators.js
+++ b/test/validators.js
@@ -2475,9 +2475,15 @@ describe('Validators', () => {
       valid: [
         'G25352389',
         'E00160027',
+        'EA1234567',
       ],
       invalid: [
         'K0123456',
+        'E-1234567',
+        'G.1234567',
+        'GA1234567',
+        'EI1234567',
+        'GO1234567',
       ],
     });
 
@@ -4569,6 +4575,8 @@ describe('Validators', () => {
         '2718760626256570',
         '6765780016990268',
         '4716989580001715211',
+        '8171999927660000',
+        '8171999900000000021',
       ],
       invalid: [
         'foo',
@@ -9242,6 +9250,36 @@ describe('Validators', () => {
     });
   });
 
+  it('should validate ISO 4217 corrency codes', () => {
+    // from https://en.wikipedia.org/wiki/ISO_4217
+    test({
+      validator: 'isISO4217',
+      valid: [
+        'AED',
+        'aed',
+        'AUD',
+        'CUC',
+        'EUR',
+        'GBP',
+        'LYD',
+        'MYR',
+        'SGD',
+        'USD',
+      ],
+      invalid: [
+        '',
+        '$',
+        'US',
+        'us',
+        'AAA',
+        'aaa',
+        'RWA',
+        'EURO',
+        'euro',
+      ],
+    });
+  });
+
   it('should validate whitelisted characters', () => {
     test({
       validator: 'isWhitelisted',
@@ -9297,27 +9335,25 @@ describe('Validators', () => {
     test({
       validator: 'isMagnetURI',
       valid: [
-        'magnet:?xt=urn:btih:06E2A9683BF4DA92C73A661AC56F0ECC9C63C5B4&dn=helloword2000&tr=udp://helloworld:1337/announce',
-        'magnet:?xt=urn:btih:3E30322D5BFC7444B7B1D8DD42404B75D0531DFB&dn=world&tr=udp://world.com:1337',
-        'magnet:?xt=urn:btih:4ODKSDJBVMSDSNJVBCBFYFBKNRU875DW8D97DWC6&dn=helloworld&tr=udp://helloworld.com:1337',
-        'magnet:?xt=urn:btih:1GSHJVBDVDVJFYEHKFHEFIO8573898434JBFEGHD&dn=foo&tr=udp://foo.com:1337',
-        'magnet:?xt=urn:btih:MCJDCYUFHEUD6E2752T7UJNEKHSUGEJFGTFHVBJS&dn=bar&tr=udp://bar.com:1337',
-        'magnet:?xt=urn:btih:LAKDHWDHEBFRFVUFJENBYYTEUY837562JH2GEFYH&dn=foobar&tr=udp://foobar.com:1337',
-        'magnet:?xt=urn:btih:MKCJBHCBJDCU725TGEB3Y6RE8EJ2U267UNJFGUID&dn=test&tr=udp://test.com:1337',
-        'magnet:?xt=urn:btih:UHWY2892JNEJ2GTEYOMDNU67E8ICGICYE92JDUGH&dn=baz&tr=udp://baz.com:1337',
-        'magnet:?xt=urn:btih:HS263FG8U3GFIDHWD7829BYFCIXB78XIHG7CWCUG&dn=foz&tr=udp://foz.com:1337',
+        'magnet:?xt.1=urn:sha1:ABCDEFGHIJKLMNOPQRSTUVWXYZ123456&xt.2=urn:sha1:ABCDEFGHIJKLMNOPQRSTUVWXYZ123456',
+        'magnet:?xt=urn:btih:ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678901234&dn=helloword2000&tr=udp://helloworld:1337/announce',
+        'magnet:?xt=urn:btih:ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678901234&dn=foo',
+        'magnet:?xt=urn:btih:ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678901234&dn=&tr=&nonexisting=hello world',
+        'magnet:?xt=urn:md5:ABCDEFGHIJKLMNOPQRSTUVWXYZ123456',
+        'magnet:?xt=urn:tree:tiger:ABCDEFGHIJKLMNOPQRSTUVWXYZ123456',
+        'magnet:?xt=urn:ed2k:ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678901234',
       ],
       invalid: [
-        '',
-        ':?xt=urn:btih:06E2A9683BF4DA92C73A661AC56F0ECC9C63C5B4&dn=helloword2000&tr=udp://helloworld:1337/announce',
-        'magnett:?xt=urn:btih:3E30322D5BFC7444B7B1D8DD42404B75D0531DFB&dn=world&tr=udp://world.com:1337',
-        'xt=urn:btih:4ODKSDJBVMSDSNJVBCBFYFBKNRU875DW8D97DWC6&dn=helloworld&tr=udp://helloworld.com:1337',
-        'magneta:?xt=urn:btih:1GSHJVBDVDVJFYEHKFHEFIO8573898434JBFEGHD&dn=foo&tr=udp://foo.com:1337',
-        'magnet:?xt=uarn:btih:MCJDCYUFHEUD6E2752T7UJNEKHSUGEJFGTFHVBJS&dn=bar&tr=udp://bar.com:1337',
-        'magnet:?xt=urn:btihz&dn=foobar&tr=udp://foobar.com:1337',
-        'magnet:?xat=urn:btih:MKCJBHCBJDCU725TGEB3Y6RE8EJ2U267UNJFGUID&dn=test&tr=udp://test.com:1337',
-        'magnet::?xt=urn:btih:UHWY2892JNEJ2GTEYOMDNU67E8ICGICYE92JDUGH&dn=baz&tr=udp://baz.com:1337',
-        'magnet:?xt:btih:HS263FG8U3GFIDHWD7829BYFCIXB78XIHG7CWCUG&dn=foz&tr=udp://foz.com:1337',
+        ':?xt=urn:btih:ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678901234',
+        'xt=urn:btih:ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678901234',
+        'magneta:?xt=urn:btih:ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678901234',
+        'magnet:?xt=uarn:btih:ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678901234',
+        'magnet:?xt=urn:btihz',
+        'magnet::?xt=urn:btih:UHWY2892JNEJ2GTEYOMDNU67E8ICGICYE92JDUGH',
+        'magnet:?xt:btih:ABCDEFGHIJKLMNOPQRSTUVWXYZ',
+        'magnet:?xt:urn:nonexisting:ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678901234',
+        'magnet:?xt.2=urn:btih:ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678901234',
+        'magnet:?xt=urn:ed2k:ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678901234567890123456789ABCD',
       ],
     });
     /* eslint-enable max-len */
@@ -10674,6 +10710,25 @@ describe('Validators', () => {
         '2020/03-15',
       ],
     });
+    test({
+      validator: 'isDate',
+      args: [{ format: 'MM.DD.YYYY', delimiters: ['.'], strictMode: true }],
+      valid: [
+        '01.15.2020',
+        '02.15.2014',
+        '03.15.2014',
+        '02.29.2020',
+      ],
+      invalid: [
+        '2014-02-15',
+        '2020-02-29',
+        '15-07/2002',
+        new Date(),
+        new Date([2014, 2, 15]),
+        new Date('2014-03-15'),
+        '29.02.2020',
+      ],
+    });
   });
   it('should be valid license plate', () => {
     test({