tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

validator.js (54384B)


      1 /*
      2 * Copyright (c) 2016 Chris O"Hara <cohara87@gmail.com>
      3 *
      4 * Permission is hereby granted, free of charge, to any person obtaining
      5 * a copy of this software and associated documentation files (the
      6 * "Software"), to deal in the Software without restriction, including
      7 * without limitation the rights to use, copy, modify, merge, publish,
      8 * distribute, sublicense, and/or sell copies of the Software, and to
      9 * permit persons to whom the Software is furnished to do so, subject to
     10 * the following conditions:
     11 *
     12 * The above copyright notice and this permission notice shall be
     13 * included in all copies or substantial portions of the Software.
     14 *
     15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
     16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
     17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
     18 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
     19 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
     20 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
     21 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     22 *
     23 * NOTE: This utility is derived from https://github.com/chriso/validator.js but it is
     24 *       **NOT** the same as the original. We have made the following changes:
     25 *         - Changed mocha tests to xpcshell based tests.
     26 *         - Merged the following pull requests:
     27 *           - [isMobileNumber] Added Lithuanian number pattern #667
     28 *           - Hongkong mobile number #665
     29 *           - Added option to validate any phone locale #663
     30 *           - Added validation for ISRC strings #660
     31 *         - Added isRFC5646 for rfc 5646 #572
     32 *         - Added isSemVer for version numbers.
     33 *         - Added isRGBColor for RGB colors.
     34 *
     35 * UPDATING: PLEASE FOLLOW THE INSTRUCTIONS INSIDE UPDATING.md
     36 */
     37 
     38 "use strict";
     39 
     40 (function (global, factory) {
     41      typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
     42      typeof define === 'function' && define.amd ? define(factory) :
     43      (global.validator = factory());
     44 }(this, function () { 'use strict';
     45 
     46      function assertString(input) {
     47        if (typeof input !== 'string') {
     48          throw new TypeError('This library (validator.js) validates strings only');
     49        }
     50      }
     51 
     52      function toDate(date) {
     53        assertString(date);
     54        date = Date.parse(date);
     55        return !isNaN(date) ? new Date(date) : null;
     56      }
     57 
     58      function toFloat(str) {
     59        assertString(str);
     60        return parseFloat(str);
     61      }
     62 
     63      function toInt(str, radix) {
     64        assertString(str);
     65        return parseInt(str, radix || 10);
     66      }
     67 
     68      function toBoolean(str, strict) {
     69        assertString(str);
     70        if (strict) {
     71          return str === '1' || str === 'true';
     72        }
     73        return str !== '0' && str !== 'false' && str !== '';
     74      }
     75 
     76      function equals(str, comparison) {
     77        assertString(str);
     78        return str === comparison;
     79      }
     80 
     81      var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
     82        return typeof obj;
     83      } : function (obj) {
     84        return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
     85      };
     86 
     87      var asyncGenerator = function () {
     88        function AwaitValue(value) {
     89          this.value = value;
     90        }
     91 
     92        function AsyncGenerator(gen) {
     93          var front, back;
     94 
     95          function send(key, arg) {
     96            return new Promise(function (resolve, reject) {
     97              var request = {
     98                key: key,
     99                arg: arg,
    100                resolve: resolve,
    101                reject: reject,
    102                next: null
    103              };
    104 
    105              if (back) {
    106                back = back.next = request;
    107              } else {
    108                front = back = request;
    109                resume(key, arg);
    110              }
    111            });
    112          }
    113 
    114          function resume(key, arg) {
    115            try {
    116              var result = gen[key](arg);
    117              var value = result.value;
    118 
    119              if (value instanceof AwaitValue) {
    120                Promise.resolve(value.value).then(function (arg) {
    121                  resume("next", arg);
    122                }, function (arg) {
    123                  resume("throw", arg);
    124                });
    125              } else {
    126                settle(result.done ? "return" : "normal", result.value);
    127              }
    128            } catch (err) {
    129              settle("throw", err);
    130            }
    131          }
    132 
    133          function settle(type, value) {
    134            switch (type) {
    135              case "return":
    136                front.resolve({
    137                  value: value,
    138                  done: true
    139                });
    140                break;
    141 
    142              case "throw":
    143                front.reject(value);
    144                break;
    145 
    146              default:
    147                front.resolve({
    148                  value: value,
    149                  done: false
    150                });
    151                break;
    152            }
    153 
    154            front = front.next;
    155 
    156            if (front) {
    157              resume(front.key, front.arg);
    158            } else {
    159              back = null;
    160            }
    161          }
    162 
    163          this._invoke = send;
    164 
    165          if (typeof gen.return !== "function") {
    166            this.return = undefined;
    167          }
    168        }
    169 
    170        if (typeof Symbol === "function" && Symbol.asyncIterator) {
    171          AsyncGenerator.prototype[Symbol.asyncIterator] = function () {
    172            return this;
    173          };
    174        }
    175 
    176        AsyncGenerator.prototype.next = function (arg) {
    177          return this._invoke("next", arg);
    178        };
    179 
    180        AsyncGenerator.prototype.throw = function (arg) {
    181          return this._invoke("throw", arg);
    182        };
    183 
    184        AsyncGenerator.prototype.return = function (arg) {
    185          return this._invoke("return", arg);
    186        };
    187 
    188        return {
    189          wrap: function (fn) {
    190            return function () {
    191              return new AsyncGenerator(fn.apply(this, arguments));
    192            };
    193          },
    194          await: function (value) {
    195            return new AwaitValue(value);
    196          }
    197        };
    198      }();
    199 
    200      function toString(input) {
    201        if ((typeof input === 'undefined' ? 'undefined' : _typeof(input)) === 'object' && input !== null) {
    202          if (typeof input.toString === 'function') {
    203            input = input.toString();
    204          } else {
    205            input = '[object Object]';
    206          }
    207        } else if (input === null || typeof input === 'undefined' || isNaN(input) && !input.length) {
    208          input = '';
    209        }
    210        return String(input);
    211      }
    212 
    213      function contains(str, elem) {
    214        assertString(str);
    215        return str.indexOf(toString(elem)) >= 0;
    216      }
    217 
    218      function matches(str, pattern, modifiers) {
    219        assertString(str);
    220        if (Object.prototype.toString.call(pattern) !== '[object RegExp]') {
    221          pattern = new RegExp(pattern, modifiers);
    222        }
    223        return pattern.test(str);
    224      }
    225 
    226      function merge() {
    227        var obj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
    228        var defaults = arguments[1];
    229 
    230        for (var key in defaults) {
    231          if (typeof obj[key] === 'undefined') {
    232            obj[key] = defaults[key];
    233          }
    234        }
    235        return obj;
    236      }
    237 
    238      /* eslint-disable prefer-rest-params */
    239      function isByteLength(str, options) {
    240        assertString(str);
    241        var min = void 0;
    242        var max = void 0;
    243        if ((typeof options === 'undefined' ? 'undefined' : _typeof(options)) === 'object') {
    244          min = options.min || 0;
    245          max = options.max;
    246        } else {
    247          // backwards compatibility: isByteLength(str, min [, max])
    248          min = arguments[1];
    249          max = arguments[2];
    250        }
    251        var len = encodeURI(str).split(/%..|./).length - 1;
    252        return len >= min && (typeof max === 'undefined' || len <= max);
    253      }
    254 
    255      var default_fqdn_options = {
    256        require_tld: true,
    257        allow_underscores: false,
    258        allow_trailing_dot: false
    259      };
    260 
    261      function isFDQN(str, options) {
    262        assertString(str);
    263        options = merge(options, default_fqdn_options);
    264 
    265        /* Remove the optional trailing dot before checking validity */
    266        if (options.allow_trailing_dot && str[str.length - 1] === '.') {
    267          str = str.substring(0, str.length - 1);
    268        }
    269        var parts = str.split('.');
    270        if (options.require_tld) {
    271          var tld = parts.pop();
    272          if (!parts.length || !/^([a-z\u00a1-\uffff]{2,}|xn[a-z0-9-]{2,})$/i.test(tld)) {
    273            return false;
    274          }
    275        }
    276        for (var part, i = 0; i < parts.length; i++) {
    277          part = parts[i];
    278          if (options.allow_underscores) {
    279            part = part.replace(/_/g, '');
    280          }
    281          if (!/^[a-z\u00a1-\uffff0-9-]+$/i.test(part)) {
    282            return false;
    283          }
    284          if (/[\uff01-\uff5e]/.test(part)) {
    285            // disallow full-width chars
    286            return false;
    287          }
    288          if (part[0] === '-' || part[part.length - 1] === '-') {
    289            return false;
    290          }
    291        }
    292        return true;
    293      }
    294 
    295      var default_email_options = {
    296        allow_display_name: false,
    297        require_display_name: false,
    298        allow_utf8_local_part: true,
    299        require_tld: true
    300      };
    301 
    302      /* eslint-disable max-len */
    303      /* eslint-disable no-control-regex */
    304      var displayName = /^[a-z\d!#\$%&'\*\+\-\/=\?\^_`{\|}~\.\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+[a-z\d!#\$%&'\*\+\-\/=\?\^_`{\|}~\.\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF\s]*<(.+)>$/i;
    305      var emailUserPart = /^[a-z\d!#\$%&'\*\+\-\/=\?\^_`{\|}~]+$/i;
    306      var quotedEmailUser = /^([\s\x01-\x08\x0b\x0c\x0e-\x1f\x7f\x21\x23-\x5b\x5d-\x7e]|(\\[\x01-\x09\x0b\x0c\x0d-\x7f]))*$/i;
    307      var emailUserUtf8Part = /^[a-z\d!#\$%&'\*\+\-\/=\?\^_`{\|}~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+$/i;
    308      var quotedEmailUserUtf8 = /^([\s\x01-\x08\x0b\x0c\x0e-\x1f\x7f\x21\x23-\x5b\x5d-\x7e\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|(\\[\x01-\x09\x0b\x0c\x0d-\x7f\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))*$/i;
    309      /* eslint-enable max-len */
    310      /* eslint-enable no-control-regex */
    311 
    312      function isEmail(str, options) {
    313        assertString(str);
    314        options = merge(options, default_email_options);
    315 
    316        if (options.require_display_name || options.allow_display_name) {
    317          var display_email = str.match(displayName);
    318          if (display_email) {
    319            str = display_email[1];
    320          } else if (options.require_display_name) {
    321            return false;
    322          }
    323        }
    324 
    325        var parts = str.split('@');
    326        var domain = parts.pop();
    327        var user = parts.join('@');
    328 
    329        var lower_domain = domain.toLowerCase();
    330        if (lower_domain === 'gmail.com' || lower_domain === 'googlemail.com') {
    331          user = user.replace(/\./g, '').toLowerCase();
    332        }
    333 
    334        if (!isByteLength(user, { max: 64 }) || !isByteLength(domain, { max: 256 })) {
    335          return false;
    336        }
    337 
    338        if (!isFDQN(domain, { require_tld: options.require_tld })) {
    339          return false;
    340        }
    341 
    342        if (user[0] === '"') {
    343          user = user.slice(1, user.length - 1);
    344          return options.allow_utf8_local_part ? quotedEmailUserUtf8.test(user) : quotedEmailUser.test(user);
    345        }
    346 
    347        var pattern = options.allow_utf8_local_part ? emailUserUtf8Part : emailUserPart;
    348 
    349        var user_parts = user.split('.');
    350        for (var i = 0; i < user_parts.length; i++) {
    351          if (!pattern.test(user_parts[i])) {
    352            return false;
    353          }
    354        }
    355 
    356        return true;
    357      }
    358 
    359      var ipv4Maybe = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
    360      var ipv6Block = /^[0-9A-F]{1,4}$/i;
    361 
    362      function isIP(str) {
    363        var version = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
    364 
    365        assertString(str);
    366        version = String(version);
    367        if (!version) {
    368          return isIP(str, 4) || isIP(str, 6);
    369        } else if (version === '4') {
    370          if (!ipv4Maybe.test(str)) {
    371            return false;
    372          }
    373          var parts = str.split('.').sort(function (a, b) {
    374            return a - b;
    375          });
    376          return parts[3] <= 255;
    377        } else if (version === '6') {
    378          var blocks = str.split(':');
    379          var foundOmissionBlock = false; // marker to indicate ::
    380 
    381          // At least some OS accept the last 32 bits of an IPv6 address
    382          // (i.e. 2 of the blocks) in IPv4 notation, and RFC 3493 says
    383          // that '::ffff:a.b.c.d' is valid for IPv4-mapped IPv6 addresses,
    384          // and '::a.b.c.d' is deprecated, but also valid.
    385          var foundIPv4TransitionBlock = isIP(blocks[blocks.length - 1], 4);
    386          var expectedNumberOfBlocks = foundIPv4TransitionBlock ? 7 : 8;
    387 
    388          if (blocks.length > expectedNumberOfBlocks) {
    389            return false;
    390          }
    391          // initial or final ::
    392          if (str === '::') {
    393            return true;
    394          } else if (str.substr(0, 2) === '::') {
    395            blocks.shift();
    396            blocks.shift();
    397            foundOmissionBlock = true;
    398          } else if (str.substr(str.length - 2) === '::') {
    399            blocks.pop();
    400            blocks.pop();
    401            foundOmissionBlock = true;
    402          }
    403 
    404          for (var i = 0; i < blocks.length; ++i) {
    405            // test for a :: which can not be at the string start/end
    406            // since those cases have been handled above
    407            if (blocks[i] === '' && i > 0 && i < blocks.length - 1) {
    408              if (foundOmissionBlock) {
    409                return false; // multiple :: in address
    410              }
    411              foundOmissionBlock = true;
    412            } else if (foundIPv4TransitionBlock && i === blocks.length - 1) {
    413              // it has been checked before that the last
    414              // block is a valid IPv4 address
    415            } else if (!ipv6Block.test(blocks[i])) {
    416              return false;
    417            }
    418          }
    419          if (foundOmissionBlock) {
    420            return blocks.length >= 1;
    421          }
    422          return blocks.length === expectedNumberOfBlocks;
    423        }
    424        return false;
    425      }
    426 
    427      var default_url_options = {
    428        protocols: ['http', 'https', 'ftp'],
    429        require_tld: true,
    430        require_protocol: false,
    431        require_host: true,
    432        require_valid_protocol: true,
    433        allow_underscores: false,
    434        allow_trailing_dot: false,
    435        allow_protocol_relative_urls: false
    436      };
    437 
    438      var wrapped_ipv6 = /^\[([^\]]+)\](?::([0-9]+))?$/;
    439 
    440      function isRegExp(obj) {
    441        return Object.prototype.toString.call(obj) === '[object RegExp]';
    442      }
    443 
    444      function checkHost(host, matches) {
    445        for (var i = 0; i < matches.length; i++) {
    446          var match = matches[i];
    447          if (host === match || isRegExp(match) && match.test(host)) {
    448            return true;
    449          }
    450        }
    451        return false;
    452      }
    453 
    454      function isURL(url, options) {
    455        assertString(url);
    456        if (!url || url.length >= 2083 || /[\s<>]/.test(url)) {
    457          return false;
    458        }
    459        if (url.indexOf('mailto:') === 0) {
    460          return false;
    461        }
    462        options = merge(options, default_url_options);
    463        var protocol = void 0,
    464            auth = void 0,
    465            host = void 0,
    466            hostname = void 0,
    467            port = void 0,
    468            port_str = void 0,
    469            split = void 0,
    470            ipv6 = void 0;
    471 
    472        split = url.split('#');
    473        url = split.shift();
    474 
    475        split = url.split('?');
    476        url = split.shift();
    477 
    478        split = url.split('://');
    479        if (split.length > 1) {
    480          protocol = split.shift();
    481          if (options.require_valid_protocol && options.protocols.indexOf(protocol) === -1) {
    482            return false;
    483          }
    484        } else if (options.require_protocol) {
    485          return false;
    486        } else if (options.allow_protocol_relative_urls && url.substr(0, 2) === '//') {
    487          split[0] = url.substr(2);
    488        }
    489        url = split.join('://');
    490 
    491        split = url.split('/');
    492        url = split.shift();
    493 
    494        if (url === '' && !options.require_host) {
    495          return true;
    496        }
    497 
    498        split = url.split('@');
    499        if (split.length > 1) {
    500          auth = split.shift();
    501          if (auth.indexOf(':') >= 0 && auth.split(':').length > 2) {
    502            return false;
    503          }
    504        }
    505        hostname = split.join('@');
    506 
    507        port_str = ipv6 = null;
    508        var ipv6_match = hostname.match(wrapped_ipv6);
    509        if (ipv6_match) {
    510          host = '';
    511          ipv6 = ipv6_match[1];
    512          port_str = ipv6_match[2] || null;
    513        } else {
    514          split = hostname.split(':');
    515          host = split.shift();
    516          if (split.length) {
    517            port_str = split.join(':');
    518          }
    519        }
    520 
    521        if (port_str !== null) {
    522          port = parseInt(port_str, 10);
    523          if (!/^[0-9]+$/.test(port_str) || port <= 0 || port > 65535) {
    524            return false;
    525          }
    526        }
    527 
    528        if (!isIP(host) && !isFDQN(host, options) && (!ipv6 || !isIP(ipv6, 6)) && host !== 'localhost') {
    529          return false;
    530        }
    531 
    532        host = host || ipv6;
    533 
    534        if (options.host_whitelist && !checkHost(host, options.host_whitelist)) {
    535          return false;
    536        }
    537        if (options.host_blacklist && checkHost(host, options.host_blacklist)) {
    538          return false;
    539        }
    540 
    541        return true;
    542      }
    543 
    544      var macAddress = /^([0-9a-fA-F][0-9a-fA-F]:){5}([0-9a-fA-F][0-9a-fA-F])$/;
    545 
    546      function isMACAddress(str) {
    547        assertString(str);
    548        return macAddress.test(str);
    549      }
    550 
    551      function isBoolean(str) {
    552        assertString(str);
    553        return ['true', 'false', '1', '0'].indexOf(str) >= 0;
    554      }
    555 
    556      var alpha = {
    557        'en-US': /^[A-Z]+$/i,
    558        'cs-CZ': /^[A-ZÁČĎÉĚÍŇÓŘŠŤÚŮÝŽ]+$/i,
    559        'da-DK': /^[A-ZÆØÅ]+$/i,
    560        'de-DE': /^[A-ZÄÖÜß]+$/i,
    561        'es-ES': /^[A-ZÁÉÍÑÓÚÜ]+$/i,
    562        'fr-FR': /^[A-ZÀÂÆÇÉÈÊËÏÎÔŒÙÛÜŸ]+$/i,
    563        'nl-NL': /^[A-ZÉËÏÓÖÜ]+$/i,
    564        'hu-HU': /^[A-ZÁÉÍÓÖŐÚÜŰ]+$/i,
    565        'pl-PL': /^[A-ZĄĆĘŚŁŃÓŻŹ]+$/i,
    566        'pt-PT': /^[A-ZÃÁÀÂÇÉÊÍÕÓÔÚÜ]+$/i,
    567        'ru-RU': /^[А-ЯЁ]+$/i,
    568        'sr-RS@latin': /^[A-ZČĆŽŠĐ]+$/i,
    569        'sr-RS': /^[А-ЯЂЈЉЊЋЏ]+$/i,
    570        'tr-TR': /^[A-ZÇĞİıÖŞÜ]+$/i,
    571        'uk-UA': /^[А-ЩЬЮЯЄIЇҐ]+$/i,
    572        ar: /^[ءآأؤإئابةتثجحخدذرزسشصضطظعغفقكلمنهوىيًٌٍَُِّْٰ]+$/
    573      };
    574 
    575      var alphanumeric = {
    576        'en-US': /^[0-9A-Z]+$/i,
    577        'cs-CZ': /^[0-9A-ZÁČĎÉĚÍŇÓŘŠŤÚŮÝŽ]+$/i,
    578        'da-DK': /^[0-9A-ZÆØÅ]$/i,
    579        'de-DE': /^[0-9A-ZÄÖÜß]+$/i,
    580        'es-ES': /^[0-9A-ZÁÉÍÑÓÚÜ]+$/i,
    581        'fr-FR': /^[0-9A-ZÀÂÆÇÉÈÊËÏÎÔŒÙÛÜŸ]+$/i,
    582        'hu-HU': /^[0-9A-ZÁÉÍÓÖŐÚÜŰ]+$/i,
    583        'nl-NL': /^[0-9A-ZÉËÏÓÖÜ]+$/i,
    584        'pl-PL': /^[0-9A-ZĄĆĘŚŁŃÓŻŹ]+$/i,
    585        'pt-PT': /^[0-9A-ZÃÁÀÂÇÉÊÍÕÓÔÚÜ]+$/i,
    586        'ru-RU': /^[0-9А-ЯЁ]+$/i,
    587        'sr-RS@latin': /^[0-9A-ZČĆŽŠĐ]+$/i,
    588        'sr-RS': /^[0-9А-ЯЂЈЉЊЋЏ]+$/i,
    589        'tr-TR': /^[0-9A-ZÇĞİıÖŞÜ]+$/i,
    590        'uk-UA': /^[0-9А-ЩЬЮЯЄIЇҐ]+$/i,
    591        ar: /^[٠١٢٣٤٥٦٧٨٩0-9ءآأؤإئابةتثجحخدذرزسشصضطظعغفقكلمنهوىيًٌٍَُِّْٰ]+$/
    592      };
    593 
    594      var englishLocales = ['AU', 'GB', 'HK', 'IN', 'NZ', 'ZA', 'ZM'];
    595 
    596      for (var locale, i = 0; i < englishLocales.length; i++) {
    597        locale = 'en-' + englishLocales[i];
    598        alpha[locale] = alpha['en-US'];
    599        alphanumeric[locale] = alphanumeric['en-US'];
    600      }
    601 
    602      alpha['pt-BR'] = alpha['pt-PT'];
    603      alphanumeric['pt-BR'] = alphanumeric['pt-PT'];
    604 
    605      // Source: http://www.localeplanet.com/java/
    606      var arabicLocales = ['AE', 'BH', 'DZ', 'EG', 'IQ', 'JO', 'KW', 'LB', 'LY', 'MA', 'QM', 'QA', 'SA', 'SD', 'SY', 'TN', 'YE'];
    607 
    608      for (var _locale, _i = 0; _i < arabicLocales.length; _i++) {
    609        _locale = 'ar-' + arabicLocales[_i];
    610        alpha[_locale] = alpha.ar;
    611        alphanumeric[_locale] = alphanumeric.ar;
    612      }
    613 
    614      function isAlpha(str) {
    615        var locale = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'en-US';
    616 
    617        assertString(str);
    618        if (locale in alpha) {
    619          return alpha[locale].test(str);
    620        }
    621        throw new Error('Invalid locale \'' + locale + '\'');
    622      }
    623 
    624      function isAlphanumeric(str) {
    625        var locale = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'en-US';
    626 
    627        assertString(str);
    628        if (locale in alphanumeric) {
    629          return alphanumeric[locale].test(str);
    630        }
    631        throw new Error('Invalid locale \'' + locale + '\'');
    632      }
    633 
    634      var numeric = /^[-+]?[0-9]+$/;
    635 
    636      function isNumeric(str) {
    637        assertString(str);
    638        return numeric.test(str);
    639      }
    640 
    641      function isLowercase(str) {
    642        assertString(str);
    643        return str === str.toLowerCase();
    644      }
    645 
    646      function isUppercase(str) {
    647        assertString(str);
    648        return str === str.toUpperCase();
    649      }
    650 
    651      /* eslint-disable no-control-regex */
    652      var ascii = /^[\x00-\x7F]+$/;
    653      /* eslint-enable no-control-regex */
    654 
    655      function isAscii(str) {
    656        assertString(str);
    657        return ascii.test(str);
    658      }
    659 
    660      var fullWidth = /[^\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]/;
    661 
    662      function isFullWidth(str) {
    663        assertString(str);
    664        return fullWidth.test(str);
    665      }
    666 
    667      var halfWidth = /[\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]/;
    668 
    669      function isHalfWidth(str) {
    670        assertString(str);
    671        return halfWidth.test(str);
    672      }
    673 
    674      function isVariableWidth(str) {
    675        assertString(str);
    676        return fullWidth.test(str) && halfWidth.test(str);
    677      }
    678 
    679      /* eslint-disable no-control-regex */
    680      var multibyte = /[^\x00-\x7F]/;
    681      /* eslint-enable no-control-regex */
    682 
    683      function isMultibyte(str) {
    684        assertString(str);
    685        return multibyte.test(str);
    686      }
    687 
    688      var surrogatePair = /[\uD800-\uDBFF][\uDC00-\uDFFF]/;
    689 
    690      function isSurrogatePair(str) {
    691        assertString(str);
    692        return surrogatePair.test(str);
    693      }
    694 
    695      var int = /^(?:[-+]?(?:0|[1-9][0-9]*))$/;
    696      var intLeadingZeroes = /^[-+]?[0-9]+$/;
    697 
    698      function isInt(str, options) {
    699        assertString(str);
    700        options = options || {};
    701 
    702        // Get the regex to use for testing, based on whether
    703        // leading zeroes are allowed or not.
    704        var regex = options.hasOwnProperty('allow_leading_zeroes') && !options.allow_leading_zeroes ? int : intLeadingZeroes;
    705 
    706        // Check min/max/lt/gt
    707        var minCheckPassed = !options.hasOwnProperty('min') || str >= options.min;
    708        var maxCheckPassed = !options.hasOwnProperty('max') || str <= options.max;
    709        var ltCheckPassed = !options.hasOwnProperty('lt') || str < options.lt;
    710        var gtCheckPassed = !options.hasOwnProperty('gt') || str > options.gt;
    711 
    712        return regex.test(str) && minCheckPassed && maxCheckPassed && ltCheckPassed && gtCheckPassed;
    713      }
    714 
    715      var float = /^(?:[-+])?(?:[0-9]+)?(?:\.[0-9]*)?(?:[eE][\+\-]?(?:[0-9]+))?$/;
    716 
    717      function isFloat(str, options) {
    718        assertString(str);
    719        options = options || {};
    720        if (str === '' || str === '.') {
    721          return false;
    722        }
    723        return float.test(str) && (!options.hasOwnProperty('min') || str >= options.min) && (!options.hasOwnProperty('max') || str <= options.max) && (!options.hasOwnProperty('lt') || str < options.lt) && (!options.hasOwnProperty('gt') || str > options.gt);
    724      }
    725 
    726      var decimal = /^[-+]?([0-9]+|\.[0-9]+|[0-9]+\.[0-9]+)$/;
    727 
    728      function isDecimal(str) {
    729        assertString(str);
    730        return str !== '' && decimal.test(str);
    731      }
    732 
    733      var hexadecimal = /^[0-9A-F]+$/i;
    734 
    735      function isHexadecimal(str) {
    736        assertString(str);
    737        return hexadecimal.test(str);
    738      }
    739 
    740      function isDivisibleBy(str, num) {
    741        assertString(str);
    742        return toFloat(str) % parseInt(num, 10) === 0;
    743      }
    744 
    745      var hexcolor = /^#?([0-9A-F]{3}|[0-9A-F]{6})$/i;
    746 
    747      function isHexColor(str) {
    748        assertString(str);
    749        return hexcolor.test(str);
    750      }
    751 
    752      var md5 = /^[a-f0-9]{32}$/;
    753 
    754      function isMD5(str) {
    755        assertString(str);
    756        return md5.test(str);
    757      }
    758 
    759      function isJSON(str) {
    760        assertString(str);
    761        try {
    762          var obj = JSON.parse(str);
    763          return !!obj && (typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) === 'object';
    764        } catch (e) {/* ignore */}
    765        return false;
    766      }
    767 
    768      function isEmpty(str) {
    769        assertString(str);
    770        return str.length === 0;
    771      }
    772 
    773      /* eslint-disable prefer-rest-params */
    774      function isLength(str, options) {
    775        assertString(str);
    776        var min = void 0;
    777        var max = void 0;
    778        if ((typeof options === 'undefined' ? 'undefined' : _typeof(options)) === 'object') {
    779          min = options.min || 0;
    780          max = options.max;
    781        } else {
    782          // backwards compatibility: isLength(str, min [, max])
    783          min = arguments[1];
    784          max = arguments[2];
    785        }
    786        var surrogatePairs = str.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g) || [];
    787        var len = str.length - surrogatePairs.length;
    788        return len >= min && (typeof max === 'undefined' || len <= max);
    789      }
    790 
    791      var uuid = {
    792        3: /^[0-9A-F]{8}-[0-9A-F]{4}-3[0-9A-F]{3}-[0-9A-F]{4}-[0-9A-F]{12}$/i,
    793        4: /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i,
    794        5: /^[0-9A-F]{8}-[0-9A-F]{4}-5[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i,
    795        all: /^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i
    796      };
    797 
    798      function isUUID(str) {
    799        var version = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'all';
    800 
    801        assertString(str);
    802        var pattern = uuid[version];
    803        return pattern && pattern.test(str);
    804      }
    805 
    806      function isMongoId(str) {
    807        assertString(str);
    808        return isHexadecimal(str) && str.length === 24;
    809      }
    810 
    811      function isAfter(str) {
    812        var date = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : String(new Date());
    813 
    814        assertString(str);
    815        var comparison = toDate(date);
    816        var original = toDate(str);
    817        return !!(original && comparison && original > comparison);
    818      }
    819 
    820      function isBefore(str) {
    821        var date = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : String(new Date());
    822 
    823        assertString(str);
    824        var comparison = toDate(date);
    825        var original = toDate(str);
    826        return !!(original && comparison && original < comparison);
    827      }
    828 
    829      function isIn(str, options) {
    830        assertString(str);
    831        var i = void 0;
    832        if (Object.prototype.toString.call(options) === '[object Array]') {
    833          var array = [];
    834          for (i in options) {
    835            if ({}.hasOwnProperty.call(options, i)) {
    836              array[i] = toString(options[i]);
    837            }
    838          }
    839          return array.indexOf(str) >= 0;
    840        } else if ((typeof options === 'undefined' ? 'undefined' : _typeof(options)) === 'object') {
    841          return options.hasOwnProperty(str);
    842        } else if (options && typeof options.indexOf === 'function') {
    843          return options.indexOf(str) >= 0;
    844        }
    845        return false;
    846      }
    847 
    848      /* eslint-disable max-len */
    849      var creditCard = /^(?:4[0-9]{12}(?:[0-9]{3})?|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}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})|62[0-9]{14}$/;
    850      /* eslint-enable max-len */
    851 
    852      function isCreditCard(str) {
    853        assertString(str);
    854        var sanitized = str.replace(/[^0-9]+/g, '');
    855        if (!creditCard.test(sanitized)) {
    856          return false;
    857        }
    858        var sum = 0;
    859        var digit = void 0;
    860        var tmpNum = void 0;
    861        var shouldDouble = void 0;
    862        for (var i = sanitized.length - 1; i >= 0; i--) {
    863          digit = sanitized.substring(i, i + 1);
    864          tmpNum = parseInt(digit, 10);
    865          if (shouldDouble) {
    866            tmpNum *= 2;
    867            if (tmpNum >= 10) {
    868              sum += tmpNum % 10 + 1;
    869            } else {
    870              sum += tmpNum;
    871            }
    872          } else {
    873            sum += tmpNum;
    874          }
    875          shouldDouble = !shouldDouble;
    876        }
    877        return !!(sum % 10 === 0 ? sanitized : false);
    878      }
    879 
    880      var isin = /^[A-Z]{2}[0-9A-Z]{9}[0-9]$/;
    881 
    882      function isISIN(str) {
    883        assertString(str);
    884        if (!isin.test(str)) {
    885          return false;
    886        }
    887 
    888        var checksumStr = str.replace(/[A-Z]/g, function (character) {
    889          return parseInt(character, 36);
    890        });
    891 
    892        var sum = 0;
    893        var digit = void 0;
    894        var tmpNum = void 0;
    895        var shouldDouble = true;
    896        for (var i = checksumStr.length - 2; i >= 0; i--) {
    897          digit = checksumStr.substring(i, i + 1);
    898          tmpNum = parseInt(digit, 10);
    899          if (shouldDouble) {
    900            tmpNum *= 2;
    901            if (tmpNum >= 10) {
    902              sum += tmpNum + 1;
    903            } else {
    904              sum += tmpNum;
    905            }
    906          } else {
    907            sum += tmpNum;
    908          }
    909          shouldDouble = !shouldDouble;
    910        }
    911 
    912        return parseInt(str.substr(str.length - 1), 10) === (10000 - sum) % 10;
    913      }
    914 
    915      var isbn10Maybe = /^(?:[0-9]{9}X|[0-9]{10})$/;
    916      var isbn13Maybe = /^(?:[0-9]{13})$/;
    917      var factor = [1, 3];
    918 
    919      function isISBN(str) {
    920        var version = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
    921 
    922        assertString(str);
    923        version = String(version);
    924        if (!version) {
    925          return isISBN(str, 10) || isISBN(str, 13);
    926        }
    927        var sanitized = str.replace(/[\s-]+/g, '');
    928        var checksum = 0;
    929        var i = void 0;
    930        if (version === '10') {
    931          if (!isbn10Maybe.test(sanitized)) {
    932            return false;
    933          }
    934          for (i = 0; i < 9; i++) {
    935            checksum += (i + 1) * sanitized.charAt(i);
    936          }
    937          if (sanitized.charAt(9) === 'X') {
    938            checksum += 10 * 10;
    939          } else {
    940            checksum += 10 * sanitized.charAt(9);
    941          }
    942          if (checksum % 11 === 0) {
    943            return !!sanitized;
    944          }
    945        } else if (version === '13') {
    946          if (!isbn13Maybe.test(sanitized)) {
    947            return false;
    948          }
    949          for (i = 0; i < 12; i++) {
    950            checksum += factor[i % 2] * sanitized.charAt(i);
    951          }
    952          if (sanitized.charAt(12) - (10 - checksum % 10) % 10 === 0) {
    953            return !!sanitized;
    954          }
    955        }
    956        return false;
    957      }
    958 
    959      var issn = '^\\d{4}-?\\d{3}[\\dX]$';
    960 
    961      function isISSN(str) {
    962        var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
    963 
    964        assertString(str);
    965        var testIssn = issn;
    966        testIssn = options.require_hyphen ? testIssn.replace('?', '') : testIssn;
    967        testIssn = options.case_sensitive ? new RegExp(testIssn) : new RegExp(testIssn, 'i');
    968        if (!testIssn.test(str)) {
    969          return false;
    970        }
    971        var issnDigits = str.replace('-', '');
    972        var position = 8;
    973        var checksum = 0;
    974        var _iteratorNormalCompletion = true;
    975        var _didIteratorError = false;
    976        var _iteratorError = undefined;
    977 
    978        try {
    979          for (var _iterator = issnDigits[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
    980            var digit = _step.value;
    981 
    982            var digitValue = digit.toUpperCase() === 'X' ? 10 : +digit;
    983            checksum += digitValue * position;
    984            --position;
    985          }
    986        } catch (err) {
    987          _didIteratorError = true;
    988          _iteratorError = err;
    989        } finally {
    990          try {
    991            if (!_iteratorNormalCompletion && _iterator.return) {
    992              _iterator.return();
    993            }
    994          } finally {
    995            if (_didIteratorError) {
    996              throw _iteratorError;
    997            }
    998          }
    999        }
   1000 
   1001        return checksum % 11 === 0;
   1002      }
   1003 
   1004      /* eslint-disable max-len */
   1005      var phones = {
   1006        'ar-DZ': /^(\+?213|0)(5|6|7)\d{8}$/,
   1007        'ar-SY': /^(!?(\+?963)|0)?9\d{8}$/,
   1008        'ar-SA': /^(!?(\+?966)|0)?5\d{8}$/,
   1009        'en-US': /^(\+?1)?[2-9]\d{2}[2-9](?!11)\d{6}$/,
   1010        'cs-CZ': /^(\+?420)? ?[1-9][0-9]{2} ?[0-9]{3} ?[0-9]{3}$/,
   1011        'de-DE': /^(\+?49[ \.\-])?([\(]{1}[0-9]{1,6}[\)])?([0-9 \.\-\/]{3,20})((x|ext|extension)[ ]?[0-9]{1,4})?$/,
   1012        'da-DK': /^(\+?45)?(\d{8})$/,
   1013        'el-GR': /^(\+?30)?(69\d{8})$/,
   1014        'en-AU': /^(\+?61|0)4\d{8}$/,
   1015        'en-GB': /^(\+?44|0)7\d{9}$/,
   1016        // According to http://www.ofca.gov.hk/filemanager/ofca/en/content_311/no_plan.pdf
   1017        'en-HK': /^(\+?852-?)?((4(04[01]|06\d|09[3-9]|20\d|2[2-9]\d|3[3-9]\d|[467]\d{2}|5[1-9]\d|81\d|82[1-9]|8[69]\d|92[3-9]|95[2-9]|98\d)|5([1-79]\d{2})|6(0[1-9]\d|[1-9]\d{2})|7(0[1-9]\d|10[4-79]|11[458]|1[24578]\d|13[24-9]|16[0-8]|19[24579]|21[02-79]|2[456]\d|27[13-6]|3[456]\d|37[4578]|39[0146])|8(1[58]\d|2[45]\d|267|27[5-9]|2[89]\d|3[15-9]\d|32[5-8]|[46-9]\d{2}|5[013-9]\d)|9(0[1-9]\d|1[02-9]\d|[2-8]\d{2}))-?\d{4}|7130-?[0124-8]\d{3}|8167-?2\d{3})$/,
   1018        'en-IN': /^(\+?91|0)?[789]\d{9}$/,
   1019        'en-NG': /^(\+?234|0)?[789]\d{9}$/,
   1020        'en-NZ': /^(\+?64|0)2\d{7,9}$/,
   1021        'en-ZA': /^(\+?27|0)\d{9}$/,
   1022        'en-ZM': /^(\+?26)?09[567]\d{7}$/,
   1023        'es-ES': /^(\+?34)?(6\d{1}|7[1234])\d{7}$/,
   1024        'fi-FI': /^(\+?358|0)\s?(4(0|1|2|4|5)?|50)\s?(\d\s?){4,8}\d$/,
   1025        'fr-FR': /^(\+?33|0)[67]\d{8}$/,
   1026        'he-IL': /^(\+972|0)([23489]|5[0248]|77)[1-9]\d{6}/,
   1027        'hu-HU': /^(\+?36)(20|30|70)\d{7}$/,
   1028        'lt-LT': /^(\+370|8)\d{8}$/,
   1029        'id-ID': /^(\+?62|0[1-9])[\s|\d]+$/,
   1030        'it-IT': /^(\+?39)?\s?3\d{2} ?\d{6,7}$/,
   1031        'ko-KR': /^((\+?82)[ \-]?)?0?1([0|1|6|7|8|9]{1})[ \-]?\d{3,4}[ \-]?\d{4}$/,
   1032        'ja-JP': /^(\+?81|0)\d{1,4}[ \-]?\d{1,4}[ \-]?\d{4}$/,
   1033        'ms-MY': /^(\+?6?01){1}(([145]{1}(\-|\s)?\d{7,8})|([236789]{1}(\s|\-)?\d{7}))$/,
   1034        'nb-NO': /^(\+?47)?[49]\d{7}$/,
   1035        'nl-BE': /^(\+?32|0)4?\d{8}$/,
   1036        'nn-NO': /^(\+?47)?[49]\d{7}$/,
   1037        'pl-PL': /^(\+?48)? ?[5-8]\d ?\d{3} ?\d{2} ?\d{2}$/,
   1038        'pt-BR': /^(\+?55|0)\-?[1-9]{2}\-?[2-9]{1}\d{3,4}\-?\d{4}$/,
   1039        'pt-PT': /^(\+?351)?9[1236]\d{7}$/,
   1040        'ro-RO': /^(\+?4?0)\s?7\d{2}(\/|\s|\.|\-)?\d{3}(\s|\.|\-)?\d{3}$/,
   1041        'en-PK': /^((\+92)|(0092))-{0,1}\d{3}-{0,1}\d{7}$|^\d{11}$|^\d{4}-\d{7}$/,
   1042        'ru-RU': /^(\+?7|8)?9\d{9}$/,
   1043        'sr-RS': /^(\+3816|06)[- \d]{5,9}$/,
   1044        'tr-TR': /^(\+?90|0)?5\d{9}$/,
   1045        'vi-VN': /^(\+?84|0)?((1(2([0-9])|6([2-9])|88|99))|(9((?!5)[0-9])))([0-9]{7})$/,
   1046        'zh-CN': /^(\+?0?86\-?)?1[345789]\d{9}$/,
   1047        'zh-TW': /^(\+?886\-?|0)?9\d{8}$/
   1048      };
   1049      /* eslint-enable max-len */
   1050 
   1051      // aliases
   1052      phones['en-CA'] = phones['en-US'];
   1053      phones['fr-BE'] = phones['nl-BE'];
   1054      phones['zh-HK'] = phones['en-HK'];
   1055 
   1056      function isMobilePhone(str, locale) {
   1057        assertString(str);
   1058        if (locale in phones) {
   1059          return phones[locale].test(str);
   1060        } else if (locale === 'any') {
   1061          return !!Object.values(phones).find(phone => phone.test(str));
   1062        }
   1063        return false;
   1064      }
   1065 
   1066      function currencyRegex(options) {
   1067        var symbol = '(\\' + options.symbol.replace(/\./g, '\\.') + ')' + (options.require_symbol ? '' : '?'),
   1068            negative = '-?',
   1069            whole_dollar_amount_without_sep = '[1-9]\\d*',
   1070            whole_dollar_amount_with_sep = '[1-9]\\d{0,2}(\\' + options.thousands_separator + '\\d{3})*',
   1071            valid_whole_dollar_amounts = ['0', whole_dollar_amount_without_sep, whole_dollar_amount_with_sep],
   1072            whole_dollar_amount = '(' + valid_whole_dollar_amounts.join('|') + ')?',
   1073            decimal_amount = '(\\' + options.decimal_separator + '\\d{2})?';
   1074        var pattern = whole_dollar_amount + decimal_amount;
   1075 
   1076        // default is negative sign before symbol, but there are two other options (besides parens)
   1077        if (options.allow_negatives && !options.parens_for_negatives) {
   1078          if (options.negative_sign_after_digits) {
   1079            pattern += negative;
   1080          } else if (options.negative_sign_before_digits) {
   1081            pattern = negative + pattern;
   1082          }
   1083        }
   1084 
   1085        // South African Rand, for example, uses R 123 (space) and R-123 (no space)
   1086        if (options.allow_negative_sign_placeholder) {
   1087          pattern = '( (?!\\-))?' + pattern;
   1088        } else if (options.allow_space_after_symbol) {
   1089          pattern = ' ?' + pattern;
   1090        } else if (options.allow_space_after_digits) {
   1091          pattern += '( (?!$))?';
   1092        }
   1093 
   1094        if (options.symbol_after_digits) {
   1095          pattern += symbol;
   1096        } else {
   1097          pattern = symbol + pattern;
   1098        }
   1099 
   1100        if (options.allow_negatives) {
   1101          if (options.parens_for_negatives) {
   1102            pattern = '(\\(' + pattern + '\\)|' + pattern + ')';
   1103          } else if (!(options.negative_sign_before_digits || options.negative_sign_after_digits)) {
   1104            pattern = negative + pattern;
   1105          }
   1106        }
   1107 
   1108        /* eslint-disable prefer-template */
   1109        return new RegExp('^' +
   1110        // ensure there's a dollar and/or decimal amount, and that
   1111        // it doesn't start with a space or a negative sign followed by a space
   1112        '(?!-? )(?=.*\\d)' + pattern + '$');
   1113        /* eslint-enable prefer-template */
   1114      }
   1115 
   1116      var default_currency_options = {
   1117        symbol: '$',
   1118        require_symbol: false,
   1119        allow_space_after_symbol: false,
   1120        symbol_after_digits: false,
   1121        allow_negatives: true,
   1122        parens_for_negatives: false,
   1123        negative_sign_before_digits: false,
   1124        negative_sign_after_digits: false,
   1125        allow_negative_sign_placeholder: false,
   1126        thousands_separator: ',',
   1127        decimal_separator: '.',
   1128        allow_space_after_digits: false
   1129      };
   1130 
   1131      function isCurrency(str, options) {
   1132        assertString(str);
   1133        options = merge(options, default_currency_options);
   1134        return currencyRegex(options).test(str);
   1135      }
   1136 
   1137      /* eslint-disable max-len */
   1138      // from http://goo.gl/0ejHHW
   1139      var iso8601 = /^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/;
   1140      /* eslint-enable max-len */
   1141 
   1142      function isISO8601 (str) {
   1143        assertString(str);
   1144        return iso8601.test(str);
   1145      }
   1146 
   1147      function isBase64(str) {
   1148        assertString(str);
   1149        // Value length must be divisible by 4.
   1150        var len = str.length;
   1151        if (!len || len % 4 !== 0) {
   1152          return false;
   1153        }
   1154 
   1155        try {
   1156          if (atob(str)) {
   1157            return true;
   1158          }
   1159        } catch (e) {
   1160          return false;
   1161        }
   1162      }
   1163 
   1164      var dataURI = /^\s*data:([a-z]+\/[a-z0-9\-\+]+(;[a-z\-]+=[a-z0-9\-]+)?)?(;base64)?,[a-z0-9!\$&',\(\)\*\+,;=\-\._~:@\/\?%\s]*\s*$/i; // eslint-disable-line max-len
   1165 
   1166      function isDataURI(str) {
   1167        assertString(str);
   1168        return dataURI.test(str);
   1169      }
   1170 
   1171      function ltrim(str, chars) {
   1172        assertString(str);
   1173        var pattern = chars ? new RegExp('^[' + chars + ']+', 'g') : /^\s+/g;
   1174        return str.replace(pattern, '');
   1175      }
   1176 
   1177      function rtrim(str, chars) {
   1178        assertString(str);
   1179        var pattern = chars ? new RegExp('[' + chars + ']') : /\s/;
   1180 
   1181        var idx = str.length - 1;
   1182        while (idx >= 0 && pattern.test(str[idx])) {
   1183          idx--;
   1184        }
   1185 
   1186        return idx < str.length ? str.substr(0, idx + 1) : str;
   1187      }
   1188 
   1189      function trim(str, chars) {
   1190        return rtrim(ltrim(str, chars), chars);
   1191      }
   1192 
   1193      function escape(str) {
   1194            assertString(str);
   1195            return str.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/'/g, '&#x27;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\//g, '&#x2F;').replace(/\\/g, '&#x5C;').replace(/`/g, '&#96;');
   1196      }
   1197 
   1198      function unescape(str) {
   1199            assertString(str);
   1200            return str.replace(/&amp;/g, '&').replace(/&quot;/g, '"').replace(/&#x27;/g, "'").replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&#x2F;/g, '/').replace(/&#96;/g, '`');
   1201      }
   1202 
   1203      function blacklist(str, chars) {
   1204        assertString(str);
   1205        return str.replace(new RegExp('[' + chars + ']+', 'g'), '');
   1206      }
   1207 
   1208      function stripLow(str, keep_new_lines) {
   1209        assertString(str);
   1210        var chars = keep_new_lines ? '\\x00-\\x09\\x0B\\x0C\\x0E-\\x1F\\x7F' : '\\x00-\\x1F\\x7F';
   1211        return blacklist(str, chars);
   1212      }
   1213 
   1214      function whitelist(str, chars) {
   1215        assertString(str);
   1216        return str.replace(new RegExp('[^' + chars + ']+', 'g'), '');
   1217      }
   1218 
   1219      function isWhitelisted(str, chars) {
   1220        assertString(str);
   1221        for (var i = str.length - 1; i >= 0; i--) {
   1222          if (chars.indexOf(str[i]) === -1) {
   1223            return false;
   1224          }
   1225        }
   1226        return true;
   1227      }
   1228 
   1229      var default_normalize_email_options = {
   1230        // The following options apply to all email addresses
   1231        // Lowercases the local part of the email address.
   1232        // Please note this may violate RFC 5321 as per http://stackoverflow.com/a/9808332/192024).
   1233        // The domain is always lowercased, as per RFC 1035
   1234        all_lowercase: true,
   1235 
   1236        // The following conversions are specific to GMail
   1237        // Lowercases the local part of the GMail address (known to be case-insensitive)
   1238        gmail_lowercase: true,
   1239        // Removes dots from the local part of the email address, as that's ignored by GMail
   1240        gmail_remove_dots: true,
   1241        // Removes the subaddress (e.g. "+foo") from the email address
   1242        gmail_remove_subaddress: true,
   1243        // Conversts the googlemail.com domain to gmail.com
   1244        gmail_convert_googlemaildotcom: true,
   1245 
   1246        // The following conversions are specific to Outlook.com / Windows Live / Hotmail
   1247        // Lowercases the local part of the Outlook.com address (known to be case-insensitive)
   1248        outlookdotcom_lowercase: true,
   1249        // Removes the subaddress (e.g. "+foo") from the email address
   1250        outlookdotcom_remove_subaddress: true,
   1251 
   1252        // The following conversions are specific to Yahoo
   1253        // Lowercases the local part of the Yahoo address (known to be case-insensitive)
   1254        yahoo_lowercase: true,
   1255        // Removes the subaddress (e.g. "-foo") from the email address
   1256        yahoo_remove_subaddress: true,
   1257 
   1258        // The following conversions are specific to iCloud
   1259        // Lowercases the local part of the iCloud address (known to be case-insensitive)
   1260        icloud_lowercase: true,
   1261        // Removes the subaddress (e.g. "+foo") from the email address
   1262        icloud_remove_subaddress: true
   1263      };
   1264 
   1265      // List of domains used by iCloud
   1266      var icloud_domains = ['icloud.com', 'me.com'];
   1267 
   1268      // List of domains used by Outlook.com and its predecessors
   1269      // This list is likely incomplete.
   1270      // Partial reference:
   1271      // https://blogs.office.com/2013/04/17/outlook-com-gets-two-step-verification-sign-in-by-alias-and-new-international-domains/
   1272      var outlookdotcom_domains = ['hotmail.at', 'hotmail.be', 'hotmail.ca', 'hotmail.cl', 'hotmail.co.il', 'hotmail.co.nz', 'hotmail.co.th', 'hotmail.co.uk', 'hotmail.com', 'hotmail.com.ar', 'hotmail.com.au', 'hotmail.com.br', 'hotmail.com.gr', 'hotmail.com.mx', 'hotmail.com.pe', 'hotmail.com.tr', 'hotmail.com.vn', 'hotmail.cz', 'hotmail.de', 'hotmail.dk', 'hotmail.es', 'hotmail.fr', 'hotmail.hu', 'hotmail.id', 'hotmail.ie', 'hotmail.in', 'hotmail.it', 'hotmail.jp', 'hotmail.kr', 'hotmail.lv', 'hotmail.my', 'hotmail.ph', 'hotmail.pt', 'hotmail.sa', 'hotmail.sg', 'hotmail.sk', 'live.be', 'live.co.uk', 'live.com', 'live.com.ar', 'live.com.mx', 'live.de', 'live.es', 'live.eu', 'live.fr', 'live.it', 'live.nl', 'msn.com', 'outlook.at', 'outlook.be', 'outlook.cl', 'outlook.co.il', 'outlook.co.nz', 'outlook.co.th', 'outlook.com', 'outlook.com.ar', 'outlook.com.au', 'outlook.com.br', 'outlook.com.gr', 'outlook.com.pe', 'outlook.com.tr', 'outlook.com.vn', 'outlook.cz', 'outlook.de', 'outlook.dk', 'outlook.es', 'outlook.fr', 'outlook.hu', 'outlook.id', 'outlook.ie', 'outlook.in', 'outlook.it', 'outlook.jp', 'outlook.kr', 'outlook.lv', 'outlook.my', 'outlook.ph', 'outlook.pt', 'outlook.sa', 'outlook.sg', 'outlook.sk', 'passport.com'];
   1273 
   1274      // List of domains used by Yahoo Mail
   1275      // This list is likely incomplete
   1276      var yahoo_domains = ['rocketmail.com', 'yahoo.ca', 'yahoo.co.uk', 'yahoo.com', 'yahoo.de', 'yahoo.fr', 'yahoo.in', 'yahoo.it', 'ymail.com'];
   1277 
   1278      function normalizeEmail(email, options) {
   1279        options = merge(options, default_normalize_email_options);
   1280 
   1281        if (!isEmail(email)) {
   1282          return false;
   1283        }
   1284 
   1285        var raw_parts = email.split('@');
   1286        var domain = raw_parts.pop();
   1287        var user = raw_parts.join('@');
   1288        var parts = [user, domain];
   1289 
   1290        // The domain is always lowercased, as it's case-insensitive per RFC 1035
   1291        parts[1] = parts[1].toLowerCase();
   1292 
   1293        if (parts[1] === 'gmail.com' || parts[1] === 'googlemail.com') {
   1294          // Address is GMail
   1295          if (options.gmail_remove_subaddress) {
   1296            parts[0] = parts[0].split('+')[0];
   1297          }
   1298          if (options.gmail_remove_dots) {
   1299            parts[0] = parts[0].replace(/\./g, '');
   1300          }
   1301          if (!parts[0].length) {
   1302            return false;
   1303          }
   1304          if (options.all_lowercase || options.gmail_lowercase) {
   1305            parts[0] = parts[0].toLowerCase();
   1306          }
   1307          parts[1] = options.gmail_convert_googlemaildotcom ? 'gmail.com' : parts[1];
   1308        } else if (~icloud_domains.indexOf(parts[1])) {
   1309          // Address is iCloud
   1310          if (options.icloud_remove_subaddress) {
   1311            parts[0] = parts[0].split('+')[0];
   1312          }
   1313          if (!parts[0].length) {
   1314            return false;
   1315          }
   1316          if (options.all_lowercase || options.icloud_lowercase) {
   1317            parts[0] = parts[0].toLowerCase();
   1318          }
   1319        } else if (~outlookdotcom_domains.indexOf(parts[1])) {
   1320          // Address is Outlook.com
   1321          if (options.outlookdotcom_remove_subaddress) {
   1322            parts[0] = parts[0].split('+')[0];
   1323          }
   1324          if (!parts[0].length) {
   1325            return false;
   1326          }
   1327          if (options.all_lowercase || options.outlookdotcom_lowercase) {
   1328            parts[0] = parts[0].toLowerCase();
   1329          }
   1330        } else if (~yahoo_domains.indexOf(parts[1])) {
   1331          // Address is Yahoo
   1332          if (options.yahoo_remove_subaddress) {
   1333            var components = parts[0].split('-');
   1334            parts[0] = components.length > 1 ? components.slice(0, -1).join('-') : components[0];
   1335          }
   1336          if (!parts[0].length) {
   1337            return false;
   1338          }
   1339          if (options.all_lowercase || options.yahoo_lowercase) {
   1340            parts[0] = parts[0].toLowerCase();
   1341          }
   1342        } else if (options.all_lowercase) {
   1343          // Any other address
   1344          parts[0] = parts[0].toLowerCase();
   1345        }
   1346        return parts.join('@');
   1347      }
   1348 
   1349      // see http://isrc.ifpi.org/en/isrc-standard/code-syntax
   1350      var isrc = /^[A-Z]{2}[0-9A-Z]{3}\d{2}\d{5}$/;
   1351 
   1352      function isISRC(str) {
   1353        assertString(str);
   1354        return isrc.test(str);
   1355      }
   1356 
   1357      var cultureCodes = new Set(["ar", "bg", "ca", "zh-Hans", "cs", "da", "de",
   1358      "el", "en", "es", "fi", "fr", "he", "hu", "is", "it", "ja", "ko", "nl", "no",
   1359      "pl", "pt", "rm", "ro", "ru", "hr", "sk", "sq", "sv", "th", "tr", "ur", "id",
   1360      "uk", "be", "sl", "et", "lv", "lt", "tg", "fa", "vi", "hy", "az", "eu", "hsb",
   1361      "mk", "tn", "xh", "zu", "af", "ka", "fo", "hi", "mt", "se", "ga", "ms", "kk",
   1362      "ky", "sw", "tk", "uz", "tt", "bn", "pa", "gu", "or", "ta", "te", "kn", "ml",
   1363      "as", "mr", "sa", "mn", "bo", "cy", "km", "lo", "gl", "kok", "syr", "si", "iu",
   1364      "am", "tzm", "ne", "fy", "ps", "fil", "dv", "ha", "yo", "quz", "nso", "ba", "lb",
   1365      "kl", "ig", "ii", "arn", "moh", "br", "ug", "mi", "oc", "co", "gsw", "sah",
   1366      "qut", "rw", "wo", "prs", "gd", "ar-SA", "bg-BG", "ca-ES", "zh-TW", "cs-CZ",
   1367      "da-DK", "de-DE", "el-GR", "en-US", "fi-FI", "fr-FR", "he-IL", "hu-HU", "is-IS",
   1368      "it-IT", "ja-JP", "ko-KR", "nl-NL", "nb-NO", "pl-PL", "pt-BR", "rm-CH", "ro-RO",
   1369      "ru-RU", "hr-HR", "sk-SK", "sq-AL", "sv-SE", "th-TH", "tr-TR", "ur-PK", "id-ID",
   1370      "uk-UA", "be-BY", "sl-SI", "et-EE", "lv-LV", "lt-LT", "tg-Cyrl-TJ", "fa-IR",
   1371      "vi-VN", "hy-AM", "az-Latn-AZ", "eu-ES", "hsb-DE", "mk-MK", "tn-ZA", "xh-ZA",
   1372      "zu-ZA", "af-ZA", "ka-GE", "fo-FO", "hi-IN", "mt-MT", "se-NO", "ms-MY", "kk-KZ",
   1373      "ky-KG", "sw-KE", "tk-TM", "uz-Latn-UZ", "tt-RU", "bn-IN", "pa-IN", "gu-IN",
   1374      "or-IN", "ta-IN", "te-IN", "kn-IN", "ml-IN", "as-IN", "mr-IN", "sa-IN", "mn-MN",
   1375      "bo-CN", "cy-GB", "km-KH", "lo-LA", "gl-ES", "kok-IN", "syr-SY", "si-LK",
   1376      "iu-Cans-CA", "am-ET", "ne-NP", "fy-NL", "ps-AF", "fil-PH", "dv-MV",
   1377      "ha-Latn-NG", "yo-NG", "quz-BO", "nso-ZA", "ba-RU", "lb-LU", "kl-GL", "ig-NG",
   1378      "ii-CN", "arn-CL", "moh-CA", "br-FR", "ug-CN", "mi-NZ", "oc-FR", "co-FR",
   1379      "gsw-FR", "sah-RU", "qut-GT", "rw-RW", "wo-SN", "prs-AF", "gd-GB", "ar-IQ",
   1380      "zh-CN", "de-CH", "en-GB", "es-MX", "fr-BE", "it-CH", "nl-BE", "nn-NO", "pt-PT",
   1381      "sr-Latn-CS", "sv-FI", "az-Cyrl-AZ", "dsb-DE", "se-SE", "ga-IE", "ms-BN",
   1382      "uz-Cyrl-UZ", "bn-BD", "mn-Mong-CN", "iu-Latn-CA", "tzm-Latn-DZ", "quz-EC",
   1383      "ar-EG", "zh-HK", "de-AT", "en-AU", "es-ES", "fr-CA", "sr-Cyrl-CS", "se-FI",
   1384      "quz-PE", "ar-LY", "zh-SG", "de-LU", "en-CA", "es-GT", "fr-CH", "hr-BA",
   1385      "smj-NO", "ar-DZ", "zh-MO", "de-LI", "en-NZ", "es-CR", "fr-LU", "bs-Latn-BA",
   1386      "smj-SE", "ar-MA", "en-IE", "es-PA", "fr-MC", "sr-Latn-BA", "sma-NO", "ar-TN",
   1387      "en-ZA", "es-DO", "sr-Cyrl-BA", "sma-SE", "ar-OM", "en-JM", "es-VE",
   1388      "bs-Cyrl-BA", "sms-FI", "ar-YE", "en-029", "es-CO", "sr-Latn-RS", "smn-FI",
   1389      "ar-SY", "en-BZ", "es-PE", "sr-Cyrl-RS", "ar-JO", "en-TT", "es-AR", "sr-Latn-ME",
   1390      "ar-LB", "en-ZW", "es-EC", "sr-Cyrl-ME", "ar-KW", "en-PH", "es-CL", "ar-AE",
   1391      "es-UY", "ar-BH", "es-PY", "ar-QA", "en-IN", "es-BO", "en-MY", "es-SV", "en-SG",
   1392      "es-HN", "es-NI", "es-PR", "es-US", "bs-Cyrl", "bs-Latn", "sr-Cyrl", "sr-Latn",
   1393      "smn", "az-Cyrl", "sms", "zh", "nn", "bs", "az-Latn", "sma", "uz-Cyrl",
   1394      "mn-Cyrl", "iu-Cans", "zh-Hant", "nb", "sr", "tg-Cyrl", "dsb", "smj", "uz-Latn",
   1395      "mn-Mong", "iu-Latn", "tzm-Latn", "ha-Latn", "zh-CHS", "zh-CHT"]);
   1396 
   1397      function isRFC5646(str) {
   1398        assertString(str);
   1399        // According to the spec these codes are case sensitive so we can check the
   1400        // string directly.
   1401        return cultureCodes.has(str);
   1402      }
   1403 
   1404      var semver =  /^v?(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(-(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?$/i
   1405 
   1406      function isSemVer(str) {
   1407        assertString(str);
   1408        return semver.test(str);
   1409      }
   1410 
   1411      var rgbcolor =  /^rgb?\(\s*(0|[1-9]\d?|1\d\d?|2[0-4]\d|25[0-5])\s*,\s*(0|[1-9]\d?|1\d\d?|2[0-4]\d|25[0-5])\s*,\s*(0|[1-9]\d?|1\d\d?|2[0-4]\d|25[0-5])\s*\)$/i
   1412 
   1413      function isRGBColor(str) {
   1414        assertString(str);
   1415        return rgbcolor.test(str);
   1416      }
   1417 
   1418      var version = '7.0.0';
   1419 
   1420      var validator = {
   1421        blacklist: blacklist,
   1422        contains: contains,
   1423        equals: equals,
   1424        escape: escape,
   1425        isAfter: isAfter,
   1426        isAlpha: isAlpha,
   1427        isAlphanumeric: isAlphanumeric,
   1428        isAscii: isAscii,
   1429        isBase64: isBase64,
   1430        isBefore: isBefore,
   1431        isBoolean: isBoolean,
   1432        isByteLength: isByteLength,
   1433        isCreditCard: isCreditCard,
   1434        isCurrency: isCurrency,
   1435        isDataURI: isDataURI,
   1436        isDecimal: isDecimal,
   1437        isDivisibleBy: isDivisibleBy,
   1438        isEmail: isEmail,
   1439        isEmpty: isEmpty,
   1440        isFloat: isFloat,
   1441        isFQDN: isFDQN,
   1442        isFullWidth: isFullWidth,
   1443        isHalfWidth: isHalfWidth,
   1444        isHexadecimal: isHexadecimal,
   1445        isHexColor: isHexColor,
   1446        isIn: isIn,
   1447        isInt: isInt,
   1448        isIP: isIP,
   1449        isRFC5646: isRFC5646,
   1450        isISBN: isISBN,
   1451        isISIN: isISIN,
   1452        isISO8601: isISO8601,
   1453        isISRC: isISRC,
   1454        isRGBColor: isRGBColor,
   1455        isISSN: isISSN,
   1456        isJSON: isJSON,
   1457        isLength: isLength,
   1458        isLowercase: isLowercase,
   1459        isMACAddress: isMACAddress,
   1460        isMD5: isMD5,
   1461        isMobilePhone: isMobilePhone,
   1462        isMongoId: isMongoId,
   1463        isMultibyte: isMultibyte,
   1464        isNumeric: isNumeric,
   1465        isSemVer: isSemVer,
   1466        isSurrogatePair: isSurrogatePair,
   1467        isUppercase: isUppercase,
   1468        isURL: isURL,
   1469        isUUID: isUUID,
   1470        isVariableWidth: isVariableWidth,
   1471        isWhitelisted: isWhitelisted,
   1472        ltrim: ltrim,
   1473        matches: matches,
   1474        normalizeEmail: normalizeEmail,
   1475        rtrim: rtrim,
   1476        stripLow: stripLow,
   1477        toBoolean: toBoolean,
   1478        toDate: toDate,
   1479        toFloat: toFloat,
   1480        toInt: toInt,
   1481        toString: toString,
   1482        trim: trim,
   1483        unescape: unescape,
   1484        version: version,
   1485        whitelist: whitelist
   1486      };
   1487 
   1488      return validator;
   1489 }));