diff --git a/.babelrc b/.babelrc new file mode 100644 index 000000000..332bba6f9 --- /dev/null +++ b/.babelrc @@ -0,0 +1,24 @@ +{ + "env": { + "es": { + "presets": [ + ["@babel/preset-env", { + "modules": false + }] + ] + }, + "development": { + "presets": [ + ["@babel/preset-env", { "targets": { "node": "0.10" } }] + ], + "plugins": [ + [ + "add-module-exports", + { + "addDefaultProperty": true + } + ] + ] + } + } +} \ No newline at end of file diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 4627fe57a..000000000 --- a/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -validator.js -validator.min.js \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json index 15e302d6d..285c41e15 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,5 +1,6 @@ { "extends": "airbnb-base", + "parser": "babel-eslint", "parserOptions": { "ecmaVersion": 6, "sourceType": "module" @@ -11,14 +12,29 @@ "mocha": true }, "rules": { - "camelcase": [0], - "no-param-reassign": [0], + "camelcase": 0, + "no-param-reassign": 0, "one-var": 0, "one-var-declaration-per-line": 0, "func-names": 0, "no-console": 0, "newline-per-chained-call": 0, "prefer-const": 0, - "no-restricted-syntax": [2, "DebuggerStatement", "LabeledStatement", "WithStatement"] + "linebreak-style": 0, + "no-restricted-syntax": [2, "DebuggerStatement", "LabeledStatement", "WithStatement"], + "no-restricted-globals": 0, + "prefer-destructuring": 0, + "comma-dangle": [2, { + "arrays": "always-multiline", + "objects": "always-multiline", + "imports": "always-multiline", + "exports": "always-multiline", + "functions": "never" + }], + "no-plusplus": [2, { + "allowForLoopAfterthoughts": true + }], + "no-prototype-builtins": 0, + "no-useless-escape": 0 } } diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..ac5ace58e --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +open_collective: validatorjs diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..799e41348 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,23 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: "\U0001F41B bug" +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + + +**Examples** +If applicable, add screenshots to help explain your problem. + +**Reproductions** +If applicable, provide a reproduction on platforms like [runkit](npm.runkit.com/validator) + +**Additional context** +Validator.js version: +Node.js version: +OS platform: [windows, linux, macOS, etc] diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..ba6041d49 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,15 @@ + + + + + + +## Checklist + +- [ ] PR contains only changes related; no stray files, etc. +- [ ] README updated (where applicable) +- [ ] Tests written (where applicable) +- [ ] References provided in PR (where applicable) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..e0024eff3 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,29 @@ +name: CI +on: + push: + branches: [master] + pull_request: + branches: [master] +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [22, 20, 18, 16, 14, 12, 10, 8] + name: Run tests on Node.js ${{ matrix.node-version }} + steps: + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + - name: Checkout repository + uses: actions/checkout@v4 + - name: Install dependencies + run: npm install --legacy-peer-deps + - name: Run tests + run: npm test + - if: matrix.node-version == 22 + name: Send coverage info to Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 000000000..e89a9b51e --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,70 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ master ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ master ] + schedule: + - cron: '38 10 * * 4' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'javascript' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://git.io/codeql-language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml new file mode 100644 index 000000000..008a86ce6 --- /dev/null +++ b/.github/workflows/npm-publish.yml @@ -0,0 +1,26 @@ +name: NPM Publish +on: + release: + types: [created] +jobs: + publish: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - name: Setup Node.js 22 + uses: actions/setup-node@v4 + with: + node-version: 22 + registry-url: https://registry.npmjs.org/ + - name: Checkout Repository + uses: actions/checkout@v4 + - name: Install Dependencies + run: npm install --legacy-peer-deps + - name: Run Tests + run: npm test + - name: Publish Package to NPM Registry + run: npm publish --provenance + env: + NODE_AUTH_TOKEN: ${{secrets.NPM_SECRET}} diff --git a/.gitignore b/.gitignore index 322fbf702..86aaedee1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,13 @@ .DS_Store node_modules coverage +coverage.lcov +.nyc_output package-lock.json yarn.lock +/es +/lib +/index.js +validator.js +validator.min.js + diff --git a/.nycrc b/.nycrc new file mode 100644 index 000000000..6b79f893c --- /dev/null +++ b/.nycrc @@ -0,0 +1,9 @@ +{ + "reporter": [ + "html", + "text-summary" + ], + "include": [ + "src/**/*.js" + ] +} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b5cb1994e..000000000 --- a/.travis.yml +++ /dev/null @@ -1,7 +0,0 @@ -sudo: false -language: node_js -node_js: - - stable - - "6" -after_script: - - npm run coveralls diff --git a/CHANGELOG.md b/CHANGELOG.md index c8f91bca3..25192b24a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,96 +1,940 @@ +# 13.15.20 + +### Fixes, New Locales and Enhancements + +- [#2556](https://github.com/validatorjs/validator.js/pull/2556) `isMobilePhone`: add `ar-QA` locale @WardKhaddour +- [#2576](https://github.com/validatorjs/validator.js/pull/2576) `isAlpha`/`isAlphanuneric`: add Indic locales (`ta-IN`, `te-IN`, `kn-IN`, `ml-IN`, `gu-IN`, `pa-IN`, `or-IN`) @avadootharajesh +- [#2574](https://github.com/validatorjs/validator.js/pull/2574) `isBase64`: improve padding regex @KrayzeeKev +- [#2584](https://github.com/validatorjs/validator.js/pull/2584) `isVAT`: improve `FR` locale @iamAmer +- [#2608](https://github.com/validatorjs/validator.js/pull/2608) `isURL`: improve protocol detection. Resolves CVE-2025-56200 @theofidry +- **Doc fixes and others:** + - [#2563](https://github.com/validatorjs/validator.js/pull/2563) @stoneLeaf + - [#2581](https://github.com/validatorjs/validator.js/pull/2581) @camillobruni + +# 13.15.15 + +### Fixes, New Locales and Enhancements + +- `isMobilePhone` + - [#2514](https://github.com/validatorjs/validator.js/pull/2514) improve `el-CY` locale @rezk2ll + - [#2512](https://github.com/validatorjs/validator.js/pull/2512) improve `pt-AO` locale @renaldodev + - [#2502](https://github.com/validatorjs/validator.js/pull/2502) improve `ar-OM` locale @tomcastro +- [#2089](https://github.com/validatorjs/validator.js/pull/2089) `isIP`: allow usage of option object @pixelbucket-dev +- [#2526](https://github.com/validatorjs/validator.js/pull/2526) `isPassportNumber`: improve `CA` locale @evanbechtol +- [#2491](https://github.com/validatorjs/validator.js/pull/2491) `isBase64`: improve validation based on RFC4648 @aseyfpour +- [#2479](https://github.com/validatorjs/validator.js/pull/2479) `isPostalCode`: improve `FR` locale @Rajput-Balram +- [#2088](https://github.com/validatorjs/validator.js/pull/2088) `isBefore`: allow usage of option object @pixelbucket-dev +- [#2346](https://github.com/validatorjs/validator.js/pull/2346) `isRgbColor`: allow second digit in rgba alpha value @controlol +- [#2453](https://github.com/validatorjs/validator.js/pull/2453) `isIP`: improve IPv6 regex @ShreySinha02 +- [#2052](https://github.com/validatorjs/validator.js/pull/2052) `isPostalCode`: add `PK` locale @mateeni-dev +- [#2529](https://github.com/validatorjs/validator.js/pull/2529) `isPostalCode`: improve `TW` locale @Crocsx +- [#2550](https://github.com/validatorjs/validator.js/pull/2550) `isPassportNumber`: improve `US` locale @yitzchak-schechter +- [#2553](https://github.com/validatorjs/validator.js/pull/2553) `isUUID`: add `loose` option @bc-m +- [#2551](https://github.com/validatorjs/validator.js/pull/2551) `isPostalCode`: add `BD` locale @tanvirrb +- [#2555](https://github.com/validatorjs/validator.js/pull/2555) `isLicensePlate`: improve `pt-PT` locale @castrosu +- **Doc fixes and others:** + - [#2372](https://github.com/validatorjs/validator.js/pull/2372) @EmersonRabelo + - [#2538](https://github.com/validatorjs/validator.js/pull/2538) @WikiRik + - [#2539](https://github.com/validatorjs/validator.js/pull/2539) @WikiRik + - [#2540](https://github.com/validatorjs/validator.js/pull/2540) @WikiRik + - [#2549](https://github.com/validatorjs/validator.js/pull/2549) @WikiRik + - [#2537](https://github.com/validatorjs/validator.js/pull/2537) @sgress454 + +# 13.15.0 + +### New Features / Validators + +- [#2399](https://github.com/validatorjs/validator.js/pull/2399) `isISO31661Numeric` @RobinvanderVliet +- [#2294](https://github.com/validatorjs/validator.js/pull/2294) `isULID` @arafatkn +- [#2215](https://github.com/validatorjs/validator.js/pull/2215) `isISO15924` @xDivisionByZerox + +### Fixes, New Locales and Enhancements + +- `isMobilePhone` + - [#2395](https://github.com/validatorjs/validator.js/pull/2395) add `es-GT` locale @ignaciosuarezquilis + - [#1971](https://github.com/validatorjs/validator.js/pull/1971) improve `en-GB` locale @ihmpavel + - [#2359](https://github.com/validatorjs/validator.js/pull/2359) improve `uk-UA` locale @arttiger + - [#2350](https://github.com/validatorjs/validator.js/pull/2350) improve `ky-KG` locale @sadraliev + - [#2482](https://github.com/validatorjs/validator.js/pull/2482) improve `en-ZM` locale @sonikishan + - [#2362](https://github.com/validatorjs/validator.js/pull/2362) improve `en-GH` locale @NanaAb-116 + - [#2500](https://github.com/validatorjs/validator.js/pull/2500) add `mk-MK` locale @eshward95 + - [#2534](https://github.com/validatorjs/validator.js/pull/2534) improve `sq-AL` locale @nichoola +- [#2406](https://github.com/validatorjs/validator.js/pull/2406) `isBtcAddress` support all address formats and testnets @madoke +- [#2339](https://github.com/validatorjs/validator.js/pull/2339) `isIBAN` improve `VG` regex @ST-DDT +- [#2332](https://github.com/validatorjs/validator.js/pull/2332) `isISO4217` update currency codes @cbodtorf +- [#2291](https://github.com/validatorjs/validator.js/pull/2291) `isIdentityCard` add `PK` locale @Daniyal-Qureshi +- [#2414](https://github.com/validatorjs/validator.js/pull/2414) `isEmail` fix blacklist_chars @keshavlingala +- [#2416](https://github.com/validatorjs/validator.js/pull/2416) `isInt`/`isFloat` handle undefined and null values @Daniyal-Qureshi +- [#2415](https://github.com/validatorjs/validator.js/pull/2415) `isPostalCode` add `CO` locale @jorgevrgs +- [#2404](https://github.com/validatorjs/validator.js/pull/2404) `isPassportNumber` export `passportNumberLocales` @derekparnell +- [#2029](https://github.com/validatorjs/validator.js/pull/2029) `isRgbColor` add `allowSpaces` option @a-h-i +- [#2421](https://github.com/validatorjs/validator.js/pull/2421) `isUUID` require valid variant field and require RFC9562 UUID in version `all` @broofa +- [#2439](https://github.com/validatorjs/validator.js/pull/2439) `isURL` add `max_allowed_length` option @pinkiesky +- [#2437](https://github.com/validatorjs/validator.js/pull/2437) `isEmail` reject starting with double quotes @code0emperor +- [#2333](https://github.com/validatorjs/validator.js/pull/2333) `isLicensePlate` add `en-SG` locale @Sabarinathan07 +- [#2441](https://github.com/validatorjs/validator.js/pull/2441) `normalizeEmail` add `yandex_convert_yandexru` option @AayushGH +- [#2443](https://github.com/validatorjs/validator.js/pull/2443) `isDate` return false instead of Error in certain cases @pano9000 +- [#2474](https://github.com/validatorjs/validator.js/pull/2474) `isLength` add `discreteLengths` option @Suven-p +- [#2481](https://github.com/validatorjs/validator.js/pull/2481) `isDate` disallow mismatching length in `strictMode` @sonikishan +- [#2492](https://github.com/validatorjs/validator.js/pull/2492) `isISO6346` set check digit to 0 if remainder is 10 @joelcuy +- [#2493](https://github.com/validatorjs/validator.js/pull/2493) `isPostalCode` improve `BR` locale @ticmaisdev +- [#2494](https://github.com/validatorjs/validator.js/pull/2494) `isEmail` allow regexp in `host_whitelist` and `host_blacklist` @weikangchia +- [#2518](https://github.com/validatorjs/validator.js/pull/2518) `isIBAN` improve `IE`/`PS` regex @Tarasz57 +- **Doc fixes and others:** + - [#2402](https://github.com/validatorjs/validator.js/pull/2402) @BibhushanKarki + - [#2394](https://github.com/validatorjs/validator.js/pull/2394) @RobinvanderVliet + - [#1732](https://github.com/validatorjs/validator.js/pull/1732) @alguerocode + - [#2413](https://github.com/validatorjs/validator.js/pull/2413) @rubiin + - [#2408](https://github.com/validatorjs/validator.js/pull/2408) @profnandaa + - [#2411](https://github.com/validatorjs/validator.js/pull/2411) @rubiin + - [#2325](https://github.com/validatorjs/validator.js/pull/2325) @ovarn + - [#2418](https://github.com/validatorjs/validator.js/pull/2418) @ihmpavel + - [#2323](https://github.com/validatorjs/validator.js/pull/2323) @ovarn + - [#2423](https://github.com/validatorjs/validator.js/pull/2423) @rubiin + - [#2409](https://github.com/validatorjs/validator.js/pull/2409) @profnandaa + - [#2442](https://github.com/validatorjs/validator.js/pull/2442) @pano9000 + +# 13.12.0 + +### New Features / Validators + +- [#2143](https://github.com/validatorjs/validator.js/pull/2143) `isAbaRouting` @songyuew + +### Fixes, New Locales and Enhancements + +- [#2207](https://github.com/validatorjs/validator.js/pull/2207) `isLicensePlate` add Pakistani `en-PK` locale @anasshakil +- [#2208](https://github.com/validatorjs/validator.js/issues/2208) `isPort` fix invalid leading zeros @anasshakil +- [#2224](https://github.com/validatorjs/validator.js/pull/2224) `isTaxID` added Argentina `es-AR` locale @estefrare +- [#2257](https://github.com/validatorjs/validator.js/pull/2257) `isDate` timezone offset fix @tomaspanek +- [#2265](https://github.com/validatorjs/validator.js/pull/2265) `isPassportNumber` added `ZA` locale @GMorris-professional +- `isMobilePhone`: + - [#2267](https://github.com/validatorjs/validator.js/pull/2267) added `en-MW` locale @SimranSiddiqui + - [#2140](https://github.com/validatorjs/validator.js/pull/2140) fix `am-AM` locale @AlexKrupko +- [#2271](https://github.com/validatorjs/validator.js/pull/2271) `isPostalAddress` fix `NL` locale @RobinvanderVliet +- [#2273](https://github.com/validatorjs/validator.js/pull/2273) `isISO4217` add `SLE` currency @urg +- [#2278](https://github.com/validatorjs/validator.js/pull/2278) `isStrongPassword` fix symbolRegex to include `\` @nandavikas +- [#2279](https://github.com/validatorjs/validator.js/pull/2279) `isVAT` fixed `KZ` locale @MatthieuLemoine +- [#2285](https://github.com/validatorjs/validator.js/pull/2285) `isAlpha`, `isAlphanumeric` added `eo` locale @RobinvanderVliet +- [#2320](https://github.com/validatorjs/validator.js/pull/2320) `isIBAN` add Algeria `DZ` locale @thibault-lr +- [#2343](https://github.com/validatorjs/validator.js/pull/2343) `isVAT`improve `AU` locale @matthewberryman +- [#2345](https://github.com/validatorjs/validator.js/pull/2345) `isUUID` add support for v7 @ruscon +- [#2358](https://github.com/validatorjs/validator.js/pull/2358) `isTaxID` add Ukraine `uk-UA` locale @arttiger +- [#2381](https://github.com/validatorjs/validator.js/pull/2381) `isDate` disallow hiphen before year @Sumit-tech-joshi +- **Doc fixes and others:** + - [#2276](https://github.com/validatorjs/validator.js/pull/2276) @meyfa + - [#2341](https://github.com/validatorjs/validator.js/pull/2341) @WikiRik + - [#2364](https://github.com/validatorjs/validator.js/pull/2364) @rubiin + - [#2368](https://github.com/validatorjs/validator.js/pull/2368) @ZhulinskiiDanil + - [#2371](https://github.com/validatorjs/validator.js/pull/2371) @devmanbud + - [#2386](https://github.com/validatorjs/validator.js/pull/2386) @alinaghale88 + +# 13.11.0 + +### New Features / Validators + +- [#2144](https://github.com/validatorjs/validator.js/pull/2144) `isFreightContainerID`: for shipping containers IDs @songyuew +- [#2188](https://github.com/validatorjs/validator.js/pull/2188) `isMailtoURI` @uksarkar + +### Fixes, New Locales and Enhancements + +- [#2025](https://github.com/validatorjs/validator.js/pull/2025) `isIBAN` add `MA` locale @lroudge +- [#2117](https://github.com/validatorjs/validator.js/pull/2117) `isCreditCard` refactor @pano9000 +- [#2189](https://github.com/validatorjs/validator.js/pull/2189) `isLocale` add support for more language tags @kwahome +- [#2203](https://github.com/validatorjs/validator.js/pull/2203) `isVAT` for `CU` @jimmyorpheus +- [#2217](https://github.com/validatorjs/validator.js/pull/2217) `isJWT` @Prathamesh061 +- [#2222](https://github.com/validatorjs/validator.js/pull/2222) `IsFQDN` test enhancements @aalekhpatel07 +- [#2226](https://github.com/validatorjs/validator.js/pull/2226) `isAlpha`, `isAlphanumeric` for `kk-KZ` @BekStar7 +- [#2229](https://github.com/validatorjs/validator.js/pull/2229) `isEmail` support `allow_underscores` @guspower +- [#2231](https://github.com/validatorjs/validator.js/pull/2231) `isDate` enhance Date declaration compatibility across multiple environments @CiprianS +- [#2235](https://github.com/validatorjs/validator.js/pull/2235) `isIBAN` add white and blacklist options to the isIBAN validator @edilson +- [#2237](https://github.com/validatorjs/validator.js/pull/2237) `isEmail` do not allow non-breaking space in user part @jeremy21212121 +- `isMobilePhone`: + - [#2175](https://github.com/validatorjs/validator.js/pull/2175) `so-SO` @ohersi + - [#2176](https://github.com/validatorjs/validator.js/pull/2176) `fr-CF` @cheboi + - [#2197](https://github.com/validatorjs/validator.js/pull/2197) `es-CU` @klaframboise + - [#2202](https://github.com/validatorjs/validator.js/pull/2202) `pl-PL` @czerwony03 + - [#2209](https://github.com/validatorjs/validator.js/pull/2209) `fr-WF` @aidos42 + - [#2246](https://github.com/validatorjs/validator.js/pull/2246) `ar-SD` @Hussienma + +# 13.9.0 + +### New Features / Validators + +- [#1892](https://github.com/validatorjs/validator.js/pull/1892) `isISO6391`: add ISO 639-1 validator @braaar +- [#1974](https://github.com/validatorjs/validator.js/pull/1974) `isLuhnNumber` @ST-DDT + +### Fixes and Enhancements + +- [#1865](https://github.com/validatorjs/validator.js/pull/1865) `isMACAddress`: add EUI-validation @WikiRik @tux-tn +- [#1888](https://github.com/validatorjs/validator.js/pull/1888) `isBase32`: add option for Crockford's base32 alternative @BigOsvaap +- [#1916](https://github.com/validatorjs/validator.js/pull/1916) `isDataURI`: fix mediaType format @temoffey +- [#1920](https://github.com/validatorjs/validator.js/pull/1920) `isEmail`: add `host_whitelist` option @poor-coder +- [#1939](https://github.com/validatorjs/validator.js/pull/1939) `isFQDN`: fix `allow_numeric_tld` option @BigOsvaap +- [#1962](https://github.com/validatorjs/validator.js/pull/1962) `isIP`: refactor @UnKnoWn-Consortium +- [#1967](https://github.com/validatorjs/validator.js/pull/1967) `isLength` @ikkyu-3 +- [#1992](https://github.com/validatorjs/validator.js/pull/1992) `isMagnetURI` @Rhilip @tux-tn +- [#1995](https://github.com/validatorjs/validator.js/pull/1995) `isURL`: fix check for host @mortbauer +- [#2008](https://github.com/validatorjs/validator.js/pull/2008) `isCreditCard` @brianwhaley +- [#2075](https://github.com/validatorjs/validator.js/pull/2075) `isAfter`: allow usage of option object @WikiRik +- [#2114](https://github.com/validatorjs/validator.js/pull/2114) `isRgbColor` @pano9000 +- [#2122](https://github.com/validatorjs/validator.js/pull/2122) `isDataURI`: fix MIME types with underscores @pano9000 +- [#2148](https://github.com/validatorjs/validator.js/pull/2148) `isStrongPassword` @sandmule +- [#2157](https://github.com/validatorjs/validator.js/pull/2157) `isISBN`: allow usage of option object @WikiRik +- [#2170](https://github.com/validatorjs/validator.js/pull/2170) `isEmail`: fix `ignore_max_length` for FQDN @sakhmedbayev +- [#2020](https://github.com/validatorjs/validator.js/pull/2170) `isFloat`: fix comma(,) passing as float @frederike-ramin + +- Documentation fixes: + + - [#1860](https://github.com/validatorjs/validator.js/pull/1860) @leonardovillela + - [#1861](https://github.com/validatorjs/validator.js/pull/1860) @tux-tn + - [#1957](https://github.com/validatorjs/validator.js/pull/1957) @tfilo + - [#2010](https://github.com/validatorjs/validator.js/pull/2010) @marcelozarate + - [#2107](https://github.com/validatorjs/validator.js/pull/2107) @pano9000 + - [#2160](https://github.com/validatorjs/validator.js/pull/2160) @WikiRik + +- Code Refactors: + - [#1942](https://github.com/validatorjs/validator.js/pull/1942) @CommanderRoot + - [#1975](https://github.com/validatorjs/validator.js/pull/1975) @fedeci + - [#2137](https://github.com/validatorjs/validator.js/pull/2137) [#2132](https://github.com/validatorjs/validator.js/pull/2132) @pano9000 + +### New and Improved Locales + +- `isAlpha`, `isAlphanumeric`: + + - [#1678](https://github.com/validatorjs/validator.js/pull/1678) `bn-BD` @rak810 + - [#1996](https://github.com/validatorjs/validator.js/pull/1996) `si-LK` @melkorCBA + - [#2014](https://github.com/validatorjs/validator.js/pull/2014) `ja-JP` @starcharles + - [#1995](https://github.com/validatorjs/validator.js/pull/1995) `ko-KR` @Dongkyuuuu + +- `isBIC`: + + - [#2046](https://github.com/validatorjs/validator.js/pull/2046) `XK` @import-brain + +- `isIdentityCard`: + + - [#2142](https://github.com/validatorjs/validator.js/pull/2142) `hk-HK` @Dongkyuuuu + +- `isMobilePhone`: + + - [#1813](https://github.com/validatorjs/validator.js/pull/1813) `my-MM`, @ferdousulhaque + - [#1868](https://github.com/validatorjs/validator.js/pull/1868) `de-DE`, @thomaschaaf + - [#1896](https://github.com/validatorjs/validator.js/pull/1896) `en-LS`, @DevilsAutumn + - [#1897](https://github.com/validatorjs/validator.js/pull/1897) `el-CY`, @ikerasiotis + - [#1909](https://github.com/validatorjs/validator.js/pull/1909) `es-NI`, @ajGingrich + - [#1910](https://github.com/validatorjs/validator.js/pull/1910) `az-AZ`, @shaanaliyev + - [#1922](https://github.com/validatorjs/validator.js/pull/1922) `ir-IR`, @ArashST79 + - [#1924](https://github.com/validatorjs/validator.js/pull/1924) `ky-KG`, @arsalanfiroozi + - [#1925](https://github.com/validatorjs/validator.js/pull/1925) `ar-YE`, `ar-EH`, `fa-AF`, @Mustafiz04 + - [#1932](https://github.com/validatorjs/validator.js/pull/1932) `ro-MD`, @mik7up + - [#1940](https://github.com/validatorjs/validator.js/pull/1940) `ar-YE`, `en-BS`, @savannahvaith + - [#1952](https://github.com/validatorjs/validator.js/pull/1952) `ka-GE`, @avkvak + - [#1964](https://github.com/validatorjs/validator.js/pull/1964) [#1951](https://github.com/validatorjs/validator.js/pull/1951) `pt-BR`, @jhcaiafa @matheusnascgomes + - [#1983](https://github.com/validatorjs/validator.js/pull/1983) `es-HN`, @ademyan05 + - [#1985](https://github.com/validatorjs/validator.js/pull/1985) `nl-AW`, @adida948 + - [#1986](https://github.com/validatorjs/validator.js/pull/1986) `en-JM`, @ademyan05 + - [#1993](https://github.com/validatorjs/validator.js/pull/1993) `mn-MN`, @rksp25 + - [#1997](https://github.com/validatorjs/validator.js/pull/1997) `fr-BJ`, @rkuma552 @rksp25 + - [#2001](https://github.com/validatorjs/validator.js/pull/2001) `mg-MG`, @ShivangiRai1310 + - [#2002](https://github.com/validatorjs/validator.js/pull/2002) `en-PG`, @kai2128 + - [#2004](https://github.com/validatorjs/validator.js/pull/2004) `en-AG`, @jiaweilow + - [#2007](https://github.com/validatorjs/validator.js/pull/2007) `en-AI`, @elaine1129 + - [#2011](https://github.com/validatorjs/validator.js/pull/2011) `en-KN`, @Eelyneee + - [#2041](https://github.com/validatorjs/validator.js/pull/2041) `fr-CD`, @coolbeatz71 + - [#2084](https://github.com/validatorjs/validator.js/pull/2084) `en-SS`, @cheboi + - [#2109](https://github.com/validatorjs/validator.js/pull/2109) `dv-MV`, @pano9000 + - [#2129](https://github.com/validatorjs/validator.js/pull/2129) `en-HN`, @WikiRik + - [#2148](https://github.com/validatorjs/validator.js/pull/2148) `ar-KW`, @Yazan-KE @WikiRik + - [#2112](https://github.com/validatorjs/validator.js/pull/2112) `el-GR`, @pano9000 + - [#2116](https://github.com/validatorjs/validator.js/pull/2116) `en-BM`, @pano9000 + - [#2155](https://github.com/validatorjs/validator.js/pull/2155) `ms-MY`, @pano9000 + - [#2156](https://github.com/validatorjs/validator.js/pull/2156) `ro-RO`, @pano9000 + +- `isLicensePlate`: + + - [#1665](https://github.com/validatorjs/validator.js/pull/1665) `sv-SE`, @elmaxe + - [#1895](https://github.com/validatorjs/validator.js/pull/1895) `hu-HU`, @szabolcstarnai + - [#1944](https://github.com/validatorjs/validator.js/pull/1944) `en-NI`, @NishantJS + - [#1945](https://github.com/validatorjs/validator.js/pull/1945) `de-DE`, @bennetfabian + - [#1945](https://github.com/validatorjs/validator.js/pull/1945) `de-DE`, @bennetfabian + - [#2103](https://github.com/validatorjs/validator.js/pull/2103) `es-AR`, @alvarocastro + +- `isPassportNumber`: + + - [#1515](https://github.com/validatorjs/validator.js/pull/1515) `JM`,`KZ`,`LI`,`NZ` @JuanFML + - [#1814](https://github.com/validatorjs/validator.js/pull/1814) `TH` @TonPC64 @braaar + - [#2061](https://github.com/validatorjs/validator.js/pull/2061) `AZ` @djeks922 + - [#2073](https://github.com/validatorjs/validator.js/pull/2073) `PH`,`PK` @digambar-t7 + +- `isPostalCode`: + + - [#1951](https://github.com/validatorjs/validator.js/pull/1951) `BA`, @matheusnascgomes + - [#2134](https://github.com/validatorjs/validator.js/pull/2134) `BY`, @pano9000 + - [#2136](https://github.com/validatorjs/validator.js/pull/2136) `IR`, @pano9000 + +- `isTaxID`: + - [#1867](https://github.com/validatorjs/validator.js/pull/1867) `en-CA`, @boonya + - [#1989](https://github.com/validatorjs/validator.js/pull/1989) `'AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'EL', 'HU', 'IE', 'LV', 'LT', 'LU', 'MT', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE', 'AL', 'MK', 'AU', 'BY', 'CA', 'IS', 'IN', 'ID', 'IL', 'KZ', 'NZ', 'NG', 'NO', 'PH', 'RU', 'SM', 'SA', 'RS', 'CH', 'TR', 'UA', 'UZ', 'AR', 'BO', 'BR', 'CL', 'CO', 'CR', 'EC', 'SV', 'GT', 'HN', 'MX', 'NI', 'PA', 'PY', 'PE', 'DO', 'UY', 'VE'` @Dev1lDragon + +## 13.7.0 + +### New Features + +- [#1706](https://github.com/validatorjs/validator.js/pull/1706) `isISO4217`, currency code validator @jpaya17 + +### Fixes and Enhancements + +- [#1647](https://github.com/validatorjs/validator.js/pull/1647) `isFQDN`: add `allow_wildcard` option @fasenderos +- [#1654](https://github.com/validatorjs/validator.js/pull/1654) `isRFC3339`: Disallow prepended and appended strings to RFC 3339 date-time @jmacmahon +- [#1658](https://github.com/validatorjs/validator.js/pull/1658) maintenance: increase code coverage @tux-tn +- [#1669](https://github.com/validatorjs/validator.js/pull/1669) `IBAN` export list of country codes that implement IBAN @dror-heller @fedeci +- [#1676](https://github.com/validatorjs/validator.js/pull/1676) `isBoolean`: add `loose` option @brybrophy +- [#1697](https://github.com/validatorjs/validator.js/pull/1697) maintenance: fix npm installation error @rubiin +- [#1708](https://github.com/validatorjs/validator.js/pull/1708) `isISO31661Alpha3`: perf @jpaya17 +- [#1711](https://github.com/validatorjs/validator.js/pull/1711) `isDate`: allow users to strictly validate dates with `.` as delimiter @flymans +- [#1715](https://github.com/validatorjs/validator.js/pull/1715) `isCreditCard`: fix for Union Pay cards @shreyassai123 +- [#1718](https://github.com/validatorjs/validator.js/pull/1718) `isEmail`: replace all dots in GMail length validation @DasDingGehtNicht +- [#1721](https://github.com/validatorjs/validator.js/pull/1721) `isURL`: add `allow_fragments` and `allow_query_components` @cowboy-bebug +- [#1724](https://github.com/validatorjs/validator.js/pull/1724) `isISO31661Alpha2`: perf @jpaya17 +- [#1730](https://github.com/validatorjs/validator.js/pull/1730) `isMagnetURI` @tux-tn +- [#1738](https://github.com/validatorjs/validator.js/pull/1738) `rtrim`: remove regex to prevent ReDOS attack @tux-tn +- [#1747](https://github.com/validatorjs/validator.js/pull/1747) maintenance: run scripts in parallel for build and clean @sachinraja +- [#1748](https://github.com/validatorjs/validator.js/pull/1748) `isURL`: higher priority to `whitelist` @deepanshu2506 +- [#1751](https://github.com/validatorjs/validator.js/pull/1751) `isURL`: allow url with colon and no port @MatteoPierro +- [#1777](https://github.com/validatorjs/validator.js/pull/1777) `isUUID`: fix for `null` version argument @theteladras +- [#1799](https://github.com/validatorjs/validator.js/pull/1799) `isFQDN`: check more special chars @MatteoPierro +- [#1833](https://github.com/validatorjs/validator.js/pull/1833) `isURL`: allow URL with an empty user @MiguelSavignano +- [#1835](https://github.com/validatorjs/validator.js/pull/1835) `unescape`: fixed bug where intermediate string contains escaped @Marcholio +- [#1836](https://github.com/validatorjs/validator.js/pull/1836) `contains`: can check that string contains seed multiple times @Marcholio +- [#1844](https://github.com/validatorjs/validator.js/pull/1844) docs: add CDN instructions @luiscobits +- [#1848](https://github.com/validatorjs/validator.js/pull/1848) `isUUID`: add support for validation of `v1` and `v2` @theteladras +- [#1941](https://github.com/validatorjs/validator.js/pull/1641) `isEmail`: add `host_blacklist` option @fedeci + +### New and Improved Locales + +- `isAlpha`, `isAlphanumeric`: + + - [#1716](https://github.com/validatorjs/validator.js/pull/1716) `hi-IN` @MiKr13 + - [#1837](https://github.com/validatorjs/validator.js/pull/1837) `fi-FI` @Marcholio + +- `isPassportNumber`: + + - [#1656](https://github.com/validatorjs/validator.js/pull/1656) `ID` @rubiin + - [#1714](https://github.com/validatorjs/validator.js/pull/1714) `CN` @anirudhgiri + - [#1809](https://github.com/validatorjs/validator.js/pull/1809) `PL` @Ronqn + - [#1810](https://github.com/validatorjs/validator.js/pull/1810) `RU` @Theta-Dev + +- `isPostalCode`: + + - [#1788](https://github.com/validatorjs/validator.js/pull/1788) `LK` @nimanthadilz + +- `isIdentityCard`: + + - [#1657](https://github.com/validatorjs/validator.js/pull/1657) `TH` @tithanayut + - [#1745](https://github.com/validatorjs/validator.js/pull/1745) `PL` @wiktorwojcik112 @fedeci @tux-tn + - [#1786](https://github.com/validatorjs/validator.js/pull/1786) `LK` @nimanthadilz @tux-tn + - [#1838](https://github.com/validatorjs/validator.js/pull/1838) `FI` @Marcholio + +- `isMobilePhone`: + + - [#1679](https://github.com/validatorjs/validator.js/pull/1679) `de-DE` @AnnaMariaJansen + - [#1689](https://github.com/validatorjs/validator.js/pull/1689) `vi-VN` @luisrivas + - [#1695](https://github.com/validatorjs/validator.js/pull/1695) [#1682](https://github.com/validatorjs/validator.js/pull/1682) `zh-CN` @laulujan @yisibl + - [#1734](https://github.com/validatorjs/validator.js/pull/1734) `es-VE` @islasjuanp + - [#1746](https://github.com/validatorjs/validator.js/pull/1746) `nl-BE` @divikshrivastava + - [#1765](https://github.com/validatorjs/validator.js/pull/1765) `es-CU` @pasagedev + - [#1766](https://github.com/validatorjs/validator.js/pull/1766) `es-SV`, @hereje + - [#1767](https://github.com/validatorjs/validator.js/pull/1767) `ar-PS`, @brendan-c + - [#1769](https://github.com/validatorjs/validator.js/pull/1769) `en-BM` @HackProAIT + - [#1770](https://github.com/validatorjs/validator.js/pull/1770) `dz-BT` @lakshayr003 + - [#1771](https://github.com/validatorjs/validator.js/pull/1771) `en-BW`, @mgndolan + - [#1772](https://github.com/validatorjs/validator.js/pull/1772) `fr-CM` @beckettnormington + - [#1778](https://github.com/validatorjs/validator.js/pull/1778) `en-PK` @ammad20120 @tux-tn + - [#1780](https://github.com/validatorjs/validator.js/pull/1780) `tk-TM`, @Husan-Eshonqulov + - [#1784](https://github.com/validatorjs/validator.js/pull/1784) `en-GY`, @mfkrause + - [#1785](https://github.com/validatorjs/validator.js/pull/1785) `si-LK` @Madhavi96 + - [#1797](https://github.com/validatorjs/validator.js/pull/1797) `fr-PF`, @hereje + - [#1820](https://github.com/validatorjs/validator.js/pull/1820) `en-KI`, @c-tanner + - [#1826](https://github.com/validatorjs/validator.js/pull/1826) `hu-HU` @danielTiringer + - [#1834](https://github.com/validatorjs/validator.js/pull/1834) `fr-BF`, `en-NA` @lakshayr003 + - [#1846](https://github.com/validatorjs/validator.js/pull/1846) `tg-TJ` @mgnss + +- `isLicensePlate`: + + - [#1565](https://github.com/validatorjs/validator.js/pull/1565) `cs-CZ` @filiptronicek + - [#1790](https://github.com/validatorjs/validator.js/pull/1790) `fi-FI` @Marcholio + +- `isVAT`: + - [#1825](https://github.com/validatorjs/validator.js/pull/1825) `NL` @zeno4ever + +#### 13.6.1 + +- **New features**: + + - [#1495](https://github.com/validatorjs/validator.js/pull/1495) `isLicensePlate` @firlus + +- **Fixes and Enhancements**: + + - [#1651](https://github.com/validatorjs/validator.js/pull/1651) fix ReDOS vulnerabilities in `isHSL` and `isEmail` @tux-tn + - [#1644](https://github.com/validatorjs/validator.js/pull/1644) `isURL`: Allow URLs to have only a username in the userinfo subcomponent @jbuchmann-coosto + - [#1633](https://github.com/validatorjs/validator.js/pull/1633) `isISIN`: optimization @bmacnaughton + - [#1632](https://github.com/validatorjs/validator.js/pull/1632) `isIP`: improved pattern for IPv4 and IPv6 @ognjenjevremovic + - [#1625](https://github.com/validatorjs/validator.js/pull/1625) fix `[A-z]` regex range on some validators @bmacnaughton + - [#1620](https://github.com/validatorjs/validator.js/pull/1620) fix docs @prahaladbelavadi + - [#1616](https://github.com/validatorjs/validator.js/pull/1616) `isMacAddress`: improve regexes and options @fedeci + - [#1603](https://github.com/validatorjs/validator.js/pull/1603) fix ReDOS vulnerabilities in `isSlug` and `rtrim` @fedeci + - [#1594](https://github.com/validatorjs/validator.js/pull/1594) `isIPRange`: add support for IPv6 @neilime + - [#1577](https://github.com/validatorjs/validator.js/pull/1577) `isEAN`: add support for EAN-14 @varsubham @tux-tn + - [#1566](https://github.com/validatorjs/validator.js/pull/1566) `isStrongPassword`: add `@` as a valid symbol @stingalleman + - [#1548](https://github.com/validatorjs/validator.js/pull/1548) `isBtcAddress`: add base58 @ezkemboi + - [#1546](https://github.com/validatorjs/validator.js/pull/1546) `isFQDN`: numeric domain names @tux-tn + +- **New and Improved locales**: + - `isIdentityCard`, `isPassportNumber`: + - [#1595](https://github.com/validatorjs/validator.js/pull/1595) `IR` @mhf-ir @fedeci + - [#1583](https://github.com/validatorjs/validator.js/pull/1583) `ar-LY` @asghaier76 @tux-tn + - [#1574](https://github.com/validatorjs/validator.js/pull/1574) `MY` @stranger26 @tux-tn + - `isMobilePhone`: + - [#1642](https://github.com/validatorjs/validator.js/pull/1642) `zh-CN` @Akira0705 + - [#1638](https://github.com/validatorjs/validator.js/pull/1638) `lv-LV` @AntonLukichev + - [#1635](https://github.com/validatorjs/validator.js/pull/1635) `en-GH` @ankorGH + - [#1604](https://github.com/validatorjs/validator.js/pull/1604) `mz-MZ` @salmento @tux-tn + - [#1575](https://github.com/validatorjs/validator.js/pull/1575) `vi-VN` @kyled7 + - [#1573](https://github.com/validatorjs/validator.js/pull/1573) `en-SG` @liliwei25 + - [#1554](https://github.com/validatorjs/validator.js/pull/1554) `de-CH`, `fr-CH`, `it-CH` @dinfekted + - [#1541](https://github.com/validatorjs/validator.js/pull/1541) [#1623](https://github.com/validatorjs/validator.js/pull/1623) `es-CO` @ezkemboi @tux-tn + - [#1506](https://github.com/validatorjs/validator.js/pull/1506) `ar-OM` @dev-sna + - [#1505](https://github.com/validatorjs/validator.js/pull/1505) `pt-AO` @AdilsonFuxe + - `isPostalCode`: + - [#1628](https://github.com/validatorjs/validator.js/pull/1628) `KR` @greatSumini + - `isTaxID`: + - [#1613](https://github.com/validatorjs/validator.js/pull/1613) `pt-BR` @mschunke + - [#1529](https://github.com/validatorjs/validator.js/pull/1529) `el-GR` @dspinellis + - `isVAT`: + - [#1536](https://github.com/validatorjs/validator.js/pull/1536) `IT` @fedeci + +#### ~~13.5.0~~ 13.5.1 + +- **New features**: + + - `isVAT` [#1463](https://github.com/validatorjs/validator.js/pull/1463) @ CodingNagger + - `isTaxID` [#1446](https://github.com/validatorjs/validator.js/pull/1446) @tplessas + - `isBase58` [#1445](https://github.com/validatorjs/validator.js/pull/1445) @ezkemboi + - `isStrongPassword` [#1348](https://github.com/validatorjs/validator.js/pull/1348) @door-bell + +- **Fixes and Enhancements**: + + - [#1486](https://github.com/validatorjs/validator.js/pull/1486) `isISO8601`: add `strictSeparator` @brostone51 + - [#1474](https://github.com/validatorjs/validator.js/pull/1474) `isFQDN`: make more strict @CristhianMotoche + - [#1469](https://github.com/validatorjs/validator.js/pull/1469) `isFQDN`: `allow_underscore` option @gibson042 + - [#1449](https://github.com/validatorjs/validator.js/pull/1449) `isEmail`: character blacklisting @rubiin + - [#1436](https://github.com/validatorjs/validator.js/pull/1436) `isURL`: added `require_port` option @yshanli + - [#1435](https://github.com/validatorjs/validator.js/pull/1435) `isEmail`: respect `ignore_max_length` option @evantahler + - [#1402](https://github.com/validatorjs/validator.js/pull/1402) `isDate`: add strictMode and prevent mixed delimiters @tux-tn + - [#1286](https://github.com/validatorjs/validator.js/pull/1286) `isAlpha`: support `ignore` option @mum-never-proud + +- **New and Improved locales**: + - `isAlpha`, `isAlphanumeric`: + - [#1528](https://github.com/validatorjs/validator.js/pull/1528) multiple fixes @tux-tn @purell + - [#1513](https://github.com/validatorjs/validator.js/pull/1513) `id-ID` and docs update @bekicot + - [#1484](https://github.com/validatorjs/validator.js/pull/1484) [#1481](https://github.com/validatorjs/validator.js/pull/1481) `th-TH` @ipiranhaa + - [#1455](https://github.com/validatorjs/validator.js/pull/1455) `fa-IR` @fakhrip + - [#1447](https://github.com/validatorjs/validator.js/pull/1447) `az-AZ` @saidfagan + - `isMobilePhone`: + - [#1521](https://github.com/validatorjs/validator.js/pull/1521) `ar-MA` @artpumpkin + - [#1492](https://github.com/validatorjs/validator.js/pull/1492) `de-LU`,`it-SM`, `sq-AL` and `ga-IE` @firlus + - [#1487](https://github.com/validatorjs/validator.js/pull/1487) `en-HN` @jehielmartinez + - [#1473](https://github.com/validatorjs/validator.js/pull/1473) `ar-LB`, `es-PE`, `ka-GE` @rubiin + - [#1470](https://github.com/validatorjs/validator.js/pull/1444) `es-DO` @devrasec + - [#1460](https://github.com/validatorjs/validator.js/pull/1444) `es-BO` @rubiin + - [#1444](https://github.com/validatorjs/validator.js/pull/1444) `es-AR` @csrgt + - [#1407](https://github.com/validatorjs/validator.js/pull/1407) `pt-BR` @viniciushvsilva + - `isPostalCode`: + - [#1534](https://github.com/validatorjs/validator.js/pull/1534) `CN` @httpsbao + - [#1515](https://github.com/validatorjs/validator.js/pull/1515) `IR` @masoudDaliriyan + - [#1502](https://github.com/validatorjs/validator.js/pull/1502) `SG`, `MY` @stranger26 + - [#1480](https://github.com/validatorjs/validator.js/pull/1480) `TH` @ipiranhaa + - [#1459](https://github.com/validatorjs/validator.js/pull/1456) `BY` @rubiin + - [#1456](https://github.com/validatorjs/validator.js/pull/1456) `DO` and `HT` @yomed + - `isPassportNumber`: + - [#1468](https://github.com/validatorjs/validator.js/pull/1468) `BY` @zenby + - [#1467](https://github.com/validatorjs/validator.js/pull/1467) `RU` @dkochetkov + +— this release is dedicated to @dbnandaa 🧒 + +#### 13.1.17 + +- **New features**: + - None +- **Fixes and chores**: + + - [#1425](https://github.com/validatorjs/validator.js/pull/1425) fix validation for _userinfo_ part for `isURL` @heanzyzabala + - [#1419](https://github.com/validatorjs/validator.js/pull/1419) fix `isBase32` and `isBase64` to validate empty strings properly @AberDerBart + - [#1408](https://github.com/validatorjs/validator.js/pull/1408) tests for `isTaxId` @dspinellis + - [#1397](https://github.com/validatorjs/validator.js/pull/1397) added `validate_length` option for `isURL` @tomgrossman + - [#1383](https://github.com/validatorjs/validator.js/pull/1383) [#1428](https://github.com/validatorjs/validator.js/pull/1428) doc typos @0xflotus @timgates42 + - [#1376](https://github.com/validatorjs/validator.js/pull/1376) add missing tests and switch to Coverall @tux-tn + - [#1373](https://github.com/validatorjs/validator.js/pull/1373) improve code coverage @ezkemboi + - [#1357](https://github.com/validatorjs/validator.js/pull/1357) add Node v6 on build pipeline @profnandaa + +- **New and Improved locales**: + - `isMobilePhone`: + - [#1439](https://github.com/validatorjs/validator.js/pull/1439) `az-AZ` @saidfagan + - [#1420](https://github.com/validatorjs/validator.js/pull/1420) `uz-Uz` @icyice0217 + - [#1391](https://github.com/validatorjs/validator.js/pull/1391) `de-DE` @heanzyzabala + - [#1388](https://github.com/validatorjs/validator.js/pull/1388) `en-PH` @stinkymonkeyph + - [#1370](https://github.com/validatorjs/validator.js/pull/1370) `es-ES` @rubiin + - [#1356](https://github.com/validatorjs/validator.js/pull/1356) `bs-BA` @MladenZeljic + - [#1303](https://github.com/validatorjs/validator.js/pull/1301) `zh-CN` @heathcliff-hu + - `isPostalCode`: + - [#1439](https://github.com/validatorjs/validator.js/pull/1439) `AZ` @saidfagan + - [#1370](https://github.com/validatorjs/validator.js/pull/1370) `ES` @rubiin + - [#1367](https://github.com/validatorjs/validator.js/pull/1367) `IL` @rubiin + - `isAlpha`, `isAlphanumeric`: + - [#1411](https://github.com/validatorjs/validator.js/pull/1411) `fa-AF`, `fa-IR` @stinkymonkeyph + - [#1371](https://github.com/validatorjs/validator.js/pull/1371) `vi-VN` @rubiin + - `isBAN`: + - [#1394](https://github.com/validatorjs/validator.js/pull/1394) `EG`, `SV` @heanzyzabala + - `isIdentityCard`: + - [#1384](https://github.com/validatorjs/validator.js/pull/1384) `IT` @lorenzodb1 + +#### 13.1.1 + +- Hotfix for a regex incompatibility in some browsers + ([#1355](https://github.com/validatorjs/validator.js/pull/1355) + +#### 13.1.0 + +- Added an `isIMEI()` validator + ([#1346](https://github.com/validatorjs/validator.js/pull/1346)) +- Added an `isDate()` validator + ([#1270](https://github.com/validatorjs/validator.js/pull/1270)) +- Added an `isTaxID()` validator + ([#1336](https://github.com/validatorjs/validator.js/pull/1336)) +- Added DMS support to `isLatLong()` + ([#1340](https://github.com/validatorjs/validator.js/pull/1340)) +- Added support for URL-safe base64 validation + ([#1277](https://github.com/validatorjs/validator.js/pull/1277)) +- Added support for primitives in `isJSON()` + ([#1328](https://github.com/validatorjs/validator.js/pull/1328)) +- Added support for case-insensitive matching to `contains()` + ([#1334](https://github.com/validatorjs/validator.js/pull/1334)) +- Support additional cards in `isCreditCard()` + ([#1177](https://github.com/validatorjs/validator.js/pull/1177)) +- Support additional currencies in `isCurrency()` + ([#1306](https://github.com/validatorjs/validator.js/pull/1306)) +- Fixed `isFQDN()` handling of certain special chars + ([#1091](https://github.com/validatorjs/validator.js/pull/1091)) +- Fixed a bug in `isSlug()` + ([#1338](https://github.com/validatorjs/validator.js/pull/1338)) +- New and improved locales + ([#1112](https://github.com/validatorjs/validator.js/pull/1112), + [#1167](https://github.com/validatorjs/validator.js/pull/1167), + [#1198](https://github.com/validatorjs/validator.js/pull/1198), + [#1199](https://github.com/validatorjs/validator.js/pull/1199), + [#1273](https://github.com/validatorjs/validator.js/pull/1273), + [#1279](https://github.com/validatorjs/validator.js/pull/1279), + [#1281](https://github.com/validatorjs/validator.js/pull/1281), + [#1293](https://github.com/validatorjs/validator.js/pull/1293), + [#1294](https://github.com/validatorjs/validator.js/pull/1294), + [#1311](https://github.com/validatorjs/validator.js/pull/1311), + [#1312](https://github.com/validatorjs/validator.js/pull/1312), + [#1313](https://github.com/validatorjs/validator.js/pull/1313), + [#1314](https://github.com/validatorjs/validator.js/pull/1314), + [#1315](https://github.com/validatorjs/validator.js/pull/1315), + [#1317](https://github.com/validatorjs/validator.js/pull/1317), + [#1322](https://github.com/validatorjs/validator.js/pull/1322), + [#1324](https://github.com/validatorjs/validator.js/pull/1324), + [#1330](https://github.com/validatorjs/validator.js/pull/1330), + [#1337](https://github.com/validatorjs/validator.js/pull/1337)) + +#### 13.0.0 + +- Added `isEthereumAddress()` validator + to validate [Ethereum addresses](https://en.wikipedia.org/wiki/Ethereum#Addresses) + ([#1117](https://github.com/validatorjs/validator.js/pull/1117)) +- Added `isBtcAddress()` validator + to validate [Bitcoin addresses](https://en.bitcoin.it/wiki/Address) + ([#1163](https://github.com/validatorjs/validator.js/pull/1163)) +- Added `isIBAN()` validator + to validate [International Bank Account Numbers](https://en.wikipedia.org/wiki/International_Bank_Account_Number) + ([#1243](https://github.com/validatorjs/validator.js/pull/1243)) +- Added `isEAN()` validator + to validate [International Article Numbers](https://en.wikipedia.org/wiki/International_Article_Number) + ([#1244](https://github.com/validatorjs/validator.js/pull/1244)) +- Added `isSemVer()` validator + to validate [Semantic Version Numbers](https://semver.org) + ([#1246](https://github.com/validatorjs/validator.js/pull/1246)) +- Added `isPassportNumber()` validator + ([#1250](https://github.com/validatorjs/validator.js/pull/1250)) +- Added `isRgbColor()` validator + ([#1141](https://github.com/validatorjs/validator.js/pull/1141)) +- Added `isHSL()` validator + ([#1159](https://github.com/validatorjs/validator.js/pull/1159)) +- Added `isLocale()` validator + ([#1072](https://github.com/validatorjs/validator.js/pull/1072)) +- Improved the `isIP()` validator + ([#1211](https://github.com/validatorjs/validator.js/pull/1211)) +- Improved the `isMACAddress()` validator + ([#1267](https://github.com/validatorjs/validator.js/pull/1267)) +- New and improved locales + ([#1238](https://github.com/validatorjs/validator.js/pull/1238), + [#1265](https://github.com/validatorjs/validator.js/pull/1265)) + +#### 12.2.0 + +- Support CSS Colors Level 4 spec + ([#1233](https://github.com/validatorjs/validator.js/pull/1233)) +- Improve the `toFloat()` sanitizer + ([#1227](https://github.com/validatorjs/validator.js/pull/1227)) +- New and improved locales + ([#1200](https://github.com/validatorjs/validator.js/pull/1200), + [#1207](https://github.com/validatorjs/validator.js/pull/1207), + [#1213](https://github.com/validatorjs/validator.js/pull/1213), + [#1217](https://github.com/validatorjs/validator.js/pull/1217), + [#1234](https://github.com/validatorjs/validator.js/pull/1234)) + +#### 12.1.0 + +- ES module for webpack tree shaking + ([#1015](https://github.com/validatorjs/validator.js/pull/1015)) +- Updated `isIP()` to accept scoped IPv6 addresses + ([#1160](https://github.com/validatorjs/validator.js/pull/1160)) +- New and improved locales + ([#1162](https://github.com/validatorjs/validator.js/pull/1162), + [#1183](https://github.com/validatorjs/validator.js/pull/1183), + [#1187](https://github.com/validatorjs/validator.js/pull/1187), + [#1191](https://github.com/validatorjs/validator.js/pull/1191)) + +#### 12.0.0 + +- Added `isOctal()` validator + ([#1153](https://github.com/validatorjs/validator.js/pull/1153)) +- Added `isSlug()` validator + ([#1096](https://github.com/validatorjs/validator.js/pull/1096)) +- Added `isBIC()` validator for bank identification codes + ([#1071](https://github.com/validatorjs/validator.js/pull/1071)) +- Allow uppercase chars in `isHash()` + ([#1062](https://github.com/validatorjs/validator.js/pull/1062)) +- Allow additional prefixes in `isHexadecimal()` + ([#1147](https://github.com/validatorjs/validator.js/pull/1147)) +- Allow additional separators in `isMACAddress()` + ([#1065](https://github.com/validatorjs/validator.js/pull/1065)) +- Better defaults for `isLength()` + ([#1070](https://github.com/validatorjs/validator.js/pull/1070)) +- Bug fixes + ([#1074](https://github.com/validatorjs/validator.js/pull/1074)) +- New and improved locales + ([#1059](https://github.com/validatorjs/validator.js/pull/1059), + [#1060](https://github.com/validatorjs/validator.js/pull/1060), + [#1069](https://github.com/validatorjs/validator.js/pull/1069), + [#1073](https://github.com/validatorjs/validator.js/pull/1073), + [#1082](https://github.com/validatorjs/validator.js/pull/1082), + [#1092](https://github.com/validatorjs/validator.js/pull/1092), + [#1121](https://github.com/validatorjs/validator.js/pull/1121), + [#1125](https://github.com/validatorjs/validator.js/pull/1125), + [#1132](https://github.com/validatorjs/validator.js/pull/1132), + [#1152](https://github.com/validatorjs/validator.js/pull/1152), + [#1165](https://github.com/validatorjs/validator.js/pull/1165), + [#1166](https://github.com/validatorjs/validator.js/pull/1166), + [#1174](https://github.com/validatorjs/validator.js/pull/1174)) + +#### 11.1.0 + +- Code coverage improvements + ([#1024](https://github.com/validatorjs/validator.js/pull/1024)) +- New and improved locales + ([#1035](https://github.com/validatorjs/validator.js/pull/1035), + [#1040](https://github.com/validatorjs/validator.js/pull/1040), + [#1041](https://github.com/validatorjs/validator.js/pull/1041), + [#1048](https://github.com/validatorjs/validator.js/pull/1048), + [#1049](https://github.com/validatorjs/validator.js/pull/1049), + [#1052](https://github.com/validatorjs/validator.js/pull/1052), + [#1054](https://github.com/validatorjs/validator.js/pull/1054), + [#1055](https://github.com/validatorjs/validator.js/pull/1055), + [#1056](https://github.com/validatorjs/validator.js/pull/1056), + [#1057](https://github.com/validatorjs/validator.js/pull/1057)) + +#### 11.0.0 + +- Added a `isBase32()` validator + ([#1023](https://github.com/validatorjs/validator.js/pull/1023)) +- Updated `isEmail()` to validate display names according to RFC2822 + ([#1004](https://github.com/validatorjs/validator.js/pull/1004)) +- Updated `isEmail()` to check total email length + ([#1007](https://github.com/validatorjs/validator.js/pull/1007)) +- The internal `toString()` util is no longer exported + ([0277eb](https://github.com/validatorjs/validator.js/commit/0277eb00d245a3479af52adf7d927d4036895650)) +- New and improved locales + ([#999](https://github.com/validatorjs/validator.js/pull/999), + [#1010](https://github.com/validatorjs/validator.js/pull/1010), + [#1017](https://github.com/validatorjs/validator.js/pull/1017), + [#1022](https://github.com/validatorjs/validator.js/pull/1022), + [#1031](https://github.com/validatorjs/validator.js/pull/1031), + [#1032](https://github.com/validatorjs/validator.js/pull/1032)) + +#### 10.11.0 + +- Fix imports like `import .. from "validator/lib/.."` + ([#961](https://github.com/validatorjs/validator.js/pull/961)) +- New locale + ([#958](https://github.com/validatorjs/validator.js/pull/958)) + +#### 10.10.0 + +- `isISO8601()` strict mode now works in the browser + ([#932](https://github.com/validatorjs/validator.js/pull/932)) +- New and improved locales + ([#931](https://github.com/validatorjs/validator.js/pull/931), + [#933](https://github.com/validatorjs/validator.js/pull/933), + [#947](https://github.com/validatorjs/validator.js/pull/947), + [#950](https://github.com/validatorjs/validator.js/pull/950)) + +#### 10.9.0 + +- Added an option to `isURL()` to reject email-like URLs + ([#901](https://github.com/validatorjs/validator.js/pull/901)) +- Added a `strict` option to `isISO8601()` + ([#910](https://github.com/validatorjs/validator.js/pull/910)) +- Relaxed `isJWT()` signature requirements + ([#906](https://github.com/validatorjs/validator.js/pull/906)) +- New and improved locales + ([#899](https://github.com/validatorjs/validator.js/pull/899), + [#904](https://github.com/validatorjs/validator.js/pull/904), + [#913](https://github.com/validatorjs/validator.js/pull/913), + [#916](https://github.com/validatorjs/validator.js/pull/916), + [#925](https://github.com/validatorjs/validator.js/pull/925), + [#928](https://github.com/validatorjs/validator.js/pull/928)) + +#### 10.8.0 + +- Added `isIdentityCard()` + ([#846](https://github.com/validatorjs/validator.js/pull/846)) +- Better error when validators are passed an invalid type + ([#895](https://github.com/validatorjs/validator.js/pull/895)) +- Locales are now exported + ([#890](https://github.com/validatorjs/validator.js/pull/890), + [#892](https://github.com/validatorjs/validator.js/pull/892)) +- New locale + ([#896](https://github.com/validatorjs/validator.js/pull/896)) + +#### 10.7.1 + +- Ignore case when checking URL protocol + ([#887](https://github.com/validatorjs/validator.js/issues/887)) +- Locale fix + ([#889](https://github.com/validatorjs/validator.js/pull/889)) + +#### 10.7.0 + +- Added `isMagnetURI()` to validate [magnet URIs](https://en.wikipedia.org/wiki/Magnet_URI_scheme) + ([#884](https://github.com/validatorjs/validator.js/pull/884)) +- Added `isJWT()` to validate [JSON web tokens](https://en.wikipedia.org/wiki/JSON_Web_Token) + ([#885](https://github.com/validatorjs/validator.js/pull/885)) + +#### 10.6.0 + +- Updated `isMobilePhone()` to match any locale's pattern by default + ([#874](https://github.com/validatorjs/validator.js/pull/874)) +- Added an option to ignore whitespace in `isEmpty()` + ([#880](https://github.com/validatorjs/validator.js/pull/880)) +- New and improved locales + ([#878](https://github.com/validatorjs/validator.js/pull/878), + [#879](https://github.com/validatorjs/validator.js/pull/879)) + +#### 10.5.0 + +- Disabled domain-specific email validation + ([#873](https://github.com/validatorjs/validator.js/pull/873)) +- Added support for IP hostnames in `isEmail()` + ([#845](https://github.com/validatorjs/validator.js/pull/845)) +- Added a `no_symbols` option to `isNumeric()` + ([#848](https://github.com/validatorjs/validator.js/pull/848)) +- Added a `no_colons` option to `isMACAddress()` + ([#849](https://github.com/validatorjs/validator.js/pull/849)) +- Updated `isURL()` to reject protocol relative URLs unless a flag is set + ([#860](https://github.com/validatorjs/validator.js/issues/860)) +- New and improved locales + ([#801](https://github.com/validatorjs/validator.js/pull/801), + [#856](https://github.com/validatorjs/validator.js/pull/856), + [#859](https://github.com/validatorjs/validator.js/issues/859), + [#861](https://github.com/validatorjs/validator.js/pull/861), + [#862](https://github.com/validatorjs/validator.js/pull/862), + [#863](https://github.com/validatorjs/validator.js/pull/863), + [#864](https://github.com/validatorjs/validator.js/pull/864), + [#870](https://github.com/validatorjs/validator.js/pull/870), + [#872](https://github.com/validatorjs/validator.js/pull/872)) + +#### 10.4.0 + +- Added an `isIPRange()` validator + ([#842](https://github.com/validatorjs/validator.js/pull/842)) +- Accept an array of locales in `isMobilePhone()` + ([#742](https://github.com/validatorjs/validator.js/pull/742)) +- New locale + ([#843](https://github.com/validatorjs/validator.js/pull/843)) + +#### 10.3.0 + +- Strict Gmail validation in `isEmail()` + ([#832](https://github.com/validatorjs/validator.js/pull/832)) +- New locales + ([#831](https://github.com/validatorjs/validator.js/pull/831), + [#835](https://github.com/validatorjs/validator.js/pull/835), + [#836](https://github.com/validatorjs/validator.js/pull/836)) + +#### 10.2.0 + +- Export the list of supported locales in `isPostalCode()` + ([#830](https://github.com/validatorjs/validator.js/pull/830)) + +#### 10.1.0 + +- Added an `isISO31661Alpha3()` validator + ([#809](https://github.com/validatorjs/validator.js/pull/809)) + +#### 10.0.0 + +- Allow floating points in `isNumeric()` + ([#810](https://github.com/validatorjs/validator.js/pull/810)) +- Disallow GMail addresses with multiple consecutive dots, or leading/trailing dots + ([#820](https://github.com/validatorjs/validator.js/pull/820)) +- Added an `isRFC3339()` validator + ([#816](https://github.com/validatorjs/validator.js/pull/816)) +- Reject domain parts longer than 63 octets in `isFQDN()`, `isURL()` and `isEmail()` + ([bb3e542](https://github.com/validatorjs/validator.js/commit/bb3e542)) +- Added a new Amex prefix to `isCreditCard()` + ([#805](https://github.com/validatorjs/validator.js/pull/805)) +- Fixed `isFloat()` min/max/gt/lt filters when a locale with a comma decimal is used + ([2b70821](https://github.com/validatorjs/validator.js/commit/2b70821)) +- Normalize Yandex emails + ([#807](https://github.com/validatorjs/validator.js/pull/807)) +- New locales + ([#803](https://github.com/validatorjs/validator.js/pull/803)) + +#### 9.4.1 + +- Patched a [REDOS](https://en.wikipedia.org/wiki/ReDoS) vulnerability in `isDataURI` +- New and improved locales + ([#788](https://github.com/validatorjs/validator.js/pull/788)) + +#### 9.4.0 + +- Added an option to `isMobilePhone` to require a country code + ([#769](https://github.com/validatorjs/validator.js/pull/769)) +- New and improved locales + ([#785](https://github.com/validatorjs/validator.js/pull/785)) + +#### 9.3.0 + +- New and improved locales + ([#763](https://github.com/validatorjs/validator.js/pull/763), + [#768](https://github.com/validatorjs/validator.js/pull/768), + [#774](https://github.com/validatorjs/validator.js/pull/774), + [#777](https://github.com/validatorjs/validator.js/pull/777), + [#779](https://github.com/validatorjs/validator.js/pull/779)) + +#### 9.2.0 + +- Added an `isMimeType()` validator + ([#760](https://github.com/validatorjs/validator.js/pull/760)) +- New and improved locales + ([#753](https://github.com/validatorjs/validator.js/pull/753), + [#755](https://github.com/validatorjs/validator.js/pull/755), + [#764](https://github.com/validatorjs/validator.js/pull/764)) + #### 9.1.2 - Fixed a bug with the `isFloat` validator - ([#752](https://github.com/chriso/validator.js/pull/752)) + ([#752](https://github.com/validatorjs/validator.js/pull/752)) #### 9.1.1 - Locale fixes - ([#738](https://github.com/chriso/validator.js/pull/738), - [#739](https://github.com/chriso/validator.js/pull/739)) + ([#738](https://github.com/validatorjs/validator.js/pull/738), + [#739](https://github.com/validatorjs/validator.js/pull/739)) #### 9.1.0 - Added an `isISO31661Alpha2()` validator - ([#734](https://github.com/chriso/validator.js/pull/734)) + ([#734](https://github.com/validatorjs/validator.js/pull/734)) - New locales - ([#735](https://github.com/chriso/validator.js/pull/735), - [#737](https://github.com/chriso/validator.js/pull/737)) + ([#735](https://github.com/validatorjs/validator.js/pull/735), + [#737](https://github.com/validatorjs/validator.js/pull/737)) #### 9.0.0 - `normalizeEmail()` no longer validates the email address - ([#725](https://github.com/chriso/validator.js/pull/725)) + ([#725](https://github.com/validatorjs/validator.js/pull/725)) - Added locale-aware validation to `isFloat()` and `isDecimal()` - ([#721](https://github.com/chriso/validator.js/pull/721)) + ([#721](https://github.com/validatorjs/validator.js/pull/721)) - Added an `isPort()` validator - ([#733](https://github.com/chriso/validator.js/pull/733)) + ([#733](https://github.com/validatorjs/validator.js/pull/733)) - New locales - ([#731](https://github.com/chriso/validator.js/pull/731)) + ([#731](https://github.com/validatorjs/validator.js/pull/731)) #### 8.2.0 - Added an `isHash()` validator - ([#711](https://github.com/chriso/validator.js/pull/711)) + ([#711](https://github.com/validatorjs/validator.js/pull/711)) - Control decimal places in `isCurrency()` - ([#713](https://github.com/chriso/validator.js/pull/713)) + ([#713](https://github.com/validatorjs/validator.js/pull/713)) - New and improved locales - ([#700](https://github.com/chriso/validator.js/pull/700), - [#701](https://github.com/chriso/validator.js/pull/701), - [#714](https://github.com/chriso/validator.js/pull/714), - [#715](https://github.com/chriso/validator.js/pull/715), - [#718](https://github.com/chriso/validator.js/pull/718)) + ([#700](https://github.com/validatorjs/validator.js/pull/700), + [#701](https://github.com/validatorjs/validator.js/pull/701), + [#714](https://github.com/validatorjs/validator.js/pull/714), + [#715](https://github.com/validatorjs/validator.js/pull/715), + [#718](https://github.com/validatorjs/validator.js/pull/718)) #### 8.1.0 - Fix `require('validator/lib/isIS8601')` calls - ([#688](https://github.com/chriso/validator.js/issues/688)) + ([#688](https://github.com/validatorjs/validator.js/issues/688)) - Added an `isLatLong()` and `isPostalCode()` validator - ([#684](https://github.com/chriso/validator.js/pull/684)) + ([#684](https://github.com/validatorjs/validator.js/pull/684)) - Allow comma in email display names - ([#692](https://github.com/chriso/validator.js/pull/692)) + ([#692](https://github.com/validatorjs/validator.js/pull/692)) - Add missing string to `unescape()` - ([#690](https://github.com/chriso/validator.js/pull/690)) + ([#690](https://github.com/validatorjs/validator.js/pull/690)) - Fix `isMobilePhone()` with Node <= 6.x - ([#681](https://github.com/chriso/validator.js/issues/681)) + ([#681](https://github.com/validatorjs/validator.js/issues/681)) - New locales - ([#695](https://github.com/chriso/validator.js/pull/695)) + ([#695](https://github.com/validatorjs/validator.js/pull/695)) #### 8.0.0 - `isURL()` now requires the `require_tld: false` option to validate `localhost` - ([#675](https://github.com/chriso/validator.js/issues/675)) + ([#675](https://github.com/validatorjs/validator.js/issues/675)) - `isURL()` now rejects URLs that are protocol only - ([#642](https://github.com/chriso/validator.js/issues/642)) + ([#642](https://github.com/validatorjs/validator.js/issues/642)) - Fixed a bug where `isMobilePhone()` would silently return false if the locale was invalid or unsupported - ([#657](https://github.com/chriso/validator.js/issues/657)) + ([#657](https://github.com/validatorjs/validator.js/issues/657)) #### 7.2.0 - Added an option to validate any phone locale - ([#663](https://github.com/chriso/validator.js/pull/663)) + ([#663](https://github.com/validatorjs/validator.js/pull/663)) - Fixed a bug in credit card validation - ([#672](https://github.com/chriso/validator.js/pull/672)) + ([#672](https://github.com/validatorjs/validator.js/pull/672)) - Disallow whitespace, including unicode whitespace, in TLDs - ([#677](https://github.com/chriso/validator.js/pull/677)) + ([#677](https://github.com/validatorjs/validator.js/pull/677)) - New locales - ([#673](https://github.com/chriso/validator.js/pull/673), - [#676](https://github.com/chriso/validator.js/pull/676)) + ([#673](https://github.com/validatorjs/validator.js/pull/673), + [#676](https://github.com/validatorjs/validator.js/pull/676)) #### 7.1.0 - Added an `isISRC()` validator for [ISRC](https://en.wikipedia.org/wiki/International_Standard_Recording_Code) - ([#660](https://github.com/chriso/validator.js/pull/660)) + ([#660](https://github.com/validatorjs/validator.js/pull/660)) - Fixed a bug in credit card validation - ([#670](https://github.com/chriso/validator.js/pull/670)) + ([#670](https://github.com/validatorjs/validator.js/pull/670)) - Reduced the maximum allowed address in `isEmail()` based on [RFC3696 errata](http://www.rfc-editor.org/errata_search.php?rfc=3696&eid=1690) - ([#655](https://github.com/chriso/validator.js/issues/655)) + ([#655](https://github.com/validatorjs/validator.js/issues/655)) - New locales - ([#647](https://github.com/chriso/validator.js/pull/647), - [#667](https://github.com/chriso/validator.js/pull/667), - [#667](https://github.com/chriso/validator.js/pull/667), - [#671](https://github.com/chriso/validator.js/pull/671)) + ([#647](https://github.com/validatorjs/validator.js/pull/647), + [#667](https://github.com/validatorjs/validator.js/pull/667), + [#667](https://github.com/validatorjs/validator.js/pull/667), + [#671](https://github.com/validatorjs/validator.js/pull/671)) #### 7.0.0 @@ -99,290 +943,290 @@ #### 6.3.0 - Allow values like `-.01` in `isFloat()` - ([#618](https://github.com/chriso/validator.js/issues/618)) + ([#618](https://github.com/validatorjs/validator.js/issues/618)) - New locales - ([#616](https://github.com/chriso/validator.js/pull/616), - [#622](https://github.com/chriso/validator.js/pull/622), - [#627](https://github.com/chriso/validator.js/pull/627), - [#630](https://github.com/chriso/validator.js/pull/630)) + ([#616](https://github.com/validatorjs/validator.js/pull/616), + [#622](https://github.com/validatorjs/validator.js/pull/622), + [#627](https://github.com/validatorjs/validator.js/pull/627), + [#630](https://github.com/validatorjs/validator.js/pull/630)) #### 6.2.1 - Disallow `<` and `>` in URLs - ([#613](https://github.com/chriso/validator.js/issues/613)) + ([#613](https://github.com/validatorjs/validator.js/issues/613)) - New locales - ([#610](https://github.com/chriso/validator.js/pull/610)) + ([#610](https://github.com/validatorjs/validator.js/pull/610)) #### 6.2.0 - Added an option to require an email display name - ([#607](https://github.com/chriso/validator.js/pull/607)) + ([#607](https://github.com/validatorjs/validator.js/pull/607)) - Added support for `lt` and `gt` to `isInt()` - ([#588](https://github.com/chriso/validator.js/pull/588)) + ([#588](https://github.com/validatorjs/validator.js/pull/588)) - New locales - ([#601](https://github.com/chriso/validator.js/pull/601)) + ([#601](https://github.com/validatorjs/validator.js/pull/601)) #### 6.1.0 - Added support for greater or less than in `isFloat()` - ([#544](https://github.com/chriso/validator.js/issues/544)) + ([#544](https://github.com/validatorjs/validator.js/issues/544)) - Added support for ISSN validation via `isISSN()` - ([#593](https://github.com/chriso/validator.js/pull/593)) + ([#593](https://github.com/validatorjs/validator.js/pull/593)) - Fixed a bug in `normalizeEmail()` - ([#594](https://github.com/chriso/validator.js/issues/594)) + ([#594](https://github.com/validatorjs/validator.js/issues/594)) - New locales - ([#585](https://github.com/chriso/validator.js/pull/585)) + ([#585](https://github.com/validatorjs/validator.js/pull/585)) #### 6.0.0 - Renamed `isNull()` to `isEmpty()` - ([#574](https://github.com/chriso/validator.js/issues/574)) + ([#574](https://github.com/validatorjs/validator.js/issues/574)) - Backslash is now escaped in `escape()` - ([#516](https://github.com/chriso/validator.js/issues/516)) + ([#516](https://github.com/validatorjs/validator.js/issues/516)) - Improved `normalizeEmail()` - ([#583](https://github.com/chriso/validator.js/pull/583)) + ([#583](https://github.com/validatorjs/validator.js/pull/583)) - Allow leading zeroes by default in `isInt()` - ([#532](https://github.com/chriso/validator.js/pull/532)) + ([#532](https://github.com/validatorjs/validator.js/pull/532)) #### 5.7.0 - Added support for IPv6 in `isURL()` - ([#564](https://github.com/chriso/validator.js/issues/564)) + ([#564](https://github.com/validatorjs/validator.js/issues/564)) - Added support for urls without a host (e.g. `file:///foo.txt`) in `isURL()` - ([#563](https://github.com/chriso/validator.js/issues/563)) + ([#563](https://github.com/validatorjs/validator.js/issues/563)) - Added support for regular expressions in the `isURL()` host whitelist and blacklist - ([#562](https://github.com/chriso/validator.js/issues/562)) + ([#562](https://github.com/validatorjs/validator.js/issues/562)) - Added support for MasterCard 2-Series BIN - ([#576](https://github.com/chriso/validator.js/pull/576)) + ([#576](https://github.com/validatorjs/validator.js/pull/576)) - New locales - ([#575](https://github.com/chriso/validator.js/pull/575), - [#552](https://github.com/chriso/validator.js/issues/552)) + ([#575](https://github.com/validatorjs/validator.js/pull/575), + [#552](https://github.com/validatorjs/validator.js/issues/552)) #### 5.6.0 - Added an `isMD5()` validator - ([#557](https://github.com/chriso/validator.js/pull/557)) + ([#557](https://github.com/validatorjs/validator.js/pull/557)) - Fixed an exceptional case in `isDate()` - ([#566](https://github.com/chriso/validator.js/pull/566)) + ([#566](https://github.com/validatorjs/validator.js/pull/566)) - New locales - ([#559](https://github.com/chriso/validator.js/pull/559), - [#568](https://github.com/chriso/validator.js/pull/568), - [#571](https://github.com/chriso/validator.js/pull/571), - [#573](https://github.com/chriso/validator.js/pull/573)) + ([#559](https://github.com/validatorjs/validator.js/pull/559), + [#568](https://github.com/validatorjs/validator.js/pull/568), + [#571](https://github.com/validatorjs/validator.js/pull/571), + [#573](https://github.com/validatorjs/validator.js/pull/573)) #### 5.5.0 - Fixed a regex denial of service in `trim()` and `rtrim()` - ([#556](https://github.com/chriso/validator.js/pull/556)) + ([#556](https://github.com/validatorjs/validator.js/pull/556)) - Added an Algerian locale to `isMobilePhone()` - ([#540](https://github.com/chriso/validator.js/pull/540)) + ([#540](https://github.com/validatorjs/validator.js/pull/540)) - Fixed the Hungarian locale in `isAlpha()` and `isAlphanumeric()` - ([#541](https://github.com/chriso/validator.js/pull/541)) + ([#541](https://github.com/validatorjs/validator.js/pull/541)) - Added a Polish locale to `isMobilePhone()` - ([#545](https://github.com/chriso/validator.js/pull/545)) + ([#545](https://github.com/validatorjs/validator.js/pull/545)) #### 5.4.0 - Accept Union Pay credit cards in `isCreditCard()` - ([#539](https://github.com/chriso/validator.js/pull/539)) + ([#539](https://github.com/validatorjs/validator.js/pull/539)) - Added Danish locale to `isMobilePhone()` - ([#538](https://github.com/chriso/validator.js/pull/538)) + ([#538](https://github.com/validatorjs/validator.js/pull/538)) - Added Hungarian locales to `isAlpha()`, `isAlphanumeric()` and `isMobilePhone()` - ([#537](https://github.com/chriso/validator.js/pull/537)) + ([#537](https://github.com/validatorjs/validator.js/pull/537)) #### 5.3.0 - Added an `allow_leading_zeroes` option to `isInt()` - ([#532](https://github.com/chriso/validator.js/pull/532)) + ([#532](https://github.com/validatorjs/validator.js/pull/532)) - Adjust Chinese mobile phone validation - ([#523](https://github.com/chriso/validator.js/pull/523)) + ([#523](https://github.com/validatorjs/validator.js/pull/523)) - Added a Canadian locale to `isMobilePhone()` - ([#524](https://github.com/chriso/validator.js/issues/524)) + ([#524](https://github.com/validatorjs/validator.js/issues/524)) #### 5.2.0 - Added a `isDataURI()` validator - ([#521](https://github.com/chriso/validator.js/pull/521)) + ([#521](https://github.com/validatorjs/validator.js/pull/521)) - Added Czech locales - ([#522](https://github.com/chriso/validator.js/pull/522)) + ([#522](https://github.com/validatorjs/validator.js/pull/522)) - Fixed a bug with `isURL()` when protocol was missing and "://" appeared in the query - ([#518](https://github.com/chriso/validator.js/issues/518)) + ([#518](https://github.com/validatorjs/validator.js/issues/518)) #### 5.1.0 - Added a `unescape()` HTML function - ([#509](https://github.com/chriso/validator.js/pull/509)) + ([#509](https://github.com/validatorjs/validator.js/pull/509)) - Added a Malaysian locale to `isMobilePhone()` - ([#507](https://github.com/chriso/validator.js/pull/507)) + ([#507](https://github.com/validatorjs/validator.js/pull/507)) - Added Polish locales to `isAlpha()` and `isAlphanumeric()` - ([#506](https://github.com/chriso/validator.js/pull/506)) + ([#506](https://github.com/validatorjs/validator.js/pull/506)) - Added Turkish locales to `isAlpha()`, `isAlphanumeric()` and `isMobilePhone()` - ([#512](https://github.com/chriso/validator.js/pull/512)) + ([#512](https://github.com/validatorjs/validator.js/pull/512)) - Allow >1 underscore in hostnames when using `allow_underscores` - ([#510](https://github.com/chriso/validator.js/issues/510)) + ([#510](https://github.com/validatorjs/validator.js/issues/510)) #### 5.0.0 - Migrate to ES6 - ([#496](https://github.com/chriso/validator.js/pull/496)) + ([#496](https://github.com/validatorjs/validator.js/pull/496)) - Break the library up so that individual functions can be imported - ([#496](https://github.com/chriso/validator.js/pull/496)) + ([#496](https://github.com/validatorjs/validator.js/pull/496)) - Remove auto-coercion of input to a string - ([#496](https://github.com/chriso/validator.js/pull/496)) + ([#496](https://github.com/validatorjs/validator.js/pull/496)) - Remove the `extend()` function - ([#496](https://github.com/chriso/validator.js/pull/496)) + ([#496](https://github.com/validatorjs/validator.js/pull/496)) - Added Arabic locales to `isAlpha()` and `isAlphanumeric()` - ([#496](https://github.com/chriso/validator.js/pull/496#issuecomment-184781730)) + ([#496](https://github.com/validatorjs/validator.js/pull/496#issuecomment-184781730)) - Fix validation of very large base64 strings - ([#503](https://github.com/chriso/validator.js/pull/503)) + ([#503](https://github.com/validatorjs/validator.js/pull/503)) #### 4.9.0 - Added a Russian locale to `isAlpha()` and `isAlphanumeric()` - ([#499](https://github.com/chriso/validator.js/pull/499)) + ([#499](https://github.com/validatorjs/validator.js/pull/499)) - Remove the restriction on adjacent hyphens in hostnames - ([#500](https://github.com/chriso/validator.js/issues/500)) + ([#500](https://github.com/validatorjs/validator.js/issues/500)) #### 4.8.0 - Added Spanish, French, Portuguese and Dutch support for `isAlpha()` and `isAlphanumeric()` - ([#492](https://github.com/chriso/validator.js/pull/492)) + ([#492](https://github.com/validatorjs/validator.js/pull/492)) - Added a Brazilian locale to `isMobilePhone()` - ([#489](https://github.com/chriso/validator.js/pull/489)) + ([#489](https://github.com/validatorjs/validator.js/pull/489)) - Reject IPv4 addresses with invalid zero padding - ([#490](https://github.com/chriso/validator.js/pull/490)) + ([#490](https://github.com/validatorjs/validator.js/pull/490)) - Fix the client-side version when used with RequireJS - ([#494](https://github.com/chriso/validator.js/issues/494)) + ([#494](https://github.com/validatorjs/validator.js/issues/494)) #### 4.7.1 - Use [node-depd](https://github.com/dougwilson/nodejs-depd) to print deprecation notices - ([#487](https://github.com/chriso/validator.js/issues/487)) + ([#487](https://github.com/validatorjs/validator.js/issues/487)) #### 4.7.0 - Print a deprecation warning if validator input is not a string - ([1f67e1e](https://github.com/chriso/validator.js/commit/1f67e1e15198c0ae735151290dc8dc2bf14da254)). + ([1f67e1e](https://github.com/validatorjs/validator.js/commit/1f67e1e15198c0ae735151290dc8dc2bf14da254)). Note that this will be an error in v5. - Added a German locale to `isMobilePhone()`, `isAlpha()` and `isAlphanumeric()` - ([#477](https://github.com/chriso/validator.js/pull/477)) + ([#477](https://github.com/validatorjs/validator.js/pull/477)) - Added a Finnish locale to `isMobilePhone()` - ([#455](https://github.com/chriso/validator.js/pull/455)) + ([#455](https://github.com/validatorjs/validator.js/pull/455)) #### 4.6.1 - Fix coercion of objects: `Object.toString()` is `[object Object]` not `""` - ([a57f3c8](https://github.com/chriso/validator.js/commit/a57f3c843c715fba2664ee22ec80e9e28e88e0a6)) + ([a57f3c8](https://github.com/validatorjs/validator.js/commit/a57f3c843c715fba2664ee22ec80e9e28e88e0a6)) #### 4.6.0 - Added a Spanish locale to `isMobilePhone()` - ([#481](https://github.com/chriso/validator.js/pull/481)) + ([#481](https://github.com/validatorjs/validator.js/pull/481)) - Fix string coercion of objects created with `Object.create(null)` - ([#484](https://github.com/chriso/validator.js/issues/484)) + ([#484](https://github.com/validatorjs/validator.js/issues/484)) #### 4.5.2 - Fix a timezone issue with short-form ISO 8601 dates, e.g. `validator.isDate('2011-12-21')` - ([#480](https://github.com/chriso/validator.js/issues/480)) + ([#480](https://github.com/validatorjs/validator.js/issues/480)) #### 4.5.1 - Make `isLength()` / `isByteLength()` accept `{min, max}` as options object. - ([#474](https://github.com/chriso/validator.js/issues/474)) + ([#474](https://github.com/validatorjs/validator.js/issues/474)) #### 4.5.0 - Add validation for Indian mobile phone numbers - ([#471](https://github.com/chriso/validator.js/pull/471)) + ([#471](https://github.com/validatorjs/validator.js/pull/471)) - Tweak Greek and Chinese mobile phone validation - ([#467](https://github.com/chriso/validator.js/pull/467), - [#468](https://github.com/chriso/validator.js/pull/468)) + ([#467](https://github.com/validatorjs/validator.js/pull/467), + [#468](https://github.com/validatorjs/validator.js/pull/468)) - Fixed a bug in `isDate()` when validating ISO 8601 dates without a timezone - ([#472](https://github.com/chriso/validator.js/issues/472)) + ([#472](https://github.com/validatorjs/validator.js/issues/472)) #### 4.4.1 - Allow triple hyphens in IDNA hostnames - ([#466](https://github.com/chriso/validator.js/issues/466)) + ([#466](https://github.com/validatorjs/validator.js/issues/466)) #### 4.4.0 - Added `isMACAddress()` validator - ([#458](https://github.com/chriso/validator.js/pull/458)) + ([#458](https://github.com/validatorjs/validator.js/pull/458)) - Added `isWhitelisted()` validator - ([#462](https://github.com/chriso/validator.js/pull/462)) + ([#462](https://github.com/validatorjs/validator.js/pull/462)) - Added a New Zealand locale to `isMobilePhone()` - ([#452](https://github.com/chriso/validator.js/pull/452)) + ([#452](https://github.com/validatorjs/validator.js/pull/452)) - Added options to control GMail address normalization - ([#460](https://github.com/chriso/validator.js/pull/460)) + ([#460](https://github.com/validatorjs/validator.js/pull/460)) #### 4.3.0 - Support Ember CLI module definitions - ([#448](https://github.com/chriso/validator.js/pull/448)) + ([#448](https://github.com/validatorjs/validator.js/pull/448)) - Added a Vietnam locale to `isMobilePhone()` - ([#451](https://github.com/chriso/validator.js/pull/451)) + ([#451](https://github.com/validatorjs/validator.js/pull/451)) #### 4.2.1 - Fix `isDate()` handling of RFC2822 timezones - ([#447](https://github.com/chriso/validator.js/pull/447)) + ([#447](https://github.com/validatorjs/validator.js/pull/447)) #### 4.2.0 - Fix `isDate()` handling of ISO8601 timezones - ([#444](https://github.com/chriso/validator.js/pull/444)) + ([#444](https://github.com/validatorjs/validator.js/pull/444)) - Fix the incorrect `isFloat('.') === true` - ([#443](https://github.com/chriso/validator.js/pull/443)) + ([#443](https://github.com/validatorjs/validator.js/pull/443)) - Added a Norwegian locale to `isMobilePhone()` - ([#439](https://github.com/chriso/validator.js/pull/439)) + ([#439](https://github.com/validatorjs/validator.js/pull/439)) #### 4.1.0 - General `isDate()` improvements - ([#431](https://github.com/chriso/validator.js/pull/431)) + ([#431](https://github.com/validatorjs/validator.js/pull/431)) - Tests now require node 4.0+ - ([#438](https://github.com/chriso/validator.js/pull/438)) + ([#438](https://github.com/validatorjs/validator.js/pull/438)) #### 4.0.6 - Added a Taiwan locale to `isMobilePhone()` - ([#432](https://github.com/chriso/validator.js/pull/432)) + ([#432](https://github.com/validatorjs/validator.js/pull/432)) - Fixed a bug in `isBefore()` where it would return `null` - ([#436](https://github.com/chriso/validator.js/pull/436)) + ([#436](https://github.com/validatorjs/validator.js/pull/436)) #### 4.0.5 - Fixed a denial of service vulnerability in the `isEmail()` regex - ([#152](https://github.com/chriso/validator.js/issues/152#issuecomment-131874928)) + ([#152](https://github.com/validatorjs/validator.js/issues/152#issuecomment-131874928)) #### 4.0.4 - Reverted the leap year validation in `isDate()` as it introduced some regressions - ([#422](https://github.com/chriso/validator.js/issues/422), [#423](https://github.com/chriso/validator.js/issues/423)) + ([#422](https://github.com/validatorjs/validator.js/issues/422), [#423](https://github.com/validatorjs/validator.js/issues/423)) #### 4.0.3 - Added leap year validation to `isDate()` - ([#418](https://github.com/chriso/validator.js/pull/418)) + ([#418](https://github.com/validatorjs/validator.js/pull/418)) #### 4.0.2 - Fixed `isDecimal()` with an empty string - ([#419](https://github.com/chriso/validator.js/issues/419)) + ([#419](https://github.com/validatorjs/validator.js/issues/419)) #### 4.0.1 - Fixed `isByteLength()` with certain strings - ([09f0c6d](https://github.com/chriso/validator.js/commit/09f0c6d2321f0c78af6a7de42e91b63955e4c01e)) + ([09f0c6d](https://github.com/validatorjs/validator.js/commit/09f0c6d2321f0c78af6a7de42e91b63955e4c01e)) - Put length restrictions on email parts - ([#258](https://github.com/chriso/validator.js/issues/258#issuecomment-127173612)) + ([#258](https://github.com/validatorjs/validator.js/issues/258#issuecomment-127173612)) #### 4.0.0 - Simplified the `isEmail()` regex and fixed some edge cases - ([#258](https://github.com/chriso/validator.js/issues/258#issuecomment-127173612)) + ([#258](https://github.com/validatorjs/validator.js/issues/258#issuecomment-127173612)) - Added ISO 8601 date validation via `isISO8601()` - ([#373](https://github.com/chriso/validator.js/issues/373)) + ([#373](https://github.com/validatorjs/validator.js/issues/373)) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..747a30ea2 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,38 @@ +# Contributing to validator.js +Welcome to validator.js repository!! We appreciate your interest in contributing to this open library and for helping our community grow. + +## How to Contribute +### Code Contribution +In general, we follow the "fork-and-pull" Git workflow. + +1. [Fork](https://docs.github.com/en/get-started/exploring-projects-on-github/contributing-to-a-project) the repository on GitHub +2. Clone the project to your local machine +3. Work on your fork + * Install the project using `npm install --legacy-peer-deps` (see [issue](https://github.com/validatorjs/validator.js/issues/2123)) + * Make your changes and additions + - Most of your changes should be focused on src/ and test/ folders and/or [README.md](https://github.com/validatorjs/validator.js/blob/master/README.md). + - Files such as validator.js, validator.min.js and files in lib/ folder are autogenerated when running tests (npm test) and need not to be changed **manually**. + * Change or add tests if needed + * Run tests and make sure they pass + * Add changes to README.md if needed +4. Commit changes to your own branch +5. **Make sure** you merge the latest from "upstream" and resolve conflicts if there is any +6. Repeat step 3(3) above +7. Push your work back up to your fork +8. Submit a Pull request so that we can review your changes + +#### Run Tests +Tests are using mocha. To run the tests use: + +```sh +$ npm test +``` + +### Financial Contribution +We welcome financial contributions on our [open collective](https://opencollective.com/validatorjs). + +You can opt to become a [backer](https://opencollective.com/validatorjs#backer) or a [sponsor](https://opencollective.com/validatorjs#sponsor) and help our project sustain over time. + +Thank you to the people who have already contributed: + + \ No newline at end of file diff --git a/LICENSE b/LICENSE index 37a807cbc..4e49a3840 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2016 Chris O'Hara +Copyright (c) 2018 Chris O'Hara Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/README.md b/README.md index 81a8fb5fb..37fbd5e3d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,12 @@ # validator.js - -[![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Downloads][downloads-image]][npm-url] +[![NPM version][npm-image]][npm-url] +[![CI][ci-image]][ci-url] +[![Coverage][codecov-image]][codecov-url] +[![Downloads][downloads-image]][npm-url] +[![Backers on Open Collective](https://opencollective.com/validatorjs/backers/badge.svg)](#backers) +[![Sponsors on Open Collective](https://opencollective.com/validatorjs/sponsors/badge.svg)](#sponsors) +[![License](https://img.shields.io/badge/License-MIT-red.svg)](https://github.com/alguerocode/validator.js/blob/master/LICENSE) +[![Gitter][gitter-image]][gitter-url] A library of string validators and sanitizers. @@ -9,13 +15,19 @@ A library of string validators and sanitizers. **This library validates and sanitizes strings only.** If you're not sure if your input is a string, coerce it using `input + ''`. -Passing anything other than a string is an error. +Passing anything other than a string will result in an error. ## Installation and Usage ### Server-side usage -Install the library with `npm install validator` +Install the `validator` package as: + +```sh +npm i validator +yarn add validator +pnpm i validator +``` #### No ES6 @@ -37,6 +49,12 @@ Or, import only a subset of the library: import isEmail from 'validator/lib/isEmail'; ``` +#### Tree-shakeable ES imports + +```javascript +import isEmail from 'validator/es/lib/isEmail'; +``` + ### Client-side usage The library can be loaded either as a standalone script, or through an [AMD][amd]-compatible loader @@ -54,64 +72,108 @@ The library can also be installed through [bower][bower] $ bower install validator-js ``` +CDN + +```html + +``` + ## Validators Here is a list of the validators currently available. Validator | Description --------------------------------------- | -------------------------------------- -***contains(str, seed)*** | check if the string contains the seed. +**contains(str, seed [, options])** | check if the string contains the seed.

`options` is an object that defaults to `{ ignoreCase: false, minOccurrences: 1 }`.
Options:
`ignoreCase`: Ignore case when doing comparison, default false.
`minOccurrences`: Minimum number of occurrences for the seed in the string. Defaults to 1. **equals(str, comparison)** | check if the string matches the comparison. -**isAfter(str [, date])** | check if the string is a date that's after the specified date (defaults to now). -**isAlpha(str [, locale])** | check if the string contains only letters (a-zA-Z).

Locale is one of `['ar', 'ar-AE', 'ar-BH', 'ar-DZ', 'ar-EG', 'ar-IQ', 'ar-JO', 'ar-KW', 'ar-LB', 'ar-LY', 'ar-MA', 'ar-QA', 'ar-QM', 'ar-SA', 'ar-SD', 'ar-SY', 'ar-TN', 'ar-YE', 'cs-CZ', 'da-DK', 'de-DE', 'en-AU', 'en-GB', 'en-HK', 'en-IN', 'en-NZ', 'en-US', 'en-ZA', 'en-ZM', 'es-ES', 'fr-FR', 'hu-HU', 'it-IT', 'nb-NO', 'nl-NL', 'nn-NO', 'pl-PL', 'pt-BR', 'pt-PT', 'ru-RU', 'sr-RS', 'sr-RS@latin', 'sv-SE', 'tr-TR', 'uk-UA']`) and defaults to `en-US`. -**isAlphanumeric(str [, locale])** | check if the string contains only letters and numbers.

Locale is one of `['ar', 'ar-AE', 'ar-BH', 'ar-DZ', 'ar-EG', 'ar-IQ', 'ar-JO', 'ar-KW', 'ar-LB', 'ar-LY', 'ar-MA', 'ar-QA', 'ar-QM', 'ar-SA', 'ar-SD', 'ar-SY', 'ar-TN', 'ar-YE', 'cs-CZ', 'da-DK', 'de-DE', 'en-AU', 'en-GB', 'en-HK', 'en-IN', 'en-NZ', 'en-US', 'en-ZA', 'en-ZM', 'es-ES', 'fr-FR', 'hu-HU', 'it-IT', 'nb-NO', 'nl-NL', 'nn-NO', 'pl-PL', 'pt-BR', 'pt-PT', 'ru-RU', 'sr-RS', 'sr-RS@latin', 'sv-SE', 'tr-TR', 'uk-UA']`) and defaults to `en-US`. +**isAbaRouting(str)** | check if the string is an ABA routing number for US bank account / cheque. +**isAfter(str [, options])** | check if the string is a date that is after the specified date.

`options` is an object that defaults to `{ comparisonDate: Date().toString() }`.
**Options:**
`comparisonDate`: Date to compare to. Defaults to `Date().toString()` (now). +**isAlpha(str [, locale, options])** | check if the string contains only letters (a-zA-Z).

`locale` is one of `['ar', 'ar-AE', 'ar-BH', 'ar-DZ', 'ar-EG', 'ar-IQ', 'ar-JO', 'ar-KW', 'ar-LB', 'ar-LY', 'ar-MA', 'ar-QA', 'ar-QM', 'ar-SA', 'ar-SD', 'ar-SY', 'ar-TN', 'ar-YE', 'bg-BG', 'bn', 'bn-IN', '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-IR', 'fi-FI', 'fr-CA', 'fr-FR', 'gu-IN', 'he', 'hi-IN', 'hu-HU', 'it-IT', 'ja-JP', 'kk-KZ', 'kn-IN', 'ko-KR', 'ku-IQ', 'ml-IN', 'nb-NO', 'nl-NL', 'nn-NO', 'or-IN', 'pa-IN', 'pl-PL', 'pt-BR', 'pt-PT', 'ru-RU', 'si-LK', 'sk-SK', 'sl-SI', 'sr-RS', 'sr-RS@latin', 'sv-SE', 'ta-IN', 'te-IN', 'th-TH', 'tr-TR', 'uk-UA']` and defaults to `en-US`. Locale list is `validator.isAlphaLocales`. `options` is an optional object that can be supplied with the following key(s): `ignore` which can either be a String or RegExp of characters to be ignored e.g. " -" will ignore spaces and -'s. +**isAlphanumeric(str [, locale, options])** | check if the string contains only letters and numbers (a-zA-Z0-9).

`locale` is one of `['ar', 'ar-AE', 'ar-BH', 'ar-DZ', 'ar-EG', 'ar-IQ', 'ar-JO', 'ar-KW', 'ar-LB', 'ar-LY', 'ar-MA', 'ar-QA', 'ar-QM', 'ar-SA', 'ar-SD', 'ar-SY', 'ar-TN', 'ar-YE', 'bg-BG', 'bn', 'bn-IN', '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-IR', 'fi-FI', 'fr-CA', 'fr-FR', 'gu-IN', 'he', 'hi-IN', 'hu-HU', 'it-IT', 'ja-JP', 'kk-KZ', 'kn-IN', 'ko-KR', 'ku-IQ', 'ml-IN', 'nb-NO', 'nl-NL', 'nn-NO', 'or-IN', 'pa-IN', 'pl-PL', 'pt-BR', 'pt-PT', 'ru-RU', 'si-LK', 'sk-SK', 'sl-SI', 'sr-RS', 'sr-RS@latin', 'sv-SE', 'ta-IN', 'te-IN', 'th-TH', 'tr-TR', 'uk-UA']`) and defaults to `en-US`. Locale list is `validator.isAlphanumericLocales`. `options` is an optional object that can be supplied with the following key(s): `ignore` which can either be a String or RegExp of characters to be ignored e.g. " -" will ignore spaces and -'s. **isAscii(str)** | check if the string contains ASCII chars only. -**isBase64(str)** | check if a string is base64 encoded. -**isBefore(str [, date])** | check if the string is a date that's before the specified date. -**isBoolean(str)** | check if a string is a boolean. -**isByteLength(str, options)** | check if the string's length (in UTF-8 bytes) falls in a range.

`options` is an object which defaults to `{min:0, max: undefined}`. -**isCreditCard(str)** | check if the string is a credit card. -**isCurrency(str, options)** | check if the string is a valid currency amount.

`options` is an object which defaults to `{symbol: '$', require_symbol: false, allow_space_after_symbol: false, symbol_after_digits: false, allow_negatives: true, parens_for_negatives: false, negative_sign_before_digits: false, negative_sign_after_digits: false, allow_negative_sign_placeholder: false, thousands_separator: ',', decimal_separator: '.', allow_decimal: true, require_decimal: false, digits_after_decimal: [2], allow_space_after_digits: false}`.
**Note:** The array `digits_after_decimal` is filled with the exact number of digits allowd not a range, for example a range 1 to 3 will be given as [1, 2, 3]. -**isDataURI(str)** | check if the string is a [data uri format](https://developer.mozilla.org/en-US/docs/Web/HTTP/data_URIs). -**isDecimal(str, options)** | check if the string represents a decimal number, such as 0.1, .3, 1.1, 1.00003, 4.0, etc.

`options` is an opject which defaults to `{force_decimal: false, decimal_digits: '1,', locale: 'en-US'}`

`locale` determine the decimal separator and is one of `['ar', 'ar-AE', 'ar-BH', 'ar-DZ', 'ar-EG', 'ar-IQ', 'ar-JO', 'ar-KW', 'ar-LB', 'ar-LY', 'ar-MA', 'ar-QA', 'ar-QM', 'ar-SA', 'ar-SD', 'ar-SY', 'ar-TN', 'ar-YE', 'cs-CZ', 'da-DK', 'de-DE', 'en-AU', 'en-GB', 'en-HK', 'en-IN', 'en-NZ', 'en-US', 'en-ZA', 'en-ZM', 'es-ES', 'fr-FR', 'hu-HU', 'it-IT', 'nb-NO', 'nl-NL', 'nn-NO', 'pl-PL', 'pt-BR', 'pt-PT', 'ru-RU', 'sr-RS', 'sr-RS@latin', 'sv-SE', 'tr-TR', 'uk-UA']`.
**Note:** `decimal_digits` is given as a range like '1,3', a specific value like '3' or min like '1,'. -**isDivisibleBy(str, number)** | check if the string is a number that's divisible by another. -**isEmail(str [, options])** | check if the string is an email.

`options` is an object which defaults to `{ allow_display_name: false, require_display_name: false, allow_utf8_local_part: true, require_tld: true }`. If `allow_display_name` is set to true, the validator will also match `Display Name `. If `require_display_name` is set to true, the validator will reject strings without the format `Display Name `. If `allow_utf8_local_part` is set to false, the validator will not allow any non-English UTF8 character in email address' local part. If `require_tld` is set to false, e-mail addresses without having TLD in their domain will also be matched. -**isEmpty(str)** | check if the string has a length of zero. -**isFQDN(str [, options])** | check if the string is a fully qualified domain name (e.g. domain.com).

`options` is an object which defaults to `{ require_tld: true, allow_underscores: false, allow_trailing_dot: false }`. -**isFloat(str [, options])** | check if the string is a float.

`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.

`min` and `max` are equivalent to 'greater or equal' and 'less or equal', respectively while `gt` and `lt` are their strict counterparts.

`locale` determine the decimal separator and is one of `['ar', 'ar-AE', 'ar-BH', 'ar-DZ', 'ar-EG', 'ar-IQ', 'ar-JO', 'ar-KW', 'ar-LB', 'ar-LY', 'ar-MA', 'ar-QA', 'ar-QM', 'ar-SA', 'ar-SD', 'ar-SY', 'ar-TN', 'ar-YE', 'cs-CZ', 'da-DK', 'de-DE', 'en-AU', 'en-GB', 'en-HK', 'en-IN', 'en-NZ', 'en-US', 'en-ZA', 'en-ZM', 'es-ES', 'fr-FR', 'hu-HU', 'it-IT', 'nb-NO', 'nl-NL', 'nn-NO', 'pl-PL', 'pt-BR', 'pt-PT', 'ru-RU', 'sr-RS', 'sr-RS@latin', 'sv-SE', 'tr-TR', 'uk-UA']`. +**isBase32(str [, options])** | check if the string is base32 encoded. `options` is optional and defaults to `{ crockford: false }`.
When `crockford` is true it tests the given base32 encoded string using [Crockford's base32 alternative][Crockford Base32]. +**isBase58(str)** | check if the string is base58 encoded. +**isBase64(str [, options])** | check if the string is base64 encoded. `options` is optional and defaults to `{ urlSafe: false, padding: true }`
when `urlSafe` is true default value for `padding` is false and it tests the given base64 encoded string is [url safe][Base64 URL Safe]. +**isBefore(str [, options])** | check if the string is a date that is before the specified date.

`options` is an object that defaults to `{ comparisonDate: Date().toString() }`.

**Options:**
`comparisonDate`: Date to compare to. Defaults to `Date().toString()` (now). +**isBIC(str)** | check if the string is a BIC (Bank Identification Code) or SWIFT code. +**isBoolean(str [, options])** | check if the string is a boolean.
`options` is an object which defaults to `{ loose: false }`. If `loose` is set to false, the validator will strictly match ['true', 'false', '0', '1']. If `loose` is set to true, the validator will also match 'yes', 'no', and will match a valid boolean string of any case. (e.g.: ['true', 'True', 'TRUE']). +**isBtcAddress(str)** | check if the string is a valid BTC address. +**isByteLength(str [, options])** | check if the string's length (in UTF-8 bytes) falls in a range.

`options` is an object which defaults to `{ min: 0, max: undefined }`. +**isCreditCard(str [, options])** | check if the string is a credit card number.

`options` is an optional object that can be supplied with the following key(s): `provider` is an optional key whose value should be a string, and defines the company issuing the credit card. Valid values include `['amex', 'dinersclub', 'discover', 'jcb', 'mastercard', 'unionpay', 'visa']` or blank will check for any provider. +**isCurrency(str [, options])** | check if the string is a valid currency amount.

`options` is an object which defaults to `{ symbol: '$', require_symbol: false, allow_space_after_symbol: false, symbol_after_digits: false, allow_negatives: true, parens_for_negatives: false, negative_sign_before_digits: false, negative_sign_after_digits: false, allow_negative_sign_placeholder: false, thousands_separator: ',', decimal_separator: '.', allow_decimal: true, require_decimal: false, digits_after_decimal: [2], allow_space_after_digits: false }`.
**Note:** The array `digits_after_decimal` is filled with the exact number of digits allowed not a range, for example a range 1 to 3 will be given as [1, 2, 3]. +**isDataURI(str)** | check if the string is a [data uri format][Data URI Format]. +**isDate(str [, options])** | check if the string is a valid date. e.g. [`2002-07-15`, new Date()].

`options` is an object which can contain the keys `format`, `strictMode` and/or `delimiters`.

`format` is a string and defaults to `YYYY/MM/DD`.

`strictMode` is a boolean and defaults to `false`. If `strictMode` is set to true, the validator will reject strings different from `format`.

`delimiters` is an array of allowed date delimiters and defaults to `['/', '-']`. +**isDecimal(str [, options])** | check if the string represents a decimal number, such as 0.1, .3, 1.1, 1.00003, 4.0, etc.

`options` is an object which defaults to `{force_decimal: false, decimal_digits: '1,', locale: 'en-US'}`.

`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']`.
**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.

`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 `. If `require_display_name` is set to true, the validator will reject strings without the format `Display Name `. 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.

`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.

`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.

`min` and `max` are equivalent to 'greater or equal' and 'less or equal', respectively while `gt` and `lt` are their strict counterparts.

`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`. +**isFQDN(str [, options])** | check if the string is a fully qualified domain name (e.g. domain.com).

`options` is an object which defaults to `{ require_tld: true, allow_underscores: false, allow_trailing_dot: false, allow_numeric_tld: false, allow_wildcard: false, ignore_max_length: false }`.

`require_tld` - If set to false the validator will not check if the domain includes a TLD.
`allow_underscores` - if set to true, the validator will allow underscores in the domain.
`allow_trailing_dot` - if set to true, the validator will allow the domain to end with a `.` character.
`allow_numeric_tld` - if set to true, the validator will allow the TLD of the domain to be made up solely of numbers.
`allow_wildcard` - if set to true, the validator will allow domains starting with `*.` (e.g. `*.example.com` or `*.shop.example.com`).
`ignore_max_length` - if set to true, the validator will not check for the standard max length of a domain.
+**isFreightContainerID(str)** | alias for `isISO6346`, check if the string is a valid [ISO 6346](https://en.wikipedia.org/wiki/ISO_6346) shipping container identification. **isFullWidth(str)** | check if the string contains any full-width chars. **isHalfWidth(str)** | check if the string contains any half-width chars. -**isHash(str, algorithm)** | check if the string is a hash of type algorithm.

Algorithm is one of `['md4', 'md5', 'sha1', 'sha256', 'sha384', 'sha512', 'ripemd128', 'ripemd160', 'tiger128', 'tiger160', 'tiger192', 'crc32', 'crc32b']` -**isHexColor(str)** | check if the string is a hexadecimal color. +**isHash(str, algorithm)** | check if the string is a hash of type algorithm.

Algorithm is one of `['crc32', 'crc32b', 'md4', 'md5', 'ripemd128', 'ripemd160', 'sha1', 'sha256', 'sha384', 'sha512', 'tiger128', 'tiger160', 'tiger192']`. **isHexadecimal(str)** | check if the string is a hexadecimal number. -**isIP(str [, version])** | check if the string is an IP (version 4 or 6). -**isISBN(str [, version])** | check if the string is an ISBN (version 10 or 13). -**isISSN(str [, options])** | check if the string is an [ISSN](https://en.wikipedia.org/wiki/International_Standard_Serial_Number).

`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. -**isISIN(str)** | check if the string is an [ISIN][ISIN] (stock/security identifier). -**isISO8601(str)** | check if the string is a valid [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) date. -**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. -**isISRC(str)** | check if the string is a [ISRC](https://en.wikipedia.org/wiki/International_Standard_Recording_Code). -**isIn(str, values)** | check if the string is in a array of allowed values. +**isHexColor(str)** | check if the string is a hexadecimal color. +**isHSL(str)** | check if the string is an HSL (hue, saturation, lightness, optional alpha) color based on [CSS Colors Level 4 specification][CSS Colors Level 4 Specification].

Comma-separated format supported. Space-separated format supported with the exception of a few edge cases (ex: `hsl(200grad+.1%62%/1)`). +**isIBAN(str, [, options])** | check if the string is an IBAN (International Bank Account Number).

`options` is an object which accepts two attributes: `whitelist`: where you can restrict IBAN codes you want to receive data from and `blacklist`: where you can remove some of the countries from the current list. For both you can use an array with the following values `['AD','AE','AL','AT','AZ','BA','BE','BG','BH','BR','BY','CH','CR','CY','CZ','DE','DK','DO','EE','EG','ES','FI','FO','FR','GB','GE','GI','GL','GR','GT','HR','HU','IE','IL','IQ','IR','IS','IT','JO','KW','KZ','LB','LC','LI','LT','LU','LV','MC','MD','ME','MK','MR','MT','MU','MZ','NL','NO','PK','PL','PS','PT','QA','RO','RS','SA','SC','SE','SI','SK','SM','SV','TL','TN','TR','UA','VA','VG','XK']`. +**isIdentityCard(str [, locale])** | check if the string is a valid identity card code.

`locale` is one of `['LK', 'PL', 'ES', 'FI', 'IN', 'IT', 'IR', 'MZ', 'NO', 'TH', 'zh-TW', 'he-IL', 'ar-LY', 'ar-TN', 'zh-CN', 'zh-HK', 'PK']` OR `'any'`. If 'any' is used, function will check if any of the locales match.

Defaults to 'any'. +**isIMEI(str [, options]))** | check if the string is a valid [IMEI number][IMEI]. IMEI should be of format `###############` or `##-######-######-#`.

`options` is an object which can contain the keys `allow_hyphens`. Defaults to first format. If `allow_hyphens` is set to true, the validator will validate the second format. +**isIn(str, values)** | check if the string is in an array of allowed values. **isInt(str [, options])** | check if the string is an integer.

`options` is an object which can contain the keys `min` and/or `max` to check the integer is within boundaries (e.g. `{ min: 10, max: 99 }`). `options` can also contain the key `allow_leading_zeroes`, which when set to false will disallow integer values with leading zeroes (e.g. `{ allow_leading_zeroes: false }`). Finally, `options` can contain the keys `gt` and/or `lt` which will enforce integers being greater than or less than, respectively, the value provided (e.g. `{gt: 1, lt: 4}` for a number between 1 and 4). -**isJSON(str)** | check if the string is valid JSON (note: uses JSON.parse). -**isLatLong(str)**                     | check if the string is a valid latitude-longitude coordinate in the format `lat,long` or `lat, long`. -**isLength(str, options)** | check if the string's length falls in a range.

`options` is an object which defaults to `{min:0, max: undefined}`. Note: this function takes into account surrogate pairs. +**isIP(str [, options])** | check if the string is an IP address (version 4 or 6).

`options` is an object that defaults to `{ version: '' }`.

**Options:**
`version`: defines which IP version to compare to. Accepted values: `4`, `6`, `'4'`, `'6'`. +**isIPRange(str [, version])** | check if the string is an IP Range (version 4 or 6). +**isISBN(str [, options])** | check if the string is an [ISBN][ISBN].

`options` is an object that has no default.
**Options:**
`version`: ISBN version to compare to. Accepted values are '10' and '13'. If none provided, both will be tested. +**isISIN(str)** | check if the string is an [ISIN][ISIN] (stock/security identifier). +**isISO6346(str)** | check if the string is a valid [ISO 6346](https://en.wikipedia.org/wiki/ISO_6346) shipping container identification. +**isISO6391(str)** | check if the string is a valid [ISO 639-1][ISO 639-1] language code. +**isISO8601(str [, options])** | check if the string is a valid [ISO 8601][ISO 8601] date.
`options` is an object which defaults to `{ strict: false, strictSeparator: false }`. If `strict` is true, date strings with invalid dates like `2009-02-29` will be invalid. If `strictSeparator` is true, date strings with date and time separated by anything other than a T will be invalid. +**isISO15924(str)** | check if the string is a valid [ISO 15924][ISO 15924] officially assigned script code. +**isISO31661Alpha2(str)** | check if the string is a valid [ISO 3166-1 alpha-2][ISO 3166-1 alpha-2] officially assigned country code. +**isISO31661Alpha3(str)** | check if the string is a valid [ISO 3166-1 alpha-3][ISO 3166-1 alpha-3] officially assigned country code. +**isISO31661Numeric(str)** | check if the string is a valid [ISO 3166-1 numeric][ISO 3166-1 numeric] officially assigned country code. +**isISO4217(str)** | check if the string is a valid [ISO 4217][ISO 4217] officially assigned currency code. +**isISRC(str)** | check if the string is an [ISRC][ISRC]. +**isISSN(str [, options])** | check if the string is an [ISSN][ISSN].

`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).

`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. +**isJWT(str)** | check if the string is valid JWT token. +**isLatLong(str [, options])** | check if the string is a valid latitude-longitude coordinate in the format `lat,long` or `lat, long`.

`options` is an object that defaults to `{ checkDMS: false }`. Pass `checkDMS` as `true` to validate DMS(degrees, minutes, and seconds) latitude-longitude format. +**isLength(str [, options])** | check if the string's length falls in a range and equal to any of the integers of the `discreteLengths` array if provided.

`options` is an object which defaults to `{ min: 0, max: undefined, discreteLengths: undefined }`. Note: this function takes into account surrogate pairs. +**isLicensePlate(str, locale)** | check if the string matches the format of a country's license plate.

`locale` is one of `['cs-CZ', 'de-DE', 'de-LI', 'en-IN', 'en-SG', 'en-PK', 'es-AR', 'hu-HU', 'pt-BR', 'pt-PT', 'sq-AL', 'sv-SE']` or `'any'`. +**isLocale(str)** | check if the string is a locale. **isLowercase(str)** | check if the string is lowercase. -**isMACAddress(str)** | check if the string is a MAC address. -**isMD5(str)** | check if the string is a MD5 hash. -**isMobilePhone(str, locale)** | check if the string is a mobile phone number,

(locale is one of `['ar-AE', 'ar-DZ','ar-EG', 'ar-JO', 'ar-SA', 'ar-SY', 'cs-CZ', 'de-DE', 'da-DK', 'el-GR', 'en-AU', 'en-CA', 'en-GB', 'en-HK', 'en-IN', 'en-KE', 'en-NG', 'en-NZ', 'en-RW', 'en-SG', 'en-UG', 'en-US', 'en-TZ', 'en-ZA', 'en-ZM', 'en-PK', 'es-ES', 'et-EE', 'fa-IR', 'fi-FI', 'fr-FR', 'he-IL', 'hu-HU', 'it-IT', 'ja-JP', 'ko-KR', 'lt-LT', 'ms-MY', 'nb-NO', 'nn-NO', 'pl-PL', 'pt-PT', 'ro-RO', 'ru-RU', 'sk-SK', 'sr-RS', 'tr-TR', 'uk-UA', 'vi-VN', 'zh-CN', 'zh-HK', 'zh-TW']` OR 'any'. If 'any' is used, function will check if any of the locales match). +**isLuhnNumber(str)** | check if the string passes the [Luhn algorithm check](https://en.wikipedia.org/wiki/Luhn_algorithm). +**isMACAddress(str [, options])** | check if the string is a MAC address.

`options` is an object which defaults to `{ no_separators: false }`. It allows the use of hyphens, spaces or dots e.g. '01 02 03 04 05 ab', '01-02-03-04-05-ab' or '0102.0304.05ab'. If `no_separators` is true, the validator will then only check MAC addresses without separators. The options also allow a `eui` property to specify if it needs to be validated against EUI-48 or EUI-64. The accepted values of `eui` are: 48, 64. +**isMagnetURI(str)** | check if the string is a [Magnet URI format][Magnet URI Format]. +**isMailtoURI(str, [, options])** | check if the string is a [Mailto URI format][Mailto URI Format].

`options` is an object of validating emails inside the URI (check `isEmail`s options for details). +**isMD5(str)** | check if the string is a MD5 hash.

Please note that you can also use the `isHash(str, 'md5')` function. Keep in mind that MD5 has some collision weaknesses compared to other algorithms (e.g., SHA). +**isMimeType(str)** | check if the string matches to a valid [MIME type][MIME Type] format. +**isMobilePhone(str [, locale [, options]])** | check if the string is a mobile phone number,

`locale` is either an array of locales (e.g. `['sk-SK', 'sr-RS']`) OR one of `['am-Am', 'ar-AE', 'ar-BH', 'ar-DZ', 'ar-EG', 'ar-EH', 'ar-IQ', 'ar-JO', 'ar-KW', 'ar-PS', 'ar-SA', 'ar-SD', 'ar-SY', 'ar-TN', 'ar-YE', 'az-AZ', 'az-LB', 'az-LY', 'be-BY', 'bg-BG', 'bn-BD', 'bs-BA', 'ca-AD', 'cs-CZ', 'da-DK', 'de-AT', 'de-CH', 'de-DE', 'de-LU', 'dv-MV', 'dz-BT', 'el-CY', 'el-GR', 'en-AG', 'en-AI', 'en-AU', 'en-BM', 'en-BS', 'en-BW', 'en-CA', 'en-GB', 'en-GG', 'en-GH', 'en-GY', 'en-HK', 'en-IE', 'en-IN', 'en-JM', 'en-KE', 'en-KI', 'en-KN', 'en-LS', 'en-MO', 'en-MT', 'en-MU', 'en-MW', 'en-NG', 'en-NZ', 'en-PG', 'en-PH', 'en-PK', 'en-RW', 'en-SG', 'en-SL', 'en-SS', 'en-TZ', 'en-UG', 'en-US', 'en-ZA', 'en-ZM', 'en-ZW', 'es-AR', 'es-BO', 'es-CL', 'es-CO', 'es-CR', 'es-CU', 'es-DO', 'es-EC', 'es-ES', 'es-GT','es-HN', 'es-MX', 'es-NI', 'es-PA', 'es-PE', 'es-PY', 'es-SV', 'es-UY', 'es-VE', 'et-EE', 'fa-AF', 'fa-IR', 'fi-FI', 'fj-FJ', 'fo-FO', 'fr-BE', 'fr-BF', 'fr-BJ', 'fr-CD', 'fr-CF', 'fr-FR', 'fr-GF', 'fr-GP', 'fr-MQ', 'fr-PF', 'fr-RE', 'fr-WF', 'ga-IE', 'he-IL', 'hu-HU', 'id-ID', 'ir-IR', 'it-IT', 'it-SM', 'ja-JP', 'ka-GE', 'kk-KZ', 'kl-GL', 'ko-KR', 'ky-KG', 'lt-LT', 'mg-MG', 'mn-MN', 'mk-MK', 'ms-MY', 'my-MM', 'mz-MZ', 'nb-NO', 'ne-NP', 'nl-AW', 'nl-BE', 'nl-NL', 'nn-NO', 'pl-PL', 'pt-AO', 'pt-BR', 'pt-PT', 'ro-Md', 'ro-RO', 'ru-RU', 'si-LK', 'sk-SK', 'sl-SI', 'so-SO', 'sq-AL', 'sr-RS', 'sv-SE', 'tg-TJ', 'th-TH', 'tk-TM', 'tr-TR', 'uk-UA', 'uz-UZ', 'vi-VN', 'zh-CN', 'zh-HK', 'zh-MO', 'zh-TW']` OR defaults to `'any'`. If 'any' or a falsey value is used, function will check if any of the locales match).

`options` is an optional object that can be supplied with the following keys: `strictMode`, if this is set to `true`, the mobile phone number must be supplied with the country code and therefore must start with `+`. Locale list is `validator.isMobilePhoneLocales`. **isMongoId(str)** | check if the string is a valid hex-encoded representation of a [MongoDB ObjectId][mongoid]. **isMultibyte(str)** | check if the string contains one or more multibyte chars. -**isNumeric(str)** | check if the string contains only numbers. +**isNumeric(str [, options])** | check if the string contains only numbers.

`options` is an object which defaults to `{ no_symbols: false }` it also has `locale` as an option. If `no_symbols` is true, the validator will reject numeric strings that feature a symbol (e.g. `+`, `-`, or `.`).

`locale` determines the decimal separator and is one of `['ar', 'ar-AE', 'ar-BH', 'ar-DZ', 'ar-EG', 'ar-IQ', 'ar-JO', 'ar-KW', 'ar-LB', 'ar-LY', 'ar-MA', 'ar-QA', 'ar-QM', 'ar-SA', 'ar-SD', 'ar-SY', 'ar-TN', 'ar-YE', 'bg-BG', 'cs-CZ', 'da-DK', 'de-DE', 'en-AU', 'en-GB', 'en-HK', 'en-IN', 'en-NZ', 'en-US', 'en-ZA', 'en-ZM', 'eo', 'es-ES', 'fr-FR', 'fr-CA', 'hu-HU', 'it-IT', 'nb-NO', 'nl-NL', 'nn-NO', 'pl-PL', 'pt-BR', 'pt-PT', 'ru-RU', 'sl-SI', 'sr-RS', 'sr-RS@latin', 'sv-SE', 'tr-TR', 'uk-UA']`. +**isOctal(str)** | check if the string is a valid octal number. +**isPassportNumber(str, countryCode)** | check if the string is a valid passport number.

`countryCode` is one of `['AM', 'AR', 'AT', 'AU', 'AZ', 'BE', 'BG', 'BY', 'BR', 'CA', 'CH', 'CN', 'CY', 'CZ', 'DE', 'DK', 'DZ', 'EE', 'ES', 'FI', 'FR', 'GB', 'GR', 'HR', 'HU', 'IE', 'IN', 'IR', 'ID', 'IS', 'IT', 'JM', 'JP', 'KR', 'KZ', 'LI', 'LT', 'LU', 'LV', 'LY', 'MT', 'MX', 'MY', 'MZ', 'NL', 'NZ', 'PH', 'PK', 'PL', 'PT', 'RO', 'RU', 'SE', 'SL', 'SK', 'TH', 'TR', 'UA', 'US', 'ZA']`. Locale list is `validator.passportNumberLocales`. **isPort(str)** | check if the string is a valid port number. -**isPostalCode(str, locale)** | check if the string is a postal code,

(locale is one of `[ 'AT', 'AU', 'BE', 'CA', 'CH', 'CZ', 'DE', 'DK', 'DZ', 'ES', 'FI', 'FR', 'GB', 'GR', 'IL', 'IN', 'IS', 'IT', 'JP', 'KE', 'LI', 'MX', 'NL', 'NO', 'PL', 'PT', 'RO', 'RU', 'SA', 'SE', 'TW', 'US', 'ZA', 'ZM' ]` OR 'any'. If 'any' is used, function will check if any of the locals match). +**isPostalCode(str, locale)** | check if the string is a postal code.

`locale` is one of `['AD', 'AT', 'AU', 'AZ', 'BA', 'BD', 'BE', 'BG', 'BR', 'BY', 'CA', 'CH', 'CN', 'CO', 'CZ', 'DE', 'DK', 'DO', 'DZ', 'EE', 'ES', 'FI', 'FR', 'GB', 'GR', 'HR', 'HT', 'HU', 'ID', 'IE', 'IL', 'IN', 'IR', 'IS', 'IT', 'JP', 'KE', 'KR', 'LI', 'LK', 'LT', 'LU', 'LV', 'MG', 'MT', 'MX', 'MY', 'NL', 'NO', 'NP', 'NZ', 'PK', 'PL', 'PR', 'PT', 'RO', 'RU', 'SA', 'SE', 'SG', 'SI', 'SK', 'TH', 'TN', 'TW', 'UA', 'US', 'ZA', 'ZM']` OR `'any'`. If 'any' is used, function will check if any of the locales match. Locale list is `validator.isPostalCodeLocales`. +**isRFC3339(str)** | check if the string is a valid [RFC 3339][RFC 3339] date. +**isRgbColor(str [,options])** | check if the string is a rgb or rgba color.

`options` is an object with the following properties

`includePercentValues` defaults to `true`. If you don't want to allow to set `rgb` or `rgba` values with percents, like `rgb(5%,5%,5%)`, or `rgba(90%,90%,90%,.3)`, then set it to false.

`allowSpaces` defaults to `true`, which prohibits whitespace. If set to false, whitespace between color values is allowed, such as `rgb(255, 255, 255)` or even `rgba(255, 128, 0, 0.7)`. +**isSemVer(str)** | check if the string is a Semantic Versioning Specification (SemVer). **isSurrogatePair(str)** | check if the string contains any surrogate pairs chars. -**isURL(str [, options])** | check if the string is an URL.

`options` is an object which defaults to `{ protocols: ['http','https','ftp'], require_tld: true, require_protocol: false, require_host: true, require_valid_protocol: true, allow_underscores: false, host_whitelist: false, host_blacklist: false, allow_trailing_dot: false, allow_protocol_relative_urls: false }`. -**isUUID(str [, version])** | check if the string is a UUID (version 3, 4 or 5). **isUppercase(str)** | check if the string is uppercase. +**isSlug(str)** | check if the string is of type slug. +**isStrongPassword(str [, options])** | check if the string can be considered a strong password or not. Allows for custom requirements or scoring rules. If `returnScore` is true, then the function returns an integer score for the password rather than a boolean.
Default options:
`{ minLength: 8, minLowercase: 1, minUppercase: 1, minNumbers: 1, minSymbols: 1, returnScore: false, pointsPerUnique: 1, pointsPerRepeat: 0.5, pointsForContainingLower: 10, pointsForContainingUpper: 10, pointsForContainingNumber: 10, pointsForContainingSymbol: 10 }` +**isTime(str [, options])** | check if the string is a valid time e.g. [`23:01:59`, new Date().toLocaleTimeString()].

`options` is an object which can contain the keys `hourFormat` or `mode`.

`hourFormat` is a key and defaults to `'hour24'`.

`mode` is a key and defaults to `'default'`.

`hourFormat` can contain the values `'hour12'` or `'hour24'`, `'hour24'` will validate hours in 24 format and `'hour12'` will validate hours in 12 format.

`mode` can contain the values `'default', 'withSeconds', withOptionalSeconds`, `'default'` will validate `HH:MM` format, `'withSeconds'` will validate the `HH:MM:SS` format, `'withOptionalSeconds'` will validate `'HH:MM'` and `'HH:MM:SS'` formats. +**isTaxID(str, locale)** | check if the string is a valid Tax Identification Number. Default locale is `en-US`.

More info about exact TIN support can be found in `src/lib/isTaxID.js`.

Supported locales: `[ 'bg-BG', 'cs-CZ', 'de-AT', 'de-DE', 'dk-DK', 'el-CY', 'el-GR', 'en-CA', 'en-GB', 'en-IE', 'en-US', 'es-AR', 'es-ES', 'et-EE', 'fi-FI', 'fr-BE', 'fr-CA', 'fr-FR', 'fr-LU', 'hr-HR', 'hu-HU', 'it-IT', 'lb-LU', 'lt-LT', 'lv-LV', 'mt-MT', 'nl-BE', 'nl-NL', 'pl-PL', 'pt-BR', 'pt-PT', 'ro-RO', 'sk-SK', 'sl-SI', 'sv-SE', 'uk-UA']`. +**isURL(str [, options])** | check if the string is a URL.

`options` is an object which defaults to `{ protocols: ['http','https','ftp'], require_tld: true, require_protocol: false, require_host: true, require_port: false, require_valid_protocol: true, allow_underscores: false, host_whitelist: false, host_blacklist: false, allow_trailing_dot: false, allow_protocol_relative_urls: false, allow_fragments: true, allow_query_components: true, disallow_auth: false, validate_length: true }`.

`protocols` - valid protocols can be modified with this option.
`require_tld` - If set to false isURL will not check if the URL's host includes a top-level domain.
`require_protocol` - **RECOMMENDED** if set to true isURL will return false if protocol is not present in the URL. Without this setting, some malicious URLs cannot be distinguishable from a valid URL with authentication information.
`require_host` - if set to false isURL will not check if host is present in the URL.
`require_port` - if set to true isURL will check if port is present in the URL.
`require_valid_protocol` - isURL will check if the URL's protocol is present in the protocols option.
`allow_underscores` - if set to true, the validator will allow underscores in the URL.
`host_whitelist` - if set to an array of strings or regexp, and the domain matches none of the strings defined in it, the validation fails.
`host_blacklist` - if set to an array of strings or regexp, and the domain matches any of the strings defined in it, the validation fails.
`allow_trailing_dot` - if set to true, the validator will allow the domain to end with a `.` character.
`allow_protocol_relative_urls` - if set to true protocol relative URLs will be allowed.
`allow_fragments` - if set to false isURL will return false if fragments are present.
`allow_query_components` - if set to false isURL will return false if query components are present.
`disallow_auth` - if set to true, the validator will fail if the URL contains an authentication component, e.g. `http://username:password@example.com`.
`validate_length` - if set to false isURL will skip string length validation. `max_allowed_length` will be ignored if this is set as `false`.
`max_allowed_length` - if set, isURL will not allow URLs longer than the specified value (default is 2084 that IE maximum URL length).
+**isULID(str)** | check if the string is a [ULID](https://github.com/ulid/spec). +**isUUID(str [, version])** | check if the string is an RFC9562 UUID.
`version` is one of `'1'`-`'8'`, `'nil'`, `'max'`, `'all'` or `'loose'`. The `'loose'` option checks if the string is a UUID-like string with hexadecimal values, ignoring RFC9565. **isVariableWidth(str)** | check if the string contains a mixture of full and half-width chars. -**isWhitelisted(str, chars)** | checks characters if they appear in the whitelist. -**matches(str, pattern [, modifiers])** | check if string matches the pattern.

Either `matches('foo', /foo/i)` or `matches('foo', 'foo', 'i')`. +**isVAT(str, countryCode)** | check if the string is a [valid VAT number][VAT Number] if validation is available for the given country code matching [ISO 3166-1 alpha-2][ISO 3166-1 alpha-2].

`countryCode` is one of `['AL', 'AR', 'AT', 'AU', 'BE', 'BG', 'BO', 'BR', 'BY', 'CA', 'CH', 'CL', 'CO', 'CR', 'CY', 'CZ', 'DE', 'DK', 'DO', 'EC', 'EE', 'EL', 'ES', 'FI', 'FR', 'GB', 'GT', 'HN', 'HR', 'HU', 'ID', 'IE', 'IL', 'IN', 'IS', 'IT', 'KZ', 'LT', 'LU', 'LV', 'MK', 'MT', 'MX', 'NG', 'NI', 'NL', 'NO', 'NZ', 'PA', 'PE', 'PH', 'PL', 'PT', 'PY', 'RO', 'RS', 'RU', 'SA', 'SE', 'SI', 'SK', 'SM', 'SV', 'TR', 'UA', 'UY', 'UZ', 'VE']`. +**isWhitelisted(str, chars)** | check if the string consists only of characters that appear in the whitelist `chars`. +**matches(str, pattern [, modifiers])** | check if the string matches the pattern.

Either `matches('foo', /foo/i)` or `matches('foo', 'foo', 'i')`. ## Sanitizers @@ -120,10 +182,9 @@ Here is a list of the sanitizers currently available. Sanitizer | Description -------------------------------------- | ------------------------------- **blacklist(input, chars)** | remove characters that appear in the blacklist. The characters are used in a RegExp and so you will need to escape some chars, e.g. `blacklist(input, '\\[\\]')`. -**escape(input)** | replace `<`, `>`, `&`, `'`, `"` and `/` with HTML entities. -**unescape(input)** | replaces HTML encoded entities with `<`, `>`, `&`, `'`, `"` and `/`. +**escape(input)** | replace `<`, `>`, `&`, `'`, `"`, `` ` ``, `\` and `/` with HTML entities. **ltrim(input [, chars])** | trim characters from the left-side of the input. -**normalizeEmail(email [, options])** | canonicalizes an email address. (This doesn't validate that the input is an email, if you want to validate the email use isEmail beforehand)

`options` is an object with the following keys and default values:
  • *all_lowercase: true* - Transforms the local part (before the @ symbol) of all email addresses to lowercase. Please note that this may violate RFC 5321, which gives providers the possibility to treat the local part of email addresses in a case sensitive way (although in practice most - yet not all - providers don't). The domain part of the email address is always lowercased, as it's case insensitive per RFC 1035.
  • *gmail_lowercase: true* - GMail addresses are known to be case-insensitive, so this switch allows lowercasing them even when *all_lowercase* is set to false. Please note that when *all_lowercase* is true, GMail addresses are lowercased regardless of the value of this setting.
  • *gmail_remove_dots: true*: Removes dots from the local part of the email address, as GMail ignores them (e.g. "john.doe" and "johndoe" are considered equal).
  • *gmail_remove_subaddress: true*: Normalizes addresses by removing "sub-addresses", which is the part following a "+" sign (e.g. "foo+bar@gmail.com" becomes "foo@gmail.com").
  • *gmail_convert_googlemaildotcom: true*: Converts addresses with domain @googlemail.com to @gmail.com, as they're equivalent.
  • *outlookdotcom_lowercase: true* - Outlook.com addresses (including Windows Live and Hotmail) are known to be case-insensitive, so this switch allows lowercasing them even when *all_lowercase* is set to false. Please note that when *all_lowercase* is true, Outlook.com addresses are lowercased regardless of the value of this setting.
  • *outlookdotcom_remove_subaddress: true*: Normalizes addresses by removing "sub-addresses", which is the part following a "+" sign (e.g. "foo+bar@outlook.com" becomes "foo@outlook.com").
  • *yahoo_lowercase: true* - Yahoo Mail addresses are known to be case-insensitive, so this switch allows lowercasing them even when *all_lowercase* is set to false. Please note that when *all_lowercase* is true, Yahoo Mail addresses are lowercased regardless of the value of this setting.
  • *yahoo_remove_subaddress: true*: Normalizes addresses by removing "sub-addresses", which is the part following a "-" sign (e.g. "foo-bar@yahoo.com" becomes "foo@yahoo.com").
  • *icloud_lowercase: true* - iCloud addresses (including MobileMe) are known to be case-insensitive, so this switch allows lowercasing them even when *all_lowercase* is set to false. Please note that when *all_lowercase* is true, iCloud addresses are lowercased regardless of the value of this setting.
  • *icloud_remove_subaddress: true*: Normalizes addresses by removing "sub-addresses", which is the part following a "+" sign (e.g. "foo+bar@icloud.com" becomes "foo@icloud.com").
+**normalizeEmail(email [, options])** | canonicalize an email address. (This doesn't validate that the input is an email, if you want to validate the email use isEmail beforehand).

`options` is an object with the following keys and default values:
  • *all_lowercase: true* - Transforms the local part (before the @ symbol) of all email addresses to lowercase. Please note that this may violate RFC 5321, which gives providers the possibility to treat the local part of email addresses in a case sensitive way (although in practice most - yet not all - providers don't). The domain part of the email address is always lowercased, as it is case insensitive per RFC 1035.
  • *gmail_lowercase: true* - Gmail addresses are known to be case-insensitive, so this switch allows lowercasing them even when *all_lowercase* is set to false. Please note that when *all_lowercase* is true, Gmail addresses are lowercased regardless of the value of this setting.
  • *gmail_remove_dots: true*: Removes dots from the local part of the email address, as Gmail ignores them (e.g. "john.doe" and "johndoe" are considered equal).
  • *gmail_remove_subaddress: true*: Normalizes addresses by removing "sub-addresses", which is the part following a "+" sign (e.g. "foo+bar@gmail.com" becomes "foo@gmail.com").
  • *gmail_convert_googlemaildotcom: true*: Converts addresses with domain @googlemail.com to @gmail.com, as they're equivalent.
  • *outlookdotcom_lowercase: true* - Outlook.com addresses (including Windows Live and Hotmail) are known to be case-insensitive, so this switch allows lowercasing them even when *all_lowercase* is set to false. Please note that when *all_lowercase* is true, Outlook.com addresses are lowercased regardless of the value of this setting.
  • *outlookdotcom_remove_subaddress: true*: Normalizes addresses by removing "sub-addresses", which is the part following a "+" sign (e.g. "foo+bar@outlook.com" becomes "foo@outlook.com").
  • *yahoo_lowercase: true* - Yahoo Mail addresses are known to be case-insensitive, so this switch allows lowercasing them even when *all_lowercase* is set to false. Please note that when *all_lowercase* is true, Yahoo Mail addresses are lowercased regardless of the value of this setting.
  • *yahoo_remove_subaddress: true*: Normalizes addresses by removing "sub-addresses", which is the part following a "-" sign (e.g. "foo-bar@yahoo.com" becomes "foo@yahoo.com").
  • *icloud_lowercase: true* - iCloud addresses (including MobileMe) are known to be case-insensitive, so this switch allows lowercasing them even when *all_lowercase* is set to false. Please note that when *all_lowercase* is true, iCloud addresses are lowercased regardless of the value of this setting.
  • *icloud_remove_subaddress: true*: Normalizes addresses by removing "sub-addresses", which is the part following a "+" sign (e.g. "foo+bar@icloud.com" becomes "foo@icloud.com").
**rtrim(input [, chars])** | trim characters from the right-side of the input. **stripLow(input [, keep_new_lines])** | remove characters with a numerical value < 32 and 127, mostly control characters. If `keep_new_lines` is `true`, newline characters are preserved (`\n` and `\r`, hex `0xA` and `0xD`). Unicode-safe in JavaScript. **toBoolean(input [, strict])** | convert the input string to a boolean. Everything except for `'0'`, `'false'` and `''` returns `true`. In strict mode only `'1'` and `'true'` return `true`. @@ -131,77 +192,78 @@ Sanitizer | Description **toFloat(input)** | convert the input string to a float, or `NaN` if the input is not a float. **toInt(input [, radix])** | convert the input string to an integer, or `NaN` if the input is not an integer. **trim(input [, chars])** | trim characters (whitespace by default) from both sides of the input. +**unescape(input)** | replace HTML encoded entities with `<`, `>`, `&`, `'`, `"`, `` ` ``, `\` and `/`. **whitelist(input, chars)** | remove characters that do not appear in the whitelist. The characters are used in a RegExp and so you will need to escape some chars, e.g. `whitelist(input, '\\[\\]')`. ### XSS Sanitization -XSS sanitization was removed from the library in [2d5d6999](https://github.com/chriso/validator.js/commit/2d5d6999541add350fb396ef02dc42ca3215049e). +XSS sanitization was removed from the library in [2d5d6999](https://github.com/validatorjs/validator.js/commit/2d5d6999541add350fb396ef02dc42ca3215049e). For an alternative, have a look at Yahoo's [xss-filters library](https://github.com/yahoo/xss-filters) or at [DOMPurify](https://github.com/cure53/DOMPurify). -## Contributing +## Maintainers -In general, we follow the "fork-and-pull" Git workflow. - -1. Fork the repo on GitHub -2. Clone the project to your own machine -3. Work on your fork - 1. Make your changes and additions - 2. Change or add tests if needed - 3. Run tests and make sure they pass - 4. Add changes to README.md if needed -4. Commit changes to your own branch -5. **Make sure** you merge the latest from "upstream" and resolve conflicts if there is any -6. Push your work back up to your fork -7. Submit a Pull request so that we can review your changes - -## Tests - -Tests are using mocha, to run the tests use: - -```sh -$ npm test -``` +- [chriso](https://github.com/chriso) - **Chris O'Hara** (author) +- [profnandaa](https://github.com/profnandaa) - **Anthony Nandaa** +- [rubiin](https://github.com/rubiin) - **Rubin Bhandari** +- [wikirik](https://github.com/wikirik) - **Rik Smale** +- [ezkemboi](https://github.com/ezkemboi) - **Ezrqn Kemboi** +- [tux-tn](https://github.com/tux-tn) - **Sarhan Aissi** ## Reading Remember, validating can be troublesome sometimes. See [A list of articles about programming assumptions commonly made that aren't true](https://github.com/jameslk/awesome-falsehoods). -## License (MIT) +## Contributing -``` -Copyright (c) 2017 Chris O'Hara - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -``` +We welcome contributions from the community! If you're interested in contributing to this project, please read our [Contribution Guide](CONTRIBUTING.md) to get started. + +## License + +This project is licensed under the [MIT](LICENSE). See the [LICENSE](LICENSE) file for details. [downloads-image]: http://img.shields.io/npm/dm/validator.svg [npm-url]: https://npmjs.org/package/validator [npm-image]: http://img.shields.io/npm/v/validator.svg -[travis-url]: https://travis-ci.org/chriso/validator.js -[travis-image]: http://img.shields.io/travis/chriso/validator.js.svg +[codecov-url]: https://codecov.io/gh/validatorjs/validator.js +[codecov-image]: https://codecov.io/gh/validatorjs/validator.js/branch/master/graph/badge.svg + +[ci-url]: https://github.com/validatorjs/validator.js/actions?query=workflow%3ACI +[ci-image]: https://github.com/validatorjs/validator.js/workflows/CI/badge.svg?branch=master + +[gitter-url]: https://gitter.im/validatorjs/community +[gitter-image]: https://badges.gitter.im/validatorjs/community.svg + +[huntr-url]: https://huntr.dev/bounties/disclose/?target=https://github.com/validatorjs/validator.js +[huntr-image]: https://cdn.huntr.dev/huntr_security_badge_mono.svg [amd]: http://requirejs.org/docs/whyamd.html [bower]: http://bower.io/ -[mongoid]: http://docs.mongodb.org/manual/reference/object-id/ +[Crockford Base32]: http://www.crockford.com/base32.html +[Base64 URL Safe]: https://base64.guru/standards/base64url +[Data URI Format]: https://developer.mozilla.org/en-US/docs/Web/HTTP/data_URIs +[European Article Number]: https://en.wikipedia.org/wiki/International_Article_Number +[Ethereum]: https://ethereum.org/ +[CSS Colors Level 4 Specification]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value +[IMEI]: https://en.wikipedia.org/wiki/International_Mobile_Equipment_Identity +[ISBN]: https://en.wikipedia.org/wiki/ISBN [ISIN]: https://en.wikipedia.org/wiki/International_Securities_Identification_Number +[ISO 639-1]: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes +[ISO 8601]: https://en.wikipedia.org/wiki/ISO_8601 +[ISO 15924]: https://en.wikipedia.org/wiki/ISO_15924 +[ISO 3166-1 alpha-2]: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 +[ISO 3166-1 alpha-3]: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3 +[ISO 3166-1 numeric]: https://en.wikipedia.org/wiki/ISO_3166-1_numeric +[ISO 4217]: https://en.wikipedia.org/wiki/ISO_4217 +[ISRC]: https://en.wikipedia.org/wiki/International_Standard_Recording_Code +[ISSN]: https://en.wikipedia.org/wiki/International_Standard_Serial_Number +[Luhn Check]: https://en.wikipedia.org/wiki/Luhn_algorithm +[Magnet URI Format]: https://en.wikipedia.org/wiki/Magnet_URI_scheme +[Mailto URI Format]: https://en.wikipedia.org/wiki/Mailto +[MIME Type]: https://en.wikipedia.org/wiki/Media_type +[mongoid]: http://docs.mongodb.org/manual/reference/object-id/ +[RFC 3339]: https://tools.ietf.org/html/rfc3339 +[VAT Number]: https://en.wikipedia.org/wiki/VAT_identification_number diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..266d8d844 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,11 @@ +# Security Policy + +## Supported Versions + +In the case of a confirmed security issue, only the current version of validator is guaranteed to be patched. + +## Reporting a Vulnerability + +**Please don't disclose security-related issues publicly.** + +Report the security issue to the Node.js Security Working Group through the [HackerOne program](https://hackerone.com/nodejs-ecosystem) for ecosystem modules on npm, or to [Snyk Security Team](https://snyk.io/vulnerability-disclosure). They will help triage the security issue and work with all involved parties to remediate and release a fix. diff --git a/bower.json b/bower.json index fb98d0ebc..427670cfc 100644 --- a/bower.json +++ b/bower.json @@ -1,7 +1,7 @@ { "name": "validator-js", "main": "validator.js", - "homepage": "https://github.com/chriso/validator.js", + "homepage": "https://github.com/validatorjs/validator.js", "authors": [ "Chris O'Hara " ], diff --git a/build-browser.js b/build-browser.js index 4243caaca..c863bd399 100644 --- a/build-browser.js +++ b/build-browser.js @@ -1,28 +1,32 @@ -const pkg = require('./package.json'); -const fs = require('fs'); -const rollup = require('rollup').rollup; -const babel = require('rollup-plugin-babel'); +/* eslint import/no-extraneous-dependencies: 0 */ +import fs from "fs"; +import { rollup } from "rollup"; +import babel from "rollup-plugin-babel"; +import babelPresetEnv from "@babel/preset-env"; +import pkg from "./package.json"; rollup({ - entry: 'src/index.js', + entry: "src/index.js", plugins: [ babel({ - presets: ['es2015-rollup'], + presets: [[babelPresetEnv, { modules: false }]], babelrc: false, }), ], -}).then(bundle => ( - bundle.write({ - dest: 'validator.js', - format: 'umd', - moduleName: pkg.name, - banner: ( - '/*!\n' + - String(fs.readFileSync('./LICENSE')).trim().split('\n').map(l => ` * ${l}`).join('\n') + - '\n */' - ), - }) -)).catch(e => { - process.stderr.write(e.message + '\n'); - process.exit(1); -}); +}) + .then((bundle) => + bundle.write({ + dest: "validator.js", + format: "umd", + moduleName: pkg.name, + banner: `/*!\n${String(fs.readFileSync("./LICENSE")) + .trim() + .split("\n") + .map((l) => ` * ${l}`) + .join("\n")}\n */`, + }) + ) + .catch((e) => { + process.stderr.write(`${e.message}\n`); + process.exit(1); + }); diff --git a/index.js b/index.js deleted file mode 100644 index e4ed3876b..000000000 --- a/index.js +++ /dev/null @@ -1,346 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _toDate = require('./lib/toDate'); - -var _toDate2 = _interopRequireDefault(_toDate); - -var _toFloat = require('./lib/toFloat'); - -var _toFloat2 = _interopRequireDefault(_toFloat); - -var _toInt = require('./lib/toInt'); - -var _toInt2 = _interopRequireDefault(_toInt); - -var _toBoolean = require('./lib/toBoolean'); - -var _toBoolean2 = _interopRequireDefault(_toBoolean); - -var _equals = require('./lib/equals'); - -var _equals2 = _interopRequireDefault(_equals); - -var _contains = require('./lib/contains'); - -var _contains2 = _interopRequireDefault(_contains); - -var _matches = require('./lib/matches'); - -var _matches2 = _interopRequireDefault(_matches); - -var _isEmail = require('./lib/isEmail'); - -var _isEmail2 = _interopRequireDefault(_isEmail); - -var _isURL = require('./lib/isURL'); - -var _isURL2 = _interopRequireDefault(_isURL); - -var _isMACAddress = require('./lib/isMACAddress'); - -var _isMACAddress2 = _interopRequireDefault(_isMACAddress); - -var _isIP = require('./lib/isIP'); - -var _isIP2 = _interopRequireDefault(_isIP); - -var _isFQDN = require('./lib/isFQDN'); - -var _isFQDN2 = _interopRequireDefault(_isFQDN); - -var _isBoolean = require('./lib/isBoolean'); - -var _isBoolean2 = _interopRequireDefault(_isBoolean); - -var _isAlpha = require('./lib/isAlpha'); - -var _isAlpha2 = _interopRequireDefault(_isAlpha); - -var _isAlphanumeric = require('./lib/isAlphanumeric'); - -var _isAlphanumeric2 = _interopRequireDefault(_isAlphanumeric); - -var _isNumeric = require('./lib/isNumeric'); - -var _isNumeric2 = _interopRequireDefault(_isNumeric); - -var _isPort = require('./lib/isPort'); - -var _isPort2 = _interopRequireDefault(_isPort); - -var _isLowercase = require('./lib/isLowercase'); - -var _isLowercase2 = _interopRequireDefault(_isLowercase); - -var _isUppercase = require('./lib/isUppercase'); - -var _isUppercase2 = _interopRequireDefault(_isUppercase); - -var _isAscii = require('./lib/isAscii'); - -var _isAscii2 = _interopRequireDefault(_isAscii); - -var _isFullWidth = require('./lib/isFullWidth'); - -var _isFullWidth2 = _interopRequireDefault(_isFullWidth); - -var _isHalfWidth = require('./lib/isHalfWidth'); - -var _isHalfWidth2 = _interopRequireDefault(_isHalfWidth); - -var _isVariableWidth = require('./lib/isVariableWidth'); - -var _isVariableWidth2 = _interopRequireDefault(_isVariableWidth); - -var _isMultibyte = require('./lib/isMultibyte'); - -var _isMultibyte2 = _interopRequireDefault(_isMultibyte); - -var _isSurrogatePair = require('./lib/isSurrogatePair'); - -var _isSurrogatePair2 = _interopRequireDefault(_isSurrogatePair); - -var _isInt = require('./lib/isInt'); - -var _isInt2 = _interopRequireDefault(_isInt); - -var _isFloat = require('./lib/isFloat'); - -var _isFloat2 = _interopRequireDefault(_isFloat); - -var _isDecimal = require('./lib/isDecimal'); - -var _isDecimal2 = _interopRequireDefault(_isDecimal); - -var _isHexadecimal = require('./lib/isHexadecimal'); - -var _isHexadecimal2 = _interopRequireDefault(_isHexadecimal); - -var _isDivisibleBy = require('./lib/isDivisibleBy'); - -var _isDivisibleBy2 = _interopRequireDefault(_isDivisibleBy); - -var _isHexColor = require('./lib/isHexColor'); - -var _isHexColor2 = _interopRequireDefault(_isHexColor); - -var _isISRC = require('./lib/isISRC'); - -var _isISRC2 = _interopRequireDefault(_isISRC); - -var _isMD = require('./lib/isMD5'); - -var _isMD2 = _interopRequireDefault(_isMD); - -var _isHash = require('./lib/isHash'); - -var _isHash2 = _interopRequireDefault(_isHash); - -var _isJSON = require('./lib/isJSON'); - -var _isJSON2 = _interopRequireDefault(_isJSON); - -var _isEmpty = require('./lib/isEmpty'); - -var _isEmpty2 = _interopRequireDefault(_isEmpty); - -var _isLength = require('./lib/isLength'); - -var _isLength2 = _interopRequireDefault(_isLength); - -var _isByteLength = require('./lib/isByteLength'); - -var _isByteLength2 = _interopRequireDefault(_isByteLength); - -var _isUUID = require('./lib/isUUID'); - -var _isUUID2 = _interopRequireDefault(_isUUID); - -var _isMongoId = require('./lib/isMongoId'); - -var _isMongoId2 = _interopRequireDefault(_isMongoId); - -var _isAfter = require('./lib/isAfter'); - -var _isAfter2 = _interopRequireDefault(_isAfter); - -var _isBefore = require('./lib/isBefore'); - -var _isBefore2 = _interopRequireDefault(_isBefore); - -var _isIn = require('./lib/isIn'); - -var _isIn2 = _interopRequireDefault(_isIn); - -var _isCreditCard = require('./lib/isCreditCard'); - -var _isCreditCard2 = _interopRequireDefault(_isCreditCard); - -var _isISIN = require('./lib/isISIN'); - -var _isISIN2 = _interopRequireDefault(_isISIN); - -var _isISBN = require('./lib/isISBN'); - -var _isISBN2 = _interopRequireDefault(_isISBN); - -var _isISSN = require('./lib/isISSN'); - -var _isISSN2 = _interopRequireDefault(_isISSN); - -var _isMobilePhone = require('./lib/isMobilePhone'); - -var _isMobilePhone2 = _interopRequireDefault(_isMobilePhone); - -var _isCurrency = require('./lib/isCurrency'); - -var _isCurrency2 = _interopRequireDefault(_isCurrency); - -var _isISO = require('./lib/isISO8601'); - -var _isISO2 = _interopRequireDefault(_isISO); - -var _isISO31661Alpha = require('./lib/isISO31661Alpha2'); - -var _isISO31661Alpha2 = _interopRequireDefault(_isISO31661Alpha); - -var _isBase = require('./lib/isBase64'); - -var _isBase2 = _interopRequireDefault(_isBase); - -var _isDataURI = require('./lib/isDataURI'); - -var _isDataURI2 = _interopRequireDefault(_isDataURI); - -var _isLatLong = require('./lib/isLatLong'); - -var _isLatLong2 = _interopRequireDefault(_isLatLong); - -var _isPostalCode = require('./lib/isPostalCode'); - -var _isPostalCode2 = _interopRequireDefault(_isPostalCode); - -var _ltrim = require('./lib/ltrim'); - -var _ltrim2 = _interopRequireDefault(_ltrim); - -var _rtrim = require('./lib/rtrim'); - -var _rtrim2 = _interopRequireDefault(_rtrim); - -var _trim = require('./lib/trim'); - -var _trim2 = _interopRequireDefault(_trim); - -var _escape = require('./lib/escape'); - -var _escape2 = _interopRequireDefault(_escape); - -var _unescape = require('./lib/unescape'); - -var _unescape2 = _interopRequireDefault(_unescape); - -var _stripLow = require('./lib/stripLow'); - -var _stripLow2 = _interopRequireDefault(_stripLow); - -var _whitelist = require('./lib/whitelist'); - -var _whitelist2 = _interopRequireDefault(_whitelist); - -var _blacklist = require('./lib/blacklist'); - -var _blacklist2 = _interopRequireDefault(_blacklist); - -var _isWhitelisted = require('./lib/isWhitelisted'); - -var _isWhitelisted2 = _interopRequireDefault(_isWhitelisted); - -var _normalizeEmail = require('./lib/normalizeEmail'); - -var _normalizeEmail2 = _interopRequireDefault(_normalizeEmail); - -var _toString = require('./lib/util/toString'); - -var _toString2 = _interopRequireDefault(_toString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var version = '9.1.2'; - -var validator = { - version: version, - toDate: _toDate2.default, - toFloat: _toFloat2.default, - toInt: _toInt2.default, - toBoolean: _toBoolean2.default, - equals: _equals2.default, - contains: _contains2.default, - matches: _matches2.default, - isEmail: _isEmail2.default, - isURL: _isURL2.default, - isMACAddress: _isMACAddress2.default, - isIP: _isIP2.default, - isFQDN: _isFQDN2.default, - isBoolean: _isBoolean2.default, - isAlpha: _isAlpha2.default, - isAlphanumeric: _isAlphanumeric2.default, - isNumeric: _isNumeric2.default, - isPort: _isPort2.default, - isLowercase: _isLowercase2.default, - isUppercase: _isUppercase2.default, - isAscii: _isAscii2.default, - isFullWidth: _isFullWidth2.default, - isHalfWidth: _isHalfWidth2.default, - isVariableWidth: _isVariableWidth2.default, - isMultibyte: _isMultibyte2.default, - isSurrogatePair: _isSurrogatePair2.default, - isInt: _isInt2.default, - isFloat: _isFloat2.default, - isDecimal: _isDecimal2.default, - isHexadecimal: _isHexadecimal2.default, - isDivisibleBy: _isDivisibleBy2.default, - isHexColor: _isHexColor2.default, - isISRC: _isISRC2.default, - isMD5: _isMD2.default, - isHash: _isHash2.default, - isJSON: _isJSON2.default, - isEmpty: _isEmpty2.default, - isLength: _isLength2.default, - isByteLength: _isByteLength2.default, - isUUID: _isUUID2.default, - isMongoId: _isMongoId2.default, - isAfter: _isAfter2.default, - isBefore: _isBefore2.default, - isIn: _isIn2.default, - isCreditCard: _isCreditCard2.default, - isISIN: _isISIN2.default, - isISBN: _isISBN2.default, - isISSN: _isISSN2.default, - isMobilePhone: _isMobilePhone2.default, - isPostalCode: _isPostalCode2.default, - isCurrency: _isCurrency2.default, - isISO8601: _isISO2.default, - isISO31661Alpha2: _isISO31661Alpha2.default, - isBase64: _isBase2.default, - isDataURI: _isDataURI2.default, - isLatLong: _isLatLong2.default, - ltrim: _ltrim2.default, - rtrim: _rtrim2.default, - trim: _trim2.default, - escape: _escape2.default, - unescape: _unescape2.default, - stripLow: _stripLow2.default, - whitelist: _whitelist2.default, - blacklist: _blacklist2.default, - isWhitelisted: _isWhitelisted2.default, - normalizeEmail: _normalizeEmail2.default, - toString: _toString2.default -}; - -exports.default = validator; -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/alpha.js b/lib/alpha.js deleted file mode 100644 index bfcd861ec..000000000 --- a/lib/alpha.js +++ /dev/null @@ -1,90 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -var alpha = exports.alpha = { - 'en-US': /^[A-Z]+$/i, - 'cs-CZ': /^[A-ZÁČĎÉĚÍŇÓŘŠŤÚŮÝŽ]+$/i, - 'da-DK': /^[A-ZÆØÅ]+$/i, - 'de-DE': /^[A-ZÄÖÜß]+$/i, - 'es-ES': /^[A-ZÁÉÍÑÓÚÜ]+$/i, - 'fr-FR': /^[A-ZÀÂÆÇÉÈÊËÏÎÔŒÙÛÜŸ]+$/i, - 'it-IT': /^[A-ZÀÉÈÌÎÓÒÙ]+$/i, - 'nb-NO': /^[A-ZÆØÅ]+$/i, - 'nl-NL': /^[A-ZÁÉËÏÓÖÜÚ]+$/i, - 'nn-NO': /^[A-ZÆØÅ]+$/i, - 'hu-HU': /^[A-ZÁÉÍÓÖŐÚÜŰ]+$/i, - 'pl-PL': /^[A-ZĄĆĘŚŁŃÓŻŹ]+$/i, - 'pt-PT': /^[A-ZÃÁÀÂÇÉÊÍÕÓÔÚÜ]+$/i, - 'ru-RU': /^[А-ЯЁ]+$/i, - 'sr-RS@latin': /^[A-ZČĆŽŠĐ]+$/i, - 'sr-RS': /^[А-ЯЂЈЉЊЋЏ]+$/i, - 'sv-SE': /^[A-ZÅÄÖ]+$/i, - 'tr-TR': /^[A-ZÇĞİıÖŞÜ]+$/i, - 'uk-UA': /^[А-ЩЬЮЯЄIЇҐі]+$/i, - ar: /^[ءآأؤإئابةتثجحخدذرزسشصضطظعغفقكلمنهوىيًٌٍَُِّْٰ]+$/ -}; - -var alphanumeric = exports.alphanumeric = { - 'en-US': /^[0-9A-Z]+$/i, - 'cs-CZ': /^[0-9A-ZÁČĎÉĚÍŇÓŘŠŤÚŮÝŽ]+$/i, - 'da-DK': /^[0-9A-ZÆØÅ]+$/i, - 'de-DE': /^[0-9A-ZÄÖÜß]+$/i, - 'es-ES': /^[0-9A-ZÁÉÍÑÓÚÜ]+$/i, - 'fr-FR': /^[0-9A-ZÀÂÆÇÉÈÊËÏÎÔŒÙÛÜŸ]+$/i, - 'it-IT': /^[0-9A-ZÀÉÈÌÎÓÒÙ]+$/i, - 'hu-HU': /^[0-9A-ZÁÉÍÓÖŐÚÜŰ]+$/i, - 'nb-NO': /^[0-9A-ZÆØÅ]+$/i, - 'nl-NL': /^[0-9A-ZÁÉËÏÓÖÜÚ]+$/i, - 'nn-NO': /^[0-9A-ZÆØÅ]+$/i, - 'pl-PL': /^[0-9A-ZĄĆĘŚŁŃÓŻŹ]+$/i, - 'pt-PT': /^[0-9A-ZÃÁÀÂÇÉÊÍÕÓÔÚÜ]+$/i, - 'ru-RU': /^[0-9А-ЯЁ]+$/i, - 'sr-RS@latin': /^[0-9A-ZČĆŽŠĐ]+$/i, - 'sr-RS': /^[0-9А-ЯЂЈЉЊЋЏ]+$/i, - 'sv-SE': /^[0-9A-ZÅÄÖ]+$/i, - 'tr-TR': /^[0-9A-ZÇĞİıÖŞÜ]+$/i, - 'uk-UA': /^[0-9А-ЩЬЮЯЄIЇҐі]+$/i, - ar: /^[٠١٢٣٤٥٦٧٨٩0-9ءآأؤإئابةتثجحخدذرزسشصضطظعغفقكلمنهوىيًٌٍَُِّْٰ]+$/ -}; - -var decimal = exports.decimal = { - 'en-US': '.', - ar: '٫' -}; - -var englishLocales = exports.englishLocales = ['AU', 'GB', 'HK', 'IN', 'NZ', 'ZA', 'ZM']; - -for (var locale, i = 0; i < englishLocales.length; i++) { - locale = 'en-' + englishLocales[i]; - alpha[locale] = alpha['en-US']; - alphanumeric[locale] = alphanumeric['en-US']; - decimal[locale] = decimal['en-US']; -} - -// Source: http://www.localeplanet.com/java/ -var arabicLocales = exports.arabicLocales = ['AE', 'BH', 'DZ', 'EG', 'IQ', 'JO', 'KW', 'LB', 'LY', 'MA', 'QM', 'QA', 'SA', 'SD', 'SY', 'TN', 'YE']; - -for (var _locale, _i = 0; _i < arabicLocales.length; _i++) { - _locale = 'ar-' + arabicLocales[_i]; - alpha[_locale] = alpha.ar; - alphanumeric[_locale] = alphanumeric.ar; - decimal[_locale] = decimal.ar; -} - -// Source: https://en.wikipedia.org/wiki/Decimal_mark -var dotDecimal = exports.dotDecimal = []; -var commaDecimal = exports.commaDecimal = ['cs-CZ', 'da-DK', 'de-DE', 'es-ES', 'fr-FR', 'it-IT', 'hu-HU', 'nb-NO', 'nn-NO', 'nl-NL', 'pl-Pl', 'pt-PT', 'ru-RU', 'sr-RS@latin', 'sr-RS', 'sv-SE', 'tr-TR', 'uk-UA']; - -for (var _i2 = 0; _i2 < dotDecimal.length; _i2++) { - decimal[dotDecimal[_i2]] = decimal['en-US']; -} - -for (var _i3 = 0; _i3 < commaDecimal.length; _i3++) { - decimal[commaDecimal[_i3]] = ','; -} - -alpha['pt-BR'] = alpha['pt-PT']; -alphanumeric['pt-BR'] = alphanumeric['pt-PT']; -decimal['pt-BR'] = decimal['pt-PT']; \ No newline at end of file diff --git a/lib/blacklist.js b/lib/blacklist.js deleted file mode 100644 index 41ecdf0f6..000000000 --- a/lib/blacklist.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = blacklist; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function blacklist(str, chars) { - (0, _assertString2.default)(str); - return str.replace(new RegExp('[' + chars + ']+', 'g'), ''); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/contains.js b/lib/contains.js deleted file mode 100644 index 025e8012e..000000000 --- a/lib/contains.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = contains; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -var _toString = require('./util/toString'); - -var _toString2 = _interopRequireDefault(_toString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function contains(str, elem) { - (0, _assertString2.default)(str); - return str.indexOf((0, _toString2.default)(elem)) >= 0; -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/equals.js b/lib/equals.js deleted file mode 100644 index 30ac76a05..000000000 --- a/lib/equals.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = equals; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function equals(str, comparison) { - (0, _assertString2.default)(str); - return str === comparison; -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/escape.js b/lib/escape.js deleted file mode 100644 index abcd60774..000000000 --- a/lib/escape.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = escape; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function escape(str) { - (0, _assertString2.default)(str); - return str.replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, ''').replace(//g, '>').replace(/\//g, '/').replace(/\\/g, '\').replace(/`/g, '`'); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isAfter.js b/lib/isAfter.js deleted file mode 100644 index 9c84d67cf..000000000 --- a/lib/isAfter.js +++ /dev/null @@ -1,26 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isAfter; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -var _toDate = require('./toDate'); - -var _toDate2 = _interopRequireDefault(_toDate); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function isAfter(str) { - var date = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : String(new Date()); - - (0, _assertString2.default)(str); - var comparison = (0, _toDate2.default)(date); - var original = (0, _toDate2.default)(str); - return !!(original && comparison && original > comparison); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isAlpha.js b/lib/isAlpha.js deleted file mode 100644 index 4aff94c83..000000000 --- a/lib/isAlpha.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isAlpha; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -var _alpha = require('./alpha'); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function isAlpha(str) { - var locale = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'en-US'; - - (0, _assertString2.default)(str); - if (locale in _alpha.alpha) { - return _alpha.alpha[locale].test(str); - } - throw new Error('Invalid locale \'' + locale + '\''); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isAlphanumeric.js b/lib/isAlphanumeric.js deleted file mode 100644 index 9fb36a799..000000000 --- a/lib/isAlphanumeric.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isAlphanumeric; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -var _alpha = require('./alpha'); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function isAlphanumeric(str) { - var locale = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'en-US'; - - (0, _assertString2.default)(str); - if (locale in _alpha.alphanumeric) { - return _alpha.alphanumeric[locale].test(str); - } - throw new Error('Invalid locale \'' + locale + '\''); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isAscii.js b/lib/isAscii.js deleted file mode 100644 index 55171e844..000000000 --- a/lib/isAscii.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isAscii; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -/* eslint-disable no-control-regex */ -var ascii = /^[\x00-\x7F]+$/; -/* eslint-enable no-control-regex */ - -function isAscii(str) { - (0, _assertString2.default)(str); - return ascii.test(str); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isBase64.js b/lib/isBase64.js deleted file mode 100644 index 0b4a807f5..000000000 --- a/lib/isBase64.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isBase64; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var notBase64 = /[^A-Z0-9+\/=]/i; - -function isBase64(str) { - (0, _assertString2.default)(str); - var len = str.length; - if (!len || len % 4 !== 0 || notBase64.test(str)) { - return false; - } - var firstPaddingChar = str.indexOf('='); - return firstPaddingChar === -1 || firstPaddingChar === len - 1 || firstPaddingChar === len - 2 && str[len - 1] === '='; -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isBefore.js b/lib/isBefore.js deleted file mode 100644 index b4e3cf625..000000000 --- a/lib/isBefore.js +++ /dev/null @@ -1,26 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isBefore; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -var _toDate = require('./toDate'); - -var _toDate2 = _interopRequireDefault(_toDate); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function isBefore(str) { - var date = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : String(new Date()); - - (0, _assertString2.default)(str); - var comparison = (0, _toDate2.default)(date); - var original = (0, _toDate2.default)(str); - return !!(original && comparison && original < comparison); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isBoolean.js b/lib/isBoolean.js deleted file mode 100644 index 7dbd26f8c..000000000 --- a/lib/isBoolean.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isBoolean; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function isBoolean(str) { - (0, _assertString2.default)(str); - return ['true', 'false', '1', '0'].indexOf(str) >= 0; -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isByteLength.js b/lib/isByteLength.js deleted file mode 100644 index 5ec897c6b..000000000 --- a/lib/isByteLength.js +++ /dev/null @@ -1,33 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; - -exports.default = isByteLength; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -/* eslint-disable prefer-rest-params */ -function isByteLength(str, options) { - (0, _assertString2.default)(str); - var min = void 0; - var max = void 0; - if ((typeof options === 'undefined' ? 'undefined' : _typeof(options)) === 'object') { - min = options.min || 0; - max = options.max; - } else { - // backwards compatibility: isByteLength(str, min [, max]) - min = arguments[1]; - max = arguments[2]; - } - var len = encodeURI(str).split(/%..|./).length - 1; - return len >= min && (typeof max === 'undefined' || len <= max); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isCreditCard.js b/lib/isCreditCard.js deleted file mode 100644 index 2e204ef5c..000000000 --- a/lib/isCreditCard.js +++ /dev/null @@ -1,45 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isCreditCard; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -/* eslint-disable max-len */ -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})$/; -/* eslint-enable max-len */ - -function isCreditCard(str) { - (0, _assertString2.default)(str); - var sanitized = str.replace(/[- ]+/g, ''); - if (!creditCard.test(sanitized)) { - return false; - } - var sum = 0; - var digit = void 0; - var tmpNum = void 0; - var shouldDouble = void 0; - for (var i = sanitized.length - 1; i >= 0; i--) { - digit = sanitized.substring(i, i + 1); - tmpNum = parseInt(digit, 10); - if (shouldDouble) { - tmpNum *= 2; - if (tmpNum >= 10) { - sum += tmpNum % 10 + 1; - } else { - sum += tmpNum; - } - } else { - sum += tmpNum; - } - shouldDouble = !shouldDouble; - } - return !!(sum % 10 === 0 ? sanitized : false); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isCurrency.js b/lib/isCurrency.js deleted file mode 100644 index 5be44ef75..000000000 --- a/lib/isCurrency.js +++ /dev/null @@ -1,92 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isCurrency; - -var _merge = require('./util/merge'); - -var _merge2 = _interopRequireDefault(_merge); - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function currencyRegex(options) { - var decimal_digits = '\\d{' + options.digits_after_decimal[0] + '}'; - options.digits_after_decimal.forEach(function (digit, index) { - if (index !== 0) decimal_digits = decimal_digits + '|\\d{' + digit + '}'; - }); - var symbol = '(\\' + options.symbol.replace(/\./g, '\\.') + ')' + (options.require_symbol ? '' : '?'), - negative = '-?', - whole_dollar_amount_without_sep = '[1-9]\\d*', - whole_dollar_amount_with_sep = '[1-9]\\d{0,2}(\\' + options.thousands_separator + '\\d{3})*', - valid_whole_dollar_amounts = ['0', whole_dollar_amount_without_sep, whole_dollar_amount_with_sep], - whole_dollar_amount = '(' + valid_whole_dollar_amounts.join('|') + ')?', - decimal_amount = '(\\' + options.decimal_separator + '(' + decimal_digits + '))' + (options.require_decimal ? '' : '?'); - var pattern = whole_dollar_amount + (options.allow_decimal || options.require_decimal ? decimal_amount : ''); - - // default is negative sign before symbol, but there are two other options (besides parens) - if (options.allow_negatives && !options.parens_for_negatives) { - if (options.negative_sign_after_digits) { - pattern += negative; - } else if (options.negative_sign_before_digits) { - pattern = negative + pattern; - } - } - - // South African Rand, for example, uses R 123 (space) and R-123 (no space) - if (options.allow_negative_sign_placeholder) { - pattern = '( (?!\\-))?' + pattern; - } else if (options.allow_space_after_symbol) { - pattern = ' ?' + pattern; - } else if (options.allow_space_after_digits) { - pattern += '( (?!$))?'; - } - - if (options.symbol_after_digits) { - pattern += symbol; - } else { - pattern = symbol + pattern; - } - - if (options.allow_negatives) { - if (options.parens_for_negatives) { - pattern = '(\\(' + pattern + '\\)|' + pattern + ')'; - } else if (!(options.negative_sign_before_digits || options.negative_sign_after_digits)) { - pattern = negative + pattern; - } - } - - // ensure there's a dollar and/or decimal amount, and that - // it doesn't start with a space or a negative sign followed by a space - return new RegExp('^(?!-? )(?=.*\\d)' + pattern + '$'); -} - -var default_currency_options = { - symbol: '$', - require_symbol: false, - allow_space_after_symbol: false, - symbol_after_digits: false, - allow_negatives: true, - parens_for_negatives: false, - negative_sign_before_digits: false, - negative_sign_after_digits: false, - allow_negative_sign_placeholder: false, - thousands_separator: ',', - decimal_separator: '.', - allow_decimal: true, - require_decimal: false, - digits_after_decimal: [2], - allow_space_after_digits: false -}; - -function isCurrency(str, options) { - (0, _assertString2.default)(str); - options = (0, _merge2.default)(options, default_currency_options); - return currencyRegex(options).test(str); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isDataURI.js b/lib/isDataURI.js deleted file mode 100644 index 7ca9fae3c..000000000 --- a/lib/isDataURI.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isDataURI; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -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 - -function isDataURI(str) { - (0, _assertString2.default)(str); - return dataURI.test(str); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isDate.js b/lib/isDate.js deleted file mode 100644 index 0e641637d..000000000 --- a/lib/isDate.js +++ /dev/null @@ -1,100 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isDate; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -var _isISO = require('./isISO8601'); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function getTimezoneOffset(str) { - var iso8601Parts = str.match(_isISO.iso8601); - var timezone = void 0, - sign = void 0, - hours = void 0, - minutes = void 0; - if (!iso8601Parts) { - str = str.toLowerCase(); - timezone = str.match(/(?:\s|gmt\s*)(-|\+)(\d{1,4})(\s|$)/); - if (!timezone) { - return str.indexOf('gmt') !== -1 ? 0 : null; - } - sign = timezone[1]; - var offset = timezone[2]; - if (offset.length === 3) { - offset = '0' + offset; - } - if (offset.length <= 2) { - hours = 0; - minutes = parseInt(offset, 10); - } else { - hours = parseInt(offset.slice(0, 2), 10); - minutes = parseInt(offset.slice(2, 4), 10); - } - } else { - timezone = iso8601Parts[21]; - if (!timezone) { - // if no hour/minute was provided, the date is GMT - return !iso8601Parts[12] ? 0 : null; - } - if (timezone === 'z' || timezone === 'Z') { - return 0; - } - sign = iso8601Parts[22]; - if (timezone.indexOf(':') !== -1) { - hours = parseInt(iso8601Parts[23], 10); - minutes = parseInt(iso8601Parts[24], 10); - } else { - hours = 0; - minutes = parseInt(iso8601Parts[23], 10); - } - } - return (hours * 60 + minutes) * (sign === '-' ? 1 : -1); -} - -function isDate(str) { - (0, _assertString2.default)(str); - var normalizedDate = new Date(Date.parse(str)); - if (isNaN(normalizedDate)) { - return false; - } - - // normalizedDate is in the user's timezone. Apply the input - // timezone offset to the date so that the year and day match - // the input - var timezoneOffset = getTimezoneOffset(str); - if (timezoneOffset !== null) { - var timezoneDifference = normalizedDate.getTimezoneOffset() - timezoneOffset; - normalizedDate = new Date(normalizedDate.getTime() + 60000 * timezoneDifference); - } - - var day = String(normalizedDate.getDate()); - var dayOrYear = void 0, - dayOrYearMatches = void 0, - year = void 0; - // check for valid double digits that could be late days - // check for all matches since a string like '12/23' is a valid date - // ignore everything with nearby colons - dayOrYearMatches = str.match(/(^|[^:\d])[23]\d([^T:\d]|$)/g); - if (!dayOrYearMatches) { - return true; - } - dayOrYear = dayOrYearMatches.map(function (digitString) { - return digitString.match(/\d+/g)[0]; - }).join('/'); - - year = String(normalizedDate.getFullYear()).slice(-2); - if (dayOrYear === day || dayOrYear === year) { - return true; - } else if (dayOrYear === '' + day / year || dayOrYear === '' + year / day) { - return true; - } - return false; -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isDecimal.js b/lib/isDecimal.js deleted file mode 100644 index 7a66dace5..000000000 --- a/lib/isDecimal.js +++ /dev/null @@ -1,41 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isDecimal; - -var _merge = require('./util/merge'); - -var _merge2 = _interopRequireDefault(_merge); - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -var _alpha = require('./alpha'); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function decimalRegExp(options) { - var regExp = new RegExp('^[-+]?([0-9]+)?(\\' + _alpha.decimal[options.locale] + '[0-9]{' + options.decimal_digits + '})' + (options.force_decimal ? '' : '?') + '$'); - return regExp; -} - -var default_decimal_options = { - force_decimal: false, - decimal_digits: '1,', - locale: 'en-US' -}; - -var blacklist = ['', '-', '+']; - -function isDecimal(str, options) { - (0, _assertString2.default)(str); - options = (0, _merge2.default)(options, default_decimal_options); - if (options.locale in _alpha.decimal) { - return !blacklist.includes(str.replace(/ /g, '')) && decimalRegExp(options).test(str); - } - throw new Error('Invalid locale \'' + options.locale + '\''); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isDivisibleBy.js b/lib/isDivisibleBy.js deleted file mode 100644 index 133603999..000000000 --- a/lib/isDivisibleBy.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isDivisibleBy; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -var _toFloat = require('./toFloat'); - -var _toFloat2 = _interopRequireDefault(_toFloat); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function isDivisibleBy(str, num) { - (0, _assertString2.default)(str); - return (0, _toFloat2.default)(str) % parseInt(num, 10) === 0; -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isEmail.js b/lib/isEmail.js deleted file mode 100644 index 0944805be..000000000 --- a/lib/isEmail.js +++ /dev/null @@ -1,89 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isEmail; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -var _merge = require('./util/merge'); - -var _merge2 = _interopRequireDefault(_merge); - -var _isByteLength = require('./isByteLength'); - -var _isByteLength2 = _interopRequireDefault(_isByteLength); - -var _isFQDN = require('./isFQDN'); - -var _isFQDN2 = _interopRequireDefault(_isFQDN); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var default_email_options = { - allow_display_name: false, - require_display_name: false, - allow_utf8_local_part: true, - require_tld: true -}; - -/* eslint-disable max-len */ -/* eslint-disable no-control-regex */ -var displayName = /^[a-z\d!#\$%&'\*\+\-\/=\?\^_`{\|}~\.\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+[a-z\d!#\$%&'\*\+\-\/=\?\^_`{\|}~\,\.\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF\s]*<(.+)>$/i; -var emailUserPart = /^[a-z\d!#\$%&'\*\+\-\/=\?\^_`{\|}~]+$/i; -var quotedEmailUser = /^([\s\x01-\x08\x0b\x0c\x0e-\x1f\x7f\x21\x23-\x5b\x5d-\x7e]|(\\[\x01-\x09\x0b\x0c\x0d-\x7f]))*$/i; -var emailUserUtf8Part = /^[a-z\d!#\$%&'\*\+\-\/=\?\^_`{\|}~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+$/i; -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; -/* eslint-enable max-len */ -/* eslint-enable no-control-regex */ - -function isEmail(str, options) { - (0, _assertString2.default)(str); - options = (0, _merge2.default)(options, default_email_options); - - if (options.require_display_name || options.allow_display_name) { - var display_email = str.match(displayName); - if (display_email) { - str = display_email[1]; - } else if (options.require_display_name) { - return false; - } - } - - var parts = str.split('@'); - var domain = parts.pop(); - var user = parts.join('@'); - - var lower_domain = domain.toLowerCase(); - if (lower_domain === 'gmail.com' || lower_domain === 'googlemail.com') { - user = user.replace(/\./g, '').toLowerCase(); - } - - if (!(0, _isByteLength2.default)(user, { max: 64 }) || !(0, _isByteLength2.default)(domain, { max: 254 })) { - return false; - } - - if (!(0, _isFQDN2.default)(domain, { require_tld: options.require_tld })) { - return false; - } - - if (user[0] === '"') { - user = user.slice(1, user.length - 1); - return options.allow_utf8_local_part ? quotedEmailUserUtf8.test(user) : quotedEmailUser.test(user); - } - - var pattern = options.allow_utf8_local_part ? emailUserUtf8Part : emailUserPart; - - var user_parts = user.split('.'); - for (var i = 0; i < user_parts.length; i++) { - if (!pattern.test(user_parts[i])) { - return false; - } - } - - return true; -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isEmpty.js b/lib/isEmpty.js deleted file mode 100644 index f3e39e4df..000000000 --- a/lib/isEmpty.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isEmpty; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function isEmpty(str) { - (0, _assertString2.default)(str); - return str.length === 0; -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isFQDN.js b/lib/isFQDN.js deleted file mode 100644 index 9ac148da5..000000000 --- a/lib/isFQDN.js +++ /dev/null @@ -1,61 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isFQDN; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -var _merge = require('./util/merge'); - -var _merge2 = _interopRequireDefault(_merge); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var default_fqdn_options = { - require_tld: true, - allow_underscores: false, - allow_trailing_dot: false -}; - -function isFQDN(str, options) { - (0, _assertString2.default)(str); - options = (0, _merge2.default)(options, default_fqdn_options); - - /* Remove the optional trailing dot before checking validity */ - if (options.allow_trailing_dot && str[str.length - 1] === '.') { - str = str.substring(0, str.length - 1); - } - var parts = str.split('.'); - if (options.require_tld) { - var tld = parts.pop(); - if (!parts.length || !/^([a-z\u00a1-\uffff]{2,}|xn[a-z0-9-]{2,})$/i.test(tld)) { - return false; - } - // disallow spaces - if (/[\s\u2002-\u200B\u202F\u205F\u3000\uFEFF\uDB40\uDC20]/.test(tld)) { - return false; - } - } - for (var part, i = 0; i < parts.length; i++) { - part = parts[i]; - if (options.allow_underscores) { - part = part.replace(/_/g, ''); - } - if (!/^[a-z\u00a1-\uffff0-9-]+$/i.test(part)) { - return false; - } - // disallow full-width chars - if (/[\uff01-\uff5e]/.test(part)) { - return false; - } - if (part[0] === '-' || part[part.length - 1] === '-') { - return false; - } - } - return true; -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isFloat.js b/lib/isFloat.js deleted file mode 100644 index b81b2ae08..000000000 --- a/lib/isFloat.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isFloat; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -var _alpha = require('./alpha'); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function isFloat(str, options) { - (0, _assertString2.default)(str); - options = options || {}; - var float = new RegExp('^(?:[-+])?(?:[0-9]+)?(?:\\' + (options.locale ? _alpha.decimal[options.locale] : '.') + '[0-9]*)?(?:[eE][\\+\\-]?(?:[0-9]+))?$'); - if (str === '' || str === '.' || str === '-' || str === '+') { - return false; - } - 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); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isFullWidth.js b/lib/isFullWidth.js deleted file mode 100644 index 4215f8186..000000000 --- a/lib/isFullWidth.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.fullWidth = undefined; -exports.default = isFullWidth; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var fullWidth = exports.fullWidth = /[^\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]/; - -function isFullWidth(str) { - (0, _assertString2.default)(str); - return fullWidth.test(str); -} \ No newline at end of file diff --git a/lib/isHalfWidth.js b/lib/isHalfWidth.js deleted file mode 100644 index 986c63245..000000000 --- a/lib/isHalfWidth.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.halfWidth = undefined; -exports.default = isHalfWidth; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var halfWidth = exports.halfWidth = /[\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]/; - -function isHalfWidth(str) { - (0, _assertString2.default)(str); - return halfWidth.test(str); -} \ No newline at end of file diff --git a/lib/isHash.js b/lib/isHash.js deleted file mode 100644 index 32cc29b9f..000000000 --- a/lib/isHash.js +++ /dev/null @@ -1,35 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isHash; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var lengths = { - md5: 32, - md4: 32, - sha1: 40, - sha256: 64, - sha384: 96, - sha512: 128, - ripemd128: 32, - ripemd160: 40, - tiger128: 32, - tiger160: 40, - tiger192: 48, - crc32: 8, - crc32b: 8 -}; - -function isHash(str, algorithm) { - (0, _assertString2.default)(str); - var hash = new RegExp('^[a-f0-9]{' + lengths[algorithm] + '}$'); - return hash.test(str); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isHexColor.js b/lib/isHexColor.js deleted file mode 100644 index 90634710c..000000000 --- a/lib/isHexColor.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isHexColor; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var hexcolor = /^#?([0-9A-F]{3}|[0-9A-F]{6})$/i; - -function isHexColor(str) { - (0, _assertString2.default)(str); - return hexcolor.test(str); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isHexadecimal.js b/lib/isHexadecimal.js deleted file mode 100644 index fb7808f9d..000000000 --- a/lib/isHexadecimal.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isHexadecimal; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var hexadecimal = /^[0-9A-F]+$/i; - -function isHexadecimal(str) { - (0, _assertString2.default)(str); - return hexadecimal.test(str); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isIP.js b/lib/isIP.js deleted file mode 100644 index fce2ecbfb..000000000 --- a/lib/isIP.js +++ /dev/null @@ -1,81 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isIP; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var ipv4Maybe = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/; -var ipv6Block = /^[0-9A-F]{1,4}$/i; - -function isIP(str) { - var version = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; - - (0, _assertString2.default)(str); - version = String(version); - if (!version) { - return isIP(str, 4) || isIP(str, 6); - } else if (version === '4') { - if (!ipv4Maybe.test(str)) { - return false; - } - var parts = str.split('.').sort(function (a, b) { - return a - b; - }); - return parts[3] <= 255; - } else if (version === '6') { - var blocks = str.split(':'); - var foundOmissionBlock = false; // marker to indicate :: - - // At least some OS accept the last 32 bits of an IPv6 address - // (i.e. 2 of the blocks) in IPv4 notation, and RFC 3493 says - // that '::ffff:a.b.c.d' is valid for IPv4-mapped IPv6 addresses, - // and '::a.b.c.d' is deprecated, but also valid. - var foundIPv4TransitionBlock = isIP(blocks[blocks.length - 1], 4); - var expectedNumberOfBlocks = foundIPv4TransitionBlock ? 7 : 8; - - if (blocks.length > expectedNumberOfBlocks) { - return false; - } - // initial or final :: - if (str === '::') { - return true; - } else if (str.substr(0, 2) === '::') { - blocks.shift(); - blocks.shift(); - foundOmissionBlock = true; - } else if (str.substr(str.length - 2) === '::') { - blocks.pop(); - blocks.pop(); - foundOmissionBlock = true; - } - - for (var i = 0; i < blocks.length; ++i) { - // test for a :: which can not be at the string start/end - // since those cases have been handled above - if (blocks[i] === '' && i > 0 && i < blocks.length - 1) { - if (foundOmissionBlock) { - return false; // multiple :: in address - } - foundOmissionBlock = true; - } else if (foundIPv4TransitionBlock && i === blocks.length - 1) { - // it has been checked before that the last - // block is a valid IPv4 address - } else if (!ipv6Block.test(blocks[i])) { - return false; - } - } - if (foundOmissionBlock) { - return blocks.length >= 1; - } - return blocks.length === expectedNumberOfBlocks; - } - return false; -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isISBN.js b/lib/isISBN.js deleted file mode 100644 index fccec2849..000000000 --- a/lib/isISBN.js +++ /dev/null @@ -1,57 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isISBN; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var isbn10Maybe = /^(?:[0-9]{9}X|[0-9]{10})$/; -var isbn13Maybe = /^(?:[0-9]{13})$/; -var factor = [1, 3]; - -function isISBN(str) { - var version = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; - - (0, _assertString2.default)(str); - version = String(version); - if (!version) { - return isISBN(str, 10) || isISBN(str, 13); - } - var sanitized = str.replace(/[\s-]+/g, ''); - var checksum = 0; - var i = void 0; - if (version === '10') { - if (!isbn10Maybe.test(sanitized)) { - return false; - } - for (i = 0; i < 9; i++) { - checksum += (i + 1) * sanitized.charAt(i); - } - if (sanitized.charAt(9) === 'X') { - checksum += 10 * 10; - } else { - checksum += 10 * sanitized.charAt(9); - } - if (checksum % 11 === 0) { - return !!sanitized; - } - } else if (version === '13') { - if (!isbn13Maybe.test(sanitized)) { - return false; - } - for (i = 0; i < 12; i++) { - checksum += factor[i % 2] * sanitized.charAt(i); - } - if (sanitized.charAt(12) - (10 - checksum % 10) % 10 === 0) { - return !!sanitized; - } - } - return false; -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isISIN.js b/lib/isISIN.js deleted file mode 100644 index feb9725a7..000000000 --- a/lib/isISIN.js +++ /dev/null @@ -1,48 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isISIN; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var isin = /^[A-Z]{2}[0-9A-Z]{9}[0-9]$/; - -function isISIN(str) { - (0, _assertString2.default)(str); - if (!isin.test(str)) { - return false; - } - - var checksumStr = str.replace(/[A-Z]/g, function (character) { - return parseInt(character, 36); - }); - - var sum = 0; - var digit = void 0; - var tmpNum = void 0; - var shouldDouble = true; - for (var i = checksumStr.length - 2; i >= 0; i--) { - digit = checksumStr.substring(i, i + 1); - tmpNum = parseInt(digit, 10); - if (shouldDouble) { - tmpNum *= 2; - if (tmpNum >= 10) { - sum += tmpNum + 1; - } else { - sum += tmpNum; - } - } else { - sum += tmpNum; - } - shouldDouble = !shouldDouble; - } - - return parseInt(str.substr(str.length - 1), 10) === (10000 - sum) % 10; -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isISO31661Alpha2.js b/lib/isISO31661Alpha2.js deleted file mode 100644 index 1741cc13d..000000000 --- a/lib/isISO31661Alpha2.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isISO31661Alpha2; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -// from https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 -var validISO31661Alpha2CountriesCodes = ['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', 'DE', 'DJ', 'DK', 'DM', 'DO', 'DZ', 'EC', 'EE', 'EG', 'EH', 'ER', 'ES', 'ET', 'FI', 'FJ', 'FK', 'FM', 'FO', 'FR', 'GA', 'GB', 'GD', 'GE', 'GF', 'GG', 'GH', 'GI', 'GL', 'GM', 'GN', 'GP', 'GQ', 'GR', 'GS', 'GT', 'GU', 'GW', 'GY', 'HK', 'HM', 'HN', 'HR', 'HT', 'HU', 'ID', 'IE', 'IL', 'IM', 'IN', 'IO', 'IQ', 'IR', 'IS', 'IT', 'JE', 'JM', 'JO', 'JP', 'KE', 'KG', 'KH', 'KI', 'KM', 'KN', 'KP', 'KR', 'KW', 'KY', 'KZ', 'LA', 'LB', 'LC', 'LI', 'LK', 'LR', 'LS', 'LT', 'LU', 'LV', 'LY', 'MA', 'MC', 'MD', 'ME', 'MF', 'MG', 'MH', 'MK', 'ML', 'MM', 'MN', 'MO', 'MP', 'MQ', 'MR', 'MS', 'MT', 'MU', 'MV', 'MW', 'MX', 'MY', 'MZ', 'NA', 'NC', 'NE', 'NF', 'NG', 'NI', 'NL', 'NO', 'NP', 'NR', 'NU', 'NZ', 'OM', 'PA', 'PE', 'PF', 'PG', 'PH', 'PK', 'PL', 'PM', 'PN', 'PR', 'PS', 'PT', 'PW', 'PY', 'QA', 'RE', 'RO', 'RS', 'RU', 'RW', 'SA', 'SB', 'SC', 'SD', 'SE', 'SG', 'SH', 'SI', 'SJ', 'SK', 'SL', 'SM', 'SN', 'SO', 'SR', 'SS', 'ST', 'SV', 'SX', 'SY', 'SZ', 'TC', 'TD', 'TF', 'TG', 'TH', 'TJ', 'TK', 'TL', 'TM', 'TN', 'TO', 'TR', 'TT', 'TV', 'TW', 'TZ', 'UA', 'UG', 'UM', 'US', 'UY', 'UZ', 'VA', 'VC', 'VE', 'VG', 'VI', 'VN', 'VU', 'WF', 'WS', 'YE', 'YT', 'ZA', 'ZM', 'ZW']; - -function isISO31661Alpha2(str) { - (0, _assertString2.default)(str); - return validISO31661Alpha2CountriesCodes.includes(str.toUpperCase()); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isISO8601.js b/lib/isISO8601.js deleted file mode 100644 index e38f8611b..000000000 --- a/lib/isISO8601.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isISO8601; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -/* eslint-disable max-len */ -// from http://goo.gl/0ejHHW -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)?)?)?)?$/; -/* eslint-enable max-len */ - -function isISO8601(str) { - (0, _assertString2.default)(str); - return iso8601.test(str); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isISRC.js b/lib/isISRC.js deleted file mode 100644 index b567cec43..000000000 --- a/lib/isISRC.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isISRC; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -// see http://isrc.ifpi.org/en/isrc-standard/code-syntax -var isrc = /^[A-Z]{2}[0-9A-Z]{3}\d{2}\d{5}$/; - -function isISRC(str) { - (0, _assertString2.default)(str); - return isrc.test(str); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isISSN.js b/lib/isISSN.js deleted file mode 100644 index 79b05c04e..000000000 --- a/lib/isISSN.js +++ /dev/null @@ -1,58 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isISSN; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var issn = '^\\d{4}-?\\d{3}[\\dX]$'; - -function isISSN(str) { - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - - (0, _assertString2.default)(str); - var testIssn = issn; - testIssn = options.require_hyphen ? testIssn.replace('?', '') : testIssn; - testIssn = options.case_sensitive ? new RegExp(testIssn) : new RegExp(testIssn, 'i'); - if (!testIssn.test(str)) { - return false; - } - var issnDigits = str.replace('-', ''); - var position = 8; - var checksum = 0; - var _iteratorNormalCompletion = true; - var _didIteratorError = false; - var _iteratorError = undefined; - - try { - for (var _iterator = issnDigits[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { - var digit = _step.value; - - var digitValue = digit.toUpperCase() === 'X' ? 10 : +digit; - checksum += digitValue * position; - --position; - } - } catch (err) { - _didIteratorError = true; - _iteratorError = err; - } finally { - try { - if (!_iteratorNormalCompletion && _iterator.return) { - _iterator.return(); - } - } finally { - if (_didIteratorError) { - throw _iteratorError; - } - } - } - - return checksum % 11 === 0; -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isIn.js b/lib/isIn.js deleted file mode 100644 index 14aecde40..000000000 --- a/lib/isIn.js +++ /dev/null @@ -1,39 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; - -exports.default = isIn; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -var _toString = require('./util/toString'); - -var _toString2 = _interopRequireDefault(_toString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function isIn(str, options) { - (0, _assertString2.default)(str); - var i = void 0; - if (Object.prototype.toString.call(options) === '[object Array]') { - var array = []; - for (i in options) { - if ({}.hasOwnProperty.call(options, i)) { - array[i] = (0, _toString2.default)(options[i]); - } - } - return array.indexOf(str) >= 0; - } else if ((typeof options === 'undefined' ? 'undefined' : _typeof(options)) === 'object') { - return options.hasOwnProperty(str); - } else if (options && typeof options.indexOf === 'function') { - return options.indexOf(str) >= 0; - } - return false; -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isInt.js b/lib/isInt.js deleted file mode 100644 index 45a136700..000000000 --- a/lib/isInt.js +++ /dev/null @@ -1,33 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isInt; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var int = /^(?:[-+]?(?:0|[1-9][0-9]*))$/; -var intLeadingZeroes = /^[-+]?[0-9]+$/; - -function isInt(str, options) { - (0, _assertString2.default)(str); - options = options || {}; - - // Get the regex to use for testing, based on whether - // leading zeroes are allowed or not. - var regex = options.hasOwnProperty('allow_leading_zeroes') && !options.allow_leading_zeroes ? int : intLeadingZeroes; - - // Check min/max/lt/gt - var minCheckPassed = !options.hasOwnProperty('min') || str >= options.min; - var maxCheckPassed = !options.hasOwnProperty('max') || str <= options.max; - var ltCheckPassed = !options.hasOwnProperty('lt') || str < options.lt; - var gtCheckPassed = !options.hasOwnProperty('gt') || str > options.gt; - - return regex.test(str) && minCheckPassed && maxCheckPassed && ltCheckPassed && gtCheckPassed; -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isJSON.js b/lib/isJSON.js deleted file mode 100644 index 3d432c7e0..000000000 --- a/lib/isJSON.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; - -exports.default = isJSON; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function isJSON(str) { - (0, _assertString2.default)(str); - try { - var obj = JSON.parse(str); - return !!obj && (typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) === 'object'; - } catch (e) {/* ignore */} - return false; -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isLatLong.js b/lib/isLatLong.js deleted file mode 100644 index 230d9ad58..000000000 --- a/lib/isLatLong.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -exports.default = function (str) { - (0, _assertString2.default)(str); - if (!str.includes(',')) return false; - var pair = str.split(','); - return lat.test(pair[0]) && long.test(pair[1]); -}; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var lat = /^\(?[+-]?(90(\.0+)?|[1-8]?\d(\.\d+)?)$/; -var long = /^\s?[+-]?(180(\.0+)?|1[0-7]\d(\.\d+)?|\d{1,2}(\.\d+)?)\)?$/; - -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isLength.js b/lib/isLength.js deleted file mode 100644 index 432819029..000000000 --- a/lib/isLength.js +++ /dev/null @@ -1,34 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; - -exports.default = isLength; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -/* eslint-disable prefer-rest-params */ -function isLength(str, options) { - (0, _assertString2.default)(str); - var min = void 0; - var max = void 0; - if ((typeof options === 'undefined' ? 'undefined' : _typeof(options)) === 'object') { - min = options.min || 0; - max = options.max; - } else { - // backwards compatibility: isLength(str, min [, max]) - min = arguments[1]; - max = arguments[2]; - } - var surrogatePairs = str.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g) || []; - var len = str.length - surrogatePairs.length; - return len >= min && (typeof max === 'undefined' || len <= max); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isLowercase.js b/lib/isLowercase.js deleted file mode 100644 index 556ec6712..000000000 --- a/lib/isLowercase.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isLowercase; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function isLowercase(str) { - (0, _assertString2.default)(str); - return str === str.toLowerCase(); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isMACAddress.js b/lib/isMACAddress.js deleted file mode 100644 index 16d1631de..000000000 --- a/lib/isMACAddress.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isMACAddress; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var macAddress = /^([0-9a-fA-F][0-9a-fA-F]:){5}([0-9a-fA-F][0-9a-fA-F])$/; - -function isMACAddress(str) { - (0, _assertString2.default)(str); - return macAddress.test(str); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isMD5.js b/lib/isMD5.js deleted file mode 100644 index 6de9d17fc..000000000 --- a/lib/isMD5.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isMD5; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var md5 = /^[a-f0-9]{32}$/; - -function isMD5(str) { - (0, _assertString2.default)(str); - return md5.test(str); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isMobilePhone.js b/lib/isMobilePhone.js deleted file mode 100644 index 81b85504e..000000000 --- a/lib/isMobilePhone.js +++ /dev/null @@ -1,96 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isMobilePhone; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -/* eslint-disable max-len */ -var phones = { - 'ar-AE': /^((\+?971)|0)?5[024568]\d{7}$/, - 'ar-DZ': /^(\+?213|0)(5|6|7)\d{8}$/, - 'ar-EG': /^((\+?20)|0)?1[012]\d{8}$/, - 'ar-JO': /^(\+?962|0)?7[789]\d{7}$/, - 'ar-SA': /^(!?(\+?966)|0)?5\d{8}$/, - 'ar-SY': /^(!?(\+?963)|0)?9\d{8}$/, - 'cs-CZ': /^(\+?420)? ?[1-9][0-9]{2} ?[0-9]{3} ?[0-9]{3}$/, - 'da-DK': /^(\+?45)?\s?\d{2}\s?\d{2}\s?\d{2}\s?\d{2}$/, - 'de-DE': /^(\+?49[ \.\-])?([\(]{1}[0-9]{1,6}[\)])?([0-9 \.\-\/]{3,20})((x|ext|extension)[ ]?[0-9]{1,4})?$/, - 'el-GR': /^(\+?30)?(69\d{8})$/, - 'en-AU': /^(\+?61|0)4\d{8}$/, - 'en-GB': /^(\+?44|0)7\d{9}$/, - 'en-HK': /^(\+?852\-?)?[456789]\d{3}\-?\d{4}$/, - 'en-IN': /^(\+?91|0)?[789]\d{9}$/, - 'en-KE': /^(\+?254|0)?[7]\d{8}$/, - 'en-NG': /^(\+?234|0)?[789]\d{9}$/, - 'en-NZ': /^(\+?64|0)2\d{7,9}$/, - 'en-PK': /^((\+92)|(0092))-{0,1}\d{3}-{0,1}\d{7}$|^\d{11}$|^\d{4}-\d{7}$/, - 'en-RW': /^(\+?250|0)?[7]\d{8}$/, - 'en-SG': /^(\+65)?[89]\d{7}$/, - 'en-TZ': /^(\+?255|0)?[67]\d{8}$/, - 'en-UG': /^(\+?256|0)?[7]\d{8}$/, - 'en-US': /^(\+?1)?[2-9]\d{2}[2-9](?!11)\d{6}$/, - 'en-ZA': /^(\+?27|0)\d{9}$/, - 'en-ZM': /^(\+?26)?09[567]\d{7}$/, - 'es-ES': /^(\+?34)?(6\d{1}|7[1234])\d{7}$/, - 'et-EE': /^(\+?372)?\s?(5|8[1-4])\s?([0-9]\s?){6,7}$/, - 'fa-IR': /^(\+?98[\-\s]?|0)9[0-39]\d[\-\s]?\d{3}[\-\s]?\d{4}$/, - 'fi-FI': /^(\+?358|0)\s?(4(0|1|2|4|5|6)?|50)\s?(\d\s?){4,8}\d$/, - 'fo-FO': /^(\+?298)?\s?\d{2}\s?\d{2}\s?\d{2}$/, - 'fr-FR': /^(\+?33|0)[67]\d{8}$/, - 'he-IL': /^(\+972|0)([23489]|5[0248]|77)[1-9]\d{6}/, - 'hu-HU': /^(\+?36)(20|30|70)\d{7}$/, - 'id-ID': /^(\+?62|0[1-9])[\s|\d]+$/, - 'it-IT': /^(\+?39)?\s?3\d{2} ?\d{6,7}$/, - 'ja-JP': /^(\+?81|0)[789]0[ \-]?[1-9]\d{2}[ \-]?\d{5}$/, - 'kl-GL': /^(\+?299)?\s?\d{2}\s?\d{2}\s?\d{2}$/, - 'ko-KR': /^((\+?82)[ \-]?)?0?1([0|1|6|7|8|9]{1})[ \-]?\d{3,4}[ \-]?\d{4}$/, - 'lt-LT': /^(\+370|8)\d{8}$/, - 'ms-MY': /^(\+?6?01){1}(([145]{1}(\-|\s)?\d{7,8})|([236789]{1}(\s|\-)?\d{7}))$/, - 'nb-NO': /^(\+?47)?[49]\d{7}$/, - 'nl-BE': /^(\+?32|0)4?\d{8}$/, - 'nn-NO': /^(\+?47)?[49]\d{7}$/, - 'pl-PL': /^(\+?48)? ?[5-8]\d ?\d{3} ?\d{2} ?\d{2}$/, - 'pt-BR': /^(\+?55|0)\-?[1-9]{2}\-?[2-9]{1}\d{3,4}\-?\d{4}$/, - 'pt-PT': /^(\+?351)?9[1236]\d{7}$/, - 'ro-RO': /^(\+?4?0)\s?7\d{2}(\/|\s|\.|\-)?\d{3}(\s|\.|\-)?\d{3}$/, - 'ru-RU': /^(\+?7|8)?9\d{9}$/, - 'sk-SK': /^(\+?421)? ?[1-9][0-9]{2} ?[0-9]{3} ?[0-9]{3}$/, - 'sr-RS': /^(\+3816|06)[- \d]{5,9}$/, - 'tr-TR': /^(\+?90|0)?5\d{9}$/, - 'uk-UA': /^(\+?38|8)?0\d{9}$/, - 'vi-VN': /^(\+?84|0)?((1(2([0-9])|6([2-9])|88|99))|(9((?!5)[0-9])))([0-9]{7})$/, - 'zh-CN': /^(\+?0?86\-?)?1[345789]\d{9}$/, - 'zh-TW': /^(\+?886\-?|0)?9\d{8}$/ -}; -/* eslint-enable max-len */ - -// aliases -phones['en-CA'] = phones['en-US']; -phones['fr-BE'] = phones['nl-BE']; -phones['zh-HK'] = phones['en-HK']; - -function isMobilePhone(str, locale) { - (0, _assertString2.default)(str); - if (locale in phones) { - return phones[locale].test(str); - } else if (locale === 'any') { - for (var key in phones) { - if (phones.hasOwnProperty(key)) { - var phone = phones[key]; - if (phone.test(str)) { - return true; - } - } - } - return false; - } - throw new Error('Invalid locale \'' + locale + '\''); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isMongoId.js b/lib/isMongoId.js deleted file mode 100644 index 62646bf41..000000000 --- a/lib/isMongoId.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isMongoId; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -var _isHexadecimal = require('./isHexadecimal'); - -var _isHexadecimal2 = _interopRequireDefault(_isHexadecimal); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function isMongoId(str) { - (0, _assertString2.default)(str); - return (0, _isHexadecimal2.default)(str) && str.length === 24; -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isMultibyte.js b/lib/isMultibyte.js deleted file mode 100644 index 3411baab0..000000000 --- a/lib/isMultibyte.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isMultibyte; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -/* eslint-disable no-control-regex */ -var multibyte = /[^\x00-\x7F]/; -/* eslint-enable no-control-regex */ - -function isMultibyte(str) { - (0, _assertString2.default)(str); - return multibyte.test(str); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isNumeric.js b/lib/isNumeric.js deleted file mode 100644 index f3fde3046..000000000 --- a/lib/isNumeric.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isNumeric; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var numeric = /^[-+]?[0-9]+$/; - -function isNumeric(str) { - (0, _assertString2.default)(str); - return numeric.test(str); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isPort.js b/lib/isPort.js deleted file mode 100644 index 66e119656..000000000 --- a/lib/isPort.js +++ /dev/null @@ -1,17 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isPort; - -var _isInt = require('./isInt'); - -var _isInt2 = _interopRequireDefault(_isInt); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function isPort(str) { - return (0, _isInt2.default)(str, { min: 0, max: 65535 }); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isPostalCode.js b/lib/isPostalCode.js deleted file mode 100644 index dfd0b255e..000000000 --- a/lib/isPostalCode.js +++ /dev/null @@ -1,75 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.locales = undefined; - -exports.default = function (str, locale) { - (0, _assertString2.default)(str); - if (locale in patterns) { - return patterns[locale].test(str); - } else if (locale === 'any') { - for (var key in patterns) { - if (patterns.hasOwnProperty(key)) { - var pattern = patterns[key]; - if (pattern.test(str)) { - return true; - } - } - } - return false; - } - throw new Error('Invalid locale \'' + locale + '\''); -}; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -// common patterns -var threeDigit = /^\d{3}$/; -var fourDigit = /^\d{4}$/; -var fiveDigit = /^\d{5}$/; -var sixDigit = /^\d{6}$/; - -var patterns = { - AT: fourDigit, - AU: fourDigit, - BE: fourDigit, - CA: /^[ABCEGHJKLMNPRSTVXY]\d[ABCEGHJ-NPRSTV-Z][\s\-]?\d[ABCEGHJ-NPRSTV-Z]\d$/i, - CH: fourDigit, - CZ: /^\d{3}\s?\d{2}$/, - DE: fiveDigit, - DK: fourDigit, - DZ: fiveDigit, - ES: fiveDigit, - FI: fiveDigit, - FR: /^\d{2}\s?\d{3}$/, - GB: /^(gir\s?0aa|[a-z]{1,2}\d[\da-z]?\s?(\d[a-z]{2})?)$/i, - GR: /^\d{3}\s?\d{2}$/, - IL: fiveDigit, - IN: sixDigit, - IS: threeDigit, - IT: fiveDigit, - JP: /^\d{3}\-\d{4}$/, - KE: fiveDigit, - LI: /^(948[5-9]|949[0-7])$/, - MX: fiveDigit, - NL: /^\d{4}\s?[a-z]{2}$/i, - NO: fourDigit, - PL: /^\d{2}\-\d{3}$/, - PT: /^\d{4}(\-\d{3})?$/, - RO: sixDigit, - RU: sixDigit, - SA: fiveDigit, - SE: /^\d{3}\s?\d{2}$/, - TW: /^\d{3}(\d{2})?$/, - US: /^\d{5}(-\d{4})?$/, - ZA: fourDigit, - ZM: fiveDigit -}; - -var locales = exports.locales = Object.keys(patterns); \ No newline at end of file diff --git a/lib/isSurrogatePair.js b/lib/isSurrogatePair.js deleted file mode 100644 index d8c7a9d2d..000000000 --- a/lib/isSurrogatePair.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isSurrogatePair; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var surrogatePair = /[\uD800-\uDBFF][\uDC00-\uDFFF]/; - -function isSurrogatePair(str) { - (0, _assertString2.default)(str); - return surrogatePair.test(str); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isURL.js b/lib/isURL.js deleted file mode 100644 index 03babd3d0..000000000 --- a/lib/isURL.js +++ /dev/null @@ -1,147 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isURL; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -var _isFQDN = require('./isFQDN'); - -var _isFQDN2 = _interopRequireDefault(_isFQDN); - -var _isIP = require('./isIP'); - -var _isIP2 = _interopRequireDefault(_isIP); - -var _merge = require('./util/merge'); - -var _merge2 = _interopRequireDefault(_merge); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var default_url_options = { - protocols: ['http', 'https', 'ftp'], - require_tld: true, - require_protocol: false, - require_host: true, - require_valid_protocol: true, - allow_underscores: false, - allow_trailing_dot: false, - allow_protocol_relative_urls: false -}; - -var wrapped_ipv6 = /^\[([^\]]+)\](?::([0-9]+))?$/; - -function isRegExp(obj) { - return Object.prototype.toString.call(obj) === '[object RegExp]'; -} - -function checkHost(host, matches) { - for (var i = 0; i < matches.length; i++) { - var match = matches[i]; - if (host === match || isRegExp(match) && match.test(host)) { - return true; - } - } - return false; -} - -function isURL(url, options) { - (0, _assertString2.default)(url); - if (!url || url.length >= 2083 || /[\s<>]/.test(url)) { - return false; - } - if (url.indexOf('mailto:') === 0) { - return false; - } - options = (0, _merge2.default)(options, default_url_options); - var protocol = void 0, - auth = void 0, - host = void 0, - hostname = void 0, - port = void 0, - port_str = void 0, - split = void 0, - ipv6 = void 0; - - split = url.split('#'); - url = split.shift(); - - split = url.split('?'); - url = split.shift(); - - split = url.split('://'); - if (split.length > 1) { - protocol = split.shift(); - if (options.require_valid_protocol && options.protocols.indexOf(protocol) === -1) { - return false; - } - } else if (options.require_protocol) { - return false; - } else if (options.allow_protocol_relative_urls && url.substr(0, 2) === '//') { - split[0] = url.substr(2); - } - url = split.join('://'); - - if (url === '') { - return false; - } - - split = url.split('/'); - url = split.shift(); - - if (url === '' && !options.require_host) { - return true; - } - - split = url.split('@'); - if (split.length > 1) { - auth = split.shift(); - if (auth.indexOf(':') >= 0 && auth.split(':').length > 2) { - return false; - } - } - hostname = split.join('@'); - - port_str = null; - ipv6 = null; - var ipv6_match = hostname.match(wrapped_ipv6); - if (ipv6_match) { - host = ''; - ipv6 = ipv6_match[1]; - port_str = ipv6_match[2] || null; - } else { - split = hostname.split(':'); - host = split.shift(); - if (split.length) { - port_str = split.join(':'); - } - } - - if (port_str !== null) { - port = parseInt(port_str, 10); - if (!/^[0-9]+$/.test(port_str) || port <= 0 || port > 65535) { - return false; - } - } - - if (!(0, _isIP2.default)(host) && !(0, _isFQDN2.default)(host, options) && (!ipv6 || !(0, _isIP2.default)(ipv6, 6))) { - return false; - } - - host = host || ipv6; - - if (options.host_whitelist && !checkHost(host, options.host_whitelist)) { - return false; - } - if (options.host_blacklist && checkHost(host, options.host_blacklist)) { - return false; - } - - return true; -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isUUID.js b/lib/isUUID.js deleted file mode 100644 index d37414457..000000000 --- a/lib/isUUID.js +++ /dev/null @@ -1,28 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isUUID; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var uuid = { - 3: /^[0-9A-F]{8}-[0-9A-F]{4}-3[0-9A-F]{3}-[0-9A-F]{4}-[0-9A-F]{12}$/i, - 4: /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i, - 5: /^[0-9A-F]{8}-[0-9A-F]{4}-5[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i, - all: /^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i -}; - -function isUUID(str) { - var version = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'all'; - - (0, _assertString2.default)(str); - var pattern = uuid[version]; - return pattern && pattern.test(str); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isUppercase.js b/lib/isUppercase.js deleted file mode 100644 index c4f11ebdb..000000000 --- a/lib/isUppercase.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isUppercase; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function isUppercase(str) { - (0, _assertString2.default)(str); - return str === str.toUpperCase(); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isVariableWidth.js b/lib/isVariableWidth.js deleted file mode 100644 index 5b1cf1549..000000000 --- a/lib/isVariableWidth.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isVariableWidth; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -var _isFullWidth = require('./isFullWidth'); - -var _isHalfWidth = require('./isHalfWidth'); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function isVariableWidth(str) { - (0, _assertString2.default)(str); - return _isFullWidth.fullWidth.test(str) && _isHalfWidth.halfWidth.test(str); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/isWhitelisted.js b/lib/isWhitelisted.js deleted file mode 100644 index 1a073561e..000000000 --- a/lib/isWhitelisted.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = isWhitelisted; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function isWhitelisted(str, chars) { - (0, _assertString2.default)(str); - for (var i = str.length - 1; i >= 0; i--) { - if (chars.indexOf(str[i]) === -1) { - return false; - } - } - return true; -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/ltrim.js b/lib/ltrim.js deleted file mode 100644 index 58b2e3db4..000000000 --- a/lib/ltrim.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = ltrim; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function ltrim(str, chars) { - (0, _assertString2.default)(str); - var pattern = chars ? new RegExp('^[' + chars + ']+', 'g') : /^\s+/g; - return str.replace(pattern, ''); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/matches.js b/lib/matches.js deleted file mode 100644 index 43f788489..000000000 --- a/lib/matches.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = matches; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function matches(str, pattern, modifiers) { - (0, _assertString2.default)(str); - if (Object.prototype.toString.call(pattern) !== '[object RegExp]') { - pattern = new RegExp(pattern, modifiers); - } - return pattern.test(str); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/normalizeEmail.js b/lib/normalizeEmail.js deleted file mode 100644 index fb448b32c..000000000 --- a/lib/normalizeEmail.js +++ /dev/null @@ -1,129 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = normalizeEmail; - -var _merge = require('./util/merge'); - -var _merge2 = _interopRequireDefault(_merge); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -var default_normalize_email_options = { - // The following options apply to all email addresses - // Lowercases the local part of the email address. - // Please note this may violate RFC 5321 as per http://stackoverflow.com/a/9808332/192024). - // The domain is always lowercased, as per RFC 1035 - all_lowercase: true, - - // The following conversions are specific to GMail - // Lowercases the local part of the GMail address (known to be case-insensitive) - gmail_lowercase: true, - // Removes dots from the local part of the email address, as that's ignored by GMail - gmail_remove_dots: true, - // Removes the subaddress (e.g. "+foo") from the email address - gmail_remove_subaddress: true, - // Conversts the googlemail.com domain to gmail.com - gmail_convert_googlemaildotcom: true, - - // The following conversions are specific to Outlook.com / Windows Live / Hotmail - // Lowercases the local part of the Outlook.com address (known to be case-insensitive) - outlookdotcom_lowercase: true, - // Removes the subaddress (e.g. "+foo") from the email address - outlookdotcom_remove_subaddress: true, - - // The following conversions are specific to Yahoo - // Lowercases the local part of the Yahoo address (known to be case-insensitive) - yahoo_lowercase: true, - // Removes the subaddress (e.g. "-foo") from the email address - yahoo_remove_subaddress: true, - - // The following conversions are specific to iCloud - // Lowercases the local part of the iCloud address (known to be case-insensitive) - icloud_lowercase: true, - // Removes the subaddress (e.g. "+foo") from the email address - icloud_remove_subaddress: true -}; - -// List of domains used by iCloud -var icloud_domains = ['icloud.com', 'me.com']; - -// List of domains used by Outlook.com and its predecessors -// This list is likely incomplete. -// Partial reference: -// https://blogs.office.com/2013/04/17/outlook-com-gets-two-step-verification-sign-in-by-alias-and-new-international-domains/ -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']; - -// List of domains used by Yahoo Mail -// This list is likely incomplete -var yahoo_domains = ['rocketmail.com', 'yahoo.ca', 'yahoo.co.uk', 'yahoo.com', 'yahoo.de', 'yahoo.fr', 'yahoo.in', 'yahoo.it', 'ymail.com']; - -function normalizeEmail(email, options) { - options = (0, _merge2.default)(options, default_normalize_email_options); - - var raw_parts = email.split('@'); - var domain = raw_parts.pop(); - var user = raw_parts.join('@'); - var parts = [user, domain]; - - // The domain is always lowercased, as it's case-insensitive per RFC 1035 - parts[1] = parts[1].toLowerCase(); - - if (parts[1] === 'gmail.com' || parts[1] === 'googlemail.com') { - // Address is GMail - if (options.gmail_remove_subaddress) { - parts[0] = parts[0].split('+')[0]; - } - if (options.gmail_remove_dots) { - parts[0] = parts[0].replace(/\./g, ''); - } - if (!parts[0].length) { - return false; - } - if (options.all_lowercase || options.gmail_lowercase) { - parts[0] = parts[0].toLowerCase(); - } - parts[1] = options.gmail_convert_googlemaildotcom ? 'gmail.com' : parts[1]; - } else if (~icloud_domains.indexOf(parts[1])) { - // Address is iCloud - if (options.icloud_remove_subaddress) { - parts[0] = parts[0].split('+')[0]; - } - if (!parts[0].length) { - return false; - } - if (options.all_lowercase || options.icloud_lowercase) { - parts[0] = parts[0].toLowerCase(); - } - } else if (~outlookdotcom_domains.indexOf(parts[1])) { - // Address is Outlook.com - if (options.outlookdotcom_remove_subaddress) { - parts[0] = parts[0].split('+')[0]; - } - if (!parts[0].length) { - return false; - } - if (options.all_lowercase || options.outlookdotcom_lowercase) { - parts[0] = parts[0].toLowerCase(); - } - } else if (~yahoo_domains.indexOf(parts[1])) { - // Address is Yahoo - if (options.yahoo_remove_subaddress) { - var components = parts[0].split('-'); - parts[0] = components.length > 1 ? components.slice(0, -1).join('-') : components[0]; - } - if (!parts[0].length) { - return false; - } - if (options.all_lowercase || options.yahoo_lowercase) { - parts[0] = parts[0].toLowerCase(); - } - } else if (options.all_lowercase) { - // Any other address - parts[0] = parts[0].toLowerCase(); - } - return parts.join('@'); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/rtrim.js b/lib/rtrim.js deleted file mode 100644 index fa6d8e15d..000000000 --- a/lib/rtrim.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = rtrim; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function rtrim(str, chars) { - (0, _assertString2.default)(str); - var pattern = chars ? new RegExp('[' + chars + ']') : /\s/; - - var idx = str.length - 1; - while (idx >= 0 && pattern.test(str[idx])) { - idx--; - } - - return idx < str.length ? str.substr(0, idx + 1) : str; -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/stripLow.js b/lib/stripLow.js deleted file mode 100644 index bdf395fef..000000000 --- a/lib/stripLow.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = stripLow; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -var _blacklist = require('./blacklist'); - -var _blacklist2 = _interopRequireDefault(_blacklist); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function stripLow(str, keep_new_lines) { - (0, _assertString2.default)(str); - var chars = keep_new_lines ? '\\x00-\\x09\\x0B\\x0C\\x0E-\\x1F\\x7F' : '\\x00-\\x1F\\x7F'; - return (0, _blacklist2.default)(str, chars); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/toBoolean.js b/lib/toBoolean.js deleted file mode 100644 index 8ac35e358..000000000 --- a/lib/toBoolean.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = toBoolean; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function toBoolean(str, strict) { - (0, _assertString2.default)(str); - if (strict) { - return str === '1' || str === 'true'; - } - return str !== '0' && str !== 'false' && str !== ''; -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/toDate.js b/lib/toDate.js deleted file mode 100644 index 80e21f386..000000000 --- a/lib/toDate.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = toDate; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function toDate(date) { - (0, _assertString2.default)(date); - date = Date.parse(date); - return !isNaN(date) ? new Date(date) : null; -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/toFloat.js b/lib/toFloat.js deleted file mode 100644 index 3a8e256bb..000000000 --- a/lib/toFloat.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = toFloat; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function toFloat(str) { - (0, _assertString2.default)(str); - return parseFloat(str); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/toInt.js b/lib/toInt.js deleted file mode 100644 index 97862a41f..000000000 --- a/lib/toInt.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = toInt; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function toInt(str, radix) { - (0, _assertString2.default)(str); - return parseInt(str, radix || 10); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/trim.js b/lib/trim.js deleted file mode 100644 index cb945d450..000000000 --- a/lib/trim.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = trim; - -var _rtrim = require('./rtrim'); - -var _rtrim2 = _interopRequireDefault(_rtrim); - -var _ltrim = require('./ltrim'); - -var _ltrim2 = _interopRequireDefault(_ltrim); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function trim(str, chars) { - return (0, _rtrim2.default)((0, _ltrim2.default)(str, chars), chars); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/unescape.js b/lib/unescape.js deleted file mode 100644 index cdcd7c96c..000000000 --- a/lib/unescape.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = unescape; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function unescape(str) { - (0, _assertString2.default)(str); - return str.replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, "'").replace(/</g, '<').replace(/>/g, '>').replace(///g, '/').replace(/\/g, '\\').replace(/`/g, '`'); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/util/assertString.js b/lib/util/assertString.js deleted file mode 100644 index 4abc1228f..000000000 --- a/lib/util/assertString.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = assertString; -function assertString(input) { - var isString = typeof input === 'string' || input instanceof String; - - if (!isString) { - throw new TypeError('This library (validator.js) validates strings only'); - } -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/util/merge.js b/lib/util/merge.js deleted file mode 100644 index 68d66fbb8..000000000 --- a/lib/util/merge.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = merge; -function merge() { - var obj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - var defaults = arguments[1]; - - for (var key in defaults) { - if (typeof obj[key] === 'undefined') { - obj[key] = defaults[key]; - } - } - return obj; -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/util/toString.js b/lib/util/toString.js deleted file mode 100644 index e5d5c0db1..000000000 --- a/lib/util/toString.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; - -exports.default = toString; -function toString(input) { - if ((typeof input === 'undefined' ? 'undefined' : _typeof(input)) === 'object' && input !== null) { - if (typeof input.toString === 'function') { - input = input.toString(); - } else { - input = '[object Object]'; - } - } else if (input === null || typeof input === 'undefined' || isNaN(input) && !input.length) { - input = ''; - } - return String(input); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/lib/whitelist.js b/lib/whitelist.js deleted file mode 100644 index 7a06c5d88..000000000 --- a/lib/whitelist.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = whitelist; - -var _assertString = require('./util/assertString'); - -var _assertString2 = _interopRequireDefault(_assertString); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function whitelist(str, chars) { - (0, _assertString2.default)(str); - return str.replace(new RegExp('[^' + chars + ']+', 'g'), ''); -} -module.exports = exports['default']; \ No newline at end of file diff --git a/package.json b/package.json index 61506e924..106694955 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,15 @@ { "name": "validator", "description": "String validation and sanitization", - "version": "9.1.2", - "homepage": "http://github.com/chriso/validator.js", + "version": "13.15.20", + "sideEffects": false, + "homepage": "https://github.com/validatorjs/validator.js", "files": [ "index.js", + "es", "lib", "README.md", - "LICENCE", + "LICENSE", "validator.js", "validator.min.js" ], @@ -22,39 +24,50 @@ "assert" ], "author": "Chris O'Hara ", + "contributors": [ + "Anthony Nandaa (https://github.com/profnandaa)" + ], "main": "index.js", "bugs": { - "url": "http://github.com/chriso/validator.js/issues" + "url": "https://github.com/validatorjs/validator.js/issues" }, "repository": { "type": "git", - "url": "http://github.com/chriso/validator.js.git" + "url": "git+https://github.com/validatorjs/validator.js.git" }, "devDependencies": { - "babel-cli": "^6.24.0", - "babel-plugin-add-module-exports": "^0.2.1", - "babel-preset-es2015": "^6.24.0", - "babel-preset-es2015-rollup": "^3.0.0", - "eslint": "^4.0.0", - "eslint-config-airbnb-base": "^11.2.0", - "eslint-plugin-import": "^2.3.0", - "mocha": "^3.1.2", - "rollup": "^0.43.0", - "rollup-plugin-babel": "^2.7.1", + "@babel/cli": "^7.0.0", + "@babel/core": "^7.0.0", + "@babel/preset-env": "^7.0.0", + "@babel/register": "^7.0.0", + "babel-eslint": "^10.0.1", + "babel-plugin-add-module-exports": "^1.0.0", + "eslint": "^4.19.1", + "eslint-config-airbnb-base": "^12.1.0", + "eslint-plugin-import": "^2.11.0", + "mocha": "^6.2.3", + "npm-run-all": "^4.1.5", + "nyc": "^14.1.0", + "rimraf": "^3.0.0", + "rollup": "^0.47.0", + "rollup-plugin-babel": "^4.0.1", + "timezone-mock": "^1.3.6", "uglify-js": "^3.0.19" }, "scripts": { "lint": "eslint src test", "lint:fix": "eslint --fix src test", - "clean:node": "rm -rf index.js lib", - "clean:browser": "rm -rf validator*.js", - "clean": "npm run clean:node && npm run clean:browser", + "clean:node": "rimraf index.js lib", + "clean:es": "rimraf es", + "clean:browser": "rimraf validator*.js", + "clean": "run-p clean:*", "minify": "uglifyjs validator.js -o validator.min.js --compress --mangle --comments /Copyright/", - "build:browser": "babel-node build-browser && npm run minify", - "build:node": "babel src -d . --presets es2015 --plugins add-module-exports", - "build": "npm run build:browser && npm run build:node", - "pretest": "npm run lint && npm run build", - "test": "mocha --reporter spec" + "build:browser": "node --require @babel/register build-browser && npm run minify", + "build:es": "babel src -d es --env-name=es", + "build:node": "babel src -d .", + "build": "run-p build:*", + "pretest": "npm run build && npm run lint", + "test": "nyc --reporter=cobertura --reporter=text-summary mocha --require @babel/register --reporter dot --recursive" }, "engines": { "node": ">= 0.10" diff --git a/src/.eslintrc.json b/src/.eslintrc.json deleted file mode 100644 index 1e4ea7e6b..000000000 --- a/src/.eslintrc.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "rules": { - "no-console": 1, - "no-plusplus": 0, - "no-bitwise": 0, - "no-prototype-builtins": 0, - "no-useless-escape": 0 - } -} diff --git a/src/index.js b/src/index.js index 24996e071..b69c43649 100644 --- a/src/index.js +++ b/src/index.js @@ -10,36 +10,52 @@ import isEmail from './lib/isEmail'; import isURL from './lib/isURL'; import isMACAddress from './lib/isMACAddress'; import isIP from './lib/isIP'; +import isIPRange from './lib/isIPRange'; import isFQDN from './lib/isFQDN'; +import isDate from './lib/isDate'; +import isTime from './lib/isTime'; import isBoolean from './lib/isBoolean'; +import isLocale from './lib/isLocale'; -import isAlpha from './lib/isAlpha'; -import isAlphanumeric from './lib/isAlphanumeric'; +import isAbaRouting from './lib/isAbaRouting'; +import isAlpha, { locales as isAlphaLocales } from './lib/isAlpha'; +import isAlphanumeric, { locales as isAlphanumericLocales } from './lib/isAlphanumeric'; import isNumeric from './lib/isNumeric'; +import isPassportNumber, { locales as passportNumberLocales } from './lib/isPassportNumber'; import isPort from './lib/isPort'; import isLowercase from './lib/isLowercase'; import isUppercase from './lib/isUppercase'; +import isIMEI from './lib/isIMEI'; + import isAscii from './lib/isAscii'; import isFullWidth from './lib/isFullWidth'; import isHalfWidth from './lib/isHalfWidth'; import isVariableWidth from './lib/isVariableWidth'; import isMultibyte from './lib/isMultibyte'; +import isSemVer from './lib/isSemVer'; import isSurrogatePair from './lib/isSurrogatePair'; import isInt from './lib/isInt'; -import isFloat from './lib/isFloat'; +import isFloat, { locales as isFloatLocales } from './lib/isFloat'; import isDecimal from './lib/isDecimal'; import isHexadecimal from './lib/isHexadecimal'; +import isOctal from './lib/isOctal'; import isDivisibleBy from './lib/isDivisibleBy'; import isHexColor from './lib/isHexColor'; +import isRgbColor from './lib/isRgbColor'; +import isHSL from './lib/isHSL'; import isISRC from './lib/isISRC'; +import isIBAN, { locales as ibanLocales } from './lib/isIBAN'; +import isBIC from './lib/isBIC'; + import isMD5 from './lib/isMD5'; import isHash from './lib/isHash'; +import isJWT from './lib/isJWT'; import isJSON from './lib/isJSON'; import isEmpty from './lib/isEmpty'; @@ -47,6 +63,7 @@ import isEmpty from './lib/isEmpty'; import isLength from './lib/isLength'; import isByteLength from './lib/isByteLength'; +import isULID from './lib/isULID'; import isUUID from './lib/isUUID'; import isMongoId from './lib/isMongoId'; @@ -55,24 +72,45 @@ import isBefore from './lib/isBefore'; import isIn from './lib/isIn'; +import isLuhnNumber from './lib/isLuhnNumber'; import isCreditCard from './lib/isCreditCard'; +import isIdentityCard from './lib/isIdentityCard'; +import isEAN from './lib/isEAN'; import isISIN from './lib/isISIN'; import isISBN from './lib/isISBN'; import isISSN from './lib/isISSN'; +import isTaxID from './lib/isTaxID'; + +import isMobilePhone, { locales as isMobilePhoneLocales } from './lib/isMobilePhone'; -import isMobilePhone from './lib/isMobilePhone'; +import isEthereumAddress from './lib/isEthereumAddress'; import isCurrency from './lib/isCurrency'; +import isBtcAddress from './lib/isBtcAddress'; + +import { isISO6346, isFreightContainerID } from './lib/isISO6346'; +import isISO6391 from './lib/isISO6391'; import isISO8601 from './lib/isISO8601'; +import isRFC3339 from './lib/isRFC3339'; +import isISO15924 from './lib/isISO15924'; import isISO31661Alpha2 from './lib/isISO31661Alpha2'; +import isISO31661Alpha3 from './lib/isISO31661Alpha3'; +import isISO31661Numeric from './lib/isISO31661Numeric'; +import isISO4217 from './lib/isISO4217'; +import isBase32 from './lib/isBase32'; +import isBase58 from './lib/isBase58'; import isBase64 from './lib/isBase64'; import isDataURI from './lib/isDataURI'; +import isMagnetURI from './lib/isMagnetURI'; +import isMailtoURI from './lib/isMailtoURI'; + +import isMimeType from './lib/isMimeType'; import isLatLong from './lib/isLatLong'; -import isPostalCode from './lib/isPostalCode'; +import isPostalCode, { locales as isPostalCodeLocales } from './lib/isPostalCode'; import ltrim from './lib/ltrim'; import rtrim from './lib/rtrim'; @@ -86,9 +124,13 @@ import isWhitelisted from './lib/isWhitelisted'; import normalizeEmail from './lib/normalizeEmail'; -import toString from './lib/util/toString'; +import isSlug from './lib/isSlug'; +import isLicensePlate from './lib/isLicensePlate'; +import isStrongPassword from './lib/isStrongPassword'; + +import isVAT from './lib/isVAT'; -const version = '9.1.2'; +const version = '13.15.20'; const validator = { version, @@ -103,11 +145,19 @@ const validator = { isURL, isMACAddress, isIP, + isIPRange, isFQDN, isBoolean, + isIBAN, + isBIC, + isAbaRouting, isAlpha, + isAlphaLocales, isAlphanumeric, + isAlphanumericLocales, isNumeric, + isPassportNumber, + passportNumberLocales, isPort, isLowercase, isUppercase, @@ -116,36 +166,65 @@ const validator = { isHalfWidth, isVariableWidth, isMultibyte, + isSemVer, isSurrogatePair, isInt, + isIMEI, isFloat, + isFloatLocales, isDecimal, isHexadecimal, + isOctal, isDivisibleBy, isHexColor, + isRgbColor, + isHSL, isISRC, isMD5, isHash, + isJWT, isJSON, isEmpty, isLength, + isLocale, isByteLength, + isULID, isUUID, isMongoId, isAfter, isBefore, isIn, + isLuhnNumber, isCreditCard, + isIdentityCard, + isEAN, isISIN, isISBN, isISSN, isMobilePhone, + isMobilePhoneLocales, isPostalCode, + isPostalCodeLocales, + isEthereumAddress, isCurrency, + isBtcAddress, + isISO6346, + isFreightContainerID, + isISO6391, isISO8601, + isISO15924, + isRFC3339, isISO31661Alpha2, + isISO31661Alpha3, + isISO31661Numeric, + isISO4217, + isBase32, + isBase58, isBase64, isDataURI, + isMagnetURI, + isMailtoURI, + isMimeType, isLatLong, ltrim, rtrim, @@ -158,6 +237,14 @@ const validator = { isWhitelisted, normalizeEmail, toString, + isSlug, + isStrongPassword, + isTaxID, + isDate, + isTime, + isLicensePlate, + isVAT, + ibanLocales, }; export default validator; diff --git a/src/lib/alpha.js b/src/lib/alpha.js index ccc13e650..6f58c9aee 100644 --- a/src/lib/alpha.js +++ b/src/lib/alpha.js @@ -1,47 +1,98 @@ export const alpha = { 'en-US': /^[A-Z]+$/i, + 'az-AZ': /^[A-VXYZÇƏĞİıÖŞÜ]+$/i, + 'bg-BG': /^[А-Я]+$/i, 'cs-CZ': /^[A-ZÁČĎÉĚÍŇÓŘŠŤÚŮÝŽ]+$/i, 'da-DK': /^[A-ZÆØÅ]+$/i, 'de-DE': /^[A-ZÄÖÜß]+$/i, + 'el-GR': /^[Α-ώ]+$/i, 'es-ES': /^[A-ZÁÉÍÑÓÚÜ]+$/i, + 'fa-IR': /^[ابپتثجچحخدذرزژسشصضطظعغفقکگلمنوهی]+$/i, + 'fi-FI': /^[A-ZÅÄÖ]+$/i, 'fr-FR': /^[A-ZÀÂÆÇÉÈÊËÏÎÔŒÙÛÜŸ]+$/i, 'it-IT': /^[A-ZÀÉÈÌÎÓÒÙ]+$/i, + 'ja-JP': /^[ぁ-んァ-ヶヲ-゚一-龠ー・。、]+$/i, 'nb-NO': /^[A-ZÆØÅ]+$/i, 'nl-NL': /^[A-ZÁÉËÏÓÖÜÚ]+$/i, 'nn-NO': /^[A-ZÆØÅ]+$/i, 'hu-HU': /^[A-ZÁÉÍÓÖŐÚÜŰ]+$/i, 'pl-PL': /^[A-ZĄĆĘŚŁŃÓŻŹ]+$/i, - 'pt-PT': /^[A-ZÃÁÀÂÇÉÊÍÕÓÔÚÜ]+$/i, + 'pt-PT': /^[A-ZÃÁÀÂÄÇÉÊËÍÏÕÓÔÖÚÜ]+$/i, 'ru-RU': /^[А-ЯЁ]+$/i, + 'kk-KZ': /^[А-ЯЁ\u04D8\u04B0\u0406\u04A2\u0492\u04AE\u049A\u04E8\u04BA]+$/i, + 'sl-SI': /^[A-ZČĆĐŠŽ]+$/i, + 'sk-SK': /^[A-ZÁČĎÉÍŇÓŠŤÚÝŽĹŔĽÄÔ]+$/i, 'sr-RS@latin': /^[A-ZČĆŽŠĐ]+$/i, 'sr-RS': /^[А-ЯЂЈЉЊЋЏ]+$/i, 'sv-SE': /^[A-ZÅÄÖ]+$/i, + 'th-TH': /^[ก-๐\s]+$/i, 'tr-TR': /^[A-ZÇĞİıÖŞÜ]+$/i, 'uk-UA': /^[А-ЩЬЮЯЄIЇҐі]+$/i, + 'vi-VN': /^[A-ZÀÁẠẢÃÂẦẤẬẨẪĂẰẮẶẲẴĐÈÉẸẺẼÊỀẾỆỂỄÌÍỊỈĨÒÓỌỎÕÔỒỐỘỔỖƠỜỚỢỞỠÙÚỤỦŨƯỪỨỰỬỮỲÝỴỶỸ]+$/i, + 'ko-KR': /^[ㄱ-ㅎㅏ-ㅣ가-힣]*$/, + 'ku-IQ': /^[ئابپتجچحخدرڕزژسشعغفڤقکگلڵمنوۆھەیێيطؤثآإأكضصةظذ]+$/i, ar: /^[ءآأؤإئابةتثجحخدذرزسشصضطظعغفقكلمنهوىيًٌٍَُِّْٰ]+$/, + he: /^[א-ת]+$/, + fa: /^['آاءأؤئبپتثجچحخدذرزژسشصضطظعغفقکگلمنوهةی']+$/i, + bn: /^['ঀঁংঃঅআইঈউঊঋঌএঐওঔকখগঘঙচছজঝঞটঠডঢণতথদধনপফবভমযরলশষসহ়ঽািীুূৃৄেৈোৌ্ৎৗড়ঢ়য়ৠৡৢৣৰৱ৲৳৴৵৶৷৸৹৺৻']+$/, + eo: /^[ABCĈD-GĜHĤIJĴK-PRSŜTUŬVZ]+$/i, + 'hi-IN': /^[\u0900-\u0961]+[\u0972-\u097F]*$/i, + 'si-LK': /^[\u0D80-\u0DFF]+$/, + 'ta-IN': /^[\u0B80-\u0BFF]+$/i, + 'te-IN': /^[\u0C00-\u0C7F]+$/i, + 'kn-IN': /^[\u0C80-\u0CFF]+$/i, + 'ml-IN': /^[\u0D00-\u0D7F]+$/i, + 'gu-IN': /^[\u0A80-\u0AFF]+$/i, + 'pa-IN': /^[\u0A00-\u0A7F]+$/i, + 'or-IN': /^[\u0B00-\u0B7F]+$/i, }; export const alphanumeric = { 'en-US': /^[0-9A-Z]+$/i, + 'az-AZ': /^[0-9A-VXYZÇƏĞİıÖŞÜ]+$/i, + 'bg-BG': /^[0-9А-Я]+$/i, 'cs-CZ': /^[0-9A-ZÁČĎÉĚÍŇÓŘŠŤÚŮÝŽ]+$/i, 'da-DK': /^[0-9A-ZÆØÅ]+$/i, 'de-DE': /^[0-9A-ZÄÖÜß]+$/i, + 'el-GR': /^[0-9Α-ω]+$/i, 'es-ES': /^[0-9A-ZÁÉÍÑÓÚÜ]+$/i, + 'fi-FI': /^[0-9A-ZÅÄÖ]+$/i, 'fr-FR': /^[0-9A-ZÀÂÆÇÉÈÊËÏÎÔŒÙÛÜŸ]+$/i, 'it-IT': /^[0-9A-ZÀÉÈÌÎÓÒÙ]+$/i, + 'ja-JP': /^[0-90-9ぁ-んァ-ヶヲ-゚一-龠ー・。、]+$/i, 'hu-HU': /^[0-9A-ZÁÉÍÓÖŐÚÜŰ]+$/i, 'nb-NO': /^[0-9A-ZÆØÅ]+$/i, 'nl-NL': /^[0-9A-ZÁÉËÏÓÖÜÚ]+$/i, 'nn-NO': /^[0-9A-ZÆØÅ]+$/i, 'pl-PL': /^[0-9A-ZĄĆĘŚŁŃÓŻŹ]+$/i, - 'pt-PT': /^[0-9A-ZÃÁÀÂÇÉÊÍÕÓÔÚÜ]+$/i, + 'pt-PT': /^[0-9A-ZÃÁÀÂÄÇÉÊËÍÏÕÓÔÖÚÜ]+$/i, 'ru-RU': /^[0-9А-ЯЁ]+$/i, + 'kk-KZ': /^[0-9А-ЯЁ\u04D8\u04B0\u0406\u04A2\u0492\u04AE\u049A\u04E8\u04BA]+$/i, + 'sl-SI': /^[0-9A-ZČĆĐŠŽ]+$/i, + 'sk-SK': /^[0-9A-ZÁČĎÉÍŇÓŠŤÚÝŽĹŔĽÄÔ]+$/i, 'sr-RS@latin': /^[0-9A-ZČĆŽŠĐ]+$/i, 'sr-RS': /^[0-9А-ЯЂЈЉЊЋЏ]+$/i, 'sv-SE': /^[0-9A-ZÅÄÖ]+$/i, + 'th-TH': /^[ก-๙\s]+$/i, 'tr-TR': /^[0-9A-ZÇĞİıÖŞÜ]+$/i, 'uk-UA': /^[0-9А-ЩЬЮЯЄIЇҐі]+$/i, + 'ko-KR': /^[0-9ㄱ-ㅎㅏ-ㅣ가-힣]*$/, + 'ku-IQ': /^[٠١٢٣٤٥٦٧٨٩0-9ئابپتجچحخدرڕزژسشعغفڤقکگلڵمنوۆھەیێيطؤثآإأكضصةظذ]+$/i, + 'vi-VN': /^[0-9A-ZÀÁẠẢÃÂẦẤẬẨẪĂẰẮẶẲẴĐÈÉẸẺẼÊỀẾỆỂỄÌÍỊỈĨÒÓỌỎÕÔỒỐỘỔỖƠỜỚỢỞỠÙÚỤỦŨƯỪỨỰỬỮỲÝỴỶỸ]+$/i, ar: /^[٠١٢٣٤٥٦٧٨٩0-9ءآأؤإئابةتثجحخدذرزسشصضطظعغفقكلمنهوىيًٌٍَُِّْٰ]+$/, + he: /^[0-9א-ת]+$/, + fa: /^['0-9آاءأؤئبپتثجچحخدذرزژسشصضطظعغفقکگلمنوهةی۱۲۳۴۵۶۷۸۹۰']+$/i, + bn: /^['ঀঁংঃঅআইঈউঊঋঌএঐওঔকখগঘঙচছজঝঞটঠডঢণতথদধনপফবভমযরলশষসহ়ঽািীুূৃৄেৈোৌ্ৎৗড়ঢ়য়ৠৡৢৣ০১২৩৪৫৬৭৮৯ৰৱ৲৳৴৵৶৷৸৹৺৻']+$/, + eo: /^[0-9ABCĈD-GĜHĤIJĴK-PRSŜTUŬVZ]+$/i, + 'hi-IN': /^[\u0900-\u0963]+[\u0966-\u097F]*$/i, + 'si-LK': /^[0-9\u0D80-\u0DFF]+$/, + 'ta-IN': /^[0-9\u0B80-\u0BFF.]+$/i, + 'te-IN': /^[0-9\u0C00-\u0C7F.]+$/i, + 'kn-IN': /^[0-9\u0C80-\u0CFF.]+$/i, + 'ml-IN': /^[0-9\u0D00-\u0D7F.]+$/i, + 'gu-IN': /^[0-9\u0A80-\u0AFF.]+$/i, + 'pa-IN': /^[0-9\u0A00-\u0A7F.]+$/i, + 'or-IN': /^[0-9\u0B00-\u0B7F.]+$/i, }; export const decimal = { @@ -60,10 +111,8 @@ for (let locale, i = 0; i < englishLocales.length; i++) { } // Source: http://www.localeplanet.com/java/ -export const arabicLocales = [ - 'AE', 'BH', 'DZ', 'EG', 'IQ', 'JO', 'KW', 'LB', 'LY', - 'MA', 'QM', 'QA', 'SA', 'SD', 'SY', 'TN', 'YE', -]; +export const arabicLocales = ['AE', 'BH', 'DZ', 'EG', 'IQ', 'JO', 'KW', 'LB', 'LY', + 'MA', 'QM', 'QA', 'SA', 'SD', 'SY', 'TN', 'YE']; for (let locale, i = 0; i < arabicLocales.length; i++) { locale = `ar-${arabicLocales[i]}`; @@ -72,12 +121,30 @@ for (let locale, i = 0; i < arabicLocales.length; i++) { decimal[locale] = decimal.ar; } +export const farsiLocales = ['IR', 'AF']; + +for (let locale, i = 0; i < farsiLocales.length; i++) { + locale = `fa-${farsiLocales[i]}`; + alphanumeric[locale] = alphanumeric.fa; + decimal[locale] = decimal.ar; +} + +export const bengaliLocales = ['BD', 'IN']; + +for (let locale, i = 0; i < bengaliLocales.length; i++) { + locale = `bn-${bengaliLocales[i]}`; + alpha[locale] = alpha.bn; + alphanumeric[locale] = alphanumeric.bn; + decimal[locale] = decimal['en-US']; +} + // Source: https://en.wikipedia.org/wiki/Decimal_mark -export const dotDecimal = []; +export const dotDecimal = ['ar-EG', 'ar-LB', 'ar-LY']; export const commaDecimal = [ - 'cs-CZ', 'da-DK', 'de-DE', 'es-ES', 'fr-FR', 'it-IT', 'hu-HU', 'nb-NO', - 'nn-NO', 'nl-NL', 'pl-Pl', 'pt-PT', 'ru-RU', 'sr-RS@latin', - 'sr-RS', 'sv-SE', 'tr-TR', 'uk-UA', + 'bg-BG', 'cs-CZ', 'da-DK', 'de-DE', 'el-GR', 'en-ZM', 'eo', 'es-ES', 'fr-CA', 'fr-FR', + 'gu-IN', 'hi-IN', 'hu-HU', 'id-ID', 'it-IT', 'kk-KZ', 'kn-IN', 'ku-IQ', 'ml-IN', 'nb-NO', + 'nl-NL', 'nn-NO', 'or-IN', 'pa-IN', 'pl-PL', 'pt-PT', 'ru-RU', 'si-LK', 'sl-SI', 'sr-RS', + 'sr-RS@latin', 'sv-SE', 'ta-IN', 'te-IN', 'tr-TR', 'uk-UA', 'vi-VN', ]; for (let i = 0; i < dotDecimal.length; i++) { @@ -88,6 +155,17 @@ for (let i = 0; i < commaDecimal.length; i++) { decimal[commaDecimal[i]] = ','; } +alpha['fr-CA'] = alpha['fr-FR']; +alphanumeric['fr-CA'] = alphanumeric['fr-FR']; + alpha['pt-BR'] = alpha['pt-PT']; alphanumeric['pt-BR'] = alphanumeric['pt-PT']; decimal['pt-BR'] = decimal['pt-PT']; + +// see #862 +alpha['pl-Pl'] = alpha['pl-PL']; +alphanumeric['pl-Pl'] = alphanumeric['pl-PL']; +decimal['pl-Pl'] = decimal['pl-PL']; + +// see #1455 +alpha['fa-AF'] = alpha.fa; diff --git a/src/lib/contains.js b/src/lib/contains.js index fd8c578de..8e716c4be 100644 --- a/src/lib/contains.js +++ b/src/lib/contains.js @@ -1,7 +1,19 @@ import assertString from './util/assertString'; import toString from './util/toString'; +import merge from './util/merge'; -export default function contains(str, elem) { +const defaultContainsOptions = { + ignoreCase: false, + minOccurrences: 1, +}; + +export default function contains(str, elem, options) { assertString(str); - return str.indexOf(toString(elem)) >= 0; + options = merge(options, defaultContainsOptions); + + if (options.ignoreCase) { + return str.toLowerCase().split(toString(elem).toLowerCase()).length > options.minOccurrences; + } + + return str.split(toString(elem)).length > options.minOccurrences; } diff --git a/src/lib/isAbaRouting.js b/src/lib/isAbaRouting.js new file mode 100644 index 000000000..0c6fd7fb2 --- /dev/null +++ b/src/lib/isAbaRouting.js @@ -0,0 +1,20 @@ +import assertString from './util/assertString'; + +// http://www.brainjar.com/js/validation/ +// https://www.aba.com/news-research/research-analysis/routing-number-policy-procedures +// series reserved for future use are excluded +const isRoutingReg = /^(?!(1[3-9])|(20)|(3[3-9])|(4[0-9])|(5[0-9])|(60)|(7[3-9])|(8[1-9])|(9[0-2])|(9[3-9]))[0-9]{9}$/; + +export default function isAbaRouting(str) { + assertString(str); + + if (!isRoutingReg.test(str)) return false; + + let checkSumVal = 0; + for (let i = 0; i < str.length; i++) { + if (i % 3 === 0) checkSumVal += str[i] * 3; + else if (i % 3 === 1) checkSumVal += str[i] * 7; + else checkSumVal += str[i] * 1; + } + return (checkSumVal % 10 === 0); +} diff --git a/src/lib/isAfter.js b/src/lib/isAfter.js index 47bfb537f..149622a0d 100644 --- a/src/lib/isAfter.js +++ b/src/lib/isAfter.js @@ -1,9 +1,12 @@ -import assertString from './util/assertString'; import toDate from './toDate'; -export default function isAfter(str, date = String(new Date())) { - assertString(str); - const comparison = toDate(date); - const original = toDate(str); +export default function isAfter(date, options) { + // For backwards compatibility: + // isAfter(str [, date]), i.e. `options` could be used as argument for the legacy `date` + const comparisonDate = (typeof options === 'object' ? options.comparisonDate : options) || Date().toString(); + + const comparison = toDate(comparisonDate); + const original = toDate(date); + return !!(original && comparison && original > comparison); } diff --git a/src/lib/isAlpha.js b/src/lib/isAlpha.js index 1cc8f06b8..e961e64eb 100644 --- a/src/lib/isAlpha.js +++ b/src/lib/isAlpha.js @@ -1,10 +1,26 @@ import assertString from './util/assertString'; import { alpha } from './alpha'; -export default function isAlpha(str, locale = 'en-US') { - assertString(str); +export default function isAlpha(_str, locale = 'en-US', options = {}) { + assertString(_str); + + let str = _str; + const { ignore } = options; + + if (ignore) { + if (ignore instanceof RegExp) { + str = str.replace(ignore, ''); + } else if (typeof ignore === 'string') { + str = str.replace(new RegExp(`[${ignore.replace(/[-[\]{}()*+?.,\\^$|#\\s]/g, '\\$&')}]`, 'g'), ''); // escape regex for ignore + } else { + throw new Error('ignore should be instance of a String or RegExp'); + } + } + if (locale in alpha) { return alpha[locale].test(str); } throw new Error(`Invalid locale '${locale}'`); } + +export const locales = Object.keys(alpha); diff --git a/src/lib/isAlphanumeric.js b/src/lib/isAlphanumeric.js index b29f8174d..b259ab908 100644 --- a/src/lib/isAlphanumeric.js +++ b/src/lib/isAlphanumeric.js @@ -1,10 +1,26 @@ import assertString from './util/assertString'; import { alphanumeric } from './alpha'; -export default function isAlphanumeric(str, locale = 'en-US') { - assertString(str); +export default function isAlphanumeric(_str, locale = 'en-US', options = {}) { + assertString(_str); + + let str = _str; + const { ignore } = options; + + if (ignore) { + if (ignore instanceof RegExp) { + str = str.replace(ignore, ''); + } else if (typeof ignore === 'string') { + str = str.replace(new RegExp(`[${ignore.replace(/[-[\]{}()*+?.,\\^$|#\\s]/g, '\\$&')}]`, 'g'), ''); // escape regex for ignore + } else { + throw new Error('ignore should be instance of a String or RegExp'); + } + } + if (locale in alphanumeric) { return alphanumeric[locale].test(str); } throw new Error(`Invalid locale '${locale}'`); } + +export const locales = Object.keys(alphanumeric); diff --git a/src/lib/isBIC.js b/src/lib/isBIC.js new file mode 100644 index 000000000..b0f586728 --- /dev/null +++ b/src/lib/isBIC.js @@ -0,0 +1,19 @@ +import assertString from './util/assertString'; +import { CountryCodes } from './isISO31661Alpha2'; + +// https://en.wikipedia.org/wiki/ISO_9362 +const isBICReg = /^[A-Za-z]{6}[A-Za-z0-9]{2}([A-Za-z0-9]{3})?$/; + +export default function isBIC(str) { + assertString(str); + + // toUpperCase() should be removed when a new major version goes out that changes + // the regex to [A-Z] (per the spec). + const countryCode = str.slice(4, 6).toUpperCase(); + + if (!CountryCodes.has(countryCode) && countryCode !== 'XK') { + return false; + } + + return isBICReg.test(str); +} diff --git a/src/lib/isBase32.js b/src/lib/isBase32.js new file mode 100644 index 000000000..5e2969cbc --- /dev/null +++ b/src/lib/isBase32.js @@ -0,0 +1,24 @@ +import assertString from './util/assertString'; +import merge from './util/merge'; + +const base32 = /^[A-Z2-7]+=*$/; +const crockfordBase32 = /^[A-HJKMNP-TV-Z0-9]+$/; + +const defaultBase32Options = { + crockford: false, +}; + +export default function isBase32(str, options) { + assertString(str); + options = merge(options, defaultBase32Options); + + if (options.crockford) { + return crockfordBase32.test(str); + } + + const len = str.length; + if (len % 8 === 0 && base32.test(str)) { + return true; + } + return false; +} diff --git a/src/lib/isBase58.js b/src/lib/isBase58.js new file mode 100644 index 000000000..05c46dc18 --- /dev/null +++ b/src/lib/isBase58.js @@ -0,0 +1,12 @@ +import assertString from './util/assertString'; + +// Accepted chars - 123456789ABCDEFGH JKLMN PQRSTUVWXYZabcdefghijk mnopqrstuvwxyz +const base58Reg = /^[A-HJ-NP-Za-km-z1-9]*$/; + +export default function isBase58(str) { + assertString(str); + if (base58Reg.test(str)) { + return true; + } + return false; +} diff --git a/src/lib/isBase64.js b/src/lib/isBase64.js index 73d8ec0b7..fd876e4c0 100644 --- a/src/lib/isBase64.js +++ b/src/lib/isBase64.js @@ -1,15 +1,25 @@ import assertString from './util/assertString'; +import merge from './util/merge'; -const notBase64 = /[^A-Z0-9+\/=]/i; +const base64WithPadding = /^[A-Za-z0-9+/]+={0,2}$/; +const base64WithoutPadding = /^[A-Za-z0-9+/]+$/; +const base64UrlWithPadding = /^[A-Za-z0-9_-]+={0,2}$/; +const base64UrlWithoutPadding = /^[A-Za-z0-9_-]+$/; -export default function isBase64(str) { +export default function isBase64(str, options) { assertString(str); - const len = str.length; - if (!len || len % 4 !== 0 || notBase64.test(str)) { - return false; + options = merge(options, { urlSafe: false, padding: !options?.urlSafe }); + + if (str === '') return true; + + if (options.padding && str.length % 4 !== 0) return false; + + let regex; + if (options.urlSafe) { + regex = options.padding ? base64UrlWithPadding : base64UrlWithoutPadding; + } else { + regex = options.padding ? base64WithPadding : base64WithoutPadding; } - const firstPaddingChar = str.indexOf('='); - return firstPaddingChar === -1 || - firstPaddingChar === len - 1 || - (firstPaddingChar === len - 2 && str[len - 1] === '='); + + return (!options.padding || str.length % 4 === 0) && regex.test(str); } diff --git a/src/lib/isBefore.js b/src/lib/isBefore.js index 8314f0e14..977d0ad1a 100644 --- a/src/lib/isBefore.js +++ b/src/lib/isBefore.js @@ -1,9 +1,12 @@ -import assertString from './util/assertString'; import toDate from './toDate'; -export default function isBefore(str, date = String(new Date())) { - assertString(str); - const comparison = toDate(date); - const original = toDate(str); +export default function isBefore(date, options) { + // For backwards compatibility: + // isBefore(str [, date]), i.e. `options` could be used as argument for the legacy `date` + const comparisonDate = (typeof options === 'object' ? options.comparisonDate : options) || Date().toString(); + + const comparison = toDate(comparisonDate); + const original = toDate(date); + return !!(original && comparison && original < comparison); } diff --git a/src/lib/isBoolean.js b/src/lib/isBoolean.js index e7cb27dfa..40ebd3504 100644 --- a/src/lib/isBoolean.js +++ b/src/lib/isBoolean.js @@ -1,6 +1,16 @@ import assertString from './util/assertString'; +import includes from './util/includesArray'; -export default function isBoolean(str) { +const defaultOptions = { loose: false }; +const strictBooleans = ['true', 'false', '1', '0']; +const looseBooleans = [...strictBooleans, 'yes', 'no']; + +export default function isBoolean(str, options = defaultOptions) { assertString(str); - return (['true', 'false', '1', '0'].indexOf(str) >= 0); + + if (options.loose) { + return includes(looseBooleans, str.toLowerCase()); + } + + return includes(strictBooleans, str); } diff --git a/src/lib/isBtcAddress.js b/src/lib/isBtcAddress.js new file mode 100644 index 000000000..1a309e682 --- /dev/null +++ b/src/lib/isBtcAddress.js @@ -0,0 +1,9 @@ +import assertString from './util/assertString'; + +const bech32 = /^(bc1|tb1|bc1p|tb1p)[ac-hj-np-z02-9]{39,58}$/; +const base58 = /^(1|2|3|m)[A-HJ-NP-Za-km-z1-9]{25,39}$/; + +export default function isBtcAddress(str) { + assertString(str); + return bech32.test(str) || base58.test(str); +} diff --git a/src/lib/isCreditCard.js b/src/lib/isCreditCard.js index 085a4d3e6..938679d39 100644 --- a/src/lib/isCreditCard.js +++ b/src/lib/isCreditCard.js @@ -1,33 +1,42 @@ import assertString from './util/assertString'; +import isLuhnValid from './isLuhnNumber'; -/* eslint-disable max-len */ -const 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})$/; -/* eslint-enable max-len */ +const cards = { + amex: /^3[47][0-9]{13}$/, + dinersclub: /^3(?:0[0-5]|[68][0-9])[0-9]{11}$/, + discover: /^6(?:011|5[0-9][0-9])[0-9]{12,15}$/, + jcb: /^(?:2131|1800|35\d{3})\d{11}$/, + mastercard: /^5[1-5][0-9]{2}|(222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}$/, // /^[25][1-7][0-9]{14}$/; + unionpay: /^(6[27][0-9]{14}|^(81[0-9]{14,17}))$/, + visa: /^(?:4[0-9]{12})(?:[0-9]{3,6})?$/, +}; -export default function isCreditCard(str) { - assertString(str); - const sanitized = str.replace(/[- ]+/g, ''); - if (!creditCard.test(sanitized)) { - return false; +const allCards = (() => { + const tmpCardsArray = []; + for (const cardProvider in cards) { + // istanbul ignore else + if (cards.hasOwnProperty(cardProvider)) { + tmpCardsArray.push(cards[cardProvider]); + } } - let sum = 0; - let digit; - let tmpNum; - let shouldDouble; - for (let i = sanitized.length - 1; i >= 0; i--) { - digit = sanitized.substring(i, (i + 1)); - tmpNum = parseInt(digit, 10); - if (shouldDouble) { - tmpNum *= 2; - if (tmpNum >= 10) { - sum += ((tmpNum % 10) + 1); - } else { - sum += tmpNum; - } - } else { - sum += tmpNum; + return tmpCardsArray; +})(); + +export default function isCreditCard(card, options = {}) { + assertString(card); + const { provider } = options; + const sanitized = card.replace(/[- ]+/g, ''); + if (provider && provider.toLowerCase() in cards) { + // specific provider in the list + if (!(cards[provider.toLowerCase()].test(sanitized))) { + return false; } - shouldDouble = !shouldDouble; + } else if (provider && !(provider.toLowerCase() in cards)) { + /* specific provider not in the list */ + throw new Error(`${provider} is not a valid credit card provider.`); + } else if (!allCards.some(cardProvider => cardProvider.test(sanitized))) { + // no specific provider + return false; } - return !!((sum % 10) === 0 ? sanitized : false); + return isLuhnValid(card); } diff --git a/src/lib/isCurrency.js b/src/lib/isCurrency.js old mode 100644 new mode 100755 index 8cea9fbda..54b1f7c7c --- a/src/lib/isCurrency.js +++ b/src/lib/isCurrency.js @@ -4,8 +4,9 @@ import assertString from './util/assertString'; function currencyRegex(options) { let decimal_digits = `\\d{${options.digits_after_decimal[0]}}`; options.digits_after_decimal.forEach((digit, index) => { if (index !== 0) decimal_digits = `${decimal_digits}|\\d{${digit}}`; }); + const symbol = - `(\\${options.symbol.replace(/\./g, '\\.')})${(options.require_symbol ? '' : '?')}`, + `(${options.symbol.replace(/\W/, m => `\\${m}`)})${(options.require_symbol ? '' : '?')}`, negative = '-?', whole_dollar_amount_without_sep = '[1-9]\\d*', whole_dollar_amount_with_sep = `[1-9]\\d{0,2}(\\${options.thousands_separator}\\d{3})*`, diff --git a/src/lib/isDataURI.js b/src/lib/isDataURI.js index 77b40c5f5..506544807 100644 --- a/src/lib/isDataURI.js +++ b/src/lib/isDataURI.js @@ -1,8 +1,38 @@ import assertString from './util/assertString'; -const 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 +const validMediaType = /^[a-z]+\/[a-z0-9\-\+\._]+$/i; + +const validAttribute = /^[a-z\-]+=[a-z0-9\-]+$/i; + +const validData = /^[a-z0-9!\$&'\(\)\*\+,;=\-\._~:@\/\?%\s]*$/i; export default function isDataURI(str) { assertString(str); - return dataURI.test(str); + let data = str.split(','); + if (data.length < 2) { + return false; + } + const attributes = data.shift().trim().split(';'); + const schemeAndMediaType = attributes.shift(); + if (schemeAndMediaType.slice(0, 5) !== 'data:') { + return false; + } + const mediaType = schemeAndMediaType.slice(5); + if (mediaType !== '' && !validMediaType.test(mediaType)) { + return false; + } + for (let i = 0; i < attributes.length; i++) { + if ( + !(i === attributes.length - 1 && attributes[i].toLowerCase() === 'base64') && + !validAttribute.test(attributes[i]) + ) { + return false; + } + } + for (let i = 0; i < data.length; i++) { + if (!validData.test(data[i])) { + return false; + } + } + return true; } diff --git a/src/lib/isDate.js b/src/lib/isDate.js new file mode 100644 index 000000000..3a1e4afd2 --- /dev/null +++ b/src/lib/isDate.js @@ -0,0 +1,94 @@ +import merge from './util/merge'; + +const default_date_options = { + format: 'YYYY/MM/DD', + delimiters: ['/', '-'], + strictMode: false, +}; + +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); +} + +function zip(date, format) { + const zippedArr = [], + len = Math.max(date.length, format.length); + + for (let i = 0; i < len; i++) { + zippedArr.push([date[i], format[i]]); + } + + return zippedArr; +} + +export default function isDate(input, options) { + if (typeof options === 'string') { // Allow backward compatibility for old format isDate(input [, format]) + options = merge({ format: options }, default_date_options); + } else { + options = merge(options, default_date_options); + } + if (typeof input === 'string' && isValidFormat(options.format)) { + if (options.strictMode && input.length !== options.format.length) return false; + const formatDelimiter = options.delimiters + .find(delimiter => options.format.indexOf(delimiter) !== -1); + const dateDelimiter = options.strictMode + ? formatDelimiter + : options.delimiters.find(delimiter => input.indexOf(delimiter) !== -1); + const dateAndFormat = zip( + input.split(dateDelimiter), + options.format.toLowerCase().split(formatDelimiter) + ); + const dateObj = {}; + + for (const [dateWord, formatWord] of dateAndFormat) { + if (!dateWord || !formatWord || dateWord.length !== formatWord.length) { + return false; + } + + dateObj[formatWord.charAt(0)] = dateWord; + } + + let fullYear = dateObj.y; + + // Check if the year starts with a hyphen + if (fullYear.startsWith('-')) { + return false; // Hyphen before year is not allowed + } + + if (dateObj.y.length === 2) { + const parsedYear = parseInt(dateObj.y, 10); + + if (isNaN(parsedYear)) { + return false; + } + + const currentYearLastTwoDigits = new Date().getFullYear() % 100; + + if (parsedYear < currentYearLastTwoDigits) { + fullYear = `20${dateObj.y}`; + } else { + fullYear = `19${dateObj.y}`; + } + } + + let month = dateObj.m; + + if (dateObj.m.length === 1) { + month = `0${dateObj.m}`; + } + + let day = dateObj.d; + + if (dateObj.d.length === 1) { + day = `0${dateObj.d}`; + } + + return new Date(`${fullYear}-${month}-${day}T00:00:00.000Z`).getUTCDate() === +dateObj.d; + } + + if (!options.strictMode) { + return Object.prototype.toString.call(input) === '[object Date]' && isFinite(input); + } + + return false; +} diff --git a/src/lib/isDecimal.js b/src/lib/isDecimal.js index f3f5734c2..474eff2b0 100644 --- a/src/lib/isDecimal.js +++ b/src/lib/isDecimal.js @@ -1,5 +1,6 @@ import merge from './util/merge'; import assertString from './util/assertString'; +import includes from './util/includesArray'; import { decimal } from './alpha'; function decimalRegExp(options) { @@ -19,7 +20,7 @@ export default function isDecimal(str, options) { assertString(str); options = merge(options, default_decimal_options); if (options.locale in decimal) { - return !blacklist.includes(str.replace(/ /g, '')) && decimalRegExp(options).test(str); + return !includes(blacklist, str.replace(/ /g, '')) && decimalRegExp(options).test(str); } throw new Error(`Invalid locale '${options.locale}'`); } diff --git a/src/lib/isEAN.js b/src/lib/isEAN.js new file mode 100644 index 000000000..731932ad3 --- /dev/null +++ b/src/lib/isEAN.js @@ -0,0 +1,75 @@ +/** + * The most commonly used EAN standard is + * the thirteen-digit EAN-13, while the + * less commonly used 8-digit EAN-8 barcode was + * introduced for use on small packages. + * Also EAN/UCC-14 is used for Grouping of individual + * trade items above unit level(Intermediate, Carton or Pallet). + * For more info about EAN-14 checkout: https://www.gtin.info/itf-14-barcodes/ + * EAN consists of: + * GS1 prefix, manufacturer code, product code and check digit + * Reference: https://en.wikipedia.org/wiki/International_Article_Number + * Reference: https://www.gtin.info/ + */ + +import assertString from './util/assertString'; + +/** + * Define EAN Lengths; 8 for EAN-8; 13 for EAN-13; 14 for EAN-14 + * and Regular Expression for valid EANs (EAN-8, EAN-13, EAN-14), + * with exact numeric matching of 8 or 13 or 14 digits [0-9] + */ +const LENGTH_EAN_8 = 8; +const LENGTH_EAN_14 = 14; +const validEanRegex = /^(\d{8}|\d{13}|\d{14})$/; + + +/** + * Get position weight given: + * EAN length and digit index/position + * + * @param {number} length + * @param {number} index + * @return {number} + */ +function getPositionWeightThroughLengthAndIndex(length, index) { + if (length === LENGTH_EAN_8 || length === LENGTH_EAN_14) { + return (index % 2 === 0) ? 3 : 1; + } + + return (index % 2 === 0) ? 1 : 3; +} + +/** + * Calculate EAN Check Digit + * Reference: https://en.wikipedia.org/wiki/International_Article_Number#Calculation_of_checksum_digit + * + * @param {string} ean + * @return {number} + */ +function calculateCheckDigit(ean) { + const checksum = ean + .slice(0, -1) + .split('') + .map((char, index) => Number(char) * getPositionWeightThroughLengthAndIndex(ean.length, index)) + .reduce((acc, partialSum) => acc + partialSum, 0); + + const remainder = 10 - (checksum % 10); + + return remainder < 10 ? remainder : 0; +} + +/** + * Check if string is valid EAN: + * Matches EAN-8/EAN-13/EAN-14 regex + * Has valid check digit. + * + * @param {string} str + * @return {boolean} + */ +export default function isEAN(str) { + assertString(str); + const actualCheckDigit = Number(str.slice(-1)); + + return validEanRegex.test(str) && actualCheckDigit === calculateCheckDigit(str); +} diff --git a/src/lib/isEmail.js b/src/lib/isEmail.js index e3d9dd9c0..abe465052 100644 --- a/src/lib/isEmail.js +++ b/src/lib/isEmail.js @@ -1,58 +1,172 @@ import assertString from './util/assertString'; +import checkHost from './util/checkHost'; -import merge from './util/merge'; import isByteLength from './isByteLength'; import isFQDN from './isFQDN'; +import isIP from './isIP'; +import merge from './util/merge'; const default_email_options = { allow_display_name: false, + allow_underscores: false, require_display_name: false, allow_utf8_local_part: true, require_tld: true, + blacklisted_chars: '', + ignore_max_length: false, + host_blacklist: [], + host_whitelist: [], }; /* eslint-disable max-len */ /* eslint-disable no-control-regex */ -const displayName = /^[a-z\d!#\$%&'\*\+\-\/=\?\^_`{\|}~\.\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+[a-z\d!#\$%&'\*\+\-\/=\?\^_`{\|}~\,\.\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF\s]*<(.+)>$/i; +const splitNameAddress = /^([^\x00-\x1F\x7F-\x9F\cX]+)]/.test(display_name_without_quotes); + if (contains_illegal) { + // if contains illegal characters, + // must to be enclosed in double-quotes, otherwise it's not a valid display name + if (display_name_without_quotes === display_name) { + return false; + } + + // the quotes in display name must start with character symbol \ + const all_start_with_back_slash = + display_name_without_quotes.split('"').length === display_name_without_quotes.split('\\"').length; + if (!all_start_with_back_slash) { + return false; + } + } + + return true; +} + export default function isEmail(str, options) { assertString(str); options = merge(options, default_email_options); if (options.require_display_name || options.allow_display_name) { - const display_email = str.match(displayName); + const display_email = str.match(splitNameAddress); if (display_email) { - str = display_email[1]; + let display_name = display_email[1]; + + // Remove display name and angle brackets to get email address + // Can be done in the regex but will introduce a ReDOS (See #1597 for more info) + str = str.replace(display_name, '').replace(/(^<|>$)/g, ''); + + // sometimes need to trim the last space to get the display name + // because there may be a space between display name and email address + // eg. myname + // the display name is `myname` instead of `myname `, so need to trim the last space + if (display_name.endsWith(' ')) { + display_name = display_name.slice(0, -1); + } + + if (!validateDisplayName(display_name)) { + return false; + } } else if (options.require_display_name) { return false; } } + if (!options.ignore_max_length && str.length > defaultMaxEmailLength) { + return false; + } const parts = str.split('@'); const domain = parts.pop(); - let user = parts.join('@'); - const lower_domain = domain.toLowerCase(); - if (lower_domain === 'gmail.com' || lower_domain === 'googlemail.com') { - user = user.replace(/\./g, '').toLowerCase(); + + if (options.host_blacklist.length > 0 && checkHost(lower_domain, options.host_blacklist)) { + return false; } - if (!isByteLength(user, { max: 64 }) || - !isByteLength(domain, { max: 254 })) { + if (options.host_whitelist.length > 0 && !checkHost(lower_domain, options.host_whitelist)) { return false; } - if (!isFQDN(domain, { require_tld: options.require_tld })) { + let user = parts.join('@'); + + if (options.domain_specific_validation && (lower_domain === 'gmail.com' || lower_domain === 'googlemail.com')) { + /* + Previously we removed dots for gmail addresses before validating. + This was removed because it allows `multiple..dots@gmail.com` + to be reported as valid, but it is not. + Gmail only normalizes single dots, removing them from here is pointless, + should be done in normalizeEmail + */ + user = user.toLowerCase(); + + // Removing sub-address from username before gmail validation + const username = user.split('+')[0]; + + // Dots are not included in gmail length restriction + if (!isByteLength(username.replace(/\./g, ''), { min: 6, max: 30 })) { + return false; + } + + const user_parts = username.split('.'); + for (let i = 0; i < user_parts.length; i++) { + if (!gmailUserPart.test(user_parts[i])) { + return false; + } + } + } + + if (options.ignore_max_length === false && ( + !isByteLength(user, { max: 64 }) || + !isByteLength(domain, { max: 254 })) + ) { return false; } - if (user[0] === '"') { + if (!isFQDN(domain, { + require_tld: options.require_tld, + ignore_max_length: options.ignore_max_length, + allow_underscores: options.allow_underscores, + })) { + if (!options.allow_ip_domain) { + return false; + } + + if (!isIP(domain)) { + if (!domain.startsWith('[') || !domain.endsWith(']')) { + return false; + } + + let noBracketdomain = domain.slice(1, -1); + + if (noBracketdomain.length === 0 || !isIP(noBracketdomain)) { + return false; + } + } + } + + if (options.blacklisted_chars) { + if (user.search(new RegExp(`[${options.blacklisted_chars}]+`, 'g')) !== -1) return false; + } + + if (user[0] === '"' && user[user.length - 1] === '"') { user = user.slice(1, user.length - 1); return options.allow_utf8_local_part ? quotedEmailUserUtf8.test(user) : diff --git a/src/lib/isEmpty.js b/src/lib/isEmpty.js index 1b1d906b3..eeb5997d0 100644 --- a/src/lib/isEmpty.js +++ b/src/lib/isEmpty.js @@ -1,6 +1,13 @@ import assertString from './util/assertString'; +import merge from './util/merge'; -export default function isEmpty(str) { +const default_is_empty_options = { + ignore_whitespace: false, +}; + +export default function isEmpty(str, options) { assertString(str); - return str.length === 0; + options = merge(options, default_is_empty_options); + + return (options.ignore_whitespace ? str.trim().length : str.length) === 0; } diff --git a/src/lib/isEthereumAddress.js b/src/lib/isEthereumAddress.js new file mode 100644 index 000000000..e538760bf --- /dev/null +++ b/src/lib/isEthereumAddress.js @@ -0,0 +1,8 @@ +import assertString from './util/assertString'; + +const eth = /^(0x)[0-9a-f]{40}$/i; + +export default function isEthereumAddress(str) { + assertString(str); + return eth.test(str); +} diff --git a/src/lib/isFQDN.js b/src/lib/isFQDN.js index 66d9ca5f3..eb6928fda 100644 --- a/src/lib/isFQDN.js +++ b/src/lib/isFQDN.js @@ -5,6 +5,9 @@ const default_fqdn_options = { require_tld: true, allow_underscores: false, allow_trailing_dot: false, + allow_numeric_tld: false, + allow_wildcard: false, + ignore_max_length: false, }; export default function isFQDN(str, options) { @@ -15,32 +18,59 @@ export default function isFQDN(str, options) { if (options.allow_trailing_dot && str[str.length - 1] === '.') { str = str.substring(0, str.length - 1); } + + /* Remove the optional wildcard before checking validity */ + if (options.allow_wildcard === true && str.indexOf('*.') === 0) { + str = str.substring(2); + } + const parts = str.split('.'); + const tld = parts[parts.length - 1]; + if (options.require_tld) { - const tld = parts.pop(); - if (!parts.length || !/^([a-z\u00a1-\uffff]{2,}|xn[a-z0-9-]{2,})$/i.test(tld)) { + // disallow fqdns without tld + if (parts.length < 2) { + return false; + } + + if (!options.allow_numeric_tld && !/^([a-z\u00A1-\u00A8\u00AA-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]{2,}|xn[a-z0-9-]{2,})$/i.test(tld)) { return false; } + // disallow spaces - if (/[\s\u2002-\u200B\u202F\u205F\u3000\uFEFF\uDB40\uDC20]/.test(tld)) { + if (/\s/.test(tld)) { return false; } } - for (let part, i = 0; i < parts.length; i++) { - part = parts[i]; - if (options.allow_underscores) { - part = part.replace(/_/g, ''); + + // reject numeric TLDs + if (!options.allow_numeric_tld && /^\d+$/.test(tld)) { + return false; + } + + return parts.every((part) => { + if (part.length > 63 && !options.ignore_max_length) { + return false; } - if (!/^[a-z\u00a1-\uffff0-9-]+$/i.test(part)) { + + if (!/^[a-z_\u00a1-\uffff0-9-]+$/i.test(part)) { return false; } + // disallow full-width chars if (/[\uff01-\uff5e]/.test(part)) { return false; } - if (part[0] === '-' || part[part.length - 1] === '-') { + + // disallow parts starting or ending with hyphen + if (/^-|-$/.test(part)) { + return false; + } + + if (!options.allow_underscores && /_/.test(part)) { return false; } - } - return true; + + return true; + }); } diff --git a/src/lib/isFloat.js b/src/lib/isFloat.js index 97ff39519..84bdc782c 100644 --- a/src/lib/isFloat.js +++ b/src/lib/isFloat.js @@ -1,16 +1,20 @@ import assertString from './util/assertString'; +import isNullOrUndefined from './util/nullUndefinedCheck'; import { decimal } from './alpha'; export default function isFloat(str, options) { assertString(str); options = options || {}; const float = new RegExp(`^(?:[-+])?(?:[0-9]+)?(?:\\${options.locale ? decimal[options.locale] : '.'}[0-9]*)?(?:[eE][\\+\\-]?(?:[0-9]+))?$`); - if (str === '' || str === '.' || str === '-' || str === '+') { + if (str === '' || str === '.' || str === ',' || str === '-' || str === '+') { return false; } + const value = parseFloat(str.replace(',', '.')); 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); + (!options.hasOwnProperty('min') || isNullOrUndefined(options.min) || value >= options.min) && + (!options.hasOwnProperty('max') || isNullOrUndefined(options.max) || value <= options.max) && + (!options.hasOwnProperty('lt') || isNullOrUndefined(options.lt) || value < options.lt) && + (!options.hasOwnProperty('gt') || isNullOrUndefined(options.gt) || value > options.gt); } + +export const locales = Object.keys(decimal); diff --git a/src/lib/isHSL.js b/src/lib/isHSL.js new file mode 100644 index 000000000..05cb43962 --- /dev/null +++ b/src/lib/isHSL.js @@ -0,0 +1,19 @@ +import assertString from './util/assertString'; + + +const hslComma = /^hsla?\(((\+|\-)?([0-9]+(\.[0-9]+)?(e(\+|\-)?[0-9]+)?|\.[0-9]+(e(\+|\-)?[0-9]+)?))(deg|grad|rad|turn)?(,(\+|\-)?([0-9]+(\.[0-9]+)?(e(\+|\-)?[0-9]+)?|\.[0-9]+(e(\+|\-)?[0-9]+)?)%){2}(,((\+|\-)?([0-9]+(\.[0-9]+)?(e(\+|\-)?[0-9]+)?|\.[0-9]+(e(\+|\-)?[0-9]+)?)%?))?\)$/i; +const hslSpace = /^hsla?\(((\+|\-)?([0-9]+(\.[0-9]+)?(e(\+|\-)?[0-9]+)?|\.[0-9]+(e(\+|\-)?[0-9]+)?))(deg|grad|rad|turn)?(\s(\+|\-)?([0-9]+(\.[0-9]+)?(e(\+|\-)?[0-9]+)?|\.[0-9]+(e(\+|\-)?[0-9]+)?)%){2}\s?(\/\s((\+|\-)?([0-9]+(\.[0-9]+)?(e(\+|\-)?[0-9]+)?|\.[0-9]+(e(\+|\-)?[0-9]+)?)%?)\s?)?\)$/i; + + +export default function isHSL(str) { + assertString(str); + + // Strip duplicate spaces before calling the validation regex (See #1598 for more info) + const strippedStr = str.replace(/\s+/g, ' ').replace(/\s?(hsla?\(|\)|,)\s?/ig, '$1'); + + if (strippedStr.indexOf(',') !== -1) { + return hslComma.test(strippedStr); + } + + return hslSpace.test(strippedStr); +} diff --git a/src/lib/isHash.js b/src/lib/isHash.js index 62af2d413..6efe7c035 100644 --- a/src/lib/isHash.js +++ b/src/lib/isHash.js @@ -18,6 +18,6 @@ const lengths = { export default function isHash(str, algorithm) { assertString(str); - const hash = new RegExp(`^[a-f0-9]{${lengths[algorithm]}}$`); + const hash = new RegExp(`^[a-fA-F0-9]{${lengths[algorithm]}}$`); return hash.test(str); } diff --git a/src/lib/isHexColor.js b/src/lib/isHexColor.js index 01f9d0e85..21a037504 100644 --- a/src/lib/isHexColor.js +++ b/src/lib/isHexColor.js @@ -1,6 +1,6 @@ import assertString from './util/assertString'; -const hexcolor = /^#?([0-9A-F]{3}|[0-9A-F]{6})$/i; +const hexcolor = /^#?([0-9A-F]{3}|[0-9A-F]{4}|[0-9A-F]{6}|[0-9A-F]{8})$/i; export default function isHexColor(str) { assertString(str); diff --git a/src/lib/isHexadecimal.js b/src/lib/isHexadecimal.js index 1e4f17e03..19adb077a 100644 --- a/src/lib/isHexadecimal.js +++ b/src/lib/isHexadecimal.js @@ -1,6 +1,6 @@ import assertString from './util/assertString'; -const hexadecimal = /^[0-9A-F]+$/i; +const hexadecimal = /^(0x|0h)?[0-9A-F]+$/i; export default function isHexadecimal(str) { assertString(str); diff --git a/src/lib/isIBAN.js b/src/lib/isIBAN.js new file mode 100644 index 000000000..ed0abd6c0 --- /dev/null +++ b/src/lib/isIBAN.js @@ -0,0 +1,184 @@ +import assertString from './util/assertString'; +import includes from './util/includesArray'; + +/** + * List of country codes with + * corresponding IBAN regular expression + * Reference: https://en.wikipedia.org/wiki/International_Bank_Account_Number + */ +const ibanRegexThroughCountryCode = { + AD: /^(AD[0-9]{2})\d{8}[A-Z0-9]{12}$/, + AE: /^(AE[0-9]{2})\d{3}\d{16}$/, + AL: /^(AL[0-9]{2})\d{8}[A-Z0-9]{16}$/, + AT: /^(AT[0-9]{2})\d{16}$/, + AZ: /^(AZ[0-9]{2})[A-Z0-9]{4}\d{20}$/, + BA: /^(BA[0-9]{2})\d{16}$/, + BE: /^(BE[0-9]{2})\d{12}$/, + BG: /^(BG[0-9]{2})[A-Z]{4}\d{6}[A-Z0-9]{8}$/, + BH: /^(BH[0-9]{2})[A-Z]{4}[A-Z0-9]{14}$/, + BR: /^(BR[0-9]{2})\d{23}[A-Z]{1}[A-Z0-9]{1}$/, + BY: /^(BY[0-9]{2})[A-Z0-9]{4}\d{20}$/, + CH: /^(CH[0-9]{2})\d{5}[A-Z0-9]{12}$/, + CR: /^(CR[0-9]{2})\d{18}$/, + CY: /^(CY[0-9]{2})\d{8}[A-Z0-9]{16}$/, + CZ: /^(CZ[0-9]{2})\d{20}$/, + DE: /^(DE[0-9]{2})\d{18}$/, + DK: /^(DK[0-9]{2})\d{14}$/, + DO: /^(DO[0-9]{2})[A-Z]{4}\d{20}$/, + DZ: /^(DZ\d{24})$/, + EE: /^(EE[0-9]{2})\d{16}$/, + EG: /^(EG[0-9]{2})\d{25}$/, + ES: /^(ES[0-9]{2})\d{20}$/, + FI: /^(FI[0-9]{2})\d{14}$/, + FO: /^(FO[0-9]{2})\d{14}$/, + FR: /^(FR[0-9]{2})\d{10}[A-Z0-9]{11}\d{2}$/, + GB: /^(GB[0-9]{2})[A-Z]{4}\d{14}$/, + GE: /^(GE[0-9]{2})[A-Z0-9]{2}\d{16}$/, + GI: /^(GI[0-9]{2})[A-Z]{4}[A-Z0-9]{15}$/, + GL: /^(GL[0-9]{2})\d{14}$/, + GR: /^(GR[0-9]{2})\d{7}[A-Z0-9]{16}$/, + GT: /^(GT[0-9]{2})[A-Z0-9]{4}[A-Z0-9]{20}$/, + HR: /^(HR[0-9]{2})\d{17}$/, + HU: /^(HU[0-9]{2})\d{24}$/, + IE: /^(IE[0-9]{2})[A-Z]{4}\d{14}$/, + IL: /^(IL[0-9]{2})\d{19}$/, + IQ: /^(IQ[0-9]{2})[A-Z]{4}\d{15}$/, + IR: /^(IR[0-9]{2})0\d{2}0\d{18}$/, + IS: /^(IS[0-9]{2})\d{22}$/, + IT: /^(IT[0-9]{2})[A-Z]{1}\d{10}[A-Z0-9]{12}$/, + JO: /^(JO[0-9]{2})[A-Z]{4}\d{22}$/, + KW: /^(KW[0-9]{2})[A-Z]{4}[A-Z0-9]{22}$/, + KZ: /^(KZ[0-9]{2})\d{3}[A-Z0-9]{13}$/, + LB: /^(LB[0-9]{2})\d{4}[A-Z0-9]{20}$/, + LC: /^(LC[0-9]{2})[A-Z]{4}[A-Z0-9]{24}$/, + LI: /^(LI[0-9]{2})\d{5}[A-Z0-9]{12}$/, + LT: /^(LT[0-9]{2})\d{16}$/, + LU: /^(LU[0-9]{2})\d{3}[A-Z0-9]{13}$/, + LV: /^(LV[0-9]{2})[A-Z]{4}[A-Z0-9]{13}$/, + MA: /^(MA[0-9]{26})$/, + MC: /^(MC[0-9]{2})\d{10}[A-Z0-9]{11}\d{2}$/, + MD: /^(MD[0-9]{2})[A-Z0-9]{20}$/, + ME: /^(ME[0-9]{2})\d{18}$/, + MK: /^(MK[0-9]{2})\d{3}[A-Z0-9]{10}\d{2}$/, + MR: /^(MR[0-9]{2})\d{23}$/, + MT: /^(MT[0-9]{2})[A-Z]{4}\d{5}[A-Z0-9]{18}$/, + MU: /^(MU[0-9]{2})[A-Z]{4}\d{19}[A-Z]{3}$/, + MZ: /^(MZ[0-9]{2})\d{21}$/, + NL: /^(NL[0-9]{2})[A-Z]{4}\d{10}$/, + NO: /^(NO[0-9]{2})\d{11}$/, + PK: /^(PK[0-9]{2})[A-Z0-9]{4}\d{16}$/, + PL: /^(PL[0-9]{2})\d{24}$/, + PS: /^(PS[0-9]{2})[A-Z]{4}[A-Z0-9]{21}$/, + PT: /^(PT[0-9]{2})\d{21}$/, + QA: /^(QA[0-9]{2})[A-Z]{4}[A-Z0-9]{21}$/, + RO: /^(RO[0-9]{2})[A-Z]{4}[A-Z0-9]{16}$/, + RS: /^(RS[0-9]{2})\d{18}$/, + SA: /^(SA[0-9]{2})\d{2}[A-Z0-9]{18}$/, + SC: /^(SC[0-9]{2})[A-Z]{4}\d{20}[A-Z]{3}$/, + SE: /^(SE[0-9]{2})\d{20}$/, + SI: /^(SI[0-9]{2})\d{15}$/, + SK: /^(SK[0-9]{2})\d{20}$/, + SM: /^(SM[0-9]{2})[A-Z]{1}\d{10}[A-Z0-9]{12}$/, + SV: /^(SV[0-9]{2})[A-Z0-9]{4}\d{20}$/, + TL: /^(TL[0-9]{2})\d{19}$/, + TN: /^(TN[0-9]{2})\d{20}$/, + TR: /^(TR[0-9]{2})\d{5}[A-Z0-9]{17}$/, + UA: /^(UA[0-9]{2})\d{6}[A-Z0-9]{19}$/, + VA: /^(VA[0-9]{2})\d{18}$/, + VG: /^(VG[0-9]{2})[A-Z]{4}\d{16}$/, + XK: /^(XK[0-9]{2})\d{16}$/, +}; + +/** + * Check if the country codes passed are valid using the + * ibanRegexThroughCountryCode as a reference + * + * @param {array} countryCodeArray + * @return {boolean} + */ + +function hasOnlyValidCountryCodes(countryCodeArray) { + const countryCodeArrayFilteredWithObjectIbanCode = countryCodeArray + .filter(countryCode => !(countryCode in ibanRegexThroughCountryCode)); + + if (countryCodeArrayFilteredWithObjectIbanCode.length > 0) { + return false; + } + + return true; +} + +/** + * Check whether string has correct universal IBAN format + * The IBAN consists of up to 34 alphanumeric characters, as follows: + * Country Code using ISO 3166-1 alpha-2, two letters + * check digits, two digits and + * Basic Bank Account Number (BBAN), up to 30 alphanumeric characters. + * NOTE: Permitted IBAN characters are: digits [0-9] and the 26 latin alphabetic [A-Z] + * + * @param {string} str - string under validation + * @param {object} options - object to pass the countries to be either whitelisted or blacklisted + * @return {boolean} + */ +function hasValidIbanFormat(str, options) { + // Strip white spaces and hyphens + const strippedStr = str.replace(/[\s\-]+/gi, '').toUpperCase(); + const isoCountryCode = strippedStr.slice(0, 2).toUpperCase(); + + const isoCountryCodeInIbanRegexCodeObject = isoCountryCode in ibanRegexThroughCountryCode; + + if (options.whitelist) { + if (!hasOnlyValidCountryCodes(options.whitelist)) { + return false; + } + + const isoCountryCodeInWhiteList = includes(options.whitelist, isoCountryCode); + + if (!isoCountryCodeInWhiteList) { + return false; + } + } + + if (options.blacklist) { + const isoCountryCodeInBlackList = includes(options.blacklist, isoCountryCode); + + if (isoCountryCodeInBlackList) { + return false; + } + } + + return (isoCountryCodeInIbanRegexCodeObject) && + ibanRegexThroughCountryCode[isoCountryCode].test(strippedStr); +} + +/** + * Check whether string has valid IBAN Checksum + * by performing basic mod-97 operation and + * the remainder should equal 1 + * -- Start by rearranging the IBAN by moving the four initial characters to the end of the string + * -- Replace each letter in the string with two digits, A -> 10, B = 11, Z = 35 + * -- Interpret the string as a decimal integer and + * -- compute the remainder on division by 97 (mod 97) + * Reference: https://en.wikipedia.org/wiki/International_Bank_Account_Number + * + * @param {string} str + * @return {boolean} + */ +function hasValidIbanChecksum(str) { + const strippedStr = str.replace(/[^A-Z0-9]+/gi, '').toUpperCase(); // Keep only digits and A-Z latin alphabetic + const rearranged = strippedStr.slice(4) + strippedStr.slice(0, 4); + const alphaCapsReplacedWithDigits = rearranged.replace(/[A-Z]/g, char => char.charCodeAt(0) - 55); + + const remainder = alphaCapsReplacedWithDigits.match(/\d{1,7}/g) + .reduce((acc, value) => Number(acc + value) % 97, ''); + + return remainder === 1; +} + +export default function isIBAN(str, options = {}) { + assertString(str); + + return hasValidIbanFormat(str, options) && hasValidIbanChecksum(str); +} + +export const locales = Object.keys(ibanRegexThroughCountryCode); diff --git a/src/lib/isIMEI.js b/src/lib/isIMEI.js new file mode 100644 index 000000000..984b4fb88 --- /dev/null +++ b/src/lib/isIMEI.js @@ -0,0 +1,50 @@ +import assertString from './util/assertString'; + + +let imeiRegexWithoutHyphens = /^[0-9]{15}$/; +let imeiRegexWithHyphens = /^\d{2}-\d{6}-\d{6}-\d{1}$/; + + +export default function isIMEI(str, options) { + assertString(str); + options = options || {}; + + // default regex for checking imei is the one without hyphens + + let imeiRegex = imeiRegexWithoutHyphens; + + if (options.allow_hyphens) { + imeiRegex = imeiRegexWithHyphens; + } + + + if (!imeiRegex.test(str)) { + return false; + } + + str = str.replace(/-/g, ''); + + let sum = 0, + mul = 2, + l = 14; + + for (let i = 0; i < l; i++) { + const digit = str.substring(l - i - 1, l - i); + const tp = parseInt(digit, 10) * mul; + if (tp >= 10) { + sum += (tp % 10) + 1; + } else { + sum += tp; + } + if (mul === 1) { + mul += 1; + } else { + mul -= 1; + } + } + const chk = ((10 - (sum % 10)) % 10); + if (chk !== parseInt(str.substring(14, 15), 10)) { + return false; + } + return true; +} diff --git a/src/lib/isIP.js b/src/lib/isIP.js index e4776e491..da9bb6ca9 100644 --- a/src/lib/isIP.js +++ b/src/lib/isIP.js @@ -1,65 +1,67 @@ import assertString from './util/assertString'; +/** +11.3. Examples -const ipv4Maybe = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/; -const ipv6Block = /^[0-9A-F]{1,4}$/i; + The following addresses -export default function isIP(str, version = '') { - assertString(str); - version = String(version); - if (!version) { - return isIP(str, 4) || isIP(str, 6); - } else if (version === '4') { - if (!ipv4Maybe.test(str)) { - return false; - } - const parts = str.split('.').sort((a, b) => a - b); - return parts[3] <= 255; - } else if (version === '6') { - const blocks = str.split(':'); - let foundOmissionBlock = false; // marker to indicate :: + fe80::1234 (on the 1st link of the node) + ff02::5678 (on the 5th link of the node) + ff08::9abc (on the 10th organization of the node) + + would be represented as follows: + + fe80::1234%1 + ff02::5678%5 + ff08::9abc%10 + + (Here we assume a natural translation from a zone index to the + part, where the Nth zone of any scope is translated into + "N".) + + If we use interface names as , those addresses could also be + represented as follows: + + fe80::1234%ne0 + ff02::5678%pvc1.3 + ff08::9abc%interface10 + + where the interface "ne0" belongs to the 1st link, "pvc1.3" belongs + to the 5th link, and "interface10" belongs to the 10th organization. + * * */ +const IPv4SegmentFormat = '(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'; +const IPv4AddressFormat = `(${IPv4SegmentFormat}[.]){3}${IPv4SegmentFormat}`; +const IPv4AddressRegExp = new RegExp(`^${IPv4AddressFormat}$`); - // At least some OS accept the last 32 bits of an IPv6 address - // (i.e. 2 of the blocks) in IPv4 notation, and RFC 3493 says - // that '::ffff:a.b.c.d' is valid for IPv4-mapped IPv6 addresses, - // and '::a.b.c.d' is deprecated, but also valid. - const foundIPv4TransitionBlock = isIP(blocks[blocks.length - 1], 4); - const expectedNumberOfBlocks = foundIPv4TransitionBlock ? 7 : 8; +const IPv6SegmentFormat = '(?:[0-9a-fA-F]{1,4})'; +const IPv6AddressRegExp = new RegExp('^(' + + `(?:${IPv6SegmentFormat}:){7}(?:${IPv6SegmentFormat}|:)|` + + `(?:${IPv6SegmentFormat}:){6}(?:${IPv4AddressFormat}|:${IPv6SegmentFormat}|:)|` + + `(?:${IPv6SegmentFormat}:){5}(?::${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,2}|:)|` + + `(?:${IPv6SegmentFormat}:){4}(?:(:${IPv6SegmentFormat}){0,1}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,3}|:)|` + + `(?:${IPv6SegmentFormat}:){3}(?:(:${IPv6SegmentFormat}){0,2}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,4}|:)|` + + `(?:${IPv6SegmentFormat}:){2}(?:(:${IPv6SegmentFormat}){0,3}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,5}|:)|` + + `(?:${IPv6SegmentFormat}:){1}(?:(:${IPv6SegmentFormat}){0,4}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,6}|:)|` + + `(?::((?::${IPv6SegmentFormat}){0,5}:${IPv4AddressFormat}|(?::${IPv6SegmentFormat}){1,7}|:))` + + ')(%[0-9a-zA-Z.]{1,})?$'); - if (blocks.length > expectedNumberOfBlocks) { - return false; - } - // initial or final :: - if (str === '::') { - return true; - } else if (str.substr(0, 2) === '::') { - blocks.shift(); - blocks.shift(); - foundOmissionBlock = true; - } else if (str.substr(str.length - 2) === '::') { - blocks.pop(); - blocks.pop(); - foundOmissionBlock = true; - } +export default function isIP(ipAddress, options = {}) { + assertString(ipAddress); - for (let i = 0; i < blocks.length; ++i) { - // test for a :: which can not be at the string start/end - // since those cases have been handled above - if (blocks[i] === '' && i > 0 && i < blocks.length - 1) { - if (foundOmissionBlock) { - return false; // multiple :: in address - } - foundOmissionBlock = true; - } else if (foundIPv4TransitionBlock && i === blocks.length - 1) { - // it has been checked before that the last - // block is a valid IPv4 address - } else if (!ipv6Block.test(blocks[i])) { - return false; - } - } - if (foundOmissionBlock) { - return blocks.length >= 1; - } - return blocks.length === expectedNumberOfBlocks; + // accessing 'arguments' for backwards compatibility: isIP(ipAddress [, version]) + // eslint-disable-next-line prefer-rest-params + const version = (typeof options === 'object' ? options.version : arguments[1]) || ''; + + if (!version) { + return isIP(ipAddress, { version: 4 }) || isIP(ipAddress, { version: 6 }); + } + + if (version.toString() === '4') { + return IPv4AddressRegExp.test(ipAddress); } + + if (version.toString() === '6') { + return IPv6AddressRegExp.test(ipAddress); + } + return false; } diff --git a/src/lib/isIPRange.js b/src/lib/isIPRange.js new file mode 100644 index 000000000..5bb6916c5 --- /dev/null +++ b/src/lib/isIPRange.js @@ -0,0 +1,47 @@ +import assertString from './util/assertString'; +import isIP from './isIP'; + +const subnetMaybe = /^\d{1,3}$/; +const v4Subnet = 32; +const v6Subnet = 128; + +export default function isIPRange(str, version = '') { + assertString(str); + const parts = str.split('/'); + + // parts[0] -> ip, parts[1] -> subnet + if (parts.length !== 2) { + return false; + } + + if (!subnetMaybe.test(parts[1])) { + return false; + } + + // Disallow preceding 0 i.e. 01, 02, ... + if (parts[1].length > 1 && parts[1].startsWith('0')) { + return false; + } + + const isValidIP = isIP(parts[0], version); + if (!isValidIP) { + return false; + } + + // Define valid subnet according to IP's version + let expectedSubnet = null; + switch (String(version)) { + case '4': + expectedSubnet = v4Subnet; + break; + + case '6': + expectedSubnet = v6Subnet; + break; + + default: + expectedSubnet = isIP(parts[0], '6') ? v6Subnet : v4Subnet; + } + + return parts[1] <= expectedSubnet && parts[1] >= 0; +} diff --git a/src/lib/isISBN.js b/src/lib/isISBN.js index c66c4c991..4499c59a0 100644 --- a/src/lib/isISBN.js +++ b/src/lib/isISBN.js @@ -1,43 +1,55 @@ import assertString from './util/assertString'; -const isbn10Maybe = /^(?:[0-9]{9}X|[0-9]{10})$/; -const isbn13Maybe = /^(?:[0-9]{13})$/; +const possibleIsbn10 = /^(?:[0-9]{9}X|[0-9]{10})$/; +const possibleIsbn13 = /^(?:[0-9]{13})$/; const factor = [1, 3]; -export default function isISBN(str, version = '') { - assertString(str); - version = String(version); - if (!version) { - return isISBN(str, 10) || isISBN(str, 13); +export default function isISBN(isbn, options) { + assertString(isbn); + + // For backwards compatibility: + // isISBN(str [, version]), i.e. `options` could be used as argument for the legacy `version` + const version = String(options?.version || options); + + if (!(options?.version || options)) { + return isISBN(isbn, { version: 10 }) || isISBN(isbn, { version: 13 }); } - const sanitized = str.replace(/[\s-]+/g, ''); + + const sanitizedIsbn = isbn.replace(/[\s-]+/g, ''); + let checksum = 0; - let i; + if (version === '10') { - if (!isbn10Maybe.test(sanitized)) { + if (!possibleIsbn10.test(sanitizedIsbn)) { return false; } - for (i = 0; i < 9; i++) { - checksum += (i + 1) * sanitized.charAt(i); + + for (let i = 0; i < version - 1; i++) { + checksum += (i + 1) * sanitizedIsbn.charAt(i); } - if (sanitized.charAt(9) === 'X') { + + if (sanitizedIsbn.charAt(9) === 'X') { checksum += 10 * 10; } else { - checksum += 10 * sanitized.charAt(9); + checksum += 10 * sanitizedIsbn.charAt(9); } + if ((checksum % 11) === 0) { - return !!sanitized; + return true; } } else if (version === '13') { - if (!isbn13Maybe.test(sanitized)) { + if (!possibleIsbn13.test(sanitizedIsbn)) { return false; } - for (i = 0; i < 12; i++) { - checksum += factor[i % 2] * sanitized.charAt(i); + + for (let i = 0; i < 12; i++) { + checksum += factor[i % 2] * sanitizedIsbn.charAt(i); } - if (sanitized.charAt(12) - ((10 - (checksum % 10)) % 10) === 0) { - return !!sanitized; + + if (sanitizedIsbn.charAt(12) - ((10 - (checksum % 10)) % 10) === 0) { + return true; } } + return false; } diff --git a/src/lib/isISIN.js b/src/lib/isISIN.js index 4576b684c..a6f6fa645 100644 --- a/src/lib/isISIN.js +++ b/src/lib/isISIN.js @@ -2,6 +2,12 @@ import assertString from './util/assertString'; const isin = /^[A-Z]{2}[0-9A-Z]{9}[0-9]$/; +// this link details how the check digit is calculated: +// https://www.isin.org/isin-format/. it is a little bit +// odd in that it works with digits, not numbers. in order +// to make only one pass through the ISIN characters, the +// each alpha character is handled as 2 characters within +// the loop. export default function isISIN(str) { assertString(str); @@ -9,27 +15,44 @@ export default function isISIN(str) { return false; } - const checksumStr = str.replace(/[A-Z]/g, character => (parseInt(character, 36))); - + let double = true; let sum = 0; - let digit; - let tmpNum; - let shouldDouble = true; - for (let i = checksumStr.length - 2; i >= 0; i--) { - digit = checksumStr.substring(i, (i + 1)); - tmpNum = parseInt(digit, 10); - if (shouldDouble) { - tmpNum *= 2; - if (tmpNum >= 10) { - sum += tmpNum + 1; - } else { - sum += tmpNum; + // convert values + for (let i = str.length - 2; i >= 0; i--) { + if (str[i] >= 'A' && str[i] <= 'Z') { + const value = str[i].charCodeAt(0) - 55; + const lo = value % 10; + const hi = Math.trunc(value / 10); + // letters have two digits, so handle the low order + // and high order digits separately. + for (const digit of [lo, hi]) { + if (double) { + if (digit >= 5) { + sum += 1 + ((digit - 5) * 2); + } else { + sum += digit * 2; + } + } else { + sum += digit; + } + double = !double; } } else { - sum += tmpNum; + const digit = str[i].charCodeAt(0) - '0'.charCodeAt(0); + if (double) { + if (digit >= 5) { + sum += 1 + ((digit - 5) * 2); + } else { + sum += digit * 2; + } + } else { + sum += digit; + } + double = !double; } - shouldDouble = !shouldDouble; } - return parseInt(str.substr(str.length - 1), 10) === (10000 - sum) % 10; + const check = (Math.trunc(((sum + 9) / 10)) * 10) - sum; + + return +str[str.length - 1] === check; } diff --git a/src/lib/isISO15924.js b/src/lib/isISO15924.js new file mode 100644 index 000000000..30f9e4a4f --- /dev/null +++ b/src/lib/isISO15924.js @@ -0,0 +1,37 @@ +import assertString from './util/assertString'; + +// from https://www.unicode.org/iso15924/iso15924-codes.html +const validISO15924Codes = new Set([ + 'Adlm', 'Afak', 'Aghb', 'Ahom', 'Arab', 'Aran', 'Armi', 'Armn', 'Avst', + 'Bali', 'Bamu', 'Bass', 'Batk', 'Beng', 'Bhks', 'Blis', 'Bopo', 'Brah', 'Brai', 'Bugi', 'Buhd', + 'Cakm', 'Cans', 'Cari', 'Cham', 'Cher', 'Chis', 'Chrs', 'Cirt', 'Copt', 'Cpmn', 'Cprt', 'Cyrl', 'Cyrs', + 'Deva', 'Diak', 'Dogr', 'Dsrt', 'Dupl', + 'Egyd', 'Egyh', 'Egyp', 'Elba', 'Elym', 'Ethi', + 'Gara', 'Geok', 'Geor', 'Glag', 'Gong', 'Gonm', 'Goth', 'Gran', 'Grek', 'Gujr', 'Gukh', 'Guru', + 'Hanb', 'Hang', 'Hani', 'Hano', 'Hans', 'Hant', 'Hatr', 'Hebr', 'Hira', 'Hluw', 'Hmng', 'Hmnp', 'Hrkt', 'Hung', + 'Inds', 'Ital', + 'Jamo', 'Java', 'Jpan', 'Jurc', + 'Kali', 'Kana', 'Kawi', 'Khar', 'Khmr', 'Khoj', 'Kitl', 'Kits', 'Knda', 'Kore', 'Kpel', 'Krai', 'Kthi', + 'Lana', 'Laoo', 'Latf', 'Latg', 'Latn', 'Leke', 'Lepc', 'Limb', 'Lina', 'Linb', 'Lisu', 'Loma', 'Lyci', 'Lydi', + 'Mahj', 'Maka', 'Mand', 'Mani', 'Marc', 'Maya', 'Medf', 'Mend', 'Merc', 'Mero', 'Mlym', 'Modi', 'Mong', 'Moon', 'Mroo', 'Mtei', 'Mult', 'Mymr', + 'Nagm', 'Nand', 'Narb', 'Nbat', 'Newa', 'Nkdb', 'Nkgb', 'Nkoo', 'Nshu', + 'Ogam', 'Olck', 'Onao', 'Orkh', 'Orya', 'Osge', 'Osma', 'Ougr', + 'Palm', 'Pauc', 'Pcun', 'Pelm', 'Perm', 'Phag', 'Phli', 'Phlp', 'Phlv', 'Phnx', 'Plrd', 'Piqd', 'Prti', 'Psin', + 'Qaaa', 'Qaab', 'Qaac', 'Qaad', 'Qaae', 'Qaaf', 'Qaag', 'Qaah', 'Qaai', 'Qaaj', 'Qaak', 'Qaal', 'Qaam', 'Qaan', 'Qaao', 'Qaap', 'Qaaq', 'Qaar', 'Qaas', 'Qaat', 'Qaau', 'Qaav', 'Qaaw', 'Qaax', 'Qaay', 'Qaaz', 'Qaba', 'Qabb', 'Qabc', 'Qabd', 'Qabe', 'Qabf', 'Qabg', 'Qabh', 'Qabi', 'Qabj', 'Qabk', 'Qabl', 'Qabm', 'Qabn', 'Qabo', 'Qabp', 'Qabq', 'Qabr', 'Qabs', 'Qabt', 'Qabu', 'Qabv', 'Qabw', 'Qabx', + 'Ranj', 'Rjng', 'Rohg', 'Roro', 'Runr', + 'Samr', 'Sara', 'Sarb', 'Saur', 'Sgnw', 'Shaw', 'Shrd', 'Shui', 'Sidd', 'Sidt', 'Sind', 'Sinh', 'Sogd', 'Sogo', 'Sora', 'Soyo', 'Sund', 'Sunu', 'Sylo', 'Syrc', 'Syre', 'Syrj', 'Syrn', + 'Tagb', 'Takr', 'Tale', 'Talu', 'Taml', 'Tang', 'Tavt', 'Tayo', 'Telu', 'Teng', 'Tfng', 'Tglg', 'Thaa', 'Thai', 'Tibt', 'Tirh', 'Tnsa', 'Todr', 'Tols', 'Toto', 'Tutg', + 'Ugar', + 'Vaii', 'Visp', 'Vith', + 'Wara', 'Wcho', 'Wole', + 'Xpeo', 'Xsux', + 'Yezi', 'Yiii', + 'Zanb', 'Zinh', 'Zmth', 'Zsye', 'Zsym', 'Zxxx', 'Zyyy', 'Zzzz', +]); + +export default function isISO15924(str) { + assertString(str); + return validISO15924Codes.has(str); +} + +export const ScriptCodes = validISO15924Codes; diff --git a/src/lib/isISO31661Alpha2.js b/src/lib/isISO31661Alpha2.js index af0faf2b6..e67bb1e15 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,9 +27,11 @@ const validISO31661Alpha2CountriesCodes = [ 'WF', 'WS', 'YE', 'YT', 'ZA', 'ZM', 'ZW', -]; +]); export default function isISO31661Alpha2(str) { assertString(str); - return validISO31661Alpha2CountriesCodes.includes(str.toUpperCase()); + return validISO31661Alpha2CountriesCodes.has(str.toUpperCase()); } + +export const CountryCodes = validISO31661Alpha2CountriesCodes; diff --git a/src/lib/isISO31661Alpha3.js b/src/lib/isISO31661Alpha3.js new file mode 100644 index 000000000..34e552cdd --- /dev/null +++ b/src/lib/isISO31661Alpha3.js @@ -0,0 +1,26 @@ +import assertString from './util/assertString'; + +// from https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3 +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', + 'COL', 'COM', 'COG', 'COD', 'COK', 'CRI', 'CIV', 'HRV', 'CUB', 'CUW', 'CYP', 'CZE', 'DNK', 'DJI', 'DMA', 'DOM', + 'ECU', 'EGY', 'SLV', 'GNQ', 'ERI', 'EST', 'ETH', 'FLK', 'FRO', 'FJI', 'FIN', 'FRA', 'GUF', 'PYF', 'ATF', 'GAB', + 'GMB', 'GEO', 'DEU', 'GHA', 'GIB', 'GRC', 'GRL', 'GRD', 'GLP', 'GUM', 'GTM', 'GGY', 'GIN', 'GNB', 'GUY', 'HTI', + 'HMD', 'VAT', 'HND', 'HKG', 'HUN', 'ISL', 'IND', 'IDN', 'IRN', 'IRQ', 'IRL', 'IMN', 'ISR', 'ITA', 'JAM', 'JPN', + 'JEY', 'JOR', 'KAZ', 'KEN', 'KIR', 'PRK', 'KOR', 'KWT', 'KGZ', 'LAO', 'LVA', 'LBN', 'LSO', 'LBR', 'LBY', 'LIE', + 'LTU', 'LUX', 'MAC', 'MKD', 'MDG', 'MWI', 'MYS', 'MDV', 'MLI', 'MLT', 'MHL', 'MTQ', 'MRT', 'MUS', 'MYT', 'MEX', + 'FSM', 'MDA', 'MCO', 'MNG', 'MNE', 'MSR', 'MAR', 'MOZ', 'MMR', 'NAM', 'NRU', 'NPL', 'NLD', 'NCL', 'NZL', 'NIC', + 'NER', 'NGA', 'NIU', 'NFK', 'MNP', 'NOR', 'OMN', 'PAK', 'PLW', 'PSE', 'PAN', 'PNG', 'PRY', 'PER', 'PHL', 'PCN', + 'POL', 'PRT', 'PRI', 'QAT', 'REU', 'ROU', 'RUS', 'RWA', 'BLM', 'SHN', 'KNA', 'LCA', 'MAF', 'SPM', 'VCT', 'WSM', + 'SMR', 'STP', 'SAU', 'SEN', 'SRB', 'SYC', 'SLE', 'SGP', 'SXM', 'SVK', 'SVN', 'SLB', 'SOM', 'ZAF', 'SGS', 'SSD', + '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 validISO31661Alpha3CountriesCodes.has(str.toUpperCase()); +} diff --git a/src/lib/isISO31661Numeric.js b/src/lib/isISO31661Numeric.js new file mode 100644 index 000000000..8b32a8d79 --- /dev/null +++ b/src/lib/isISO31661Numeric.js @@ -0,0 +1,26 @@ +import assertString from './util/assertString'; + +// from https://en.wikipedia.org/wiki/ISO_3166-1_numeric +const validISO31661NumericCountriesCodes = new Set([ + '004', '008', '010', '012', '016', '020', '024', '028', '031', '032', '036', '040', '044', '048', '050', '051', + '052', '056', '060', '064', '068', '070', '072', '074', '076', '084', '086', '090', '092', '096', '100', '104', + '108', '112', '116', '120', '124', '132', '136', '140', '144', '148', '152', '156', '158', '162', '166', '170', + '174', '175', '178', '180', '184', '188', '191', '192', '196', '203', '204', '208', '212', '214', '218', '222', + '226', '231', '232', '233', '234', '238', '239', '242', '246', '248', '250', '254', '258', '260', '262', '266', + '268', '270', '275', '276', '288', '292', '296', '300', '304', '308', '312', '316', '320', '324', '328', '332', + '334', '336', '340', '344', '348', '352', '356', '360', '364', '368', '372', '376', '380', '384', '388', '392', + '398', '400', '404', '408', '410', '414', '417', '418', '422', '426', '428', '430', '434', '438', '440', '442', + '446', '450', '454', '458', '462', '466', '470', '474', '478', '480', '484', '492', '496', '498', '499', '500', + '504', '508', '512', '516', '520', '524', '528', '531', '533', '534', '535', '540', '548', '554', '558', '562', + '566', '570', '574', '578', '580', '581', '583', '584', '585', '586', '591', '598', '600', '604', '608', '612', + '616', '620', '624', '626', '630', '634', '638', '642', '643', '646', '652', '654', '659', '660', '662', '663', + '666', '670', '674', '678', '682', '686', '688', '690', '694', '702', '703', '704', '705', '706', '710', '716', + '724', '728', '729', '732', '740', '744', '748', '752', '756', '760', '762', '764', '768', '772', '776', '780', + '784', '788', '792', '795', '796', '798', '800', '804', '807', '818', '826', '831', '832', '833', '834', '840', + '850', '854', '858', '860', '862', '876', '882', '887', '894', +]); + +export default function isISO31661Numeric(str) { + assertString(str); + return validISO31661NumericCountriesCodes.has(str); +} diff --git a/src/lib/isISO4217.js b/src/lib/isISO4217.js new file mode 100644 index 000000000..bbca596a9 --- /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', 'CUP', 'CVE', 'CZK', + 'DJF', 'DKK', 'DOP', 'DZD', + 'EGP', 'ERN', 'ETB', 'EUR', + 'FJD', 'FKP', + 'GBP', 'GEL', 'GHS', 'GIP', 'GMD', 'GNF', 'GTQ', 'GYD', + 'HKD', 'HNL', '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', 'SLE', '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', + 'VED', '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/isISO6346.js b/src/lib/isISO6346.js new file mode 100644 index 000000000..2c28c1123 --- /dev/null +++ b/src/lib/isISO6346.js @@ -0,0 +1,38 @@ +import assertString from './util/assertString'; + +// https://en.wikipedia.org/wiki/ISO_6346 +// according to ISO6346 standard, checksum digit is mandatory for freight container but recommended +// for other container types (J and Z) +const isISO6346Str = /^[A-Z]{3}(U[0-9]{7})|([J,Z][0-9]{6,7})$/; +const isDigit = /^[0-9]$/; + +export function isISO6346(str) { + assertString(str); + + str = str.toUpperCase(); + + if (!isISO6346Str.test(str)) return false; + + if (str.length === 11) { + let sum = 0; + for (let i = 0; i < str.length - 1; i++) { + if (!isDigit.test(str[i])) { + let convertedCode; + const letterCode = str.charCodeAt(i) - 55; + if (letterCode < 11) convertedCode = letterCode; + else if (letterCode >= 11 && letterCode <= 20) convertedCode = 12 + (letterCode % 11); + else if (letterCode >= 21 && letterCode <= 30) convertedCode = 23 + (letterCode % 21); + else convertedCode = 34 + (letterCode % 31); + sum += convertedCode * (2 ** i); + } else sum += str[i] * (2 ** i); + } + + let checkSumDigit = sum % 11; + if (checkSumDigit === 10) checkSumDigit = 0; + return Number(str[str.length - 1]) === checkSumDigit; + } + + return true; +} + +export const isFreightContainerID = isISO6346; diff --git a/src/lib/isISO6391.js b/src/lib/isISO6391.js new file mode 100644 index 000000000..eaa01c5b4 --- /dev/null +++ b/src/lib/isISO6391.js @@ -0,0 +1,35 @@ +import assertString from './util/assertString'; + +const isISO6391Set = new Set([ + 'aa', 'ab', 'ae', 'af', 'ak', 'am', 'an', 'ar', 'as', 'av', 'ay', 'az', 'az', + 'ba', 'be', 'bg', 'bh', 'bi', 'bm', 'bn', 'bo', 'br', 'bs', + 'ca', 'ce', 'ch', 'co', 'cr', 'cs', 'cu', 'cv', 'cy', + 'da', 'de', 'dv', 'dz', + 'ee', 'el', 'en', 'eo', 'es', 'et', 'eu', + 'fa', 'ff', 'fi', 'fj', 'fo', 'fr', 'fy', + 'ga', 'gd', 'gl', 'gn', 'gu', 'gv', + 'ha', 'he', 'hi', 'ho', 'hr', 'ht', 'hu', 'hy', 'hz', + 'ia', 'id', 'ie', 'ig', 'ii', 'ik', 'io', 'is', 'it', 'iu', + 'ja', 'jv', + 'ka', 'kg', 'ki', 'kj', 'kk', 'kl', 'km', 'kn', 'ko', 'kr', 'ks', 'ku', 'kv', 'kw', 'ky', + 'la', 'lb', 'lg', 'li', 'ln', 'lo', 'lt', 'lu', 'lv', + 'mg', 'mh', 'mi', 'mk', 'ml', 'mn', 'mr', 'ms', 'mt', 'my', + 'na', 'nb', 'nd', 'ne', 'ng', 'nl', 'nn', 'no', 'nr', 'nv', 'ny', + 'oc', 'oj', 'om', 'or', 'os', + 'pa', 'pi', 'pl', 'ps', 'pt', + 'qu', + 'rm', 'rn', 'ro', 'ru', 'rw', + 'sa', 'sc', 'sd', 'se', 'sg', 'si', 'sk', 'sl', 'sm', 'sn', 'so', 'sq', 'sr', 'ss', 'st', 'su', 'sv', 'sw', + 'ta', 'te', 'tg', 'th', 'ti', 'tk', 'tl', 'tn', 'to', 'tr', 'ts', 'tt', 'tw', 'ty', + 'ug', 'uk', 'ur', 'uz', + 've', 'vi', 'vo', + 'wa', 'wo', + 'xh', + 'yi', 'yo', + 'za', 'zh', 'zu', +]); + +export default function isISO6391(str) { + assertString(str); + return isISO6391Set.has(str); +} diff --git a/src/lib/isISO8601.js b/src/lib/isISO8601.js index 7b24e5fe0..1f797347d 100644 --- a/src/lib/isISO8601.js +++ b/src/lib/isISO8601.js @@ -2,10 +2,43 @@ import assertString from './util/assertString'; /* eslint-disable max-len */ // from http://goo.gl/0ejHHW -const 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)?)?)?)?$/; +const 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-3])(-?[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)?)?)?)?$/; +// same as above, except with a strict 'T' separator between date and time +const iso8601StrictSeparator = /^([\+-]?\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-3])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T]((([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)?)?)?)?$/; /* eslint-enable max-len */ +const isValidDate = (str) => { + // str must have passed the ISO8601 check + // this check is meant to catch invalid dates + // like 2009-02-31 + // first check for ordinal dates + const ordinalMatch = str.match(/^(\d{4})-?(\d{3})([ T]{1}\.*|$)/); + if (ordinalMatch) { + const oYear = Number(ordinalMatch[1]); + const oDay = Number(ordinalMatch[2]); + // if is leap year + if ((oYear % 4 === 0 && oYear % 100 !== 0) || oYear % 400 === 0) return oDay <= 366; + return oDay <= 365; + } + const match = str.match(/(\d{4})-?(\d{0,2})-?(\d*)/).map(Number); + const year = match[1]; + const month = match[2]; + const day = match[3]; + const monthString = month ? `0${month}`.slice(-2) : month; + const dayString = day ? `0${day}`.slice(-2) : day; -export default function isISO8601(str) { + // create a date object and compare + const d = new Date(`${year}-${monthString || '01'}-${dayString || '01'}`); + if (month && day) { + return d.getUTCFullYear() === year + && (d.getUTCMonth() + 1) === month + && d.getUTCDate() === day; + } + return true; +}; + +export default function isISO8601(str, options = {}) { assertString(str); - return iso8601.test(str); + const check = options.strictSeparator ? iso8601StrictSeparator.test(str) : iso8601.test(str); + if (check && options.strict) return isValidDate(str); + return check; } diff --git a/src/lib/isISSN.js b/src/lib/isISSN.js index 967233e43..42b9ad7eb 100644 --- a/src/lib/isISSN.js +++ b/src/lib/isISSN.js @@ -10,13 +10,11 @@ export default function isISSN(str, options = {}) { if (!testIssn.test(str)) { return false; } - const issnDigits = str.replace('-', ''); - let position = 8; + const digits = str.replace('-', '').toUpperCase(); let checksum = 0; - for (const digit of issnDigits) { - const digitValue = digit.toUpperCase() === 'X' ? 10 : +digit; - checksum += digitValue * position; - --position; + for (let i = 0; i < digits.length; i++) { + const digit = digits[i]; + checksum += (digit === 'X' ? 10 : +digit) * (8 - i); } return checksum % 11 === 0; } diff --git a/src/lib/isIdentityCard.js b/src/lib/isIdentityCard.js new file mode 100644 index 000000000..fc37d4a29 --- /dev/null +++ b/src/lib/isIdentityCard.js @@ -0,0 +1,455 @@ +import assertString from './util/assertString'; +import includes from './util/includesArray'; +import isInt from './isInt'; + +const validators = { + PL: (str) => { + assertString(str); + + const weightOfDigits = { + 1: 1, + 2: 3, + 3: 7, + 4: 9, + 5: 1, + 6: 3, + 7: 7, + 8: 9, + 9: 1, + 10: 3, + 11: 0, + }; + + if (str != null && str.length === 11 && isInt(str, { allow_leading_zeroes: true })) { + const digits = str.split('').slice(0, -1); + const sum = digits.reduce((acc, digit, index) => + acc + (Number(digit) * weightOfDigits[index + 1]), 0); + + const modulo = sum % 10; + const lastDigit = Number(str.charAt(str.length - 1)); + + if ((modulo === 0 && lastDigit === 0) || lastDigit === 10 - modulo) { + return true; + } + } + + return false; + }, + ES: (str) => { + assertString(str); + + const DNI = /^[0-9X-Z][0-9]{7}[TRWAGMYFPDXBNJZSQVHLCKE]$/; + + const charsValue = { + X: 0, + Y: 1, + Z: 2, + }; + + const controlDigits = [ + 'T', 'R', 'W', 'A', 'G', 'M', 'Y', 'F', 'P', 'D', 'X', 'B', + 'N', 'J', 'Z', 'S', 'Q', 'V', 'H', 'L', 'C', 'K', 'E', + ]; + + // sanitize user input + const sanitized = str.trim().toUpperCase(); + + // validate the data structure + if (!DNI.test(sanitized)) { + return false; + } + + // validate the control digit + const number = sanitized.slice(0, -1).replace(/[X,Y,Z]/g, char => charsValue[char]); + + return sanitized.endsWith(controlDigits[number % 23]); + }, + FI: (str) => { + // https://dvv.fi/en/personal-identity-code#:~:text=control%20character%20for%20a-,personal,-identity%20code%20calculated + assertString(str); + + if (str.length !== 11) { + return false; + } + + if (!str.match(/^\d{6}[\-A\+]\d{3}[0-9ABCDEFHJKLMNPRSTUVWXY]{1}$/)) { + return false; + } + + const checkDigits = '0123456789ABCDEFHJKLMNPRSTUVWXY'; + + const idAsNumber = (parseInt(str.slice(0, 6), 10) * 1000) + parseInt(str.slice(7, 10), 10); + const remainder = idAsNumber % 31; + const checkDigit = checkDigits[remainder]; + + return checkDigit === str.slice(10, 11); + }, + IN: (str) => { + const DNI = /^[1-9]\d{3}\s?\d{4}\s?\d{4}$/; + + // multiplication table + const d = [ + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [1, 2, 3, 4, 0, 6, 7, 8, 9, 5], + [2, 3, 4, 0, 1, 7, 8, 9, 5, 6], + [3, 4, 0, 1, 2, 8, 9, 5, 6, 7], + [4, 0, 1, 2, 3, 9, 5, 6, 7, 8], + [5, 9, 8, 7, 6, 0, 4, 3, 2, 1], + [6, 5, 9, 8, 7, 1, 0, 4, 3, 2], + [7, 6, 5, 9, 8, 2, 1, 0, 4, 3], + [8, 7, 6, 5, 9, 3, 2, 1, 0, 4], + [9, 8, 7, 6, 5, 4, 3, 2, 1, 0], + ]; + + // permutation table + const p = [ + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [1, 5, 7, 6, 2, 8, 3, 0, 9, 4], + [5, 8, 0, 3, 7, 9, 6, 1, 4, 2], + [8, 9, 1, 6, 0, 4, 3, 5, 2, 7], + [9, 4, 5, 3, 1, 2, 6, 8, 7, 0], + [4, 2, 8, 6, 5, 7, 3, 9, 0, 1], + [2, 7, 9, 3, 8, 0, 6, 4, 1, 5], + [7, 0, 4, 6, 9, 1, 3, 2, 5, 8], + ]; + + // sanitize user input + const sanitized = str.trim(); + + // validate the data structure + if (!DNI.test(sanitized)) { + return false; + } + let c = 0; + let invertedArray = sanitized.replace(/\s/g, '').split('').map(Number).reverse(); + + invertedArray.forEach((val, i) => { + c = d[c][p[(i % 8)][val]]; + }); + + return c === 0; + }, + IR: (str) => { + if (!str.match(/^\d{10}$/)) return false; + str = (`0000${str}`).slice(str.length - 6); + + if (parseInt(str.slice(3, 9), 10) === 0) return false; + + const lastNumber = parseInt(str.slice(9, 10), 10); + let sum = 0; + + for (let i = 0; i < 9; i++) { + sum += parseInt(str.slice(i, i + 1), 10) * (10 - i); + } + + sum %= 11; + + return ( + (sum < 2 && lastNumber === sum) || (sum >= 2 && lastNumber === 11 - sum) + ); + }, + IT: function IT(str) { + if (str.length !== 9) return false; + if (str === 'CA00000AA') return false; // https://it.wikipedia.org/wiki/Carta_d%27identit%C3%A0_elettronica_italiana + return str.search(/C[A-Z]\d{5}[A-Z]{2}/is) > -1; + }, + NO: (str) => { + const sanitized = str.trim(); + if (isNaN(Number(sanitized))) return false; + if (sanitized.length !== 11) return false; + if (sanitized === '00000000000') return false; + + // https://no.wikipedia.org/wiki/F%C3%B8dselsnummer + const f = sanitized.split('').map(Number); + let k1 = (11 - (((3 * f[0]) + (7 * f[1]) + (6 * f[2]) + + (1 * f[3]) + (8 * f[4]) + (9 * f[5]) + (4 * f[6]) + + (5 * f[7]) + (2 * f[8])) % 11)) % 11; + let k2 = (11 - (((5 * f[0]) + (4 * f[1]) + (3 * f[2]) + + (2 * f[3]) + (7 * f[4]) + (6 * f[5]) + (5 * f[6]) + + (4 * f[7]) + (3 * f[8]) + (2 * k1)) % 11)) % 11; + + if (k1 !== f[9] || k2 !== f[10]) return false; + return true; + }, + TH: (str) => { + if (!str.match(/^[1-8]\d{12}$/)) return false; + + // validate check digit + let sum = 0; + for (let i = 0; i < 12; i++) { + sum += parseInt(str[i], 10) * (13 - i); + } + return str[12] === ((11 - (sum % 11)) % 10).toString(); + }, + LK: (str) => { + const old_nic = /^[1-9]\d{8}[vx]$/i; + const new_nic = /^[1-9]\d{11}$/i; + + if (str.length === 10 && old_nic.test(str)) return true; + else if (str.length === 12 && new_nic.test(str)) return true; + return false; + }, + 'he-IL': (str) => { + const DNI = /^\d{9}$/; + + // sanitize user input + const sanitized = str.trim(); + + // validate the data structure + if (!DNI.test(sanitized)) { + return false; + } + + const id = sanitized; + + let sum = 0, + incNum; + for (let i = 0; i < id.length; i++) { + incNum = Number(id[i]) * ((i % 2) + 1); // Multiply number by 1 or 2 + sum += incNum > 9 ? incNum - 9 : incNum; // Sum the digits up and add to total + } + return sum % 10 === 0; + }, + 'ar-LY': (str) => { + // Libya National Identity Number NIN is 12 digits, the first digit is either 1 or 2 + const NIN = /^(1|2)\d{11}$/; + + // sanitize user input + const sanitized = str.trim(); + + // validate the data structure + if (!NIN.test(sanitized)) { + return false; + } + return true; + }, + 'ar-TN': (str) => { + const DNI = /^\d{8}$/; + + // sanitize user input + const sanitized = str.trim(); + + // validate the data structure + if (!DNI.test(sanitized)) { + return false; + } + return true; + }, + 'zh-CN': (str) => { + const provincesAndCities = [ + '11', // 北京 + '12', // 天津 + '13', // 河北 + '14', // 山西 + '15', // 内蒙古 + '21', // 辽宁 + '22', // 吉林 + '23', // 黑龙江 + '31', // 上海 + '32', // 江苏 + '33', // 浙江 + '34', // 安徽 + '35', // 福建 + '36', // 江西 + '37', // 山东 + '41', // 河南 + '42', // 湖北 + '43', // 湖南 + '44', // 广东 + '45', // 广西 + '46', // 海南 + '50', // 重庆 + '51', // 四川 + '52', // 贵州 + '53', // 云南 + '54', // 西藏 + '61', // 陕西 + '62', // 甘肃 + '63', // 青海 + '64', // 宁夏 + '65', // 新疆 + '71', // 台湾 + '81', // 香港 + '82', // 澳门 + '91', // 国外 + ]; + + const powers = ['7', '9', '10', '5', '8', '4', '2', '1', '6', '3', '7', '9', '10', '5', '8', '4', '2']; + + const parityBit = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2']; + + const checkAddressCode = addressCode => includes(provincesAndCities, addressCode); + + const checkBirthDayCode = (birDayCode) => { + const yyyy = parseInt(birDayCode.substring(0, 4), 10); + const mm = parseInt(birDayCode.substring(4, 6), 10); + const dd = parseInt(birDayCode.substring(6), 10); + const xdata = new Date(yyyy, mm - 1, dd); + if (xdata > new Date()) { + return false; + // eslint-disable-next-line max-len + } else if ((xdata.getFullYear() === yyyy) && (xdata.getMonth() === mm - 1) && (xdata.getDate() === dd)) { + return true; + } + return false; + }; + + const getParityBit = (idCardNo) => { + let id17 = idCardNo.substring(0, 17); + + let power = 0; + for (let i = 0; i < 17; i++) { + power += parseInt(id17.charAt(i), 10) * parseInt(powers[i], 10); + } + + let mod = power % 11; + return parityBit[mod]; + }; + + const checkParityBit = idCardNo => getParityBit(idCardNo) === idCardNo.charAt(17).toUpperCase(); + + + const check15IdCardNo = (idCardNo) => { + let check = /^[1-9]\d{7}((0[1-9])|(1[0-2]))((0[1-9])|([1-2][0-9])|(3[0-1]))\d{3}$/.test(idCardNo); + if (!check) return false; + let addressCode = idCardNo.substring(0, 2); + check = checkAddressCode(addressCode); + if (!check) return false; + let birDayCode = `19${idCardNo.substring(6, 12)}`; + check = checkBirthDayCode(birDayCode); + if (!check) return false; + return true; + }; + + const check18IdCardNo = (idCardNo) => { + let check = /^[1-9]\d{5}[1-9]\d{3}((0[1-9])|(1[0-2]))((0[1-9])|([1-2][0-9])|(3[0-1]))\d{3}(\d|x|X)$/.test(idCardNo); + if (!check) return false; + let addressCode = idCardNo.substring(0, 2); + check = checkAddressCode(addressCode); + if (!check) return false; + let birDayCode = idCardNo.substring(6, 14); + check = checkBirthDayCode(birDayCode); + if (!check) return false; + return checkParityBit(idCardNo); + }; + + const checkIdCardNo = (idCardNo) => { + let check = /^\d{15}|(\d{17}(\d|x|X))$/.test(idCardNo); + if (!check) return false; + if (idCardNo.length === 15) { + return check15IdCardNo(idCardNo); + } + return check18IdCardNo(idCardNo); + }; + return checkIdCardNo(str); + }, + 'zh-HK': (str) => { + // sanitize user input + str = str.trim(); + + // HKID number starts with 1 or 2 letters, followed by 6 digits, + // then a checksum contained in square / round brackets or nothing + const regexHKID = /^[A-Z]{1,2}[0-9]{6}((\([0-9A]\))|(\[[0-9A]\])|([0-9A]))$/; + const regexIsDigit = /^[0-9]$/; + + // convert the user input to all uppercase and apply regex + str = str.toUpperCase(); + if (!regexHKID.test(str)) return false; + str = str.replace(/\[|\]|\(|\)/g, ''); + + if (str.length === 8) str = `3${str}`; + let checkSumVal = 0; + for (let i = 0; i <= 7; i++) { + let convertedChar; + if (!regexIsDigit.test(str[i])) convertedChar = (str[i].charCodeAt(0) - 55) % 11; + else convertedChar = str[i]; + checkSumVal += (convertedChar * (9 - i)); + } + checkSumVal %= 11; + + let checkSumConverted; + if (checkSumVal === 0) checkSumConverted = '0'; + else if (checkSumVal === 1) checkSumConverted = 'A'; + else checkSumConverted = String(11 - checkSumVal); + if (checkSumConverted === str[str.length - 1]) return true; + return false; + }, + 'zh-TW': (str) => { + const ALPHABET_CODES = { + A: 10, + B: 11, + C: 12, + D: 13, + E: 14, + F: 15, + G: 16, + H: 17, + I: 34, + J: 18, + K: 19, + L: 20, + M: 21, + N: 22, + O: 35, + P: 23, + Q: 24, + R: 25, + S: 26, + T: 27, + U: 28, + V: 29, + W: 32, + X: 30, + Y: 31, + Z: 33, + }; + + const sanitized = str.trim().toUpperCase(); + + if (!/^[A-Z][0-9]{9}$/.test(sanitized)) return false; + + return Array.from(sanitized).reduce((sum, number, index) => { + if (index === 0) { + const code = ALPHABET_CODES[number]; + + return ((code % 10) * 9) + Math.floor(code / 10); + } + + if (index === 9) { + return ((10 - (sum % 10)) - Number(number)) % 10 === 0; + } + + return sum + (Number(number) * (9 - index)); + }, 0); + }, + PK: (str) => { + // Pakistani National Identity Number CNIC is 13 digits + const CNIC = /^[1-7][0-9]{4}-[0-9]{7}-[1-9]$/; + + // sanitize user input + const sanitized = str.trim(); + + // validate the data structure + return CNIC.test(sanitized); + }, +}; + +export default function isIdentityCard(str, locale) { + assertString(str); + if (locale in validators) { + return validators[locale](str); + } else if (locale === 'any') { + for (const key in validators) { + // https://github.com/gotwarlost/istanbul/blob/master/ignoring-code-for-coverage.md#ignoring-code-for-coverage-purposes + // istanbul ignore else + if (validators.hasOwnProperty(key)) { + const validator = validators[key]; + if (validator(str)) { + return true; + } + } + } + return false; + } + throw new Error(`Invalid locale '${locale}'`); +} diff --git a/src/lib/isIn.js b/src/lib/isIn.js index 2b00355ef..e7f15804f 100644 --- a/src/lib/isIn.js +++ b/src/lib/isIn.js @@ -7,6 +7,8 @@ export default function isIn(str, options) { if (Object.prototype.toString.call(options) === '[object Array]') { const array = []; for (i in options) { + // https://github.com/gotwarlost/istanbul/blob/master/ignoring-code-for-coverage.md#ignoring-code-for-coverage-purposes + // istanbul ignore else if ({}.hasOwnProperty.call(options, i)) { array[i] = toString(options[i]); } diff --git a/src/lib/isInt.js b/src/lib/isInt.js index 8047a6969..d5616089a 100644 --- a/src/lib/isInt.js +++ b/src/lib/isInt.js @@ -1,4 +1,5 @@ import assertString from './util/assertString'; +import isNullOrUndefined from './util/nullUndefinedCheck'; const int = /^(?:[-+]?(?:0|[1-9][0-9]*))$/; const intLeadingZeroes = /^[-+]?[0-9]+$/; @@ -9,16 +10,13 @@ export default function isInt(str, options) { // Get the regex to use for testing, based on whether // leading zeroes are allowed or not. - let regex = ( - options.hasOwnProperty('allow_leading_zeroes') && !options.allow_leading_zeroes ? - int : intLeadingZeroes - ); + const regex = options.allow_leading_zeroes === false ? int : intLeadingZeroes; // Check min/max/lt/gt - let minCheckPassed = (!options.hasOwnProperty('min') || str >= options.min); - let maxCheckPassed = (!options.hasOwnProperty('max') || str <= options.max); - let ltCheckPassed = (!options.hasOwnProperty('lt') || str < options.lt); - let gtCheckPassed = (!options.hasOwnProperty('gt') || str > options.gt); + let minCheckPassed = (!options.hasOwnProperty('min') || isNullOrUndefined(options.min) || str >= options.min); + let maxCheckPassed = (!options.hasOwnProperty('max') || isNullOrUndefined(options.max) || str <= options.max); + let ltCheckPassed = (!options.hasOwnProperty('lt') || isNullOrUndefined(options.lt) || str < options.lt); + let gtCheckPassed = (!options.hasOwnProperty('gt') || isNullOrUndefined(options.gt) || str > options.gt); return regex.test(str) && minCheckPassed && maxCheckPassed && ltCheckPassed && gtCheckPassed; } diff --git a/src/lib/isJSON.js b/src/lib/isJSON.js index ac434839e..5c51dd31c 100644 --- a/src/lib/isJSON.js +++ b/src/lib/isJSON.js @@ -1,10 +1,22 @@ import assertString from './util/assertString'; +import includes from './util/includesArray'; +import merge from './util/merge'; -export default function isJSON(str) { +const default_json_options = { + allow_primitives: false, +}; + +export default function isJSON(str, options) { assertString(str); try { + options = merge(options, default_json_options); + let primitives = []; + if (options.allow_primitives) { + primitives = [null, false, true]; + } + const obj = JSON.parse(str); - return !!obj && typeof obj === 'object'; + return includes(primitives, obj) || (!!obj && typeof obj === 'object'); } catch (e) { /* ignore */ } return false; } diff --git a/src/lib/isJWT.js b/src/lib/isJWT.js new file mode 100644 index 000000000..1d0ade5ee --- /dev/null +++ b/src/lib/isJWT.js @@ -0,0 +1,15 @@ +import assertString from './util/assertString'; +import isBase64 from './isBase64'; + +export default function isJWT(str) { + assertString(str); + + const dotSplit = str.split('.'); + const len = dotSplit.length; + + if (len !== 3) { + return false; + } + + return dotSplit.reduce((acc, currElem) => acc && isBase64(currElem, { urlSafe: true }), true); +} diff --git a/src/lib/isLatLong.js b/src/lib/isLatLong.js index ec401575e..c4e622753 100644 --- a/src/lib/isLatLong.js +++ b/src/lib/isLatLong.js @@ -1,11 +1,28 @@ import assertString from './util/assertString'; +import merge from './util/merge'; +import includes from './util/includesString'; const lat = /^\(?[+-]?(90(\.0+)?|[1-8]?\d(\.\d+)?)$/; const long = /^\s?[+-]?(180(\.0+)?|1[0-7]\d(\.\d+)?|\d{1,2}(\.\d+)?)\)?$/; -export default function (str) { +const latDMS = /^(([1-8]?\d)\D+([1-5]?\d|60)\D+([1-5]?\d|60)(\.\d+)?|90\D+0\D+0)\D+[NSns]?$/i; +const longDMS = /^\s*([1-7]?\d{1,2}\D+([1-5]?\d|60)\D+([1-5]?\d|60)(\.\d+)?|180\D+0\D+0)\D+[EWew]?$/i; + +const defaultLatLongOptions = { + checkDMS: false, +}; + +export default function isLatLong(str, options) { assertString(str); - if (!str.includes(',')) return false; + options = merge(options, defaultLatLongOptions); + + if (!includes(str, ',')) return false; const pair = str.split(','); + if ((pair[0].startsWith('(') && !pair[1].endsWith(')')) + || (pair[1].endsWith(')') && !pair[0].startsWith('('))) return false; + + if (options.checkDMS) { + return latDMS.test(pair[0]) && longDMS.test(pair[1]); + } return lat.test(pair[0]) && long.test(pair[1]); } diff --git a/src/lib/isLength.js b/src/lib/isLength.js index 67e1ece11..56d8e3fbf 100644 --- a/src/lib/isLength.js +++ b/src/lib/isLength.js @@ -5,14 +5,23 @@ export default function isLength(str, options) { assertString(str); let min; let max; + if (typeof (options) === 'object') { min = options.min || 0; max = options.max; } else { // backwards compatibility: isLength(str, min [, max]) - min = arguments[1]; + min = arguments[1] || 0; max = arguments[2]; } + + const presentationSequences = str.match(/[^\uFE0F\uFE0E][\uFE0F\uFE0E]/g) || []; const surrogatePairs = str.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g) || []; - const len = str.length - surrogatePairs.length; - return len >= min && (typeof max === 'undefined' || len <= max); + const len = str.length - presentationSequences.length - surrogatePairs.length; + const isInsideRange = len >= min && (typeof max === 'undefined' || len <= max); + + if (isInsideRange && Array.isArray(options?.discreteLengths)) { + return options.discreteLengths.some(discreteLen => discreteLen === len); + } + + return isInsideRange; } diff --git a/src/lib/isLicensePlate.js b/src/lib/isLicensePlate.js new file mode 100644 index 000000000..54d80635f --- /dev/null +++ b/src/lib/isLicensePlate.js @@ -0,0 +1,40 @@ +import assertString from './util/assertString'; + +const validators = { + 'cs-CZ': str => + /^(([ABCDEFHIJKLMNPRSTUVXYZ]|[0-9])-?){5,8}$/.test(str), + 'de-DE': str => + /^((A|AA|AB|AC|AE|AH|AK|AM|AN|AÖ|AP|AS|AT|AU|AW|AZ|B|BA|BB|BC|BE|BF|BH|BI|BK|BL|BM|BN|BO|BÖ|BS|BT|BZ|C|CA|CB|CE|CO|CR|CW|D|DA|DD|DE|DH|DI|DL|DM|DN|DO|DU|DW|DZ|E|EA|EB|ED|EE|EF|EG|EH|EI|EL|EM|EN|ER|ES|EU|EW|F|FB|FD|FF|FG|FI|FL|FN|FO|FR|FS|FT|FÜ|FW|FZ|G|GA|GC|GD|GE|GF|GG|GI|GK|GL|GM|GN|GÖ|GP|GR|GS|GT|GÜ|GV|GW|GZ|H|HA|HB|HC|HD|HE|HF|HG|HH|HI|HK|HL|HM|HN|HO|HP|HR|HS|HU|HV|HX|HY|HZ|IK|IL|IN|IZ|J|JE|JL|K|KA|KB|KC|KE|KF|KG|KH|KI|KK|KL|KM|KN|KO|KR|KS|KT|KU|KW|KY|L|LA|LB|LC|LD|LF|LG|LH|LI|LL|LM|LN|LÖ|LP|LR|LU|M|MA|MB|MC|MD|ME|MG|MH|MI|MK|ML|MM|MN|MO|MQ|MR|MS|MÜ|MW|MY|MZ|N|NB|ND|NE|NF|NH|NI|NK|NM|NÖ|NP|NR|NT|NU|NW|NY|NZ|OA|OB|OC|OD|OE|OF|OG|OH|OK|OL|OP|OS|OZ|P|PA|PB|PE|PF|PI|PL|PM|PN|PR|PS|PW|PZ|R|RA|RC|RD|RE|RG|RH|RI|RL|RM|RN|RO|RP|RS|RT|RU|RV|RW|RZ|S|SB|SC|SE|SG|SI|SK|SL|SM|SN|SO|SP|SR|ST|SU|SW|SY|SZ|TE|TF|TG|TO|TP|TR|TS|TT|TÜ|ÜB|UE|UH|UL|UM|UN|V|VB|VG|VK|VR|VS|W|WA|WB|WE|WF|WI|WK|WL|WM|WN|WO|WR|WS|WT|WÜ|WW|WZ|Z|ZE|ZI|ZP|ZR|ZW|ZZ)[- ]?[A-Z]{1,2}[- ]?\d{1,4}|(ABG|ABI|AIB|AIC|ALF|ALZ|ANA|ANG|ANK|APD|ARN|ART|ASL|ASZ|AUR|AZE|BAD|BAR|BBG|BCH|BED|BER|BGD|BGL|BID|BIN|BIR|BIT|BIW|BKS|BLB|BLK|BNA|BOG|BOH|BOR|BOT|BRA|BRB|BRG|BRK|BRL|BRV|BSB|BSK|BTF|BÜD|BUL|BÜR|BÜS|BÜZ|CAS|CHA|CLP|CLZ|COC|COE|CUX|DAH|DAN|DAU|DBR|DEG|DEL|DGF|DIL|DIN|DIZ|DKB|DLG|DON|DUD|DÜW|EBE|EBN|EBS|ECK|EIC|EIL|EIN|EIS|EMD|EMS|ERB|ERH|ERK|ERZ|ESB|ESW|FDB|FDS|FEU|FFB|FKB|FLÖ|FOR|FRG|FRI|FRW|FTL|FÜS|GAN|GAP|GDB|GEL|GEO|GER|GHA|GHC|GLA|GMN|GNT|GOA|GOH|GRA|GRH|GRI|GRM|GRZ|GTH|GUB|GUN|GVM|HAB|HAL|HAM|HAS|HBN|HBS|HCH|HDH|HDL|HEB|HEF|HEI|HER|HET|HGN|HGW|HHM|HIG|HIP|HMÜ|HOG|HOH|HOL|HOM|HOR|HÖS|HOT|HRO|HSK|HST|HVL|HWI|IGB|ILL|JÜL|KEH|KEL|KEM|KIB|KLE|KLZ|KÖN|KÖT|KÖZ|KRU|KÜN|KUS|KYF|LAN|LAU|LBS|LBZ|LDK|LDS|LEO|LER|LEV|LIB|LIF|LIP|LÖB|LOS|LRO|LSZ|LÜN|LUP|LWL|MAB|MAI|MAK|MAL|MED|MEG|MEI|MEK|MEL|MER|MET|MGH|MGN|MHL|MIL|MKK|MOD|MOL|MON|MOS|MSE|MSH|MSP|MST|MTK|MTL|MÜB|MÜR|MYK|MZG|NAB|NAI|NAU|NDH|NEA|NEB|NEC|NEN|NES|NEW|NMB|NMS|NOH|NOL|NOM|NOR|NVP|NWM|OAL|OBB|OBG|OCH|OHA|ÖHR|OHV|OHZ|OPR|OSL|OVI|OVL|OVP|PAF|PAN|PAR|PCH|PEG|PIR|PLÖ|PRÜ|QFT|QLB|RDG|REG|REH|REI|RID|RIE|ROD|ROF|ROK|ROL|ROS|ROT|ROW|RSL|RÜD|RÜG|SAB|SAD|SAN|SAW|SBG|SBK|SCZ|SDH|SDL|SDT|SEB|SEE|SEF|SEL|SFB|SFT|SGH|SHA|SHG|SHK|SHL|SIG|SIM|SLE|SLF|SLK|SLN|SLS|SLÜ|SLZ|SMÜ|SOB|SOG|SOK|SÖM|SON|SPB|SPN|SRB|SRO|STA|STB|STD|STE|STL|SUL|SÜW|SWA|SZB|TBB|TDO|TET|TIR|TÖL|TUT|UEM|UER|UFF|USI|VAI|VEC|VER|VIB|VIE|VIT|VOH|WAF|WAK|WAN|WAR|WAT|WBS|WDA|WEL|WEN|WER|WES|WHV|WIL|WIS|WIT|WIZ|WLG|WMS|WND|WOB|WOH|WOL|WOR|WOS|WRN|WSF|WST|WSW|WTL|WTM|WUG|WÜM|WUN|WUR|WZL|ZEL|ZIG)[- ]?(([A-Z][- ]?\d{1,4})|([A-Z]{2}[- ]?\d{1,3})))[- ]?(E|H)?$/.test(str), + 'de-LI': str => /^FL[- ]?\d{1,5}[UZ]?$/.test(str), + 'en-IN': str => /^[A-Z]{2}[ -]?[0-9]{1,2}(?:[ -]?[A-Z])(?:[ -]?[A-Z]*)?[ -]?[0-9]{4}$/.test(str), + 'en-SG': str => /^[A-Z]{3}[ -]?[\d]{4}[ -]?[A-Z]{1}$/.test(str), + 'es-AR': str => /^(([A-Z]{2} ?[0-9]{3} ?[A-Z]{2})|([A-Z]{3} ?[0-9]{3}))$/.test(str), + 'fi-FI': str => /^(?=.{4,7})(([A-Z]{1,3}|[0-9]{1,3})[\s-]?([A-Z]{1,3}|[0-9]{1,5}))$/.test(str), + 'hu-HU': str => /^((((?!AAA)(([A-NPRSTVZWXY]{1})([A-PR-Z]{1})([A-HJ-NPR-Z]))|(A[ABC]I)|A[ABC]O|A[A-W]Q|BPI|BPO|UCO|UDO|XAO)-(?!000)\d{3})|(M\d{6})|((CK|DT|CD|HC|H[ABEFIKLMNPRSTVX]|MA|OT|R[A-Z]) \d{2}-\d{2})|(CD \d{3}-\d{3})|(C-(C|X) \d{4})|(X-(A|B|C) \d{4})|(([EPVZ]-\d{5}))|(S A[A-Z]{2} \d{2})|(SP \d{2}-\d{2}))$/.test(str), + 'pt-BR': str => + /^[A-Z]{3}[ -]?[0-9][A-Z][0-9]{2}|[A-Z]{3}[ -]?[0-9]{4}$/.test(str), + 'pt-PT': str => + /^(([A-Z]{2}[ -·]?[0-9]{2}[ -·]?[0-9]{2})|([0-9]{2}[ -·]?[A-Z]{2}[ -·]?[0-9]{2})|([0-9]{2}[ -·]?[0-9]{2}[ -·]?[A-Z]{2})|([A-Z]{2}[ -·]?[0-9]{2}[ -·]?[A-Z]{2}))$/.test(str), + 'sq-AL': str => + /^[A-Z]{2}[- ]?((\d{3}[- ]?(([A-Z]{2})|T))|(R[- ]?\d{3}))$/.test(str), + 'sv-SE': str => + /^[A-HJ-PR-UW-Z]{3} ?[\d]{2}[A-HJ-PR-UW-Z1-9]$|(^[A-ZÅÄÖ ]{2,7}$)/.test(str.trim()), + 'en-PK': str => /(^[A-Z]{2}((\s|-){0,1})[0-9]{3,4}((\s|-)[0-9]{2}){0,1}$)|(^[A-Z]{3}((\s|-){0,1})[0-9]{3,4}((\s|-)[0-9]{2}){0,1}$)|(^[A-Z]{4}((\s|-){0,1})[0-9]{3,4}((\s|-)[0-9]{2}){0,1}$)|(^[A-Z]((\s|-){0,1})[0-9]{4}((\s|-)[0-9]{2}){0,1}$)/.test(str.trim()), +}; + +export default function isLicensePlate(str, locale) { + assertString(str); + if (locale in validators) { + return validators[locale](str); + } else if (locale === 'any') { + for (const key in validators) { + /* eslint guard-for-in: 0 */ + const validator = validators[key]; + if (validator(str)) { + return true; + } + } + return false; + } + throw new Error(`Invalid locale '${locale}'`); +} diff --git a/src/lib/isLocale.js b/src/lib/isLocale.js new file mode 100644 index 000000000..ec84c8fce --- /dev/null +++ b/src/lib/isLocale.js @@ -0,0 +1,111 @@ +import assertString from './util/assertString'; + +/* + = 3ALPHA ; selected ISO 639 codes + *2("-" 3ALPHA) ; permanently reserved + */ +const extlang = '([A-Za-z]{3}(-[A-Za-z]{3}){0,2})'; + +/* + = 2*3ALPHA ; shortest ISO 639 code + ["-" extlang] ; sometimes followed by + ; extended language subtags + / 4ALPHA ; or reserved for future use + / 5*8ALPHA ; or registered language subtag + */ +const language = `(([a-zA-Z]{2,3}(-${extlang})?)|([a-zA-Z]{5,8}))`; + +/* + = 4ALPHA ; ISO 15924 code + */ +const script = '([A-Za-z]{4})'; + +/* + = 2ALPHA ; ISO 3166-1 code + / 3DIGIT ; UN M.49 code + */ +const region = '([A-Za-z]{2}|\\d{3})'; + +/* + = 5*8alphanum ; registered variants + / (DIGIT 3alphanum) + */ +const variant = '([A-Za-z0-9]{5,8}|(\\d[A-Z-a-z0-9]{3}))'; + +/* + = DIGIT ; 0 - 9 + / %x41-57 ; A - W + / %x59-5A ; Y - Z + / %x61-77 ; a - w + / %x79-7A ; y - z + */ +const singleton = '(\\d|[A-W]|[Y-Z]|[a-w]|[y-z])'; + +/* + = singleton 1*("-" (2*8alphanum)) + ; Single alphanumerics + ; "x" reserved for private use + */ +const extension = `(${singleton}(-[A-Za-z0-9]{2,8})+)`; + +/* + = "x" 1*("-" (1*8alphanum)) + */ +const privateuse = '(x(-[A-Za-z0-9]{1,8})+)'; + +// irregular tags do not match the 'langtag' production and would not +// otherwise be considered 'well-formed'. These tags are all valid, but +// most are deprecated in favor of more modern subtags or subtag combination + +const irregular = '((en-GB-oed)|(i-ami)|(i-bnn)|(i-default)|(i-enochian)|' + + '(i-hak)|(i-klingon)|(i-lux)|(i-mingo)|(i-navajo)|(i-pwn)|(i-tao)|' + + '(i-tay)|(i-tsu)|(sgn-BE-FR)|(sgn-BE-NL)|(sgn-CH-DE))'; + +// regular tags match the 'langtag' production, but their subtags are not +// extended language or variant subtags: their meaning is defined by +// their registration and all of these are deprecated in favor of a more +// modern subtag or sequence of subtags + +const regular = '((art-lojban)|(cel-gaulish)|(no-bok)|(no-nyn)|(zh-guoyu)|' + + '(zh-hakka)|(zh-min)|(zh-min-nan)|(zh-xiang))'; + +/* + = irregular ; non-redundant tags registered + / regular ; during the RFC 3066 era + + */ +const grandfathered = `(${irregular}|${regular})`; + +/* + RFC 5646 defines delimitation of subtags via a hyphen: + + "Subtag" refers to a specific section of a tag, delimited by a + hyphen, such as the subtags 'zh', 'Hant', and 'CN' in the tag "zh- + Hant-CN". Examples of subtags in this document are enclosed in + single quotes ('Hant') + + However, we need to add "_" to maintain the existing behaviour. + */ +const delimiter = '(-|_)'; + +/* + = language + ["-" script] + ["-" region] + *("-" variant) + *("-" extension) + ["-" privateuse] + */ +const langtag = `${language}(${delimiter}${script})?(${delimiter}${region})?(${delimiter}${variant})*(${delimiter}${extension})*(${delimiter}${privateuse})?`; + +/* + Regex implementation based on BCP RFC 5646 + Tags for Identifying Languages + https://www.rfc-editor.org/rfc/rfc5646.html + */ +const languageTagRegex = new RegExp(`(^${privateuse}$)|(^${grandfathered}$)|(^${langtag}$)`); + +export default function isLocale(str) { + assertString(str); + return languageTagRegex.test(str); +} diff --git a/src/lib/isLuhnNumber.js b/src/lib/isLuhnNumber.js new file mode 100644 index 000000000..95a066115 --- /dev/null +++ b/src/lib/isLuhnNumber.js @@ -0,0 +1,26 @@ +import assertString from './util/assertString'; + +export default function isLuhnNumber(str) { + assertString(str); + const sanitized = str.replace(/[- ]+/g, ''); + let sum = 0; + let digit; + let tmpNum; + let shouldDouble; + for (let i = sanitized.length - 1; i >= 0; i--) { + digit = sanitized.substring(i, (i + 1)); + tmpNum = parseInt(digit, 10); + if (shouldDouble) { + tmpNum *= 2; + if (tmpNum >= 10) { + sum += ((tmpNum % 10) + 1); + } else { + sum += tmpNum; + } + } else { + sum += tmpNum; + } + shouldDouble = !shouldDouble; + } + return !!((sum % 10) === 0 ? sanitized : false); +} diff --git a/src/lib/isMACAddress.js b/src/lib/isMACAddress.js index 78647bd0e..d87cd4aa7 100644 --- a/src/lib/isMACAddress.js +++ b/src/lib/isMACAddress.js @@ -1,8 +1,34 @@ import assertString from './util/assertString'; -const macAddress = /^([0-9a-fA-F][0-9a-fA-F]:){5}([0-9a-fA-F][0-9a-fA-F])$/; +const macAddress48 = /^(?:[0-9a-fA-F]{2}([-:\s]))([0-9a-fA-F]{2}\1){4}([0-9a-fA-F]{2})$/; +const macAddress48NoSeparators = /^([0-9a-fA-F]){12}$/; +const macAddress48WithDots = /^([0-9a-fA-F]{4}\.){2}([0-9a-fA-F]{4})$/; +const macAddress64 = /^(?:[0-9a-fA-F]{2}([-:\s]))([0-9a-fA-F]{2}\1){6}([0-9a-fA-F]{2})$/; +const macAddress64NoSeparators = /^([0-9a-fA-F]){16}$/; +const macAddress64WithDots = /^([0-9a-fA-F]{4}\.){3}([0-9a-fA-F]{4})$/; -export default function isMACAddress(str) { +export default function isMACAddress(str, options) { assertString(str); - return macAddress.test(str); + if (options?.eui) { + options.eui = String(options.eui); + } + /** + * @deprecated `no_colons` TODO: remove it in the next major + */ + if (options?.no_colons || options?.no_separators) { + if (options.eui === '48') { + return macAddress48NoSeparators.test(str); + } + if (options.eui === '64') { + return macAddress64NoSeparators.test(str); + } + return macAddress48NoSeparators.test(str) || macAddress64NoSeparators.test(str); + } + if (options?.eui === '48') { + return macAddress48.test(str) || macAddress48WithDots.test(str); + } + if (options?.eui === '64') { + return macAddress64.test(str) || macAddress64WithDots.test(str); + } + return isMACAddress(str, { eui: '48' }) || isMACAddress(str, { eui: '64' }); } diff --git a/src/lib/isMagnetURI.js b/src/lib/isMagnetURI.js new file mode 100644 index 000000000..e00ee3c32 --- /dev/null +++ b/src/lib/isMagnetURI.js @@ -0,0 +1,13 @@ +import assertString from './util/assertString'; + +const magnetURIComponent = /(?:^magnet:\?|[^?&]&)xt(?:\.1)?=urn:(?:(?:aich|bitprint|btih|ed2k|ed2khash|kzhash|md5|sha1|tree:tiger):[a-z0-9]{32}(?:[a-z0-9]{8})?|btmh:1220[a-z0-9]{64})(?:$|&)/i; + +export default function isMagnetURI(url) { + assertString(url); + + if (url.indexOf('magnet:?') !== 0) { + return false; + } + + return magnetURIComponent.test(url); +} diff --git a/src/lib/isMailtoURI.js b/src/lib/isMailtoURI.js new file mode 100644 index 000000000..67748a553 --- /dev/null +++ b/src/lib/isMailtoURI.js @@ -0,0 +1,67 @@ +import trim from './trim'; +import isEmail from './isEmail'; +import assertString from './util/assertString'; + +function parseMailtoQueryString(queryString) { + const allowedParams = new Set(['subject', 'body', 'cc', 'bcc']), + query = { cc: '', bcc: '' }; + let isParseFailed = false; + + const queryParams = queryString.split('&'); + + if (queryParams.length > 4) { + return false; + } + + for (const q of queryParams) { + const [key, value] = q.split('='); + + // checked for invalid and duplicated query params + if (key && !allowedParams.has(key)) { + isParseFailed = true; + break; + } + + if (value && (key === 'cc' || key === 'bcc')) { + query[key] = value; + } + + if (key) { + allowedParams.delete(key); + } + } + + return isParseFailed ? false : query; +} + +export default function isMailtoURI(url, options) { + assertString(url); + + if (url.indexOf('mailto:') !== 0) { + return false; + } + + const [to, queryString = ''] = url.replace('mailto:', '').split('?'); + + if (!to && !queryString) { + return true; + } + + const query = parseMailtoQueryString(queryString); + + if (!query) { + return false; + } + + return `${to},${query.cc},${query.bcc}` + .split(',') + .every((email) => { + email = trim(email, ' '); + + if (email) { + return isEmail(email, options); + } + + return true; + }); +} diff --git a/src/lib/isMimeType.js b/src/lib/isMimeType.js new file mode 100644 index 000000000..820fa4dc2 --- /dev/null +++ b/src/lib/isMimeType.js @@ -0,0 +1,40 @@ +import assertString from './util/assertString'; + +/* + Checks if the provided string matches to a correct Media type format (MIME type) + + This function only checks is the string format follows the + established rules by the according RFC specifications. + This function supports 'charset' in textual media types + (https://tools.ietf.org/html/rfc6657). + + This function does not check against all the media types listed + by the IANA (https://www.iana.org/assignments/media-types/media-types.xhtml) + because of lightness purposes : it would require to include + all these MIME types in this library, which would weigh it + significantly. This kind of effort maybe is not worth for the use that + this function has in this entire library. + + More information in the RFC specifications : + - https://tools.ietf.org/html/rfc2045 + - https://tools.ietf.org/html/rfc2046 + - https://tools.ietf.org/html/rfc7231#section-3.1.1.1 + - https://tools.ietf.org/html/rfc7231#section-3.1.1.5 +*/ + +// Match simple MIME types +// NB : +// Subtype length must not exceed 100 characters. +// This rule does not comply to the RFC specs (what is the max length ?). +const mimeTypeSimple = /^(application|audio|font|image|message|model|multipart|text|video)\/[a-zA-Z0-9\.\-\+_]{1,100}$/i; // eslint-disable-line max-len + +// Handle "charset" in "text/*" +const mimeTypeText = /^text\/[a-zA-Z0-9\.\-\+]{1,100};\s?charset=("[a-zA-Z0-9\.\-\+\s]{0,70}"|[a-zA-Z0-9\.\-\+]{0,70})(\s?\([a-zA-Z0-9\.\-\+\s]{1,20}\))?$/i; // eslint-disable-line max-len + +// Handle "boundary" in "multipart/*" +const mimeTypeMultipart = /^multipart\/[a-zA-Z0-9\.\-\+]{1,100}(;\s?(boundary|charset)=("[a-zA-Z0-9\.\-\+\s]{0,70}"|[a-zA-Z0-9\.\-\+]{0,70})(\s?\([a-zA-Z0-9\.\-\+\s]{1,20}\))?){0,2}$/i; // eslint-disable-line max-len + +export default function isMimeType(str) { + assertString(str); + return mimeTypeSimple.test(str) || mimeTypeText.test(str) || mimeTypeMultipart.test(str); +} diff --git a/src/lib/isMobilePhone.js b/src/lib/isMobilePhone.js index 4823945b9..b00391ea6 100644 --- a/src/lib/isMobilePhone.js +++ b/src/lib/isMobilePhone.js @@ -2,75 +2,202 @@ import assertString from './util/assertString'; /* eslint-disable max-len */ const phones = { + 'am-AM': /^(\+?374|0)(33|4[134]|55|77|88|9[13-689])\d{6}$/, 'ar-AE': /^((\+?971)|0)?5[024568]\d{7}$/, + 'ar-BH': /^(\+?973)?(3|6)\d{7}$/, 'ar-DZ': /^(\+?213|0)(5|6|7)\d{8}$/, - 'ar-EG': /^((\+?20)|0)?1[012]\d{8}$/, + 'ar-LB': /^(\+?961)?((3|81)\d{6}|7\d{7})$/, + 'ar-EG': /^((\+?20)|0)?1[0125]\d{8}$/, + 'ar-IQ': /^(\+?964|0)?7[0-9]\d{8}$/, 'ar-JO': /^(\+?962|0)?7[789]\d{7}$/, + 'ar-KW': /^(\+?965)([569]\d{7}|41\d{6})$/, + 'ar-LY': /^((\+?218)|0)?(9[1-6]\d{7}|[1-8]\d{7,9})$/, + 'ar-MA': /^(?:(?:\+|00)212|0)[5-7]\d{8}$/, + 'ar-OM': /^((\+|00)968)?([79][1-9])\d{6}$/, + 'ar-PS': /^(\+?970|0)5[6|9](\d{7})$/, 'ar-SA': /^(!?(\+?966)|0)?5\d{8}$/, + 'ar-SD': /^((\+?249)|0)?(9[012369]|1[012])\d{7}$/, 'ar-SY': /^(!?(\+?963)|0)?9\d{8}$/, + 'ar-TN': /^(\+?216)?[2459]\d{7}$/, + 'az-AZ': /^(\+994|0)(10|5[015]|7[07]|99)\d{7}$/, + 'ar-QA': /^(\+?974|0)?([3567]\d{7})$/, + 'bs-BA': /^((((\+|00)3876)|06))((([0-3]|[5-6])\d{6})|(4\d{7}))$/, + 'be-BY': /^(\+?375)?(24|25|29|33|44)\d{7}$/, + 'bg-BG': /^(\+?359|0)?8[789]\d{7}$/, + 'bn-BD': /^(\+?880|0)1[13456789][0-9]{8}$/, + 'ca-AD': /^(\+376)?[346]\d{5}$/, 'cs-CZ': /^(\+?420)? ?[1-9][0-9]{2} ?[0-9]{3} ?[0-9]{3}$/, 'da-DK': /^(\+?45)?\s?\d{2}\s?\d{2}\s?\d{2}\s?\d{2}$/, - 'de-DE': /^(\+?49[ \.\-])?([\(]{1}[0-9]{1,6}[\)])?([0-9 \.\-\/]{3,20})((x|ext|extension)[ ]?[0-9]{1,4})?$/, - 'el-GR': /^(\+?30)?(69\d{8})$/, + 'de-DE': /^((\+49|0)1)(5[0-25-9]\d|6([23]|0\d?)|7([0-57-9]|6\d))\d{7,9}$/, + 'de-AT': /^(\+43|0)\d{1,4}\d{3,12}$/, + 'de-CH': /^(\+41|0)([1-9])\d{1,9}$/, + 'de-LU': /^(\+352)?((6\d1)\d{6})$/, + 'dv-MV': /^(\+?960)?(7[2-9]|9[1-9])\d{5}$/, + 'el-GR': /^(\+?30|0)?6(8[5-9]|9(?![26])[0-9])\d{7}$/, + 'el-CY': /^(\+?357?)?(9(9|7|6|5|4)\d{6})$/, + 'en-AI': /^(\+?1|0)264(?:2(35|92)|4(?:6[1-2]|76|97)|5(?:3[6-9]|8[1-4])|7(?:2(4|9)|72))\d{4}$/, 'en-AU': /^(\+?61|0)4\d{8}$/, - 'en-GB': /^(\+?44|0)7\d{9}$/, - 'en-HK': /^(\+?852\-?)?[456789]\d{3}\-?\d{4}$/, - 'en-IN': /^(\+?91|0)?[789]\d{9}$/, - 'en-KE': /^(\+?254|0)?[7]\d{8}$/, + 'en-AG': /^(?:\+1|1)268(?:464|7(?:1[3-9]|[28]\d|3[0246]|64|7[0-689]))\d{4}$/, + 'en-BM': /^(\+?1)?441(((3|7)\d{6}$)|(5[0-3][0-9]\d{4}$)|(59\d{5}$))/, + 'en-BS': /^(\+?1[-\s]?|0)?\(?242\)?[-\s]?\d{3}[-\s]?\d{4}$/, + 'en-GB': /^(\+?44|0)7[1-9]\d{8}$/, + 'en-GG': /^(\+?44|0)1481\d{6}$/, + 'en-GH': /^(\+233|0)(20|50|24|54|27|57|26|56|23|53|28|55|59)\d{7}$/, + 'en-GY': /^(\+592|0)6\d{6}$/, + 'en-HK': /^(\+?852[-\s]?)?[456789]\d{3}[-\s]?\d{4}$/, + 'en-MO': /^(\+?853[-\s]?)?[6]\d{3}[-\s]?\d{4}$/, + 'en-IE': /^(\+?353|0)8[356789]\d{7}$/, + 'en-IN': /^(\+?91|0)?[6789]\d{9}$/, + 'en-JM': /^(\+?876)?\d{7}$/, + 'en-KE': /^(\+?254|0)(7|1)\d{8}$/, + 'fr-CF': /^(\+?236| ?)(70|75|77|72|21|22)\d{6}$/, + 'en-SS': /^(\+?211|0)(9[1257])\d{7}$/, + 'en-KI': /^((\+686|686)?)?( )?((6|7)(2|3|8)[0-9]{6})$/, + 'en-KN': /^(?:\+1|1)869(?:46\d|48[89]|55[6-8]|66\d|76[02-7])\d{4}$/, + 'en-LS': /^(\+?266)(22|28|57|58|59|27|52)\d{6}$/, + 'en-MT': /^(\+?356|0)?(99|79|77|21|27|22|25)[0-9]{6}$/, + 'en-MU': /^(\+?230|0)?\d{8}$/, + 'en-MW': /^(\+?265|0)(((77|88|31|99|98|21)\d{7})|(((111)|1)\d{6})|(32000\d{4}))$/, + 'en-NA': /^(\+?264|0)(6|8)\d{7}$/, 'en-NG': /^(\+?234|0)?[789]\d{9}$/, - 'en-NZ': /^(\+?64|0)2\d{7,9}$/, - 'en-PK': /^((\+92)|(0092))-{0,1}\d{3}-{0,1}\d{7}$|^\d{11}$|^\d{4}-\d{7}$/, + 'en-NZ': /^(\+?64|0)[28]\d{7,9}$/, + 'en-PG': /^(\+?675|0)?(7\d|8[18])\d{6}$/, + 'en-PK': /^((00|\+)?92|0)3[0-6]\d{8}$/, + 'en-PH': /^(09|\+639)\d{9}$/, 'en-RW': /^(\+?250|0)?[7]\d{8}$/, - 'en-SG': /^(\+65)?[89]\d{7}$/, + 'en-SG': /^(\+65)?[3689]\d{7}$/, + 'en-SL': /^(\+?232|0)\d{8}$/, 'en-TZ': /^(\+?255|0)?[67]\d{8}$/, 'en-UG': /^(\+?256|0)?[7]\d{8}$/, - 'en-US': /^(\+?1)?[2-9]\d{2}[2-9](?!11)\d{6}$/, + 'en-US': /^((\+1|1)?( |-)?)?(\([2-9][0-9]{2}\)|[2-9][0-9]{2})( |-)?([2-9][0-9]{2}( |-)?[0-9]{4})$/, 'en-ZA': /^(\+?27|0)\d{9}$/, - 'en-ZM': /^(\+?26)?09[567]\d{7}$/, - 'es-ES': /^(\+?34)?(6\d{1}|7[1234])\d{7}$/, + 'en-ZM': /^(\+?26)?0[79][567]\d{7}$/, + 'en-ZW': /^(\+263)[0-9]{9}$/, + 'en-BW': /^(\+?267)?(7[1-8]{1})\d{6}$/, + 'es-AR': /^\+?549(11|[2368]\d)\d{8}$/, + 'es-BO': /^(\+?591)?(6|7)\d{7}$/, + 'es-CO': /^(\+?57)?3(0(0|1|2|4|5)|1\d|2[0-4]|5(0|1))\d{7}$/, + 'es-CL': /^(\+?56|0)[2-9]\d{1}\d{7}$/, + 'es-CR': /^(\+506)?[2-8]\d{7}$/, + 'es-CU': /^(\+53|0053)?5\d{7}$/, + 'es-DO': /^(\+?1)?8[024]9\d{7}$/, + 'es-HN': /^(\+?504)?[9|8|3|2]\d{7}$/, + 'es-EC': /^(\+?593|0)([2-7]|9[2-9])\d{7}$/, + 'es-ES': /^(\+?34)?[6|7]\d{8}$/, + 'es-GT': /^(\+?502)?[2|6|7]\d{7}$/, + 'es-PE': /^(\+?51)?9\d{8}$/, + 'es-MX': /^(\+?52)?(1|01)?\d{10,11}$/, + 'es-NI': /^(\+?505)\d{7,8}$/, + 'es-PA': /^(\+?507)\d{7,8}$/, + 'es-PY': /^(\+?595|0)9[9876]\d{7}$/, + 'es-SV': /^(\+?503)?[67]\d{7}$/, + 'es-UY': /^(\+598|0)9[1-9][\d]{6}$/, + 'es-VE': /^(\+?58)?(2|4)\d{9}$/, 'et-EE': /^(\+?372)?\s?(5|8[1-4])\s?([0-9]\s?){6,7}$/, 'fa-IR': /^(\+?98[\-\s]?|0)9[0-39]\d[\-\s]?\d{3}[\-\s]?\d{4}$/, - 'fi-FI': /^(\+?358|0)\s?(4(0|1|2|4|5|6)?|50)\s?(\d\s?){4,8}\d$/, + 'fi-FI': /^(\+?358|0)\s?(4[0-6]|50)\s?(\d\s?){4,8}$/, + 'fj-FJ': /^(\+?679)?\s?\d{3}\s?\d{4}$/, 'fo-FO': /^(\+?298)?\s?\d{2}\s?\d{2}\s?\d{2}$/, + 'fr-BF': /^(\+226|0)[67]\d{7}$/, + 'fr-BJ': /^(\+229)\d{8}$/, + 'fr-CD': /^(\+?243|0)?(8|9)\d{8}$/, + 'fr-CM': /^(\+?237)6[0-9]{8}$/, 'fr-FR': /^(\+?33|0)[67]\d{8}$/, - 'he-IL': /^(\+972|0)([23489]|5[0248]|77)[1-9]\d{6}/, - 'hu-HU': /^(\+?36)(20|30|70)\d{7}$/, - 'id-ID': /^(\+?62|0[1-9])[\s|\d]+$/, + 'fr-GF': /^(\+?594|0|00594)[67]\d{8}$/, + 'fr-GP': /^(\+?590|0|00590)[67]\d{8}$/, + 'fr-MQ': /^(\+?596|0|00596)[67]\d{8}$/, + 'fr-PF': /^(\+?689)?8[789]\d{6}$/, + 'fr-RE': /^(\+?262|0|00262)[67]\d{8}$/, + 'fr-WF': /^(\+681)?\d{6}$/, + 'he-IL': /^(\+972|0)([23489]|5[012345689]|77)[1-9]\d{6}$/, + 'hu-HU': /^(\+?36|06)(20|30|31|50|70)\d{7}$/, + 'id-ID': /^(\+?62|0)8(1[123456789]|2[1238]|3[1238]|5[12356789]|7[78]|9[56789]|8[123456789])([\s?|\d]{5,11})$/, + 'ir-IR': /^(\+98|0)?9\d{9}$/, 'it-IT': /^(\+?39)?\s?3\d{2} ?\d{6,7}$/, - 'ja-JP': /^(\+?81|0)[789]0[ \-]?[1-9]\d{2}[ \-]?\d{5}$/, + 'it-SM': /^((\+378)|(0549)|(\+390549)|(\+3780549))?6\d{5,9}$/, + 'ja-JP': /^(\+81[ \-]?(\(0\))?|0)[6789]0[ \-]?\d{4}[ \-]?\d{4}$/, + 'ka-GE': /^(\+?995)?(79\d{7}|5\d{8})$/, + 'kk-KZ': /^(\+?7|8)?7\d{9}$/, 'kl-GL': /^(\+?299)?\s?\d{2}\s?\d{2}\s?\d{2}$/, 'ko-KR': /^((\+?82)[ \-]?)?0?1([0|1|6|7|8|9]{1})[ \-]?\d{3,4}[ \-]?\d{4}$/, + 'ky-KG': /^(\+996\s?)?(22[0-9]|50[0-9]|55[0-9]|70[0-9]|75[0-9]|77[0-9]|880|990|995|996|997|998)\s?\d{3}\s?\d{3}$/, 'lt-LT': /^(\+370|8)\d{8}$/, - 'ms-MY': /^(\+?6?01){1}(([145]{1}(\-|\s)?\d{7,8})|([236789]{1}(\s|\-)?\d{7}))$/, + 'lv-LV': /^(\+?371)2\d{7}$/, + 'mg-MG': /^((\+?261|0)(2|3)\d)?\d{7}$/, + 'mn-MN': /^(\+|00|011)?976(77|81|88|91|94|95|96|99)\d{6}$/, + 'my-MM': /^(\+?959|09|9)(2[5-7]|3[1-2]|4[0-5]|6[6-9]|7[5-9]|9[6-9])[0-9]{7}$/, + 'ms-MY': /^(\+?60|0)1(([0145](-|\s)?\d{7,8})|([236-9](-|\s)?\d{7}))$/, + 'mz-MZ': /^(\+?258)?8[234567]\d{7}$/, 'nb-NO': /^(\+?47)?[49]\d{7}$/, - 'nl-BE': /^(\+?32|0)4?\d{8}$/, + 'ne-NP': /^(\+?977)?9[78]\d{8}$/, + 'nl-BE': /^(\+?32|0)4\d{8}$/, + 'nl-NL': /^(((\+|00)?31\(0\))|((\+|00)?31)|0)6{1}\d{8}$/, + 'nl-AW': /^(\+)?297(56|59|64|73|74|99)\d{5}$/, 'nn-NO': /^(\+?47)?[49]\d{7}$/, - 'pl-PL': /^(\+?48)? ?[5-8]\d ?\d{3} ?\d{2} ?\d{2}$/, - 'pt-BR': /^(\+?55|0)\-?[1-9]{2}\-?[2-9]{1}\d{3,4}\-?\d{4}$/, + 'pl-PL': /^(\+?48)? ?([5-8]\d|45) ?\d{3} ?\d{2} ?\d{2}$/, + 'pt-BR': /^((\+?55\ ?[1-9]{2}\ ?)|(\+?55\ ?\([1-9]{2}\)\ ?)|(0[1-9]{2}\ ?)|(\([1-9]{2}\)\ ?)|([1-9]{2}\ ?))((\d{4}\-?\d{4})|(9[1-9]{1}\d{3}\-?\d{4}))$/, 'pt-PT': /^(\+?351)?9[1236]\d{7}$/, - 'ro-RO': /^(\+?4?0)\s?7\d{2}(\/|\s|\.|\-)?\d{3}(\s|\.|\-)?\d{3}$/, + 'pt-AO': /^(\+?244)?9\d{8}$/, + 'ro-MD': /^(\+?373|0)((6(0|1|2|6|7|8|9))|(7(6|7|8|9)))\d{6}$/, + 'ro-RO': /^(\+?40|0)\s?7\d{2}(\/|\s|\.|-)?\d{3}(\s|\.|-)?\d{3}$/, 'ru-RU': /^(\+?7|8)?9\d{9}$/, + 'si-LK': /^(?:0|94|\+94)?(7(0|1|2|4|5|6|7|8)( |-)?)\d{7}$/, + 'sl-SI': /^(\+386\s?|0)(\d{1}\s?\d{3}\s?\d{2}\s?\d{2}|\d{2}\s?\d{3}\s?\d{3})$/, 'sk-SK': /^(\+?421)? ?[1-9][0-9]{2} ?[0-9]{3} ?[0-9]{3}$/, + 'so-SO': /^(\+?252|0)((6[0-9])\d{7}|(7[1-9])\d{7})$/, + 'sq-AL': /^(\+355|0)6[2-9]\d{7}$/, 'sr-RS': /^(\+3816|06)[- \d]{5,9}$/, + 'sv-SE': /^(\+?46|0)[\s\-]?7[\s\-]?[02369]([\s\-]?\d){7}$/, + 'tg-TJ': /^(\+?992)?[5][5]\d{7}$/, + 'th-TH': /^(\+66|66|0)\d{9}$/, 'tr-TR': /^(\+?90|0)?5\d{9}$/, - 'uk-UA': /^(\+?38|8)?0\d{9}$/, - 'vi-VN': /^(\+?84|0)?((1(2([0-9])|6([2-9])|88|99))|(9((?!5)[0-9])))([0-9]{7})$/, - 'zh-CN': /^(\+?0?86\-?)?1[345789]\d{9}$/, + 'tk-TM': /^(\+993|993|8)\d{8}$/, + 'uk-UA': /^(\+?38)?0(50|6[36-8]|7[357]|9[1-9])\d{7}$/, + 'uz-UZ': /^(\+?998)?(6[125-79]|7[1-69]|88|9\d)\d{7}$/, + 'vi-VN': /^((\+?84)|0)((3([2-9]))|(5([25689]))|(7([0|6-9]))|(8([1-9]))|(9([0-9])))([0-9]{7})$/, + 'zh-CN': /^((\+|00)86)?(1[3-9]|9[28])\d{9}$/, 'zh-TW': /^(\+?886\-?|0)?9\d{8}$/, + 'dz-BT': /^(\+?975|0)?(17|16|77|02)\d{6}$/, + 'ar-YE': /^(((\+|00)9677|0?7)[0137]\d{7}|((\+|00)967|0)[1-7]\d{6})$/, + 'ar-EH': /^(\+?212|0)[\s\-]?(5288|5289)[\s\-]?\d{5}$/, + 'fa-AF': /^(\+93|0)?(2{1}[0-8]{1}|[3-5]{1}[0-4]{1})(\d{7})$/, + 'mk-MK': /^(\+?389|0)?((?:2[2-9]\d{6}|(?:3[1-4]|4[2-8])\d{6}|500\d{5}|5[2-9]\d{6}|7[0-9][2-9]\d{5}|8[1-9]\d{6}|800\d{5}|8009\d{4}))$/, }; /* eslint-enable max-len */ // aliases phones['en-CA'] = phones['en-US']; +phones['fr-CA'] = phones['en-CA']; phones['fr-BE'] = phones['nl-BE']; phones['zh-HK'] = phones['en-HK']; +phones['zh-MO'] = phones['en-MO']; +phones['ga-IE'] = phones['en-IE']; +phones['fr-CH'] = phones['de-CH']; +phones['it-CH'] = phones['fr-CH']; -export default function isMobilePhone(str, locale) { +export default function isMobilePhone(str, locale, options) { assertString(str); - if (locale in phones) { + if (options && options.strictMode && !str.startsWith('+')) { + return false; + } + if (Array.isArray(locale)) { + return locale.some((key) => { + // https://github.com/gotwarlost/istanbul/blob/master/ignoring-code-for-coverage.md#ignoring-code-for-coverage-purposes + // istanbul ignore else + if (phones.hasOwnProperty(key)) { + const phone = phones[key]; + if (phone.test(str)) { + return true; + } + } + return false; + }); + } else if (locale in phones) { return phones[locale].test(str); - } else if (locale === 'any') { + // alias falsey locale as 'any' + } else if (!locale || locale === 'any') { for (const key in phones) { + // istanbul ignore else if (phones.hasOwnProperty(key)) { const phone = phones[key]; if (phone.test(str)) { @@ -82,3 +209,5 @@ export default function isMobilePhone(str, locale) { } throw new Error(`Invalid locale '${locale}'`); } + +export const locales = Object.keys(phones); diff --git a/src/lib/isNumeric.js b/src/lib/isNumeric.js index 2b703e6e2..4cc7ea5b3 100644 --- a/src/lib/isNumeric.js +++ b/src/lib/isNumeric.js @@ -1,8 +1,12 @@ import assertString from './util/assertString'; +import { decimal } from './alpha'; -const numeric = /^[-+]?[0-9]+$/; +const numericNoSymbols = /^[0-9]+$/; -export default function isNumeric(str) { +export default function isNumeric(str, options) { assertString(str); - return numeric.test(str); + if (options && options.no_symbols) { + return numericNoSymbols.test(str); + } + return (new RegExp(`^[+-]?([0-9]*[${(options || {}).locale ? decimal[options.locale] : '.'}])?[0-9]+$`)).test(str); } diff --git a/src/lib/isOctal.js b/src/lib/isOctal.js new file mode 100644 index 000000000..205b98ab6 --- /dev/null +++ b/src/lib/isOctal.js @@ -0,0 +1,8 @@ +import assertString from './util/assertString'; + +const octal = /^(0o)?[0-7]+$/i; + +export default function isOctal(str) { + assertString(str); + return octal.test(str); +} diff --git a/src/lib/isPassportNumber.js b/src/lib/isPassportNumber.js new file mode 100644 index 000000000..c3b842e59 --- /dev/null +++ b/src/lib/isPassportNumber.js @@ -0,0 +1,89 @@ +import assertString from './util/assertString'; + +/** + * Reference: + * https://en.wikipedia.org/ -- Wikipedia + * https://docs.microsoft.com/en-us/microsoft-365/compliance/eu-passport-number -- EU Passport Number + * https://countrycode.org/ -- Country Codes + */ +const passportRegexByCountryCode = { + AM: /^[A-Z]{2}\d{7}$/, // ARMENIA + AR: /^[A-Z]{3}\d{6}$/, // ARGENTINA + AT: /^[A-Z]\d{7}$/, // AUSTRIA + AU: /^[A-Z]\d{7}$/, // AUSTRALIA + AZ: /^[A-Z]{1}\d{8}$/, // AZERBAIJAN + BE: /^[A-Z]{2}\d{6}$/, // BELGIUM + BG: /^\d{9}$/, // BULGARIA + BR: /^[A-Z]{2}\d{6}$/, // BRAZIL + BY: /^[A-Z]{2}\d{7}$/, // BELARUS + CA: /^[A-Z]{2}\d{6}$|^[A-Z]\d{6}[A-Z]{2}$/, // CANADA + CH: /^[A-Z]\d{7}$/, // SWITZERLAND + CN: /^G\d{8}$|^E(?![IO])[A-Z0-9]\d{7}$/, // CHINA [G=Ordinary, E=Electronic] followed by 8-digits, or E followed by any UPPERCASE letter (except I and O) followed by 7 digits + CY: /^[A-Z](\d{6}|\d{8})$/, // CYPRUS + CZ: /^\d{8}$/, // CZECH REPUBLIC + DE: /^[CFGHJKLMNPRTVWXYZ0-9]{9}$/, // GERMANY + DK: /^\d{9}$/, // DENMARK + DZ: /^\d{9}$/, // ALGERIA + EE: /^([A-Z]\d{7}|[A-Z]{2}\d{7})$/, // ESTONIA (K followed by 7-digits), e-passports have 2 UPPERCASE followed by 7 digits + ES: /^[A-Z0-9]{2}([A-Z0-9]?)\d{6}$/, // SPAIN + FI: /^[A-Z]{2}\d{7}$/, // FINLAND + FR: /^\d{2}[A-Z]{2}\d{5}$/, // FRANCE + GB: /^\d{9}$/, // UNITED KINGDOM + GR: /^[A-Z]{2}\d{7}$/, // GREECE + HR: /^\d{9}$/, // CROATIA + HU: /^[A-Z]{2}(\d{6}|\d{7})$/, // HUNGARY + IE: /^[A-Z0-9]{2}\d{7}$/, // IRELAND + IN: /^[A-Z]{1}-?\d{7}$/, // INDIA + ID: /^[A-C]\d{7}$/, // INDONESIA + IR: /^[A-Z]\d{8}$/, // IRAN + IS: /^(A)\d{7}$/, // ICELAND + IT: /^[A-Z0-9]{2}\d{7}$/, // ITALY + JM: /^[Aa]\d{7}$/, // JAMAICA + JP: /^[A-Z]{2}\d{7}$/, // JAPAN + KR: /^[MS]\d{8}$/, // SOUTH KOREA, REPUBLIC OF KOREA, [S=PS Passports, M=PM Passports] + KZ: /^[a-zA-Z]\d{7}$/, // KAZAKHSTAN + LI: /^[a-zA-Z]\d{5}$/, // LIECHTENSTEIN + LT: /^[A-Z0-9]{8}$/, // LITHUANIA + LU: /^[A-Z0-9]{8}$/, // LUXEMBURG + LV: /^[A-Z0-9]{2}\d{7}$/, // LATVIA + LY: /^[A-Z0-9]{8}$/, // LIBYA + MT: /^\d{7}$/, // MALTA + MZ: /^([A-Z]{2}\d{7})|(\d{2}[A-Z]{2}\d{5})$/, // MOZAMBIQUE + MY: /^[AHK]\d{8}$/, // MALAYSIA + MX: /^\d{10,11}$/, // MEXICO + NL: /^[A-Z]{2}[A-Z0-9]{6}\d$/, // NETHERLANDS + NZ: /^([Ll]([Aa]|[Dd]|[Ff]|[Hh])|[Ee]([Aa]|[Pp])|[Nn])\d{6}$/, // NEW ZEALAND + PH: /^([A-Z](\d{6}|\d{7}[A-Z]))|([A-Z]{2}(\d{6}|\d{7}))$/, // PHILIPPINES + PK: /^[A-Z]{2}\d{7}$/, // PAKISTAN + PL: /^[A-Z]{2}\d{7}$/, // POLAND + PT: /^[A-Z]\d{6}$/, // PORTUGAL + RO: /^\d{8,9}$/, // ROMANIA + RU: /^\d{9}$/, // RUSSIAN FEDERATION + SE: /^\d{8}$/, // SWEDEN + SL: /^(P)[A-Z]\d{7}$/, // SLOVENIA + SK: /^[0-9A-Z]\d{7}$/, // SLOVAKIA + TH: /^[A-Z]{1,2}\d{6,7}$/, // THAILAND + TR: /^[A-Z]\d{8}$/, // TURKEY + UA: /^[A-Z]{2}\d{6}$/, // UKRAINE + US: /^\d{9}$|^[A-Z]\d{8}$/, // UNITED STATES + ZA: /^[TAMD]\d{8}$/, // SOUTH AFRICA +}; + +export const locales = Object.keys(passportRegexByCountryCode); + +/** + * Check if str is a valid passport number + * relative to provided ISO Country Code. + * + * @param {string} str + * @param {string} countryCode + * @return {boolean} + */ +export default function isPassportNumber(str, countryCode) { + assertString(str); + /** Remove All Whitespaces, Convert to UPPERCASE */ + const normalizedStr = str.replace(/\s/g, '').toUpperCase(); + + return (countryCode.toUpperCase() in passportRegexByCountryCode) && + passportRegexByCountryCode[countryCode].test(normalizedStr); +} diff --git a/src/lib/isPort.js b/src/lib/isPort.js index 0b316b78c..0a9ddce1d 100644 --- a/src/lib/isPort.js +++ b/src/lib/isPort.js @@ -1,5 +1,5 @@ import isInt from './isInt'; export default function isPort(str) { - return isInt(str, { min: 0, max: 65535 }); + return isInt(str, { allow_leading_zeroes: false, min: 0, max: 65535 }); } diff --git a/src/lib/isPostalCode.js b/src/lib/isPostalCode.js index 5b0b5cbeb..da3fbdcf8 100644 --- a/src/lib/isPostalCode.js +++ b/src/lib/isPostalCode.js @@ -7,37 +7,73 @@ const fiveDigit = /^\d{5}$/; const sixDigit = /^\d{6}$/; const patterns = { + AD: /^AD\d{3}$/, AT: fourDigit, AU: fourDigit, + AZ: /^AZ\d{4}$/, + BA: /^([7-8]\d{4}$)/, + BD: /^([1-8][0-9]{3}|9[0-4][0-9]{2})$/, BE: fourDigit, + BG: fourDigit, + BR: /^\d{5}-?\d{3}$/, + BY: /^2[1-4]\d{4}$/, CA: /^[ABCEGHJKLMNPRSTVXY]\d[ABCEGHJ-NPRSTV-Z][\s\-]?\d[ABCEGHJ-NPRSTV-Z]\d$/i, CH: fourDigit, + CN: /^(0[1-7]|1[012356]|2[0-7]|3[0-6]|4[0-7]|5[1-7]|6[1-7]|7[1-5]|8[1345]|9[09])\d{4}$/, + CO: /^(05|08|11|13|15|17|18|19|20|23|25|27|41|44|47|50|52|54|63|66|68|70|73|76|81|85|86|88|91|94|95|97|99)(\d{4})$/, CZ: /^\d{3}\s?\d{2}$/, DE: fiveDigit, DK: fourDigit, + DO: fiveDigit, DZ: fiveDigit, - ES: fiveDigit, + EE: fiveDigit, + ES: /^(5[0-2]{1}|[0-4]{1}\d{1})\d{3}$/, FI: fiveDigit, - FR: /^\d{2}\s?\d{3}$/, + FR: /^(?:(?:0[1-9]|[1-8]\d|9[0-5])\d{3}|97[1-46]\d{2})$/, GB: /^(gir\s?0aa|[a-z]{1,2}\d[\da-z]?\s?(\d[a-z]{2})?)$/i, GR: /^\d{3}\s?\d{2}$/, - IL: fiveDigit, - IN: sixDigit, + HR: /^([1-5]\d{4}$)/, + HT: /^HT\d{4}$/, + HU: fourDigit, + ID: fiveDigit, + IE: /^(?!.*(?:o))[A-Za-z]\d[\dw]\s\w{4}$/i, + IL: /^(\d{5}|\d{7})$/, + IN: /^((?!10|29|35|54|55|65|66|86|87|88|89)[1-9][0-9]{5})$/, + IR: /^(?!(\d)\1{3})[13-9]{4}[1346-9][013-9]{5}$/, IS: threeDigit, IT: fiveDigit, JP: /^\d{3}\-\d{4}$/, KE: fiveDigit, + KR: /^(\d{5}|\d{6})$/, LI: /^(948[5-9]|949[0-7])$/, + LT: /^LT\-\d{5}$/, + LU: fourDigit, + LV: /^LV\-\d{4}$/, + LK: fiveDigit, + MG: threeDigit, MX: fiveDigit, - NL: /^\d{4}\s?[a-z]{2}$/i, + MT: /^[A-Za-z]{3}\s{0,1}\d{4}$/, + MY: fiveDigit, + NL: /^[1-9]\d{3}\s?(?!sa|sd|ss)[a-z]{2}$/i, NO: fourDigit, + NP: /^(10|21|22|32|33|34|44|45|56|57)\d{3}$|^(977)$/i, + NZ: fourDigit, + // https://www.pakpost.gov.pk/postcodes.php + PK: fiveDigit, PL: /^\d{2}\-\d{3}$/, - PT: /^\d{4}(\-\d{3})?$/, + PR: /^00[679]\d{2}([ -]\d{4})?$/, + PT: /^\d{4}\-\d{3}?$/, RO: sixDigit, RU: sixDigit, SA: fiveDigit, - SE: /^\d{3}\s?\d{2}$/, - TW: /^\d{3}(\d{2})?$/, + SE: /^[1-9]\d{2}\s?\d{2}$/, + SG: sixDigit, + SI: fourDigit, + SK: /^\d{3}\s?\d{2}$/, + TH: fiveDigit, + TN: fourDigit, + TW: /^\d{3}(\d{2,3})?$/, + UA: fiveDigit, US: /^\d{5}(-\d{4})?$/, ZA: fourDigit, ZM: fiveDigit, @@ -45,12 +81,14 @@ const patterns = { export const locales = Object.keys(patterns); -export default function (str, locale) { +export default function isPostalCode(str, locale) { assertString(str); if (locale in patterns) { return patterns[locale].test(str); } else if (locale === 'any') { for (const key in patterns) { + // https://github.com/gotwarlost/istanbul/blob/master/ignoring-code-for-coverage.md#ignoring-code-for-coverage-purposes + // istanbul ignore else if (patterns.hasOwnProperty(key)) { const pattern = patterns[key]; if (pattern.test(str)) { diff --git a/src/lib/isRFC3339.js b/src/lib/isRFC3339.js new file mode 100644 index 000000000..48b025e0f --- /dev/null +++ b/src/lib/isRFC3339.js @@ -0,0 +1,27 @@ +import assertString from './util/assertString'; + +/* Based on https://tools.ietf.org/html/rfc3339#section-5.6 */ + +const dateFullYear = /[0-9]{4}/; +const dateMonth = /(0[1-9]|1[0-2])/; +const dateMDay = /([12]\d|0[1-9]|3[01])/; + +const timeHour = /([01][0-9]|2[0-3])/; +const timeMinute = /[0-5][0-9]/; +const timeSecond = /([0-5][0-9]|60)/; + +const timeSecFrac = /(\.[0-9]+)?/; +const timeNumOffset = new RegExp(`[-+]${timeHour.source}:${timeMinute.source}`); +const timeOffset = new RegExp(`([zZ]|${timeNumOffset.source})`); + +const partialTime = new RegExp(`${timeHour.source}:${timeMinute.source}:${timeSecond.source}${timeSecFrac.source}`); + +const fullDate = new RegExp(`${dateFullYear.source}-${dateMonth.source}-${dateMDay.source}`); +const fullTime = new RegExp(`${partialTime.source}${timeOffset.source}`); + +const rfc3339 = new RegExp(`^${fullDate.source}[ tT]${fullTime.source}$`); + +export default function isRFC3339(str) { + assertString(str); + return rfc3339.test(str); +} diff --git a/src/lib/isRgbColor.js b/src/lib/isRgbColor.js new file mode 100644 index 000000000..6e2866243 --- /dev/null +++ b/src/lib/isRgbColor.js @@ -0,0 +1,42 @@ +/* eslint-disable prefer-rest-params */ +import assertString from './util/assertString'; + +const rgbColor = /^rgb\((([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]),){2}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\)$/; +const rgbaColor = /^rgba\((([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]),){3}(0?\.\d\d?|1(\.0)?|0(\.0)?)\)$/; +const rgbColorPercent = /^rgb\((([0-9]%|[1-9][0-9]%|100%),){2}([0-9]%|[1-9][0-9]%|100%)\)$/; +const rgbaColorPercent = /^rgba\((([0-9]%|[1-9][0-9]%|100%),){3}(0?\.\d\d?|1(\.0)?|0(\.0)?)\)$/; +const startsWithRgb = /^rgba?/; + +export default function isRgbColor(str, options) { + assertString(str); + // default options to true for percent and false for spaces + let allowSpaces = false; + let includePercentValues = true; + if (typeof options !== 'object') { + if (arguments.length >= 2) { + includePercentValues = arguments[1]; + } + } else { + allowSpaces = options.allowSpaces !== undefined ? options.allowSpaces : allowSpaces; + includePercentValues = options.includePercentValues !== undefined ? + options.includePercentValues : includePercentValues; + } + + if (allowSpaces) { + // make sure it starts with continous rgba? without spaces before stripping + if (!startsWithRgb.test(str)) { + return false; + } + // strip all whitespace + str = str.replace(/\s/g, ''); + } + + if (!includePercentValues) { + return rgbColor.test(str) || rgbaColor.test(str); + } + + return rgbColor.test(str) || + rgbaColor.test(str) || + rgbColorPercent.test(str) || + rgbaColorPercent.test(str); +} diff --git a/src/lib/isSemVer.js b/src/lib/isSemVer.js new file mode 100644 index 000000000..172d36496 --- /dev/null +++ b/src/lib/isSemVer.js @@ -0,0 +1,20 @@ +import assertString from './util/assertString'; +import multilineRegexp from './util/multilineRegex'; + +/** + * Regular Expression to match + * semantic versioning (SemVer) + * built from multi-line, multi-parts regexp + * Reference: https://semver.org/ + */ +const semanticVersioningRegex = multilineRegexp([ + '^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)', + '(?:-((?:0|[1-9]\\d*|\\d*[a-z-][0-9a-z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-z-][0-9a-z-]*))*))', + '?(?:\\+([0-9a-z-]+(?:\\.[0-9a-z-]+)*))?$', +], 'i'); + +export default function isSemVer(str) { + assertString(str); + + return semanticVersioningRegex.test(str); +} diff --git a/src/lib/isSlug.js b/src/lib/isSlug.js new file mode 100644 index 000000000..28b872a92 --- /dev/null +++ b/src/lib/isSlug.js @@ -0,0 +1,8 @@ +import assertString from './util/assertString'; + +let charsetRegex = /^[^\s-_](?!.*?[-_]{2,})[a-z0-9-\\][^\s]*[^-_\s]$/; + +export default function isSlug(str) { + assertString(str); + return (charsetRegex.test(str)); +} diff --git a/src/lib/isStrongPassword.js b/src/lib/isStrongPassword.js new file mode 100644 index 000000000..8fe9223b7 --- /dev/null +++ b/src/lib/isStrongPassword.js @@ -0,0 +1,97 @@ +import merge from './util/merge'; +import assertString from './util/assertString'; + +const upperCaseRegex = /^[A-Z]$/; +const lowerCaseRegex = /^[a-z]$/; +const numberRegex = /^[0-9]$/; +const symbolRegex = /^[-#!$@£%^&*()_+|~=`{}\[\]:";'<>?,.\/\\ ]$/; + +const defaultOptions = { + minLength: 8, + minLowercase: 1, + minUppercase: 1, + minNumbers: 1, + minSymbols: 1, + returnScore: false, + pointsPerUnique: 1, + pointsPerRepeat: 0.5, + pointsForContainingLower: 10, + pointsForContainingUpper: 10, + pointsForContainingNumber: 10, + pointsForContainingSymbol: 10, +}; + +/* Counts number of occurrences of each char in a string + * could be moved to util/ ? +*/ +function countChars(str) { + let result = {}; + Array.from(str).forEach((char) => { + let curVal = result[char]; + if (curVal) { + result[char] += 1; + } else { + result[char] = 1; + } + }); + return result; +} + +/* Return information about a password */ +function analyzePassword(password) { + let charMap = countChars(password); + let analysis = { + length: password.length, + uniqueChars: Object.keys(charMap).length, + uppercaseCount: 0, + lowercaseCount: 0, + numberCount: 0, + symbolCount: 0, + }; + Object.keys(charMap).forEach((char) => { + /* istanbul ignore else */ + if (upperCaseRegex.test(char)) { + analysis.uppercaseCount += charMap[char]; + } else if (lowerCaseRegex.test(char)) { + analysis.lowercaseCount += charMap[char]; + } else if (numberRegex.test(char)) { + analysis.numberCount += charMap[char]; + } else if (symbolRegex.test(char)) { + analysis.symbolCount += charMap[char]; + } + }); + return analysis; +} + +function scorePassword(analysis, scoringOptions) { + let points = 0; + points += analysis.uniqueChars * scoringOptions.pointsPerUnique; + points += (analysis.length - analysis.uniqueChars) * scoringOptions.pointsPerRepeat; + if (analysis.lowercaseCount > 0) { + points += scoringOptions.pointsForContainingLower; + } + if (analysis.uppercaseCount > 0) { + points += scoringOptions.pointsForContainingUpper; + } + if (analysis.numberCount > 0) { + points += scoringOptions.pointsForContainingNumber; + } + if (analysis.symbolCount > 0) { + points += scoringOptions.pointsForContainingSymbol; + } + return points; +} + +export default function isStrongPassword(str, options = null) { + assertString(str); + const analysis = analyzePassword(str); + options = merge(options || {}, defaultOptions); + if (options.returnScore) { + return scorePassword(analysis, options); + } + return analysis.length >= options.minLength + && analysis.lowercaseCount >= options.minLowercase + && analysis.uppercaseCount >= options.minUppercase + && analysis.numberCount >= options.minNumbers + && analysis.symbolCount >= options.minSymbols; +} diff --git a/src/lib/isTaxID.js b/src/lib/isTaxID.js new file mode 100644 index 000000000..5e5f8cb5b --- /dev/null +++ b/src/lib/isTaxID.js @@ -0,0 +1,1283 @@ +import assertString from './util/assertString'; +import * as algorithms from './util/algorithms'; +import isDate from './isDate'; + +/** + * TIN Validation + * Validates Tax Identification Numbers (TINs) from the US, EU member states and the United Kingdom. + * + * EU-UK: + * National TIN validity is calculated using public algorithms as made available by DG TAXUD. + * + * See `https://ec.europa.eu/taxation_customs/tin/specs/FS-TIN%20Algorithms-Public.docx` for more information. + * + * US: + * An Employer Identification Number (EIN), also known as a Federal Tax Identification Number, + * is used to identify a business entity. + * + * NOTES: + * - Prefix 47 is being reserved for future use + * - Prefixes 26, 27, 45, 46 and 47 were previously assigned by the Philadelphia campus. + * + * See `http://www.irs.gov/Businesses/Small-Businesses-&-Self-Employed/How-EINs-are-Assigned-and-Valid-EIN-Prefixes` + * for more information. + */ + +// Locale functions + +/* + * bg-BG validation function + * (Edinen graždanski nomer (EGN/ЕГН), persons only) + * Checks if birth date (first six digits) is valid and calculates check (last) digit + */ +function bgBgCheck(tin) { + // Extract full year, normalize month and check birth date validity + let century_year = tin.slice(0, 2); + let month = parseInt(tin.slice(2, 4), 10); + if (month > 40) { + month -= 40; + century_year = `20${century_year}`; + } else if (month > 20) { + month -= 20; + century_year = `18${century_year}`; + } else { + century_year = `19${century_year}`; + } + if (month < 10) { month = `0${month}`; } + const date = `${century_year}/${month}/${tin.slice(4, 6)}`; + if (!isDate(date, 'YYYY/MM/DD')) { return false; } + + // split digits into an array for further processing + const digits = tin.split('').map(a => parseInt(a, 10)); + + // Calculate checksum by multiplying digits with fixed values + const multip_lookup = [2, 4, 8, 5, 10, 9, 7, 3, 6]; + let checksum = 0; + for (let i = 0; i < multip_lookup.length; i++) { + checksum += digits[i] * multip_lookup[i]; + } + checksum = checksum % 11 === 10 ? 0 : checksum % 11; + return checksum === digits[9]; +} + +/** + * Check if an input is a valid Canadian SIN (Social Insurance Number) + * + * The Social Insurance Number (SIN) is a 9 digit number that + * you need to work in Canada or to have access to government programs and benefits. + * + * https://en.wikipedia.org/wiki/Social_Insurance_Number + * https://www.canada.ca/en/employment-social-development/services/sin.html + * https://www.codercrunch.com/challenge/819302488/sin-validator + * + * @param {string} input + * @return {boolean} + */ +function isCanadianSIN(input) { + const digitsArray = input.split(''); + const even = digitsArray + .filter((_, idx) => idx % 2) + .map(i => Number(i) * 2) + .join('') + .split(''); + + const total = digitsArray + .filter((_, idx) => !(idx % 2)) + .concat(even) + .map(i => Number(i)) + .reduce((acc, cur) => acc + cur); + + return (total % 10 === 0); +} + +/* + * cs-CZ validation function + * (Rodné číslo (RČ), persons only) + * Checks if birth date (first six digits) is valid and divisibility by 11 + * Material not in DG TAXUD document sourced from: + * -`https://lorenc.info/3MA381/overeni-spravnosti-rodneho-cisla.htm` + * -`https://www.mvcr.cz/clanek/rady-a-sluzby-dokumenty-rodne-cislo.aspx` + */ +function csCzCheck(tin) { + tin = tin.replace(/\W/, ''); + + // Extract full year from TIN length + let full_year = parseInt(tin.slice(0, 2), 10); + if (tin.length === 10) { + if (full_year < 54) { + full_year = `20${full_year}`; + } else { + full_year = `19${full_year}`; + } + } else { + if (tin.slice(6) === '000') { return false; } // Three-zero serial not assigned before 1954 + if (full_year < 54) { + full_year = `19${full_year}`; + } else { + return false; // No 18XX years seen in any of the resources + } + } + // Add missing zero if needed + if (full_year.length === 3) { + full_year = [full_year.slice(0, 2), '0', full_year.slice(2)].join(''); + } + + // Extract month from TIN and normalize + let month = parseInt(tin.slice(2, 4), 10); + if (month > 50) { + month -= 50; + } + if (month > 20) { + // Month-plus-twenty was only introduced in 2004 + if (parseInt(full_year, 10) < 2004) { return false; } + month -= 20; + } + if (month < 10) { month = `0${month}`; } + + // Check date validity + const date = `${full_year}/${month}/${tin.slice(4, 6)}`; + if (!isDate(date, 'YYYY/MM/DD')) { return false; } + + // Verify divisibility by 11 + if (tin.length === 10) { + if (parseInt(tin, 10) % 11 !== 0) { + // Some numbers up to and including 1985 are still valid if + // check (last) digit equals 0 and modulo of first 9 digits equals 10 + const checkdigit = parseInt(tin.slice(0, 9), 10) % 11; + if (parseInt(full_year, 10) < 1986 && checkdigit === 10) { + if (parseInt(tin.slice(9), 10) !== 0) { return false; } + } else { + return false; + } + } + } + return true; +} + +/* + * de-AT validation function + * (Abgabenkontonummer, persons/entities) + * Verify TIN validity by calling luhnCheck() + */ +function deAtCheck(tin) { + return algorithms.luhnCheck(tin); +} + +/* + * de-DE validation function + * (Steueridentifikationsnummer (Steuer-IdNr.), persons only) + * Tests for single duplicate/triplicate value, then calculates ISO 7064 check (last) digit + * Partial implementation of spec (same result with both algorithms always) + */ +function deDeCheck(tin) { + // Split digits into an array for further processing + const digits = tin.split('').map(a => parseInt(a, 10)); + + // Fill array with strings of number positions + let occurrences = []; + for (let i = 0; i < digits.length - 1; i++) { + occurrences.push(''); + for (let j = 0; j < digits.length - 1; j++) { + if (digits[i] === digits[j]) { + occurrences[i] += j; + } + } + } + + // Remove digits with one occurrence and test for only one duplicate/triplicate + occurrences = occurrences.filter(a => a.length > 1); + if (occurrences.length !== 2 && occurrences.length !== 3) { return false; } + + // In case of triplicate value only two digits are allowed next to each other + if (occurrences[0].length === 3) { + const trip_locations = occurrences[0].split('').map(a => parseInt(a, 10)); + let recurrent = 0; // Amount of neighbor occurrences + for (let i = 0; i < trip_locations.length - 1; i++) { + if (trip_locations[i] + 1 === trip_locations[i + 1]) { + recurrent += 1; + } + } + if (recurrent === 2) { + return false; + } + } + return algorithms.iso7064Check(tin); +} + +/* + * dk-DK validation function + * (CPR-nummer (personnummer), persons only) + * Checks if birth date (first six digits) is valid and assigned to century (seventh) digit, + * and calculates check (last) digit + */ +function dkDkCheck(tin) { + tin = tin.replace(/\W/, ''); + + // Extract year, check if valid for given century digit and add century + let year = parseInt(tin.slice(4, 6), 10); + const century_digit = tin.slice(6, 7); + switch (century_digit) { + case '0': + case '1': + case '2': + case '3': + year = `19${year}`; + break; + case '4': + case '9': + if (year < 37) { + year = `20${year}`; + } else { + year = `19${year}`; + } + break; + default: + if (year < 37) { + year = `20${year}`; + } else if (year > 58) { + year = `18${year}`; + } else { + return false; + } + break; + } + // Add missing zero if needed + if (year.length === 3) { + year = [year.slice(0, 2), '0', year.slice(2)].join(''); + } + // Check date validity + const date = `${year}/${tin.slice(2, 4)}/${tin.slice(0, 2)}`; + if (!isDate(date, 'YYYY/MM/DD')) { return false; } + + // Split digits into an array for further processing + const digits = tin.split('').map(a => parseInt(a, 10)); + let checksum = 0; + let weight = 4; + // Multiply by weight and add to checksum + for (let i = 0; i < 9; i++) { + checksum += digits[i] * weight; + weight -= 1; + if (weight === 1) { + weight = 7; + } + } + checksum %= 11; + if (checksum === 1) { return false; } + return checksum === 0 ? digits[9] === 0 : digits[9] === 11 - checksum; +} + +/* + * el-CY validation function + * (Arithmos Forologikou Mitroou (AFM/ΑΦΜ), persons only) + * Verify TIN validity by calculating ASCII value of check (last) character + */ +function elCyCheck(tin) { + // split digits into an array for further processing + const digits = tin.slice(0, 8).split('').map(a => parseInt(a, 10)); + + let checksum = 0; + // add digits in even places + for (let i = 1; i < digits.length; i += 2) { + checksum += digits[i]; + } + + // add digits in odd places + for (let i = 0; i < digits.length; i += 2) { + if (digits[i] < 2) { + checksum += 1 - digits[i]; + } else { + checksum += (2 * (digits[i] - 2)) + 5; + if (digits[i] > 4) { + checksum += 2; + } + } + } + return String.fromCharCode((checksum % 26) + 65) === tin.charAt(8); +} + +/* + * el-GR validation function + * (Arithmos Forologikou Mitroou (AFM/ΑΦΜ), persons/entities) + * Verify TIN validity by calculating check (last) digit + * Algorithm not in DG TAXUD document- sourced from: + * - `http://epixeirisi.gr/%CE%9A%CE%A1%CE%99%CE%A3%CE%99%CE%9C%CE%91-%CE%98%CE%95%CE%9C%CE%91%CE%A4%CE%91-%CE%A6%CE%9F%CE%A1%CE%9F%CE%9B%CE%9F%CE%93%CE%99%CE%91%CE%A3-%CE%9A%CE%91%CE%99-%CE%9B%CE%9F%CE%93%CE%99%CE%A3%CE%A4%CE%99%CE%9A%CE%97%CE%A3/23791/%CE%91%CF%81%CE%B9%CE%B8%CE%BC%CF%8C%CF%82-%CE%A6%CE%BF%CF%81%CE%BF%CE%BB%CE%BF%CE%B3%CE%B9%CE%BA%CE%BF%CF%8D-%CE%9C%CE%B7%CF%84%CF%81%CF%8E%CE%BF%CF%85` + */ +function elGrCheck(tin) { + // split digits into an array for further processing + const digits = tin.split('').map(a => parseInt(a, 10)); + + let checksum = 0; + for (let i = 0; i < 8; i++) { + checksum += digits[i] * (2 ** (8 - i)); + } + return ((checksum % 11) % 10) === digits[8]; +} + +/* + * en-GB validation function (should go here if needed) + * (National Insurance Number (NINO) or Unique Taxpayer Reference (UTR), + * persons/entities respectively) + */ + +/* + * en-IE validation function + * (Personal Public Service Number (PPS No), persons only) + * Verify TIN validity by calculating check (second to last) character + */ +function enIeCheck(tin) { + let checksum = algorithms.reverseMultiplyAndSum(tin.split('').slice(0, 7).map(a => parseInt(a, 10)), 8); + if (tin.length === 9 && tin[8] !== 'W') { + checksum += (tin[8].charCodeAt(0) - 64) * 9; + } + + checksum %= 23; + if (checksum === 0) { + return tin[7].toUpperCase() === 'W'; + } + return tin[7].toUpperCase() === String.fromCharCode(64 + checksum); +} + +// Valid US IRS campus prefixes +const enUsCampusPrefix = { + andover: ['10', '12'], + atlanta: ['60', '67'], + austin: ['50', '53'], + brookhaven: ['01', '02', '03', '04', '05', '06', '11', '13', '14', '16', '21', '22', '23', '25', '34', '51', '52', '54', '55', '56', '57', '58', '59', '65'], + cincinnati: ['30', '32', '35', '36', '37', '38', '61'], + fresno: ['15', '24'], + internet: ['20', '26', '27', '45', '46', '47'], + kansas: ['40', '44'], + memphis: ['94', '95'], + ogden: ['80', '90'], + philadelphia: ['33', '39', '41', '42', '43', '46', '48', '62', '63', '64', '66', '68', '71', '72', '73', '74', '75', '76', '77', '81', '82', '83', '84', '85', '86', '87', '88', '91', '92', '93', '98', '99'], + sba: ['31'], +}; + +// Return an array of all US IRS campus prefixes +function enUsGetPrefixes() { + const prefixes = []; + + for (const location in enUsCampusPrefix) { + // https://github.com/gotwarlost/istanbul/blob/master/ignoring-code-for-coverage.md#ignoring-code-for-coverage-purposes + // istanbul ignore else + if (enUsCampusPrefix.hasOwnProperty(location)) { + prefixes.push(...enUsCampusPrefix[location]); + } + } + + return prefixes; +} + +/* + * en-US validation function + * Verify that the TIN starts with a valid IRS campus prefix + */ +function enUsCheck(tin) { + return enUsGetPrefixes().indexOf(tin.slice(0, 2)) !== -1; +} + +/* + * es-AR validation function + * Clave Única de Identificación Tributaria (CUIT/CUIL) + * Sourced from: + * - https://servicioscf.afip.gob.ar/publico/abc/ABCpaso2.aspx?id_nivel1=3036&id_nivel2=3040&p=Conceptos%20b%C3%A1sicos + * - https://es.wikipedia.org/wiki/Clave_%C3%9Anica_de_Identificaci%C3%B3n_Tributaria + */ + +function esArCheck(tin) { + let accum = 0; + let digits = tin.split(''); + let digit = parseInt(digits.pop(), 10); + for (let i = 0; i < digits.length; i++) { + accum += digits[9 - i] * (2 + (i % 6)); + } + let verif = 11 - (accum % 11); + if (verif === 11) { + verif = 0; + } else if (verif === 10) { + verif = 9; + } + return digit === verif; +} + +/* + * es-ES validation function + * (Documento Nacional de Identidad (DNI) + * or Número de Identificación de Extranjero (NIE), persons only) + * Verify TIN validity by calculating check (last) character + */ +function esEsCheck(tin) { + // Split characters into an array for further processing + let chars = tin.toUpperCase().split(''); + + // Replace initial letter if needed + if (isNaN(parseInt(chars[0], 10)) && chars.length > 1) { + let lead_replace = 0; + switch (chars[0]) { + case 'Y': + lead_replace = 1; + break; + case 'Z': + lead_replace = 2; + break; + default: + } + chars.splice(0, 1, lead_replace); + // Fill with zeros if smaller than proper + } else { + while (chars.length < 9) { + chars.unshift(0); + } + } + + // Calculate checksum and check according to lookup + const lookup = ['T', 'R', 'W', 'A', 'G', 'M', 'Y', 'F', 'P', 'D', 'X', 'B', 'N', 'J', 'Z', 'S', 'Q', 'V', 'H', 'L', 'C', 'K', 'E']; + chars = chars.join(''); + let checksum = (parseInt(chars.slice(0, 8), 10) % 23); + return chars[8] === lookup[checksum]; +} + +/* + * et-EE validation function + * (Isikukood (IK), persons only) + * Checks if birth date (century digit and six following) is valid and calculates check (last) digit + * Material not in DG TAXUD document sourced from: + * - `https://www.oecd.org/tax/automatic-exchange/crs-implementation-and-assistance/tax-identification-numbers/Estonia-TIN.pdf` + */ +function etEeCheck(tin) { + // Extract year and add century + let full_year = tin.slice(1, 3); + const century_digit = tin.slice(0, 1); + switch (century_digit) { + case '1': + case '2': + full_year = `18${full_year}`; + break; + case '3': + case '4': + full_year = `19${full_year}`; + break; + default: + full_year = `20${full_year}`; + break; + } + // Check date validity + const date = `${full_year}/${tin.slice(3, 5)}/${tin.slice(5, 7)}`; + if (!isDate(date, 'YYYY/MM/DD')) { return false; } + + // Split digits into an array for further processing + const digits = tin.split('').map(a => parseInt(a, 10)); + let checksum = 0; + let weight = 1; + // Multiply by weight and add to checksum + for (let i = 0; i < 10; i++) { + checksum += digits[i] * weight; + weight += 1; + if (weight === 10) { + weight = 1; + } + } + // Do again if modulo 11 of checksum is 10 + if (checksum % 11 === 10) { + checksum = 0; + weight = 3; + for (let i = 0; i < 10; i++) { + checksum += digits[i] * weight; + weight += 1; + if (weight === 10) { + weight = 1; + } + } + if (checksum % 11 === 10) { return digits[10] === 0; } + } + + return checksum % 11 === digits[10]; +} + +/* + * fi-FI validation function + * (Henkilötunnus (HETU), persons only) + * Checks if birth date (first six digits plus century symbol) is valid + * and calculates check (last) digit + */ +function fiFiCheck(tin) { + // Extract year and add century + let full_year = tin.slice(4, 6); + const century_symbol = tin.slice(6, 7); + switch (century_symbol) { + case '+': + full_year = `18${full_year}`; + break; + case '-': + full_year = `19${full_year}`; + break; + default: + full_year = `20${full_year}`; + break; + } + // Check date validity + const date = `${full_year}/${tin.slice(2, 4)}/${tin.slice(0, 2)}`; + if (!isDate(date, 'YYYY/MM/DD')) { return false; } + + // Calculate check character + let checksum = parseInt((tin.slice(0, 6) + tin.slice(7, 10)), 10) % 31; + if (checksum < 10) { return checksum === parseInt(tin.slice(10), 10); } + + checksum -= 10; + const letters_lookup = ['A', 'B', 'C', 'D', 'E', 'F', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y']; + return letters_lookup[checksum] === tin.slice(10); +} + +/* + * fr/nl-BE validation function + * (Numéro national (N.N.), persons only) + * Checks if birth date (first six digits) is valid and calculates check (last two) digits + */ +function frBeCheck(tin) { + // Zero month/day value is acceptable + if (tin.slice(2, 4) !== '00' || tin.slice(4, 6) !== '00') { + // Extract date from first six digits of TIN + const date = `${tin.slice(0, 2)}/${tin.slice(2, 4)}/${tin.slice(4, 6)}`; + if (!isDate(date, 'YY/MM/DD')) { return false; } + } + + let checksum = 97 - (parseInt(tin.slice(0, 9), 10) % 97); + const checkdigits = parseInt(tin.slice(9, 11), 10); + if (checksum !== checkdigits) { + checksum = 97 - (parseInt(`2${tin.slice(0, 9)}`, 10) % 97); + if (checksum !== checkdigits) { + return false; + } + } + return true; +} + +/* + * fr-FR validation function + * (Numéro fiscal de référence (numéro SPI), persons only) + * Verify TIN validity by calculating check (last three) digits + */ +function frFrCheck(tin) { + tin = tin.replace(/\s/g, ''); + const checksum = parseInt(tin.slice(0, 10), 10) % 511; + const checkdigits = parseInt(tin.slice(10, 13), 10); + return checksum === checkdigits; +} + +/* + * fr/lb-LU validation function + * (numéro d’identification personnelle, persons only) + * Verify birth date validity and run Luhn and Verhoeff checks + */ +function frLuCheck(tin) { + // Extract date and check validity + const date = `${tin.slice(0, 4)}/${tin.slice(4, 6)}/${tin.slice(6, 8)}`; + if (!isDate(date, 'YYYY/MM/DD')) { return false; } + + // Run Luhn check + if (!algorithms.luhnCheck(tin.slice(0, 12))) { return false; } + // Remove Luhn check digit and run Verhoeff check + return algorithms.verhoeffCheck(`${tin.slice(0, 11)}${tin[12]}`); +} + +/* + * hr-HR validation function + * (Osobni identifikacijski broj (OIB), persons/entities) + * Verify TIN validity by calling iso7064Check(digits) + */ +function hrHrCheck(tin) { + return algorithms.iso7064Check(tin); +} + +/* + * hu-HU validation function + * (Adóazonosító jel, persons only) + * Verify TIN validity by calculating check (last) digit + */ +function huHuCheck(tin) { + // split digits into an array for further processing + const digits = tin.split('').map(a => parseInt(a, 10)); + + let checksum = 8; + for (let i = 1; i < 9; i++) { + checksum += digits[i] * (i + 1); + } + return checksum % 11 === digits[9]; +} + +/* + * lt-LT validation function (should go here if needed) + * (Asmens kodas, persons/entities respectively) + * Current validation check is alias of etEeCheck- same format applies + */ + +/* + * it-IT first/last name validity check + * Accepts it-IT TIN-encoded names as a three-element character array and checks their validity + * Due to lack of clarity between resources ("Are only Italian consonants used? + * What happens if a person has X in their name?" etc.) only two test conditions + * have been implemented: + * Vowels may only be followed by other vowels or an X character + * and X characters after vowels may only be followed by other X characters. + */ +function itItNameCheck(name) { + // true at the first occurrence of a vowel + let vowelflag = false; + + // true at the first occurrence of an X AFTER vowel + // (to properly handle last names with X as consonant) + let xflag = false; + + for (let i = 0; i < 3; i++) { + if (!vowelflag && /[AEIOU]/.test(name[i])) { + vowelflag = true; + } else if (!xflag && vowelflag && (name[i] === 'X')) { + xflag = true; + } else if (i > 0) { + if (vowelflag && !xflag) { + if (!/[AEIOU]/.test(name[i])) { return false; } + } + if (xflag) { + if (!/X/.test(name[i])) { return false; } + } + } + } + return true; +} + +/* + * it-IT validation function + * (Codice fiscale (TIN-IT), persons only) + * Verify name, birth date and codice catastale validity + * and calculate check character. + * Material not in DG-TAXUD document sourced from: + * `https://en.wikipedia.org/wiki/Italian_fiscal_code` + */ +function itItCheck(tin) { + // Capitalize and split characters into an array for further processing + const chars = tin.toUpperCase().split(''); + + // Check first and last name validity calling itItNameCheck() + if (!itItNameCheck(chars.slice(0, 3))) { return false; } + if (!itItNameCheck(chars.slice(3, 6))) { return false; } + + // Convert letters in number spaces back to numbers if any + const number_locations = [6, 7, 9, 10, 12, 13, 14]; + const number_replace = { + L: '0', + M: '1', + N: '2', + P: '3', + Q: '4', + R: '5', + S: '6', + T: '7', + U: '8', + V: '9', + }; + for (const i of number_locations) { + if (chars[i] in number_replace) { + chars.splice(i, 1, number_replace[chars[i]]); + } + } + + // Extract month and day, and check date validity + const month_replace = { + A: '01', + B: '02', + C: '03', + D: '04', + E: '05', + H: '06', + L: '07', + M: '08', + P: '09', + R: '10', + S: '11', + T: '12', + }; + let month = month_replace[chars[8]]; + + let day = parseInt(chars[9] + chars[10], 10); + if (day > 40) { day -= 40; } + if (day < 10) { day = `0${day}`; } + + const date = `${chars[6]}${chars[7]}/${month}/${day}`; + if (!isDate(date, 'YY/MM/DD')) { return false; } + + // Calculate check character by adding up even and odd characters as numbers + let checksum = 0; + for (let i = 1; i < chars.length - 1; i += 2) { + let char_to_int = parseInt(chars[i], 10); + if (isNaN(char_to_int)) { + char_to_int = chars[i].charCodeAt(0) - 65; + } + checksum += char_to_int; + } + + const odd_convert = { // Maps of characters at odd places + A: 1, + B: 0, + C: 5, + D: 7, + E: 9, + F: 13, + G: 15, + H: 17, + I: 19, + J: 21, + K: 2, + L: 4, + M: 18, + N: 20, + O: 11, + P: 3, + Q: 6, + R: 8, + S: 12, + T: 14, + U: 16, + V: 10, + W: 22, + X: 25, + Y: 24, + Z: 23, + 0: 1, + 1: 0, + }; + for (let i = 0; i < chars.length - 1; i += 2) { + let char_to_int = 0; + if (chars[i] in odd_convert) { + char_to_int = odd_convert[chars[i]]; + } else { + let multiplier = parseInt(chars[i], 10); + char_to_int = (2 * multiplier) + 1; + if (multiplier > 4) { + char_to_int += 2; + } + } + checksum += char_to_int; + } + + if (String.fromCharCode(65 + (checksum % 26)) !== chars[15]) { return false; } + return true; +} + +/* + * lv-LV validation function + * (Personas kods (PK), persons only) + * Check validity of birth date and calculate check (last) digit + * Support only for old format numbers (not starting with '32', issued before 2017/07/01) + * Material not in DG TAXUD document sourced from: + * `https://boot.ritakafija.lv/forums/index.php?/topic/88314-personas-koda-algoritms-%C4%8Deksumma/` + */ +function lvLvCheck(tin) { + tin = tin.replace(/\W/, ''); + // Extract date from TIN + const day = tin.slice(0, 2); + if (day !== '32') { // No date/checksum check if new format + const month = tin.slice(2, 4); + if (month !== '00') { // No date check if unknown month + let full_year = tin.slice(4, 6); + switch (tin[6]) { + case '0': + full_year = `18${full_year}`; + break; + case '1': + full_year = `19${full_year}`; + break; + default: + full_year = `20${full_year}`; + break; + } + // Check date validity + const date = `${full_year}/${tin.slice(2, 4)}/${day}`; + if (!isDate(date, 'YYYY/MM/DD')) { return false; } + } + + // Calculate check digit + let checksum = 1101; + const multip_lookup = [1, 6, 3, 7, 9, 10, 5, 8, 4, 2]; + for (let i = 0; i < tin.length - 1; i++) { + checksum -= parseInt(tin[i], 10) * multip_lookup[i]; + } + return (parseInt(tin[10], 10) === checksum % 11); + } + return true; +} + +/* + * mt-MT validation function + * (Identity Card Number or Unique Taxpayer Reference, persons/entities) + * Verify Identity Card Number structure (no other tests found) + */ +function mtMtCheck(tin) { + if (tin.length !== 9) { // No tests for UTR + let chars = tin.toUpperCase().split(''); + // Fill with zeros if smaller than proper + while (chars.length < 8) { + chars.unshift(0); + } + // Validate format according to last character + switch (tin[7]) { + case 'A': + case 'P': + if (parseInt(chars[6], 10) === 0) { return false; } + break; + default: { + const first_part = parseInt(chars.join('').slice(0, 5), 10); + if (first_part > 32000) { return false; } + const second_part = parseInt(chars.join('').slice(5, 7), 10); + if (first_part === second_part) { return false; } + } + } + } + return true; +} + +/* + * nl-NL validation function + * (Burgerservicenummer (BSN) or Rechtspersonen Samenwerkingsverbanden Informatie Nummer (RSIN), + * persons/entities respectively) + * Verify TIN validity by calculating check (last) digit (variant of MOD 11) + */ +function nlNlCheck(tin) { + return algorithms.reverseMultiplyAndSum(tin.split('').slice(0, 8).map(a => parseInt(a, 10)), 9) % 11 === parseInt(tin[8], 10); +} + +/* + * pl-PL validation function + * (Powszechny Elektroniczny System Ewidencji Ludności (PESEL) + * or Numer identyfikacji podatkowej (NIP), persons/entities) + * Verify TIN validity by validating birth date (PESEL) and calculating check (last) digit + */ +function plPlCheck(tin) { + // NIP + if (tin.length === 10) { + // Calculate last digit by multiplying with lookup + const lookup = [6, 5, 7, 2, 3, 4, 5, 6, 7]; + let checksum = 0; + for (let i = 0; i < lookup.length; i++) { + checksum += parseInt(tin[i], 10) * lookup[i]; + } + checksum %= 11; + if (checksum === 10) { return false; } + return (checksum === parseInt(tin[9], 10)); + } + + // PESEL + // Extract full year using month + let full_year = tin.slice(0, 2); + let month = parseInt(tin.slice(2, 4), 10); + if (month > 80) { + full_year = `18${full_year}`; + month -= 80; + } else if (month > 60) { + full_year = `22${full_year}`; + month -= 60; + } else if (month > 40) { + full_year = `21${full_year}`; + month -= 40; + } else if (month > 20) { + full_year = `20${full_year}`; + month -= 20; + } else { + full_year = `19${full_year}`; + } + // Add leading zero to month if needed + if (month < 10) { month = `0${month}`; } + // Check date validity + const date = `${full_year}/${month}/${tin.slice(4, 6)}`; + if (!isDate(date, 'YYYY/MM/DD')) { return false; } + + // Calculate last digit by multiplying with odd one-digit numbers except 5 + let checksum = 0; + let multiplier = 1; + for (let i = 0; i < tin.length - 1; i++) { + checksum += (parseInt(tin[i], 10) * multiplier) % 10; + multiplier += 2; + if (multiplier > 10) { + multiplier = 1; + } else if (multiplier === 5) { + multiplier += 2; + } + } + checksum = 10 - (checksum % 10); + return checksum === parseInt(tin[10], 10); +} + +/* +* pt-BR validation function +* (Cadastro de Pessoas Físicas (CPF, persons) +* Cadastro Nacional de Pessoas Jurídicas (CNPJ, entities) +* Both inputs will be validated +*/ + +function ptBrCheck(tin) { + if (tin.length === 11) { + let sum; + let remainder; + sum = 0; + + if ( // Reject known invalid CPFs + tin === '11111111111' || + tin === '22222222222' || + tin === '33333333333' || + tin === '44444444444' || + tin === '55555555555' || + tin === '66666666666' || + tin === '77777777777' || + tin === '88888888888' || + tin === '99999999999' || + tin === '00000000000' + ) return false; + + for (let i = 1; i <= 9; i++) sum += parseInt(tin.substring(i - 1, i), 10) * (11 - i); + remainder = (sum * 10) % 11; + if (remainder === 10) remainder = 0; + if (remainder !== parseInt(tin.substring(9, 10), 10)) return false; + sum = 0; + + for (let i = 1; i <= 10; i++) sum += parseInt(tin.substring(i - 1, i), 10) * (12 - i); + remainder = (sum * 10) % 11; + if (remainder === 10) remainder = 0; + if (remainder !== parseInt(tin.substring(10, 11), 10)) return false; + + return true; + } + + if ( // Reject know invalid CNPJs + tin === '00000000000000' || + tin === '11111111111111' || + tin === '22222222222222' || + tin === '33333333333333' || + tin === '44444444444444' || + tin === '55555555555555' || + tin === '66666666666666' || + tin === '77777777777777' || + tin === '88888888888888' || + tin === '99999999999999') { return false; } + + let length = tin.length - 2; + let identifiers = tin.substring(0, length); + let verificators = tin.substring(length); + let sum = 0; + let pos = length - 7; + + for (let i = length; i >= 1; i--) { + sum += identifiers.charAt(length - i) * pos; + pos -= 1; + if (pos < 2) { pos = 9; } + } + let result = sum % 11 < 2 ? 0 : 11 - (sum % 11); + if (result !== parseInt(verificators.charAt(0), 10)) { return false; } + + length += 1; + identifiers = tin.substring(0, length); + sum = 0; + pos = length - 7; + for (let i = length; i >= 1; i--) { + sum += identifiers.charAt(length - i) * pos; + pos -= 1; + if (pos < 2) { pos = 9; } + } + result = sum % 11 < 2 ? 0 : 11 - (sum % 11); + if (result !== parseInt(verificators.charAt(1), 10)) { return false; } + + return true; +} + +/* + * pt-PT validation function + * (Número de identificação fiscal (NIF), persons/entities) + * Verify TIN validity by calculating check (last) digit (variant of MOD 11) + */ +function ptPtCheck(tin) { + let checksum = 11 - (algorithms.reverseMultiplyAndSum(tin.split('').slice(0, 8).map(a => parseInt(a, 10)), 9) % 11); + if (checksum > 9) { return parseInt(tin[8], 10) === 0; } + return checksum === parseInt(tin[8], 10); +} + +/* + * ro-RO validation function + * (Cod Numeric Personal (CNP) or Cod de înregistrare fiscală (CIF), + * persons only) + * Verify CNP validity by calculating check (last) digit (test not found for CIF) + * Material not in DG TAXUD document sourced from: + * `https://en.wikipedia.org/wiki/National_identification_number#Romania` + */ +function roRoCheck(tin) { + if (tin.slice(0, 4) !== '9000') { // No test found for this format + // Extract full year using century digit if possible + let full_year = tin.slice(1, 3); + switch (tin[0]) { + case '1': + case '2': + full_year = `19${full_year}`; + break; + case '3': + case '4': + full_year = `18${full_year}`; + break; + case '5': + case '6': + full_year = `20${full_year}`; + break; + default: + } + + // Check date validity + const date = `${full_year}/${tin.slice(3, 5)}/${tin.slice(5, 7)}`; + if (date.length === 8) { + if (!isDate(date, 'YY/MM/DD')) { return false; } + } else if (!isDate(date, 'YYYY/MM/DD')) { return false; } + + // Calculate check digit + const digits = tin.split('').map(a => parseInt(a, 10)); + const multipliers = [2, 7, 9, 1, 4, 6, 3, 5, 8, 2, 7, 9]; + let checksum = 0; + for (let i = 0; i < multipliers.length; i++) { + checksum += digits[i] * multipliers[i]; + } + if (checksum % 11 === 10) { return digits[12] === 1; } + return digits[12] === checksum % 11; + } + return true; +} + +/* + * sk-SK validation function + * (Rodné číslo (RČ) or bezvýznamové identifikačné číslo (BIČ), persons only) + * Checks validity of pre-1954 birth numbers (rodné číslo) only + * Due to the introduction of the pseudo-random BIČ it is not possible to test + * post-1954 birth numbers without knowing whether they are BIČ or RČ beforehand + */ +function skSkCheck(tin) { + if (tin.length === 9) { + tin = tin.replace(/\W/, ''); + if (tin.slice(6) === '000') { return false; } // Three-zero serial not assigned before 1954 + + // Extract full year from TIN length + let full_year = parseInt(tin.slice(0, 2), 10); + if (full_year > 53) { return false; } + if (full_year < 10) { + full_year = `190${full_year}`; + } else { + full_year = `19${full_year}`; + } + + // Extract month from TIN and normalize + let month = parseInt(tin.slice(2, 4), 10); + if (month > 50) { + month -= 50; + } + if (month < 10) { month = `0${month}`; } + + // Check date validity + const date = `${full_year}/${month}/${tin.slice(4, 6)}`; + if (!isDate(date, 'YYYY/MM/DD')) { return false; } + } + return true; +} + +/* + * sl-SI validation function + * (Davčna številka, persons/entities) + * Verify TIN validity by calculating check (last) digit (variant of MOD 11) + */ +function slSiCheck(tin) { + let checksum = 11 - (algorithms.reverseMultiplyAndSum(tin.split('').slice(0, 7).map(a => parseInt(a, 10)), 8) % 11); + if (checksum === 10) { return parseInt(tin[7], 10) === 0; } + return checksum === parseInt(tin[7], 10); +} + +/* + * sv-SE validation function + * (Personnummer or samordningsnummer, persons only) + * Checks validity of birth date and calls luhnCheck() to validate check (last) digit + */ +function svSeCheck(tin) { + // Make copy of TIN and normalize to two-digit year form + let tin_copy = tin.slice(0); + if (tin.length > 11) { + tin_copy = tin_copy.slice(2); + } + + // Extract date of birth + let full_year = ''; + const month = tin_copy.slice(2, 4); + let day = parseInt(tin_copy.slice(4, 6), 10); + if (tin.length > 11) { + full_year = tin.slice(0, 4); + } else { + full_year = tin.slice(0, 2); + if (tin.length === 11 && day < 60) { + // Extract full year from centenarian symbol + // Should work just fine until year 10000 or so + let current_year = new Date().getFullYear().toString(); + const current_century = parseInt(current_year.slice(0, 2), 10); + current_year = parseInt(current_year, 10); + if (tin[6] === '-') { + if (parseInt(`${current_century}${full_year}`, 10) > current_year) { + full_year = `${current_century - 1}${full_year}`; + } else { + full_year = `${current_century}${full_year}`; + } + } else { + full_year = `${current_century - 1}${full_year}`; + if (current_year - parseInt(full_year, 10) < 100) { return false; } + } + } + } + + // Normalize day and check date validity + if (day > 60) { day -= 60; } + if (day < 10) { day = `0${day}`; } + const date = `${full_year}/${month}/${day}`; + if (date.length === 8) { + if (!isDate(date, 'YY/MM/DD')) { return false; } + } else if (!isDate(date, 'YYYY/MM/DD')) { return false; } + + return algorithms.luhnCheck(tin.replace(/\W/, '')); +} + +/** + * uk-UA validation function + * Verify TIN validity by calculating check (last) digit (variant of MOD 11) + */ +function ukUaCheck(tin) { + // Calculate check digit + const digits = tin.split('').map(a => parseInt(a, 10)); + const multipliers = [-1, 5, 7, 9, 4, 6, 10, 5, 7]; + let checksum = 0; + for (let i = 0; i < multipliers.length; i++) { + checksum += digits[i] * multipliers[i]; + } + return checksum % 11 === 10 ? digits[9] === 0 : digits[9] === checksum % 11; +} + +// Locale lookup objects + +/* + * Tax id regex formats for various locales + * + * Where not explicitly specified in DG-TAXUD document both + * uppercase and lowercase letters are acceptable. + */ +const taxIdFormat = { + 'bg-BG': /^\d{10}$/, + 'cs-CZ': /^\d{6}\/{0,1}\d{3,4}$/, + 'de-AT': /^\d{9}$/, + 'de-DE': /^[1-9]\d{10}$/, + 'dk-DK': /^\d{6}-{0,1}\d{4}$/, + 'el-CY': /^[09]\d{7}[A-Z]$/, + 'el-GR': /^([0-4]|[7-9])\d{8}$/, + 'en-CA': /^\d{9}$/, + 'en-GB': /^\d{10}$|^(?!GB|NK|TN|ZZ)(?![DFIQUV])[A-Z](?![DFIQUVO])[A-Z]\d{6}[ABCD ]$/i, + 'en-IE': /^\d{7}[A-W][A-IW]{0,1}$/i, + 'en-US': /^\d{2}[- ]{0,1}\d{7}$/, + 'es-AR': /(20|23|24|27|30|33|34)[0-9]{8}[0-9]/, + 'es-ES': /^(\d{0,8}|[XYZKLM]\d{7})[A-HJ-NP-TV-Z]$/i, + 'et-EE': /^[1-6]\d{6}(00[1-9]|0[1-9][0-9]|[1-6][0-9]{2}|70[0-9]|710)\d$/, + 'fi-FI': /^\d{6}[-+A]\d{3}[0-9A-FHJ-NPR-Y]$/i, + 'fr-BE': /^\d{11}$/, + 'fr-FR': /^[0-3]\d{12}$|^[0-3]\d\s\d{2}(\s\d{3}){3}$/, // Conforms both to official spec and provided example + 'fr-LU': /^\d{13}$/, + 'hr-HR': /^\d{11}$/, + 'hu-HU': /^8\d{9}$/, + 'it-IT': /^[A-Z]{6}[L-NP-V0-9]{2}[A-EHLMPRST][L-NP-V0-9]{2}[A-ILMZ][L-NP-V0-9]{3}[A-Z]$/i, + 'lv-LV': /^\d{6}-{0,1}\d{5}$/, // Conforms both to DG TAXUD spec and original research + 'mt-MT': /^\d{3,7}[APMGLHBZ]$|^([1-8])\1\d{7}$/i, + 'nl-NL': /^\d{9}$/, + 'pl-PL': /^\d{10,11}$/, + 'pt-BR': /(?:^\d{11}$)|(?:^\d{14}$)/, + 'pt-PT': /^\d{9}$/, + 'ro-RO': /^\d{13}$/, + 'sk-SK': /^\d{6}\/{0,1}\d{3,4}$/, + 'sl-SI': /^[1-9]\d{7}$/, + 'sv-SE': /^(\d{6}[-+]{0,1}\d{4}|(18|19|20)\d{6}[-+]{0,1}\d{4})$/, + 'uk-UA': /^\d{10}$/, +}; +// taxIdFormat locale aliases +taxIdFormat['lb-LU'] = taxIdFormat['fr-LU']; +taxIdFormat['lt-LT'] = taxIdFormat['et-EE']; +taxIdFormat['nl-BE'] = taxIdFormat['fr-BE']; +taxIdFormat['fr-CA'] = taxIdFormat['en-CA']; + +// Algorithmic tax id check functions for various locales +const taxIdCheck = { + 'bg-BG': bgBgCheck, + 'cs-CZ': csCzCheck, + 'de-AT': deAtCheck, + 'de-DE': deDeCheck, + 'dk-DK': dkDkCheck, + 'el-CY': elCyCheck, + 'el-GR': elGrCheck, + 'en-CA': isCanadianSIN, + 'en-IE': enIeCheck, + 'en-US': enUsCheck, + 'es-AR': esArCheck, + 'es-ES': esEsCheck, + 'et-EE': etEeCheck, + 'fi-FI': fiFiCheck, + 'fr-BE': frBeCheck, + 'fr-FR': frFrCheck, + 'fr-LU': frLuCheck, + 'hr-HR': hrHrCheck, + 'hu-HU': huHuCheck, + 'it-IT': itItCheck, + 'lv-LV': lvLvCheck, + 'mt-MT': mtMtCheck, + 'nl-NL': nlNlCheck, + 'pl-PL': plPlCheck, + 'pt-BR': ptBrCheck, + 'pt-PT': ptPtCheck, + 'ro-RO': roRoCheck, + 'sk-SK': skSkCheck, + 'sl-SI': slSiCheck, + 'sv-SE': svSeCheck, + 'uk-UA': ukUaCheck, +}; +// taxIdCheck locale aliases +taxIdCheck['lb-LU'] = taxIdCheck['fr-LU']; +taxIdCheck['lt-LT'] = taxIdCheck['et-EE']; +taxIdCheck['nl-BE'] = taxIdCheck['fr-BE']; +taxIdCheck['fr-CA'] = taxIdCheck['en-CA']; + +// Regexes for locales where characters should be omitted before checking format +const allsymbols = /[-\\\/!@#$%\^&\*\(\)\+\=\[\]]+/g; +const sanitizeRegexes = { + 'de-AT': allsymbols, + 'de-DE': /[\/\\]/g, + 'fr-BE': allsymbols, +}; +// sanitizeRegexes locale aliases +sanitizeRegexes['nl-BE'] = sanitizeRegexes['fr-BE']; + +/* + * Validator function + * Return true if the passed string is a valid tax identification number + * for the specified locale. + * Throw an error exception if the locale is not supported. + */ +export default function isTaxID(str, locale = 'en-US') { + assertString(str); + // Copy TIN to avoid replacement if sanitized + let strcopy = str.slice(0); + + if (locale in taxIdFormat) { + if (locale in sanitizeRegexes) { + strcopy = strcopy.replace(sanitizeRegexes[locale], ''); + } + if (!taxIdFormat[locale].test(strcopy)) { + return false; + } + + if (locale in taxIdCheck) { + return taxIdCheck[locale](strcopy); + } + // Fallthrough; not all locales have algorithmic checks + return true; + } + throw new Error(`Invalid locale '${locale}'`); +} diff --git a/src/lib/isTime.js b/src/lib/isTime.js new file mode 100644 index 000000000..3169864c2 --- /dev/null +++ b/src/lib/isTime.js @@ -0,0 +1,25 @@ +import merge from './util/merge'; + +const default_time_options = { + hourFormat: 'hour24', + mode: 'default', +}; + +const formats = { + hour24: { + default: /^([01]?[0-9]|2[0-3]):([0-5][0-9])$/, + withSeconds: /^([01]?[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$/, + withOptionalSeconds: /^([01]?[0-9]|2[0-3]):([0-5][0-9])(?::([0-5][0-9]))?$/, + }, + hour12: { + default: /^(0?[1-9]|1[0-2]):([0-5][0-9]) (A|P)M$/, + withSeconds: /^(0?[1-9]|1[0-2]):([0-5][0-9]):([0-5][0-9]) (A|P)M$/, + withOptionalSeconds: /^(0?[1-9]|1[0-2]):([0-5][0-9])(?::([0-5][0-9]))? (A|P)M$/, + }, +}; + +export default function isTime(input, options) { + options = merge(options, default_time_options); + if (typeof input !== 'string') return false; + return formats[options.hourFormat][options.mode].test(input); +} diff --git a/src/lib/isULID.js b/src/lib/isULID.js new file mode 100644 index 000000000..2ace6f471 --- /dev/null +++ b/src/lib/isULID.js @@ -0,0 +1,6 @@ +import assertString from './util/assertString'; + +export default function isULID(str) { + assertString(str); + return /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/i.test(str); +} diff --git a/src/lib/isURL.js b/src/lib/isURL.js index ea4e14778..b55f8e031 100644 --- a/src/lib/isURL.js +++ b/src/lib/isURL.js @@ -1,45 +1,80 @@ import assertString from './util/assertString'; +import checkHost from './util/checkHost'; +import includes from './util/includesString'; import isFQDN from './isFQDN'; import isIP from './isIP'; import merge from './util/merge'; +/* +options for isURL method + +protocols - valid protocols can be modified with this option. +require_tld - If set to false isURL will not check if the URL's host includes a top-level domain. +require_protocol - if set to true isURL will return false if protocol is not present in the URL. +require_host - if set to false isURL will not check if host is present in the URL. +require_port - if set to true isURL will check if port is present in the URL. +require_valid_protocol - isURL will check if the URL's protocol is present in the protocols option. +allow_underscores - if set to true, the validator will allow underscores in the URL. +host_whitelist - if set to an array of strings or regexp, and the domain matches none of the strings + defined in it, the validation fails. +host_blacklist - if set to an array of strings or regexp, and the domain matches any of the strings + defined in it, the validation fails. +allow_trailing_dot - if set to true, the validator will allow the domain to end with + a `.` character. +allow_protocol_relative_urls - if set to true protocol relative URLs will be allowed. +allow_fragments - if set to false isURL will return false if fragments are present. +allow_query_components - if set to false isURL will return false if query components are present. +disallow_auth - if set to true, the validator will fail if the URL contains an authentication + component, e.g. `http://username:password@example.com` +validate_length - if set to false isURL will skip string length validation. `max_allowed_length` + will be ignored if this is set as `false`. +max_allowed_length - if set, isURL will not allow URLs longer than the specified value (default is + 2084 that IE maximum URL length). + +*/ + + const default_url_options = { protocols: ['http', 'https', 'ftp'], require_tld: true, require_protocol: false, require_host: true, + require_port: false, require_valid_protocol: true, allow_underscores: false, allow_trailing_dot: false, allow_protocol_relative_urls: false, + allow_fragments: true, + allow_query_components: true, + validate_length: true, + max_allowed_length: 2084, }; 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 || url.length >= 2083 || /[\s<>]/.test(url)) { + if (!url || /[\s<>]/.test(url)) { return false; } if (url.indexOf('mailto:') === 0) { return false; } options = merge(options, default_url_options); + + if (options.validate_length && url.length > options.max_allowed_length) { + return false; + } + + if (!options.allow_fragments && includes(url, '#')) { + return false; + } + + if (!options.allow_query_components && (includes(url, '?') || includes(url, '&'))) { + return false; + } + let protocol, auth, host, hostname, port, port_str, split, ipv6; split = url.split('#'); @@ -48,18 +83,107 @@ export default function isURL(url, options) { split = url.split('?'); url = split.shift(); - split = url.split('://'); - if (split.length > 1) { - protocol = split.shift(); + // Replaced the 'split("://")' logic with a regex to match the protocol. + // This correctly identifies schemes like `javascript:` which don't use `//`. + // However, we need to be careful not to confuse authentication credentials (user:password@host) + // with protocols. A colon before an @ symbol might be part of auth, not a protocol separator. + const protocol_match = url.match(/^([a-z][a-z0-9+\-.]*):/i); + let had_explicit_protocol = false; + + const cleanUpProtocol = (potential_protocol) => { + had_explicit_protocol = true; + protocol = potential_protocol.toLowerCase(); + if (options.require_valid_protocol && options.protocols.indexOf(protocol) === -1) { + // The identified protocol is not in the allowed list. return false; } + + // Remove the protocol from the URL string. + return url.substring(protocol_match[0].length); + }; + + if (protocol_match) { + const potential_protocol = protocol_match[1]; + const after_colon = url.substring(protocol_match[0].length); + + // Check if what follows looks like authentication credentials (user:password@host) + // rather than a protocol. This happens when: + // 1. There's no `//` after the colon (protocols like `http://` have this) + // 2. There's an `@` symbol before any `/` + // 3. The part before `@` contains only valid auth characters (alphanumeric, -, _, ., %, :) + const starts_with_slashes = after_colon.slice(0, 2) === '//'; + + if (!starts_with_slashes) { + const first_slash_position = after_colon.indexOf('/'); + const before_slash = first_slash_position === -1 + ? after_colon + : after_colon.substring(0, first_slash_position); + const at_position = before_slash.indexOf('@'); + + if (at_position !== -1) { + const before_at = before_slash.substring(0, at_position); + const valid_auth_regex = /^[a-zA-Z0-9\-_.%:]*$/; + const is_valid_auth = valid_auth_regex.test(before_at); + + if (is_valid_auth) { + // This looks like authentication (e.g., user:password@host), not a protocol + if (options.require_protocol) { + return false; + } + + // Don't consume the colon; let the auth parsing handle it later + } else { + // This looks like a malicious protocol (e.g., javascript:alert();@host) + url = cleanUpProtocol(potential_protocol); + + if (url === false) { + return false; + } + } + } else { + // No @ symbol found. Check if this could be a port number instead of a protocol. + // If what's after the colon is numeric (or starts with a digit and contains only + // valid port characters until a path separator), it's likely hostname:port, not a protocol. + const looks_like_port = /^[0-9]/.test(after_colon); + + if (looks_like_port) { + // This looks like hostname:port, not a protocol + if (options.require_protocol) { + return false; + } + // Don't consume anything; let it be parsed as hostname:port + } else { + // This is definitely a protocol + url = cleanUpProtocol(potential_protocol); + + if (url === false) { + return false; + } + } + } + } else { + // Starts with '//', this is definitely a protocol like http:// + url = cleanUpProtocol(potential_protocol); + + if (url === false) { + return false; + } + } } else if (options.require_protocol) { return false; - } else if (options.allow_protocol_relative_urls && url.substr(0, 2) === '//') { - split[0] = url.substr(2); } - url = split.join('://'); + + // Handle leading '//' only as protocol-relative when there was NO explicit protocol. + // If there was an explicit protocol, '//' is the normal separator + // and should be stripped unconditionally. + if (url.slice(0, 2) === '//') { + if (!had_explicit_protocol && !options.allow_protocol_relative_urls) { + return false; + } + + url = url.slice(2); + } if (url === '') { return false; @@ -74,10 +198,20 @@ export default function isURL(url, options) { split = url.split('@'); if (split.length > 1) { + if (options.disallow_auth) { + return false; + } + if (split[0] === '') { + return false; + } auth = split.shift(); if (auth.indexOf(':') >= 0 && auth.split(':').length > 2) { return false; } + const [user, password] = auth.split(':'); + if (user === '' && password === '') { + return false; + } } hostname = split.join('@'); @@ -96,11 +230,21 @@ export default function isURL(url, options) { } } - if (port_str !== null) { + if (port_str !== null && port_str.length > 0) { port = parseInt(port_str, 10); if (!/^[0-9]+$/.test(port_str) || port <= 0 || port > 65535) { return false; } + } else if (options.require_port) { + return false; + } + + if (options.host_whitelist) { + return checkHost(host, options.host_whitelist); + } + + if (host === '' && !options.require_host) { + return true; } if (!isIP(host) && !isFQDN(host, options) && (!ipv6 || !isIP(ipv6, 6))) { @@ -109,9 +253,6 @@ export default function isURL(url, options) { host = host || ipv6; - if (options.host_whitelist && !checkHost(host, options.host_whitelist)) { - return false; - } if (options.host_blacklist && checkHost(host, options.host_blacklist)) { return false; } diff --git a/src/lib/isUUID.js b/src/lib/isUUID.js index 61d938ac3..9d6040f04 100644 --- a/src/lib/isUUID.js +++ b/src/lib/isUUID.js @@ -1,14 +1,29 @@ import assertString from './util/assertString'; const uuid = { - 3: /^[0-9A-F]{8}-[0-9A-F]{4}-3[0-9A-F]{3}-[0-9A-F]{4}-[0-9A-F]{12}$/i, + 1: /^[0-9A-F]{8}-[0-9A-F]{4}-1[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i, + 2: /^[0-9A-F]{8}-[0-9A-F]{4}-2[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i, + 3: /^[0-9A-F]{8}-[0-9A-F]{4}-3[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i, 4: /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i, 5: /^[0-9A-F]{8}-[0-9A-F]{4}-5[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i, - all: /^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i, + 6: /^[0-9A-F]{8}-[0-9A-F]{4}-6[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i, + 7: /^[0-9A-F]{8}-[0-9A-F]{4}-7[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i, + 8: /^[0-9A-F]{8}-[0-9A-F]{4}-8[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i, + + nil: /^00000000-0000-0000-0000-000000000000$/i, + max: /^ffffffff-ffff-ffff-ffff-ffffffffffff$/i, + loose: /^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i, + + // From https://github.com/uuidjs/uuid/blob/main/src/regex.js + all: /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/i, }; -export default function isUUID(str, version = 'all') { +export default function isUUID(str, version) { assertString(str); - const pattern = uuid[version]; - return pattern && pattern.test(str); + + if (version === undefined || version === null) { + version = 'all'; + } + + return version in uuid ? uuid[version].test(str) : false; } diff --git a/src/lib/isVAT.js b/src/lib/isVAT.js new file mode 100644 index 000000000..1ec2c5991 --- /dev/null +++ b/src/lib/isVAT.js @@ -0,0 +1,140 @@ +import assertString from './util/assertString'; +import * as algorithms from './util/algorithms'; + +const AU = (str) => { + const match = str.match(/^(AU)?(\d{11})$/); + if (!match) { + return false; + } + // @see {@link https://abr.business.gov.au/Help/AbnFormat} + const weights = [10, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19]; + str = str.replace(/^AU/, ''); + const ABN = (parseInt(str.slice(0, 1), 10) - 1).toString() + str.slice(1); + let total = 0; + for (let i = 0; i < 11; i++) { + total += weights[i] * ABN.charAt(i); + } + return (total !== 0 && total % 89 === 0); +}; + +const CH = (str) => { + // @see {@link https://www.ech.ch/de/ech/ech-0097/5.2.0} + const hasValidCheckNumber = (digits) => { + const lastDigit = digits.pop(); // used as check number + const weights = [5, 4, 3, 2, 7, 6, 5, 4]; + const calculatedCheckNumber = (11 - (digits.reduce((acc, el, idx) => + acc + (el * weights[idx]), 0) % 11)) % 11; + + return lastDigit === calculatedCheckNumber; + }; + + // @see {@link https://www.estv.admin.ch/estv/de/home/mehrwertsteuer/uid/mwst-uid-nummer.html} + return /^(CHE[- ]?)?(\d{9}|(\d{3}\.\d{3}\.\d{3})|(\d{3} \d{3} \d{3})) ?(TVA|MWST|IVA)?$/.test(str) && hasValidCheckNumber((str.match(/\d/g).map(el => +el))); +}; + +const PT = (str) => { + const match = str.match(/^(PT)?(\d{9})$/); + if (!match) { + return false; + } + + const tin = match[2]; + + const checksum = 11 - (algorithms.reverseMultiplyAndSum(tin.split('').slice(0, 8).map(a => parseInt(a, 10)), 9) % 11); + if (checksum > 9) { + return parseInt(tin[8], 10) === 0; + } + return checksum === parseInt(tin[8], 10); +}; + +export const vatMatchers = { + /** + * European Union VAT identification numbers + */ + AT: str => /^(AT)?U\d{8}$/.test(str), + BE: str => /^(BE)?\d{10}$/.test(str), + BG: str => /^(BG)?\d{9,10}$/.test(str), + HR: str => /^(HR)?\d{11}$/.test(str), + CY: str => /^(CY)?\w{9}$/.test(str), + CZ: str => /^(CZ)?\d{8,10}$/.test(str), + DK: str => /^(DK)?\d{8}$/.test(str), + EE: str => /^(EE)?\d{9}$/.test(str), + FI: str => /^(FI)?\d{8}$/.test(str), + FR: str => /^(FR)([A-Z0-9]{2}\d{9})$/.test(str), + DE: str => /^(DE)?\d{9}$/.test(str), + EL: str => /^(EL)?\d{9}$/.test(str), + HU: str => /^(HU)?\d{8}$/.test(str), + IE: str => /^(IE)?\d{7}\w{1}(W)?$/.test(str), + IT: str => /^(IT)?\d{11}$/.test(str), + LV: str => /^(LV)?\d{11}$/.test(str), + LT: str => /^(LT)?\d{9,12}$/.test(str), + LU: str => /^(LU)?\d{8}$/.test(str), + MT: str => /^(MT)?\d{8}$/.test(str), + NL: str => /^(NL)?\d{9}B\d{2}$/.test(str), + PL: str => /^(PL)?(\d{10}|(\d{3}-\d{3}-\d{2}-\d{2})|(\d{3}-\d{2}-\d{2}-\d{3}))$/.test(str), + PT, + RO: str => /^(RO)?\d{2,10}$/.test(str), + SK: str => /^(SK)?\d{10}$/.test(str), + SI: str => /^(SI)?\d{8}$/.test(str), + ES: str => /^(ES)?\w\d{7}[A-Z]$/.test(str), + SE: str => /^(SE)?\d{12}$/.test(str), + + /** + * VAT numbers of non-EU countries + */ + AL: str => /^(AL)?\w{9}[A-Z]$/.test(str), + MK: str => /^(MK)?\d{13}$/.test(str), + AU, + BY: str => /^(УНП )?\d{9}$/.test(str), + CA: str => /^(CA)?\d{9}$/.test(str), + IS: str => /^(IS)?\d{5,6}$/.test(str), + IN: str => /^(IN)?\d{15}$/.test(str), + ID: str => /^(ID)?(\d{15}|(\d{2}.\d{3}.\d{3}.\d{1}-\d{3}.\d{3}))$/.test(str), + IL: str => /^(IL)?\d{9}$/.test(str), + KZ: str => /^(KZ)?\d{12}$/.test(str), + NZ: str => /^(NZ)?\d{9}$/.test(str), + NG: str => /^(NG)?(\d{12}|(\d{8}-\d{4}))$/.test(str), + NO: str => /^(NO)?\d{9}MVA$/.test(str), + PH: str => /^(PH)?(\d{12}|\d{3} \d{3} \d{3} \d{3})$/.test(str), + RU: str => /^(RU)?(\d{10}|\d{12})$/.test(str), + SM: str => /^(SM)?\d{5}$/.test(str), + SA: str => /^(SA)?\d{15}$/.test(str), + RS: str => /^(RS)?\d{9}$/.test(str), + CH, + TR: str => /^(TR)?\d{10}$/.test(str), + UA: str => /^(UA)?\d{12}$/.test(str), + GB: str => /^GB((\d{3} \d{4} ([0-8][0-9]|9[0-6]))|(\d{9} \d{3})|(((GD[0-4])|(HA[5-9]))[0-9]{2}))$/.test(str), + UZ: str => /^(UZ)?\d{9}$/.test(str), + + /** + * VAT numbers of Latin American countries + */ + AR: str => /^(AR)?\d{11}$/.test(str), + BO: str => /^(BO)?\d{7}$/.test(str), + BR: str => /^(BR)?((\d{2}.\d{3}.\d{3}\/\d{4}-\d{2})|(\d{3}.\d{3}.\d{3}-\d{2}))$/.test(str), + CL: str => /^(CL)?\d{8}-\d{1}$/.test(str), + CO: str => /^(CO)?\d{10}$/.test(str), + CR: str => /^(CR)?\d{9,12}$/.test(str), + EC: str => /^(EC)?\d{13}$/.test(str), + SV: str => /^(SV)?\d{4}-\d{6}-\d{3}-\d{1}$/.test(str), + GT: str => /^(GT)?\d{7}-\d{1}$/.test(str), + HN: str => /^(HN)?$/.test(str), + MX: str => /^(MX)?\w{3,4}\d{6}\w{3}$/.test(str), + NI: str => /^(NI)?\d{3}-\d{6}-\d{4}\w{1}$/.test(str), + PA: str => /^(PA)?$/.test(str), + PY: str => /^(PY)?\d{6,8}-\d{1}$/.test(str), + PE: str => /^(PE)?\d{11}$/.test(str), + DO: str => /^(DO)?(\d{11}|(\d{3}-\d{7}-\d{1})|[1,4,5]{1}\d{8}|([1,4,5]{1})-\d{2}-\d{5}-\d{1})$/.test(str), + UY: str => /^(UY)?\d{12}$/.test(str), + VE: str => /^(VE)?[J,G,V,E]{1}-(\d{9}|(\d{8}-\d{1}))$/.test(str), +}; + +export default function isVAT(str, countryCode) { + assertString(str); + assertString(countryCode); + + if (countryCode in vatMatchers) { + return vatMatchers[countryCode](str); + } + throw new Error(`Invalid country code: '${countryCode}'`); +} diff --git a/src/lib/ltrim.js b/src/lib/ltrim.js index 7f1ee11cd..372d2df8e 100644 --- a/src/lib/ltrim.js +++ b/src/lib/ltrim.js @@ -2,6 +2,7 @@ import assertString from './util/assertString'; export default function ltrim(str, chars) { assertString(str); - const pattern = chars ? new RegExp(`^[${chars}]+`, 'g') : /^\s+/g; + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Escaping + const pattern = chars ? new RegExp(`^[${chars.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}]+`, 'g') : /^\s+/g; return str.replace(pattern, ''); } diff --git a/src/lib/matches.js b/src/lib/matches.js index 5b435e2af..9e23c2e46 100644 --- a/src/lib/matches.js +++ b/src/lib/matches.js @@ -5,5 +5,5 @@ export default function matches(str, pattern, modifiers) { if (Object.prototype.toString.call(pattern) !== '[object RegExp]') { pattern = new RegExp(pattern, modifiers); } - return pattern.test(str); + return !!str.match(pattern); } diff --git a/src/lib/normalizeEmail.js b/src/lib/normalizeEmail.js index e69d39054..ceb252f34 100644 --- a/src/lib/normalizeEmail.js +++ b/src/lib/normalizeEmail.js @@ -29,6 +29,12 @@ const default_normalize_email_options = { // Removes the subaddress (e.g. "-foo") from the email address yahoo_remove_subaddress: true, + // The following conversions are specific to Yandex + // Lowercases the local part of the Yandex address (known to be case-insensitive) + yandex_lowercase: true, + // all yandex domains are equal, this explicitly sets the domain to 'yandex.ru' + yandex_convert_yandexru: true, + // The following conversions are specific to iCloud // Lowercases the local part of the iCloud address (known to be case-insensitive) icloud_lowercase: true, @@ -145,6 +151,24 @@ const yahoo_domains = [ 'ymail.com', ]; +// List of domains used by yandex.ru +const yandex_domains = [ + 'yandex.ru', + 'yandex.ua', + 'yandex.kz', + 'yandex.com', + 'yandex.by', + 'ya.ru', +]; + +// replace single dots, but not multiple consecutive dots +function dotsReplacer(match) { + if (match.length > 1) { + return match; + } + return ''; +} + export default function normalizeEmail(email, options) { options = merge(options, default_normalize_email_options); @@ -162,7 +186,8 @@ export default function normalizeEmail(email, options) { parts[0] = parts[0].split('+')[0]; } if (options.gmail_remove_dots) { - parts[0] = parts[0].replace(/\./g, ''); + // this does not replace consecutive dots like example..email@gmail.com + parts[0] = parts[0].replace(/\.+/g, dotsReplacer); } if (!parts[0].length) { return false; @@ -171,7 +196,7 @@ export default function normalizeEmail(email, options) { parts[0] = parts[0].toLowerCase(); } parts[1] = options.gmail_convert_googlemaildotcom ? 'gmail.com' : parts[1]; - } else if (~icloud_domains.indexOf(parts[1])) { + } else if (icloud_domains.indexOf(parts[1]) >= 0) { // Address is iCloud if (options.icloud_remove_subaddress) { parts[0] = parts[0].split('+')[0]; @@ -182,7 +207,7 @@ export default function normalizeEmail(email, options) { if (options.all_lowercase || options.icloud_lowercase) { parts[0] = parts[0].toLowerCase(); } - } else if (~outlookdotcom_domains.indexOf(parts[1])) { + } else if (outlookdotcom_domains.indexOf(parts[1]) >= 0) { // Address is Outlook.com if (options.outlookdotcom_remove_subaddress) { parts[0] = parts[0].split('+')[0]; @@ -193,7 +218,7 @@ export default function normalizeEmail(email, options) { if (options.all_lowercase || options.outlookdotcom_lowercase) { parts[0] = parts[0].toLowerCase(); } - } else if (~yahoo_domains.indexOf(parts[1])) { + } else if (yahoo_domains.indexOf(parts[1]) >= 0) { // Address is Yahoo if (options.yahoo_remove_subaddress) { let components = parts[0].split('-'); @@ -205,6 +230,11 @@ export default function normalizeEmail(email, options) { if (options.all_lowercase || options.yahoo_lowercase) { parts[0] = parts[0].toLowerCase(); } + } else if (yandex_domains.indexOf(parts[1]) >= 0) { + if (options.all_lowercase || options.yandex_lowercase) { + parts[0] = parts[0].toLowerCase(); + } + parts[1] = options.yandex_convert_yandexru ? 'yandex.ru' : parts[1]; } else if (options.all_lowercase) { // Any other address parts[0] = parts[0].toLowerCase(); diff --git a/src/lib/rtrim.js b/src/lib/rtrim.js index 9045bb72e..2d311574b 100644 --- a/src/lib/rtrim.js +++ b/src/lib/rtrim.js @@ -2,12 +2,16 @@ import assertString from './util/assertString'; export default function rtrim(str, chars) { assertString(str); - const pattern = chars ? new RegExp(`[${chars}]`) : /\s/; - - let idx = str.length - 1; - while (idx >= 0 && pattern.test(str[idx])) { - idx--; + if (chars) { + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Escaping + const pattern = new RegExp(`[${chars.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}]+$`, 'g'); + return str.replace(pattern, ''); + } + // Use a faster and more safe than regex trim method https://blog.stevenlevithan.com/archives/faster-trim-javascript + let strIndex = str.length - 1; + while (/\s/.test(str.charAt(strIndex))) { + strIndex -= 1; } - return idx < str.length ? str.substr(0, idx + 1) : str; + return str.slice(0, strIndex + 1); } diff --git a/src/lib/toBoolean.js b/src/lib/toBoolean.js index b5d5f292b..95e210ca3 100644 --- a/src/lib/toBoolean.js +++ b/src/lib/toBoolean.js @@ -3,7 +3,7 @@ import assertString from './util/assertString'; export default function toBoolean(str, strict) { assertString(str); if (strict) { - return str === '1' || str === 'true'; + return str === '1' || /^true$/i.test(str); } - return str !== '0' && str !== 'false' && str !== ''; + return str !== '0' && !/^false$/i.test(str) && str !== ''; } diff --git a/src/lib/toDate.js b/src/lib/toDate.js index 7cf80510e..179645fda 100644 --- a/src/lib/toDate.js +++ b/src/lib/toDate.js @@ -2,6 +2,7 @@ import assertString from './util/assertString'; export default function toDate(date) { assertString(date); + date = Date.parse(date); return !isNaN(date) ? new Date(date) : null; } diff --git a/src/lib/toFloat.js b/src/lib/toFloat.js index eb3eb5a33..fa7d7b7c7 100644 --- a/src/lib/toFloat.js +++ b/src/lib/toFloat.js @@ -1,6 +1,7 @@ -import assertString from './util/assertString'; +import isFloat from './isFloat'; export default function toFloat(str) { - assertString(str); + if (!isFloat(str)) return NaN; + return parseFloat(str); } diff --git a/src/lib/unescape.js b/src/lib/unescape.js index 213a0f70b..feb255ac0 100644 --- a/src/lib/unescape.js +++ b/src/lib/unescape.js @@ -2,12 +2,15 @@ import assertString from './util/assertString'; export default function unescape(str) { assertString(str); - return (str.replace(/&/g, '&') - .replace(/"/g, '"') + return (str.replace(/"/g, '"') .replace(/'/g, "'") .replace(/</g, '<') .replace(/>/g, '>') .replace(///g, '/') .replace(/\/g, '\\') - .replace(/`/g, '`')); + .replace(/`/g, '`') + .replace(/&/g, '&')); + // & replacement has to be the last one to prevent + // bugs with intermediate strings containing escape sequences + // See: https://github.com/validatorjs/validator.js/issues/1827 } diff --git a/src/lib/util/algorithms.js b/src/lib/util/algorithms.js new file mode 100644 index 000000000..4ac1f2784 --- /dev/null +++ b/src/lib/util/algorithms.js @@ -0,0 +1,97 @@ +/** + * Algorithmic validation functions + * May be used as is or implemented in the workflow of other validators. + */ + +/* + * ISO 7064 validation function + * Called with a string of numbers (incl. check digit) + * to validate according to ISO 7064 (MOD 11, 10). + */ +export function iso7064Check(str) { + let checkvalue = 10; + for (let i = 0; i < str.length - 1; i++) { + checkvalue = (parseInt(str[i], 10) + checkvalue) % 10 === 0 ? (10 * 2) % 11 : + (((parseInt(str[i], 10) + checkvalue) % 10) * 2) % 11; + } + checkvalue = checkvalue === 1 ? 0 : 11 - checkvalue; + return checkvalue === parseInt(str[10], 10); +} + +/* + * Luhn (mod 10) validation function + * Called with a string of numbers (incl. check digit) + * to validate according to the Luhn algorithm. + */ +export function luhnCheck(str) { + let checksum = 0; + let second = false; + for (let i = str.length - 1; i >= 0; i--) { + if (second) { + const product = parseInt(str[i], 10) * 2; + if (product > 9) { + // sum digits of product and add to checksum + checksum += product.toString().split('').map(a => parseInt(a, 10)).reduce((a, b) => a + b, 0); + } else { + checksum += product; + } + } else { + checksum += parseInt(str[i], 10); + } + second = !second; + } + return checksum % 10 === 0; +} + +/* + * Reverse TIN multiplication and summation helper function + * Called with an array of single-digit integers and a base multiplier + * to calculate the sum of the digits multiplied in reverse. + * Normally used in variations of MOD 11 algorithmic checks. + */ +export function reverseMultiplyAndSum(digits, base) { + let total = 0; + for (let i = 0; i < digits.length; i++) { + total += digits[i] * (base - i); + } + return total; +} + +/* + * Verhoeff validation helper function + * Called with a string of numbers + * to validate according to the Verhoeff algorithm. + */ +export function verhoeffCheck(str) { + const d_table = [ + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [1, 2, 3, 4, 0, 6, 7, 8, 9, 5], + [2, 3, 4, 0, 1, 7, 8, 9, 5, 6], + [3, 4, 0, 1, 2, 8, 9, 5, 6, 7], + [4, 0, 1, 2, 3, 9, 5, 6, 7, 8], + [5, 9, 8, 7, 6, 0, 4, 3, 2, 1], + [6, 5, 9, 8, 7, 1, 0, 4, 3, 2], + [7, 6, 5, 9, 8, 2, 1, 0, 4, 3], + [8, 7, 6, 5, 9, 3, 2, 1, 0, 4], + [9, 8, 7, 6, 5, 4, 3, 2, 1, 0], + ]; + + const p_table = [ + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [1, 5, 7, 6, 2, 8, 3, 0, 9, 4], + [5, 8, 0, 3, 7, 9, 6, 1, 4, 2], + [8, 9, 1, 6, 0, 4, 3, 5, 2, 7], + [9, 4, 5, 3, 1, 2, 6, 8, 7, 0], + [4, 2, 8, 6, 5, 7, 3, 9, 0, 1], + [2, 7, 9, 3, 8, 0, 6, 4, 1, 5], + [7, 0, 4, 6, 9, 1, 3, 2, 5, 8], + ]; + + // Copy (to prevent replacement) and reverse + const str_copy = str.split('').reverse().join(''); + let checksum = 0; + for (let i = 0; i < str_copy.length; i++) { + checksum = d_table[checksum][p_table[i % 8][parseInt(str_copy[i], 10)]]; + } + return checksum === 0; +} diff --git a/src/lib/util/assertString.js b/src/lib/util/assertString.js index e4fac8854..3baa4452f 100644 --- a/src/lib/util/assertString.js +++ b/src/lib/util/assertString.js @@ -1,7 +1,4 @@ export default function assertString(input) { - const isString = (typeof input === 'string' || input instanceof String); - - if (!isString) { - throw new TypeError('This library (validator.js) validates strings only'); - } + if (input === undefined || input === null) throw new TypeError(`Expected a string but received a ${input}`); + if (input.constructor.name !== 'String') throw new TypeError(`Expected a string but received a ${input.constructor.name}`); } 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/src/lib/util/includesArray.js b/src/lib/util/includesArray.js new file mode 100644 index 000000000..70ace8857 --- /dev/null +++ b/src/lib/util/includesArray.js @@ -0,0 +1,3 @@ +const includes = (arr, val) => arr.some(arrVal => val === arrVal); + +export default includes; diff --git a/src/lib/util/includesString.js b/src/lib/util/includesString.js new file mode 100644 index 000000000..41a50bba8 --- /dev/null +++ b/src/lib/util/includesString.js @@ -0,0 +1,3 @@ +const includes = (str, val) => str.indexOf(val) !== -1; + +export default includes; diff --git a/src/lib/util/multilineRegex.js b/src/lib/util/multilineRegex.js new file mode 100644 index 000000000..706d8a639 --- /dev/null +++ b/src/lib/util/multilineRegex.js @@ -0,0 +1,13 @@ +/** + * Build RegExp object from an array + * of multiple/multi-line regexp parts + * + * @param {string[]} parts + * @param {string} flags + * @return {object} - RegExp object + */ +export default function multilineRegexp(parts, flags) { + const regexpAsStringLiteral = parts.join(''); + + return new RegExp(regexpAsStringLiteral, flags); +} diff --git a/src/lib/util/nullUndefinedCheck.js b/src/lib/util/nullUndefinedCheck.js new file mode 100644 index 000000000..c45bfbe82 --- /dev/null +++ b/src/lib/util/nullUndefinedCheck.js @@ -0,0 +1,3 @@ +export default function isNullOrUndefined(value) { + return value === null || value === undefined; +} diff --git a/src/lib/util/typeOf.js b/src/lib/util/typeOf.js new file mode 100644 index 000000000..f96af511a --- /dev/null +++ b/src/lib/util/typeOf.js @@ -0,0 +1,10 @@ +/** + * Better way to handle type checking + * null, {}, array and date are objects, which confuses + */ +export default function typeOf(input) { + const rawObject = Object.prototype.toString.call(input).toLowerCase(); + const typeOfRegex = /\[object (.*)]/g; + const type = typeOfRegex.exec(rawObject)[1]; + return type; +} diff --git a/test/.eslintrc.json b/test/.eslintrc.json deleted file mode 100644 index 4b03fb38c..000000000 --- a/test/.eslintrc.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "parserOptions": { - "ecmaVersion": 5, - "sourceType": "module" - }, - "rules": { - "no-var": 0, - "vars-on-top": 0, - "prefer-arrow-callback": 0, - "object-shorthand": [0], - "quote-props": 0, - "no-plusplus": 0, - "no-bitwise": 0 - } -} diff --git a/test/client-side.js b/test/client-side.js deleted file mode 100644 index 503ca2b2f..000000000 --- a/test/client-side.js +++ /dev/null @@ -1,27 +0,0 @@ -var assert = require('assert'); -var validator = require('../validator'); -var min = require('../validator.min'); - -describe('Minified version', function () { - it('should export the same things as the server-side version', function () { - for (var key in validator) { - if ({}.hasOwnProperty.call(validator, key)) { - assert.equal(typeof validator[key], - typeof min[key], `Minified version did not export ${key}`); - } - } - }); - - it('should be up to date', function () { - assert.equal(min.version, validator.version, 'Minified version mismatch. Run `make min`'); - }); - - it('should validate strings', function () { - assert.equal(min.isEmail('foo@bar.com'), true); - assert.equal(min.isEmail('foo'), false); - }); - - it('should sanitize strings', function () { - assert.equal(min.toBoolean('1'), true); - }); -}); diff --git a/test/clientSide.test.js b/test/clientSide.test.js new file mode 100644 index 000000000..f0b5ca72c --- /dev/null +++ b/test/clientSide.test.js @@ -0,0 +1,29 @@ +import assert from 'assert'; +import validator from '../validator'; +import min from '../validator.min'; + +describe('Minified version', () => { + it('should export the same things as the server-side version', () => { + for (let key in validator) { + if ({}.hasOwnProperty.call(validator, key)) { + assert.strictEqual( + typeof validator[key], + typeof min[key], `Minified version did not export ${key}` + ); + } + } + }); + + it('should be up to date', () => { + assert.strictEqual(min.version, validator.version, 'Minified version mismatch. Run `make min`'); + }); + + it('should validate strings', () => { + assert.strictEqual(min.isEmail('foo@bar.com'), true); + assert.strictEqual(min.isEmail('foo'), false); + }); + + it('should sanitize strings', () => { + assert.strictEqual(min.toBoolean('1'), true); + }); +}); diff --git a/test/exports.js b/test/exports.js deleted file mode 100644 index ec9c5961e..000000000 --- a/test/exports.js +++ /dev/null @@ -1,26 +0,0 @@ -var assert = require('assert'); -var validator = require('../index'); -var isPostalCodeLocales = require('../lib/isPostalCode').locales; - -describe('Exports', function () { - it('should export validators', function () { - assert.equal(typeof validator.isEmail, 'function'); - assert.equal(typeof validator.isAlpha, 'function'); - }); - - it('should export sanitizers', function () { - assert.equal(typeof validator.toBoolean, 'function'); - assert.equal(typeof validator.toFloat, 'function'); - }); - - it('should export the version number', function () { - /* eslint-disable global-require */ - assert.equal(validator.version, require('../package.json').version, - 'Version number mismatch in "package.json" vs. "validator.js"'); - /* eslint-enable global-require */ - }); - - it('should export isPostalCode\'s supported locales', function () { - assert.ok(isPostalCodeLocales instanceof Array); - }); -}); diff --git a/test/exports.test.js b/test/exports.test.js new file mode 100644 index 000000000..a5f458f05 --- /dev/null +++ b/test/exports.test.js @@ -0,0 +1,65 @@ +import assert from 'assert'; +import validator from '../index'; +import { locales as isPostalCodeLocales } from '../src/lib/isPostalCode'; +import { locales as isAlphaLocales } from '../src/lib/isAlpha'; +import { locales as isAlphanumericLocales } from '../src/lib/isAlphanumeric'; +import { locales as isMobilePhoneLocales } from '../src/lib/isMobilePhone'; +import { locales as isFloatLocales } from '../src/lib/isFloat'; +import { locales as ibanCountryCodes } from '../src/lib/isIBAN'; +import { locales as passportNumberLocales } from '../src/lib/isPassportNumber'; + +describe('Exports', () => { + it('should export isPassportNumbers\'s supported locales', () => { + assert.ok(passportNumberLocales instanceof Array); + assert.ok(validator.passportNumberLocales instanceof Array); + }); + + it('should export validators', () => { + assert.strictEqual(typeof validator.isEmail, 'function'); + assert.strictEqual(typeof validator.isAlpha, 'function'); + }); + + it('should export sanitizers', () => { + assert.strictEqual(typeof validator.toBoolean, 'function'); + assert.strictEqual(typeof validator.toFloat, 'function'); + }); + + it('should export the version number', () => { + /* eslint-disable global-require */ + assert.strictEqual( + validator.version, require('../package.json').version, + 'Version number mismatch in "package.json" vs. "validator.js"' + ); + /* eslint-enable global-require */ + }); + + it('should export isPostalCode\'s supported locales', () => { + assert.ok(isPostalCodeLocales instanceof Array); + assert.ok(validator.isPostalCodeLocales instanceof Array); + }); + + it('should export isAlpha\'s supported locales', () => { + assert.ok(isAlphaLocales instanceof Array); + assert.ok(validator.isAlphaLocales instanceof Array); + }); + + it('should export isAlphanumeric\'s supported locales', () => { + assert.ok(isAlphanumericLocales instanceof Array); + assert.ok(validator.isAlphanumericLocales instanceof Array); + }); + + it('should export isMobilePhone\'s supported locales', () => { + assert.ok(isMobilePhoneLocales instanceof Array); + assert.ok(validator.isMobilePhoneLocales instanceof Array); + }); + + it('should export isFloat\'s supported locales', () => { + assert.ok(isFloatLocales instanceof Array); + assert.ok(validator.isFloatLocales instanceof Array); + }); + + it('should export a list of country codes that implement IBAN', () => { + assert.ok(ibanCountryCodes instanceof Array); + assert.ok(validator.ibanLocales instanceof Array); + }); +}); diff --git a/test/sanitizers.js b/test/sanitizers.test.js similarity index 70% rename from test/sanitizers.js rename to test/sanitizers.test.js index 3fa3120b7..e36ba48d3 100644 --- a/test/sanitizers.js +++ b/test/sanitizers.test.js @@ -1,56 +1,68 @@ -var validator = require('../index'); -var format = require('util').format; +import { format } from 'util'; +import validator from '../src/index'; function test(options) { - var args = options.args || []; + let args = options.args || []; args.unshift(null); - Object.keys(options.expect).forEach(function (input) { + Object.keys(options.expect).forEach((input) => { args[0] = input; - var result = validator[options.sanitizer](...args); - var expected = options.expect[input]; + let result = validator[options.sanitizer](...args); + let expected = options.expect[input]; if (isNaN(result) && !result.length && isNaN(expected)) { return; } if (result !== expected) { - var warning = format('validator.%s(%s) returned "%s" but should have returned "%s"', - options.sanitizer, args.join(', '), result, expected); + let warning = format( + 'validator.%s(%s) returned "%s" but should have returned "%s"', + options.sanitizer, args.join(', '), result, expected + ); throw new Error(warning); } }); } -describe('Sanitizers', function () { - it('should sanitize boolean strings', function () { +describe('Sanitizers', () => { + it('should sanitize boolean strings', () => { test({ sanitizer: 'toBoolean', expect: { - '0': false, + 0: false, '': false, - '1': true, - 'true': true, - 'foobar': true, + 1: true, + true: true, + True: true, + TRUE: true, + foobar: true, ' ': true, + false: false, + False: false, + FALSE: false, }, }); test({ sanitizer: 'toBoolean', args: [true], // strict expect: { - '0': false, + 0: false, '': false, - '1': true, - 'true': true, - 'foobar': false, + 1: true, + true: true, + True: true, + TRUE: true, + foobar: false, ' ': false, + false: false, + False: false, + FALSE: false, }, }); }); - it('should trim whitespace', function () { + it('should trim whitespace', () => { test({ sanitizer: 'trim', expect: { @@ -76,7 +88,7 @@ describe('Sanitizers', function () { }); }); - it('should trim custom characters', function () { + it('should trim custom characters', () => { test({ sanitizer: 'trim', args: ['01'], @@ -89,45 +101,59 @@ describe('Sanitizers', function () { expect: { '010100201000': '201000' }, }); + test({ + sanitizer: 'ltrim', + args: ['\\S'], + expect: { '\\S01010020100001': '01010020100001' }, + }); + + test({ sanitizer: 'rtrim', args: ['01'], expect: { '010100201000': '0101002' }, }); + + test({ + sanitizer: 'rtrim', + args: ['\\S'], + expect: { '01010020100001\\S': '01010020100001' }, + }); }); - it('should convert strings to integers', function () { + it('should convert strings to integers', () => { test({ sanitizer: 'toInt', expect: { - '3': 3, + 3: 3, ' 3 ': 3, - '2.4': 2, - 'foo': NaN, + 2.4: 2, + foo: NaN, }, }); test({ sanitizer: 'toInt', args: [16], - expect: { 'ff': 255 }, + expect: { ff: 255 }, }); }); - it('should convert strings to floats', function () { + it('should convert strings to floats', () => { test({ sanitizer: 'toFloat', expect: { - '2': 2.0, + 2: 2.0, '2.': 2.0, '-2.5': -2.5, '.5': 0.5, - 'foo': NaN, + '2020-01-06T14:31:00.135Z': NaN, + foo: NaN, }, }); }); - it('should escape HTML', function () { + it('should escape HTML', () => { test({ sanitizer: 'escape', expect: { @@ -146,7 +172,7 @@ describe('Sanitizers', function () { }); }); - it('should unescape HTML', function () { + it('should unescape HTML', () => { test({ sanitizer: 'unescape', expect: { @@ -158,11 +184,14 @@ describe('Sanitizers', function () { 'Backtick: `': 'Backtick: `', + + 'Escaped string: &lt;': + 'Escaped string: <', }, }); }); - it('should remove control characters (<32 and 127)', function () { + it('should remove control characters (<32 and 127)', () => { // Check basic functionality test({ sanitizer: 'stripLow', @@ -177,7 +206,7 @@ describe('Sanitizers', function () { test({ sanitizer: 'stripLow', expect: { - 'perch\u00e9': 'perch\u00e9', + perché: 'perch\u00e9', '\u20ac': '\u20ac', '\u2206\x0A': '\u2206', '\ud83d\ude04': '\ud83d\ude04', @@ -194,33 +223,69 @@ describe('Sanitizers', function () { }); }); - it('should sanitize a string based on a whitelist', function () { + it('should sanitize a string based on a whitelist', () => { test({ sanitizer: 'whitelist', args: ['abc'], expect: { - 'abcdef': 'abc', - 'aaaaaaaaaabbbbbbbbbb': 'aaaaaaaaaabbbbbbbbbb', - 'a1b2c3': 'abc', + abcdef: 'abc', + aaaaaaaaaabbbbbbbbbb: 'aaaaaaaaaabbbbbbbbbb', + a1b2c3: 'abc', ' ': '', }, }); }); - it('should sanitize a string based on a blacklist', function () { + it('should sanitize a string based on a blacklist', () => { test({ sanitizer: 'blacklist', args: ['abc'], expect: { - 'abcdef': 'def', - 'aaaaaaaaaabbbbbbbbbb': '', - 'a1b2c3': '123', + abcdef: 'def', + aaaaaaaaaabbbbbbbbbb: '', + a1b2c3: '123', ' ': ' ', }, }); }); - it('should normalize an email based on domain', function () { + it('should score passwords', () => { + test({ + sanitizer: 'isStrongPassword', + args: [{ + returnScore: true, + pointsPerUnique: 1, + pointsPerRepeat: 0.5, + pointsForContainingLower: 10, + pointsForContainingUpper: 10, + pointsForContainingNumber: 10, + pointsForContainingSymbol: 10, + }], + expect: { + abc: 13, + abcc: 13.5, + aBc: 23, + 'Abc123!': 47, + '!@#$%^&*()': 20, + }, + }); + }); + + it('should score passwords with default options', () => { + test({ + sanitizer: 'isStrongPassword', + expect: { + abc: false, + abcc: false, + aBc: false, + 'Abc123!': false, + '!@#$%^&*()': false, + 'abc123!@f#rA': true, + }, + }); + }); + + it('should normalize an email based on domain', () => { test({ sanitizer: 'normalizeEmail', expect: { @@ -235,8 +300,19 @@ describe('Sanitizers', function () { 'some.name.midd.leNa.me.+extension@GoogleMail.com': 'somenamemiddlename@gmail.com', 'some.name+extension@unknown.com': 'some.name+extension@unknown.com', 'hans@m端ller.com': 'hans@m端ller.com', - 'some.name.midd..leNa...me...+extension@GoogleMail.com': 'somenamemiddlename@gmail.com', + 'some.name.midd..leNa...me...+extension@GoogleMail.com': 'somenamemidd..lena...me...@gmail.com', + 'matthew..example@gmail.com': 'matthew..example@gmail.com', '"foo@bar"@baz.com': '"foo@bar"@baz.com', + 'test@ya.ru': 'test@yandex.ru', + 'test@yandex.kz': 'test@yandex.ru', + 'test@yandex.ru': 'test@yandex.ru', + 'test@yandex.ua': 'test@yandex.ru', + 'test@yandex.com': 'test@yandex.ru', + 'test@yandex.by': 'test@yandex.ru', + '@gmail.com': false, + '@icloud.com': false, + '@outlook.com': false, + '@yahoo.com': false, }, }); @@ -259,6 +335,7 @@ describe('Sanitizers', function () { 'SOME.name@yahoo.ca': 'some.name@yahoo.ca', 'SOME.name@outlook.ie': 'some.name@outlook.ie', 'SOME.name@me.com': 'some.name@me.com', + 'SOME.name@yandex.ru': 'some.name@yandex.ru', }, }); @@ -271,6 +348,7 @@ describe('Sanitizers', function () { icloud_lowercase: false, outlookdotcom_lowercase: false, yahoo_lowercase: false, + yandex_lowercase: false, }], expect: { 'TEST@FOO.COM': 'TEST@foo.com', // all_lowercase @@ -280,6 +358,7 @@ describe('Sanitizers', function () { 'ME@outlook.COM': 'ME@outlook.com', // outlookdotcom_lowercase 'JOHN@live.CA': 'JOHN@live.ca', // outlookdotcom_lowercase 'ME@ymail.COM': 'ME@ymail.com', // yahoo_lowercase + 'ME@yandex.RU': 'ME@yandex.ru', // yandex_lowercase }, }); @@ -326,6 +405,7 @@ describe('Sanitizers', function () { expect: { 'SOME.name@GMAIL.com': 'somename@gmail.com', 'SOME.name+me@GMAIL.com': 'somename@gmail.com', + 'some.name..multiple@gmail.com': 'somename..multiple@gmail.com', 'my.self@foo.com': 'my.self@foo.com', }, }); @@ -401,5 +481,34 @@ describe('Sanitizers', function () { 'my.self@foo.com': 'my.self@foo.com', }, }); + + // Testing yandex_convert_yandexru + test({ + sanitizer: 'normalizeEmail', + args: [{ + yandex_convert_yandexru: false, + }], + expect: { + 'test@yandex.kz': 'test@yandex.kz', + 'test@yandex.ru': 'test@yandex.ru', + 'test@yandex.ua': 'test@yandex.ua', + 'test@yandex.com': 'test@yandex.com', + 'test@yandex.by': 'test@yandex.by', + }, + }); + + test({ + sanitizer: 'normalizeEmail', + args: [{ + yandex_convert_yandexru: true, + }], + expect: { + 'test@yandex.kz': 'test@yandex.ru', + 'test@yandex.ru': 'test@yandex.ru', + 'test@yandex.ua': 'test@yandex.ru', + 'test@yandex.com': 'test@yandex.ru', + 'test@yandex.by': 'test@yandex.ru', + }, + }); }); }); diff --git a/test/testFunctions.js b/test/testFunctions.js new file mode 100644 index 000000000..5fc133bec --- /dev/null +++ b/test/testFunctions.js @@ -0,0 +1,60 @@ +import assert from 'assert'; +import { format } from 'util'; +import validator from '../src/index'; + +function stringifyArgs(argsArr) { + return argsArr.map(arg => JSON.stringify(arg)).join(', '); +} + +export default function test(options) { + const args = options.args || []; + + args.unshift(null); + + if (options.error) { + options.error.forEach((error) => { + args[0] = error; + + try { + assert.throws(() => validator[options.validator](...args)); + } catch (err) { + const warning = format( + 'validator.%s(%s) passed but should error', + options.validator, stringifyArgs(args) + ); + + throw new Error(warning); + } + }); + } + + if (options.valid) { + options.valid.forEach((valid) => { + args[0] = valid; + + if (validator[options.validator](...args) !== true) { + const warning = format( + 'validator.%s(%s) failed but should have passed', + options.validator, stringifyArgs(args) + ); + + throw new Error(warning); + } + }); + } + + if (options.invalid) { + options.invalid.forEach((invalid) => { + args[0] = invalid; + + if (validator[options.validator](...args) !== false) { + const warning = format( + 'validator.%s(%s) passed but should have failed', + options.validator, stringifyArgs(args) + ); + + throw new Error(warning); + } + }); + } +} diff --git a/test/util.test.js b/test/util.test.js new file mode 100644 index 000000000..0146a4e2c --- /dev/null +++ b/test/util.test.js @@ -0,0 +1,64 @@ +/** + * All tests that tests any utility. + * Prevent any breaking of functionality + */ +import assert from 'assert'; +import typeOf from '../src/lib/util/typeOf'; +import assertString from '../src/lib/util/assertString'; + + +describe('Util', () => { + it('should validate different typeOf', () => { + assert.strictEqual(typeOf([]), 'array'); + assert.strictEqual(typeOf(null), 'null'); + assert.strictEqual(typeOf({}), 'object'); + assert.strictEqual(typeOf(new Date()), 'date'); + assert.strictEqual(typeOf('ezkemboi'), 'string'); + assert.strictEqual(typeOf(String('kemboi')), 'string'); + assert.strictEqual(typeOf(undefined), 'undefined'); + assert.strictEqual(typeOf(2021), 'number'); + assert.notStrictEqual(typeOf([]), 'object'); + }); +}); + +describe('assertString', () => { + it('Should throw an error if argument provided is an undefined', () => { + assert.throws(() => { assertString(); }, TypeError); + }); + + it('Should throw an error if argument provided is a null', () => { + assert.throws(() => { assertString(null); }, TypeError); + }); + + it('Should throw an error if argument provided is a Boolean', () => { + assert.throws(() => { assertString(true); }, TypeError); + }); + + it('Should throw an error if argument provided is a Date', () => { + assert.throws(() => { assertString(new Date()); }, TypeError); + }); + + it('Should throw an error if argument provided is a Number(NaN)', () => { + assert.throws(() => { assertString(NaN); }, TypeError); + }); + + it('Should throw an error if argument provided is a Number', () => { + assert.throws(() => { assertString(2024); }, TypeError); + }); + + it('Should throw an error if argument provided is an Object', () => { + assert.throws(() => { assertString({}); }, TypeError); + }); + + it('Should throw an error if argument provided is an Array', () => { + assert.throws(() => { assertString([]); }, TypeError); + }); + + it('Should not throw an error if the argument is an empty string', () => { + assert.doesNotThrow(() => { assertString(''); }); + }); + + it('Should not throw an error if the argument is a String', () => { + assert.doesNotThrow(() => { assertString('antidisestablishmentarianism'); }); + }); +}); diff --git a/test/validators.js b/test/validators.js deleted file mode 100644 index 235299fc3..000000000 --- a/test/validators.js +++ /dev/null @@ -1,5144 +0,0 @@ -var validator = require('../index'), - format = require('util').format, - assert = require('assert'), - path = require('path'), - fs = require('fs'), - vm = require('vm'); - -var validator_js = fs.readFileSync(path.join(__dirname, '../validator.js')).toString(); - -function test(options) { - var args = options.args || []; - args.unshift(null); - if (options.valid) { - options.valid.forEach(function (valid) { - args[0] = valid; - if (validator[options.validator](...args) !== true) { - var warning = format('validator.%s(%s) failed but should have passed', - options.validator, args.join(', ')); - throw new Error(warning); - } - }); - } - if (options.invalid) { - options.invalid.forEach(function (invalid) { - args[0] = invalid; - if (validator[options.validator](...args) !== false) { - var warning = format('validator.%s(%s) passed but should have failed', - options.validator, args.join(', ')); - throw new Error(warning); - } - }); - } -} - -function repeat(str, count) { - var result = ''; - while (count--) { - result += str; - } - return result; -} - -describe('Validators', function () { - it('should validate email addresses', function () { - test({ - validator: 'isEmail', - valid: [ - 'foo@bar.com', - 'x@x.au', - 'foo@bar.com.au', - 'foo+bar@bar.com', - 'hans.m端ller@test.com', - 'hans@m端ller.com', - 'test|123@m端ller.com', - 'test+ext@gmail.com', - 'some.name.midd.leNa.me.+extension@GoogleMail.com', - 'gmail...ignores...dots...@gmail.com', - '"foobar"@example.com', - '" foo m端ller "@example.com', - '"foo\\@bar"@example.com', - `${repeat('a', 64)}@${repeat('a', 250)}.com`, - ], - invalid: [ - 'invalidemail@', - 'invalid.com', - '@invalid.com', - 'foo@bar.com.', - 'somename@gmail.com', - 'foo@bar.co.uk.', - 'z@co.c', - 'gmailgmailgmailgmailgmail@gmail.com', - `${repeat('a', 64)}@${repeat('a', 251)}.com`, - `${repeat('a', 65)}@${repeat('a', 250)}.com`, - 'test1@invalid.co m', - 'test2@invalid.co m', - 'test3@invalid.co m', - 'test4@invalid.co m', - 'test5@invalid.co m', - 'test6@invalid.co m', - 'test7@invalid.co m', - 'test8@invalid.co m', - 'test9@invalid.co m', - 'test10@invalid.co m', - 'test11@invalid.co m', - 'test12@invalid.co m', - 'test13@invalid.co m', - ], - }); - }); - - it('should validate email addresses without UTF8 characters in local part', function () { - test({ - validator: 'isEmail', - args: [{ allow_utf8_local_part: false }], - valid: [ - 'foo@bar.com', - 'x@x.au', - 'foo@bar.com.au', - 'foo+bar@bar.com', - 'hans@m端ller.com', - 'test|123@m端ller.com', - 'test+ext@gmail.com', - 'some.name.midd.leNa.me.+extension@GoogleMail.com', - '"foobar"@example.com', - '"foo\\@bar"@example.com', - '" foo bar "@example.com', - ], - invalid: [ - 'invalidemail@', - 'invalid.com', - '@invalid.com', - 'foo@bar.com.', - 'foo@bar.co.uk.', - 'somename@gmail.com', - 'hans.m端ller@test.com', - 'z@co.c', - 'tüst@invalid.com', - ], - }); - }); - - it('should validate email addresses with display names', function () { - test({ - validator: 'isEmail', - args: [{ allow_display_name: true }], - valid: [ - 'foo@bar.com', - 'x@x.au', - 'foo@bar.com.au', - 'foo+bar@bar.com', - 'hans.m端ller@test.com', - 'hans@m端ller.com', - 'test|123@m端ller.com', - 'test+ext@gmail.com', - 'some.name.midd.leNa.me.+extension@GoogleMail.com', - 'Some Name ', - 'Some Name ', - 'Some Name ', - 'Some Name ', - 'Some Name ', - 'Some Name ', - 'Some Name ', - 'Some Name ', - '\'Foo Bar, Esq\'', - 'Some Name ', - 'Some Middle Name ', - 'Name ', - 'Name', - ], - invalid: [ - 'invalidemail@', - 'invalid.com', - '@invalid.com', - 'foo@bar.com.', - 'foo@bar.co.uk.', - 'Some Name ', - 'Some Name ', - 'Some Name <@invalid.com>', - 'Some Name ', - 'Some Name ', - 'Some Name foo@bar.co.uk.>', - 'Some Name ', - 'Name foo@bar.co.uk', - ], - }); - }); - - it('should validate email addresses with required display names', function () { - test({ - validator: 'isEmail', - args: [{ require_display_name: true }], - valid: [ - 'Some Name ', - 'Some Name ', - 'Some Name ', - 'Some Name ', - 'Some Name ', - 'Some Name ', - 'Some Name ', - 'Some Name ', - 'Some Name ', - 'Some Middle Name ', - 'Name ', - 'Name', - ], - invalid: [ - 'some.name.midd.leNa.me.+extension@GoogleMail.com', - 'foo@bar.com', - 'x@x.au', - 'foo@bar.com.au', - 'foo+bar@bar.com', - 'hans.m端ller@test.com', - 'hans@m端ller.com', - 'test|123@m端ller.com', - 'test+ext@gmail.com', - 'invalidemail@', - 'invalid.com', - '@invalid.com', - 'foo@bar.com.', - 'foo@bar.co.uk.', - 'Some Name ', - 'Some Name ', - 'Some Name <@invalid.com>', - 'Some Name ', - 'Some Name ', - 'Some Name foo@bar.co.uk.>', - 'Some Name ', - 'Name foo@bar.co.uk', - ], - }); - }); - - - it('should validate URLs', function () { - test({ - validator: 'isURL', - valid: [ - 'foobar.com', - 'www.foobar.com', - 'foobar.com/', - 'valid.au', - 'http://www.foobar.com/', - 'http://www.foobar.com:23/', - 'http://www.foobar.com:65535/', - 'http://www.foobar.com:5/', - 'https://www.foobar.com/', - 'ftp://www.foobar.com/', - 'http://www.foobar.com/~foobar', - 'http://user:pass@www.foobar.com/', - 'http://user:@www.foobar.com/', - 'http://127.0.0.1/', - 'http://10.0.0.0/', - 'http://189.123.14.13/', - 'http://duckduckgo.com/?q=%2F', - 'http://foobar.com/t$-_.+!*\'(),', - 'http://foobar.com/?foo=bar#baz=qux', - 'http://foobar.com?foo=bar', - 'http://foobar.com#baz=qux', - 'http://www.xn--froschgrn-x9a.net/', - 'http://xn--froschgrn-x9a.com/', - 'http://foo--bar.com', - 'http://høyfjellet.no', - 'http://xn--j1aac5a4g.xn--j1amh', - 'http://xn------eddceddeftq7bvv7c4ke4c.xn--p1ai', - 'http://кулік.укр', - 'test.com?ref=http://test2.com', - 'http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html', - 'http://[1080:0:0:0:8:800:200C:417A]/index.html', - 'http://[3ffe:2a00:100:7031::1]', - 'http://[1080::8:800:200C:417A]/foo', - 'http://[::192.9.5.5]/ipng', - 'http://[::FFFF:129.144.52.38]:80/index.html', - 'http://[2010:836B:4179::836B:4179]', - ], - invalid: [ - 'http://localhost:3000/', - 'xyz://foobar.com', - 'invalid/', - 'invalid.x', - 'invalid.', - '.com', - 'http://com/', - 'http://300.0.0.1/', - 'mailto:foo@bar.com', - 'rtmp://foobar.com', - 'http://www.xn--.com/', - 'http://xn--.com/', - 'http://www.foobar.com:0/', - 'http://www.foobar.com:70000/', - 'http://www.foobar.com:99999/', - 'http://www.-foobar.com/', - 'http://www.foobar-.com/', - 'http://foobar/# lol', - 'http://foobar/? lol', - 'http://foobar/ lol/', - 'http://lol @foobar.com/', - 'http://lol:lol @foobar.com/', - 'http://lol:lol:lol@foobar.com/', - 'http://lol: @foobar.com/', - 'http://www.foo_bar.com/', - 'http://www.foobar.com/\t', - 'http://\n@www.foobar.com/', - '', - `http://foobar.com/${new Array(2083).join('f')}`, - 'http://*.foo.com', - '*.foo.com', - '!.foo.com', - 'http://example.com.', - 'http://localhost:61500this is an invalid url!!!!', - '////foobar.com', - 'http:////foobar.com', - 'https://example.com/foo//', - ], - }); - }); - - it('should validate URLs with custom protocols', function () { - test({ - validator: 'isURL', - args: [{ - protocols: ['rtmp'], - }], - valid: [ - 'rtmp://foobar.com', - ], - invalid: [ - 'http://foobar.com', - ], - }); - }); - - it('should validate file URLs without a host', function () { - test({ - validator: 'isURL', - args: [{ - protocols: ['file'], - require_host: false, - require_tld: false, - }], - valid: [ - 'file://localhost/foo.txt', - 'file:///foo.txt', - 'file:///', - ], - invalid: [ - 'http://foobar.com', - 'file://', - ], - }); - }); - - it('should validate URLs with any protocol', function () { - test({ - validator: 'isURL', - args: [{ - require_valid_protocol: false, - }], - valid: [ - 'rtmp://foobar.com', - 'http://foobar.com', - 'test://foobar.com', - ], - invalid: [ - 'mailto:test@example.com', - ], - }); - }); - - it('should validate URLs with underscores', function () { - test({ - validator: 'isURL', - args: [{ - allow_underscores: true, - }], - valid: [ - 'http://foo_bar.com', - 'http://pr.example_com.294.example.com/', - 'http://foo__bar.com', - ], - invalid: [], - }); - }); - - it('should validate URLs that do not have a TLD', function () { - test({ - validator: 'isURL', - args: [{ - require_tld: false, - }], - valid: [ - 'http://foobar.com/', - 'http://foobar/', - 'http://localhost/', - 'foobar/', - 'foobar', - ], - invalid: [], - }); - }); - - it('should validate URLs with a trailing dot option', function () { - test({ - validator: 'isURL', - args: [{ - allow_trailing_dot: true, - require_tld: false, - }], - valid: [ - 'http://example.com.', - 'foobar.', - ], - }); - }); - - it('should validate protocol relative URLs', function () { - test({ - validator: 'isURL', - args: [{ - allow_protocol_relative_urls: true, - }], - valid: [ - '//foobar.com', - 'http://foobar.com', - 'foobar.com', - ], - invalid: [ - '://foobar.com', - '/foobar.com', - '////foobar.com', - 'http:////foobar.com', - ], - }); - }); - - it('should not validate protocol relative URLs when require protocol is true', function () { - test({ - validator: 'isURL', - args: [{ - allow_protocol_relative_urls: true, - require_protocol: true, - }], - valid: [ - 'http://foobar.com', - ], - invalid: [ - '//foobar.com', - '://foobar.com', - '/foobar.com', - 'foobar.com', - ], - }); - }); - - it('should let users specify whether URLs require a protocol', function () { - test({ - validator: 'isURL', - args: [{ - require_protocol: true, - }], - valid: [ - 'http://foobar.com/', - ], - invalid: [ - 'http://localhost/', - 'foobar.com', - 'foobar', - ], - }); - }); - - it('should let users specify a host whitelist', function () { - test({ - validator: 'isURL', - args: [{ - host_whitelist: ['foo.com', 'bar.com'], - }], - valid: [ - 'http://bar.com/', - 'http://foo.com/', - ], - invalid: [ - 'http://foobar.com', - 'http://foo.bar.com/', - 'http://qux.com', - ], - }); - }); - - it('should allow regular expressions in the host whitelist', function () { - test({ - validator: 'isURL', - args: [{ - host_whitelist: ['bar.com', 'foo.com', /\.foo\.com$/], - }], - valid: [ - 'http://bar.com/', - 'http://foo.com/', - 'http://images.foo.com/', - 'http://cdn.foo.com/', - 'http://a.b.c.foo.com/', - ], - invalid: [ - 'http://foobar.com', - 'http://foo.bar.com/', - 'http://qux.com', - ], - }); - }); - - it('should let users specify a host blacklist', function () { - test({ - validator: 'isURL', - args: [{ - host_blacklist: ['foo.com', 'bar.com'], - }], - valid: [ - 'http://foobar.com', - 'http://foo.bar.com/', - 'http://qux.com', - ], - invalid: [ - 'http://bar.com/', - 'http://foo.com/', - ], - }); - }); - - it('should allow regular expressions in the host blacklist', function () { - test({ - validator: 'isURL', - args: [{ - host_blacklist: ['bar.com', 'foo.com', /\.foo\.com$/], - }], - valid: [ - 'http://foobar.com', - 'http://foo.bar.com/', - 'http://qux.com', - ], - invalid: [ - 'http://bar.com/', - 'http://foo.com/', - 'http://images.foo.com/', - 'http://cdn.foo.com/', - 'http://a.b.c.foo.com/', - ], - }); - }); - - it('should validate MAC addresses', function () { - test({ - validator: 'isMACAddress', - valid: [ - 'ab:ab:ab:ab:ab:ab', - 'FF:FF:FF:FF:FF:FF', - '01:02:03:04:05:ab', - '01:AB:03:04:05:06', - ], - invalid: [ - 'abc', - '01:02:03:04:05', - '01:02:03:04::ab', - '1:2:3:4:5:6', - 'AB:CD:EF:GH:01:02', - ], - }); - }); - - it('should validate IP addresses', function () { - test({ - validator: 'isIP', - valid: [ - '127.0.0.1', - '0.0.0.0', - '255.255.255.255', - '1.2.3.4', - '::1', - '2001:db8:0000:1:1:1:1:1', - '2001:41d0:2:a141::1', - '::ffff:127.0.0.1', - '::0000', - '0000::', - '1::', - '1111:1:1:1:1:1:1:1', - 'fe80::a6db:30ff:fe98:e946', - '::', - '::ffff:127.0.0.1', - '0:0:0:0:0:ffff:127.0.0.1', - ], - invalid: [ - 'abc', - '256.0.0.0', - '0.0.0.256', - '26.0.0.256', - '0200.200.200.200', - '200.0200.200.200', - '200.200.0200.200', - '200.200.200.0200', - '::banana', - 'banana::', - '::1banana', - '::1::', - '1:', - ':1', - ':1:1:1::2', - '1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1', - '::11111', - '11111:1:1:1:1:1:1:1', - '2001:db8:0000:1:1:1:1::1', - '0:0:0:0:0:0:ffff:127.0.0.1', - '0:0:0:0:ffff:127.0.0.1', - ], - }); - test({ - validator: 'isIP', - args: [4], - valid: [ - '127.0.0.1', - '0.0.0.0', - '255.255.255.255', - '1.2.3.4', - ], - invalid: [ - '::1', - '2001:db8:0000:1:1:1:1:1', - '::ffff:127.0.0.1', - ], - }); - test({ - validator: 'isIP', - args: [6], - valid: [ - '::1', - '2001:db8:0000:1:1:1:1:1', - '::ffff:127.0.0.1', - ], - invalid: [ - '127.0.0.1', - '0.0.0.0', - '255.255.255.255', - '1.2.3.4', - '::ffff:287.0.0.1', - ], - }); - test({ - validator: 'isIP', - args: [10], - valid: [], - invalid: [ - '127.0.0.1', - '0.0.0.0', - '255.255.255.255', - '1.2.3.4', - '::1', - '2001:db8:0000:1:1:1:1:1', - ], - }); - }); - - it('should validate FQDN', function () { - test({ - validator: 'isFQDN', - valid: [ - 'domain.com', - 'dom.plato', - 'a.domain.co', - 'foo--bar.com', - 'xn--froschgrn-x9a.com', - 'rebecca.blackfriday', - ], - invalid: [ - 'abc', - '256.0.0.0', - '_.com', - '*.some.com', - 's!ome.com', - 'domain.com/', - '/more.com', - ], - }); - }); - it('should validate FQDN with trailing dot option', function () { - test({ - validator: 'isFQDN', - args: [ - { allow_trailing_dot: true }, - ], - valid: [ - 'example.com.', - ], - }); - }); - - it('should validate alpha strings', function () { - test({ - validator: 'isAlpha', - valid: [ - 'abc', - 'ABC', - 'FoObar', - ], - invalid: [ - 'abc1', - ' foo ', - '', - 'ÄBC', - 'FÜübar', - 'Jön', - 'Heiß', - ], - }); - }); - - it('should validate czech alpha strings', function () { - test({ - validator: 'isAlpha', - args: ['cs-CZ'], - valid: [ - 'žluťoučký', - 'KŮŇ', - 'Pěl', - 'Ďábelské', - 'ódy', - ], - invalid: [ - 'ábc1', - ' fůj ', - '', - ], - }); - }); - - it('should validate danish alpha strings', function () { - test({ - validator: 'isAlpha', - args: ['da-DK'], - valid: [ - 'aøå', - 'Ære', - 'Øre', - 'Åre', - ], - invalid: [ - 'äbc123', - 'ÄBC11', - '', - ], - }); - }); - - it('should validate dutch alpha strings', function () { - test({ - validator: 'isAlpha', - args: ['nl-NL'], - valid: [ - 'Kán', - 'één', - 'vóór', - 'nú', - 'héél', - ], - invalid: [ - 'äca ', - 'abcß', - 'Øre', - ], - }); - }); - - it('should validate german alpha strings', function () { - test({ - validator: 'isAlpha', - args: ['de-DE'], - valid: [ - 'äbc', - 'ÄBC', - 'FöÖbär', - 'Heiß', - ], - invalid: [ - 'äbc1', - ' föö ', - '', - ], - }); - }); - - it('should validate hungarian alpha strings', function () { - test({ - validator: 'isAlpha', - args: ['hu-HU'], - valid: [ - 'árvíztűrőtükörfúrógép', - 'ÁRVÍZTŰRŐTÜKÖRFÚRÓGÉP', - ], - invalid: [ - 'äbc1', - ' fäö ', - 'Heiß', - '', - ], - }); - }); - - it('should validate italian alpha strings', function () { - test({ - validator: 'isAlpha', - args: ['it-IT'], - valid: [ - 'àéèìîóòù', - 'correnti', - 'DEFINIZIONE', - 'compilazione', - 'metró', - 'pèsca', - 'PÉSCA', - 'genî', - ], - invalid: [ - 'äbc123', - 'ÄBC11', - 'æøå', - '', - ], - }); - }); - - it('should validate arabic alpha strings', function () { - test({ - validator: 'isAlpha', - args: ['ar'], - valid: [ - 'أبت', - 'اَبِتَثّجً', - ], - invalid: [ - '١٢٣أبت', - '١٢٣', - 'abc1', - ' foo ', - '', - 'ÄBC', - 'FÜübar', - 'Jön', - 'Heiß', - ], - }); - }); - - it('should validate norwegian alpha strings', function () { - test({ - validator: 'isAlpha', - args: ['nb-NO'], - valid: [ - 'aøå', - 'Ære', - 'Øre', - 'Åre', - ], - invalid: [ - 'äbc123', - 'ÄBC11', - '', - ], - }); - }); - - it('should validate polish alpha strings', function () { - test({ - validator: 'isAlpha', - args: ['pl-PL'], - valid: [ - 'kreską', - 'zamknięte', - 'zwykłe', - 'kropką', - 'przyjęły', - 'święty', - 'Pozwól', - ], - invalid: [ - '12řiď ', - 'blé!!', - 'föö!2!', - ], - }); - }); - - it('should validate serbian cyrillic alpha strings', function () { - test({ - validator: 'isAlpha', - args: ['sr-RS'], - valid: [ - 'ШћжЂљЕ', - 'ЧПСТЋЏ', - ], - invalid: [ - 'řiď ', - 'blé33!!', - 'föö!!', - ], - }); - }); - - it('should validate serbian latin alpha strings', function () { - test({ - validator: 'isAlpha', - args: ['sr-RS@latin'], - valid: [ - 'ŠAabčšđćž', - 'ŠATROĆčđš', - ], - invalid: [ - '12řiď ', - 'blé!!', - 'föö!2!', - ], - }); - }); - - it('should validate spanish alpha strings', function () { - test({ - validator: 'isAlpha', - args: ['es-ES'], - valid: [ - 'ábcó', - 'ÁBCÓ', - 'dormís', - 'volvés', - 'español', - ], - invalid: [ - 'äca ', - 'abcß', - 'föö!!', - ], - }); - }); - - it('should validate swedish alpha strings', function () { - test({ - validator: 'isAlpha', - args: ['sv-SE'], - valid: [ - 'religiös', - 'stjäla', - 'västgöte', - 'Åre', - ], - invalid: [ - 'AİıÖöÇ窺ĞğÜüZ', - 'religiös23', - '', - ], - }); - }); - - it('should validate defined arabic locales alpha strings', function () { - test({ - validator: 'isAlpha', - args: ['ar-SY'], - valid: [ - 'أبت', - 'اَبِتَثّجً', - ], - invalid: [ - '١٢٣أبت', - '١٢٣', - 'abc1', - ' foo ', - '', - 'ÄBC', - 'FÜübar', - 'Jön', - 'Heiß', - ], - }); - }); - - it('should validate turkish alpha strings', function () { - test({ - validator: 'isAlpha', - args: ['tr-TR'], - valid: [ - 'AİıÖöÇ窺ĞğÜüZ', - ], - invalid: [ - '0AİıÖöÇ窺ĞğÜüZ1', - ' AİıÖöÇ窺ĞğÜüZ ', - 'abc1', - ' foo ', - '', - 'ÄBC', - 'Heiß', - ], - }); - }); - - it('should validate urkrainian alpha strings', function () { - test({ - validator: 'isAlpha', - args: ['uk-UA'], - valid: [ - 'АБВГҐДЕЄЖЗИIЇЙКЛМНОПРСТУФХЦШЩЬЮЯ', - ], - invalid: [ - '0AİıÖöÇ窺ĞğÜüZ1', - ' AİıÖöÇ窺ĞğÜüZ ', - 'abc1', - ' foo ', - '', - 'ÄBC', - 'Heiß', - 'ЫыЪъЭэ', - ], - }); - }); - - it('should validate alphanumeric strings', function () { - test({ - validator: 'isAlphanumeric', - valid: [ - 'abc123', - 'ABC11', - ], - invalid: [ - 'abc ', - 'foo!!', - 'ÄBC', - 'FÜübar', - 'Jön', - ], - }); - }); - - it('should validate defined english aliases', function () { - test({ - validator: 'isAlphanumeric', - args: ['en-GB'], - valid: [ - 'abc123', - 'ABC11', - ], - invalid: [ - 'abc ', - 'foo!!', - 'ÄBC', - 'FÜübar', - 'Jön', - ], - }); - }); - - it('should validate czech alphanumeric strings', function () { - test({ - validator: 'isAlphanumeric', - args: ['cs-CZ'], - valid: [ - 'řiť123', - 'KŮŇ11', - ], - invalid: [ - 'řiď ', - 'blé!!', - ], - }); - }); - - it('should validate danish alphanumeric strings', function () { - test({ - validator: 'isAlphanumeric', - args: ['da-DK'], - valid: [ - 'ÆØÅ123', - 'Ære321', - '321Øre', - '123Åre', - ], - invalid: [ - 'äbc123', - 'ÄBC11', - '', - ], - }); - }); - - it('should validate dutch alphanumeric strings', function () { - test({ - validator: 'isAlphanumeric', - args: ['nl-NL'], - valid: [ - 'Kán123', - 'één354', - 'v4óór', - 'nú234', - 'hé54él', - ], - invalid: [ - '1äca ', - 'ab3cß', - 'Øre', - ], - }); - }); - - it('should validate german alphanumeric strings', function () { - test({ - validator: 'isAlphanumeric', - args: ['de-DE'], - valid: [ - 'äbc123', - 'ÄBC11', - ], - invalid: [ - 'äca ', - 'föö!!', - ], - }); - }); - - it('should validate hungarian alphanumeric strings', function () { - test({ - validator: 'isAlphanumeric', - args: ['hu-HU'], - valid: [ - '0árvíztűrőtükörfúrógép123', - '0ÁRVÍZTŰRŐTÜKÖRFÚRÓGÉP123', - ], - invalid: [ - '1időúr!', - 'äbc1', - ' fäö ', - 'Heiß!', - '', - ], - }); - }); - - it('should validate italian alphanumeric strings', function () { - test({ - validator: 'isAlphanumeric', - args: ['it-IT'], - valid: [ - '123àéèìîóòù', - '123correnti', - 'DEFINIZIONE321', - 'compil123azione', - 'met23ró', - 'pès56ca', - 'PÉS45CA', - 'gen45î', - ], - invalid: [ - 'äbc123', - 'ÄBC11', - 'æøå', - '', - ], - }); - }); - - it('should validate spanish alphanumeric strings', function () { - test({ - validator: 'isAlphanumeric', - args: ['es-ES'], - valid: [ - 'ábcó123', - 'ÁBCÓ11', - ], - invalid: [ - 'äca ', - 'abcß', - 'föö!!', - ], - }); - }); - - it('should validate arabic alphanumeric strings', function () { - test({ - validator: 'isAlphanumeric', - args: ['ar'], - valid: [ - 'أبت123', - 'أبتَُِ١٢٣', - ], - invalid: [ - 'äca ', - 'abcß', - 'föö!!', - ], - }); - }); - - it('should validate defined arabic aliases', function () { - test({ - validator: 'isAlphanumeric', - args: ['ar-SY'], - valid: [ - 'أبت123', - 'أبتَُِ١٢٣', - ], - invalid: [ - 'abc ', - 'foo!!', - 'ÄBC', - 'FÜübar', - 'Jön', - ], - }); - }); - - it('should validate norwegian alphanumeric strings', function () { - test({ - validator: 'isAlphanumeric', - args: ['nb-NO'], - valid: [ - 'ÆØÅ123', - 'Ære321', - '321Øre', - '123Åre', - ], - invalid: [ - 'äbc123', - 'ÄBC11', - '', - ], - }); - }); - - it('should validate polish alphanumeric strings', function () { - test({ - validator: 'isAlphanumeric', - args: ['pl-PL'], - valid: [ - 'kre123ską', - 'zam21knięte', - 'zw23ykłe', - '123', - 'prz23yjęły', - 'świ23ęty', - 'Poz1322wól', - ], - invalid: [ - '12řiď ', - 'blé!!', - 'föö!2!', - ], - }); - }); - - it('should validate serbian cyrillic alphanumeric strings', function () { - test({ - validator: 'isAlphanumeric', - args: ['sr-RS'], - valid: [ - 'ШћжЂљЕ123', - 'ЧПСТ132ЋЏ', - ], - invalid: [ - 'řiď ', - 'blé!!', - 'föö!!', - ], - }); - }); - - it('should validate serbian latin alphanumeric strings', function () { - test({ - validator: 'isAlphanumeric', - args: ['sr-RS@latin'], - valid: [ - 'ŠAabčšđćž123', - 'ŠATRO11Ćčđš', - ], - invalid: [ - 'řiď ', - 'blé!!', - 'föö!!', - ], - }); - }); - - it('should validate swedish alphanumeric strings', function () { - test({ - validator: 'isAlphanumeric', - args: ['sv-SE'], - valid: [ - 'religiös13', - 'st23jäla', - 'västgöte123', - '123Åre', - ], - invalid: [ - 'AİıÖöÇ窺ĞğÜüZ', - 'foo!!', - '', - ], - }); - }); - - it('should validate turkish alphanumeric strings', function () { - test({ - validator: 'isAlphanumeric', - args: ['tr-TR'], - valid: [ - 'AİıÖöÇ窺ĞğÜüZ123', - ], - invalid: [ - 'AİıÖöÇ窺ĞğÜüZ ', - 'foo!!', - 'ÄBC', - ], - }); - }); - - it('should validate urkrainian alphanumeric strings', function () { - test({ - validator: 'isAlphanumeric', - args: ['uk-UA'], - valid: [ - 'АБВГҐДЕЄЖЗИIЇЙКЛМНОПРСТУФХЦШЩЬЮЯ123', - ], - invalid: [ - 'éeoc ', - 'foo!!', - 'ÄBC', - 'ЫыЪъЭэ', - ], - }); - }); - - it('should error on invalid locale', function () { - try { - validator.isAlphanumeric('abc123', 'in-INVALID'); - assert(false); - } catch (err) { - assert(true); - } - }); - - it('should validate numeric strings', function () { - test({ - validator: 'isNumeric', - valid: [ - '123', - '00123', - '-00123', - '0', - '-0', - '+123', - ], - invalid: [ - '123.123', - ' ', - '.', - ], - }); - }); - - it('should validate ports', function () { - test({ - validator: 'isPort', - valid: [ - '0', - '22', - '80', - '443', - '3000', - '8080', - '65535', - ], - invalid: [ - '', - '-1', - '65536', - ], - }); - }); - - it('should validate decimal numbers', function () { - test({ - validator: 'isDecimal', - valid: [ - '123', - '00123', - '-00123', - '0', - '-0', - '+123', - '0.01', - '.1', - '1.0', - '-.25', - '-0', - '0.0000000000001', - ], - invalid: [ - '0,01', - ',1', - '1,0', - '-,25', - '0,0000000000001', - '0٫01', - '٫1', - '1٫0', - '-٫25', - '0٫0000000000001', - '....', - ' ', - '', - '-', - '+', - '.', - '0.1a', - 'a', - '\n', - ], - }); - - test({ - validator: 'isDecimal', - args: [{ locale: 'en-AU' }], - valid: [ - '123', - '00123', - '-00123', - '0', - '-0', - '+123', - '0.01', - '.1', - '1.0', - '-.25', - '-0', - '0.0000000000001', - ], - invalid: [ - '0,01', - ',1', - '1,0', - '-,25', - '0,0000000000001', - '0٫01', - '٫1', - '1٫0', - '-٫25', - '0٫0000000000001', - '....', - ' ', - '', - '-', - '+', - '.', - '0.1a', - 'a', - '\n', - ], - }); - - test({ - validator: 'isDecimal', - args: [{ locale: ['cs-CZ'] }], - valid: [ - '123', - '00123', - '-00123', - '0', - '-0', - '+123', - '0,01', - ',1', - '1,0', - '-,25', - '-0', - '0,0000000000001', - ], - invalid: [ - '0.0000000000001', - '0.01', - '.1', - '1.0', - '-.25', - '0٫01', - '٫1', - '1٫0', - '-٫25', - '0٫0000000000001', - '....', - ' ', - '', - '-', - '+', - '.', - '0.1a', - 'a', - '\n', - ], - }); - - test({ - validator: 'isDecimal', - args: [{ locale: ['ar-JO'] }], - valid: [ - '123', - '00123', - '-00123', - '0', - '-0', - '+123', - '0٫01', - '٫1', - '1٫0', - '-٫25', - '-0', - '0٫0000000000001', - ], - invalid: [ - '0,0000000000001', - '0,01', - ',1', - '1,0', - '-,25', - '0.0000000000001', - '0.01', - '.1', - '1.0', - '-.25', - '....', - ' ', - '', - '-', - '+', - '.', - '0.1a', - 'a', - '\n', - ], - }); - - test({ - validator: 'isDecimal', - args: [{ force_decimal: true }], - valid: [ - '0.01', - '.1', - '1.0', - '-.25', - '0.0000000000001', - ], - invalid: [ - '-0', - '123', - '00123', - '-00123', - '0', - '-0', - '+123', - '0,0000000000001', - '0,01', - ',1', - '1,0', - '-,25', - '....', - ' ', - '', - '-', - '+', - '.', - '0.1a', - 'a', - '\n', - ], - }); - - test({ - validator: 'isDecimal', - args: [{ decimal_digits: '2,3' }], - valid: [ - '123', - '00123', - '-00123', - '0', - '-0', - '+123', - '0.01', - '1.043', - '.15', - '-.255', - '-0', - ], - invalid: [ - '0.0000000000001', - '0.0', - '.1', - '1.0', - '-.2564', - '0.0', - '٫1', - '1٫0', - '-٫25', - '0٫0000000000001', - '....', - ' ', - '', - '-', - '+', - '.', - '0.1a', - 'a', - '\n', - ], - }); - }); - - it('should validate lowercase strings', function () { - test({ - validator: 'isLowercase', - valid: [ - 'abc', - 'abc123', - 'this is lowercase.', - 'tr竪s 端ber', - ], - invalid: [ - 'fooBar', - '123A', - ], - }); - }); - - it('should validate uppercase strings', function () { - test({ - validator: 'isUppercase', - valid: [ - 'ABC', - 'ABC123', - 'ALL CAPS IS FUN.', - ' .', - ], - invalid: [ - 'fooBar', - '123abc', - ], - }); - }); - - it('should validate integers', function () { - test({ - validator: 'isInt', - valid: [ - '13', - '123', - '0', - '123', - '-0', - '+1', - '01', - '-01', - '000', - ], - invalid: [ - '100e10', - '123.123', - ' ', - '', - ], - }); - test({ - validator: 'isInt', - args: [{ allow_leading_zeroes: false }], - valid: [ - '13', - '123', - '0', - '123', - '-0', - '+1', - ], - invalid: [ - '01', - '-01', - '000', - '100e10', - '123.123', - ' ', - '', - ], - }); - test({ - validator: 'isInt', - args: [{ allow_leading_zeroes: true }], - valid: [ - '13', - '123', - '0', - '123', - '-0', - '+1', - '01', - '-01', - '000', - '-000', - '+000', - ], - invalid: [ - '100e10', - '123.123', - ' ', - '', - ], - }); - test({ - validator: 'isInt', - args: [{ - min: 10, - }], - valid: [ - '15', - '80', - '99', - ], - invalid: [ - '9', - '6', - '3.2', - 'a', - ], - }); - test({ - validator: 'isInt', - args: [{ - min: 10, - max: 15, - }], - valid: [ - '15', - '11', - '13', - ], - invalid: [ - '9', - '2', - '17', - '3.2', - '33', - 'a', - ], - }); - test({ - validator: 'isInt', - args: [{ - gt: 10, - lt: 15, - }], - valid: [ - '14', - '11', - '13', - ], - invalid: [ - '10', - '15', - '17', - '3.2', - '33', - 'a', - ], - }); - }); - - it('should validate floats', function () { - test({ - validator: 'isFloat', - valid: [ - '123', - '123.', - '123.123', - '-123.123', - '-0.123', - '+0.123', - '0.123', - '.0', - '-.123', - '+.123', - '01.123', - '-0.22250738585072011e-307', - ], - invalid: [ - '+', - '-', - ' ', - '', - '.', - 'foo', - ], - }); - - test({ - validator: 'isFloat', - args: [{ locale: 'en-AU' }], - valid: [ - '123', - '123.', - '123.123', - '-123.123', - '-0.123', - '+0.123', - '0.123', - '.0', - '-.123', - '+.123', - '01.123', - '-0.22250738585072011e-307', - ], - invalid: [ - '123٫123', - '123,123', - ' ', - '', - '.', - 'foo', - ], - }); - - test({ - validator: 'isFloat', - args: [{ locale: 'de-DE' }], - valid: [ - '123', - '123,', - '123,123', - '-123,123', - '-0,123', - '+0,123', - '0,123', - ',0', - '-,123', - '+,123', - '01,123', - '-0,22250738585072011e-307', - ], - invalid: [ - '123.123', - '123٫123', - ' ', - '', - '.', - 'foo', - ], - }); - - test({ - validator: 'isFloat', - args: [{ locale: 'ar-JO' }], - valid: [ - '123', - '123٫', - '123٫123', - '-123٫123', - '-0٫123', - '+0٫123', - '0٫123', - '٫0', - '-٫123', - '+٫123', - '01٫123', - '-0٫22250738585072011e-307', - ], - invalid: [ - '123,123', - '123.123', - ' ', - '', - '.', - 'foo', - ], - }); - - test({ - validator: 'isFloat', - args: [{ - min: 3.7, - }], - valid: [ - '3.888', - '3.92', - '4.5', - '50', - '3.7', - '3.71', - ], - invalid: [ - '3.6', - '3.69', - '3', - '1.5', - 'a', - ], - }); - test({ - validator: 'isFloat', - args: [{ - min: 0.1, - max: 1.0, - }], - valid: [ - '0.1', - '1.0', - '0.15', - '0.33', - '0.57', - '0.7', - ], - invalid: [ - '0', - '0.0', - 'a', - '1.3', - '0.05', - '5', - ], - }); - test({ - validator: 'isFloat', - args: [{ - gt: -5.5, - lt: 10, - }], - valid: [ - '9.9', - '1.0', - '0', - '-1', - '7', - '-5.4', - ], - invalid: [ - '10', - '-5.5', - 'a', - '-20.3', - '20e3', - '10.00001', - ], - }); - test({ - validator: 'isFloat', - args: [{ - min: -5.5, - max: 10, - gt: -5.5, - lt: 10, - }], - valid: [ - '9.99999', - '-5.499999', - ], - invalid: [ - '10', - '-5.5', - ], - }); - }); - - it('should validate hexadecimal strings', function () { - test({ - validator: 'isHexadecimal', - valid: [ - 'deadBEEF', - 'ff0044', - ], - invalid: [ - 'abcdefg', - '', - '..', - ], - }); - }); - - it('should validate hexadecimal color strings', function () { - test({ - validator: 'isHexColor', - valid: [ - '#ff0034', - '#CCCCCC', - 'fff', - '#f00', - ], - invalid: [ - '#ff', - 'fff0', - '#ff12FG', - ], - }); - }); - - it('should validate ISRC code strings', function () { - test({ - validator: 'isISRC', - valid: [ - 'USAT29900609', - 'GBAYE6800011', - 'USRC15705223', - 'USCA29500702', - ], - invalid: [ - 'USAT2990060', - 'SRC15705223', - 'US-CA29500702', - 'USARC15705223', - ], - }); - }); - - it('should validate md5 strings', function () { - test({ - validator: 'isMD5', - valid: [ - 'd94f3f016ae679c3008de268209132f2', - '751adbc511ccbe8edf23d486fa4581cd', - '88dae00e614d8f24cfd5a8b3f8002e93', - '0bf1c35032a71a14c2f719e5a14c1e96', - ], - invalid: [ - 'KYT0bf1c35032a71a14c2f719e5a14c1', - 'q94375dj93458w34', - '39485729348', - '%&FHKJFvk', - ], - }); - }); - - it('should validate hash strings', function () { - test({ - validator: 'isHash', - args: ['md5', 'md4', 'ripemd128', 'tiger128'], - valid: [ - 'd94f3f016ae679c3008de268209132f2', - '751adbc511ccbe8edf23d486fa4581cd', - '88dae00e614d8f24cfd5a8b3f8002e93', - '0bf1c35032a71a14c2f719e5a14c1e96', - ], - invalid: [ - 'KYT0bf1c35032a71a14c2f719e5a14c1', - 'q94375dj93458w34', - '39485729348', - '%&FHKJFvk', - ], - }); - test({ - validator: 'isHash', - args: ['crc32', 'crc32b'], - valid: [ - 'd94f3f01', - '751adbc5', - '88dae00e', - '0bf1c350', - ], - invalid: [ - 'KYT0bf1c35032a71a14c2f719e5a14c1', - 'q94375dj93458w34', - 'q943', - '39485729348', - '%&FHKJFvk', - ], - }); - test({ - validator: 'isHash', - args: ['sha1', 'tiger160', 'ripemd160'], - valid: [ - '3ca25ae354e192b26879f651a51d92aa8a34d8d3', - 'aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d', - 'beb8c3f30da46be179b8df5f5ecb5e4b10508230', - 'efd5d3b190e893ed317f38da2420d63b7ae0d5ed', - ], - invalid: [ - 'KYT0bf1c35032a71a14c2f719e5a14c1', - 'KYT0bf1c35032a71a14c2f719e5a14c1dsjkjkjkjkkjk', - 'q94375dj93458w34', - '39485729348', - '%&FHKJFvk', - ], - }); - test({ - validator: 'isHash', - args: ['sha256'], - valid: [ - '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824', - '1d996e033d612d9af2b44b70061ee0e868bfd14c2dd90b129e1edeb7953e7985', - '80f70bfeaed5886e33536bcfa8c05c60afef5a0e48f699a7912d5e399cdcc441', - '579282cfb65ca1f109b78536effaf621b853c9f7079664a3fbe2b519f435898c', - ], - invalid: [ - 'KYT0bf1c35032a71a14c2f719e5a14c1', - 'KYT0bf1c35032a71a14c2f719e5a14c1dsjkjkjkjkkjk', - 'q94375dj93458w34', - '39485729348', - '%&FHKJFvk', - ], - }); - test({ - validator: 'isHash', - args: ['sha384'], - valid: [ - '3fed1f814d28dc5d63e313f8a601ecc4836d1662a19365cbdcf6870f6b56388850b58043f7ebf2418abb8f39c3a42e31', - 'b330f4e575db6e73500bd3b805db1a84b5a034e5d21f0041d91eec85af1dfcb13e40bb1c4d36a72487e048ac6af74b58', - 'bf547c3fc5841a377eb1519c2890344dbab15c40ae4150b4b34443d2212e5b04aa9d58865bf03d8ae27840fef430b891', - 'fc09a3d11368386530f985dacddd026ae1e44e0e297c805c3429d50744e6237eb4417c20ffca8807b071823af13a3f65', - ], - invalid: [ - 'KYT0bf1c35032a71a14c2f719e5a14c1', - 'KYT0bf1c35032a71a14c2f719e5a14c1dsjkjkjkjkkjk', - 'q94375dj93458w34', - '39485729348', - '%&FHKJFvk', - ], - }); - test({ - validator: 'isHash', - args: ['sha512'], - valid: [ - '9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043', - '83c586381bf5ba94c8d9ba8b6b92beb0997d76c257708742a6c26d1b7cbb9269af92d527419d5b8475f2bb6686d2f92a6649b7f174c1d8306eb335e585ab5049', - '45bc5fa8cb45ee408c04b6269e9f1e1c17090c5ce26ffeeda2af097735b29953ce547e40ff3ad0d120e5361cc5f9cee35ea91ecd4077f3f589b4d439168f91b9', - '432ac3d29e4f18c7f604f7c3c96369a6c5c61fc09bf77880548239baffd61636d42ed374f41c261e424d20d98e320e812a6d52865be059745fdb2cb20acff0ab', - ], - invalid: [ - 'KYT0bf1c35032a71a14c2f719e5a14c1', - 'KYT0bf1c35032a71a14c2f719e5a14c1dsjkjkjkjkkjk', - 'q94375dj93458w34', - '39485729348', - '%&FHKJFvk', - ], - }); - test({ - validator: 'isHash', - args: ['tiger192'], - valid: [ - '6281a1f098c5e7290927ed09150d43ff3990a0fe1a48267c', - '56268f7bc269cf1bc83d3ce42e07a85632394737918f4760', - '46fc0125a148788a3ac1d649566fc04eb84a746f1a6e4fa7', - '7731ea1621ae99ea3197b94583d034fdbaa4dce31a67404a', - ], - invalid: [ - 'KYT0bf1c35032a71a14c2f719e5a14c1', - 'KYT0bf1c35032a71a14c2f719e5a14c1dsjkjkjkjkkjk', - 'q94375dj93458w34', - '39485729348', - '%&FHKJFvk', - ], - }); - }); - - it('should validate null strings', function () { - test({ - validator: 'isEmpty', - valid: [ - '', - ], - invalid: [ - ' ', - 'foo', - '3', - ], - }); - }); - - it('should validate strings against an expected value', function () { - test({ validator: 'equals', args: ['abc'], valid: ['abc'], invalid: ['Abc', '123'] }); - }); - - it('should validate strings contain another string', function () { - test({ - validator: 'contains', - args: ['foo'], - valid: ['foo', 'foobar', 'bazfoo'], - invalid: ['bar', 'fobar'], - }); - }); - - it('should validate strings against a pattern', function () { - test({ - validator: 'matches', - args: [/abc/], - valid: ['abc', 'abcdef', '123abc'], - invalid: ['acb', 'Abc'], - }); - test({ - validator: 'matches', - args: ['abc'], - valid: ['abc', 'abcdef', '123abc'], - invalid: ['acb', 'Abc'], - }); - test({ - validator: 'matches', - args: ['abc', 'i'], - valid: ['abc', 'abcdef', '123abc', 'AbC'], - invalid: ['acb'], - }); - }); - - it('should validate strings by length (deprecated api)', function () { - test({ - validator: 'isLength', - args: [2], - valid: ['abc', 'de', 'abcd'], - invalid: ['', 'a'], - }); - test({ - validator: 'isLength', - args: [2, 3], - valid: ['abc', 'de'], - invalid: ['', 'a', 'abcd'], - }); - test({ - validator: 'isLength', - args: [2, 3], - valid: ['干𩸽', '𠮷野家'], - invalid: ['', '𠀋', '千竈通り'], - }); - test({ - validator: 'isLength', - args: [0, 0], - valid: [''], - invalid: ['a', 'ab'], - }); - }); - - it('should validate strings by byte length (deprecated api)', function () { - test({ - validator: 'isByteLength', - args: [2], - valid: ['abc', 'de', 'abcd', 'gmail'], - invalid: ['', 'a'], - }); - test({ - validator: 'isByteLength', - args: [2, 3], - valid: ['abc', 'de', 'g'], - invalid: ['', 'a', 'abcd', 'gm'], - }); - test({ - validator: 'isByteLength', - args: [0, 0], - valid: [''], - invalid: ['g', 'a'], - }); - }); - - it('should validate strings by length', function () { - test({ - validator: 'isLength', - args: [{ min: 2 }], - valid: ['abc', 'de', 'abcd'], - invalid: ['', 'a'], - }); - test({ - validator: 'isLength', - args: [{ min: 2, max: 3 }], - valid: ['abc', 'de'], - invalid: ['', 'a', 'abcd'], - }); - test({ - validator: 'isLength', - args: [{ min: 2, max: 3 }], - valid: ['干𩸽', '𠮷野家'], - invalid: ['', '𠀋', '千竈通り'], - }); - test({ - validator: 'isLength', - args: [{ max: 3 }], - valid: ['abc', 'de', 'a', ''], - invalid: ['abcd'], - }); - test({ - validator: 'isLength', - args: [{ max: 0 }], - valid: [''], - invalid: ['a', 'ab'], - }); - }); - - it('should validate strings by byte length', function () { - test({ - validator: 'isByteLength', - args: [{ min: 2 }], - valid: ['abc', 'de', 'abcd', 'gmail'], - invalid: ['', 'a'], - }); - test({ - validator: 'isByteLength', - args: [{ min: 2, max: 3 }], - valid: ['abc', 'de', 'g'], - invalid: ['', 'a', 'abcd', 'gm'], - }); - test({ - validator: 'isByteLength', - args: [{ max: 3 }], - valid: ['abc', 'de', 'g', 'a', ''], - invalid: ['abcd', 'gm'], - }); - test({ - validator: 'isByteLength', - args: [{ max: 0 }], - valid: [''], - invalid: ['g', 'a'], - }); - }); - - it('should validate UUIDs', function () { - test({ - validator: 'isUUID', - valid: [ - 'A987FBC9-4BED-3078-CF07-9141BA07C9F3', - 'A987FBC9-4BED-4078-8F07-9141BA07C9F3', - 'A987FBC9-4BED-5078-AF07-9141BA07C9F3', - ], - invalid: [ - '', - 'xxxA987FBC9-4BED-3078-CF07-9141BA07C9F3', - 'A987FBC9-4BED-3078-CF07-9141BA07C9F3xxx', - 'A987FBC94BED3078CF079141BA07C9F3', - '934859', - '987FBC9-4BED-3078-CF07A-9141BA07C9F3', - 'AAAAAAAA-1111-1111-AAAG-111111111111', - ], - }); - test({ - validator: 'isUUID', - args: [3], - valid: [ - 'A987FBC9-4BED-3078-CF07-9141BA07C9F3', - ], - invalid: [ - '', - 'xxxA987FBC9-4BED-3078-CF07-9141BA07C9F3', - '934859', - 'AAAAAAAA-1111-1111-AAAG-111111111111', - 'A987FBC9-4BED-4078-8F07-9141BA07C9F3', - 'A987FBC9-4BED-5078-AF07-9141BA07C9F3', - ], - }); - test({ - validator: 'isUUID', - args: [4], - valid: [ - '713ae7e3-cb32-45f9-adcb-7c4fa86b90c1', - '625e63f3-58f5-40b7-83a1-a72ad31acffb', - '57b73598-8764-4ad0-a76a-679bb6640eb1', - '9c858901-8a57-4791-81fe-4c455b099bc9', - ], - invalid: [ - '', - 'xxxA987FBC9-4BED-3078-CF07-9141BA07C9F3', - '934859', - 'AAAAAAAA-1111-1111-AAAG-111111111111', - 'A987FBC9-4BED-5078-AF07-9141BA07C9F3', - 'A987FBC9-4BED-3078-CF07-9141BA07C9F3', - ], - }); - test({ - validator: 'isUUID', - args: [5], - valid: [ - '987FBC97-4BED-5078-AF07-9141BA07C9F3', - '987FBC97-4BED-5078-BF07-9141BA07C9F3', - '987FBC97-4BED-5078-8F07-9141BA07C9F3', - '987FBC97-4BED-5078-9F07-9141BA07C9F3', - ], - invalid: [ - '', - 'xxxA987FBC9-4BED-3078-CF07-9141BA07C9F3', - '934859', - 'AAAAAAAA-1111-1111-AAAG-111111111111', - '9c858901-8a57-4791-81fe-4c455b099bc9', - 'A987FBC9-4BED-3078-CF07-9141BA07C9F3', - ], - }); - }); - - it('should validate a string that is in another string or array', function () { - test({ - validator: 'isIn', - args: ['foobar'], - valid: ['foo', 'bar', 'foobar', ''], - invalid: ['foobarbaz', 'barfoo'], - }); - test({ - validator: 'isIn', - args: [['foo', 'bar']], - valid: ['foo', 'bar'], - invalid: ['foobar', 'barfoo', ''], - }); - test({ - validator: 'isIn', - args: [['1', '2', '3']], - valid: ['1', '2', '3'], - invalid: ['4', ''], - }); - test({ validator: 'isIn', invalid: ['foo', ''] }); - }); - - it('should validate a string that is in another object', function () { - test({ - validator: 'isIn', - args: [{ 'foo': 1, 'bar': 2, 'foobar': 3 }], - valid: ['foo', 'bar', 'foobar'], - invalid: ['foobarbaz', 'barfoo', ''], - }); - test({ - validator: 'isIn', - args: [{ 1: 3, 2: 0, 3: 1 }], - valid: ['1', '2', '3'], - invalid: ['4', ''], - }); - }); - - it('should validate dates against a start date', function () { - test({ - validator: 'isAfter', - args: ['2011-08-03'], - valid: ['2011-08-04', new Date(2011, 8, 10).toString()], - invalid: ['2010-07-02', '2011-08-03', new Date(0).toString(), 'foo'], - }); - test({ - validator: 'isAfter', - valid: ['2100-08-04', new Date(Date.now() + 86400000).toString()], - invalid: ['2010-07-02', new Date(0).toString()], - }); - test({ - validator: 'isAfter', - args: ['2011-08-03'], - valid: ['2015-09-17'], - invalid: ['invalid date'], - }); - test({ - validator: 'isAfter', - args: ['invalid date'], - invalid: ['invalid date', '2015-09-17'], - }); - }); - - it('should validate dates against an end date', function () { - test({ - validator: 'isBefore', - args: ['08/04/2011'], - valid: ['2010-07-02', '2010-08-04', new Date(0).toString()], - invalid: ['08/04/2011', new Date(2011, 9, 10).toString()], - }); - test({ - validator: 'isBefore', - args: [new Date(2011, 7, 4).toString()], - valid: ['2010-07-02', '2010-08-04', new Date(0).toString()], - invalid: ['08/04/2011', new Date(2011, 9, 10).toString()], - }); - test({ - validator: 'isBefore', - valid: [ - '2000-08-04', - new Date(0).toString(), - new Date(Date.now() - 86400000).toString(), - ], - invalid: ['2100-07-02', new Date(2217, 10, 10).toString()], - }); - test({ - validator: 'isBefore', - args: ['2011-08-03'], - valid: ['1999-12-31'], - invalid: ['invalid date'], - }); - test({ - validator: 'isBefore', - args: ['invalid date'], - invalid: ['invalid date', '1999-12-31'], - }); - }); - - it('should validate that integer strings are divisible by a number', function () { - test({ - validator: 'isDivisibleBy', - args: [2], - valid: ['2', '4', '100', '1000'], - invalid: [ - '1', - '2.5', - '101', - 'foo', - '', - ], - }); - }); - - it('should validate credit cards', function () { - test({ - validator: 'isCreditCard', - valid: [ - '375556917985515', - '36050234196908', - '4716461583322103', - '4716-2210-5188-5662', - '4929 7226 5379 7141', - '5398228707871527', - '6283875070985593', - '6263892624162870', - '6234917882863855', - '6234698580215388', - '6226050967750613', - '6246281879460688', - '2222155765072228', - '2225855203075256', - '2720428011723762', - '2718760626256570', - ], - invalid: [ - 'foo', - 'foo', - '5398228707871528', - '2718760626256571', - '2721465526338453', - '2220175103860763', - '375556917985515999999993', - '899999996234917882863855', - 'prefix6234917882863855', - '623491788middle2863855', - '6234917882863855suffix', - ], - }); - }); - - it('should validate ISINs', function () { - test({ - validator: 'isISIN', - valid: [ - 'AU0000XVGZA3', - 'DE000BAY0017', - 'BE0003796134', - 'SG1G55870362', - 'GB0001411924', - 'DE000WCH8881', - 'PLLWBGD00016', - ], - invalid: [ - 'DE000BAY0018', - 'PLLWBGD00019', - 'foo', - '5398228707871528', - ], - }); - }); - - it('should validate ISBNs', function () { - test({ - validator: 'isISBN', - args: [10], - valid: [ - '3836221195', '3-8362-2119-5', '3 8362 2119 5', - '1617290858', '1-61729-085-8', '1 61729 085-8', - '0007269706', '0-00-726970-6', '0 00 726970 6', - '3423214120', '3-423-21412-0', '3 423 21412 0', - '340101319X', '3-401-01319-X', '3 401 01319 X', - ], - invalid: [ - '3423214121', '3-423-21412-1', '3 423 21412 1', - '978-3836221191', '9783836221191', - '123456789a', 'foo', '', - ], - }); - test({ - validator: 'isISBN', - args: [13], - valid: [ - '9783836221191', '978-3-8362-2119-1', '978 3 8362 2119 1', - '9783401013190', '978-3401013190', '978 3401013190', - '9784873113685', '978-4-87311-368-5', '978 4 87311 368 5', - ], - invalid: [ - '9783836221190', '978-3-8362-2119-0', '978 3 8362 2119 0', - '3836221195', '3-8362-2119-5', '3 8362 2119 5', - '01234567890ab', 'foo', '', - ], - }); - test({ - validator: 'isISBN', - valid: [ - '340101319X', - '9784873113685', - ], - invalid: [ - '3423214121', - '9783836221190', - ], - }); - test({ - validator: 'isISBN', - args: ['foo'], - invalid: [ - '340101319X', - '9784873113685', - ], - }); - }); - - it('should validate ISSNs', function () { - test({ - validator: 'isISSN', - valid: [ - '0378-5955', - '0000-0000', - '2434-561X', - '2434-561x', - '01896016', - '20905076', - ], - invalid: [ - '0378-5954', - '0000-0001', - '0378-123', - '037-1234', - '0', - '2434-561c', - '1684-5370', - '19960791', - '', - ], - }); - test({ - validator: 'isISSN', - args: [{ case_sensitive: true }], - valid: [ - '2434-561X', - '2434561X', - '0378-5955', - '03785955', - ], - invalid: [ - '2434-561x', - '2434561x', - ], - }); - test({ - validator: 'isISSN', - args: [{ require_hyphen: true }], - valid: [ - '2434-561X', - '2434-561x', - '0378-5955', - ], - invalid: [ - '2434561X', - '2434561x', - '03785955', - ], - }); - test({ - validator: 'isISSN', - args: [{ case_sensitive: true, require_hyphen: true }], - valid: [ - '2434-561X', - '0378-5955', - ], - invalid: [ - '2434-561x', - '2434561X', - '2434561x', - '03785955', - ], - }); - }); - - it('should validate JSON', function () { - test({ - validator: 'isJSON', - valid: [ - '{ "key": "value" }', - '{}', - ], - invalid: [ - '{ key: "value" }', - '{ \'key\': \'value\' }', - 'null', - '1234', - 'false', - '"nope"', - ], - }); - }); - - it('should validate multibyte strings', function () { - test({ - validator: 'isMultibyte', - valid: [ - 'ひらがな・カタカナ、.漢字', - 'あいうえお foobar', - 'test@example.com', - '1234abcDExyz', - 'カタカナ', - '中文', - ], - invalid: [ - 'abc', - 'abc123', - '<>@" *.', - ], - }); - }); - - it('should validate ascii strings', function () { - test({ - validator: 'isAscii', - valid: [ - 'foobar', - '0987654321', - 'test@example.com', - '1234abcDEF', - ], - invalid: [ - 'foobar', - 'xyz098', - '123456', - 'カタカナ', - ], - }); - }); - - it('should validate full-width strings', function () { - test({ - validator: 'isFullWidth', - valid: [ - 'ひらがな・カタカナ、.漢字', - '3ー0 a@com', - 'Fカタカナ゙ᆲ', - 'Good=Parts', - ], - invalid: [ - 'abc', - 'abc123', - '!"#$%&()<>/+=-_? ~^|.,@`{}[]', - ], - }); - }); - - it('should validate half-width strings', function () { - test({ - validator: 'isHalfWidth', - valid: [ - '!"#$%&()<>/+=-_? ~^|.,@`{}[]', - 'l-btn_02--active', - 'abc123い', - 'カタカナ゙ᆲ←', - ], - invalid: [ - 'あいうえお', - '0011', - ], - }); - }); - - it('should validate variable-width strings', function () { - test({ - validator: 'isVariableWidth', - valid: [ - 'ひらがなカタカナ漢字ABCDE', - '3ー0123', - 'Fカタカナ゙ᆲ', - 'Good=Parts', - ], - invalid: [ - 'abc', - 'abc123', - '!"#$%&()<>/+=-_? ~^|.,@`{}[]', - 'ひらがな・カタカナ、.漢字', - '123456', - 'カタカナ゙ᆲ', - ], - }); - }); - - it('should validate surrogate pair strings', function () { - test({ - validator: 'isSurrogatePair', - valid: [ - '𠮷野𠮷', - '𩸽', - 'ABC千𥧄1-2-3', - ], - invalid: [ - '吉野竈', - '鮪', - 'ABC1-2-3', - ], - }); - }); - - it('should validate base64 strings', function () { - test({ - validator: 'isBase64', - valid: [ - 'Zg==', - 'Zm8=', - 'Zm9v', - 'Zm9vYg==', - 'Zm9vYmE=', - 'Zm9vYmFy', - 'TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdC4=', - 'Vml2YW11cyBmZXJtZW50dW0gc2VtcGVyIHBvcnRhLg==', - 'U3VzcGVuZGlzc2UgbGVjdHVzIGxlbw==', - 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuMPNS1Ufof9EW/M98FNw' + - 'UAKrwflsqVxaxQjBQnHQmiI7Vac40t8x7pIb8gLGV6wL7sBTJiPovJ0V7y7oc0Ye' + - 'rhKh0Rm4skP2z/jHwwZICgGzBvA0rH8xlhUiTvcwDCJ0kc+fh35hNt8srZQM4619' + - 'FTgB66Xmp4EtVyhpQV+t02g6NzK72oZI0vnAvqhpkxLeLiMCyrI416wHm5Tkukhx' + - 'QmcL2a6hNOyu0ixX/x2kSFXApEnVrJ+/IxGyfyw8kf4N2IZpW5nEP847lpfj0SZZ' + - 'Fwrd1mnfnDbYohX2zRptLy2ZUn06Qo9pkG5ntvFEPo9bfZeULtjYzIl6K8gJ2uGZ' + - 'HQIDAQAB', - ], - invalid: [ - '12345', - '', - 'Vml2YW11cyBmZXJtZtesting123', - 'Zg=', - 'Z===', - 'Zm=8', - '=m9vYg==', - 'Zm9vYmFy====', - ], - }); - for (var i = 0, str = '', encoded; i < 1000; i++) { - str += String.fromCharCode(Math.random() * 26 | 97); - encoded = new Buffer(str).toString('base64'); - if (!validator.isBase64(encoded)) { - var msg = format('validator.isBase64() failed with "%s"', encoded); - throw new Error(msg); - } - } - }); - - it('should validate hex-encoded MongoDB ObjectId', function () { - test({ - validator: 'isMongoId', - valid: [ - '507f1f77bcf86cd799439011', - ], - invalid: [ - '507f1f77bcf86cd7994390', - '507f1f77bcf86cd79943901z', - '', - '507f1f77bcf86cd799439011 ', - ], - }); - }); - - it('should define the module using an AMD-compatible loader', function () { - var window = { - validator: null, - define: function (module) { - window.validator = module(); - }, - }; - window.define.amd = true; - - var sandbox = vm.createContext(window); - vm.runInContext(validator_js, sandbox); - assert.equal(window.validator.trim(' foobar '), 'foobar'); - }); - - it('should bind validator to the window if no module loaders are available', function () { - var window = {}; - var sandbox = vm.createContext(window); - vm.runInContext(validator_js, sandbox); - assert.equal(window.validator.trim(' foobar '), 'foobar'); - }); - - it('should validate mobile phone number', function () { - var fixtures = [ - { - locale: 'ar-AE', - valid: [ - '+971502674453', - '+971521247658', - '+971541255684', - '+971555454458', - '+971561498855', - '+971585215778', - '971585215778', - '0585215778', - '585215778', - ], - invalid: [ - '12345', - '+971511498855', - '+9715614988556', - '+9745614988556', - '', - '+9639626626262', - '+963332210972', - '0114152198', - '962796477263', - ], - }, - { - locale: 'ar-EG', - valid: [ - '+201004513789', - '+201111453489', - '+201221204610', - '+201144621154', - '+201200124304', - '+201011201564', - '+201124679001', - '+201064790156', - '+201274652177', - '+201280134679', - '+201090124576', - '201090124576', - '01090124576', - '1090124576', - ], - invalid: [ - '+221004513789', - '+201404513789', - '12345', - '', - '+9639626626262', - '+963332210972', - '0114152198', - '962796477263', - ], - }, - { - locale: 'ar-JO', - valid: [ - '0796477263', - '0777866254', - '0786725261', - '+962796477263', - '+962777866254', - '+962786725261', - '962796477263', - '962777866254', - '962786725261', - ], - invalid: [ - '00962786725261', - '00962796477263', - '12345', - '', - '+9639626626262', - '+963332210972', - '0114152198', - ], - }, - { - locale: 'ar-SY', - valid: [ - '0944549710', - '+963944549710', - '956654379', - '0944549710', - '0962655597', - ], - invalid: [ - '12345', - '', - '+9639626626262', - '+963332210972', - '0114152198', - ], - }, - { - locale: 'ar-SA', - valid: [ - '0556578654', - '+966556578654', - '966556578654', - '596578654', - '572655597', - ], - invalid: [ - '12345', - '', - '+9665626626262', - '+96633221097', - '0114152198', - ], - }, - { - locale: 'cs-CZ', - valid: [ - '+420 123 456 789', - '+420 123456789', - '+420123456789', - '123 456 789', - '123456789', - ], - invalid: [ - '', - '+42012345678', - '+421 123 456 789', - '+420 023456789', - '+4201234567892', - ], - }, - { - locale: 'sk-SK', - valid: [ - '+421 123 456 789', - '+421 123456789', - '+421123456789', - '123 456 789', - '123456789', - ], - invalid: [ - '', - '+42112345678', - '+422 123 456 789', - '+421 023456789', - '+4211234567892', - ], - }, - { - locale: 'de-DE', - valid: [ - '+49 (0) 123 456 789', - '+49 (0) 123 456789', - '0123/4567890', - '+49 01234567890', - '01234567890', - ], - invalid: [ - '', - 'Vml2YW11cyBmZXJtZtesting123', - ], - }, - { - locale: 'pt-BR', - valid: [ - '55-17-3332-2155', - '55-15-25661234', - '551223456789', - '01523456987', - '022995678947', - '+55-12-996551215', - ], - invalid: [ - '+017-123456789', - '5501599623874', - '+55012962308', - '+55-015-1234-3214', - ], - }, - { - locale: 'zh-CN', - valid: [ - '15323456787', - '13523333233', - '13898728332', - '+086-13238234822', - '08613487234567', - '8617823492338', - '86-17823492338', - ], - invalid: [ - '12345', - '', - 'Vml2YW11cyBmZXJtZtesting123', - '010-38238383', - ], - }, - { - locale: 'zh-TW', - valid: [ - '0987123456', - '+886987123456', - '886987123456', - '+886-987123456', - '886-987123456', - ], - invalid: [ - '12345', - '', - 'Vml2YW11cyBmZXJtZtesting123', - '0-987123456', - ], - }, - { - locale: 'en-ZA', - valid: [ - '0821231234', - '+27821231234', - '27821231234', - ], - invalid: [ - '082123', - '08212312345', - '21821231234', - '+21821231234', - '+0821231234', - ], - }, - { - locale: 'en-AU', - valid: [ - '61404111222', - '+61411222333', - '0417123456', - ], - invalid: [ - '082123', - '08212312345', - '21821231234', - '+21821231234', - '+0821231234', - '04123456789', - ], - }, - { - locale: 'en-HK', - valid: [ - '91234567', - '9123-4567', - '61234567', - '51234567', - '+85291234567', - '+852-91234567', - '+852-9123-4567', - '852-91234567', - ], - invalid: [ - '999', - '+852-912345678', - '123456789', - '+852-1234-56789', - ], - }, - { - locale: 'en-KE', - valid: [ - '+254728590432', - '+254733875610', - '254728590234', - '0733346543', - '0700459022', - ], - invalid: [ - '999', - '+25489032', - '123456789', - '+254800723845', - ], - }, - { - locale: 'en-UG', - valid: [ - '+256728590432', - '+256733875610', - '256728590234', - '0773346543', - '0700459022', - ], - invalid: [ - '999', - '+254728590432', - '+25489032', - '123456789', - '+254800723845', - ], - }, - { - locale: 'en-RW', - valid: [ - '+250728590432', - '+250733875610', - '250738590234', - '0753346543', - '0780459022', - ], - invalid: [ - '999', - '+254728590432', - '+25089032', - '123456789', - '+250800723845', - ], - }, - { - locale: 'en-TZ', - valid: [ - '+255728590432', - '+255733875610', - '255628590234', - '0673346543', - '0600459022', - ], - invalid: [ - '999', - '+254728590432', - '+25589032', - '123456789', - '+255800723845', - ], - }, - { - locale: 'fr-FR', - valid: [ - '0612457898', - '+33612457898', - '33612457898', - '0712457898', - '+33712457898', - '33712457898', - ], - invalid: [ - '061245789', - '06124578980', - '0112457898', - '0212457898', - '0312457898', - '0412457898', - '0512457898', - '0812457898', - '0912457898', - '+34612457898', - '+336124578980', - '+3361245789', - ], - }, - { - locale: 'el-GR', - valid: [ - '+306944848966', - '6944848966', - '306944848966', - ], - invalid: [ - '2102323234', - '+302646041461', - '120000000', - '20000000000', - '68129485729', - '6589394827', - '298RI89572', - ], - }, - { - locale: 'en-GB', - valid: [ - '447789345856', - '+447861235675', - '07888814488', - ], - invalid: [ - '67699567', - '0773894868', - '077389f8688', - '+07888814488', - '0152456999', - '442073456754', - '+443003434751', - '05073456754', - '08001123123', - ], - }, - { - locale: 'en-SG', - valid: [ - '87654321', - '98765432', - '+6587654321', - '+6598765432', - ], - invalid: [ - '987654321', - '876543219', - '8765432', - '9876543', - '12345678', - '+98765432', - '+9876543212', - '+15673628910', - '19876543210', - '8005552222', - ], - }, - { - locale: 'en-US', - valid: [ - '19876543210', - '8005552222', - '+15673628910', - ], - invalid: [ - '564785', - '0123456789', - '1437439210', - '8009112340', - '+10345672645', - '11435213543', - '2436119753', - '16532116190', - ], - }, - { - locale: 'en-CA', - valid: [ - '19876543210', - '8005552222', - '+15673628910', - ], - invalid: [ - '564785', - '0123456789', - '1437439210', - '8009112340', - '+10345672645', - '11435213543', - '2436119753', - '16532116190', - ], - }, - { - locale: 'en-ZM', - valid: [ - '0956684590', - '0966684590', - '0976684590', - '+260956684590', - '+260966684590', - '+260976684590', - '260976684590', - ], - invalid: [ - '12345', - '', - 'Vml2YW11cyBmZXJtZtesting123', - '010-38238383', - '966684590', - ], - }, - { - locale: 'ru-RU', - valid: [ - '+79676338855', - '79676338855', - '89676338855', - '9676338855', - ], - invalid: [ - '12345', - '', - 'Vml2YW11cyBmZXJtZtesting123', - '010-38238383', - '+9676338855', - '19676338855', - '6676338855', - '+99676338855', - ], - }, - { - locale: 'sr-RS', - valid: [ - '0640133338', - '063333133', - '0668888878', - '+381645678912', - '+381611314000', - '0655885010', - ], - invalid: [ - '12345', - '', - 'Vml2YW11cyBmZXJtZtesting123', - '010-38238383', - '+9676338855', - '19676338855', - '6676338855', - '+99676338855', - ], - }, - { - locale: 'en-NZ', - valid: [ - '+6427987035', - '642240512347', - '0293981646', - '029968425', - ], - invalid: [ - '12345', - '', - 'Vml2YW11cyBmZXJtZtesting123', - '+642956696123566', - '+02119620856', - '+9676338855', - '19676338855', - '6676338855', - '+99676338855', - ], - }, - { - locale: ['nb-NO', 'nn-NO'], // for multiple locales - valid: [ - '+4796338855', - '+4746338855', - '4796338855', - '4746338855', - '46338855', - '96338855', - ], - invalid: [ - '12345', - '', - 'Vml2YW11cyBmZXJtZtesting123', - '+4676338855', - '19676338855', - '+4726338855', - '4736338855', - '66338855', - ], - }, - { - locale: 'vi-VN', - valid: [ - '01636012403', - '+841636012403', - '1636012403', - '841636012403', - '+84999999999', - '84999999999', - '0999999999', - '999999999', - ], - invalid: [ - '12345', - '', - 'Vml2YW11cyBmZXJtZtesting123', - '010-38238383', - '260976684590', - ], - }, - { - locale: 'es-ES', - valid: [ - '+34654789321', - '654789321', - '+34714789321', - '714789321', - '+34744789321', - '744789321', - ], - invalid: [ - '12345', - '', - 'Vml2YW11cyBmZXJtZtesting123', - '+3465478932', - '65478932', - '+346547893210', - '6547893210', - '+34704789321', - '704789321', - '+34754789321', - '754789321', - ], - }, - { - locale: 'et-EE', - valid: [ - '+372 512 34 567', - '372 512 34 567', - '+37251234567', - '51234567', - '81234567', - '+372842345678', - ], - invalid: [ - '12345', - '', - 'NotANumber', - '+333 51234567', - '61234567', - '+51234567', - '+372 539 57 4', - '+372 900 1234', - '12345678', - ], - }, - { - locale: 'pl-PL', - valid: [ - '+48512689767', - '+48 56 376 87 47', - '56 566 78 46', - '657562855', - '+48657562855', - '+48 887472765', - '+48 56 6572724', - '+48 67 621 5461', - '48 67 621 5461', - ], - invalid: [ - '+48 67 621 5461', - '+55657562855', - '3454535', - 'teststring', - '', - '1800-88-8687', - '+6019-5830837', - '357562855', - ], - }, - { - locale: 'fa-IR', - valid: [ - '+989123456789', - '989223456789', - '09323456789', - '09021456789', - '+98-990-345-6789', - '+98 938 345 6789', - '0938 345 6789', - ], - invalid: [ - '', - '+989623456789', - '+981123456789', - '01234567890', - '09423456789', - '09823456789', - '9123456789', - '091234567890', - '0912345678', - '+98 912 3456 6789', - '0912 345 678', - ], - }, - { - locale: 'fi-FI', - valid: [ - '+358505557171', - '0455571', - '0505557171', - '358505557171', - '04412345', - '0457 123 45 67', - '+358457 123 45 67', - '+358 50 555 7171', - ], - invalid: [ - '12345', - '', - '045557', - '045555717112312332423423421', - 'Vml2YW11cyBmZXJtZtesting123', - '010-38238383', - '+3-585-0555-7171', - '+9676338855', - '19676338855', - '6676338855', - '+99676338855', - '044123', - '019123456789012345678901', - ], - }, - { - locale: 'ms-MY', - valid: [ - '+60128228789', - '+60195830837', - '+6019-5830837', - '+6019-5830837', - '0128737867', - '01468987837', - '016-2838768', - '016 2838768', - ], - invalid: [ - '12345', - '601238788657', - '088387675', - '16-2838768', - '032551433', - '6088-387888', - '088-261987', - '1800-88-8687', - '088-320000', - ], - }, - { - locale: 'ko-KR', - valid: [ - '+82-010-1234-5678', - '+82-10-1234-5678', - '82-010-1234-5678', - '82-10-1234-5678', - '+82 10 1234 5678', - '010-123-5678', - '10-1234-5678', - '+82 10 1234 5678', - '011 1234 5678', - '+820112345678', - '01012345678', - '+82 016 1234 5678', - '82 19 1234 5678', - '+82 010 12345678', - ], - invalid: [ - 'abcdefghi', - '+82 10 1234 567', - '+82 10o 1234 1234', - '+82 101 1234 5678', - '+82 10 12 5678', - '+011 7766 1234', - '011_7766_1234', - '+820 11 7766 1234', - ], - }, - { - locale: 'ja-JP', - valid: [ - '09012345688', - '090 123 45678', - '+8190-123-45678', - ], - invalid: [ - '12345', - '', - '045555717112312332423423421', - 'Vml2YW11cyBmZXJtZtesting123', - '+3-585-0555-7171', - '0 1234 5689', - '16 1234 5689', - '03_1234_5689', - '0312345678', - '0721234567', - '08002345678', - '06 1234 5678', - '072 123 4567', - '0729 12 3456', - '07296 1 2345', - '072961 2345', - '03-1234-5678', - '+81312345678', - '+816-1234-5678', - ], - }, - { - locale: 'it-IT', - valid: [ - '370 3175423', - '333202925', - '+39 310 7688449', - '+39 3339847632', - ], - invalid: [ - '011 7387545', - '12345', - '+45 345 6782395', - ], - }, - { - locale: 'fr-BE', - valid: [ - '0470123456', - '+32470123456', - '32470123456', - '021234567', - '+3221234567', - '3221234567', - ], - invalid: [ - '12345', - '+3212345', - '3212345', - '04701234567', - '+3204701234567', - '3204701234567', - '0212345678', - '+320212345678', - '320212345678', - ], - }, - { - locale: 'nl-BE', - valid: [ - '0470123456', - '+32470123456', - '32470123456', - '021234567', - '+3221234567', - '3221234567', - ], - invalid: [ - '12345', - '+3212345', - '3212345', - '04701234567', - '+3204701234567', - '3204701234567', - '0212345678', - '+320212345678', - '320212345678', - ], - }, - { - locale: 'ro-RO', - valid: [ - '+40740123456', - '+40 740123456', - '+40740 123 456', - '+40740.123.456', - '+40740-123-456', - '40740123456', - '40 740123456', - '40740 123 456', - '40740.123.456', - '40740-123-456', - '0740123456', - '0740/123456', - '0740 123 456', - '0740.123.456', - '0740-123-456', - ], - invalid: [ - '', - 'Vml2YW11cyBmZXJtZtesting123', - '123456', - '740123456', - '+40640123456', - '+40210123456', - ], - }, - { - locale: 'id-ID', - valid: [ - '0217123456', - '0811 778 998', - '089931236181900', - '622178878890', - '62811 778 998', - '62811778998', - '6289931236181900', - '6221 740123456', - '62899 740123456', - '62899 7401 2346', - '0341 8123456', - '0778 89800910', - '0741 123456', - '+6221740123456', - '+62811 778 998', - '+62811778998', - ], - invalid: [ - '+65740 123 456', - '', - 'ASDFGJKLmZXJtZtesting123', - '123456', - '740123456', - '+65640123456', - '+64210123456', - ], - }, - { - locale: 'lt-LT', - valid: [ - '+37051234567', - '851234567', - ], - invalid: [ - '+65740 123 456', - '', - 'ASDFGJKLmZXJtZtesting123', - '123456', - '740123456', - '+65640123456', - '+64210123456', - ], - }, - { - locale: 'uk-UA', - valid: [ - '+380982345679', - '380982345679', - '80982345679', - '0982345679', - ], - invalid: [ - '+30982345679', - '982345679', - '+380 98 234 5679', - '+380-98-234-5679', - '', - 'ASDFGJKLmZXJtZtesting123', - '123456', - '740123456', - ], - }, - { - locale: 'da-DK', - valid: [ - '12345678', - '12 34 56 78', - '45 12345678', - '4512345678', - '45 12 34 56 78', - '+45 12 34 56 78', - ], - invalid: [ - '', - '+45010203', - 'ASDFGJKLmZXJtZtesting123', - '123456', - '12 34 56', - '123 123 12', - ], - }, - { - locale: 'fo-FO', - valid: [ - '123456', - '12 34 56', - '298 123456', - '298123456', - '298 12 34 56', - '+298 12 34 56', - ], - invalid: [ - '', - '+4501020304', - 'ASDFGJKLmZXJtZtesting123', - '12345678', - '12 34 56 78', - ], - }, - { - locale: 'kl-GL', - valid: [ - '123456', - '12 34 56', - '299 123456', - '299123456', - '299 12 34 56', - '+299 12 34 56', - ], - invalid: [ - '', - '+4501020304', - 'ASDFGJKLmZXJtZtesting123', - '12345678', - '12 34 56 78', - ], - }, - ]; - - var allValid = []; - - fixtures.forEach(function (fixture) { - // to be used later on for validating 'any' locale - if (fixture.valid) allValid = allValid.concat(fixture.valid); - - if (Array.isArray(fixture.locale)) { - // for fixtures that are shared across multiple locales - // e.g. 'nb-NO' and 'nn-NO' - fixture.locale.forEach(function (locale) { - test({ - validator: 'isMobilePhone', - valid: fixture.valid, - invalid: fixture.invalid, - args: [locale], - }); - }); - } else { - test({ - validator: 'isMobilePhone', - valid: fixture.valid, - invalid: fixture.invalid, - args: [fixture.locale], - }); - } - }); - - test({ - validator: 'isMobilePhone', - valid: allValid, - invalid: [ - '', - 'asdf', - '1', - 'ASDFGJKLmZXJtZtesting123', - 'Vml2YW11cyBmZXJtZtesting123', - ], - args: ['any'], - }); - }); - - it('should validate currency', function () { - test({ - validator: 'isCurrency', - args: [ - { }, - '-$##,###.## (en-US, en-CA, en-AU, en-NZ, en-HK)', - ], - valid: [ - '-$10,123.45', - '$10,123.45', - '$10123.45', - '10,123.45', - '10123.45', - '10,123', - '1,123,456', - '1123456', - '1.39', - '.03', - '0.10', - '$0.10', - '-$0.01', - '-$.99', - '$100,234,567.89', - '$10,123', - '10,123', - '-10123', - ], - invalid: [ - '1.234', - '$1.1', - '$ 32.50', - '500$', - '.0001', - '$.001', - '$0.001', - '12,34.56', - '123456,123,123456', - '123,4', - ',123', - '$-,123', - '$', - '.', - ',', - '00', - '$-', - '$-,.', - '-', - '-$', - '', - '- $', - ], - }); - - test({ - validator: 'isCurrency', - args: [ - { - allow_decimal: false, - }, - '-$##,###.## (en-US, en-CA, en-AU, en-NZ, en-HK)', - ], - valid: [ - '-$10,123', - '$10,123', - '$10123', - '10,123', - '10123', - '10,123', - '1,123,456', - '1123456', - '1', - '0', - '$0', - '-$0', - '$100,234,567', - '$10,123', - '10,123', - '-10123', - ], - invalid: [ - '-$10,123.45', - '$10,123.45', - '$10123.45', - '10,123.45', - '10123.45', - '1.39', - '.03', - '0.10', - '$0.10', - '-$0.01', - '-$.99', - '$100,234,567.89', - '1.234', - '$1.1', - '$ 32.50', - '.0001', - '$.001', - '$0.001', - '12,34.56', - '123,4', - ',123', - '$-,123', - '$', - '.', - ',', - '00', - '$-', - '$-,.', - '-', - '-$', - '', - '- $', - ], - }); - - test({ - validator: 'isCurrency', - args: [ - { - require_decimal: true, - }, - '-$##,###.## (en-US, en-CA, en-AU, en-NZ, en-HK)', - ], - valid: [ - '-$10,123.45', - '$10,123.45', - '$10123.45', - '10,123.45', - '10123.45', - '10,123.00', - '1.39', - '.03', - '0.10', - '$0.10', - '-$0.01', - '-$.99', - '$100,234,567.89', - ], - invalid: [ - '$10,123', - '10,123', - '-10123', - '1,123,456', - '1123456', - '1.234', - '$1.1', - '$ 32.50', - '500$', - '.0001', - '$.001', - '$0.001', - '12,34.56', - '123456,123,123456', - '123,4', - ',123', - '$-,123', - '$', - '.', - ',', - '00', - '$-', - '$-,.', - '-', - '-$', - '', - '- $', - ], - }); - - test({ - validator: 'isCurrency', - args: [ - { - digits_after_decimal: [1, 3], - }, - '-$##,###.## (en-US, en-CA, en-AU, en-NZ, en-HK)', - ], - valid: [ - '-$10,123.4', - '$10,123.454', - '$10123.452', - '10,123.453', - '10123.450', - '10,123', - '1,123,456', - '1123456', - '1.3', - '.030', - '0.100', - '$0.1', - '-$0.0', - '-$.9', - '$100,234,567.893', - '$10,123', - '10,123.123', - '-10123.1', - ], - invalid: [ - '1.23', - '$1.13322', - '$ 32.50', - '500$', - '.0001', - '$.01', - '$0.01', - '12,34.56', - '123456,123,123456', - '123,4', - ',123', - '$-,123', - '$', - '.', - ',', - '00', - '$-', - '$-,.', - '-', - '-$', - '', - '- $', - ], - }); - - test({ - validator: 'isCurrency', - args: [ - { - require_symbol: true, - }, - '-$##,###.## with $ required (en-US, en-CA, en-AU, en-NZ, en-HK)', - ], - valid: [ - '-$10,123.45', - '$10,123.45', - '$10123.45', - '$10,123.45', - '$10,123', - '$1,123,456', - '$1123456', - '$1.39', - '$.03', - '$0.10', - '$0.10', - '-$0.01', - '-$.99', - '$100,234,567.89', - '$10,123', - '-$10123', - ], - invalid: [ - '1.234', - '$1.234', - '1.1', - '$1.1', - '$ 32.50', - ' 32.50', - '500', - '10,123,456', - '.0001', - '$.001', - '$0.001', - '1,234.56', - '123456,123,123456', - '$123456,123,123456', - '123.4', - '$123.4', - ',123', - '$,123', - '$-,123', - '$', - '.', - '$.', - ',', - '$,', - '00', - '$00', - '$-', - '$-,.', - '-', - '-$', - '', - '$ ', - '- $', - ], - }); - - test({ - validator: 'isCurrency', - args: [ - { - symbol: '¥', - negative_sign_before_digits: true, - }, - '¥-##,###.## (zh-CN)', - ], - valid: [ - '123,456.78', - '-123,456.78', - '¥6,954,231', - '¥-6,954,231', - '¥10.03', - '¥-10.03', - '10.03', - '1.39', - '.03', - '0.10', - '¥-10567.01', - '¥0.01', - '¥1,234,567.89', - '¥10,123', - '¥-10,123', - '¥-10,123.45', - '10,123', - '10123', - '¥-100', - ], - invalid: [ - '1.234', - '¥1.1', - '5,00', - '.0001', - '¥.001', - '¥0.001', - '12,34.56', - '123456,123,123456', - '123 456', - ',123', - '¥-,123', - '', - ' ', - '¥', - '¥-', - '¥-,.', - '-', - '- ¥', - '-¥', - ], - }); - - test({ - validator: 'isCurrency', - args: [ - { - symbol: '¥', - allow_negatives: false, - }, - '¥##,###.## with no negatives (zh-CN)', - ], - valid: [ - '123,456.78', - '¥6,954,231', - '¥10.03', - '10.03', - '1.39', - '.03', - '0.10', - '¥0.01', - '¥1,234,567.89', - '¥10,123', - '10,123', - '10123', - '¥100', - ], - invalid: [ - '1.234', - '-123,456.78', - '¥-6,954,231', - '¥-10.03', - '¥-10567.01', - '¥1.1', - '¥-10,123', - '¥-10,123.45', - '5,00', - '¥-100', - '.0001', - '¥.001', - '¥-.001', - '¥0.001', - '12,34.56', - '123456,123,123456', - '123 456', - ',123', - '¥-,123', - '', - ' ', - '¥', - '¥-', - '¥-,.', - '-', - '- ¥', - '-¥', - ], - }); - - test({ - validator: 'isCurrency', - args: [ - { - symbol: 'R', - negative_sign_before_digits: true, - thousands_separator: ' ', - decimal_separator: ',', - allow_negative_sign_placeholder: true, - }, - 'R ## ###,## and R-10 123,25 (el-ZA)', - ], - valid: [ - '123 456,78', - '-10 123', - 'R-10 123', - 'R 6 954 231', - 'R10,03', - '10,03', - '1,39', - ',03', - '0,10', - 'R10567,01', - 'R0,01', - 'R1 234 567,89', - 'R10 123', - 'R 10 123', - 'R 10123', - 'R-10123', - '10 123', - '10123', - ], - invalid: [ - '1,234', - 'R -10123', - 'R- 10123', - 'R,1', - ',0001', - 'R,001', - 'R0,001', - '12 34,56', - '123456 123 123456', - ' 123', - '- 123', - '123 ', - '', - ' ', - 'R', - 'R- .1', - 'R-', - '-', - '-R 10123', - 'R00', - 'R -', - '-R', - ], - }); - - test({ - validator: 'isCurrency', - args: [ - { - symbol: '€', - thousands_separator: '.', - decimal_separator: ',', - allow_space_after_symbol: true, - }, - '-€ ##.###,## (it-IT)', - ], - valid: [ - '123.456,78', - '-123.456,78', - '€6.954.231', - '-€6.954.231', - '€ 896.954.231', - '-€ 896.954.231', - '16.954.231', - '-16.954.231', - '€10,03', - '-€10,03', - '10,03', - '-10,03', - '-1,39', - ',03', - '0,10', - '-€10567,01', - '-€ 10567,01', - '€ 0,01', - '€1.234.567,89', - '€10.123', - '10.123', - '-€10.123', - '€ 10.123', - '€10.123', - '€ 10123', - '10.123', - '-10123', - ], - invalid: [ - '1,234', - '€ 1,1', - '50#,50', - '123,@€ ', - '€€500', - ',0001', - '€ ,001', - '€0,001', - '12.34,56', - '123456.123.123456', - '€123€', - '', - ' ', - '€', - ' €', - '€ ', - '€€', - ' 123', - '- 123', - '.123', - '-€.123', - '123 ', - '€-', - '- €', - '€ - ', - '-', - '- ', - '-€', - ], - }); - - test({ - validator: 'isCurrency', - args: [ - { - symbol: '€', - thousands_separator: '.', - symbol_after_digits: true, - decimal_separator: ',', - allow_space_after_digits: true, - }, - '-##.###,## € (el-GR)', - ], - valid: [ - '123.456,78', - '-123.456,78', - '6.954.231 €', - '-6.954.231 €', - '896.954.231', - '-896.954.231', - '16.954.231', - '-16.954.231', - '10,03€', - '-10,03€', - '10,03', - '-10,03', - '1,39', - ',03', - '-,03', - '-,03 €', - '-,03€', - '0,10', - '10567,01€', - '0,01 €', - '1.234.567,89€', - '10.123€', - '10.123', - '10.123€', - '10.123 €', - '10123 €', - '10.123', - '10123', - ], - invalid: [ - '1,234', - '1,1 €', - ',0001', - ',001 €', - '0,001€', - '12.34,56', - '123456.123.123456', - '€123€', - '', - ' ', - '€', - ' €', - '€ ', - ' 123', - '- 123', - '.123', - '-.123€', - '-.123 €', - '123 ', - '-€', - '- €', - '-', - '- ', - ], - }); - - test({ - validator: 'isCurrency', - args: [ - { - symbol: 'kr.', - negative_sign_before_digits: true, - thousands_separator: '.', - decimal_separator: ',', - allow_space_after_symbol: true, - }, - 'kr. -##.###,## (da-DK)', - ], - valid: [ - '123.456,78', - '-10.123', - 'kr. -10.123', - 'kr.-10.123', - 'kr. 6.954.231', - 'kr.10,03', - 'kr. -10,03', - '10,03', - '1,39', - ',03', - '0,10', - 'kr. 10567,01', - 'kr. 0,01', - 'kr. 1.234.567,89', - 'kr. -1.234.567,89', - '10.123', - 'kr. 10.123', - 'kr.10.123', - '10123', - '10.123', - 'kr.-10123', - ], - invalid: [ - '1,234', - 'kr. -10123', - 'kr.,1', - ',0001', - 'kr. ,001', - 'kr.0,001', - '12.34,56', - '123456.123.123456', - '.123', - 'kr.-.123', - 'kr. -.123', - '- 123', - '123 ', - '', - ' ', - 'kr.', - ' kr.', - 'kr. ', - 'kr.-', - 'kr. -', - 'kr. - ', - ' - ', - '-', - '- kr.', - '-kr.', - ], - }); - - test({ - validator: 'isCurrency', - args: [ - { - symbol: 'kr.', - allow_negatives: false, - negative_sign_before_digits: true, - thousands_separator: '.', - decimal_separator: ',', - allow_space_after_symbol: true, - }, - 'kr. ##.###,## with no negatives (da-DK)', - ], - valid: [ - '123.456,78', - '10.123', - 'kr. 10.123', - 'kr.10.123', - 'kr. 6.954.231', - 'kr.10,03', - 'kr. 10,03', - '10,03', - '1,39', - ',03', - '0,10', - 'kr. 10567,01', - 'kr. 0,01', - 'kr. 1.234.567,89', - 'kr.1.234.567,89', - '10.123', - 'kr. 10.123', - 'kr.10.123', - '10123', - '10.123', - 'kr.10123', - ], - invalid: [ - '1,234', - '-10.123', - 'kr. -10.123', - 'kr. -1.234.567,89', - 'kr.-10123', - 'kr. -10123', - 'kr.-10.123', - 'kr. -10,03', - 'kr.,1', - ',0001', - 'kr. ,001', - 'kr.0,001', - '12.34,56', - '123456.123.123456', - '.123', - 'kr.-.123', - 'kr. -.123', - '- 123', - '123 ', - '', - ' ', - 'kr.', - ' kr.', - 'kr. ', - 'kr.-', - 'kr. -', - 'kr. - ', - ' - ', - '-', - '- kr.', - '-kr.', - ], - }); - - test({ - validator: 'isCurrency', - args: [ - { - parens_for_negatives: true, - }, - '($##,###.##) (en-US, en-HK)', - ], - valid: [ - '1,234', - '(1,234)', - '($6,954,231)', - '$10.03', - '(10.03)', - '($10.03)', - '1.39', - '.03', - '(.03)', - '($.03)', - '0.10', - '$10567.01', - '($0.01)', - '$1,234,567.89', - '$10,123', - '(10,123)', - '10123', - ], - invalid: [ - '1.234', - '($1.1)', - '-$1.10', - '$ 32.50', - '500$', - '.0001', - '$.001', - '($0.001)', - '12,34.56', - '123456,123,123456', - '( 123)', - ',123', - '$-,123', - '', - ' ', - ' ', - ' ', - '$', - '$ ', - ' $', - ' 123', - '(123) ', - '.', - ',', - '00', - '$-', - '$ - ', - '$- ', - ' - ', - '-', - '- $', - '-$', - '()', - '( )', - '( -)', - '( - )', - '( - )', - '(-)', - '(-$)', - ], - }); - - test({ - validator: 'isCurrency', - args: [ - { allow_negatives: false }, - '$##,###.## with no negatives (en-US, en-CA, en-AU, en-HK)', - ], - valid: [ - '$10,123.45', - '$10123.45', - '10,123.45', - '10123.45', - '10,123', - '1,123,456', - '1123456', - '1.39', - '.03', - '0.10', - '$0.10', - '$100,234,567.89', - '$10,123', - '10,123', - ], - invalid: [ - '1.234', - '-1.234', - '-10123', - '-$0.01', - '-$.99', - '$1.1', - '-$1.1', - '$ 32.50', - '500$', - '.0001', - '$.001', - '$0.001', - '12,34.56', - '123456,123,123456', - '-123456,123,123456', - '123,4', - ',123', - '$-,123', - '$', - '.', - ',', - '00', - '$-', - '$-,.', - '-', - '-$', - '', - '- $', - '-$10,123.45', - ], - }); - - test({ - validator: 'isBoolean', - valid: [ - 'true', - 'false', - '0', - '1', - ], - invalid: [ - '1.0', - '0.0', - 'true ', - 'False', - 'True', - 'yes', - ], - }); - }); - - it('should validate ISO 8601 dates', function () { - // from http://www.pelagodesign.com/blog/2009/05/20/iso-8601-date-validation-that-doesnt-suck/ - test({ - validator: 'isISO8601', - valid: [ - '2009-12T12:34', - '2009', - '2009-05-19', - '2009-05-19', - '20090519', - '2009123', - '2009-05', - '2009-123', - '2009-222', - '2009-001', - '2009-W01-1', - '2009-W51-1', - '2009-W511', - '2009-W33', - '2009W511', - '2009-05-19', - '2009-05-19 00:00', - '2009-05-19 14', - '2009-05-19 14:31', - '2009-05-19 14:39:22', - '2009-05-19T14:39Z', - '2009-W21-2', - '2009-W21-2T01:22', - '2009-139', - '2009-05-19 14:39:22-06:00', - '2009-05-19 14:39:22+0600', - '2009-05-19 14:39:22-01', - '20090621T0545Z', - '2007-04-06T00:00', - '2007-04-05T24:00', - '2010-02-18T16:23:48.5', - '2010-02-18T16:23:48,444', - '2010-02-18T16:23:48,3-06:00', - '2010-02-18T16:23.4', - '2010-02-18T16:23,25', - '2010-02-18T16:23.33+0600', - '2010-02-18T16.23334444', - '2010-02-18T16,2283', - '2009-05-19 143922.500', - '2009-05-19 1439,55', - ], - invalid: [ - '200905', - '2009367', - '2009-', - '2007-04-05T24:50', - '2009-000', - '2009-M511', - '2009M511', - '2009-05-19T14a39r', - '2009-05-19T14:3924', - '2009-0519', - '2009-05-1914:39', - '2009-05-19 14:', - '2009-05-19r14:39', - '2009-05-19 14a39a22', - '200912-01', - '2009-05-19 14:39:22+06a00', - '2009-05-19 146922.500', - '2010-02-18T16.5:23.35:48', - '2010-02-18T16:23.35:48', - '2010-02-18T16:23.35:48.45', - '2009-05-19 14.5.44', - '2010-02-18T16:23.33.600', - '2010-02-18T16,25:23:48,444', - ], - }); - }); - - it('should validate ISO 3166-1 alpha 2 country codes', function () { - // from https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 - test({ - validator: 'isISO31661Alpha2', - valid: [ - 'FR', - 'fR', - 'GB', - 'PT', - 'CM', - 'JP', - 'PM', - 'ZW', - 'MM', - 'cc', - 'GG', - ], - invalid: [ - '', - 'FRA', - 'AA', - 'PI', - 'RP', - 'WV', - 'WL', - 'UK', - 'ZZ', - ], - }); - }); - - it('should validate whitelisted characters', function () { - test({ - validator: 'isWhitelisted', - args: ['abcdefghijklmnopqrstuvwxyz-'], - valid: ['foo', 'foobar', 'baz-foo'], - invalid: ['foo bar', 'fo.bar', 'türkçe'], - }); - }); - - it('should error on non-string input', function () { - var empty = [undefined, null, [], NaN]; - empty.forEach(function (item) { - assert.throws(validator.isEmpty.bind(null, item)); - }); - }); - - it('should validate dataURI', function () { - /* eslint-disable max-len */ - test({ - validator: 'isDataURI', - valid: [ - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD///+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4Ug9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC', - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIBAMAAAA2IaO4AAAAFVBMVEXk5OTn5+ft7e319fX29vb5+fn///++GUmVAAAALUlEQVQIHWNICnYLZnALTgpmMGYIFWYIZTA2ZFAzTTFlSDFVMwVyQhmAwsYMAKDaBy0axX/iAAAAAElFTkSuQmCC', - ' data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIBAMAAAA2IaO4AAAAFVBMVEXk5OTn5+ft7e319fX29vb5+fn///++GUmVAAAALUlEQVQIHWNICnYLZnALTgpmMGYIFWYIZTA2ZFAzTTFlSDFVMwVyQhmAwsYMAKDaBy0axX/iAAAAAElFTkSuQmCC ', - 'data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%3E%3Crect%20fill%3D%22%2300B1FF%22%20width%3D%22100%22%20height%3D%22100%22%2F%3E%3C%2Fsvg%3E', - 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAiIGhlaWdodD0iMTAwIj48cmVjdCBmaWxsPSIjMDBCMUZGIiB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIvPjwvc3ZnPg==', - ' data:,Hello%2C%20World!', - ' data:,Hello World!', - ' data:text/plain;base64,SGVsbG8sIFdvcmxkIQ%3D%3D', - ' data:text/html,%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E', - 'data:,A%20brief%20note', - 'data:text/html;charset=US-ASCII,%3Ch1%3EHello!%3C%2Fh1%3E', - ], - invalid: [ - 'dataxbase64', - 'data:HelloWorld', - 'data:text/html;charset=,%3Ch1%3EHello!%3C%2Fh1%3E', - 'data:text/html;charset,%3Ch1%3EHello!%3C%2Fh1%3E', 'data:base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD///+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4Ug9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC', - '', - 'http://wikipedia.org', - 'base64', - 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD///+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4Ug9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC', - ], - }); - /* eslint-enable max-len */ - }); - - it('should validate LatLong', function () { - test({ - validator: 'isLatLong', - valid: [ - '(-17.738223, 85.605469)', - '(-12.3456789, +12.3456789)', - '(-60.978437, -0.175781)', - '(77.719772, -37.529297)', - '(7.264394, 165.058594)', - '0.955766, -19.863281', - '(31.269161,164.355469)', - '+12.3456789, -12.3456789', - '-15.379543, -137.285156', - '(11.770570, -162.949219)', - '-55.034319, 113.027344', - '58.025555, 36.738281', - '55.720923,-28.652344', - '-90.00000,-180.00000', - '(-71, -146)', - '(-71.616864, -146.616864)', - '-0.55, +0.22', - '90, 180', - '+90, -180', - '-90,+180', - '90,180', - '0, 0', - ], - invalid: [ - '(020.000000, 010.000000000)', - '89.9999999989, 360.0000000', - '90.1000000, 180.000000', - '+90.000000, -180.00001', - '090.0000, 0180.0000', - '126, -158', - '(-126.400010, -158.400010)', - '-95, -96', - '-95.738043, -96.738043', - '137, -148', - '(-137.5942, -148.5942)', - '(-120, -203)', - '(-119, -196)', - '+119.821728, -196.821728', - '(-110, -223)', - '-110.369532, 223.369532', - '(-120.969949, +203.969949)', - '-116, -126', - '-116.894222, -126.894222', - '-112, -160', - '-112.96381, -160.96381', - '-90., -180.', - '+90.1, -180.1', - '+,-', - '(,)', - ',', - ' ', - ], - }); - }); - - it('should validate postal code', function () { - const fixtures = [ - { - locale: 'AU', - valid: [ - '4000', - '2620', - '3000', - '2017', - '0800', - ], - }, - { - locale: 'CA', - valid: [ - 'L4T 0A5', - 'G1A-0A2', - 'A1A 1A1', - 'X0A-0H0', - 'V5K 0A1', - ], - }, - { - locale: 'JP', - valid: [ - '135-0000', - '874-8577', - '669-1161', - '470-0156', - '672-8031', - ], - }, - { - locale: 'GR', - valid: [ - '022 93', - '29934', - '90293', - '299 42', - '94944', - ], - }, - { - locale: 'GB', - valid: [ - 'TW8 9GS', - 'BS98 1TL', - 'DE99 3GG', - 'DE55 4SW', - 'DH98 1BT', - 'DH99 1NS', - 'GIR0aa', - 'SA99', - 'W1N 4DJ', - 'AA9A 9AA', - 'AA99 9AA', - 'BS98 1TL', - 'DE993GG', - ], - }, - { - locale: 'FR', - valid: [ - '75008', - '44 522', - '98025', - '38 499', - '39940', - ], - }, - { - locale: 'CZ', - valid: [ - '20134', - '392 90', - '39919', - '938 29', - '39949', - ], - }, - { - locale: 'NL', - valid: [ - '1012 SZ', - '3432FE', - '1118 BH', - '3950IO', - '3997 GH', - ], - }, - { - locale: 'PL', - valid: [ - '47-260', - '12-930', - '78-399', - '39-490', - '38-483', - ], - }, - { - locale: 'TW', - valid: [ - '360', - '90312', - '399', - '935', - '38842', - ], - }, - { - locale: 'LI', - valid: [ - '9485', - '9497', - '9491', - '9489', - '9496', - ], - }, - { - locale: 'PT', - valid: [ - '4827', - '4829-489', - '0294-348', - '1928', - '8156-392', - ], - }, - { - locale: 'SE', - valid: [ - '12994', - '284 39', - '39556', - '489 39', - '499 49', - ], - }, - ]; - - let allValid = []; - - // Test fixtures - fixtures.forEach(function (fixture) { - if (fixture.valid) allValid = allValid.concat(fixture.valid); - test({ - validator: 'isPostalCode', - valid: fixture.valid, - invalid: fixture.invalid, - args: [fixture.locale], - }); - }); - - // Test generics - test({ - validator: 'isPostalCode', - valid: [ - ...allValid, - '1234', - '6900', - '1292', - '9400', - '27616', - '90210', - '10001', - '21201', - '33142', - '060623', - '123456', - '293940', - '002920', - ], - invalid: [ - 'asdf', - '1', - 'ASDFGJKLmZXJtZtesting123', - 'Vml2YW11cyBmZXJtZtesting123', - '48380480343', - '29923-329393-2324', - '4294924224', - '13', - ], - args: ['any'], - }); - }); -}); diff --git a/test/validators.test.js b/test/validators.test.js new file mode 100644 index 000000000..c5ea4dc99 --- /dev/null +++ b/test/validators.test.js @@ -0,0 +1,15969 @@ +import assert from 'assert'; +import fs from 'fs'; +import timezone_mock from 'timezone-mock'; +import vm from 'vm'; +import test from './testFunctions'; + +let validator_js = fs.readFileSync(require.resolve('../validator.js')).toString(); + +describe('Validators', () => { + it('should validate email addresses', () => { + test({ + validator: 'isEmail', + valid: [ + 'foo@bar.com', + 'x@x.au', + 'foo@bar.com.au', + 'foo+bar@bar.com', + 'hans.m端ller@test.com', + 'hans@m端ller.com', + 'test|123@m端ller.com', + 'test123+ext@gmail.com', + 'some.name.midd.leNa.me.and.locality+extension@GoogleMail.com', + '"foobar"@example.com', + '" foo m端ller "@example.com', + '"foo\\@bar"@example.com', + `${'a'.repeat(64)}@${'a'.repeat(63)}.com`, + `${'a'.repeat(64)}@${'a'.repeat(63)}.com`, + `${'a'.repeat(31)}@gmail.com`, + 'test@gmail.com', + 'test.1@gmail.com', + 'test@1337.com', + ], + invalid: [ + 'invalidemail@', + 'invalid.com', + '@invalid.com', + 'foo@bar.com.', + 'foo@_bar.com', + 'somename@gmail.com', + 'foo@bar.co.uk.', + 'z@co.c', + 'gmailgmailgmailgmailgmail@gmail.com', + `${'a'.repeat(64)}@${'a'.repeat(251)}.com`, + `${'a'.repeat(65)}@${'a'.repeat(250)}.com`, + `${'a'.repeat(64)}@${'a'.repeat(64)}.com`, + `${'a'.repeat(64)}@${'a'.repeat(63)}.${'a'.repeat(63)}.${'a'.repeat(63)}.${'a'.repeat(58)}.com`, + 'test1@invalid.co m', + 'test2@invalid.co m', + 'test3@invalid.co m', + 'test4@invalid.co m', + 'test5@invalid.co m', + 'test6@invalid.co m', + 'test7@invalid.co m', + 'test8@invalid.co m', + 'test9@invalid.co m', + 'test10@invalid.co m', + 'test11@invalid.co m', + 'test12@invalid.co m', + 'test13@invalid.co m', + 'multiple..dots@stillinvalid.com', + 'test123+invalid! sub_address@gmail.com', + 'gmail...ignores...dots...@gmail.com', + 'ends.with.dot.@gmail.com', + 'multiple..dots@gmail.com', + 'wrong()[]",:;<>@@gmail.com', + '"wrong()[]",:;<>@@gmail.com', + 'username@domain.com�', + 'username@domain.com©', + 'nbsp test@test.com', + 'nbsp_test@te st.com', + 'nbsp_test@test.co m', + '"foobar@gmail.com', + '"foo"bar@gmail.com', + 'foo"bar"@gmail.com', + ], + }); + }); + + it('should validate email addresses with domain specific validation', () => { + test({ + validator: 'isEmail', + args: [{ domain_specific_validation: true }], + valid: [ + 'foobar@gmail.com', + 'foo.bar@gmail.com', + 'foo.bar@googlemail.com', + `${'a'.repeat(30)}@gmail.com`, + ], + invalid: [ + `${'a'.repeat(31)}@gmail.com`, + 'test@gmail.com', + 'test.1@gmail.com', + '.foobar@gmail.com', + ], + }); + }); + + it('should validate email addresses with underscores in the domain', () => { + test({ + validator: 'isEmail', + args: [{ allow_underscores: true }], + valid: [ + 'foobar@my_sarisari_store.typepad.com', + ], + invalid: [], + }); + }); + + it('should validate email addresses without UTF8 characters in local part', () => { + test({ + validator: 'isEmail', + args: [{ allow_utf8_local_part: false }], + valid: [ + 'foo@bar.com', + 'x@x.au', + 'foo@bar.com.au', + 'foo+bar@bar.com', + 'hans@m端ller.com', + 'test|123@m端ller.com', + 'test123+ext@gmail.com', + 'some.name.midd.leNa.me+extension@GoogleMail.com', + '"foobar"@example.com', + '"foo\\@bar"@example.com', + '" foo bar "@example.com', + ], + invalid: [ + 'invalidemail@', + 'invalid.com', + '@invalid.com', + 'foo@bar.com.', + 'foo@bar.co.uk.', + 'somename@gmail.com', + 'hans.m端ller@test.com', + 'z@co.c', + 'tüst@invalid.com', + 'nbsp test@test.com', + ], + }); + }); + + it('should validate email addresses with display names', () => { + test({ + validator: 'isEmail', + args: [{ allow_display_name: true }], + valid: [ + 'foo@bar.com', + 'x@x.au', + 'foo@bar.com.au', + 'foo+bar@bar.com', + 'hans.m端ller@test.com', + 'hans@m端ller.com', + 'test|123@m端ller.com', + 'test123+ext@gmail.com', + 'some.name.midd.leNa.me+extension@GoogleMail.com', + 'Some Name ', + 'Some Name ', + 'Some Name ', + 'Some Name ', + 'Some Name ', + 'Some Name ', + 'Some Name ', + 'Some Name ', + '\'Foo Bar, Esq\'', + 'Some Name ', + 'Some Middle Name ', + 'Name ', + 'Name', + 'Some Name ', + 'Name🍓With🍑Emoji🚴‍♀️🏆', + '🍇🍗🍑', + '""', + '"\\"quotes\\""', + '"name;"', + '"name;" ', + ], + invalid: [ + 'invalidemail@', + 'invalid.com', + '@invalid.com', + 'foo@bar.com.', + 'foo@bar.co.uk.', + 'Some Name ', + 'Some Name ', + 'Some Name <@invalid.com>', + 'Some Name ', + 'Some Name ', + 'Some Name foo@bar.co.uk.>', + 'Some Name ', + 'Name foo@bar.co.uk', + 'Some Name ', + 'Some Name', + 'invisibleCharacter\u001F', + '', + '\\"quotes\\"', + '""quotes""', + 'name;', + ' ', + '" "', + ], + }); + }); + + it('should validate email addresses with required display names', () => { + test({ + validator: 'isEmail', + args: [{ require_display_name: true }], + valid: [ + 'Some Name ', + 'Some Name ', + 'Some Name ', + 'Some Name ', + 'Some Name ', + 'Some Name ', + 'Some Name ', + 'Some Name ', + 'Some Name ', + 'Some Middle Name ', + 'Name ', + 'Name', + ], + invalid: [ + 'some.name.midd.leNa.me+extension@GoogleMail.com', + 'foo@bar.com', + 'x@x.au', + 'foo@bar.com.au', + 'foo+bar@bar.com', + 'hans.m端ller@test.com', + 'hans@m端ller.com', + 'test|123@m端ller.com', + 'test123+ext@gmail.com', + 'invalidemail@', + 'invalid.com', + '@invalid.com', + 'foo@bar.com.', + 'foo@bar.co.uk.', + 'Some Name ', + 'Some Name ', + 'Some Name <@invalid.com>', + 'Some Name ', + 'Some Name ', + 'Some Name foo@bar.co.uk.>', + 'Some Name ', + 'Name foo@bar.co.uk', + ], + }); + }); + + it('should validate email addresses with allowed IPs', () => { + test({ + validator: 'isEmail', + args: [{ allow_ip_domain: true }], + valid: [ + 'email@[123.123.123.123]', + 'email@255.255.255.255', + ], + invalid: [ + 'email@0.0.0.256', + 'email@26.0.0.256', + 'email@[266.266.266.266]', + ], + }); + }); + + it('should not validate email addresses with blacklisted chars in the name', () => { + test({ + validator: 'isEmail', + args: [{ blacklisted_chars: 'abc"' }], + valid: [ + 'emil@gmail.com', + ], + invalid: [ + 'email@gmail.com', + '"foobr"@example.com', + '" foo m端ller "@example.com', + '"foo\@br"@example.com', + ], + }); + }); + + + it('should validate really long emails if ignore_max_length is set', () => { + test({ + validator: 'isEmail', + args: [{ ignore_max_length: false }], + valid: [], + invalid: [ + 'Deleted-user-id-19430-Team-5051deleted-user-id-19430-team-5051XXXXXX@example.com', + ], + }); + + test({ + validator: 'isEmail', + args: [{ ignore_max_length: true }], + valid: [ + 'Deleted-user-id-19430-Team-5051deleted-user-id-19430-team-5051XXXXXX@example.com', + ], + invalid: [], + }); + + test({ + validator: 'isEmail', + args: [{ ignore_max_length: true }], + valid: [ + 'Deleted-user-id-19430-Team-5051deleted-user-id-19430-team-5051XXXXXX@Deleted-user-id-19430-Team-5051deleted-user-id-19430-team-5051XXXXXX.com', + ], + invalid: [], + }); + }); + + it('should not validate email addresses with denylisted domains', () => { + test({ + validator: 'isEmail', + args: [{ host_blacklist: ['gmail.com', 'foo.bar.com'] }], + valid: [ + 'email@foo.gmail.com', + ], + invalid: [ + 'foo+bar@gmail.com', + 'email@foo.bar.com', + ], + }); + }); + + it('should allow regular expressions in the host blacklist of isEmail', () => { + test({ + validator: 'isEmail', + args: [{ + host_blacklist: ['bar.com', 'foo.com', /\.foo\.com$/], + }], + valid: [ + 'email@foobar.com', + 'email@foo.bar.com', + 'email@qux.com', + ], + invalid: [ + 'email@bar.com', + 'email@foo.com', + 'email@a.b.c.foo.com', + ], + }); + }); + + it('should validate only email addresses with whitelisted domains', () => { + test({ + validator: 'isEmail', + args: [{ host_whitelist: ['gmail.com', 'foo.bar.com'] }], + valid: [ + 'email@gmail.com', + 'test@foo.bar.com', + ], + invalid: [ + 'foo+bar@test.com', + 'email@foo.com', + 'email@bar.com', + ], + }); + }); + + it('should allow regular expressions in the host whitelist of isEmail', () => { + test({ + validator: 'isEmail', + args: [{ + host_whitelist: ['bar.com', 'foo.com', /\.foo\.com$/], + }], + valid: [ + 'email@bar.com', + 'email@foo.com', + 'email@a.b.c.foo.com', + ], + invalid: [ + 'email@foobar.com', + 'email@foo.bar.com', + 'email@qux.com', + ], + }); + }); + + it('should validate URLs', () => { + test({ + validator: 'isURL', + valid: [ + 'foobar.com', + 'www.foobar.com', + 'foobar.com/', + 'valid.au', + 'http://www.foobar.com/', + 'HTTP://WWW.FOOBAR.COM/', + 'https://www.foobar.com/', + 'HTTPS://WWW.FOOBAR.COM/', + 'http://www.foobar.com:23/', + 'http://www.foobar.com:65535/', + 'http://www.foobar.com:5/', + 'https://www.foobar.com/', + 'ftp://www.foobar.com/', + 'http://www.foobar.com/~foobar', + 'http://user:pass@www.foobar.com/', + 'http://user:@www.foobar.com/', + 'http://:pass@www.foobar.com/', + 'http://user@www.foobar.com', + 'http://127.0.0.1/', + 'http://10.0.0.0/', + 'http://189.123.14.13/', + 'http://duckduckgo.com/?q=%2F', + 'http://foobar.com/t$-_.+!*\'(),', + 'http://foobar.com/?foo=bar#baz=qux', + 'http://foobar.com?foo=bar', + 'http://foobar.com#baz=qux', + 'http://www.xn--froschgrn-x9a.net/', + 'http://xn--froschgrn-x9a.com/', + 'http://foo--bar.com', + 'http://høyfjellet.no', + 'http://xn--j1aac5a4g.xn--j1amh', + 'http://xn------eddceddeftq7bvv7c4ke4c.xn--p1ai', + 'http://кулік.укр', + 'test.com?ref=http://test2.com', + 'http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html', + 'http://[1080:0:0:0:8:800:200C:417A]/index.html', + 'http://[3ffe:2a00:100:7031::1]', + 'http://[1080::8:800:200C:417A]/foo', + 'http://[::192.9.5.5]/ipng', + 'http://[::FFFF:129.144.52.38]:80/index.html', + 'http://[2010:836B:4179::836B:4179]', + 'http://example.com/example.json#/foo/bar', + 'http://1337.com', + // TODO: those probably should not be marked as valid URLs; CVE-2025-56200 + /* eslint-disable no-script-url */ + 'javascript:%61%6c%65%72%74%28%31%29@example.com', + 'http://evil-site.com@example.com/', + 'javascript:alert(1)@example.com', + /* eslint-enable no-script-url */ + ], + invalid: [ + 'http://localhost:3000/', + '//foobar.com', + 'xyz://foobar.com', + 'invalid/', + 'invalid.x', + 'invalid.', + '.com', + 'http://com/', + 'http://300.0.0.1/', + 'mailto:foo@bar.com', + 'rtmp://foobar.com', + 'http://www.xn--.com/', + 'http://xn--.com/', + 'http://www.foobar.com:0/', + 'http://www.foobar.com:70000/', + 'http://www.foobar.com:99999/', + 'http://www.-foobar.com/', + 'http://www.foobar-.com/', + 'http://foobar/# lol', + 'http://foobar/? lol', + 'http://foobar/ lol/', + 'http://lol @foobar.com/', + 'http://lol:lol @foobar.com/', + 'http://lol:lol:lol@foobar.com/', + 'http://lol: @foobar.com/', + 'http://www.foo_bar.com/', + 'http://www.foobar.com/\t', + 'http://@foobar.com', + 'http://:@foobar.com', + 'http://\n@www.foobar.com/', + '', + `http://foobar.com/${new Array(2083).join('f')}`, + 'http://*.foo.com', + '*.foo.com', + '!.foo.com', + 'http://example.com.', + 'http://localhost:61500this is an invalid url!!!!', + '////foobar.com', + 'http:////foobar.com', + 'https://example.com/foo//', + // the following tests are because of CVE-2025-56200 + /* eslint-disable no-script-url */ + "javascript:alert(1);a=';@example.com/alert(1)'", + 'JaVaScRiPt:alert(1)@example.com', + 'javascript:/* comment */alert(1)@example.com', + 'javascript:var a=1; alert(a);@example.com', + 'javascript:alert(1)@user@example.com', + 'javascript:alert(1)@example.com?q=safe', + 'data:text/html,@example.com', + 'vbscript:msgbox("XSS")@example.com', + '//evil-site.com/path@example.com', + /* eslint-enable no-script-url */ + ], + }); + }); + + it('should validate URLs without protocol', () => { + test({ + validator: 'isURL', + args: [{ + require_tld: false, + require_valid_protocol: false, + }], + valid: [ + 'localhost', + 'localhost:3000', + 'service-name:8080', + 'https://localhost', + 'http://localhost:3000', + 'http://service-name:8080', + 'user:password@localhost', + 'user:pass@service-name:8080', + ], + invalid: [], + }); + + // Test with require_protocol: true - should reject hostnames with ports but no protocol + test({ + validator: 'isURL', + args: [{ + require_tld: false, + require_protocol: true, + require_valid_protocol: false, + }], + valid: [ + 'http://localhost:3000', + 'https://service-name:8080', + 'custom://localhost', + ], + invalid: [ + 'localhost:3000', + 'service-name:8080', + 'user:password@localhost', + ], + }); + + // Test non-numeric patterns after colon (should be treated as protocols) + test({ + validator: 'isURL', + args: [{ + require_tld: false, + require_valid_protocol: false, + protocols: ['custom', 'myscheme'], + }], + valid: [ + 'custom:something', + 'myscheme:data', + ], + invalid: [], + }); + }); + + it('should validate URLs with custom protocols', () => { + test({ + validator: 'isURL', + args: [{ + protocols: ['rtmp'], + }], + valid: [ + 'rtmp://foobar.com', + 'rtmp:foobar.com', + ], + invalid: [ + 'http://foobar.com', + 'tel:+15551234567', + ], + }); + }); + + it('should validate file URLs without a host', () => { + test({ + validator: 'isURL', + args: [{ + protocols: ['file'], + require_host: false, + require_tld: false, + }], + valid: [ + 'file://localhost/foo.txt', + 'file:///foo.txt', + 'file:///', + ], + invalid: [ + 'http://foobar.com', + 'file://', + ], + }); + }); + + it('should validate postgres URLs without a host', () => { + test({ + validator: 'isURL', + args: [{ + protocols: ['postgres'], + require_host: false, + }], + valid: [ + 'postgres://user:pw@/test', + ], + invalid: [ + 'http://foobar.com', + 'postgres://', + ], + }); + }); + + + it('should validate URLs with any protocol', () => { + test({ + validator: 'isURL', + args: [{ + require_valid_protocol: false, + }], + valid: [ + 'rtmp://foobar.com', + 'http://foobar.com', + 'test://foobar.com', + // Dangerous! This allows to mark malicious URLs as a valid URL (CVE-2025-56200) + // eslint-disable-next-line no-script-url + 'javascript:alert(1);@example.com', + ], + invalid: [ + 'mailto:test@example.com', + ], + }); + }); + + it('should validate URLs with underscores', () => { + test({ + validator: 'isURL', + args: [{ + allow_underscores: true, + }], + valid: [ + 'http://foo_bar.com', + 'http://pr.example_com.294.example.com/', + 'http://foo__bar.com', + 'http://_.example.com', + ], + invalid: [], + }); + }); + + it('should validate URLs that do not have a TLD', () => { + test({ + validator: 'isURL', + args: [{ + require_tld: false, + }], + valid: [ + 'http://foobar.com/', + 'http://foobar/', + 'http://localhost/', + 'foobar/', + 'foobar', + ], + invalid: [], + }); + }); + + it('should validate URLs with a trailing dot option', () => { + test({ + validator: 'isURL', + args: [{ + allow_trailing_dot: true, + require_tld: false, + }], + valid: [ + 'http://example.com.', + 'foobar.', + ], + }); + }); + + it('should validate URLs with column and no port', () => { + test({ + validator: 'isURL', + valid: [ + 'http://example.com:', + 'ftp://example.com:', + ], + invalid: [ + 'https://example.com:abc', + ], + }); + }); + + it('should validate sftp protocol URL containing column and no port', () => { + test({ + validator: 'isURL', + args: [{ + protocols: ['sftp'], + }], + valid: [ + 'sftp://user:pass@terminal.aws.test.nl:/incoming/things.csv', + ], + }); + }); + + it('should validate protocol relative URLs', () => { + test({ + validator: 'isURL', + args: [{ + allow_protocol_relative_urls: true, + }], + valid: [ + '//foobar.com', + 'http://foobar.com', + 'foobar.com', + ], + invalid: [ + '://foobar.com', + '/foobar.com', + '////foobar.com', + 'http:////foobar.com', + ], + }); + }); + + it('should not validate URLs with fragments when allow fragments is false', () => { + test({ + validator: 'isURL', + args: [{ + allow_fragments: false, + }], + valid: [ + 'http://foobar.com', + 'foobar.com', + ], + invalid: [ + 'http://foobar.com#part', + 'foobar.com#part', + ], + }); + }); + + it('should not validate URLs with query components when allow query components is false', () => { + test({ + validator: 'isURL', + args: [{ + allow_query_components: false, + }], + valid: [ + 'http://foobar.com', + 'foobar.com', + ], + invalid: [ + 'http://foobar.com?foo=bar', + 'http://foobar.com?foo=bar&bar=foo', + 'foobar.com?foo=bar', + 'foobar.com?foo=bar&bar=foo', + ], + }); + }); + + it('should not validate protocol relative URLs when require protocol is true', () => { + test({ + validator: 'isURL', + args: [{ + allow_protocol_relative_urls: true, + require_protocol: true, + }], + valid: [ + 'http://foobar.com', + ], + invalid: [ + '//foobar.com', + '://foobar.com', + '/foobar.com', + 'foobar.com', + ], + }); + }); + + it('should let users specify whether URLs require a protocol', () => { + test({ + validator: 'isURL', + args: [{ + require_protocol: true, + }], + valid: [ + 'http://foobar.com/', + ], + invalid: [ + 'http://localhost/', + 'foobar.com', + 'foobar', + ], + }); + }); + + it('should validate authentication strings if a protocol is not required', () => { + test({ + validator: 'isURL', + args: [{ + require_protocol: false, + }], + valid: [ + 'user:pw@foobar.com/', + ], + invalid: [ + 'user:pw,@foobar.com/', + ], + }); + }); + + it('should reject authentication strings if a protocol is required', () => { + test({ + validator: 'isURL', + args: [{ + require_protocol: true, + }], + valid: [ + 'http://user:pw@foobar.com/', + 'https://user:password@example.com', + 'ftp://admin:pass@ftp.example.com/', + ], + invalid: [ + 'user:pw@foobar.com/', + 'user:password@example.com', + 'admin:pass@ftp.example.com/', + ], + }); + }); + + it('should reject invalid protocols when require_valid_protocol is enabled', () => { + test({ + validator: 'isURL', + args: [{ + require_valid_protocol: true, + protocols: ['http', 'https', 'ftp'], + }], + valid: [ + 'http://example.com', + 'https://example.com', + 'ftp://example.com', + ], + invalid: [ + // eslint-disable-next-line no-script-url + 'javascript:alert(1);@example.com', + 'data:text/html,@example.com', + 'file:///etc/passwd@example.com', + ], + }); + }); + + it('should let users specify a host whitelist', () => { + test({ + validator: 'isURL', + args: [{ + host_whitelist: ['foo.com', 'bar.com'], + }], + valid: [ + 'http://bar.com/', + 'http://foo.com/', + ], + invalid: [ + 'http://foobar.com', + 'http://foo.bar.com/', + 'http://qux.com', + ], + }); + }); + + it('should allow regular expressions in the host whitelist', () => { + test({ + validator: 'isURL', + args: [{ + host_whitelist: ['bar.com', 'foo.com', /\.foo\.com$/], + }], + valid: [ + 'http://bar.com/', + 'http://foo.com/', + 'http://images.foo.com/', + 'http://cdn.foo.com/', + 'http://a.b.c.foo.com/', + ], + invalid: [ + 'http://foobar.com', + 'http://foo.bar.com/', + 'http://qux.com', + ], + }); + }); + + it('should let users specify a host blacklist', () => { + test({ + validator: 'isURL', + args: [{ + host_blacklist: ['foo.com', 'bar.com'], + }], + valid: [ + 'http://foobar.com', + 'http://foo.bar.com/', + 'http://qux.com', + ], + invalid: [ + 'http://bar.com/', + 'http://foo.com/', + ], + }); + }); + + it('should allow regular expressions in the host blacklist', () => { + test({ + validator: 'isURL', + args: [{ + host_blacklist: ['bar.com', 'foo.com', /\.foo\.com$/], + }], + valid: [ + 'http://foobar.com', + 'http://foo.bar.com/', + 'http://qux.com', + ], + invalid: [ + 'http://bar.com/', + 'http://foo.com/', + 'http://images.foo.com/', + 'http://cdn.foo.com/', + 'http://a.b.c.foo.com/', + ], + }); + }); + + it('GHSA-9965-vmph-33xx vulnerability - protocol delimiter parsing difference', () => { + const DOMAIN_WHITELIST = ['example.com']; + + test({ + validator: 'isURL', + args: [{ + protocols: ['https'], + host_whitelist: DOMAIN_WHITELIST, + require_host: false, + }], + valid: [], + invalid: [ + // eslint-disable-next-line no-script-url + "javascript:alert(1);a=';@example.com/alert(1)", + ], + }); + }); + + it('should allow rejecting urls containing authentication information', () => { + test({ + validator: 'isURL', + args: [{ disallow_auth: true }], + valid: [ + 'doe.com', + ], + invalid: [ + 'john@doe.com', + 'john:john@doe.com', + ], + }); + }); + + it('should accept urls containing authentication information', () => { + test({ + validator: 'isURL', + args: [{ disallow_auth: false }], + valid: [ + 'user@example.com', + 'user:@example.com', + 'user:password@example.com', + ], + invalid: [ + 'user:user:password@example.com', + '@example.com', + ':@example.com', + ':example.com', + ], + }); + }); + + it('should allow user to skip URL length validation', () => { + test({ + validator: 'isURL', + args: [{ validate_length: false }], + valid: [ + 'http://foobar.com/f', + `http://foobar.com/${new Array(2083).join('f')}`, + ], + invalid: [], + }); + }); + + it('should allow user to configure the maximum URL length', () => { + test({ + validator: 'isURL', + args: [{ max_allowed_length: 20 }], + valid: [ + 'http://foobar.com/12', // 20 characters + 'http://foobar.com/', + ], + invalid: [ + 'http://foobar.com/123', // 21 characters + 'http://foobar.com/1234567890', + ], + }); + }); + + it('should validate URLs with port present', () => { + test({ + validator: 'isURL', + args: [{ require_port: true }], + valid: [ + 'http://user:pass@www.foobar.com:1', + 'http://user:@www.foobar.com:65535', + 'http://127.0.0.1:23', + 'http://10.0.0.0:256', + 'http://189.123.14.13:256', + 'http://duckduckgo.com:65535?q=%2F', + ], + invalid: [ + 'http://user:pass@www.foobar.com/', + 'http://user:@www.foobar.com/', + 'http://127.0.0.1/', + 'http://10.0.0.0/', + 'http://189.123.14.13/', + 'http://duckduckgo.com/?q=%2F', + ], + }); + }); + + it('should validate MAC addresses', () => { + test({ + validator: 'isMACAddress', + valid: [ + 'ab:ab:ab:ab:ab:ab', + 'FF:FF:FF:FF:FF:FF', + '01:02:03:04:05:ab', + '01:AB:03:04:05:06', + 'A9 C5 D4 9F EB D3', + '01 02 03 04 05 ab', + '01-02-03-04-05-ab', + '0102.0304.05ab', + 'ab:ab:ab:ab:ab:ab:ab:ab', + 'FF:FF:FF:FF:FF:FF:FF:FF', + '01:02:03:04:05:06:07:ab', + '01:AB:03:04:05:06:07:08', + 'A9 C5 D4 9F EB D3 B6 65', + '01 02 03 04 05 06 07 ab', + '01-02-03-04-05-06-07-ab', + '0102.0304.0506.07ab', + ], + invalid: [ + 'abc', + '01:02:03:04:05', + '01:02:03:04:05:z0', + '01:02:03:04::ab', + '1:2:3:4:5:6', + 'AB:CD:EF:GH:01:02', + 'A9C5 D4 9F EB D3', + '01-02 03:04 05 ab', + '0102.03:04.05ab', + '900f/dffs/sdea', + '01:02:03:04:05:06:07', + '01:02:03:04:05:06:07:z0', + '01:02:03:04:05:06::ab', + '1:2:3:4:5:6:7:8', + 'AB:CD:EF:GH:01:02:03:04', + 'A9C5 D4 9F EB D3 B6 65', + '01-02 03:04 05 06 07 ab', + '0102.03:04.0506.07ab', + '900f/dffs/sdea/54gh', + ], + }); + test({ + validator: 'isMACAddress', + args: [{ + eui: '48', + }], + valid: [ + 'ab:ab:ab:ab:ab:ab', + 'FF:FF:FF:FF:FF:FF', + '01:02:03:04:05:ab', + '01:AB:03:04:05:06', + 'A9 C5 D4 9F EB D3', + '01 02 03 04 05 ab', + '01-02-03-04-05-ab', + '0102.0304.05ab', + ], + invalid: [ + 'ab:ab:ab:ab:ab:ab:ab:ab', + 'FF:FF:FF:FF:FF:FF:FF:FF', + '01:02:03:04:05:06:07:ab', + '01:AB:03:04:05:06:07:08', + 'A9 C5 D4 9F EB D3 B6 65', + '01 02 03 04 05 06 07 ab', + '01-02-03-04-05-06-07-ab', + '0102.0304.0506.07ab', + ], + }); + test({ + validator: 'isMACAddress', + args: [{ + eui: '64', + }], + valid: [ + 'ab:ab:ab:ab:ab:ab:ab:ab', + 'FF:FF:FF:FF:FF:FF:FF:FF', + '01:02:03:04:05:06:07:ab', + '01:AB:03:04:05:06:07:08', + 'A9 C5 D4 9F EB D3 B6 65', + '01 02 03 04 05 06 07 ab', + '01-02-03-04-05-06-07-ab', + '0102.0304.0506.07ab', + ], + invalid: [ + 'ab:ab:ab:ab:ab:ab', + 'FF:FF:FF:FF:FF:FF', + '01:02:03:04:05:ab', + '01:AB:03:04:05:06', + 'A9 C5 D4 9F EB D3', + '01 02 03 04 05 ab', + '01-02-03-04-05-ab', + '0102.0304.05ab', + ], + }); + }); + + it('should validate MAC addresses without separator', () => { + test({ + validator: 'isMACAddress', + args: [{ + no_separators: true, + }], + valid: [ + 'abababababab', + 'FFFFFFFFFFFF', + '0102030405ab', + '01AB03040506', + 'abababababababab', + 'FFFFFFFFFFFFFFFF', + '01020304050607ab', + '01AB030405060708', + ], + invalid: [ + 'abc', + '01:02:03:04:05', + '01:02:03:04::ab', + '1:2:3:4:5:6', + 'AB:CD:EF:GH:01:02', + 'ab:ab:ab:ab:ab:ab', + 'FF:FF:FF:FF:FF:FF', + '01:02:03:04:05:ab', + '01:AB:03:04:05:06', + '0102030405', + '01020304ab', + '123456', + 'ABCDEFGH0102', + '01:02:03:04:05:06:07', + '01:02:03:04:05:06::ab', + '1:2:3:4:5:6:7:8', + 'AB:CD:EF:GH:01:02:03:04', + 'ab:ab:ab:ab:ab:ab:ab:ab', + 'FF:FF:FF:FF:FF:FF:FF:FF', + '01:02:03:04:05:06:07:ab', + '01:AB:03:04:05:06:07:08', + '01020304050607', + '010203040506ab', + '12345678', + 'ABCDEFGH01020304', + ], + }); + test({ + validator: 'isMACAddress', + args: [{ + no_separators: true, + eui: '48', + }], + valid: [ + 'abababababab', + 'FFFFFFFFFFFF', + '0102030405ab', + '01AB03040506', + ], + invalid: [ + 'abababababababab', + 'FFFFFFFFFFFFFFFF', + '01020304050607ab', + '01AB030405060708', + ], + }); + test({ + validator: 'isMACAddress', + args: [{ + no_separators: true, + eui: '64', + }], + valid: [ + 'abababababababab', + 'FFFFFFFFFFFFFFFF', + '01020304050607ab', + '01AB030405060708', + ], + invalid: [ + 'abababababab', + 'FFFFFFFFFFFF', + '0102030405ab', + '01AB03040506', + ], + }); + }); + + it('should validate isIPRange', () => { + test({ + validator: 'isIPRange', + valid: [ + '127.0.0.1/24', + '0.0.0.0/0', + '255.255.255.0/32', + '::/0', + '::/128', + '2001::/128', + '2001:800::/128', + '::ffff:127.0.0.1/128', + ], + invalid: [ + 'abc', + '127.200.230.1/35', + '127.200.230.1/-1', + '1.1.1.1/011', + '1.1.1/24.1', + '1.1.1.1/01', + '1.1.1.1/1.1', + '1.1.1.1/1.', + '1.1.1.1/1/1', + '1.1.1.1', + '::1', + '::1/164', + '2001::/240', + '2001::/-1', + '2001::/001', + '2001::/24.1', + '2001:db8:0000:1:1:1:1:1', + '::ffff:127.0.0.1', + ], + }); + test({ + validator: 'isIPRange', + args: [4], + valid: [ + '127.0.0.1/1', + '0.0.0.0/1', + '255.255.255.255/1', + '1.2.3.4/1', + '255.0.0.1/1', + '0.0.1.1/1', + ], + invalid: [ + 'abc', + '::1', + '2001:db8:0000:1:1:1:1:1', + '::ffff:127.0.0.1', + '137.132.10.01', + '0.256.0.256', + '255.256.255.256', + ], + }); + test({ + validator: 'isIPRange', + args: [6], + valid: [ + '::1/1', + '2001:db8:0000:1:1:1:1:1/1', + '::ffff:127.0.0.1/1', + ], + invalid: [ + 'abc', + '127.0.0.1', + '0.0.0.0', + '255.255.255.255', + '1.2.3.4', + '::ffff:287.0.0.1', + '::ffff:287.0.0.1/254', + '%', + 'fe80::1234%', + 'fe80::1234%1%3%4', + 'fe80%fe80%', + ], + }); + test({ + validator: 'isIPRange', + args: [10], + valid: [], + invalid: [ + 'abc', + '127.0.0.1/1', + '0.0.0.0/1', + '255.255.255.255/1', + '1.2.3.4/1', + '::1/1', + '2001:db8:0000:1:1:1:1:1/1', + ], + }); + }); + + it('should validate FQDN', () => { + test({ + validator: 'isFQDN', + valid: [ + 'domain.com', + 'dom.plato', + 'a.domain.co', + 'foo--bar.com', + 'xn--froschgrn-x9a.com', + 'rebecca.blackfriday', + '1337.com', + ], + invalid: [ + 'abc', + '256.0.0.0', + '_.com', + '*.some.com', + 's!ome.com', + 'domain.com/', + '/more.com', + 'domain.com�', + 'domain.co\u00A0m', + 'domain.co\u1680m', + 'domain.co\u2006m', + 'domain.co\u2028m', + 'domain.co\u2029m', + 'domain.co\u202Fm', + 'domain.co\u205Fm', + 'domain.co\u3000m', + 'domain.com\uDC00', + 'domain.co\uEFFFm', + 'domain.co\uFDDAm', + 'domain.co\uFFF4m', + 'domain.com©', + 'example.0', + '192.168.0.9999', + '192.168.0', + ], + }); + }); + it('should validate FQDN with trailing dot option', () => { + test({ + validator: 'isFQDN', + args: [ + { allow_trailing_dot: true }, + ], + valid: [ + 'example.com.', + ], + }); + }); + it('should invalidate FQDN when not require_tld', () => { + test({ + validator: 'isFQDN', + args: [ + { require_tld: false }, + ], + invalid: [ + 'example.0', + '192.168.0', + '192.168.0.9999', + ], + }); + }); + it('should validate FQDN when not require_tld but allow_numeric_tld', () => { + test({ + validator: 'isFQDN', + args: [ + { allow_numeric_tld: true, require_tld: false }, + ], + valid: [ + 'example.0', + '192.168.0', + '192.168.0.9999', + ], + }); + }); + it('should validate FQDN with wildcard option', () => { + test({ + validator: 'isFQDN', + args: [ + { allow_wildcard: true }, + ], + valid: [ + '*.example.com', + '*.shop.example.com', + ], + }); + }); + it('should validate FQDN with required allow_trailing_dot, allow_underscores and allow_numeric_tld options', () => { + test({ + validator: 'isFQDN', + args: [ + { allow_trailing_dot: true, allow_underscores: true, allow_numeric_tld: true }, + ], + valid: [ + 'abc.efg.g1h.', + 'as1s.sad3s.ssa2d.', + ], + }); + }); + + it('should validate alpha strings', () => { + test({ + validator: 'isAlpha', + valid: [ + 'abc', + 'ABC', + 'FoObar', + ], + invalid: [ + 'abc1', + ' foo ', + '', + 'ÄBC', + 'FÜübar', + 'Jön', + 'Heiß', + ], + }); + }); + + it('should validate alpha string with ignored characters', () => { + test({ + validator: 'isAlpha', + args: ['en-US', { ignore: '- /' }], // ignore [space-/] + valid: [ + 'en-US', + 'this is a valid alpha string', + 'us/usa', + ], + invalid: [ + '1. this is not a valid alpha string', + 'this$is also not a valid.alpha string', + 'this is also not a valid alpha string.', + ], + }); + + test({ + validator: 'isAlpha', + args: ['en-US', { ignore: /[\s/-]/g }], // ignore [space -] + valid: [ + 'en-US', + 'this is a valid alpha string', + ], + invalid: [ + '1. this is not a valid alpha string', + 'this$is also not a valid.alpha string', + 'this is also not a valid alpha string.', + ], + }); + + test({ + validator: 'isAlpha', + args: ['en-US', { ignore: 1234 }], // invalid ignore matcher + error: [ + 'alpha', + ], + }); + }); + + it('should validate Azerbaijani alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['az-AZ'], + valid: [ + 'Azərbaycan', + 'Bakı', + 'üöğıəçş', + 'sizAzərbaycanlaşdırılmışlardansınızmı', + 'dahaBirDüzgünString', + 'abcçdeəfgğhxıijkqlmnoöprsştuüvyz', + ], + invalid: [ + 'rəqəm1', + ' foo ', + '', + 'ab(cd)', + 'simvol@', + 'wəkil', + ], + }); + }); + + it('should validate bulgarian alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['bg-BG'], + valid: [ + 'абв', + 'АБВ', + 'жаба', + 'яГоДа', + ], + invalid: [ + 'abc1', + ' foo ', + '', + 'ЁЧПС', + '_аз_обичам_обувки_', + 'ехо!', + ], + }); + }); + + it('should validate Bengali alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['bn-BD'], + valid: [ + 'অয়াওর', + 'ফগফদ্রত', + 'ফদ্ম্যতভ', + 'বেরেওভচনভন', + 'আমারবাসগা', + ], + invalid: [ + 'দাস২৩৪', + ' দ্গফহ্নভ ', + '', + '(গফদ)', + ], + }); + }); + + it('should validate czech alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['cs-CZ'], + valid: [ + 'žluťoučký', + 'KŮŇ', + 'Pěl', + 'Ďábelské', + 'ódy', + ], + invalid: [ + 'ábc1', + ' fůj ', + '', + ], + }); + }); + + it('should validate slovak alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['sk-SK'], + valid: [ + 'môj', + 'ľúbím', + 'mäkčeň', + 'stĹp', + 'vŕba', + 'ňorimberk', + 'ťava', + 'žanéta', + 'Ďábelské', + 'ódy', + ], + invalid: [ + '1moj', + '你好世界', + ' Привет мир ', + 'مرحبا العا ', + ], + }); + }); + + it('should validate danish alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['da-DK'], + valid: [ + 'aøå', + 'Ære', + 'Øre', + 'Åre', + ], + invalid: [ + 'äbc123', + 'ÄBC11', + '', + ], + }); + }); + + it('should validate dutch alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['nl-NL'], + valid: [ + 'Kán', + 'één', + 'vóór', + 'nú', + 'héél', + ], + invalid: [ + 'äca ', + 'abcß', + 'Øre', + ], + }); + }); + + it('should validate german alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['de-DE'], + valid: [ + 'äbc', + 'ÄBC', + 'FöÖbär', + 'Heiß', + ], + invalid: [ + 'äbc1', + ' föö ', + '', + ], + }); + }); + + it('should validate hungarian alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['hu-HU'], + valid: [ + 'árvíztűrőtükörfúrógép', + 'ÁRVÍZTŰRŐTÜKÖRFÚRÓGÉP', + ], + invalid: [ + 'äbc1', + ' fäö ', + 'Heiß', + '', + ], + }); + }); + + it('should validate portuguese alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['pt-PT'], + valid: [ + 'palíndromo', + 'órgão', + 'qwértyúão', + 'àäãcëüïÄÏÜ', + ], + invalid: [ + '12abc', + 'Heiß', + 'Øre', + 'æøå', + '', + ], + }); + }); + + it('should validate italian alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['it-IT'], + valid: [ + 'àéèìîóòù', + 'correnti', + 'DEFINIZIONE', + 'compilazione', + 'metró', + 'pèsca', + 'PÉSCA', + 'genî', + ], + invalid: [ + 'äbc123', + 'ÄBC11', + 'æøå', + '', + ], + }); + }); + + it('should validate Japanese alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['ja-JP'], + valid: [ + 'あいうえお', + 'がぎぐげご', + 'ぁぃぅぇぉ', + 'アイウエオ', + 'ァィゥェ', + 'アイウエオ', + '吾輩は猫である', + '臥薪嘗胆', + '新世紀エヴァンゲリオン', + '天国と地獄', + '七人の侍', + 'シン・ウルトラマン', + ], + invalid: [ + 'あいう123', + 'abcあいう', + '1984', + ], + }); + }); + + it('should validate kazakh alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['kk-KZ'], + valid: [ + 'Сәлем', + 'қанағаттандырылмағандықтарыңыздан', + 'Кешіріңіз', + 'Өкінішке', + 'Қайталаңызшы', + 'ағылшынша', + 'түсінбедім', + ], + invalid: [ + 'Кешіріңіз1', + ' Кет бар ', + 'مرحبا العا', + ], + }); + }); + + it('should validate Vietnamese alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['vi-VN'], + valid: [ + 'thiến', + 'nghiêng', + 'xin', + 'chào', + 'thế', + 'giới', + ], + invalid: [ + 'thầy3', + 'Ba gà', + '', + ], + }); + }); + + it('should validate arabic alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['ar'], + valid: [ + 'أبت', + 'اَبِتَثّجً', + ], + invalid: [ + '١٢٣أبت', + '١٢٣', + 'abc1', + ' foo ', + '', + 'ÄBC', + 'FÜübar', + 'Jön', + 'Heiß', + ], + }); + }); + + it('should validate farsi alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['fa-IR'], + valid: [ + 'پدر', + 'مادر', + 'برادر', + 'خواهر', + ], + invalid: [ + 'فارسی۱۲۳', + '۱۶۴', + 'abc1', + ' foo ', + '', + 'ÄBC', + 'FÜübar', + 'Jön', + 'Heiß', + ], + }); + }); + + it('should validate finnish alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['fi-FI'], + valid: [ + 'äiti', + 'Öljy', + 'Åke', + 'testÖ', + ], + invalid: [ + 'AİıÖöÇ窺ĞğÜüZ', + 'äöå123', + '', + ], + }); + }); + + it('should validate kurdish alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['ku-IQ'], + valid: [ + 'ئؤڤگێ', + 'کوردستان', + ], + invalid: [ + 'ئؤڤگێ١٢٣', + '١٢٣', + 'abc1', + ' foo ', + '', + 'ÄBC', + 'FÜübar', + 'Jön', + 'Heiß', + ], + }); + }); + + it('should validate norwegian alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['nb-NO'], + valid: [ + 'aøå', + 'Ære', + 'Øre', + 'Åre', + ], + invalid: [ + 'äbc123', + 'ÄBC11', + '', + ], + }); + }); + + it('should validate polish alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['pl-PL'], + valid: [ + 'kreską', + 'zamknięte', + 'zwykłe', + 'kropką', + 'przyjęły', + 'święty', + 'Pozwól', + ], + invalid: [ + '12řiď ', + 'blé!!', + 'föö!2!', + ], + }); + }); + + it('should validate serbian cyrillic alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['sr-RS'], + valid: [ + 'ШћжЂљЕ', + 'ЧПСТЋЏ', + ], + invalid: [ + 'řiď ', + 'blé33!!', + 'föö!!', + ], + }); + }); + + it('should validate serbian latin alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['sr-RS@latin'], + valid: [ + 'ŠAabčšđćž', + 'ŠATROĆčđš', + ], + invalid: [ + '12řiď ', + 'blé!!', + 'föö!2!', + ], + }); + }); + + it('should validate spanish alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['es-ES'], + valid: [ + 'ábcó', + 'ÁBCÓ', + 'dormís', + 'volvés', + 'español', + ], + invalid: [ + 'äca ', + 'abcß', + 'föö!!', + ], + }); + }); + + it('should validate swedish alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['sv-SE'], + valid: [ + 'religiös', + 'stjäla', + 'västgöte', + 'Åre', + ], + invalid: [ + 'AİıÖöÇ窺ĞğÜüZ', + 'religiös23', + '', + ], + }); + }); + + it('should validate defined arabic locales alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['ar-SY'], + valid: [ + 'أبت', + 'اَبِتَثّجً', + ], + invalid: [ + '١٢٣أبت', + '١٢٣', + 'abc1', + ' foo ', + '', + 'ÄBC', + 'FÜübar', + 'Jön', + 'Heiß', + ], + }); + }); + + it('should validate turkish alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['tr-TR'], + valid: [ + 'AİıÖöÇ窺ĞğÜüZ', + ], + invalid: [ + '0AİıÖöÇ窺ĞğÜüZ1', + ' AİıÖöÇ窺ĞğÜüZ ', + 'abc1', + ' foo ', + '', + 'ÄBC', + 'Heiß', + ], + }); + }); + + it('should validate urkrainian alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['uk-UA'], + valid: [ + 'АБВГҐДЕЄЖЗИIЇЙКЛМНОПРСТУФХЦШЩЬЮЯ', + ], + invalid: [ + '0AİıÖöÇ窺ĞğÜüZ1', + ' AİıÖöÇ窺ĞğÜüZ ', + 'abc1', + ' foo ', + '', + 'ÄBC', + 'Heiß', + 'ЫыЪъЭэ', + ], + }); + }); + + it('should validate greek alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['el-GR'], + valid: [ + 'αβγδεζηθικλμνξοπρςστυφχψω', + 'ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ', + 'άέήίΰϊϋόύώ', + 'ΆΈΉΊΪΫΎΏ', + ], + invalid: [ + '0AİıÖöÇ窺ĞğÜüZ1', + ' AİıÖöÇ窺ĞğÜüZ ', + 'ÄBC', + 'Heiß', + 'ЫыЪъЭэ', + '120', + 'jαckγ', + ], + }); + }); + + it('should validate Hebrew alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['he'], + valid: [ + 'בדיקה', + 'שלום', + ], + invalid: [ + 'בדיקה123', + ' foo ', + 'abc1', + '', + ], + }); + }); + + it('should validate Hindi alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['hi-IN'], + valid: [ + 'अतअपनाअपनीअपनेअभीअंदरआदिआपइत्यादिइनइनकाइन्हींइन्हेंइन्होंइसइसकाइसकीइसकेइसमेंइसीइसेउनउनकाउनकीउनकेउनकोउन्हींउन्हेंउन्होंउसउसकेउसीउसेएकएवंएसऐसेऔरकईकरकरताकरतेकरनाकरनेकरेंकहतेकहाकाकाफ़ीकिकितनाकिन्हेंकिन्होंकियाकिरकिसकिसीकिसेकीकुछकुलकेकोकोईकौनकौनसागयाघरजबजहाँजाजितनाजिनजिन्हेंजिन्होंजिसजिसेजीधरजैसाजैसेजोतकतबतरहतिनतिन्हेंतिन्होंतिसतिसेतोथाथीथेदबारादियादुसरादूसरेदोद्वाराननकेनहींनानिहायतनीचेनेपरपहलेपूरापेफिरबनीबहीबहुतबादबालाबिलकुलभीभीतरमगरमानोमेमेंयदियहयहाँयहीयायिहयेरखेंरहारहेऱ्वासालिएलियेलेकिनववग़ैरहवर्गवहवहाँवहींवालेवुहवेवोसकतासकतेसबसेसभीसाथसाबुतसाभसारासेसोसंगहीहुआहुईहुएहैहैंहोहोताहोतीहोतेहोनाहोने', + 'इन्हें', + ], + invalid: [ + 'अत०२३४५६७८९', + 'अत 12', + ' अत ', + 'abc1', + 'abc', + '', + ], + }); + }); + + it('should validate Tamil alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['ta-IN'], + valid: [ + 'அஆஇஈஉஊஎஏஐஒஓஔகஙசஞடணதநபமயரலவழளறனஶஜஷஸஹ', + 'தமிழ்', + ], + invalid: [ + 'தமிழ்123', + 'தமிழ் ', + 'தமிழ்.', + 'abc', + '', + ], + }); + }); + it('should validate Telugu alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['te-IN'], + valid: [ + 'అఆఇఈఉఊఋఌఎఏఐఒఓఔకఖగఘఙచఛజఝఞటఠడఢణతథదధనపఫబభమయరలవశషసహ', + 'తెలుగు', + ], + invalid: ['తెలుగు123', 'తెలుగు.', 'abc', ''], + }); + }); + it('should validate Kannada alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['kn-IN'], + valid: [ + 'ಅಆಇಈಉಊಋಎಏಐಒಓಔಕಖಗಘಙಚಛಜಝಞಟಠಡಢಣತಥದಧನಪಫಬಭಮಯರಲವಶಷಸಹಳ', + 'ಕನ್ನಡ', + ], + invalid: ['ಕನ್ನಡ123', 'ಕನ್ನಡ.', 'abc', ''], + }); + }); + it('should validate Malayalam alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['ml-IN'], + valid: [ + 'അആഇഈഉഊഋഎഏഐഒഓഔകഖഗഘങചഛജഝഞടഠഡഢണതഥദധനപഫബഭമയരലവശഷസഹള', + 'മലയാളം', + ], + invalid: ['മലയാളം123', 'മലയാളം.', 'abc', ''], + }); + }); + it('should validate Gujarati alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['gu-IN'], + valid: [ + 'અઆઇઈઉઊઋએઐઓઔકખગઘચછજઝટઠડઢણતથદધનપફબભમયરલવશષસહળ', + 'ગુજરાતી', + ], + invalid: ['ગુજરાતી123', 'ગુજરાતી.', 'abc', ''], + }); + }); + it('should validate Punjabi alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['pa-IN'], + valid: [ + 'ਅਆਇਈਉਊਏਐਓਔਕਖਗਘਙਚਛਜਝਞਟਠਡਢਣਤਥਦਧਨਪਫਬਭਮਯਰਲਵਸ਼ਸਹ', + 'ਪੰਜਾਬੀ', + ], + invalid: ['ਪੰਜਾਬੀ123', 'ਪੰਜਾਬੀ.', 'abc', ''], + }); + }); + it('should validate Odia alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['or-IN'], + valid: [ + 'ଅଆଇଈଉଊଋଌଏଐଓଔକଖଗଘଙଚଛଜଝଞଟଠଡଢଣତଥଦଧନପଫବଭମଯରଲଶଷସହଳ', + 'ଓଡ଼ିଆ', + ], + invalid: ['ଓଡ଼ିଆ123', 'ଓଡ଼ିଆ.', 'abc', ''], + }); + }); + it('should validate Bengali alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['bn-IN'], + valid: [ + 'অআইঈউঊঋএঐওঔকখগঘঙচছজঝঞটঠডঢণতথদধনপফবভমযরলশষসহ', + 'বাংলা', + ], + invalid: ['বাংলা123', 'বাংলা.', 'abc', ''], + }); + }); + it('should validate persian alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['fa-IR'], + valid: [ + 'تست', + 'عزیزم', + 'ح', + ], + invalid: [ + 'تست 1', + ' عزیزم ', + '', + ], + }); + }); + + it('should validate Thai alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['th-TH'], + valid: [ + 'สวัสดี', + 'ยินดีต้อนรับ เทสเคส', + ], + invalid: [ + 'สวัสดีHi', + '123 ยินดีต้อนรับ', + 'ยินดีต้อนรับ-๑๒๓', + ], + }); + }); + + it('should validate Korea alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['ko-KR'], + valid: [ + 'ㄱ', + 'ㅑ', + 'ㄱㄴㄷㅏㅕ', + '세종대왕', + '나랏말싸미듕귁에달아문자와로서르사맛디아니할쎄', + ], + invalid: [ + 'abc', + '123', + '흥선대원군 문호개방', + '1592년임진왜란', + '대한민국!', + ], + }); + }); + + it('should validate Sinhala alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['si-LK'], + valid: [ + 'චතුර', + 'කචටදබ', + 'ඎඏදාෛපසුගො', + ], + invalid: [ + 'ஆஐअतක', + 'කචට 12', + ' ඎ ', + 'abc1', + 'abc', + '', + ], + }); + }); + + it('should validate Esperanto alpha strings', () => { + test({ + validator: 'isAlpha', + args: ['eo'], + valid: [ + 'saluton', + 'eĥoŝanĝoĉiuĵaŭde', + 'EĤOŜANĜOĈIUĴAŬDE', + 'Esperanto', + 'LaŭLudovikoZamenhofBongustasFreŝaĈeĥaManĝaĵoKunSpicoj', + ], + invalid: [ + 'qwxyz', + '1887', + 'qwxyz 1887', + ], + }); + }); + + it('should error on invalid locale', () => { + test({ + validator: 'isAlpha', + args: ['is-NOT'], + error: [ + 'abc', + 'ABC', + ], + }); + }); + + it('should validate alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + valid: [ + 'abc123', + 'ABC11', + ], + invalid: [ + 'abc ', + 'foo!!', + 'ÄBC', + 'FÜübar', + 'Jön', + ], + }); + }); + + it('should validate alphanumeric string with ignored characters', () => { + test({ + validator: 'isAlphanumeric', + args: ['en-US', { ignore: '@_- ' }], // ignore [@ space _ -] + valid: [ + 'Hello@123', + 'this is a valid alphaNumeric string', + 'En-US @ alpha_numeric', + ], + invalid: [ + 'In*Valid', + 'hello$123', + '{invalid}', + ], + }); + + test({ + validator: 'isAlphanumeric', + args: ['en-US', { ignore: /[\s/-]/g }], // ignore [space -] + valid: [ + 'en-US', + 'this is a valid alphaNumeric string', + ], + invalid: [ + 'INVALID$ AlphaNum Str', + 'hello@123', + 'abc*123', + ], + }); + + test({ + validator: 'isAlphanumeric', + args: ['en-US', { ignore: 1234 }], // invalid ignore matcher (ignore should be instance of a String or RegExp) + error: [ + 'alpha', + ], + }); + }); + + it('should validate defined english aliases', () => { + test({ + validator: 'isAlphanumeric', + args: ['en-GB'], + valid: [ + 'abc123', + 'ABC11', + ], + invalid: [ + 'abc ', + 'foo!!', + 'ÄBC', + 'FÜübar', + 'Jön', + ], + }); + }); + + it('should validate Azerbaijani alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['az-AZ'], + valid: [ + 'Azərbaycan', + 'Bakı', + 'abc1', + 'abcç2', + '3kərə4kərə', + ], + invalid: [ + ' foo1 ', + '', + 'ab(cd)', + 'simvol@', + 'wəkil', + ], + }); + }); + + it('should validate bulgarian alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['bg-BG'], + valid: [ + 'абв1', + '4АБ5В6', + 'жаба', + 'яГоДа2', + 'йЮя', + '123', + ], + invalid: [ + ' ', + '789 ', + 'hello000', + ], + }); + }); + + it('should validate Bengali alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['bn-BD'], + valid: [ + 'দ্গজ্ঞহ্রত্য১২৩', + 'দ্গগফ৮৯০', + 'চব৩৬৫ভবচ', + '১২৩৪', + '৩৪২৩৪দফজ্ঞদফ', + ], + invalid: [ + ' ', + '১২৩ ', + 'hel৩২0', + ], + }); + }); + + it('should validate czech alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['cs-CZ'], + valid: [ + 'řiť123', + 'KŮŇ11', + ], + invalid: [ + 'řiď ', + 'blé!!', + ], + }); + }); + + it('should validate slovak alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['sk-SK'], + valid: [ + '1môj', + '2ľúbím', + '3mäkčeň', + '4stĹp', + '5vŕba', + '6ňorimberk', + '7ťava', + '8žanéta', + '9Ďábelské', + '10ódy', + ], + invalid: [ + '1moj!', + '你好世界', + ' Привет мир ', + ], + }); + }); + + it('should validate danish alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['da-DK'], + valid: [ + 'ÆØÅ123', + 'Ære321', + '321Øre', + '123Åre', + ], + invalid: [ + 'äbc123', + 'ÄBC11', + '', + ], + }); + }); + + it('should validate dutch alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['nl-NL'], + valid: [ + 'Kán123', + 'één354', + 'v4óór', + 'nú234', + 'hé54él', + ], + invalid: [ + '1äca ', + 'ab3cß', + 'Øre', + ], + }); + }); + + it('should validate finnish alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['fi-FI'], + valid: [ + 'äiti124', + 'ÖLJY1234', + '123Åke', + '451åå23', + ], + invalid: [ + 'AİıÖöÇ窺ĞğÜüZ', + 'foo!!', + '', + ], + }); + }); + + it('should validate german alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['de-DE'], + valid: [ + 'äbc123', + 'ÄBC11', + ], + invalid: [ + 'äca ', + 'föö!!', + ], + }); + }); + + it('should validate hungarian alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['hu-HU'], + valid: [ + '0árvíztűrőtükörfúrógép123', + '0ÁRVÍZTŰRŐTÜKÖRFÚRÓGÉP123', + ], + invalid: [ + '1időúr!', + 'äbc1', + ' fäö ', + 'Heiß!', + '', + ], + }); + }); + + it('should validate portuguese alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['pt-PT'], + valid: [ + 'palíndromo', + '2órgão', + 'qwértyúão9', + 'àäãcë4üïÄÏÜ', + ], + invalid: [ + '!abc', + 'Heiß', + 'Øre', + 'æøå', + '', + ], + }); + }); + + it('should validate italian alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['it-IT'], + valid: [ + '123àéèìîóòù', + '123correnti', + 'DEFINIZIONE321', + 'compil123azione', + 'met23ró', + 'pès56ca', + 'PÉS45CA', + 'gen45î', + ], + invalid: [ + 'äbc123', + 'ÄBC11', + 'æøå', + '', + ], + }); + }); + + it('should validate spanish alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['es-ES'], + valid: [ + 'ábcó123', + 'ÁBCÓ11', + ], + invalid: [ + 'äca ', + 'abcß', + 'föö!!', + ], + }); + }); + + it('should validate Vietnamese alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['vi-VN'], + valid: [ + 'Thầy3', + '3Gà', + ], + invalid: [ + 'toang!', + 'Cậu Vàng', + ], + }); + }); + + it('should validate arabic alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['ar'], + valid: [ + 'أبت123', + 'أبتَُِ١٢٣', + ], + invalid: [ + 'äca ', + 'abcß', + 'föö!!', + ], + }); + }); + + it('should validate Hindi alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['hi-IN'], + valid: [ + 'अतअपनाअपनीअपनेअभीअंदरआदिआपइत्यादिइनइनकाइन्हींइन्हेंइन्होंइसइसकाइसकीइसकेइसमेंइसीइसेउनउनकाउनकीउनकेउनकोउन्हींउन्हेंउन्होंउसउसकेउसीउसेएकएवंएसऐसेऔरकईकरकरताकरतेकरनाकरनेकरेंकहतेकहाकाकाफ़ीकिकितनाकिन्हेंकिन्होंकियाकिरकिसकिसीकिसेकीकुछकुलकेकोकोईकौनकौनसागयाघरजबजहाँजाजितनाजिनजिन्हेंजिन्होंजिसजिसेजीधरजैसाजैसेजोतकतबतरहतिनतिन्हेंतिन्होंतिसतिसेतोथाथीथेदबारादियादुसरादूसरेदोद्वाराननकेनहींनानिहायतनीचेनेपरपहलेपूरापेफिरबनीबहीबहुतबादबालाबिलकुलभीभीतरमगरमानोमेमेंयदियहयहाँयहीयायिहयेरखेंरहारहेऱ्वासालिएलियेलेकिनववग़ैरहवर्गवहवहाँवहींवालेवुहवेवोसकतासकतेसबसेसभीसाथसाबुतसाभसारासेसोसंगहीहुआहुईहुएहैहैंहोहोताहोतीहोतेहोनाहोने०२३४५६७८९', + 'इन्हें४५६७८९', + ], + invalid: [ + 'अत ०२३४५६७८९', + ' ३४५६७८९', + '12 ', + ' अत ', + 'abc1', + 'abc', + '', + ], + }); + }); + + it('should validate farsi alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['fa-IR'], + valid: [ + 'پارسی۱۲۳', + '۱۴۵۶', + 'مژگان9', + ], + invalid: [ + 'äca ', + 'abcßة', + 'föö!!', + '٤٥٦', + ], + }); + }); + + it('should validate Japanese alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['ja-JP'], + valid: [ + 'あいうえお123', + '123がぎぐげご', + 'ぁぃぅぇぉ', + 'アイウエオ', + 'ァィゥェ', + 'アイウエオ', + '20世紀少年', + '華氏451度', + ], + invalid: [ + ' あいう123 ', + 'abcあいう', + '生きろ!!', + ], + }); + }); + + it('should validate kazakh alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['kk-KZ'], + valid: [ + 'Сәлем777', + '123Бәсе', + 'солай', + 'Жиенсу', + '90тоқсан', + 'жалғыз', + '570бердім', + ], + invalid: [ + ' кешіріңіз ', + 'abcағылшынша', + 'мүмкін!!', + ], + }); + }); + + it('should validate kurdish alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['ku-IQ'], + valid: [ + 'ئؤڤگێ١٢٣', + ], + invalid: [ + 'äca ', + 'abcß', + 'föö!!', + ], + }); + }); + + it('should validate defined arabic aliases', () => { + test({ + validator: 'isAlphanumeric', + args: ['ar-SY'], + valid: [ + 'أبت123', + 'أبتَُِ١٢٣', + ], + invalid: [ + 'abc ', + 'foo!!', + 'ÄBC', + 'FÜübar', + 'Jön', + ], + }); + }); + + it('should validate norwegian alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['nb-NO'], + valid: [ + 'ÆØÅ123', + 'Ære321', + '321Øre', + '123Åre', + ], + invalid: [ + 'äbc123', + 'ÄBC11', + '', + ], + }); + }); + + it('should validate polish alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['pl-PL'], + valid: [ + 'kre123ską', + 'zam21knięte', + 'zw23ykłe', + '123', + 'prz23yjęły', + 'świ23ęty', + 'Poz1322wól', + ], + invalid: [ + '12řiď ', + 'blé!!', + 'föö!2!', + ], + }); + }); + + it('should validate serbian cyrillic alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['sr-RS'], + valid: [ + 'ШћжЂљЕ123', + 'ЧПСТ132ЋЏ', + ], + invalid: [ + 'řiď ', + 'blé!!', + 'föö!!', + ], + }); + }); + + it('should validate serbian latin alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['sr-RS@latin'], + valid: [ + 'ŠAabčšđćž123', + 'ŠATRO11Ćčđš', + ], + invalid: [ + 'řiď ', + 'blé!!', + 'föö!!', + ], + }); + }); + + it('should validate swedish alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['sv-SE'], + valid: [ + 'religiös13', + 'st23jäla', + 'västgöte123', + '123Åre', + ], + invalid: [ + 'AİıÖöÇ窺ĞğÜüZ', + 'foo!!', + '', + ], + }); + }); + + it('should validate turkish alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['tr-TR'], + valid: [ + 'AİıÖöÇ窺ĞğÜüZ123', + ], + invalid: [ + 'AİıÖöÇ窺ĞğÜüZ ', + 'foo!!', + 'ÄBC', + ], + }); + }); + + it('should validate urkrainian alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['uk-UA'], + valid: [ + 'АБВГҐДЕЄЖЗИIЇЙКЛМНОПРСТУФХЦШЩЬЮЯ123', + ], + invalid: [ + 'éeoc ', + 'foo!!', + 'ÄBC', + 'ЫыЪъЭэ', + ], + }); + }); + + it('should validate greek alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['el-GR'], + valid: [ + 'αβγδεζηθικλμνξοπρςστυφχψω', + 'ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ', + '20θ', + '1234568960', + ], + invalid: [ + '0AİıÖöÇ窺ĞğÜüZ1', + ' AİıÖöÇ窺ĞğÜüZ ', + 'ÄBC', + 'Heiß', + 'ЫыЪъЭэ', + 'jαckγ', + ], + }); + }); + + it('should validate Hebrew alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['he'], + valid: [ + 'אבג123', + 'שלום11', + ], + invalid: [ + 'אבג ', + 'לא!!', + 'abc', + ' foo ', + ], + }); + }); + + it('should validate Thai alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['th-TH'], + valid: [ + 'สวัสดี ๑๒๓', + 'ยินดีต้อนรับทั้ง ๒ คน', + ], + invalid: [ + '1.สวัสดี', + 'ยินดีต้อนรับทั้ง 2 คน', + ], + }); + }); + + it('should validate Korea alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['ko-KR'], + valid: [ + '2002', + '훈민정음', + '1446년훈민정음반포', + ], + invalid: [ + '2022!', + '2019 코로나시작', + '1.로렘입숨', + ], + }); + }); + + it('should validate Sinhala alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['si-LK'], + valid: [ + 'චතුර', + 'කචට12', + 'ඎඏදාෛපසුගො2', + '1234', + ], + invalid: [ + 'ஆஐअतක', + 'කචට 12', + ' ඎ ', + 'a1234', + 'abc', + '', + ], + }); + }); + + it('should validate Esperanto alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['eo'], + valid: [ + 'saluton', + 'eĥoŝanĝoĉiuĵaŭde0123456789', + 'EĤOŜANĜOĈIUĴAŬDE0123456789', + 'Esperanto1887', + 'LaŭLudovikoZamenhofBongustasFreŝaĈeĥaManĝaĵoKunSpicoj', + ], + invalid: [ + 'qwxyz', + 'qwxyz 1887', + ], + }); + }); + it('should validate Tamil alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['ta-IN'], + valid: [ + 'தமிழ்', + 'தமிழ்123', + 'அஆஇஈ123', + 'தமிழ்123.45', + '123.45', + 'தமிழ்.', + ], + invalid: [ + 'தமிழ் ', + 'abc', + '', + ], + }); + }); + + it('should validate Telugu alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['te-IN'], + valid: [ + 'తెలుగు', + 'తెలుగు123', + 'అఆఇఈ123', + 'తెలుగు123.45', + '123.45', + 'తెలుగు.', + ], + invalid: [ + 'abc', + '', + ], + }); + }); + + it('should validate Kannada alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['kn-IN'], + valid: [ + 'ಕನ್ನಡ', + 'ಕನ್ನಡ123', + 'ಅಆಇಈ123', + 'ಕನ್ನಡ123.45', + '123.45', + 'ಕನ್ನಡ.', + ], + invalid: [ + 'abc', + '', + ], + }); + }); + + it('should validate Malayalam alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['ml-IN'], + valid: [ + 'മലയാളം', + 'മലയാളം123', + 'അആഇഈ123', + 'മലയാളം123.45', + '123.45', + 'മലയാളം.', + ], + invalid: [ + 'abc', + '', + ], + }); + }); + + it('should validate Gujarati alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['gu-IN'], + valid: [ + 'ગુજરાતી', + 'ગુજરાતી123', + 'અઆઇઈ123', + 'ગુજરાતી123.45', + '123.45', + 'ગુજરાતી.', + ], + invalid: [ + 'abc', + '', + ], + }); + }); + + it('should validate Punjabi alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['pa-IN'], + valid: [ + 'ਪੰਜਾਬੀ', + 'ਪੰਜਾਬੀ123', + 'ਅਆਇਈ123', + 'ਪੰਜਾਬੀ123.45', + '123.45', + 'ਪੰਜਾਬੀ.', + ], + invalid: [ + 'abc', + '', + ], + }); + }); + + it('should validate Odia alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['or-IN'], + valid: [ + 'ଓଡ଼ିଆ', + 'ଓଡ଼ିଆ123', + 'ଅଆଇଈ123', + 'ଓଡ଼ିଆ123.45', + '123.45', + 'ଓଡ଼ିଆ.', + ], + invalid: [ + 'abc', + '', + ], + }); + }); + + it('should validate Bengali alphanumeric strings', () => { + test({ + validator: 'isAlphanumeric', + args: ['bn-IN'], + valid: [ + 'বাংলা', + 'বাংলা১২৩', + 'অআইঈ১২৩', + '১২৩৪৫৬৭৮৯০', + 'বাংলা১২৩', + '১২৩৪৫', + 'বাংলা', + ], + invalid: [ + 'abc', + 'বাংলা123', + '123', + 'বাংলা ১২৩', + 'বাংলা,১২৩', + '১২৩٫৪৫', + '', + ], + }); + }); + + it('should error on invalid locale', () => { + test({ + validator: 'isAlphanumeric', + args: ['is-NOT'], + error: [ + '1234568960', + 'abc123', + ], + }); + }); + + it('should validate numeric strings', () => { + test({ + validator: 'isNumeric', + valid: [ + '123', + '00123', + '-00123', + '0', + '-0', + '+123', + '123.123', + '+000000', + ], + invalid: [ + ' ', + '', + '.', + ], + }); + }); + + it('should validate numeric strings without symbols', () => { + test({ + validator: 'isNumeric', + args: [{ + no_symbols: true, + }], + valid: [ + '123', + '00123', + '0', + ], + invalid: [ + '-0', + '+000000', + '', + '+123', + '123.123', + '-00123', + ' ', + '.', + ], + }); + }); + + it('should validate numeric strings with locale', () => { + test({ + validator: 'isNumeric', + args: [{ + locale: 'fr-FR', + }], + valid: [ + '123', + '00123', + '-00123', + '0', + '-0', + '+123', + '123,123', + '+000000', + ], + invalid: [ + ' ', + '', + ',', + ], + }); + }); + + it('should validate numeric strings with locale', () => { + test({ + validator: 'isNumeric', + args: [{ + locale: 'fr-CA', + }], + valid: [ + '123', + '00123', + '-00123', + '0', + '-0', + '+123', + '123,123', + '+000000', + ], + invalid: [ + ' ', + '', + '.', + ], + }); + }); + + it('should validate ports', () => { + test({ + validator: 'isPort', + valid: [ + '0', + '22', + '80', + '443', + '3000', + '8080', + '65535', + ], + invalid: [ + '', + '-1', + '65536', + '0080', + ], + }); + }); + + it('should validate passport number', () => { + test({ + validator: 'isPassportNumber', + args: ['AM'], + valid: [ + 'AF0549358', + ], + invalid: [ + 'A1054935', + ], + }); + + + test({ + validator: 'isPassportNumber', + args: ['ID'], + valid: [ + 'C1253473', + 'B5948378', + 'A4859472', + ], + invalid: [ + 'D39481728', + 'A-3847362', + '324132132', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['AR'], + valid: [ + 'AAC811035', + ], + invalid: [ + 'A11811035', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['AT'], + valid: [ + 'P 1630837', + 'P 4366918', + ], + invalid: [ + '0 1630837', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['AU'], + valid: [ + 'N0995852', + 'L4819236', + ], + invalid: [ + '1A012345', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['AZ'], + valid: [ + 'A16175905', + 'A16175958', + ], + invalid: [ + 'AZ1234584', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['BE'], + valid: [ + 'EM000000', + 'LA080402', + ], + invalid: [ + '00123456', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['BG'], + valid: [ + '346395366', + '039903356', + ], + invalid: [ + 'ABC123456', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['BR'], + valid: [ + 'FZ973689', + 'GH231233', + ], + invalid: [ + 'ABX29332', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['BY'], + valid: [ + 'MP3899901', + ], + invalid: [ + '345333454', + 'FG53334542', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['CA'], + valid: [ + 'GA302922', + 'ZE000509', + 'A123456AB', + 'Z556378HG', + ], + invalid: [ + 'AB0123456', + 'AZ556378H', + '556378HCX', + '556378432', + '5563784', + '#B12345FD', + 'A43F12354', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['CH'], + valid: [ + 'S1100409', + 'S5200073', + 'X4028791', + ], + invalid: [ + 'AB123456', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['CN'], + valid: [ + 'G25352389', + 'E00160027', + 'EA1234567', + ], + invalid: [ + 'K0123456', + 'E-1234567', + 'G.1234567', + 'GA1234567', + 'EI1234567', + 'GO1234567', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['CY'], + valid: [ + 'K00000413', + ], + invalid: [ + 'K10100', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['CZ'], + valid: [ + '99003853', + '42747260', + ], + invalid: [ + '012345678', + 'AB123456', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['DE'], + valid: [ + 'C01X00T47', + 'C26VMVVC3', + ], + invalid: [ + 'AS0123456', + 'A012345678', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['DK'], + valid: [ + '900010172', + ], + invalid: [ + '01234567', + 'K01234567', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['DZ'], + valid: [ + '855609385', + '154472412', + '197025599', + ], + invalid: [ + 'AS0123456', + 'A012345678', + '0123456789', + '12345678', + '98KK54321', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['EE'], + valid: [ + 'K4218285', + 'K3295867', + 'KB0167630', + 'VD0023777', + ], + invalid: [ + 'K01234567', + 'KB00112233', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['ES'], + valid: [ + 'AF238143', + 'ZAB000254', + ], + invalid: [ + 'AF01234567', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['FI'], + valid: [ + 'XP8271602', + 'XD8500003', + ], + invalid: [ + 'A01234567', + 'ABC012345', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['FR'], + valid: [ + '10CV28144', + '60RF19342', + '05RP34083', + ], + invalid: [ + '012345678', + 'AB0123456', + '01C234567', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['GB'], + valid: [ + '925076473', + '107182890', + '104121156', + ], + invalid: [ + 'A012345678', + 'K000000000', + '0123456789', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['GR'], + valid: [ + 'AE0000005', + 'AK0219304', + ], + invalid: [ + 'A01234567', + '012345678', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['HR'], + valid: [ + '007007007', + '138463188', + ], + invalid: [ + 'A01234567', + '00112233', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['HU'], + valid: [ + 'ZA084505', + 'BA0006902', + ], + invalid: [ + 'A01234567', + '012345678', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['IE'], + valid: [ + 'D23145890', + 'X65097105', + 'XN0019390', + ], + invalid: [ + 'XND012345', + '0123456789', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['IN'], + valid: [ + 'A-1234567', + 'A1234567', + 'X0019390', + ], + invalid: [ + 'AB-1234567', + '0123456789', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['IR'], + valid: [ + 'J97634522', + 'A01234567', + 'Z11977831', + ], + invalid: [ + 'A0123456', + 'A0123456Z', + '012345678', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['IS'], + valid: [ + 'A2040611', + 'A1197783', + ], + invalid: [ + 'K0000000', + '01234567', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['IT'], + valid: [ + 'YA8335453', + 'KK0000000', + ], + invalid: [ + '01234567', + 'KAK001122', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['JM'], + valid: [ + 'A0123456', + ], + invalid: [ + 's0123456', + 'a01234567', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['JP'], + valid: [ + 'NH1106002', + 'TE3180251', + 'XS1234567', + ], + invalid: [ + 'X12345678', + '012345678', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['KR'], + valid: [ + 'M35772699', + 'M70689098', + ], + invalid: [ + 'X12345678', + '012345678', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['KZ'], + valid: [ + 'A0123456', + 'b0123456', + ], + invalid: [ + '01234567', + 'bb0123456', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['LI'], + valid: [ + 'a01234', + 'f01234', + ], + invalid: [ + '012345', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['LT'], + valid: [ + '20200997', + 'LB311756', + ], + invalid: [ + 'LB01234567', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['LU'], + valid: [ + 'JCU9J4T2', + 'JC4E7L2H', + ], + invalid: [ + 'JCU9J4T', + 'JC4E7L2H0', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['LV'], + valid: [ + 'LV9000339', + 'LV4017173', + ], + invalid: [ + 'LV01234567', + '4017173LV', + ], + }); + test({ + validator: 'isPassportNumber', + args: ['LY'], + valid: [ + 'P79JF34X', + 'RJ45H4V2', + ], + invalid: [ + 'P79JF34', + 'RJ45H4V2C', + 'RJ4-H4V2', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['MT'], + valid: [ + '1026564', + ], + invalid: [ + '01234567', + 'MT01234', + ], + }); + test({ + validator: 'isPassportNumber', + args: ['MZ'], + valid: [ + 'AB0808212', + '08AB12123', + ], + invalid: [ + '1AB011241', + '1AB01121', + 'ABAB01121', + ], + }); + test({ + validator: 'isPassportNumber', + args: ['MY'], + valid: [ + 'A00000000', + 'H12345678', + 'K43143233', + ], + invalid: [ + 'A1234567', + 'C01234567', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['MX'], + valid: [ + '43986369222', + '01234567890', + ], + invalid: [ + 'ABC34567890', + '34567890', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['NL'], + valid: [ + 'XTR110131', + 'XR1001R58', + ], + invalid: [ + 'XTR11013R', + 'XR1001R58A', + ], + }); + test({ + validator: 'isPassportNumber', + args: ['PK'], + valid: [ + 'QZ1791293', + 'XR1001458', + ], + invalid: [ + 'XTR11013R', + 'XR1001R58A', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['PH'], + valid: [ + 'X123456', + 'XY123456', + 'XY1234567', + 'X1234567Y', + ], + invalid: [ + 'XY12345', + 'X12345Z', + 'XY12345Z', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['NZ'], + valid: [ + 'Lf012345', + 'La012345', + 'Ld012345', + 'Lh012345', + 'ea012345', + 'ep012345', + 'n012345', + ], + invalid: [ + 'Lp012345', + 'nd012345', + 'ed012345', + 'eh012345', + 'ef012345', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['PL'], + valid: [ + 'ZS 0000177', + 'AN 3000011', + ], + invalid: [ + 'A1 0000177', + '012345678', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['PT'], + valid: [ + 'I700044', + 'K453286', + ], + invalid: [ + '0700044', + 'K4532861', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['RO'], + valid: [ + '05485968', + '040005646', + ], + invalid: [ + 'R05485968', + '0511060461', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['RU'], + valid: [ + '2 32 636829', + '012 345321', + '439863692', + ], + invalid: [ + 'A 2R YU46J0', + '01A 3D5321', + 'SF233D53T', + '12345678', + '1234567890', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['SE'], + valid: [ + '59000001', + '56702928', + ], + invalid: [ + 'SE012345', + '012345678', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['SL'], + valid: [ + 'PB0036440', + 'PB1390281', + ], + invalid: [ + 'SL0123456', + 'P01234567', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['SK'], + valid: [ + 'P0000000', + ], + invalid: [ + 'SK012345', + '012345678', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['TH'], + valid: [ + 'A123456', + 'B1234567', + 'CD123456', + 'EF1234567', + ], + invalid: [ + '123456', + '1234567', + '010485371AA', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['TR'], + valid: [ + 'U 06764100', + 'U 01048537', + ], + invalid: [ + '06764100U', + '010485371', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['UA'], + valid: [ + 'EH345655', + 'EK000001', + 'AP841503', + ], + invalid: [ + '01234567', + '012345EH', + 'A012345P', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['US'], + valid: [ + '790369937', + '340007237', + 'A90583942', + 'E00007734', + ], + invalid: [ + 'US0123456', + '0123456US', + '7903699371', + '90583942', + 'E000077341', + ], + }); + + test({ + validator: 'isPassportNumber', + args: ['ZA'], + valid: [ + 'T12345678', + 'A12345678', + 'M12345678', + 'D12345678', + ], + invalid: [ + '123456789', + 'Z12345678', + ], + }); + }); + + it('should validate decimal numbers', () => { + test({ + validator: 'isDecimal', + valid: [ + '123', + '00123', + '-00123', + '0', + '-0', + '+123', + '0.01', + '.1', + '1.0', + '-.25', + '-0', + '0.0000000000001', + ], + invalid: [ + '0,01', + ',1', + '1,0', + '-,25', + '0,0000000000001', + '0٫01', + '٫1', + '1٫0', + '-٫25', + '0٫0000000000001', + '....', + ' ', + '', + '-', + '+', + '.', + '0.1a', + 'a', + '\n', + ], + }); + + test({ + validator: 'isDecimal', + args: [{ locale: 'en-AU' }], + valid: [ + '123', + '00123', + '-00123', + '0', + '-0', + '+123', + '0.01', + '.1', + '1.0', + '-.25', + '-0', + '0.0000000000001', + ], + invalid: [ + '0,01', + ',1', + '1,0', + '-,25', + '0,0000000000001', + '0٫01', + '٫1', + '1٫0', + '-٫25', + '0٫0000000000001', + '....', + ' ', + '', + '-', + '+', + '.', + '0.1a', + 'a', + '\n', + ], + }); + + test({ + validator: 'isDecimal', + args: [{ locale: ['bg-BG'] }], + valid: [ + '123', + '00123', + '-00123', + '0', + '-0', + '+123', + '0,01', + ',1', + '1,0', + '-,25', + '-0', + '0,0000000000001', + ], + invalid: [ + '0.0000000000001', + '0.01', + '.1', + '1.0', + '-.25', + '0٫01', + '٫1', + '1٫0', + '-٫25', + '0٫0000000000001', + '....', + ' ', + '', + '-', + '+', + '.', + '0.1a', + 'a', + '\n', + ], + }); + + test({ + validator: 'isDecimal', + args: [{ locale: ['cs-CZ'] }], + valid: [ + '123', + '00123', + '-00123', + '0', + '-0', + '+123', + '0,01', + ',1', + '1,0', + '-,25', + '-0', + '0,0000000000001', + ], + invalid: [ + '0.0000000000001', + '0.01', + '.1', + '1.0', + '-.25', + '0٫01', + '٫1', + '1٫0', + '-٫25', + '0٫0000000000001', + '....', + ' ', + '', + '-', + '+', + '.', + '0.1a', + 'a', + '\n', + ], + }); + + test({ + validator: 'isDecimal', + args: [{ locale: ['ar-JO'] }], + valid: [ + '123', + '00123', + '-00123', + '0', + '-0', + '+123', + '0٫01', + '٫1', + '1٫0', + '-٫25', + '-0', + '0٫0000000000001', + ], + invalid: [ + '0,0000000000001', + '0,01', + ',1', + '1,0', + '-,25', + '0.0000000000001', + '0.01', + '.1', + '1.0', + '-.25', + '....', + ' ', + '', + '-', + '+', + '.', + '0.1a', + 'a', + '\n', + ], + }); + + test({ + validator: 'isDecimal', + args: [{ locale: ['ar-EG'] }], + valid: [ + '0.01', + ], + invalid: [ + '0,01', + ], + }); + + test({ + validator: 'isDecimal', + args: [{ locale: ['en-ZM'] }], + valid: [ + '0,01', + ], + invalid: [ + '0.01', + ], + }); + + test({ + validator: 'isDecimal', + args: [{ force_decimal: true }], + valid: [ + '0.01', + '.1', + '1.0', + '-.25', + '0.0000000000001', + ], + invalid: [ + '-0', + '123', + '00123', + '-00123', + '0', + '-0', + '+123', + '0,0000000000001', + '0,01', + ',1', + '1,0', + '-,25', + '....', + ' ', + '', + '-', + '+', + '.', + '0.1a', + 'a', + '\n', + ], + }); + + test({ + validator: 'isDecimal', + args: [{ decimal_digits: '2,3' }], + valid: [ + '123', + '00123', + '-00123', + '0', + '-0', + '+123', + '0.01', + '1.043', + '.15', + '-.255', + '-0', + ], + invalid: [ + '0.0000000000001', + '0.0', + '.1', + '1.0', + '-.2564', + '0.0', + '٫1', + '1٫0', + '-٫25', + '0٫0000000000001', + '....', + ' ', + '', + '-', + '+', + '.', + '0.1a', + 'a', + '\n', + ], + }); + }); + + it('should error on invalid locale', () => { + test({ + validator: 'isDecimal', + args: [{ locale: ['is-NOT'] }], + error: [ + '123', + '0.01', + '0,01', + ], + }); + }); + + it('should validate lowercase strings', () => { + test({ + validator: 'isLowercase', + valid: [ + 'abc', + 'abc123', + 'this is lowercase.', + 'tr竪s 端ber', + ], + invalid: [ + 'fooBar', + '123A', + ], + }); + }); + + + it('should validate imei strings', () => { + test({ + validator: 'isIMEI', + valid: [ + '352099001761481', + '868932036356090', + '490154203237518', + '546918475942169', + '998227667144730', + '532729766805999', + ], + invalid: [ + '490154203237517', + '3568680000414120', + '3520990017614823', + ], + }); + }); + + + it('should validate imei strings with hyphens', () => { + test({ + validator: 'isIMEI', + args: [{ allow_hyphens: true }], + valid: [ + '35-209900-176148-1', + '86-893203-635609-0', + '49-015420-323751-8', + '54-691847-594216-9', + '99-822766-714473-0', + '53-272976-680599-9', + ], + invalid: [ + '49-015420-323751-7', + '35-686800-0041412-0', + '35-209900-1761482-3', + ], + }); + }); + + + it('should validate uppercase strings', () => { + test({ + validator: 'isUppercase', + valid: [ + 'ABC', + 'ABC123', + 'ALL CAPS IS FUN.', + ' .', + ], + invalid: [ + 'fooBar', + '123abc', + ], + }); + }); + + it('should validate integers', () => { + test({ + validator: 'isInt', + valid: [ + '13', + '123', + '0', + '123', + '-0', + '+1', + '01', + '-01', + '000', + ], + invalid: [ + '100e10', + '123.123', + ' ', + '', + ], + }); + test({ + validator: 'isInt', + args: [{ allow_leading_zeroes: false }], + valid: [ + '13', + '123', + '0', + '123', + '-0', + '+1', + ], + invalid: [ + '01', + '-01', + '000', + '100e10', + '123.123', + ' ', + '', + ], + }); + test({ + validator: 'isInt', + args: [{ allow_leading_zeroes: true }], + valid: [ + '13', + '123', + '0', + '123', + '-0', + '+1', + '01', + '-01', + '000', + '-000', + '+000', + ], + invalid: [ + '100e10', + '123.123', + ' ', + '', + ], + }); + test({ + validator: 'isInt', + args: [{ + min: 10, + }], + valid: [ + '15', + '80', + '99', + ], + invalid: [ + '9', + '6', + '3.2', + 'a', + ], + }); + test({ + validator: 'isInt', + args: [{ + min: 10, + max: 15, + }], + valid: [ + '15', + '11', + '13', + ], + invalid: [ + '9', + '2', + '17', + '3.2', + '33', + 'a', + ], + }); + test({ + validator: 'isInt', + args: [{ + gt: 10, + lt: 15, + }], + valid: [ + '14', + '11', + '13', + ], + invalid: [ + '10', + '15', + '17', + '3.2', + '33', + 'a', + ], + }); + test({ + validator: 'isInt', + args: [{ + min: undefined, + max: undefined, + }], + valid: [ + '143', + '15', + '767777575', + ], + invalid: [ + '10.4', + 'bar', + '10a', + 'c44', + ], + }); + test({ + validator: 'isInt', + args: [{ + gt: undefined, + lt: undefined, + }], + valid: [ + '289373466', + '55', + '989', + ], + invalid: [ + '10.4', + 'baz', + '66a', + 'c21', + ], + }); + test({ + validator: 'isInt', + args: [{ + gt: null, + max: null, + }], + valid: [ + '1', + '886', + '84512345', + ], + invalid: [ + '10.4', + 'h', + '1.2', + '+', + ], + }); + test({ + validator: 'isInt', + args: [{ + lt: null, + min: null, + }], + valid: [ + '289373466', + '55', + '989', + ], + invalid: [ + ',', + '+11212+', + 'fail', + '111987234i', + ], + }); + }); + + it('should validate floats', () => { + test({ + validator: 'isFloat', + valid: [ + '123', + '123.', + '123.123', + '-123.123', + '-0.123', + '+0.123', + '0.123', + '.0', + '-.123', + '+.123', + '01.123', + '-0.22250738585072011e-307', + ], + invalid: [ + '+', + '-', + ' ', + '', + '.', + ',', + 'foo', + '20.foo', + '2020-01-06T14:31:00.135Z', + ], + }); + + test({ + validator: 'isFloat', + args: [{ locale: 'en-AU' }], + valid: [ + '123', + '123.', + '123.123', + '-123.123', + '-0.123', + '+0.123', + '0.123', + '.0', + '-.123', + '+.123', + '01.123', + '-0.22250738585072011e-307', + ], + invalid: [ + '123٫123', + '123,123', + ' ', + '', + '.', + 'foo', + ], + }); + + test({ + validator: 'isFloat', + args: [{ locale: 'de-DE' }], + valid: [ + '123', + '123,', + '123,123', + '-123,123', + '-0,123', + '+0,123', + '0,123', + ',0', + '-,123', + '+,123', + '01,123', + '-0,22250738585072011e-307', + ], + invalid: [ + '123.123', + '123٫123', + ' ', + '', + '.', + 'foo', + ], + }); + + test({ + validator: 'isFloat', + args: [{ locale: 'ar-JO' }], + valid: [ + '123', + '123٫', + '123٫123', + '-123٫123', + '-0٫123', + '+0٫123', + '0٫123', + '٫0', + '-٫123', + '+٫123', + '01٫123', + '-0٫22250738585072011e-307', + ], + invalid: [ + '123,123', + '123.123', + ' ', + '', + '.', + 'foo', + ], + }); + + test({ + validator: 'isFloat', + args: [{ + min: 3.7, + }], + valid: [ + '3.888', + '3.92', + '4.5', + '50', + '3.7', + '3.71', + ], + invalid: [ + '3.6', + '3.69', + '3', + '1.5', + 'a', + ], + }); + test({ + validator: 'isFloat', + args: [{ + min: 0.1, + max: 1.0, + }], + valid: [ + '0.1', + '1.0', + '0.15', + '0.33', + '0.57', + '0.7', + ], + invalid: [ + '0', + '0.0', + 'a', + '1.3', + '0.05', + '5', + ], + }); + test({ + validator: 'isFloat', + args: [{ + gt: -5.5, + lt: 10, + }], + valid: [ + '9.9', + '1.0', + '0', + '-1', + '7', + '-5.4', + ], + invalid: [ + '10', + '-5.5', + 'a', + '-20.3', + '20e3', + '10.00001', + ], + }); + test({ + validator: 'isFloat', + args: [{ + min: -5.5, + max: 10, + gt: -5.5, + lt: 10, + }], + valid: [ + '9.99999', + '-5.499999', + ], + invalid: [ + '10', + '-5.5', + ], + }); + test({ + validator: 'isFloat', + args: [{ + locale: 'de-DE', + min: 3.1, + }], + valid: [ + '123', + '123,', + '123,123', + '3,1', + '3,100001', + ], + invalid: [ + '3,09', + '-,123', + '+,123', + '01,123', + '-0,22250738585072011e-307', + '-123,123', + '-0,123', + '+0,123', + '0,123', + ',0', + '123.123', + '123٫123', + ' ', + '', + '.', + 'foo', + ], + }); + test({ + validator: 'isFloat', + args: [{ + min: undefined, + max: undefined, + }], + valid: [ + '123', + '123.', + '123.123', + '-767.767', + '+111.111', + ], + invalid: [ + 'ab565', + '-,123', + '+,123', + '7866.t', + '123,123', + '123,', + ], + }); + test({ + validator: 'isFloat', + args: [{ + gt: undefined, + lt: undefined, + }], + valid: [ + '14.34343', + '11.1', + '456', + ], + invalid: [ + 'ab565', + '-,123', + '+,123', + '7866.t', + ], + }); + test({ + validator: 'isFloat', + args: [{ + locale: 'ar', + gt: null, + max: null, + }], + valid: [ + '13324٫', + '12321', + '444٫83874', + ], + invalid: [ + '55.55.55', + '1;23', + '+-123', + '1111111l1', + '3.3', + ], + }); + test({ + validator: 'isFloat', + args: [{ + locale: 'ru-RU', + lt: null, + min: null, + }], + valid: [ + '11231554,34343', + '11,1', + '456', + ',311', + ], + invalid: [ + 'ab565', + '-.123', + '+.123', + '7866.t', + '22.3', + ], + }); + }); + + it('should validate hexadecimal strings', () => { + test({ + validator: 'isHexadecimal', + valid: [ + 'deadBEEF', + 'ff0044', + '0xff0044', + '0XfF0044', + '0x0123456789abcDEF', + '0X0123456789abcDEF', + '0hfedCBA9876543210', + '0HfedCBA9876543210', + '0123456789abcDEF', + ], + invalid: [ + 'abcdefg', + '', + '..', + '0xa2h', + '0xa20x', + '0x0123456789abcDEFq', + '0hfedCBA9876543210q', + '01234q56789abcDEF', + ], + }); + }); + + it('should validate octal strings', () => { + test({ + validator: 'isOctal', + valid: [ + '076543210', + '0o01234567', + ], + invalid: [ + 'abcdefg', + '012345678', + '012345670c', + '00c12345670c', + '', + '..', + ], + }); + }); + + it('should validate hexadecimal color strings', () => { + test({ + validator: 'isHexColor', + valid: [ + '#ff0000ff', + '#ff0034', + '#CCCCCC', + '0f38', + 'fff', + '#f00', + ], + invalid: [ + '#ff', + 'fff0a', + '#ff12FG', + ], + }); + }); + + it('should validate HSL color strings', () => { + test({ + validator: 'isHSL', + valid: [ + 'hsl(360,0000000000100%,000000100%)', + 'hsl(000010, 00000000001%, 00000040%)', + 'HSL(00000,0000000000100%,000000100%)', + 'hsL(0, 0%, 0%)', + 'hSl( 360 , 100% , 100% )', + 'Hsl( 00150 , 000099% , 01% )', + 'hsl(01080, 03%, 4%)', + 'hsl(-540, 03%, 4%)', + 'hsla(+540, 03%, 4%)', + 'hsla(+540, 03%, 4%, 500)', + 'hsl(+540deg, 03%, 4%, 500)', + 'hsl(+540gRaD, 03%, 4%, 500)', + 'hsl(+540.01e-98rad, 03%, 4%, 500)', + 'hsl(-540.5turn, 03%, 4%, 500)', + 'hsl(+540, 03%, 4%, 500e-01)', + 'hsl(+540, 03%, 4%, 500e+80)', + 'hsl(4.71239rad, 60%, 70%)', + 'hsl(270deg, 60%, 70%)', + 'hsl(200, +.1%, 62%, 1)', + 'hsl(270 60% 70%)', + 'hsl(200, +.1e-9%, 62e10%, 1)', + 'hsl(.75turn, 60%, 70%)', + // 'hsl(200grad+.1%62%/1)', //supposed to pass, but need to handle delimiters + 'hsl(200grad +.1% 62% / 1)', + 'hsl(270, 60%, 50%, .15)', + 'hsl(270, 60%, 50%, 15%)', + 'hsl(270 60% 50% / .15)', + 'hsl(270 60% 50% / 15%)', + ], + invalid: [ + 'hsl (360,0000000000100%,000000100%)', + 'hsl(0260, 100 %, 100%)', + 'hsl(0160, 100%, 100%, 100 %)', + 'hsl(-0160, 100%, 100a)', + 'hsl(-0160, 100%, 100)', + 'hsl(-0160 100%, 100%, )', + 'hsl(270 deg, 60%, 70%)', + 'hsl( deg, 60%, 70%)', + 'hsl(, 60%, 70%)', + 'hsl(3000deg, 70%)', + ], + }); + }); + + it('should validate rgb color strings', () => { + test({ + validator: 'isRgbColor', + valid: [ + 'rgb(0,0,0)', + 'rgb(255,255,255)', + 'rgba(0,0,0,0)', + 'rgba(255,255,255,1)', + 'rgba(255,255,255,.1)', + 'rgba(255,255,255,0.1)', + 'rgba(255,255,255,.12)', + 'rgb(5%,5%,5%)', + 'rgba(5%,5%,5%,.3)', + 'rgba(5%,5%,5%,.32)', + ], + invalid: [ + 'rgb(0,0,0,)', + 'rgb(0,0,)', + 'rgb(0,0,256)', + 'rgb()', + 'rgba(0,0,0)', + 'rgba(255,255,255,2)', + 'rgba(255,255,255,.123)', + 'rgba(255,255,256,0.1)', + 'rgb(4,4,5%)', + 'rgba(5%,5%,5%)', + 'rgba(3,3,3%,.3)', + 'rgba(5%,5%,5%,.321)', + 'rgb(101%,101%,101%)', + 'rgba(3%,3%,101%,0.3)', + 'rgb(101%,101%,101%) additional invalid string part', + 'rgba(3%,3%,101%,0.3) additional invalid string part', + 'r g b( 0, 251, 222 )', + 'r g ba( 0, 251, 222 )', + 'rg ba(0, 251, 22, 0.5)', + 'rgb( 255,255 ,255)', + 'rgba(255, 255, 255, 0.5)', + 'rgba(255, 255, 255, 0.5)', + 'rgb(5%, 5%, 5%)', + ], + }); + + // test empty options object + test({ + validator: 'isRgbColor', + args: [{}], + valid: [ + 'rgb(0,0,0)', + 'rgb(255,255,255)', + 'rgba(0,0,0,0)', + 'rgba(255,255,255,1)', + 'rgba(255,255,255,.1)', + 'rgba(255,255,255,.12)', + 'rgba(255,255,255,0.1)', + 'rgb(5%,5%,5%)', + 'rgba(5%,5%,5%,.3)', + ], + invalid: [ + 'rgb(0,0,0,)', + 'rgb(0,0,)', + 'rgb(0,0,256)', + 'rgb()', + 'rgba(0,0,0)', + 'rgba(255,255,255,2)', + 'rgba(255,255,256,0.1)', + 'rgb(4,4,5%)', + 'rgba(5%,5%,5%)', + 'rgba(3,3,3%,.3)', + 'rgb(101%,101%,101%)', + 'rgba(3%,3%,101%,0.3)', + 'r g b( 0, 251, 222 )', + 'r g ba( 0, 251, 222 )', + 'rg ba(0, 251, 22, 0.5)', + 'rgb( 255,255 ,255)', + 'rgba(255, 255, 255, 0.5)', + 'rgba(255, 255, 255, 0.5)', + 'rgb(5%, 5%, 5%)', + ], + }); + // test where includePercentValues is given as false + test({ + validator: 'isRgbColor', + args: [false], + valid: [ + 'rgb(5,5,5)', + 'rgba(5,5,5,.3)', + ], + invalid: [ + 'rgb(4,4,5%)', + 'rgba(5%,5%,5%)', + 'r g b( 0, 251, 222 )', + 'r g ba( 0, 251, 222 )', + ], + }); + + // test where includePercentValues is given as false as part of options object + test({ + validator: 'isRgbColor', + args: [{ includePercentValues: false }], + valid: [ + 'rgb(5,5,5)', + 'rgba(5,5,5,.3)', + ], + invalid: [ + 'rgb(4,4,5%)', + 'rgba(5%,5%,5%)', + 'r g b( 0, 251, 222 )', + 'rgba(255, 255, 255 ,0.2)', + 'r g ba( 0, 251, 222 )', + ], + }); + + // test where include percent is true explciitly + test({ + validator: 'isRgbColor', + args: [true], + valid: [ + 'rgb(5,5,5)', + 'rgba(5,5,5,.3)', + 'rgb(0,0,0)', + 'rgb(255,255,255)', + 'rgba(0,0,0,0)', + 'rgba(255,255,255,1)', + 'rgba(255,255,255,.1)', + 'rgba(255,255,255,.12)', + 'rgba(255,255,255,0.1)', + 'rgb(5%,5%,5%)', + 'rgba(5%,5%,5%,.3)', + 'rgb(5%,5%,5%)', + 'rgba(255,255,255,0.5)', + ], + invalid: [ + 'rgba(255, 255, 255, 0.5)', + 'rgb(5%, 5%, 5%)', + 'rgb(4,4,5%)', + 'rgba(5%,5%,5%)', + 'r g b( 0, 251, 222 )', + 'r g ba( 0, 251, 222 )', + 'rgb(0,0,0,)', + 'rgb(0,0,)', + 'rgb(0,0,256)', + 'rgb()', + 'rgba(0,0,0)', + 'rgba(255,255,255,2)', + 'rgba(255,255,256,0.1)', + 'rgb(4,4,5%)', + 'rgba(5%,5%,5%)', + 'rgba(3,3,3%,.3)', + 'rgb(101%,101%,101%)', + 'rgba(3%,3%,101%,0.3)', + ], + }); + + // test where percent value is false and allowSpaces is true as part of options object + test({ + validator: 'isRgbColor', + args: [{ includePercentValues: false, allowSpaces: true }], + valid: [ + 'rgb(5,5,5)', + 'rgba(5,5,5,.3)', + 'rgba(255,255,255,0.2)', + 'rgba(255, 255, 255 ,0.2)', + ], + invalid: [ + 'rgb(4,4,5%)', + 'rgba(5%,5%,5%)', + 'rgba(5% ,5%, 5%)', + 'r g b( 0, 251, 222 )', + 'r g ba( 0, 251, 222 )', + 'rgb(0,0,)', + 'rgb()', + 'rgb(4,4,5%)', + 'rgb(5%,5%,5%)', + 'rgba(3,3,3%,.3)', + 'rgb(101%, 101%, 101%)', + 'rgba(3%,3%,101%,0.3)', + ], + + }); + + // test where both are true as part of options object + test({ + validator: 'isRgbColor', + args: [{ includePercentValues: true, allowSpaces: true }], + valid: [ + 'rgb( 5, 5, 5)', + 'rgba(5, 5, 5, .3)', + 'rgb(0, 0, 0)', + 'rgb(255, 255, 255)', + 'rgba(0, 0, 0, 0)', + 'rgba(255, 255, 255, 1)', + 'rgba(255, 255, 255, .1)', + 'rgba(255, 255, 255, 0.1)', + 'rgb(5% ,5% ,5%)', + 'rgba(5%,5%,5%, .3)', + ], + invalid: [ + 'r g b( 0, 251, 222 )', + 'rgb(4,4,5%)', + 'rgb(101%,101%,101%)', + + ], + }); + + // test where allowSpaces is false as part of options object + test({ + validator: 'isRgbColor', + args: [{ includePercentValues: true, allowSpaces: false }], + valid: [ + 'rgb(5,5,5)', + 'rgba(5,5,5,.3)', + 'rgb(0,0,0)', + 'rgb(255,255,255)', + 'rgba(0,0,0,0)', + 'rgba(255,255,255,1)', + 'rgba(255,255,255,.1)', + 'rgba(255,255,255,.12)', + 'rgba(255,255,255,0.1)', + 'rgb(5%,5%,5%)', + 'rgba(5%,5%,5%,.3)', + + ], + invalid: [ + 'rgb( 255,255 ,255)', + 'rgba(255, 255, 255, 0.5)', + 'rgb(5%, 5%, 5%)', + 'rgba(255, 255, 255, 0.5)', + 'rgb(4,4,5%)', + 'rgba(5%,5%,5%)', + 'r g b( 0, 251, 222 )', + 'r g ba( 0, 251, 222 )', + 'rgb(0,0,0,)', + 'rgb(0,0,)', + 'rgb(0,0,256)', + 'rgb()', + 'rgba(0,0,0)', + 'rgba(255,255,255,2)', + 'rgba(255,255,256,0.1)', + 'rgb(4,4,5%)', + 'rgba(5%,5%,5%)', + 'rgba(3,3,3%,.3)', + 'rgb(101%,101%,101%)', + 'rgba(3%,3%,101%,0.3)', + ], + }); + }); + + it('should validate ISRC code strings', () => { + test({ + validator: 'isISRC', + valid: [ + 'USAT29900609', + 'GBAYE6800011', + 'USRC15705223', + 'USCA29500702', + ], + invalid: [ + 'USAT2990060', + 'SRC15705223', + 'US-CA29500702', + 'USARC15705223', + ], + }); + }); + + it('should validate md5 strings', () => { + test({ + validator: 'isMD5', + valid: [ + 'd94f3f016ae679c3008de268209132f2', + '751adbc511ccbe8edf23d486fa4581cd', + '88dae00e614d8f24cfd5a8b3f8002e93', + '0bf1c35032a71a14c2f719e5a14c1e96', + ], + invalid: [ + 'KYT0bf1c35032a71a14c2f719e5a14c1', + 'q94375dj93458w34', + '39485729348', + '%&FHKJFvk', + ], + }); + }); + + it('should validate hash strings', () => { + ['md5', 'md4', 'ripemd128', 'tiger128'].forEach((algorithm) => { + test({ + validator: 'isHash', + args: [algorithm], + valid: [ + 'd94f3f016ae679c3008de268209132f2', + '751adbc511ccbe8edf23d486fa4581cd', + '88dae00e614d8f24cfd5a8b3f8002e93', + '0bf1c35032a71a14c2f719e5a14c1e96', + 'd94f3F016Ae679C3008de268209132F2', + '88DAE00e614d8f24cfd5a8b3f8002E93', + ], + invalid: [ + 'q94375dj93458w34', + '39485729348', + '%&FHKJFvk', + 'KYT0bf1c35032a71a14c2f719e5a1', + ], + }); + }); + + ['crc32', 'crc32b'].forEach((algorithm) => { + test({ + validator: 'isHash', + args: [algorithm], + valid: [ + 'd94f3f01', + '751adbc5', + '88dae00e', + '0bf1c350', + '88DAE00e', + '751aDBc5', + ], + invalid: [ + 'KYT0bf1c35032a71a14c2f719e5a14c1', + 'q94375dj93458w34', + 'q943', + '39485729348', + '%&FHKJFvk', + ], + }); + }); + + ['sha1', 'tiger160', 'ripemd160'].forEach((algorithm) => { + test({ + validator: 'isHash', + args: [algorithm], + valid: [ + '3ca25ae354e192b26879f651a51d92aa8a34d8d3', + 'aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d', + 'beb8c3f30da46be179b8df5f5ecb5e4b10508230', + 'efd5d3b190e893ed317f38da2420d63b7ae0d5ed', + 'AAF4c61ddCC5e8a2dabede0f3b482cd9AEA9434D', + '3ca25AE354e192b26879f651A51d92aa8a34d8D3', + ], + invalid: [ + 'KYT0bf1c35032a71a14c2f719e5a14c1', + 'KYT0bf1c35032a71a14c2f719e5a14c1dsjkjkjkjkkjk', + 'q94375dj93458w34', + '39485729348', + '%&FHKJFvk', + ], + }); + }); + + test({ + validator: 'isHash', + args: ['sha256'], + valid: [ + '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824', + '1d996e033d612d9af2b44b70061ee0e868bfd14c2dd90b129e1edeb7953e7985', + '80f70bfeaed5886e33536bcfa8c05c60afef5a0e48f699a7912d5e399cdcc441', + '579282cfb65ca1f109b78536effaf621b853c9f7079664a3fbe2b519f435898c', + '2CF24dba5FB0a30e26E83b2AC5b9E29E1b161e5C1fa7425E73043362938b9824', + '80F70bFEAed5886e33536bcfa8c05c60aFEF5a0e48f699a7912d5e399cdCC441', + ], + invalid: [ + 'KYT0bf1c35032a71a14c2f719e5a14c1', + 'KYT0bf1c35032a71a14c2f719e5a14c1dsjkjkjkjkkjk', + 'q94375dj93458w34', + '39485729348', + '%&FHKJFvk', + ], + }); + test({ + validator: 'isHash', + args: ['sha384'], + valid: [ + '3fed1f814d28dc5d63e313f8a601ecc4836d1662a19365cbdcf6870f6b56388850b58043f7ebf2418abb8f39c3a42e31', + 'b330f4e575db6e73500bd3b805db1a84b5a034e5d21f0041d91eec85af1dfcb13e40bb1c4d36a72487e048ac6af74b58', + 'bf547c3fc5841a377eb1519c2890344dbab15c40ae4150b4b34443d2212e5b04aa9d58865bf03d8ae27840fef430b891', + 'fc09a3d11368386530f985dacddd026ae1e44e0e297c805c3429d50744e6237eb4417c20ffca8807b071823af13a3f65', + '3fed1f814d28dc5d63e313f8A601ecc4836d1662a19365CBDCf6870f6b56388850b58043f7ebf2418abb8f39c3a42e31', + 'b330f4E575db6e73500bd3b805db1a84b5a034e5d21f0041d91EEC85af1dfcb13e40bb1c4d36a72487e048ac6af74b58', + ], + invalid: [ + 'KYT0bf1c35032a71a14c2f719e5a14c1', + 'KYT0bf1c35032a71a14c2f719e5a14c1dsjkjkjkjkkjk', + 'q94375dj93458w34', + '39485729348', + '%&FHKJFvk', + ], + }); + test({ + validator: 'isHash', + args: ['sha512'], + valid: [ + '9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043', + '83c586381bf5ba94c8d9ba8b6b92beb0997d76c257708742a6c26d1b7cbb9269af92d527419d5b8475f2bb6686d2f92a6649b7f174c1d8306eb335e585ab5049', + '45bc5fa8cb45ee408c04b6269e9f1e1c17090c5ce26ffeeda2af097735b29953ce547e40ff3ad0d120e5361cc5f9cee35ea91ecd4077f3f589b4d439168f91b9', + '432ac3d29e4f18c7f604f7c3c96369a6c5c61fc09bf77880548239baffd61636d42ed374f41c261e424d20d98e320e812a6d52865be059745fdb2cb20acff0ab', + '9B71D224bd62f3785D96d46ad3ea3d73319bFBC2890CAAdae2dff72519673CA72323C3d99ba5c11d7c7ACC6e14b8c5DA0c4663475c2E5c3adef46f73bcDEC043', + '432AC3d29E4f18c7F604f7c3c96369A6C5c61fC09Bf77880548239baffd61636d42ed374f41c261e424d20d98e320e812a6d52865be059745fdb2cb20acff0ab', + ], + invalid: [ + 'KYT0bf1c35032a71a14c2f719e5a14c1', + 'KYT0bf1c35032a71a14c2f719e5a14c1dsjkjkjkjkkjk', + 'q94375dj93458w34', + '39485729348', + '%&FHKJFvk', + ], + }); + test({ + validator: 'isHash', + args: ['tiger192'], + valid: [ + '6281a1f098c5e7290927ed09150d43ff3990a0fe1a48267c', + '56268f7bc269cf1bc83d3ce42e07a85632394737918f4760', + '46fc0125a148788a3ac1d649566fc04eb84a746f1a6e4fa7', + '7731ea1621ae99ea3197b94583d034fdbaa4dce31a67404a', + '6281A1f098c5e7290927ed09150d43ff3990a0fe1a48267C', + '46FC0125a148788a3AC1d649566fc04eb84A746f1a6E4fa7', + ], + invalid: [ + 'KYT0bf1c35032a71a14c2f719e5a14c1', + 'KYT0bf1c35032a71a14c2f719e5a14c1dsjkjkjkjkkjk', + 'q94375dj93458w34', + '39485729348', + '%&FHKJFvk', + ], + }); + }); + it('should validate JWT tokens', () => { + test({ + validator: 'isJWT', + valid: [ + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRJbkFzIjoiYWRtaW4iLCJpYXQiOjE0MjI3Nzk2Mzh9.gzSraSYS8EXBxLN_oWnFSRgCzcmJmMjLiuyu5CSpyHI', + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb3JlbSI6Imlwc3VtIn0.ymiJSsMJXR6tMSr8G9usjQ15_8hKPDv_CArLhxw28MI', + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkb2xvciI6InNpdCIsImFtZXQiOlsibG9yZW0iLCJpcHN1bSJdfQ.rRpe04zbWbbJjwM43VnHzAboDzszJtGrNsUxaqQ-GQ8', + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqb2huIjp7ImFnZSI6MjUsImhlaWdodCI6MTg1fSwiamFrZSI6eyJhZ2UiOjMwLCJoZWlnaHQiOjI3MH19.YRLPARDmhGMC3BBk_OhtwwK21PIkVCqQe8ncIRPKo-E', + ], + invalid: [ + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9', + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NSIsIm5hbWUiOiJKb2huIERvZSIsImlhdCI6MTUxNjIzOTAyMn0', + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NSIsIm5hbWUiOiJKb2huIERvZSIsImlhdCI6MTYxNjY1Mzg3Mn0.eyJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwiaWF0IjoxNjE2NjUzODcyLCJleHAiOjE2MTY2NTM4ODJ9.a1jLRQkO5TV5y5ERcaPAiM9Xm2gBdRjKrrCpHkGr_8M', + '$Zs.ewu.su84', + 'ks64$S/9.dy$§kz.3sd73b', + ], + error: [ + [], + {}, + null, + undefined, + ], + }); + }); + + it('should validate null strings', () => { + test({ + validator: 'isEmpty', + valid: [ + '', + ], + invalid: [ + ' ', + 'foo', + '3', + ], + }); + test({ + validator: 'isEmpty', + args: [{ ignore_whitespace: false }], + valid: [ + '', + ], + invalid: [ + ' ', + 'foo', + '3', + ], + }); + test({ + validator: 'isEmpty', + args: [{ ignore_whitespace: true }], + valid: [ + '', + ' ', + ], + invalid: [ + 'foo', + '3', + ], + }); + }); + + it('should validate strings against an expected value', () => { + test({ + validator: 'equals', args: ['abc'], valid: ['abc'], invalid: ['Abc', '123'], + }); + }); + + it('should validate strings contain another string', () => { + test({ + validator: 'contains', + args: ['foo'], + valid: ['foo', 'foobar', 'bazfoo'], + invalid: ['bar', 'fobar'], + }); + + test({ + validator: 'contains', + args: ['foo', { + ignoreCase: true, + }], + valid: ['Foo', 'FOObar', 'BAZfoo'], + invalid: ['bar', 'fobar', 'baxoof'], + }); + + test({ + validator: 'contains', + args: ['foo', { + minOccurrences: 2, + }], + valid: ['foofoofoo', '12foo124foo', 'fofooofoooofoooo', 'foo1foo'], + invalid: ['foo', 'foobar', 'Fooofoo', 'foofo'], + }); + }); + + it('should validate strings against a pattern', () => { + test({ + validator: 'matches', + args: [/abc/], + valid: ['abc', 'abcdef', '123abc'], + invalid: ['acb', 'Abc'], + }); + test({ + validator: 'matches', + args: ['abc'], + valid: ['abc', 'abcdef', '123abc'], + invalid: ['acb', 'Abc'], + }); + test({ + validator: 'matches', + args: ['abc', 'i'], + valid: ['abc', 'abcdef', '123abc', 'AbC'], + invalid: ['acb'], + }); + }); + + + it('should validate isLocale codes', () => { + test({ + validator: 'isLocale', + valid: [ + 'uz_Latn_UZ', + 'en', + 'gsw', + 'en-US', + 'es_ES', + 'es-419', + 'sw_KE', + 'am_ET', + 'zh-CHS', + 'ca_ES_VALENCIA', + 'en_US_POSIX', + 'hak-CN', + 'zh-Hant', + 'zh-Hans', + 'sr-Cyrl', + 'sr-Latn', + 'zh-cmn-Hans-CN', + 'cmn-Hans-CN', + 'zh-yue-HK', + 'yue-HK', + 'zh-Hans-CN', + 'sr-Latn-RS', + 'sl-rozaj', + 'sl-rozaj-biske', + 'sl-nedis', + 'de-CH-1901', + 'sl-IT-nedis', + 'hy-Latn-IT-arevela', + 'i-enochian', + 'en-scotland-fonipa', + 'sl-IT-rozaj-biske-1994', + 'de-CH-x-phonebk', + 'az-Arab-x-AZE-derbend', + 'x-whatever', + 'qaa-Qaaa-QM-x-southern', + 'de-Qaaa', + 'sr-Latn-QM', + 'sr-Qaaa-RS', + 'en-US-u-islamcal', + 'zh-CN-a-myext-x-private', + 'en-a-myext-b-another', + ], + invalid: [ + 'lo_POP', + '12', + '12_DD', + 'de-419-DE', + 'a-DE', + ], + }); + }); + + it('should validate strings by byte length (deprecated api)', () => { + test({ + validator: 'isByteLength', + args: [2], + valid: ['abc', 'de', 'abcd', 'gmail'], + invalid: ['', 'a'], + }); + test({ + validator: 'isByteLength', + args: [2, 3], + valid: ['abc', 'de', 'g'], + invalid: ['', 'a', 'abcd', 'gm'], + }); + test({ + validator: 'isByteLength', + args: [0, 0], + valid: [''], + invalid: ['g', 'a'], + }); + }); + + + it('should validate strings by byte length', () => { + test({ + validator: 'isByteLength', + args: [{ min: 2 }], + valid: ['abc', 'de', 'abcd', 'gmail'], + invalid: ['', 'a'], + }); + test({ + validator: 'isByteLength', + args: [{ min: 2, max: 3 }], + valid: ['abc', 'de', 'g'], + invalid: ['', 'a', 'abcd', 'gm'], + }); + test({ + validator: 'isByteLength', + args: [{ max: 3 }], + valid: ['abc', 'de', 'g', 'a', ''], + invalid: ['abcd', 'gm'], + }); + test({ + validator: 'isByteLength', + args: [{ max: 0 }], + valid: [''], + invalid: ['g', 'a'], + }); + }); + + it('should validate ULIDs', () => { + test({ + validator: 'isULID', + valid: [ + '01HBGW8CWQ5Q6DTT7XP89VV4KT', + '01HBGW8CWR8MZQMBG6FA2QHMDD', + '01HBGW8CWS3MEEK12Y9G7SVW4V', + '01hbgw8cws1tq2njavy9amb0wx', + '01HBGW8cwS43H4jkQ0A4ZRJ7QV', + ], + invalid: [ + '', + '01HBGW-CWS3MEEK1#Y9G7SVW4V', + '91HBGW8CWS3MEEK12Y9G7SVW4V', + '81HBGW8CWS3MEEK12Y9G7SVW4V', + '934859', + '01HBGW8CWS3MEEK12Y9G7SVW4VXXX', + '01UBGW8IWS3MOEK12Y9G7SVW4V', + '01HBGW8CuS43H4JKQ0A4ZRJ7QV', + ], + }); + }); + + it('should validate UUIDs', () => { + test({ + validator: 'isUUID', + valid: [ + '9deb20fe-a6e0-355c-81ea-288b009e4f6d', + 'A987FBC9-4BED-4078-8F07-9141BA07C9F3', + 'A987FBC9-4BED-5078-AF07-9141BA07C9F3', + 'A987FBC9-4BED-6078-AF07-9141BA07C9F3', + '018C544A-D384-7000-BB74-3B1738ABE43C', + 'A987FBC9-4BED-8078-AF07-9141BA07C9F3', + ], + invalid: [ + '', + 'xxxA987FBC9-4BED-3078-CF07-9141BA07C9F3', + 'A987FBC9-4BED-3078-CF07-9141BA07C9F3', + 'A987FBC9-4BED-3078-CF07-9141BA07C9F3xxx', + 'A987FBC94BED3078CF079141BA07C9F3', + '934859', + '987FBC9-4BED-3078-CF07A-9141BA07C9F3', + 'AAAAAAAA-1111-1111-AAAG-111111111111', + ], + }); + test({ + validator: 'isUUID', + args: [undefined], + valid: [ + '9deb20fe-a6e0-355c-81ea-288b009e4f6d', + 'A117FBC9-4BED-5078-AF07-9141BA07C9F3', + '018C544A-D384-7000-BB74-3B1738ABE43C', + ], + invalid: [ + '', + 'A117FBC9-4BED-3078-CF07-9141BA07C9F3', + 'xxxA987FBC9-4BED-3078-CF07-9141BA07C9F3', + 'A987FBC94BED3078CF079141BA07C9F3', + 'A11AAAAA-1111-1111-AAAG-111111111111', + ], + }); + test({ + validator: 'isUUID', + args: [null], + valid: [ + 'A127FBC9-4BED-3078-AF07-9141BA07C9F3', + '018C544A-D384-7000-BB74-3B1738ABE43C', + ], + invalid: [ + '', + 'xxxA987FBC9-4BED-3078-CF07-9141BA07C9F3', + 'A127FBC9-4BED-3078-CF07-9141BA07C9F3', + 'A127FBC9-4BED-3078-CF07-9141BA07C9F3xxx', + '912859', + 'A12AAAAA-1111-1111-AAAG-111111111111', + ], + }); + test({ + validator: 'isUUID', + args: [1], + valid: [ + 'E034B584-7D89-11E9-9669-1AECF481A97B', + ], + invalid: [ + 'xxxA987FBC9-4BED-3078-CF07-9141BA07C9F3', + 'AAAAAAAA-1111-2222-AAAG', + 'AAAAAAAA-1111-2222-AAAG-111111111111', + 'A987FBC9-4BED-4078-8F07-9141BA07C9F3', + 'A987FBC9-4BED-5078-AF07-9141BA07C9F3', + '018C544A-D384-7000-BB74-3B1738ABE43C', + ], + }); + test({ + validator: 'isUUID', + args: [2], + valid: [ + 'A987FBC9-4BED-2078-AF07-9141BA07C9F3', + ], + invalid: [ + '', + 'xxxA987FBC9-4BED-3078-CF07-9141BA07C9F3', + '11111', + 'AAAAAAAA-1111-1111-AAAG-111111111111', + 'A987FBC9-4BED-2078-CF07-9141BA07C9F3', + 'A987FBC9-4BED-4078-8F07-9141BA07C9F3', + 'A987FBC9-4BED-5078-AF07-9141BA07C9F3', + '018C544A-D384-7000-BB74-3B1738ABE43C', + ], + }); + test({ + validator: 'isUUID', + args: [3], + valid: [ + '9deb20fe-a6e0-355c-81ea-288b009e4f6d', + ], + invalid: [ + '', + 'A987FBC9-4BED-3078-CF07-9141BA07C9F3', + 'xxxA987FBC9-4BED-3078-CF07-9141BA07C9F3', + '934859', + 'AAAAAAAA-1111-1111-AAAG-111111111111', + 'A987FBC9-4BED-4078-8F07-9141BA07C9F3', + 'A987FBC9-4BED-5078-AF07-9141BA07C9F3', + '018C544A-D384-7000-BB74-3B1738ABE43C', + ], + }); + test({ + validator: 'isUUID', + args: [4], + valid: [ + '713ae7e3-cb32-45f9-adcb-7c4fa86b90c1', + '625e63f3-58f5-40b7-83a1-a72ad31acffb', + '57b73598-8764-4ad0-a76a-679bb6640eb1', + '9c858901-8a57-4791-81fe-4c455b099bc9', + ], + invalid: [ + '', + 'xxxA987FBC9-4BED-3078-CF07-9141BA07C9F3', + '934859', + 'AAAAAAAA-1111-1111-AAAG-111111111111', + 'A987FBC9-4BED-5078-AF07-9141BA07C9F3', + 'A987FBC9-4BED-3078-CF07-9141BA07C9F3', + '018C544A-D384-7000-BB74-3B1738ABE43C', + ], + }); + test({ + validator: 'isUUID', + args: [5], + valid: [ + '987FBC97-4BED-5078-AF07-9141BA07C9F3', + '987FBC97-4BED-5078-BF07-9141BA07C9F3', + '987FBC97-4BED-5078-8F07-9141BA07C9F3', + '987FBC97-4BED-5078-9F07-9141BA07C9F3', + ], + invalid: [ + '', + 'xxxA987FBC9-4BED-3078-CF07-9141BA07C9F3', + '934859', + 'AAAAAAAA-1111-1111-AAAG-111111111111', + '9c858901-8a57-4791-81fe-4c455b099bc9', + 'A987FBC9-4BED-3078-CF07-9141BA07C9F3', + '018C544A-D384-7000-BB74-3B1738ABE43C', + ], + }); + test({ + validator: 'isUUID', + args: [6], + valid: [ + '1ef29908-cde1-69d0-be16-bfc8518a95f0', + ], + invalid: [ + '987FBC97-4BED-1078-AF07-9141BA07C9F3', + '987FBC97-4BED-2078-AF07-9141BA07C9F3', + '987FBC97-4BED-3078-AF07-9141BA07C9F3', + '987FBC97-4BED-4078-AF07-9141BA07C9F3', + '987FBC97-4BED-5078-AF07-9141BA07C9F3', + '018C544A-D384-7000-BB74-3B1738ABE43C', + '987FBC97-4BED-8078-AF07-9141BA07C9F3', + ], + }); + test({ + validator: 'isUUID', + args: [7], + valid: [ + '018C544A-D384-7000-BB74-3B1738ABE43C', + ], + invalid: [ + '', + 'xxxA987FBC9-4BED-3078-CF07-9141BA07C9F3', + '934859', + 'AAAAAAAA-1111-1111-AAAG-111111111111', + 'A987FBC9-4BED-5078-AF07-9141BA07C9F3', + 'A987FBC9-4BED-3078-CF07-9141BA07C9F3', + 'A987FBC9-4BED-6078-AF07-9141BA07C9F3', + 'A987FBC9-4BED-8078-AF07-9141BA07C9F3', + '713ae7e3-cb32-45f9-adcb-7c4fa86b90c1', + '625e63f3-58f5-40b7-83a1-a72ad31acffb', + '57b73598-8764-4ad0-a76a-679bb6640eb1', + '9c858901-8a57-4791-81fe-4c455b099bc9', + ], + }); + test({ + validator: 'isUUID', + args: [8], + valid: [ + '018C544A-D384-8000-BB74-3B1738ABE43C', + ], + invalid: [ + '', + 'xxxA987FBC9-4BED-3078-CF07-9141BA07C9F3', + '934859', + 'AAAAAAAA-1111-1111-AAAG-111111111111', + 'A987FBC9-4BED-5078-AF07-9141BA07C9F3', + 'A987FBC9-4BED-3078-CF07-9141BA07C9F3', + 'A987FBC9-4BED-6078-AF07-9141BA07C9F3', + 'A987FBC9-4BED-7078-AF07-9141BA07C9F3', + '713ae7e3-cb32-45f9-adcb-7c4fa86b90c1', + '625e63f3-58f5-40b7-83a1-a72ad31acffb', + '57b73598-8764-4ad0-a76a-679bb6640eb1', + '9c858901-8a57-4791-81fe-4c455b099bc9', + ], + }); + test({ + validator: 'isUUID', + args: ['nil'], + valid: [ + '00000000-0000-0000-0000-000000000000', + ], + invalid: [ + '', + 'xxxA987FBC9-4BED-3078-CF07-9141BA07C9F3', + 'A987FBC9-4BED-3078-CF07-9141BA07C9F3', + 'A987FBC9-4BED-3078-CF07-9141BA07C9F3xxx', + 'A987FBC94BED3078CF079141BA07C9F3', + '934859', + '987FBC9-4BED-3078-CF07A-9141BA07C9F3', + 'AAAAAAAA-1111-1111-AAAG-111111111111', + '9deb20fe-a6e0-355c-81ea-288b009e4f6d', + 'A987FBC9-4BED-4078-8F07-9141BA07C9F3', + 'A987FBC9-4BED-5078-AF07-9141BA07C9F3', + 'A987FBC9-4BED-6078-AF07-9141BA07C9F3', + '018C544A-D384-7000-BB74-3B1738ABE43C', + 'A987FBC9-4BED-8078-AF07-9141BA07C9F3', + 'ffffffff-ffff-ffff-ffff-ffffffffffff', + 'FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF', + ], + }); + test({ + validator: 'isUUID', + args: ['max'], + valid: [ + 'ffffffff-ffff-ffff-ffff-ffffffffffff', + 'FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF', + ], + invalid: [ + '', + 'xxxA987FBC9-4BED-3078-CF07-9141BA07C9F3', + 'A987FBC9-4BED-3078-CF07-9141BA07C9F3', + 'A987FBC9-4BED-3078-CF07-9141BA07C9F3xxx', + 'A987FBC94BED3078CF079141BA07C9F3', + '934859', + '987FBC9-4BED-3078-CF07A-9141BA07C9F3', + 'AAAAAAAA-1111-1111-AAAG-111111111111', + '9deb20fe-a6e0-355c-81ea-288b009e4f6d', + 'A987FBC9-4BED-4078-8F07-9141BA07C9F3', + 'A987FBC9-4BED-5078-AF07-9141BA07C9F3', + 'A987FBC9-4BED-6078-AF07-9141BA07C9F3', + '018C544A-D384-7000-BB74-3B1738ABE43C', + 'A987FBC9-4BED-8078-AF07-9141BA07C9F3', + '00000000-0000-0000-0000-000000000000', + ], + }); + test({ + validator: 'isUUID', + args: ['loose'], + valid: [ + '9deb20fe-a6e0-355c-81ea-288b009e4f6d', + 'A987FBC9-4BED-3078-CF07-9141BA07C9F3', + 'A987FBC9-4BED-4078-8F07-9141BA07C9F3', + 'A987FBC9-4BED-5078-AF07-9141BA07C9F3', + 'A987FBC9-4BED-6078-AF07-9141BA07C9F3', + '018C544A-D384-7000-BB74-3B1738ABE43C', + 'A987FBC9-4BED-8078-AF07-9141BA07C9F3', + 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', + 'AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA', + 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee', + 'EEEEEEEE-EEEE-EEEE-EEEE-EEEEEEEEEEEE', + '99999999-9999-9999-9999-999999999999', + ], + invalid: [ + '', + 'xxxA987FBC9-4BED-3078-CF07-9141BA07C9F3', + 'A987FBC9-4BED-3078-CF07-9141BA07C9F3xxx', + 'A987FBC94BED3078CF079141BA07C9F3', + '987FBC9-4BED-3078-CF07A-9141BA07C9F3', + '934859', + 'AAAAAAAA-1111-1111-AAAG-111111111111', + ], + }); + test({ + validator: 'isUUID', + args: ['all'], + valid: [ + '9deb20fe-a6e0-355c-81ea-288b009e4f6d', + 'A987FBC9-4BED-4078-8F07-9141BA07C9F3', + 'A987FBC9-4BED-5078-AF07-9141BA07C9F3', + 'A987FBC9-4BED-6078-AF07-9141BA07C9F3', + '018C544A-D384-7000-BB74-3B1738ABE43C', + 'A987FBC9-4BED-8078-AF07-9141BA07C9F3', + '00000000-0000-0000-0000-000000000000', + 'ffffffff-ffff-ffff-ffff-ffffffffffff', + 'FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF', + ], + invalid: [ + '', + 'xxxA987FBC9-4BED-3078-CF07-9141BA07C9F3', + 'A987FBC9-4BED-3078-CF07-9141BA07C9F3', + 'A987FBC9-4BED-3078-CF07-9141BA07C9F3xxx', + 'A987FBC94BED3078CF079141BA07C9F3', + '934859', + '987FBC9-4BED-3078-CF07A-9141BA07C9F3', + 'AAAAAAAA-1111-1111-AAAG-111111111111', + ], + }); + test({ + validator: 'isUUID', + args: ['invalid'], + valid: [], + invalid: [ + '', + 'xxxA987FBC9-4BED-3078-CF07-9141BA07C9F3', + 'A987FBC9-4BED-3078-CF07-9141BA07C9F3', + 'A987FBC9-4BED-3078-CF07-9141BA07C9F3xxx', + 'A987FBC94BED3078CF079141BA07C9F3', + '934859', + '987FBC9-4BED-3078-CF07A-9141BA07C9F3', + 'AAAAAAAA-1111-1111-AAAG-111111111111', + '9deb20fe-a6e0-355c-81ea-288b009e4f6d', + 'A987FBC9-4BED-4078-8F07-9141BA07C9F3', + 'A987FBC9-4BED-5078-AF07-9141BA07C9F3', + 'A987FBC9-4BED-6078-AF07-9141BA07C9F3', + '018C544A-D384-7000-BB74-3B1738ABE43C', + 'A987FBC9-4BED-8078-AF07-9141BA07C9F3', + '00000000-0000-0000-0000-000000000000', + 'ffffffff-ffff-ffff-ffff-ffffffffffff', + 'FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF', + ], + }); + }); + + it('should validate a string that is in another string or array', () => { + test({ + validator: 'isIn', + args: ['foobar'], + valid: ['foo', 'bar', 'foobar', ''], + invalid: ['foobarbaz', 'barfoo'], + }); + test({ + validator: 'isIn', + args: [['foo', 'bar']], + valid: ['foo', 'bar'], + invalid: ['foobar', 'barfoo', ''], + }); + test({ + validator: 'isIn', + args: [['1', '2', '3']], + valid: ['1', '2', '3'], + invalid: ['4', ''], + }); + test({ + validator: 'isIn', + args: [['1', '2', '3', { foo: 'bar' }, () => 5, { toString: 'test' }]], + valid: ['1', '2', '3', ''], + invalid: ['4'], + }); + test({ validator: 'isIn', invalid: ['foo', ''] }); + }); + + it('should validate a string that is in another object', () => { + test({ + validator: 'isIn', + args: [{ foo: 1, bar: 2, foobar: 3 }], + valid: ['foo', 'bar', 'foobar'], + invalid: ['foobarbaz', 'barfoo', ''], + }); + test({ + validator: 'isIn', + args: [{ 1: 3, 2: 0, 3: 1 }], + valid: ['1', '2', '3'], + invalid: ['4', ''], + }); + }); + + it('should validate ABA routing number', () => { + test({ + validator: 'isAbaRouting', + valid: [ + '322070381', + '011103093', + '263170175', + '124303065', + ], + invalid: [ + '426317017', + '789456124', + '603558459', + 'qwerty', + '12430306', + '382070381', + ], + }); + }); + + it('should validate IBAN', () => { + test({ + validator: 'isIBAN', + valid: [ + 'SC52BAHL01031234567890123456USD', + 'LC14BOSL123456789012345678901234', + 'MT31MALT01100000000000000000123', + 'SV43ACAT00000000000000123123', + 'EG800002000156789012345180002', + 'BE71 0961 2345 6769', + 'FR76 3000 6000 0112 3456 7890 189', + 'DE91 1000 0000 0123 4567 89', + 'GR96 0810 0010 0000 0123 4567 890', + 'RO09 BCYP 0000 0012 3456 7890', + 'SA44 2000 0001 2345 6789 1234', + 'ES79 2100 0813 6101 2345 6789', + 'CH56 0483 5012 3456 7800 9', + 'GB98 MIDL 0700 9312 3456 78', + 'IL170108000000012612345', + 'IT60X0542811101000000123456', + 'JO71CBJO0000000000001234567890', + 'TR320010009999901234567890', + 'BR1500000000000010932840814P2', + 'LB92000700000000123123456123', + 'IR200170000000339545727003', + 'MZ97123412341234123412341', + 'MA64011519000001205000534921', + 'VG96VPVG0000012345678901', + 'DZ580002100001113000000570', + 'IE29AIBK93115212345678', + 'PS92PALS000000000400123456702', + 'PS92PALS00000000040012345670O', + ], + invalid: [ + 'XX22YYY1234567890123', + 'FR14 2004 1010 0505 0001 3', + 'FR7630006000011234567890189@', + 'FR7630006000011234567890189😅', + 'FR763000600001123456!!🤨7890189@', + 'VG46H07Y0223060094359858', + 'IE95TE8270900834048660', + 'PS072435171802145240705922007', + ], + }); + test({ + validator: 'isIBAN', + args: [{ whitelist: ['DK', 'GB'] }], + valid: [ + 'DK5000400440116243', + 'GB29NWBK60161331926819', + ], + invalid: [ + 'BE71 0961 2345 6769', + 'FR76 3000 6000 0112 3456 7890 189', + 'DE91 1000 0000 0123 4567 89', + 'GR96 0810 0010 0000 0123 4567 890', + 'RO09 BCYP 0000 0012 3456 7890', + 'SA44 2000 0001 2345 6789 1234', + 'ES79 2100 0813 6101 2345 6789', + 'XX22YYY1234567890123', + 'FR14 2004 1010 0505 0001 3', + 'FR7630006000011234567890189@', + 'FR7630006000011234567890189😅', + 'FR763000600001123456!!🤨7890189@', + ], + }); + test({ + validator: 'isIBAN', + args: [{ whitelist: ['XX', 'AA'] }], + invalid: [ + 'DK5000400440116243', + 'GB29NWBK60161331926819', + 'BE71 0961 2345 6769', + 'FR76 3000 6000 0112 3456 7890 189', + 'DE91 1000 0000 0123 4567 89', + 'GR96 0810 0010 0000 0123 4567 890', + 'RO09 BCYP 0000 0012 3456 7890', + 'SA44 2000 0001 2345 6789 1234', + 'ES79 2100 0813 6101 2345 6789', + 'XX22YYY1234567890123', + 'FR14 2004 1010 0505 0001 3', + 'FR7630006000011234567890189@', + 'FR7630006000011234567890189😅', + 'FR763000600001123456!!🤨7890189@', + ], + }); + test({ + validator: 'isIBAN', + args: [{ blacklist: ['IT'] }], + valid: [ + 'SC52BAHL01031234567890123456USD', + 'LC14BOSL123456789012345678901234', + 'MT31MALT01100000000000000000123', + 'SV43ACAT00000000000000123123', + 'EG800002000156789012345180002', + 'BE71 0961 2345 6769', + 'FR76 3000 6000 0112 3456 7890 189', + 'DE91 1000 0000 0123 4567 89', + 'GR96 0810 0010 0000 0123 4567 890', + 'RO09 BCYP 0000 0012 3456 7890', + 'SA44 2000 0001 2345 6789 1234', + 'ES79 2100 0813 6101 2345 6789', + 'CH56 0483 5012 3456 7800 9', + 'GB98 MIDL 0700 9312 3456 78', + 'IL170108000000012612345', + 'JO71CBJO0000000000001234567890', + 'TR320010009999901234567890', + 'BR1500000000000010932840814P2', + 'LB92000700000000123123456123', + 'IR200170000000339545727003', + 'MZ97123412341234123412341', + ], + invalid: [ + 'XX22YYY1234567890123', + 'FR14 2004 1010 0505 0001 3', + 'FR7630006000011234567890189@', + 'FR7630006000011234567890189😅', + 'FR763000600001123456!!🤨7890189@', + 'IT60X0542811101000000123456', + ], + }); + }); + + it('should validate BIC codes', () => { + test({ + validator: 'isBIC', + valid: [ + 'SBICKEN1345', + 'SBICKEN1', + 'SBICKENY', + 'SBICKEN1YYP', + 'SBICXKN1YYP', + ], + invalid: [ + 'SBIC23NXXX', + 'S23CKENXXXX', + 'SBICKENXX', + 'SBICKENXX9', + 'SBICKEN13458', + 'SBICKEN', + 'SBICXK', + ], + }); + }); + + it('should validate that integer strings are divisible by a number', () => { + test({ + validator: 'isDivisibleBy', + args: [2], + valid: ['2', '4', '100', '1000'], + invalid: [ + '1', + '2.5', + '101', + 'foo', + '', + '2020-01-06T14:31:00.135Z', + ], + }); + }); + + it('should validate luhn numbers', () => { + test({ + validator: 'isLuhnNumber', + valid: [ + '0', + '5421', + '01234567897', + '0123456789012345678906', + '0123456789012345678901234567891', + '123456789012345678906', + '375556917985515', + '36050234196908', + '4716461583322103', + '4716-2210-5188-5662', + '4929 7226 5379 7141', + ], + invalid: [ + '', + '1', + '5422', + 'foo', + 'prefix6234917882863855', + '623491788middle2863855', + '6234917882863855suffix', + ], + }); + }); + + it('should validate credit cards', () => { + test({ + validator: 'isCreditCard', + valid: [ + '375556917985515', + '36050234196908', + '4716461583322103', + '4716-2210-5188-5662', + '4929 7226 5379 7141', + '5398228707871527', + '6283875070985593', + '6263892624162870', + '6234917882863855', + '6234698580215388', + '6226050967750613', + '6246281879460688', + '2222155765072228', + '2225855203075256', + '2720428011723762', + '2718760626256570', + '6765780016990268', + '4716989580001715211', + '8171999927660000', + '8171999900000000021', + ], + invalid: [ + 'foo', + 'foo', + '5398228707871528', + '2718760626256571', + '2721465526338453', + '2220175103860763', + '375556917985515999999993', + '899999996234917882863855', + 'prefix6234917882863855', + '623491788middle2863855', + '6234917882863855suffix', + '4716989580001715213', + ], + }); + }); + + + it('should validate credit cards without a proper provider', () => { + test({ + validator: 'isCreditCard', + args: [{ provider: 'Plorf' }], + error: [ + 'foo', + // valid cc # + '375556917985515', + '4716-2210-5188-5662', + '375556917985515999999993', + '6234917882863855suffix', + ], + }); + }); + + + it('should validate AmEx provided credit cards', () => { + test({ + validator: 'isCreditCard', + args: [{ provider: 'AmEx' }], + valid: [ + '375556917985515', + ], + invalid: [ + 'foo', + '2222155765072228', + '2225855203075256', + '2720428011723762', + '2718760626256570', + '36050234196908', + '375556917985515999999993', + '4716461583322103', + '4716-2210-5188-5662', + '4716989580001715211', + '4929 7226 5379 7141', + '5398228707871527', + '6234917882863855suffix', + '6283875070985593', + '6263892624162870', + '6234917882863855', + '6234698580215388', + '6226050967750613', + '6246281879460688', + '6283875070985593', + '6765780016990268', + '8171999927660000', + '8171999900000000021', + ], + }); + }); + + + it('should validate Diners Club provided credit cards', () => { + test({ + validator: 'isCreditCard', + args: [{ provider: 'DinersClub' }], + valid: [ + '36050234196908', + ], + invalid: [ + 'foo', + '2222155765072228', + '2225855203075256', + '2720428011723762', + '2718760626256570', + '375556917985515', + '375556917985515999999993', + '4716461583322103', + '4716-2210-5188-5662', + '4716989580001715211', + '4929 7226 5379 7141', + '5398228707871527', + '6234917882863855suffix', + '6283875070985593', + '6263892624162870', + '6234917882863855', + '6234698580215388', + '6226050967750613', + '6246281879460688', + '6283875070985593', + '6765780016990268', + '8171999927660000', + '8171999900000000021', + ], + }); + }); + + it('should validate Discover provided credit cards', () => { + test({ + validator: 'isCreditCard', + args: [{ provider: 'Discover' }], + valid: [ + '6011111111111117', + '6011000990139424', + ], + invalid: [ + 'foo', + '2222155765072228', + '2225855203075256', + '2720428011723762', + '2718760626256570', + '36050234196908', + '375556917985515', + '375556917985515999999993', + '4716461583322103', + '4716-2210-5188-5662', + '4716989580001715211', + '4929 7226 5379 7141', + '5398228707871527', + '6234917882863855suffix', + '6283875070985593', + '6263892624162870', + '6234917882863855', + '6234698580215388', + '6226050967750613', + '6246281879460688', + '6283875070985593', + '6765780016990268', + '8171999927660000', + '8171999900000000021', + ], + }); + }); + + it('should validate JCB provided credit cards', () => { + test({ + validator: 'isCreditCard', + args: [{ provider: 'JCB' }], + valid: [ + '3530111333300000', + '3566002020360505', + ], + invalid: [ + 'foo', + '2222155765072228', + '2225855203075256', + '2720428011723762', + '2718760626256570', + '36050234196908', + '375556917985515', + '375556917985515999999993', + '4716461583322103', + '4716-2210-5188-5662', + '4716989580001715211', + '4929 7226 5379 7141', + '5398228707871527', + '6234917882863855suffix', + '6283875070985593', + '6263892624162870', + '6234917882863855', + '6234698580215388', + '6226050967750613', + '6246281879460688', + '6283875070985593', + '6765780016990268', + '8171999927660000', + '8171999900000000021', + ], + }); + }); + + + it('should validate Mastercard provided credit cards', () => { + test({ + validator: 'isCreditCard', + args: [{ provider: 'Mastercard' }], + valid: [ + '2222155765072228', + '2225855203075256', + '2718760626256570', + '2720428011723762', + '5398228707871527', + ], + invalid: [ + 'foo', + '36050234196908', + '375556917985515', + '375556917985515999999993', + '4716461583322103', + '4716-2210-5188-5662', + '4716989580001715211', + '4929 7226 5379 7141', + '6234917882863855suffix', + '6283875070985593', + '6263892624162870', + '6234917882863855', + '6234698580215388', + '6226050967750613', + '6246281879460688', + '6283875070985593', + '6765780016990268', + '8171999927660000', + '8171999900000000021', + ], + }); + }); + + + it('should validate Union Pay provided credit cards', () => { + test({ + validator: 'isCreditCard', + args: [{ provider: 'UnionPay' }], + valid: [ + '6226050967750613', + '6234917882863855', + '6234698580215388', + '6246281879460688', + '6263892624162870', + '6283875070985593', + '6765780016990268', + '8171999927660000', + '8171999900000000021', + ], + invalid: [ + 'foo', + '2222155765072228', + '2225855203075256', + '2720428011723762', + '2718760626256570', + '36050234196908', + '375556917985515', + '375556917985515999999993', + '4716461583322103', + '4716-2210-5188-5662', + '4716989580001715211', + '4929 7226 5379 7141', + '5398228707871527', + '6234917882863855suffix', + ], + }); + }); + + + it('should validate Visa provided credit cards', () => { + test({ + validator: 'isCreditCard', + args: [{ provider: 'Visa' }], + valid: [ + '4716-2210-5188-5662', + '4716461583322103', + '4716989580001715211', + '4929 7226 5379 7141', + ], + invalid: [ + 'foo', + '2222155765072228', + '2225855203075256', + '2720428011723762', + '2718760626256570', + '36050234196908', + '375556917985515', + '375556917985515999999993', + '5398228707871527', + '6234917882863855suffix', + '6283875070985593', + '6263892624162870', + '6234917882863855', + '6234698580215388', + '6226050967750613', + '6246281879460688', + '6283875070985593', + '6765780016990268', + '8171999927660000', + '8171999900000000021', + ], + }); + }); + + + it('should validate identity cards', () => { + const fixtures = [ + { + locale: 'PK', + valid: [ + '45504-4185771-3', + '39915-6182971-9', + '21143-6182971-2', + '34543-2323471-1', + '72345-2345678-7', + '63456-8765432-8', + '55672-1234567-5', + '21234-9876543-6', + ], + invalid: [ + '08000-1234567-5', + '74321-87654321-1', + '51234-98765-2', + '00000-0000000-0', + '88888-88888888-0', + '99999-9999999-9', + '11111', + ], + }, + { + locale: 'zh-HK', + valid: [ + 'OV290326[A]', + 'Q803337[0]', + 'Z0977986', + 'W520128(7)', + 'A494866[4]', + 'A494866(4)', + 'Z867821A', + 'ag293013(9)', + 'k348609(5)', + ], + invalid: [ + 'A1234567890', + '98765432', + 'O962472(9)', + 'M4578601', + 'X731324[8]', + 'C503134(5)', + 'RH265886(3)', + ], + }, + { + locale: 'LK', + valid: [ + '722222222v', + '722222222V', + '993151225x', + '993151225X', + '188888388x', + '935632124V', + '199931512253', + '200023125632', + ], + invalid: [ + '023125648V', + '023345621v', + '021354211X', + '055321231x', + '02135465462', + '199931512253X', + ], + }, + { + locale: 'PL', + valid: [ + '99012229019', + '09210215408', + '20313034701', + '86051575214', + '77334586883', + '54007481320', + '06566860643', + '77552478861', + ], + invalid: [ + 'aa', + '5', + '195', + '', + ' ', + '12345678901', + '99212229019', + '09210215402', + '20313534701', + '86241579214', + ], + }, + { + locale: 'ES', + valid: [ + '99999999R', + '12345678Z', + '01234567L', + '01234567l', + 'X1234567l', + 'x1234567l', + 'X1234567L', + 'Y1234567X', + 'Z1234567R', + ], + invalid: [ + '123456789', + '12345678A', + '12345 678Z', + '12345678-Z', + '1234*6789', + '1234*678Z', + '12345678!', + '1234567L', + 'A1234567L', + 'X1234567A', + 'Y1234567B', + 'Z1234567C', + ], + }, + { + locale: 'FI', + valid: [ + '131052-308T', // People born in 1900s + '131052A308T', // People born in 2000s + '131052+308T', // People born in 1800s + '131052-313Y', + ], + invalid: [ + '131052308T', + '131052-308T ', + '131052-308A', + ], + }, + { + locale: 'IN', + valid: [ + '298448863364', + '2984 4886 3364', + ], + invalid: [ + '99999999R', + '12345678Z', + '01234567L', + '01234567l', + 'X1234567l', + 'x1234567l', + 'X1234567L', + ], + }, + { + locale: 'IR', + valid: [ + '0499370899', + '0790419904', + '0084575948', + '0963695398', + '0684159414', + '0067749828', + '0650451252', + '1583250689', + '4032152314', + '0076229645', + '4271467685', + '0200203241', + ], + invalid: [ + '1260293040', + '0000000001', + '1999999999', + '9999999991', + 'AAAAAAAAAA', + '0684159415', + ], + }, + { + locale: 'IT', + valid: [ + 'CR43675TM', + 'CA79382RA', + ], + invalid: [ + 'CA00000AA', + 'CB2342TG', + 'CS123456A', + 'C1236EC', + ], + }, + { + locale: 'NO', + valid: [ + '09053426694', + '26028338723', + '08031470790', + '12051539514', + '02077448074', + '14035638319', + '13031379673', + '29126214926', + ], + invalid: [ + '09053426699', + '00000000000', + '26028338724', + '92031470790', + ], + }, + { + locale: 'TH', + valid: [ + '1101230000001', + '1101230000060', + ], + invalid: [ + 'abc', + '1101230', + '11012300000011', + 'aaaaaaaaaaaaa', + '110123abcd001', + '1101230000007', + '0101123450000', + '0101123450004', + '9101123450008', + ], + }, + { + locale: 'he-IL', + valid: [ + '219472156', + '219486610', + '219488962', + '219566726', + '219640216', + '219645041', + '334795465', + '335211686', + '335240479', + '335472171', + '336999842', + '337090443', + ], + invalid: [ + '123456789', + '12345678A', + '12345 678Z', + '12345678-Z', + '1234*6789', + '1234*678Z', + '12345678!', + '1234567L', + 'A1234567L', + 'X1234567A', + 'Y1234567B', + 'Z1234567C', + '219772156', + '219487710', + '334705465', + '336000842', + ], + }, + { + locale: 'ar-LY', + valid: [ + '119803455876', + '120024679875', + '219624876201', + '220103480657', + ], + invalid: [ + '987654320123', + '123-456-7890', + '012345678912', + '1234567890', + 'AFJBHUYTREWR', + 'C4V6B1X0M5T6', + '9876543210123', + ], + }, + { + locale: 'ar-TN', + valid: [ + '09958092', + '09151092', + '65126506', + '79378815', + '58994407', + '73089789', + '73260311', + ], + invalid: [ + '123456789546', + '123456789', + '023456789', + '12345678A', + '12345', + '1234578A', + '123 578A', + '12345 678Z', + '12345678-Z', + '1234*6789', + '1234*678Z', + 'GE9800as98', + 'X231071922', + '1234*678Z', + '12345678!', + ], + }, + { + locale: 'zh-CN', + valid: [ + '235407195106112745', + '210203197503102721', + '520323197806058856', + '110101491001001', + ], + invalid: [ + '160323197806058856', + '010203197503102721', + '520323297806058856', + '520323197802318856', + '235407195106112742', + '010101491001001', + '110101491041001', + '160101491001001', + '110101940231001', + 'xx1234567', + '135407195106112742', + '123456789546', + '123456789', + '023456789', + '12345678A', + '12345', + '1234578A', + '123 578A', + '12345 678Z', + '12345678-Z', + '1234*6789', + '1234*678Z', + 'GE9800as98', + 'X231071922', + '1234*678Z', + '12345678!', + '235407207006112742', + ], + }, + { + locale: 'zh-TW', + valid: [ + 'B176944193', + 'K101189797', + 'F112866121', + 'A219758834', + 'A244144802', + 'A146047171', + 'Q170219004', + 'Z277018381', + 'X231071923', + ], + invalid: [ + '123456789', + 'A185034995', + 'X431071923', + 'GE9800as98', + 'X231071922', + '1234*678Z', + '12345678!', + '1234567L', + 'A1234567L', + 'X1234567A', + 'Y1234567B', + 'Z1234567C', + '219772156', + '219487710', + '334705465', + '336000842', + ], + }, + ]; + + let allValid = []; + + // Test fixtures + fixtures.forEach((fixture) => { + if (fixture.valid) allValid = allValid.concat(fixture.valid); + test({ + validator: 'isIdentityCard', + valid: fixture.valid, + invalid: fixture.invalid, + args: [fixture.locale], + }); + }); + + // Test generics + test({ + validator: 'isIdentityCard', + valid: [ + ...allValid, + ], + invalid: [ + 'foo', + ], + args: ['any'], + }); + }); + + it('should error on invalid locale', () => { + test({ + validator: 'isIdentityCard', + args: ['is-NOT'], + error: [ + '99999999R', + '12345678Z', + ], + }); + }); + + it('should validate ISINs', () => { + test({ + validator: 'isISIN', + valid: [ + 'AU0000XVGZA3', + 'DE000BAY0017', + 'BE0003796134', + 'SG1G55870362', + 'GB0001411924', + 'DE000WCH8881', + 'PLLWBGD00016', + 'US0378331005', + ], + invalid: [ + 'DE000BAY0018', + 'PLLWBGD00019', + 'foo', + '5398228707871528', + ], + }); + }); + + it('should validate EANs', () => { + test({ + validator: 'isEAN', + valid: [ + '9421023610112', + '1234567890128', + '4012345678901', + '9771234567003', + '9783161484100', + '73513537', + '00012345600012', + '10012345678902', + '20012345678909', + ], + invalid: [ + '5901234123451', + '079777681629', + '0705632085948', + ], + }); + }); + + it('should validate ISSNs', () => { + test({ + validator: 'isISSN', + valid: [ + '0378-5955', + '0000-0000', + '2434-561X', + '2434-561x', + '01896016', + '20905076', + ], + invalid: [ + '0378-5954', + '0000-0001', + '0378-123', + '037-1234', + '0', + '2434-561c', + '1684-5370', + '19960791', + '', + ], + }); + test({ + validator: 'isISSN', + args: [{ case_sensitive: true }], + valid: [ + '2434-561X', + '2434561X', + '0378-5955', + '03785955', + ], + invalid: [ + '2434-561x', + '2434561x', + ], + }); + test({ + validator: 'isISSN', + args: [{ require_hyphen: true }], + valid: [ + '2434-561X', + '2434-561x', + '0378-5955', + ], + invalid: [ + '2434561X', + '2434561x', + '03785955', + ], + }); + test({ + validator: 'isISSN', + args: [{ case_sensitive: true, require_hyphen: true }], + valid: [ + '2434-561X', + '0378-5955', + ], + invalid: [ + '2434-561x', + '2434561X', + '2434561x', + '03785955', + ], + }); + }); + + it('should validate JSON', () => { + test({ + validator: 'isJSON', + valid: [ + '{ "key": "value" }', + '{}', + ], + invalid: [ + '{ key: "value" }', + '{ \'key\': \'value\' }', + 'null', + '1234', + '"nope"', + ], + }); + }); + + it('should validate JSON with primitives', () => { + test({ + validator: 'isJSON', + args: [{ allow_primitives: true }], + valid: [ + '{ "key": "value" }', + '{}', + 'null', + 'false', + 'true', + ], + invalid: [ + '{ key: "value" }', + '{ \'key\': \'value\' }', + '{ "key": value }', + '1234', + '"nope"', + ], + }); + }); + + it('should validate multibyte strings', () => { + test({ + validator: 'isMultibyte', + valid: [ + 'ひらがな・カタカナ、.漢字', + 'あいうえお foobar', + 'test@example.com', + '1234abcDExyz', + 'カタカナ', + '中文', + ], + invalid: [ + 'abc', + 'abc123', + '<>@" *.', + ], + }); + }); + + it('should validate ascii strings', () => { + test({ + validator: 'isAscii', + valid: [ + 'foobar', + '0987654321', + 'test@example.com', + '1234abcDEF', + ], + invalid: [ + 'foobar', + 'xyz098', + '123456', + 'カタカナ', + ], + }); + }); + + it('should validate full-width strings', () => { + test({ + validator: 'isFullWidth', + valid: [ + 'ひらがな・カタカナ、.漢字', + '3ー0 a@com', + 'Fカタカナ゙ᆲ', + 'Good=Parts', + ], + invalid: [ + 'abc', + 'abc123', + '!"#$%&()<>/+=-_? ~^|.,@`{}[]', + ], + }); + }); + + it('should validate half-width strings', () => { + test({ + validator: 'isHalfWidth', + valid: [ + '!"#$%&()<>/+=-_? ~^|.,@`{}[]', + 'l-btn_02--active', + 'abc123い', + 'カタカナ゙ᆲ←', + ], + invalid: [ + 'あいうえお', + '0011', + ], + }); + }); + + it('should validate variable-width strings', () => { + test({ + validator: 'isVariableWidth', + valid: [ + 'ひらがなカタカナ漢字ABCDE', + '3ー0123', + 'Fカタカナ゙ᆲ', + 'Good=Parts', + ], + invalid: [ + 'abc', + 'abc123', + '!"#$%&()<>/+=-_? ~^|.,@`{}[]', + 'ひらがな・カタカナ、.漢字', + '123456', + 'カタカナ゙ᆲ', + ], + }); + }); + + it('should validate surrogate pair strings', () => { + test({ + validator: 'isSurrogatePair', + valid: [ + '𠮷野𠮷', + '𩸽', + 'ABC千𥧄1-2-3', + ], + invalid: [ + '吉野竈', + '鮪', + 'ABC1-2-3', + ], + }); + }); + + it('should validate Semantic Versioning Specification (SemVer) strings', () => { + test({ + validator: 'isSemVer', + valid: [ + '0.0.4', + '1.2.3', + '10.20.30', + '1.1.2-prerelease+meta', + '1.1.2+meta', + '1.1.2+meta-valid', + '1.0.0-alpha', + '1.0.0-beta', + '1.0.0-alpha.beta', + '1.0.0-alpha.beta.1', + '1.0.0-alpha.1', + '1.0.0-alpha0.valid', + '1.0.0-alpha.0valid', + '1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay', + '1.0.0-rc.1+build.1', + '2.0.0-rc.1+build.123', + '1.2.3-beta', + '10.2.3-DEV-SNAPSHOT', + '1.2.3-SNAPSHOT-123', + '1.0.0', + '2.0.0', + '1.1.7', + '2.0.0+build.1848', + '2.0.1-alpha.1227', + '1.0.0-alpha+beta', + '1.2.3----RC-SNAPSHOT.12.9.1--.12+788', + '1.2.3----R-S.12.9.1--.12+meta', + '1.2.3----RC-SNAPSHOT.12.9.1--.12', + '1.0.0+0.build.1-rc.10000aaa-kk-0.1', + '99999999999999999999999.999999999999999999.99999999999999999', + '1.0.0-0A.is.legal', + ], + invalid: [ + '-invalid+invalid', + '-invalid.01', + 'alpha', + 'alpha.beta', + 'alpha.beta.1', + 'alpha.1', + 'alpha+beta', + 'alpha_beta', + 'alpha.', + 'alpha..', + 'beta', + '1.0.0-alpha_beta', + '-alpha.', + '1.0.0-alpha..', + '1.0.0-alpha..1', + '1.0.0-alpha...1', + '1.0.0-alpha....1', + '1.0.0-alpha.....1', + '1.0.0-alpha......1', + '1.0.0-alpha.......1', + '01.1.1', + '1.01.1', + '1.1.01', + '1.2', + '1.2.3.DEV', + '1.2-SNAPSHOT', + '1.2.31.2.3----RC-SNAPSHOT.12.09.1--..12+788', + '1.2-RC-SNAPSHOT', + '-1.0.3-gamma+b7718', + '+justmeta', + '9.8.7+meta+meta', + '9.8.7-whatever+meta+meta', + '99999999999999999999999.999999999999999999.99999999999999999-', + '---RC-SNAPSHOT.12.09.1--------------------------------..12', + ], + }); + }); + + it('should validate base32 strings', () => { + test({ + validator: 'isBase32', + valid: [ + 'ZG======', + 'JBSQ====', + 'JBSWY===', + 'JBSWY3A=', + 'JBSWY3DP', + 'JBSWY3DPEA======', + 'K5SWYY3PNVSSA5DPEBXG6ZA=', + 'K5SWYY3PNVSSA5DPEBXG6===', + ], + invalid: [ + '12345', + '', + 'JBSWY3DPtesting123', + 'ZG=====', + 'Z======', + 'Zm=8JBSWY3DP', + '=m9vYg==', + 'Zm9vYm/y====', + ], + }); + }); + + it('should validate base32 strings with crockford alternative', () => { + test({ + validator: 'isBase32', + args: [{ crockford: true }], + valid: [ + '91JPRV3F41BPYWKCCGGG', + '60', + '64', + 'B5QQA833C5Q20S3F41MQ8', + ], + invalid: [ + '91JPRV3F41BUPYWKCCGGG', + 'B5QQA833C5Q20S3F41MQ8L', + '60I', + 'B5QQA833OULIC5Q20S3F41MQ8', + ], + }); + }); + + it('should validate base58 strings', () => { + test({ + validator: 'isBase58', + valid: [ + 'BukQL', + '3KMUV89zab', + '91GHkLMNtyo98', + 'YyjKm3H', + 'Mkhss145TRFg', + '7678765677', + 'abcodpq', + 'AAVHJKLPY', + ], + invalid: [ + '0OPLJH', + 'IMKLP23', + 'KLMOmk986', + 'LL1l1985hG', + '*MP9K', + 'Zm=8JBSWY3DP', + ')()(=9292929MKL', + ], + }); + }); + + + it('should validate hex-encoded MongoDB ObjectId', () => { + test({ + validator: 'isMongoId', + valid: [ + '507f1f77bcf86cd799439011', + ], + invalid: [ + '507f1f77bcf86cd7994390', + '507f1f77bcf86cd79943901z', + '', + '507f1f77bcf86cd799439011 ', + ], + }); + }); + + it('should define the module using an AMD-compatible loader', () => { + let window = { + validator: null, + define(module) { + window.validator = module(); + }, + }; + window.define.amd = true; + + let sandbox = vm.createContext(window); + vm.runInContext(validator_js, sandbox); + assert.strictEqual(window.validator.trim(' foobar '), 'foobar'); + }); + + it('should bind validator to the window if no module loaders are available', () => { + let window = {}; + let sandbox = vm.createContext(window); + vm.runInContext(validator_js, sandbox); + assert.strictEqual(window.validator.trim(' foobar '), 'foobar'); + }); + + it('should validate mobile phone number', () => { + let fixtures = [ + { + locale: 'am-AM', + valid: [ + '+37433123456', + '+37441123456', + '+37443123456', + '+37444123456', + '+37455123456', + '+37477123456', + '+37488123456', + '+37491123456', + '+37493123456', + '+37494123456', + '+37495123456', + '+37496123456', + '+37498123456', + '+37499123456', + '055123456', + '37455123456', + ], + invalid: [ + '12345', + '+37403498855', + '+37416498123', + '05614988556', + '', + '37456789000', + '37486789000', + '+37431312345', + '+37430312345', + '+37460123456', + '+37410324123', + '+37422298765', + '+37431276521', + '022698763', + '+37492123456', + ], + }, + { + locale: 'ar-AE', + valid: [ + '+971502674453', + '+971521247658', + '+971541255684', + '+971555454458', + '+971561498855', + '+971585215778', + '971585215778', + '0585215778', + '585215778', + ], + invalid: [ + '12345', + '+971511498855', + '+9715614988556', + '+9745614988556', + '', + '+9639626626262', + '+963332210972', + '0114152198', + '962796477263', + ], + }, + { + locale: 'ar-BH', + valid: [ + '+97335078110', + '+97339534385', + '+97366331055', + '+97333146000', + '97335078110', + '35078110', + '66331055', + ], + invalid: [ + '12345', + '+973350781101', + '+97379534385', + '+973035078110', + '', + '+9639626626262', + '+963332210972', + '0114152198', + '962796477263', + '035078110', + '16331055', + 'hello', + '+9733507811a', + ], + }, + { + locale: 'ar-EG', + valid: [ + '+201004513789', + '+201111453489', + '+201221204610', + '+201144621154', + '+201200124304', + '+201011201564', + '+201124679001', + '+201064790156', + '+201274652177', + '+201280134679', + '+201090124576', + '+201583728900', + '201599495596', + '201090124576', + '01090124576', + '01538920744', + '1593075993', + '1090124576', + ], + invalid: [ + '+221004513789', + '+201404513789', + '12345', + '', + '+9639626626262', + '+963332210972', + '0114152198', + '962796477263', + ], + }, + { + locale: 'ar-JO', + valid: [ + '0796477263', + '0777866254', + '0786725261', + '+962796477263', + '+962777866254', + '+962786725261', + '962796477263', + '962777866254', + '962786725261', + ], + invalid: [ + '00962786725261', + '00962796477263', + '12345', + '', + '+9639626626262', + '+963332210972', + '0114152198', + ], + }, + { + locale: 'ar-KW', + valid: [ + '96550000000', + '96560000000', + '96590000000', + '96541000000', + '+96550000000', + '+96550000220', + '+96551111220', + '+96541000000', + ], + invalid: [ + '+96570000220', + '00962786725261', + '00962796477263', + '12345', + '', + '+9639626626262', + '+963332210972', + '0114152198', + '+96540000000', + ], + }, + { + locale: 'ar-LB', + valid: [ + '+96171234568', + '+9613123456', + '3456123', + '3123456', + '81978468', + '77675798', + ], + invalid: [ + '+961712345688888', + '00912220000', + '7767579888', + '+0921110000', + '+3123456888', + '021222200000', + '213333444444', + '', + '+212234', + '+21', + '02122333', + ], + }, + { + locale: 'ar-LY', + valid: [ + '912220000', + '0923330000', + '218945550000', + '+218958880000', + '212220000', + '0212220000', + '+218212220000', + ], + invalid: [ + '9122220000', + '00912220000', + '09211110000', + '+0921110000', + '+2180921110000', + '021222200000', + '213333444444', + '', + '+212234', + '+21', + '02122333', + ], + }, + { + locale: 'ar-MA', + valid: [ + '0522714782', + '0690851123', + '0708186135', + '+212522714782', + '+212690851123', + '+212708186135', + '00212522714782', + '00212690851123', + '00212708186135', + ], + invalid: [ + '522714782', + '690851123', + '708186135', + '212522714782', + '212690851123', + '212708186135', + '0212522714782', + '0212690851123', + '0212708186135', + '', + '12345', + '0922714782', + '+212190851123', + '00212408186135', + ], + }, + { + locale: 'dz-BT', + valid: [ + '+97517374354', + '+97517454971', + '77324646', + '016329712', + '97517265559', + ], + invalid: [ + '', + '9898347255', + '+96326626262', + '963372', + '0114152198', + ], + }, + { + locale: 'ar-OM', + valid: [ + '+96891212121', + '+96871212121', + '0096899999999', + '93112211', + '99099009', + ], + invalid: [ + '+96890212121', + '0096890999999', + '0090999999', + '+9689021212', + '', + '+212234', + '+21', + '02122333', + ], + }, + { + locale: 'ar-PS', + valid: [ + '+970563459876', + '970592334218', + '0566372345', + '0598273583', + ], + invalid: [ + '+9759029487', + '97059123456789', + '598372348', + '97058aaaafjd', + '', + '05609123484', + '+97059', + '+970', + '97056', + ], + }, + { + locale: 'ar-SY', + valid: [ + '0944549710', + '+963944549710', + '956654379', + '0944549710', + '0962655597', + ], + invalid: [ + '12345', + '', + '+9639626626262', + '+963332210972', + '0114152198', + ], + }, + { + locale: 'ar-SA', + valid: [ + '0556578654', + '+966556578654', + '966556578654', + '596578654', + '572655597', + ], + invalid: [ + '12345', + '', + '+9665626626262', + '+96633221097', + '0114152198', + ], + }, + { + locale: 'ar-SD', + valid: [ + '0128652312', + '+249919425113', + '249123212345', + '0993212345', + ], + invalid: [ + '12345', + '', + '+249972662622', + '+24946266262', + '+24933221097', + '0614152198', + '096554', + ], + }, + { + locale: 'ar-TN', + valid: [ + '23456789', + '+21623456789', + '21623456789', + ], + invalid: [ + '12345', + '75200123', + '+216512345678', + '13520459', + '85479520', + ], + }, + { + locale: 'bg-BG', + valid: [ + '+359897123456', + '+359898888888', + '0897123123', + ], + invalid: [ + '', + '0898123', + '+359212555666', + '18001234567', + '12125559999', + ], + }, + { + locale: 'bn-BD', + valid: [ + '+8801794626846', + '01399098893', + '8801671163269', + '01717112029', + '8801898765432', + '+8801312345678', + '01494676946', + ], + invalid: [ + '', + '0174626346', + '017943563469', + '18001234567', + '0131234567', + ], + }, + { + locale: 'bs-BA', + valid: [ + '060123456', + '061123456', + '062123456', + '063123456', + '0641234567', + '065123456', + '066123456', + '+38760123456', + '+38761123456', + '+38762123456', + '+38763123456', + '+387641234567', + '+38765123456', + '+38766123456', + '0038760123456', + '0038761123456', + '0038762123456', + '0038763123456', + '00387641234567', + '0038765123456', + '0038766123456', + ], + invalid: [ + '0601234567', + '0611234567', + '06212345', + '06312345', + '064123456', + '0651234567', + '06612345', + '+3866123456', + '+3856123456', + '00038760123456', + '038761123456', + ], + }, + { + locale: 'cs-CZ', + valid: [ + '+420 123 456 789', + '+420 123456789', + '+420123456789', + '123 456 789', + '123456789', + ], + invalid: [ + '', + '+42012345678', + '+421 123 456 789', + '+420 023456789', + '+4201234567892', + ], + }, + { + locale: 'sk-SK', + valid: [ + '+421 123 456 789', + '+421 123456789', + '+421123456789', + '123 456 789', + '123456789', + ], + invalid: [ + '', + '+42112345678', + '+422 123 456 789', + '+421 023456789', + '+4211234567892', + ], + }, + { + locale: 'de-DE', + valid: [ + '+4915123456789', + '015123456789', + '015123456789', + '015623456789', + '015623456789', + '01601234567', + '016012345678', + '01621234567', + '01631234567', + '01701234567', + '017612345678', + ], + invalid: [ + '+4930405044550', + '34412345678', + '14412345678', + '16212345678', + '1761234567', + '16412345678', + '17012345678', + '+4912345678910', + '+49015123456789', + '015345678910', + '015412345678', + ], + }, + { + locale: 'de-AT', + valid: [ + '+436761234567', + '06761234567', + '00436123456789', + '+436123456789', + '01999', + '+4372876', + '06434908989562345', + ], + invalid: [ + '167612345678', + '1234', + '064349089895623459', + ], + }, + { + locale: 'hu-HU', + valid: [ + '06301234567', + '+36201234567', + '06701234567', + ], + invalid: [ + '1234', + '06211234567', + '+3620123456', + ], + }, + { + locale: 'mz-MZ', + valid: [ + '+258849229754', + '258849229754', + '849229754', + '829229754', + '839229754', + '869229754', + '859229754', + '869229754', + '879229754', + '+258829229754', + '+258839229754', + '+258869229754', + '+258859229754', + '+258869229754', + '+258879229754', + '258829229754', + '258839229754', + '258869229754', + '258859229754', + '258869229754', + '258879229754', + ], + invalid: [ + '+248849229754', + '158849229754', + '249229754', + '819229754', + '899229754', + '889229754', + '89229754', + '8619229754', + '87922975411', + '257829229754', + '+255839229754', + '+2258869229754', + '+1258859229754', + '+2588692297541', + '+2588792519754', + '25882922975411', + ], + }, + { + locale: 'pt-BR', + valid: [ + '+55 12 996551215', + '+55 15 97661234', + '+55 (12) 996551215', + '+55 (15) 97661234', + '55 (17) 96332-2155', + '55 (17) 6332-2155', + '55 15 976612345', + '55 15 75661234', + '+5512984567890', + '+551283456789', + '5512984567890', + '551283456789', + '015994569878', + '01593456987', + '022995678947', + '02299567894', + '(22)99567894', + '(22)9956-7894', + '(22) 99567894', + '(22) 9956-7894', + '(22)999567894', + '(22)99956-7894', + '(22) 999567894', + '(22) 99956-7894', + '(11) 94123-4567', + '(11) 91431-4567', + '+55 (11) 91431-4567', + '+55 11 91431-4567', + '+551191431-4567', + '5511914314567', + '5511912345678', + ], + invalid: [ + '0819876543', + '+55 15 7566123', + '+017 123456789', + '5501599623874', + '+55012962308', + '+55 015 1234-3214', + '+55 11 90431-4567', + '+55 (11) 90431-4567', + '+551190431-4567', + '5511904314567', + '5511902345678', + '(11) 90431-4567', + ], + }, + { + locale: 'zh-CN', + valid: [ + '13523333233', + '13838389438', + '14899230918', + '14999230918', + '15323456787', + '15052052020', + '16237108167', + '008616238234822', + '+8616238234822', + '16565600001', + '17269427292', + '17469427292', + '18199617480', + '19151751717', + '19651751717', + '+8613238234822', + '+8613487234567', + '+8617823492338', + '+8617823492338', + '+8616637108167', + '+8616637108167', + '+8616712341234', + '+8619912341234', + '+8619812341234', + '+8619712341234', + '+8619612341234', + '+8619512341234', + '+8619312341234', + '+8619212341234', + '+8619112341234', + '+8617269427292', + '008618812341234', + '008618812341234', + '008617269427292', + // Reserve number segments in the future. + '92138389438', + '+8692138389438', + '008692138389438', + '98199649964', + '+8698099649964', + '008698099649964', + ], + invalid: [ + '12345', + '', + '12038389438', + '12838389438', + '013838389438', + '+86-13838389438', + '+08613811211114', + '+008613811211114', + '08613811211114', + '0086-13811211114', + '0086-138-1121-1114', + 'Vml2YW11cyBmZXJtZtesting123', + '010-38238383', + ], + }, + { + locale: 'zh-TW', + valid: [ + '0987123456', + '+886987123456', + '886987123456', + '+886-987123456', + '886-987123456', + ], + invalid: [ + '12345', + '', + 'Vml2YW11cyBmZXJtZtesting123', + '0-987123456', + ], + }, + { + locale: 'en-LS', + valid: [ + '+26622123456', + '+26628123456', + '+26657123456', + '+26658123456', + '+26659123456', + '+26627123456', + '+26652123456', + ], + invalid: [ + '+26612345678', + '', + '2664512-21', + '+2662212345678', + 'someString', + ], + }, + { + locale: 'en-BM', + valid: [ + '+14417974653', + '14413986653', + '4415370973', + '+14415005489', + ], + invalid: [ + '85763287', + '+14412020436', + '+14412236546', + '+14418245567', + '+14416546789', + '44087635627', + '+4418970973', + '', + '+1441897465', + '+1441897465 additional invalid string part', + ], + }, + { + locale: 'en-BS', + valid: [ + '+12421231234', + '2421231234', + '+1-2421231234', + '+1-242-123-1234', + '(242)-123-1234', + '+1 (242)-123-1234', + '242 123-1234', + '(242) 123 1234', + ], + invalid: [ + '85763287', + '+1 242 12 12 12 12', + '+1424123123', + '+14418245567', + '+14416546789', + 'not a number', + '', + ], + }, + { + locale: 'en-ZA', + valid: [ + '0821231234', + '+27821231234', + '27821231234', + ], + invalid: [ + '082123', + '08212312345', + '21821231234', + '+21821231234', + '+0821231234', + ], + }, + { + locale: 'en-AU', + valid: [ + '61404111222', + '+61411222333', + '0417123456', + ], + invalid: [ + '082123', + '08212312345', + '21821231234', + '+21821231234', + '+0821231234', + '04123456789', + ], + }, + { + locale: 'es-BO', + valid: [ + '+59175553635', + '+59162223685', + '+59179783890', + '+59160081890', + '79783890', + '60081890', + ], + invalid: [ + '082123', + '08212312345', + '21821231234', + '+21821231234', + '+59199783890', + ], + }, + { + locale: 'en-GG', + valid: [ + '+441481123456', + '+441481789123', + '441481123456', + '441481789123', + ], + invalid: [ + '999', + '+441481123456789', + '+447123456789', + ], + }, + { + locale: 'en-GH', + valid: [ + '0202345671', + '0502345671', + '0242345671', + '0542345671', + '0532345671', + '0272345671', + '0572345671', + '0262345671', + '0562345671', + '0232345671', + '0282345671', + '+233202345671', + '+233502345671', + '+233242345671', + '+233542345671', + '+233532345671', + '+233272345671', + '+233572345671', + '+233262345671', + '+233562345671', + '+233232345671', + '+233282345671', + '+233592349493', + '0550298219', + ], + invalid: [ + '082123', + '232345671', + '0292345671', + '+233292345671', + ], + }, + { + locale: 'en-GY', + valid: [ + '+5926121234', + '06121234', + '06726381', + '+5926726381', + ], + invalid: [ + '5926121234', + '6121234', + '+592 6121234', + '05926121234', + '+592-6121234', + ], + }, + { + locale: 'en-HK', + valid: [ + '91234567', + '9123-4567', + '61234567', + '51234567', + '+85291234567', + '+852-91234567', + '+852-9123-4567', + '+852 9123 4567', + '9123 4567', + '852-91234567', + ], + invalid: [ + '999', + '+852-912345678', + '123456789', + '+852-1234-56789', + ], + }, + { + locale: 'en-MO', + valid: [ + '61234567', + '+85361234567', + '+853-61234567', + '+853-6123-4567', + '+853 6123 4567', + '6123 4567', + '853-61234567', + ], + invalid: [ + '999', + '12345678', + '612345678', + '+853-12345678', + '+853-22345678', + '+853-82345678', + '+853-612345678', + '+853-1234-5678', + '+853 1234 5678', + '+853-6123-45678', + ], + }, + { + locale: 'en-IE', + valid: [ + '+353871234567', + '353831234567', + '353851234567', + '353861234567', + '353871234567', + '353881234567', + '353891234567', + '0871234567', + '0851234567', + ], + invalid: [ + '999', + '+353341234567', + '+33589484858', + '353841234567', + '353811234567', + ], + }, + { + locale: 'en-JM', + valid: [ + '+8761021234', + '8761211234', + '8763511274', + '+8764511274', + ], + invalid: [ + '999', + '+876102123422', + '+8861021234', + '8761021212213', + '876102123', + ], + }, + { + locale: 'en-KE', + valid: [ + '+254728590432', + '+254733875610', + '254728590234', + '0733346543', + '0700459022', + '0110934567', + '+254110456794', + '254198452389', + ], + invalid: [ + '999', + '+25489032', + '123456789', + '+254800723845', + ], + }, + { + locale: 'fr-CF', + valid: [ + '+23670850000', + '+23675038756', + '+23677859002', + '+23672854202', + '+23621854052', + '+23622854072', + '72234650', + '70045902', + '77934567', + '21456794', + '22452389', + ], + invalid: [ + '+23689032', + '123456789', + '+236723845987', + '022452389', + '+236772345678', + '+236700456794', + + ], + }, + { + locale: 'en-KI', + valid: [ + '+68673140000', + '68673059999', + '+68663000000', + '68663019999', + ], + invalid: [ + '+68653000000', + '68664019999', + '+68619019999', + '686123456789', + '+686733445', + ], + }, + { + locale: 'en-MT', + valid: [ + '+35699000000', + '+35679000000', + '99000000', + ], + invalid: [ + '356', + '+35699000', + '+35610000000', + ], + }, + { + locale: 'en-PH', + valid: [ + '+639275149120', + '+639275142327', + '+639003002023', + '09275149116', + '09194877624', + ], + invalid: [ + '12112-13-345', + '12345678901', + 'sx23YW11cyBmZxxXJt123123', + '010-38238383', + '966684123123-2590', + ], + }, + { + locale: 'en-UG', + valid: [ + '+256728590432', + '+256733875610', + '256728590234', + '0773346543', + '0700459022', + ], + invalid: [ + '999', + '+254728590432', + '+25489032', + '123456789', + '+254800723845', + ], + }, + { + locale: 'en-RW', + valid: [ + '+250728590432', + '+250733875610', + '250738590234', + '0753346543', + '0780459022', + ], + invalid: [ + '999', + '+254728590432', + '+25089032', + '123456789', + '+250800723845', + ], + }, + { + locale: 'en-TZ', + valid: [ + '+255728590432', + '+255733875610', + '255628590234', + '0673346543', + '0600459022', + ], + invalid: [ + '999', + '+254728590432', + '+25589032', + '123456789', + '+255800723845', + ], + }, + { + locale: 'en-MW', + valid: [ + '+265994563785', + '+265111785436', + '+265318596857', + '0320008744', + '01256258', + '0882541896', + '+265984563214', + ], + invalid: [ + '58563', + '+2658256258', + '0896328741', + '0708574896', + '+26570857489635', + ], + }, + { + locale: 'es-PE', + valid: [ + '+51912232764', + '+51923464567', + '+51968267382', + '+51908792973', + '974980472', + '908792973', + '+51974980472', + ], + invalid: [ + '999', + '+51812232764', + '+5181223276499', + '+25589032', + '123456789', + ], + }, + { + locale: 'fr-FR', + valid: [ + '0612457898', + '+33612457898', + '33612457898', + '0712457898', + '+33712457898', + '33712457898', + ], + invalid: [ + '061245789', + '06124578980', + '0112457898', + '0212457898', + '0312457898', + '0412457898', + '0512457898', + '0812457898', + '0912457898', + '+34612457898', + '+336124578980', + '+3361245789', + ], + }, + { + locale: 'fr-BF', + valid: [ + '+22661245789', + '+22665903092', + '+22672457898', + '+22673572346', + '061245789', + '071245783', + ], + invalid: [ + '0612457892', + '06124578980', + '0112457898', + '0212457898', + '0312457898', + '0412457898', + '0512457898', + '0812457898', + '0912457898', + '+22762457898', + '+226724578980', + '+22634523', + ], + }, + { + locale: 'fr-BJ', + valid: [ + '+22920215789', + '+22920293092', + '+22921307898', + '+22921736346', + '+22922416346', + '+22923836346', + ], + invalid: [ + '0612457892', + '01122921737346', + '+22762457898', + '+226724578980', + '+22634523', + ], + }, + { + locale: 'fr-CA', + valid: ['19876543210', '8005552222', '+15673628910'], + invalid: [ + '564785', + '0123456789', + '1437439210', + '+10345672645', + '11435213543', + ], + }, + { + locale: 'fr-CD', + valid: [ + '+243818590432', + '+243893875610', + '243978590234', + '0813346543', + '0820459022', + '+243902590221', + ], + invalid: [ + '243', + '+254818590432', + '+24389032', + '123456789', + '+243700723845', + ], + }, + { + locale: 'fr-GF', + valid: [ + '0612457898', + '+594612457898', + '594612457898', + '0712457898', + '+594712457898', + '594712457898', + ], + invalid: [ + '061245789', + '06124578980', + '0112457898', + '0212457898', + '0312457898', + '0412457898', + '0512457898', + '0812457898', + '0912457898', + '+54612457898', + '+5946124578980', + '+59461245789', + ], + }, + { + locale: 'fr-GP', + valid: [ + '0612457898', + '+590612457898', + '590612457898', + '0712457898', + '+590712457898', + '590712457898', + ], + invalid: [ + '061245789', + '06124578980', + '0112457898', + '0212457898', + '0312457898', + '0412457898', + '0512457898', + '0812457898', + '0912457898', + '+594612457898', + '+5906124578980', + '+59061245789', + ], + }, + { + locale: 'fr-MQ', + valid: [ + '0612457898', + '+596612457898', + '596612457898', + '0712457898', + '+596712457898', + '596712457898', + ], + invalid: [ + '061245789', + '06124578980', + '0112457898', + '0212457898', + '0312457898', + '0412457898', + '0512457898', + '0812457898', + '0912457898', + '+594612457898', + '+5966124578980', + '+59661245789', + ], + }, + { + locale: 'fr-RE', + valid: [ + '0612457898', + '+262612457898', + '262612457898', + '0712457898', + '+262712457898', + '262712457898', + ], + invalid: [ + '061245789', + '06124578980', + '0112457898', + '0212457898', + '0312457898', + '0412457898', + '0512457898', + '0812457898', + '0912457898', + '+264612457898', + '+2626124578980', + '+26261245789', + ], + }, + { + locale: 'fr-PF', + valid: [ + '87123456', + '88123456', + '89123456', + '+68987123456', + '+68988123456', + '+68989123456', + '68987123456', + '68988123456', + '68989123456', + ], + invalid: [ + '7123456', + '86123456', + '87 12 34 56', + 'definitely not a number', + '01+68988123456', + '6898912345', + ], + }, + { + locale: 'fr-WF', + valid: [ + '+681408500', + '+681499387', + '+681728590', + '+681808542', + '+681828540', + '+681832014', + '408500', + '499387', + '728590', + '808542', + '828540', + '832014', + ], + invalid: [ + '+68189032', + '123456789', + '+681723845987', + '022452389', + '+681772345678', + '+681700456794', + + ], + }, + { + locale: 'ka-GE', + valid: [ + '+995500011111', + '+995515352134', + '+995798526662', + '798526662', + '500011119', + '798526662', + '+995799766525', + ], + invalid: [ + '+99550001111', + '+9957997665250', + '+9959997665251', + '+995780011111', + '20000000000', + '68129485729', + '6589394827', + '298RI89572', + ], + }, + { + locale: 'el-GR', + valid: [ + '+306944848966', + '306944848966', + '06904567890', + '6944848966', + '6904567890', + '6914567890', + '6934567890', + '6944567890', + '6954567890', + '6974567890', + '6984567890', + '6994567890', + '6854567890', + '6864567890', + '6874567890', + '6884567890', + '6894567890', + ], + invalid: [ + '2102323234', + '+302646041461', + '120000000', + '20000000000', + '68129485729', + '6589394827', + '298RI89572', + '6924567890', + '6964567890', + '6844567890', + '690456789', + '00690456789', + 'not a number', + ], + }, + { + locale: 'el-CY', + valid: [ + '96546247', + '96978927', + '+35799837145', + '+35799646792', + '96056927', + '99629593', + '99849980', + '3599701619', + '+3599148725', + '96537247', + '3596676533', + '+35795123455', + '+35797012204', + '35799123456', + '+35794123456', + '+35796123456', + ], + invalid: [ + '', + 'somechars', + '9697892', + '998499803', + '33799837145', + '+3799646792', + '93056927', + ], + }, + { + locale: 'en-GB', + valid: [ + '447789345856', + '+447861235675', + '07888814488', + ], + invalid: [ + '67699567', + '0773894868', + '077389f8688', + '+07888814488', + '0152456999', + '442073456754', + '+443003434751', + '05073456754', + '08001123123', + '07043425232', + '01273884231', + '03332654034', + ], + }, + { + locale: 'en-SG', + valid: [ + '32891278', + '87654321', + '98765432', + '+6587654321', + '+6598765432', + '+6565241234', + ], + invalid: [ + '332891231', + '987654321', + '876543219', + '8765432', + '9876543', + '12345678', + '+98765432', + '+9876543212', + '+15673628910', + '19876543210', + '8005552222', + ], + }, + { + locale: 'en-US', + valid: [ + '19876543210', + '8005552222', + '+15673628910', + '+1(567)3628910', + '+1(567)362-8910', + '+1(567) 362-8910', + '1(567)362-8910', + '1(567)362 8910', + '223-456-7890', + ], + invalid: [ + '564785', + '0123456789', + '1437439210', + '+10345672645', + '11435213543', + '1(067)362-8910', + '1(167)362-8910', + '+2(267)362-8910', + '+3365520145', + ], + }, + { + locale: 'en-CA', + valid: ['19876543210', '8005552222', '+15673628910'], + invalid: [ + '564785', + '0123456789', + '1437439210', + '+10345672645', + '11435213543', + ], + }, + { + locale: 'en-ZM', + valid: [ + '0956684590', + '0966684590', + '0976684590', + '+260956684590', + '+260966684590', + '+260976684590', + '260976684590', + '+260779493521', + '+260760010936', + ], + invalid: [ + '12345', + '', + 'Vml2YW11cyBmZXJtZtesting123', + '010-38238383', + '966684590', + '760010936', + ], + }, + { + locale: ['en-ZW'], + valid: [ + '+263561890123', + '+263715558041', + '+263775551112', + '+263775551695', + '+263715556633', + ], + invalid: [ + '12345', + '', + 'Vml2YW11cyBmZXJtZtesting123', + '+2631234567890', + '+2641234567', + '+263981234', + '4736338855', + '66338855', + ], + }, + { + locale: ['en-NA'], + valid: [ + '+26466189012', + '+26461555804', + '+26461434221', + '+26487555169', + '+26481555663', + ], + invalid: [ + '12345', + '', + 'Vml2YW11cyBmZXJtZtesting123', + '+2641234567890', + '+2641234567', + '+2648143422', + '+264981234', + '4736338855', + '66338855', + ], + }, + { + locale: 'ru-RU', + valid: [ + '+79676338855', + '79676338855', + '89676338855', + '9676338855', + ], + invalid: [ + '12345', + '', + 'Vml2YW11cyBmZXJtZtesting123', + '010-38238383', + '+9676338855', + '19676338855', + '6676338855', + '+99676338855', + ], + }, + { + locale: 'si-LK', + valid: [ + '+94766661206', + '94713114340', + '0786642116', + '078 7642116', + '078-7642116', + '0749994567', + ], + invalid: [ + '9912349956789', + '12345', + '1678123456', + '0731234567', + '0797878674', + ], + }, + { + locale: 'sr-RS', + valid: [ + '0640133338', + '063333133', + '0668888878', + '+381645678912', + '+381611314000', + '0655885010', + ], + invalid: [ + '12345', + '', + 'Vml2YW11cyBmZXJtZtesting123', + '010-38238383', + '+9676338855', + '19676338855', + '6676338855', + '+99676338855', + ], + }, + { + locale: 'en-NZ', + valid: [ + '+6427987035', + '642240512347', + '0293981646', + '029968425', + ], + invalid: [ + '12345', + '', + 'Vml2YW11cyBmZXJtZtesting123', + '+642956696123566', + '+02119620856', + '+9676338855', + '19676338855', + '6676338855', + '+99676338855', + ], + }, + { + locale: 'en-MU', + valid: [ + '+23012341234', + '12341234', + '012341234', + ], + invalid: [ + '41234', + '', + '+230', + '+2301', + '+23012', + '+230123', + '+2301234', + '+23012341', + '+230123412', + '+2301234123', + '+230123412341', + '+2301234123412', + '+23012341234123', + ], + }, + { + locale: ['nb-NO', 'nn-NO'], // for multiple locales + valid: [ + '+4796338855', + '+4746338855', + '4796338855', + '4746338855', + '46338855', + '96338855', + ], + invalid: [ + '12345', + '', + 'Vml2YW11cyBmZXJtZtesting123', + '+4676338855', + '19676338855', + '+4726338855', + '4736338855', + '66338855', + ], + }, + { + locale: ['ne-NP'], + valid: [ + '+9779817385479', + '+9779717385478', + '+9779862002615', + '+9779853660020', + ], + invalid: [ + '12345', + '', + 'Vml2YW11cyBmZXJtZtesting123', + '+97796123456789', + '+9771234567', + '+977981234', + '4736338855', + '66338855', + ], + }, + { + locale: 'vi-VN', + valid: [ + '0336012403', + '+84586012403', + '84981577798', + '0708001240', + '84813601243', + '0523803765', + '0863803732', + '0883805866', + '0892405867', + '+84888696413', + '0878123456', + '84781234567', + '0553803765', + ], + invalid: [ + '12345', + '', + 'Vml2YW11cyBmZXJtZtesting123', + '010-38238383', + '260976684590', + '01678912345', + '+841698765432', + '841626543219', + '0533803765', + '08712345678', + '+0321234567', + ], + }, + { + locale: 'es-AR', + valid: [ + '5491143214321', + '+5491143214321', + '+5492414321432', + '5498418432143', + ], + invalid: [ + '1143214321', + '91143214321', + '+91143214321', + '549841004321432', + '549 11 43214321', + '549111543214321', + '5714003425432', + '549114a214321', + '54 9 11 4321-4321', + ], + }, + { + locale: 'es-CO', + valid: [ + '+573003321235', + '573003321235', + '3003321235', + '3213321235', + '3103321235', + '3243321235', + '573011140876', + ], + invalid: [ + '1234', + '+57443875615', + '57309875615', + '57109834567', + '5792434567', + '5702345689', + '5714003425432', + '5703013347567', + '069834567', + '969834567', + '579871235', + '574321235', + '5784321235', + '5784321235', + '9821235', + '0698345', + '3321235', + ], + }, + { + locale: 'es-CL', + valid: [ + '+56733875615', + '56928590234', + '0928590294', + '0208590294', + ], + invalid: [ + '1234', + '+5633875615', + '563875615', + '56109834567', + '56069834567', + ], + }, + { + locale: 'es-EC', + valid: [ + '+593987654321', + '593987654321', + '0987654321', + '027332615', + '+59323456789', + ], + invalid: [ + '03321321', + '+593387561', + '59312345677', + '02344635', + '593123456789', + '081234567', + '+593912345678', + '+593902345678', + '+593287654321', + '593287654321', + ], + }, + { + locale: 'es-CR', + valid: [ + '+50688888888', + '+50665408090', + '+50640895069', + '25789563', + '85789563', + ], + invalid: [ + '+5081', + '+5067777777', + '+50188888888', + '+50e987643254', + '+506e4t4', + '-50688888888', + '50688888888', + '12345678', + '98765432', + '01234567', + ], + }, + { + locale: 'es-CU', + valid: [ + '+5351234567', + '005353216547', + '51234567', + '53214567', + ], + invalid: [ + '1234', + '+5341234567', + '0041234567', + '41234567', + '11234567', + '21234567', + '31234567', + '60303456', + '71234567', + '81234567', + '91234567', + '+5343216547', + '+5332165498', + '+53121234567', + '', + 'abc', + '+535123457', + '56043029304', + ], + }, + { + locale: 'es-DO', + valid: [ + '+18096622563', + '+18295614488', + '+18495259567', + '8492283478', + '8092324576', + '8292387713', + ], + invalid: [ + '+18091', + '+1849777777', + '-18296643245', + '+18086643245', + '+18396643245', + '8196643245', + '+38492283478', + '6492283478', + '8192283478', + ], + }, + { + locale: 'es-HN', + valid: [ + '+50495551876', + '+50488908787', + '+50493456789', + '+50489234567', + '+50488987896', + '+50497567389', + '+50427367389', + '+50422357389', + '+50431257389', + '+50430157389', + ], + invalid: [ + '12345', + '', + 'Vml2YW11cyBmZXJtZtesting123', + '+34683456543', + '65478932', + '+50298787654', + '+504989874', + ], + }, + { + locale: 'es-ES', + valid: [ + '+34654789321', + '654789321', + '+34714789321', + '714789321', + '+34744789321', + '744789321', + ], + invalid: [ + '12345', + '', + 'Vml2YW11cyBmZXJtZtesting123', + '+3465478932', + '65478932', + '+346547893210', + '6547893210', + '+3470478932', + '7047893210', + '+34854789321', + '7547893219', + ], + }, + { + locale: 'es-MX', + valid: [ + '+52019654789321', + '+52199654789321', + '+5201965478932', + '+5219654789321', + '52019654789321', + '52199654789321', + '5201965478932', + '5219654789321', + '87654789321', + '8654789321', + '0187654789321', + '18654789321', + ], + invalid: [ + '12345', + '', + 'Vml2YW11cyBmZXJtZtesting123', + '+3465478932', + '65478932', + '+346547893210', + '+34704789321', + '704789321', + '+34754789321', + ], + }, + { + locale: 'es-NI', + valid: [ + '+5051234567', + '+50512345678', + '5051234567', + '50512345678', + '+50555555555', + ], + invalid: [ + '1234', + '', + '1234567', + '12345678', + '+12345678', + '+505123456789', + '+50612345678', + '+50712345678', + '-50512345678', + ], + }, + { + locale: 'es-PA', + valid: [ + '+5076784565', + '+5074321557', + '5073331112', + '+50723431212', + ], + invalid: [ + '+50755555', + '+207123456', + '2001236542', + '+507987643254', + '+507jjjghtf', + ], + }, + { + locale: 'es-PY', + valid: [ + '+595991372649', + '+595992847352', + '+595993847593', + '+595994857473', + '+595995348532', + '+595996435231', + '+595981847362', + '+595982435452', + '+595983948502', + '+595984342351', + '+595985403481', + '+595986384012', + '+595971435231', + '+595972103924', + '+595973438542', + '+595974425864', + '+595975425843', + '+595976342546', + '+595961435234', + '+595963425043', + ], + invalid: [ + '12345', + '', + 'Vml2YW11cyBmZXJtZtesting123', + '65478932', + '+59599384712', + '+5959938471234', + '+595547893218', + '+591993546843', + ], + }, + { + locale: 'es-SV', + valid: [ + '62136634', + '50361366631', + '+50361366634', + '+50361367217', + '+50361367460', + '+50371367632', + '+50371367767', + '+50371368314', + ], + invalid: [ + '+5032136663', + '21346663', + '+50321366663', + '12345', + 'El salvador', + 'this should fail', + '+5032222', + '+503 1111 1111', + '00 +503 1234 5678', + ], + }, + { + locale: 'es-UY', + valid: [ + '+59899123456', + '099123456', + '+59894654321', + '091111111', + ], + invalid: [ + '54321', + 'montevideo', + '', + '+598099123456', + '090883338', + '099 999 999', + ], + }, + { + locale: 'es-VE', + valid: [ + '+582125457765', + '+582125458053', + '+584125458053', + ], + invalid: [ + '+585129934395', + '+58212993439', + '', + ], + }, + { + locale: 'et-EE', + valid: [ + '+372 512 34 567', + '372 512 34 567', + '+37251234567', + '51234567', + '81234567', + '+372842345678', + ], + invalid: [ + '12345', + '', + 'NotANumber', + '+333 51234567', + '61234567', + '+51234567', + '+372 539 57 4', + '+372 900 1234', + '12345678', + ], + }, + { + locale: 'pl-PL', + valid: [ + '+48512689767', + '+48 56 376 87 47', + '56 566 78 46', + '657562855', + '+48657562855', + '+48 887472765', + '+48 56 6572724', + '+48 67 621 5461', + '48 67 621 5461', + '+48 45 621 5461', + ], + invalid: [ + '+48 67 621 5461', + '+55657562855', + '3454535', + 'teststring', + '', + '1800-88-8687', + '+6019-5830837', + '357562855', + '+48 44 621 5461', + ], + }, + { + locale: 'fa-IR', + valid: [ + '+989123456789', + '989223456789', + '09323456789', + '09021456789', + '+98-990-345-6789', + '+98 938 345 6789', + '0938 345 6789', + ], + invalid: [ + '', + '+989623456789', + '+981123456789', + '01234567890', + '09423456789', + '09823456789', + '9123456789', + '091234567890', + '0912345678', + '+98 912 3456 6789', + '0912 345 678', + ], + }, + { + locale: 'fi-FI', + valid: [ + '+358505557171', + '0455571', + '0505557171', + '358505557171', + '04412345', + '0457 123 45 67', + '+358457 123 45 67', + '+358 50 555 7171', + '0501234', + '+358501234', + '050 1234', + ], + invalid: [ + '12345', + '', + '045557', + '045555717112312332423423421', + 'Vml2YW11cyBmZXJtZtesting123', + '010-38238383', + '+3-585-0555-7171', + '+9676338855', + '19676338855', + '6676338855', + '+99676338855', + '044123', + '019123456789012345678901', + ], + }, + { + locale: 'fj-FJ', + valid: [ + '+6799898679', + '6793788679', + '+679 989 8679', + '679 989 8679', + '679 3456799', + '679908 8909', + ], + invalid: [ + '12345', + '', + '04555792', + '902w99900030900000000099', + '8uiuiuhhyy&GUU88d', + '010-38238383', + '19676338855', + '679 9 89 8679', + '6793 45679', + ], + }, + { + locale: 'ms-MY', + valid: [ + '+60128228789', + '+60195830837', + '+6019-5830837', + '+6019-5830837', + '+6010-4357675', + '+60172012370', + '0128737867', + '0172012370', + '01468987837', + '01112347345', + '016-2838768', + '016 2838768', + ], + invalid: [ + '12345', + '601238788657', + '088387675', + '16-2838768', + '032551433', + '6088-387888', + '088-261987', + '1800-88-8687', + '088-320000', + '+01112353576', + '+0111419752', + ], + }, + { + locale: 'fr-CM', + valid: [ + '+237677936141', + '237623456789', + '+237698124842', + '237693029202', + ], + invalid: [ + 'NotANumber', + '+(703)-572-2920', + '+237 623 45 67 890', + '+2379981247429', + ], + }, + { + locale: 'ko-KR', + valid: [ + '+82-010-1234-5678', + '+82-10-1234-5678', + '82-010-1234-5678', + '82-10-1234-5678', + '+82 10 1234 5678', + '010-123-5678', + '10-1234-5678', + '+82 10 1234 5678', + '011 1234 5678', + '+820112345678', + '01012345678', + '+82 016 1234 5678', + '82 19 1234 5678', + '+82 010 12345678', + ], + invalid: [ + 'abcdefghi', + '+82 10 1234 567', + '+82 10o 1234 1234', + '+82 101 1234 5678', + '+82 10 12 5678', + '+011 7766 1234', + '011_7766_1234', + '+820 11 7766 1234', + ], + }, + { + locale: 'ky-KG', + valid: [ + '+996553033300', + '+996 222 123456', + '+996 500 987654', + '+996 555 111222', + '+996 700 333444', + '+996 770 555666', + '+996 880 777888', + '+996 990 999000', + '+996 995 555666', + '+996 996 555666', + '+996 997 555666', + '+996 998 555666', + ], + invalid: [ + '+996 201 123456', + '+996 312 123456', + '+996 3960 12345', + '+996 3961 12345', + '+996 3962 12345', + '+996 3963 12345', + '+996 3964 12345', + '+996 3965 12345', + '+996 3966 12345', + '+996 3967 12345', + '+996 3968 12345', + '+996 511 123456', + '+996 522 123456', + '+996 561 123456', + '+996 571 123456', + '+996 624 123456', + '+996 623 123456', + '+996 622 123456', + '+996 609 123456', + '+996 100 12345', + '+996 100 1234567', + '996 100 123456', + '0 100 123456', + '0 100 123abc', + ], + }, + { + locale: 'ja-JP', + valid: [ + '09012345678', + '08012345678', + '07012345678', + '06012345678', + '090 1234 5678', + '+8190-1234-5678', + '+81 (0)90-1234-5678', + '+819012345678', + '+81-(0)90-1234-5678', + '+81 90 1234 5678', + ], + invalid: [ + '12345', + '', + '045555717112312332423423421', + 'Vml2YW11cyBmZXJtZtesting123', + '+3-585-0555-7171', + '0 1234 5689', + '16 1234 5689', + '03_1234_5689', + '0312345678', + '0721234567', + '06 1234 5678', + '072 123 4567', + '0729 12 3456', + '07296 1 2345', + '072961 2345', + '03-1234-5678', + '+81312345678', + '+816-1234-5678', + '+81 090 1234 5678', + '+8109012345678', + '+81-090-1234-5678', + '90 1234 5678', + ], + }, + { + locale: 'ir-IR', + valid: [ + '09023818688', + '09123809999', + '+989023818688', + '+989103923523', + ], + invalid: [ + '19023818688', + '323254', + '+903232323257', + '++3567868', + '0902381888832', + ], + }, + { + locale: 'it-IT', + valid: [ + '370 3175423', + '333202925', + '+39 310 7688449', + '+39 3339847632', + ], + invalid: [ + '011 7387545', + '12345', + '+45 345 6782395', + ], + }, + { + locale: 'fr-BE', + valid: [ + '0470123456', + '+32470123456', + '32470123456', + '0421234567', + '+32421234567', + '32421234567', + ], + invalid: [ + '12345', + '+3212345', + '3212345', + '04701234567', + '+3204701234567', + '3204701234567', + '0212345678', + '+320212345678', + '320212345678', + '021234567', + '+3221234567', + '3221234567', + ], + }, + { + locale: 'nl-BE', + valid: [ + '0470123456', + '+32470123456', + '32470123456', + '0421234567', + '+32421234567', + '32421234567', + ], + invalid: [ + '12345', + '+3212345', + '3212345', + '04701234567', + '+3204701234567', + '3204701234567', + '0212345678', + '+320212345678', + '320212345678', + '021234567', + '+3221234567', + '3221234567', + ], + }, + { + locale: 'nl-NL', + valid: [ + '0670123456', + '0612345678', + '31612345678', + '31670123456', + '+31612345678', + '+31670123456', + '+31(0)612345678', + '0031612345678', + '0031(0)612345678', + ], + invalid: [ + '12345', + '+3112345', + '3112345', + '06701234567', + '012345678', + '+3104701234567', + '3104701234567', + '0212345678', + '021234567', + '+3121234567', + '3121234567', + '+310212345678', + '310212345678', + ], + }, + { + locale: 'nl-AW', + valid: [ + '2975612345', + '2976412345', + '+2975612345', + '+2975912345', + '+2976412345', + '+2977312345', + '+2977412345', + '+2979912345', + ], + invalid: [ + '12345', + '+2972345', + '2972345', + '06701234567', + '012345678', + '+2974701234567', + '2974701234567', + '0297345678', + '029734567', + '+2971234567', + '2971234567', + '+297212345678', + '297212345678', + 'number', + ], + }, + { + locale: 'ro-MD', + valid: [ + '+37360375781', + '+37361945673', + '+37362387563', + '+37368447788', + '+37369000101', + '+37367568910', + '+37376758294', + '+37378457892', + '+37379067436', + '37362387563', + '37368447788', + '37369000101', + '37367568910', + ], + invalid: [ + '', + '+37363373381', + '+37364310581', + '+37365578199', + '+37371088636', + 'Vml2YW11cyBmZXJtZtesting123', + '123456', + '740123456', + '+40640123456', + '+40210123456', + ], + }, + { + locale: 'ro-RO', + valid: [ + '+40740123456', + '+40 740123456', + '+40740 123 456', + '+40740.123.456', + '+40740-123-456', + '40740123456', + '40 740123456', + '40740 123 456', + '40740.123.456', + '40740-123-456', + '0740123456', + '0740/123456', + '0740 123 456', + '0740.123.456', + '0740-123-456', + ], + invalid: [ + '', + 'Vml2YW11cyBmZXJtZtesting123', + '123456', + '740123456', + '+40640123456', + '+40210123456', + '+0765351689', + '+0711419752', + ], + }, + { + locale: 'id-ID', + valid: [ + '0811 778 998', + '0811 7785 9983', + '0812 7784 9984', + '0813 7782 9982', + '0821 1234 1234', + '0822 1234 1234', + '0823 1234 1234', + '0852 1234 6764', + '0853 1234 6764', + '0851 1234 6764', + '0814 7782 9982', + '0815 7782 9982', + '0816 7782 9982', + '0855 7782 9982', + '0856 7782 9982', + '0857 7782 9982', + '0858 7782 9982', + '0817 7785 9983', + '0818 7784 9984', + '0819 7782 9982', + '0859 1234 1234', + '0877 1234 1234', + '0878 1234 1234', + '0895 7785 9983', + '0896 7784 9984', + '0897 7782 9982', + '0898 1234 1234', + '0899 1234 1234', + '0881 7785 9983', + '0882 7784 9984', + '0883 7782 9982', + '0884 1234 1234', + '0886 1234 1234', + '0887 1234 1234', + '0888 7785 9983', + '0889 7784 9984', + '0828 7784 9984', + '0838 7784 9984', + '0831 7784 9984', + '0832 7784 9984', + '0833 7784 9984', + '089931236181900', + '62811 778 998', + '62811778998', + '628993123618190', + '62898 740123456', + '62899 7401 2346', + '+62811 778 998', + '+62811778998', + '+62812 9650 3508', + '08197231819', + '085361008008', + '+62811787391', + ], + invalid: [ + '0899312361819001', + '0217123456', + '622178878890', + '6221 740123456', + '0341 8123456', + '0778 89800910', + '0741 123456', + '+6221740123456', + '+65740 123 456', + '', + 'ASDFGJKLmZXJtZtesting123', + '123456', + '740123456', + '+65640123456', + '+64210123456', + ], + }, + { + locale: 'lt-LT', + valid: [ + '+37051234567', + '851234567', + ], + invalid: [ + '+65740 123 456', + '', + 'ASDFGJKLmZXJtZtesting123', + '123456', + '740123456', + '+65640123456', + '+64210123456', + ], + }, + { + locale: 'uk-UA', + valid: [ + '+380501234567', + '+380631234567', + '+380661234567', + '+380671234567', + '+380681234567', + '+380731234567', + '+380751234567', + '+380771234567', + '+380911234567', + '+380921234567', + '+380931234567', + '+380941234567', + '+380951234567', + '+380961234567', + '+380971234567', + '+380981234567', + '+380991234567', + '380501234567', + '380631234567', + '380661234567', + '380671234567', + '380681234567', + '380731234567', + '380751234567', + '380771234567', + '380911234567', + '380921234567', + '380931234567', + '380941234567', + '380951234567', + '380961234567', + '380971234567', + '380981234567', + '380991234567', + '0501234567', + '0631234567', + '0661234567', + '0671234567', + '0681234567', + '0731234567', + '0751234567', + '0771234567', + '0911234567', + '0921234567', + '0931234567', + '0941234567', + '0951234567', + '0961234567', + '0971234567', + '0981234567', + '0991234567', + ], + invalid: [ + '+30982345679', + '+380321234567', + '+380441234567', + '982345679', + '80982345679', + '+380 98 234 5679', + '+380-98-234-5679', + '+380 (98) 234-56-79', + '', + 'ASDFGJKLmZXJtZtesting123', + '123456', + '740123456', + ], + }, + { + locale: 'uz-UZ', + valid: [ + '+998664835244', + '998664835244', + '664835244', + '+998957124555', + '998957124555', + '957124555', + ], + invalid: [ + '+998644835244', + '998644835244', + '644835244', + '+99664835244', + 'ASDFGJKLmZXJtZtesting123', + '123456789', + '870123456', + '', + '+998', + '998', + ], + }, + { + locale: 'da-DK', + valid: [ + '12345678', + '12 34 56 78', + '45 12345678', + '4512345678', + '45 12 34 56 78', + '+45 12 34 56 78', + ], + invalid: [ + '', + '+45010203', + 'ASDFGJKLmZXJtZtesting123', + '123456', + '12 34 56', + '123 123 12', + ], + }, + { + locale: 'sv-SE', + valid: [ + '+46701234567', + '46701234567', + '0721234567', + '073-1234567', + '0761-234567', + '079-123 45 67', + ], + invalid: [ + '12345', + '+4670123456', + '+46301234567', + '+0731234567', + '0731234 56', + '+7312345678', + '', + ], + }, + { + locale: 'fo-FO', + valid: [ + '123456', + '12 34 56', + '298 123456', + '298123456', + '298 12 34 56', + '+298 12 34 56', + ], + invalid: [ + '', + '+4501020304', + 'ASDFGJKLmZXJtZtesting123', + '12345678', + '12 34 56 78', + ], + }, + { + locale: 'kl-GL', + valid: [ + '123456', + '12 34 56', + '299 123456', + '299123456', + '299 12 34 56', + '+299 12 34 56', + ], + invalid: [ + '', + '+4501020304', + 'ASDFGJKLmZXJtZtesting123', + '12345678', + '12 34 56 78', + ], + }, + { + locale: 'kk-KZ', + valid: [ + '+77254716212', + '77254716212', + '87254716212', + '7254716212', + ], + invalid: [ + '12345', + '', + 'ASDFGJKLmZXJtZtesting123', + '010-38238383', + '+9676338855', + '19676338855', + '6676338855', + '+99676338855', + ], + }, + { + locale: 'be-BY', + valid: [ + '+375241234567', + '+375251234567', + '+375291234567', + '+375331234567', + '+375441234567', + '375331234567', + ], + invalid: [ + '12345', + '', + 'ASDFGJKLmZXJtZtesting123', + '010-38238383', + '+9676338855', + '19676338855', + '6676338855', + '+99676338855', + ], + }, + { + locale: 'th-TH', + valid: [ + '0912345678', + '+66912345678', + '66912345678', + ], + invalid: [ + '99123456789', + '12345', + '67812345623', + '081234567891', + ], + }, + { + locale: 'tk-TM', + valid: [ + '+99312495154', + '99312130136', + '+99312918407', + '99312183399', + '812391717', + ], + invalid: [ + '12345', + '+99412495154', + '99412495154', + '998900066506', + ], + }, + { + locale: 'en-SL', + valid: [ + '+23274560591', + '23274560591', + '074560591', + ], + invalid: [ + '0745605912', + '12345', + '232745605917', + '0797878674', + '23274560591 ', + ], + }, + { + locale: 'en-BW', + valid: [ + '+26772868545', + '+26776368790', + '+26774560512', + '26774560591', + '26778560512', + '74560512', + '76710284', + ], + invalid: [ + '0799375902', + '12345', + '+2670745605448', + '2670745605482', + '+26779685451', + '+26770685451', + '267074560', + '2670ab5608', + '+267074560', + '70560512', + '79710284', + ], + }, + { + locale: 'az-AZ', + valid: [ + '+994707007070', + '0707007070', + '+994502111111', + '0505436743', + '0554328772', + '0104328772', + '0993301022', + '+994776007139', + '+994106007139', + ], + invalid: [ + 'wrong-number', + '', + '994707007070', + '++9945005050', + '556007070', + '1234566', + '+994778008080a', + ], + }, + { + locale: 'de-LU', + valid: [ + '601123456', + '+352601123456', + ], + invalid: [ + 'NaN', + '791234', + '+352791234', + '26791234', + '+35226791234', + '+112039812', + '+352703123456', + '1234', + ], + }, + { + locale: 'it-SM', + valid: [ + '612345', + '05496123456', + '+37861234567', + '+390549612345678', + '+37805496123456789', + ], + invalid: [ + '61234567890', + '6123', + '1234567', + '+49123456', + 'NotANumber', + ], + }, + { + locale: 'so-SO', + valid: [ + '+252601234567', + '+252650101010', + '+252794567120', + '252650647388', + '252751234567', + '0601234567', + '0609876543', + ], + invalid: [ + '', + 'not a number', + '+2526012345678', + '25260123456', + '+252705555555', + '+0601234567', + '06945454545', + ], + }, + { + locale: 'sq-AL', + valid: [ + '0621234567', + '0661234567', + '0671234567', + '0681234567', + '0691234567', + '+355621234567', + '+355651234567', + '+355661234567', + '+355671234567', + '+355681234567', + '+355691234567', + ], + invalid: [ + '67123456', + '06712345', + '067123456', + '06712345678', + '0571234567', + '+3556712345', + '+35565123456', + '+35157123456', + 'NotANumber', + ], + }, + { + locale: 'ca-AD', + valid: [ + '+376312345', + '312345', + ], + invalid: [ + '31234', + '31234567', + '512345', + 'NotANumber', + ], + }, + { + locale: 'pt-AO', + valid: [ + '+244911123432', + '911123432', + '244911123432', + ], + invalid: [ + '+2449111234321', + '+244811123432', + '31234', + '31234567', + '512345', + 'NotANumber', + ], + }, + { + locale: 'lv-LV', + valid: [ + '+37121234567', + '37121234567', + ], + invalid: [ + '+37201234567', + '+3754321', + '3712123456', + '+371212345678', + 'NotANumber', + ], + }, + { + locale: 'mg-MG', + valid: [ + '+261204269174', + '261204269174', + '0204269174', + '0209269174', + '0374269174', + '4269174', + ], + invalid: [ + '0261204269174', + '+261 20 4 269174', + '+261 20 4269174', + '020 4269174', + '204269174', + '0404269174', + 'NotANumber', + ], + }, + { + locale: 'mn-MN', + valid: [ + '+97699112222', + '97696112222', + '97695112222', + '01197691112222', + '0097688112222', + '+97677112222', + '+97694112222', + '+97681112222', + ], + invalid: [ + '+97888112222', + '+97977112222', + '+97094112222', + '+97281112222', + '02297681112222', + ], + }, + { + locale: 'my-MM', + valid: [ + '+959750202595', + '09750202595', + '9750202595', + '+959260000966', + '09256000323', + '09276000323', + '09426000323', + '09456000323', + '09761234567', + '09791234567', + '09961234567', + '09771234567', + '09660000234', + ], + invalid: [ + '59750202595', + '+9597502025', + '08943234524', + '09950000966', + '959240000966', + '09246000323', + '09466000323', + '09951234567', + '09801234567', + '09650000234', + ], + }, + { + locale: 'en-PG', + valid: [ + '+67570123456', + '67570123456', + '+67571123456', + '+67572123456', + '+67573123456', + '+67574123456', + '+67575123456', + '+67576123456', + '+67577123456', + '+67578123456', + '+67579123456', + '+67581123456', + '+67588123456', + ], + invalid: [ + '', + 'not a number', + '12345', + '+675123456789', + '+67580123456', + '+67569123456', + '+67582123456', + '+6757012345', + ], + }, + { + locale: 'en-AG', + valid: [ + '12687151234', + '+12687151234', + '+12684641234', + '12684641234', + '+12687211234', + '+12687302468', + '+12687642456', + '+12687763333', + ], + invalid: [ + '2687151234', + '+12687773333', + '+126846412333', + '+12684641', + '+12687123456', + '+12687633456', + ], + }, + { + locale: 'en-AI', + valid: [ + '+12642351234', + '12642351234', + '+12644612222', + '+12645366326', + '+12645376326', + '+12647246326', + '+12647726326', + ], + invalid: [ + '', + 'not a number', + '+22642351234', + '+12902351234', + '+12642331234', + '+1264235', + '22642353456', + '+12352643456', + ], + }, + { + locale: 'en-KN', + valid: [ + '+18694699040', + '18694699040', + '+18697652917', + '18697652917', + '18694658472', + '+18696622969', + '+18694882224', + ], + invalid: [ + '', + '+18694238545', + '+1 8694882224', + '8694658472', + '+186946990', + '+1869469904', + '1869469904', + ], + }, + { + locale: 'en-PK', + valid: [ + '+923412877421', + '+923001234567', + '00923001234567', + '923001234567', + '03001234567', + ], + invalid: [ + '+3001234567', + '+933001234567', + '+924001234567', + '+92300123456720', + '030012345672', + '30012345673', + '0030012345673', + '3001234567', + ], + }, + { + locale: ['tg-TJ'], + valid: [ + '+992553388551', + '+992553322551', + '992553388551', + '992553322551', + ], + invalid: [ + '12345', + '', + 'Vml2YW11cyBmZXJtZtesting123', + '+995563388559', + '+9955633559', + '19676338855', + '+992263388505', + '9923633885', + '99255363885', + '66338855', + ], + }, + { + locale: 'dv-MV', + valid: [ + '+9609112345', + '+9609958973', + '+9607258963', + '+9607958463', + '9609112345', + '9609958973', + '9607212963', + '9607986963', + '9112345', + '9958973', + '7258963', + '7958963', + ], + invalid: [ + '+96059234567', + '+96045789', + '7812463784', + 'NotANumber', + '+9607112345', + '+9609012345', + '+609012345', + '+96071123456', + '3412345', + '9603412345', + ], + }, + { + locale: 'ar-YE', + valid: [ + '737198225', + '733111355', + '+967700990270', + ], + invalid: [ + '+5032136663', + '21346663', + '+50321366663', + '12345', + 'Yemen', + 'this should fail', + '+5032222', + '+503 1111 1111', + '00 +503 1234 5678', + ], + }, + { + locale: 'ar-EH', + valid: [ + '+212-5288-12312', + '+212-5288 12312', + '+212 5288 12312', + '212528912312', + '+212528912312', + '+212528812312', + ], + invalid: [ + '212528812312123', + '+212-5290-12312', + '++212528812312', + '12345', + 'Wester Sahara', + 'this should fail', + '212 5288---12312', + '+503 1111 1111', + '00 +503 1234 5678', + ], + }, + { + locale: 'fa-AF', + valid: [ + '0511231231', + '+93511231231', + '+93281234567', + ], + invalid: [ + '212528812312123', + '+212-5290-12312', + '++212528812312', + '12345', + 'Afghanistan', + 'this should fail', + '212 5288---12312', + '+503 1111 1111', + '00 +503 1234 5678', + ], + }, + { + locale: 'en-SS', + valid: [ + '+211928530422', + '+211913384561', + '+211972879174', + '+211952379334', + '0923346543', + '0950459022', + '0970934567', + '211979841238', + '211929843238', + '211959840238', + ], + invalid: [ + '911', + '+211999', + '123456789909', + 'South Sudan', + '21195 840 238', + '+211981234567', + '+211931234567', + '+211901234567', + '+211991234567', + + ], + }, + { + locale: 'es-GT', + valid: [ + '+50221234567', + '+50277654321', + '50226753421', + '50272332468', + '50278984455', + '+50273472492', + '71234567', + '21132398', + ], + invalid: [ + '44', + '+5022712345678', + '1234567899', + '502712345678', + 'This should fail', + '5021931234567', + '+50281234567', + ], + }, + { + locale: 'mk-MK', + valid: [ + '+38923234567', + '38931234567', + '022123456', + '22234567', + '71234567', + '31234567', + '+38923091500', + '80091234', + '81123456', + '54123456', + ], + invalid: [ + '38912345678', + '+389123456789', + '21234567', + '123456789', + '+3891234567', + '700012345', + '510123456', + 'This should fail', + '+389123456', + '389123456', + '80912345', + ], + }, + { + locale: 'ar-QA', + valid: ['+97435551234', '+97455551234', '+97465551234', '+97475551234', '35551234', '55551234', '65551234', '75551234'], + invalid: ['+97445551234', '+97405551234', '+9745555123', '+974555512345', '+97355551234', '+9125551234', '25551234', '+13005551234', '45551234', '95551234', '+9745555abcd', '', '+974'], + }, + ]; + + let allValid = []; + + fixtures.forEach((fixture) => { + // to be used later on for validating 'any' locale + if (fixture.valid) allValid = allValid.concat(fixture.valid); + + if (Array.isArray(fixture.locale)) { + test({ + validator: 'isMobilePhone', + valid: fixture.valid, + invalid: fixture.invalid, + args: [fixture.locale], + }); + } else { + test({ + validator: 'isMobilePhone', + valid: fixture.valid, + invalid: fixture.invalid, + args: [fixture.locale], + }); + } + }); + + test({ + validator: 'isMobilePhone', + valid: allValid, + invalid: [ + '', + 'asdf', + '1', + 'ASDFGJKLmZXJtZtesting123', + 'Vml2YW11cyBmZXJtZtesting123', + ], + args: ['any'], + }); + + // strict mode + test({ + validator: 'isMobilePhone', + valid: [ + '+254728530234', + '+299 12 34 56', + '+94766660206', + ], + invalid: [ + '254728530234', + '0728530234', + '+728530234', + '766667206', + '0766670206', + ], + args: ['any', { strictMode: true }], + }); + + // falsey locale defaults to 'any' + test({ + validator: 'isMobilePhone', + valid: allValid, + invalid: [ + '', + 'asdf', + '1', + 'ASDFGJKLmZXJtZtesting123', + 'Vml2YW11cyBmZXJtZtesting123', + ], + args: [], + }); + }); + + // de-CH, fr-CH, it-CH + test({ + validator: 'isMobilePhone', + valid: [ + '+41751112233', + '+41761112233', + '+41771112233', + '+41781112233', + '+41791112233', + '+411122112211', + ], + invalid: [ + '+41041112233', + ], + args: [], + }); + + + it('should error on invalid locale', () => { + test({ + validator: 'isMobilePhone', + args: [{ locale: ['is-NOT'] }], + error: [ + '+123456789', + '012345', + ], + }); + }); + + it('should validate currency', () => { + // -$##,###.## (en-US, en-CA, en-AU, en-NZ, en-HK) + test({ + validator: 'isCurrency', + valid: [ + '-$10,123.45', + '$10,123.45', + '$10123.45', + '10,123.45', + '10123.45', + '10,123', + '1,123,456', + '1123456', + '1.39', + '.03', + '0.10', + '$0.10', + '-$0.01', + '-$.99', + '$100,234,567.89', + '$10,123', + '10,123', + '-10123', + ], + invalid: [ + '1.234', + '$1.1', + '$ 32.50', + '500$', + '.0001', + '$.001', + '$0.001', + '12,34.56', + '123456,123,123456', + '123,4', + ',123', + '$-,123', + '$', + '.', + ',', + '00', + '$-', + '$-,.', + '-', + '-$', + '', + '- $', + ], + }); + + // -$##,###.## (en-US, en-CA, en-AU, en-NZ, en-HK) + test({ + validator: 'isCurrency', + args: [ + { + allow_decimal: false, + }, + ], + valid: [ + '-$10,123', + '$10,123', + '$10123', + '10,123', + '10123', + '10,123', + '1,123,456', + '1123456', + '1', + '0', + '$0', + '-$0', + '$100,234,567', + '$10,123', + '10,123', + '-10123', + ], + invalid: [ + '-$10,123.45', + '$10,123.45', + '$10123.45', + '10,123.45', + '10123.45', + '1.39', + '.03', + '0.10', + '$0.10', + '-$0.01', + '-$.99', + '$100,234,567.89', + '1.234', + '$1.1', + '$ 32.50', + '.0001', + '$.001', + '$0.001', + '12,34.56', + '123,4', + ',123', + '$-,123', + '$', + '.', + ',', + '00', + '$-', + '$-,.', + '-', + '-$', + '', + '- $', + ], + }); + + // -$##,###.## (en-US, en-CA, en-AU, en-NZ, en-HK) + test({ + validator: 'isCurrency', + args: [ + { + require_decimal: true, + }, + ], + valid: [ + '-$10,123.45', + '$10,123.45', + '$10123.45', + '10,123.45', + '10123.45', + '10,123.00', + '1.39', + '.03', + '0.10', + '$0.10', + '-$0.01', + '-$.99', + '$100,234,567.89', + ], + invalid: [ + '$10,123', + '10,123', + '-10123', + '1,123,456', + '1123456', + '1.234', + '$1.1', + '$ 32.50', + '500$', + '.0001', + '$.001', + '$0.001', + '12,34.56', + '123456,123,123456', + '123,4', + ',123', + '$-,123', + '$', + '.', + ',', + '00', + '$-', + '$-,.', + '-', + '-$', + '', + '- $', + ], + }); + + // -$##,###.## (en-US, en-CA, en-AU, en-NZ, en-HK) + test({ + validator: 'isCurrency', + args: [ + { + digits_after_decimal: [1, 3], + }, + ], + valid: [ + '-$10,123.4', + '$10,123.454', + '$10123.452', + '10,123.453', + '10123.450', + '10,123', + '1,123,456', + '1123456', + '1.3', + '.030', + '0.100', + '$0.1', + '-$0.0', + '-$.9', + '$100,234,567.893', + '$10,123', + '10,123.123', + '-10123.1', + ], + invalid: [ + '1.23', + '$1.13322', + '$ 32.50', + '500$', + '.0001', + '$.01', + '$0.01', + '12,34.56', + '123456,123,123456', + '123,4', + ',123', + '$-,123', + '$', + '.', + ',', + '00', + '$-', + '$-,.', + '-', + '-$', + '', + '- $', + ], + }); + + // -$##,###.## with $ required (en-US, en-CA, en-AU, en-NZ, en-HK) + test({ + validator: 'isCurrency', + args: [ + { + require_symbol: true, + }, + ], + valid: [ + '-$10,123.45', + '$10,123.45', + '$10123.45', + '$10,123.45', + '$10,123', + '$1,123,456', + '$1123456', + '$1.39', + '$.03', + '$0.10', + '$0.10', + '-$0.01', + '-$.99', + '$100,234,567.89', + '$10,123', + '-$10123', + ], + invalid: [ + '1.234', + '$1.234', + '1.1', + '$1.1', + '$ 32.50', + ' 32.50', + '500', + '10,123,456', + '.0001', + '$.001', + '$0.001', + '1,234.56', + '123456,123,123456', + '$123456,123,123456', + '123.4', + '$123.4', + ',123', + '$,123', + '$-,123', + '$', + '.', + '$.', + ',', + '$,', + '00', + '$00', + '$-', + '$-,.', + '-', + '-$', + '', + '$ ', + '- $', + ], + }); + + // ¥-##,###.## (zh-CN) + test({ + validator: 'isCurrency', + args: [ + { + symbol: '¥', + negative_sign_before_digits: true, + }, + ], + valid: [ + '123,456.78', + '-123,456.78', + '¥6,954,231', + '¥-6,954,231', + '¥10.03', + '¥-10.03', + '10.03', + '1.39', + '.03', + '0.10', + '¥-10567.01', + '¥0.01', + '¥1,234,567.89', + '¥10,123', + '¥-10,123', + '¥-10,123.45', + '10,123', + '10123', + '¥-100', + ], + invalid: [ + '1.234', + '¥1.1', + '5,00', + '.0001', + '¥.001', + '¥0.001', + '12,34.56', + '123456,123,123456', + '123 456', + ',123', + '¥-,123', + '', + ' ', + '¥', + '¥-', + '¥-,.', + '-', + '- ¥', + '-¥', + ], + }); + + test({ + validator: 'isCurrency', + args: [ + { + negative_sign_after_digits: true, + }, + ], + valid: [ + '$10,123.45-', + '$10,123.45', + '$10123.45', + '10,123.45', + '10123.45', + '10,123', + '1,123,456', + '1123456', + '1.39', + '.03', + '0.10', + '$0.10', + '$0.01-', + '$.99-', + '$100,234,567.89', + '$10,123', + '10,123', + '10123-', + ], + invalid: [ + '-123', + '1.234', + '$1.1', + '$ 32.50', + '500$', + '.0001', + '$.001', + '$0.001', + '12,34.56', + '123456,123,123456', + '123,4', + ',123', + '$-,123', + '$', + '.', + ',', + '00', + '$-', + '$-,.', + '-', + '-$', + '', + '- $', + ], + }); + + // ¥##,###.## with no negatives (zh-CN) + test({ + validator: 'isCurrency', + args: [ + { + symbol: '¥', + allow_negatives: false, + }, + ], + valid: [ + '123,456.78', + '¥6,954,231', + '¥10.03', + '10.03', + '1.39', + '.03', + '0.10', + '¥0.01', + '¥1,234,567.89', + '¥10,123', + '10,123', + '10123', + '¥100', + ], + invalid: [ + '1.234', + '-123,456.78', + '¥-6,954,231', + '¥-10.03', + '¥-10567.01', + '¥1.1', + '¥-10,123', + '¥-10,123.45', + '5,00', + '¥-100', + '.0001', + '¥.001', + '¥-.001', + '¥0.001', + '12,34.56', + '123456,123,123456', + '123 456', + ',123', + '¥-,123', + '', + ' ', + '¥', + '¥-', + '¥-,.', + '-', + '- ¥', + '-¥', + ], + }); + + // R ## ###,## and R-10 123,25 (el-ZA) + test({ + validator: 'isCurrency', + args: [ + { + symbol: 'R', + negative_sign_before_digits: true, + thousands_separator: ' ', + decimal_separator: ',', + allow_negative_sign_placeholder: true, + }, + ], + valid: [ + '123 456,78', + '-10 123', + 'R-10 123', + 'R 6 954 231', + 'R10,03', + '10,03', + '1,39', + ',03', + '0,10', + 'R10567,01', + 'R0,01', + 'R1 234 567,89', + 'R10 123', + 'R 10 123', + 'R 10123', + 'R-10123', + '10 123', + '10123', + ], + invalid: [ + '1,234', + 'R -10123', + 'R- 10123', + 'R,1', + ',0001', + 'R,001', + 'R0,001', + '12 34,56', + '123456 123 123456', + ' 123', + '- 123', + '123 ', + '', + ' ', + 'R', + 'R- .1', + 'R-', + '-', + '-R 10123', + 'R00', + 'R -', + '-R', + ], + }); + + // -€ ##.###,## (it-IT) + test({ + validator: 'isCurrency', + args: [ + { + symbol: '€', + thousands_separator: '.', + decimal_separator: ',', + allow_space_after_symbol: true, + }, + ], + valid: [ + '123.456,78', + '-123.456,78', + '€6.954.231', + '-€6.954.231', + '€ 896.954.231', + '-€ 896.954.231', + '16.954.231', + '-16.954.231', + '€10,03', + '-€10,03', + '10,03', + '-10,03', + '-1,39', + ',03', + '0,10', + '-€10567,01', + '-€ 10567,01', + '€ 0,01', + '€1.234.567,89', + '€10.123', + '10.123', + '-€10.123', + '€ 10.123', + '€10.123', + '€ 10123', + '10.123', + '-10123', + ], + invalid: [ + '1,234', + '€ 1,1', + '50#,50', + '123,@€ ', + '€€500', + ',0001', + '€ ,001', + '€0,001', + '12.34,56', + '123456.123.123456', + '€123€', + '', + ' ', + '€', + ' €', + '€ ', + '€€', + ' 123', + '- 123', + '.123', + '-€.123', + '123 ', + '€-', + '- €', + '€ - ', + '-', + '- ', + '-€', + ], + }); + + // -##.###,## € (el-GR) + test({ + validator: 'isCurrency', + args: [ + { + symbol: '€', + thousands_separator: '.', + symbol_after_digits: true, + decimal_separator: ',', + allow_space_after_digits: true, + }, + ], + valid: [ + '123.456,78', + '-123.456,78', + '6.954.231 €', + '-6.954.231 €', + '896.954.231', + '-896.954.231', + '16.954.231', + '-16.954.231', + '10,03€', + '-10,03€', + '10,03', + '-10,03', + '1,39', + ',03', + '-,03', + '-,03 €', + '-,03€', + '0,10', + '10567,01€', + '0,01 €', + '1.234.567,89€', + '10.123€', + '10.123', + '10.123€', + '10.123 €', + '10123 €', + '10.123', + '10123', + ], + invalid: [ + '1,234', + '1,1 €', + ',0001', + ',001 €', + '0,001€', + '12.34,56', + '123456.123.123456', + '€123€', + '', + ' ', + '€', + ' €', + '€ ', + ' 123', + '- 123', + '.123', + '-.123€', + '-.123 €', + '123 ', + '-€', + '- €', + '-', + '- ', + ], + }); + + // kr. -##.###,## (da-DK) + test({ + validator: 'isCurrency', + args: [ + { + symbol: 'kr.', + negative_sign_before_digits: true, + thousands_separator: '.', + decimal_separator: ',', + allow_space_after_symbol: true, + }, + ], + valid: [ + '123.456,78', + '-10.123', + 'kr. -10.123', + 'kr.-10.123', + 'kr. 6.954.231', + 'kr.10,03', + 'kr. -10,03', + '10,03', + '1,39', + ',03', + '0,10', + 'kr. 10567,01', + 'kr. 0,01', + 'kr. 1.234.567,89', + 'kr. -1.234.567,89', + '10.123', + 'kr. 10.123', + 'kr.10.123', + '10123', + '10.123', + 'kr.-10123', + ], + invalid: [ + '1,234', + 'kr. -10123', + 'kr.,1', + ',0001', + 'kr. ,001', + 'kr.0,001', + '12.34,56', + '123456.123.123456', + '.123', + 'kr.-.123', + 'kr. -.123', + '- 123', + '123 ', + '', + ' ', + 'kr.', + ' kr.', + 'kr. ', + 'kr.-', + 'kr. -', + 'kr. - ', + ' - ', + '-', + '- kr.', + '-kr.', + ], + }); + + // kr. ##.###,## with no negatives (da-DK) + test({ + validator: 'isCurrency', + args: [ + { + symbol: 'kr.', + allow_negatives: false, + negative_sign_before_digits: true, + thousands_separator: '.', + decimal_separator: ',', + allow_space_after_symbol: true, + }, + ], + valid: [ + '123.456,78', + '10.123', + 'kr. 10.123', + 'kr.10.123', + 'kr. 6.954.231', + 'kr.10,03', + 'kr. 10,03', + '10,03', + '1,39', + ',03', + '0,10', + 'kr. 10567,01', + 'kr. 0,01', + 'kr. 1.234.567,89', + 'kr.1.234.567,89', + '10.123', + 'kr. 10.123', + 'kr.10.123', + '10123', + '10.123', + 'kr.10123', + ], + invalid: [ + '1,234', + '-10.123', + 'kr. -10.123', + 'kr. -1.234.567,89', + 'kr.-10123', + 'kr. -10123', + 'kr.-10.123', + 'kr. -10,03', + 'kr.,1', + ',0001', + 'kr. ,001', + 'kr.0,001', + '12.34,56', + '123456.123.123456', + '.123', + 'kr.-.123', + 'kr. -.123', + '- 123', + '123 ', + '', + ' ', + 'kr.', + ' kr.', + 'kr. ', + 'kr.-', + 'kr. -', + 'kr. - ', + ' - ', + '-', + '- kr.', + '-kr.', + ], + }); + + // ($##,###.##) (en-US, en-HK) + test({ + validator: 'isCurrency', + args: [ + { + parens_for_negatives: true, + }, + ], + valid: [ + '1,234', + '(1,234)', + '($6,954,231)', + '$10.03', + '(10.03)', + '($10.03)', + '1.39', + '.03', + '(.03)', + '($.03)', + '0.10', + '$10567.01', + '($0.01)', + '$1,234,567.89', + '$10,123', + '(10,123)', + '10123', + ], + invalid: [ + '1.234', + '($1.1)', + '-$1.10', + '$ 32.50', + '500$', + '.0001', + '$.001', + '($0.001)', + '12,34.56', + '123456,123,123456', + '( 123)', + ',123', + '$-,123', + '', + ' ', + ' ', + ' ', + '$', + '$ ', + ' $', + ' 123', + '(123) ', + '.', + ',', + '00', + '$-', + '$ - ', + '$- ', + ' - ', + '-', + '- $', + '-$', + '()', + '( )', + '( -)', + '( - )', + '( - )', + '(-)', + '(-$)', + ], + }); + // $##,###.## with no negatives (en-US, en-CA, en-AU, en-HK) + test({ + validator: 'isCurrency', + args: [ + { allow_negatives: false }, + ], + valid: [ + '$10,123.45', + '$10123.45', + '10,123.45', + '10123.45', + '10,123', + '1,123,456', + '1123456', + '1.39', + '.03', + '0.10', + '$0.10', + '$100,234,567.89', + '$10,123', + '10,123', + ], + invalid: [ + '1.234', + '-1.234', + '-10123', + '-$0.01', + '-$.99', + '$1.1', + '-$1.1', + '$ 32.50', + '500$', + '.0001', + '$.001', + '$0.001', + '12,34.56', + '123456,123,123456', + '-123456,123,123456', + '123,4', + ',123', + '$-,123', + '$', + '.', + ',', + '00', + '$-', + '$-,.', + '-', + '-$', + '', + '- $', + '-$10,123.45', + ], + }); + + // R$ ##,###.## (pt_BR) + test({ + validator: 'isCurrency', + args: [ + { + symbol: 'R$', + require_symbol: true, + allow_space_after_symbol: true, + symbol_after_digits: false, + thousands_separator: '.', + decimal_separator: ',', + }, + ], + valid: [ + 'R$ 1.400,00', + 'R$ 400,00', + ], + invalid: [ + '$ 1.400,00', + '$R 1.400,00', + ], + }); + }); + + it('should validate Ethereum addresses', () => { + test({ + validator: 'isEthereumAddress', + valid: [ + '0x0000000000000000000000000000000000000001', + '0x683E07492fBDfDA84457C16546ac3f433BFaa128', + '0x88dA6B6a8D3590e88E0FcadD5CEC56A7C9478319', + '0x8a718a84ee7B1621E63E680371e0C03C417cCaF6', + '0xFCb5AFB808b5679b4911230Aa41FfCD0cd335b42', + ], + invalid: [ + '0xGHIJK05pwm37asdf5555QWERZCXV2345AoEuIdHt', + '0xFCb5AFB808b5679b4911230Aa41FfCD0cd335b422222', + '0xFCb5AFB808b5679b4911230Aa41FfCD0cd33', + '0b0110100001100101011011000110110001101111', + '683E07492fBDfDA84457C16546ac3f433BFaa128', + '1C6o5CDkLxjsVpnLSuqRs1UBFozXLEwYvU', + ], + }); + }); + + it('should validate Bitcoin addresses', () => { + test({ + validator: 'isBtcAddress', + valid: [ + '1MUz4VMYui5qY1mxUiG8BQ1Luv6tqkvaiL', + 'mucFNhKMYoBQYUAEsrFVscQ1YaFQPekBpg', + '3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy', + '2NFUBBRcTJbYc1D4HSCbJhKZp6YCV4PQFpQ', + 'bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq', + '14qViLJfdGaP4EeHnDyJbEGQysnCpwk3gd', + '35bSzXvRKLpHsHMrzb82f617cV4Srnt7hS', + '17VZNX1SN5NtKa8UQFxwQbFeFc3iqRYhemt', + 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4', + 'tb1qxhkl607frtvjsy9nlyeg03lf6fsq947pl2pe82', + 'bc1p5d7rjq7g6rdk2yhzks9smlaqtedr4dekq08ge8ztwac72sfr9rusxg3297', + 'tb1pzpelffrdh9ptpaqnurwx30dlewqv57rcxfeetp86hsssk30p4cws38tr9y', + ], + invalid: [ + '3J98t1WpEZ73CNmQviecrnyiWrnqh0WNL0', + '3J98t1WpEZ73CNmQviecrnyiWrnqh0WNLo', + '3J98t1WpEZ73CNmQviecrnyiWrnqh0WNLI', + '3J98t1WpEZ73CNmQviecrnyiWrnqh0WNLl', + '4J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy', + '0x56F0B8A998425c53c75C4A303D4eF987533c5597', + 'pp8skudq3x5hzw8ew7vzsw8tn4k8wxsqsv0lt0mf3g', + '17VZNX1SN5NlKa8UQFxwQbFeFc3iqRYhem', + 'BC1QW508D6QEJXTDG4Y5R3ZARVAYR0C5XW7KV8F3T4', + 'bc1p5d7rjq7g6rdk2yhzks9smlaqtedr4dekq08ge8ztwac72sfr9rusxg3291', + 'bc1p5d7rjq7g6rdk2yhzks9smlaqtedr4dekq08ge8ztwac72sfr9rusxg329b', + 'bc1p5d7rjq7g6rdk2yhzks9smlaqtedr4dekq08ge8ztwac72sfr9rusxg329i', + 'bc1p5d7rjq7g6rdk2yhzks9smlaqtedr4dekq08ge8ztwac72sfr9rusxg329o', + 'BC1P5D7RJQ7G6RDK2YHZKS9SMLAQTEDR4DEKQ08GE8ZTWAC72SFR9RUSXG3297', + 'TB1PZPELFFRDH9PTPAQNURWX30DLEWQV57RCXFEETP86HSSSK30P4CWS38TR9Y', + ], + }); + }); + + it('should validate booleans', () => { + test({ + validator: 'isBoolean', + valid: [ + 'true', + 'false', + '0', + '1', + ], + invalid: [ + '1.0', + '0.0', + 'true ', + 'False', + 'True', + 'yes', + ], + }); + }); + + it('should validate booleans with option loose set to true', () => { + test({ + validator: 'isBoolean', + args: [ + { loose: true }, + ], + valid: [ + 'true', + 'True', + 'TRUE', + 'false', + 'False', + 'FALSE', + '0', + '1', + 'yes', + 'Yes', + 'YES', + 'no', + 'No', + 'NO', + ], + invalid: [ + '1.0', + '0.0', + 'true ', + ' false', + ], + }); + }); + + it('should validate ISO 639-1 language codes', () => { + test({ + validator: 'isISO6391', + valid: ['ay', 'az', 'ba', 'be', 'bg'], + invalid: ['aj', 'al', 'pe', 'pf', 'abc', '123', ''], + }); + }); + + const validISO8601 = [ + '2009-12T12:34', + '2009', + '2009-05-19', + '2009-05-19', + '20090519', + '2009123', + '2009-05', + '2009-123', + '2009-222', + '2009-001', + '2009-W01-1', + '2009-W51-1', + '2009-W511', + '2009-W33', + '2009W511', + '2009-05-19', + '2009-05-19 00:00', + '2009-05-19 14', + '2009-05-19 14:31', + '2009-05-19 14:39:22', + '2009-05-19T14:39Z', + '2009-W21-2', + '2009-W21-2T01:22', + '2009-139', + '2009-05-19 14:39:22-06:00', + '2009-05-19 14:39:22+0600', + '2009-05-19 14:39:22-01', + '20090621T0545Z', + '2007-04-06T00:00', + '2007-04-05T24:00', + '2010-02-18T16:23:48.5', + '2010-02-18T16:23:48,444', + '2010-02-18T16:23:48,3-06:00', + '2010-02-18T16:23.4', + '2010-02-18T16:23,25', + '2010-02-18T16:23.33+0600', + '2010-02-18T16.23334444', + '2010-02-18T16,2283', + '2009-05-19 143922.500', + '2009-05-19 1439,55', + '2009-10-10', + '2020-366', + '2000-366', + ]; + + const invalidISO8601 = [ + '200905', + '2009367', + '2009-', + '2007-04-05T24:50', + '2009-000', + '2009-M511', + '2009M511', + '2009-05-19T14a39r', + '2009-05-19T14:3924', + '2009-0519', + '2009-05-1914:39', + '2009-05-19 14:', + '2009-05-19r14:39', + '2009-05-19 14a39a22', + '200912-01', + '2009-05-19 14:39:22+06a00', + '2009-05-19 146922.500', + '2010-02-18T16.5:23.35:48', + '2010-02-18T16:23.35:48', + '2010-02-18T16:23.35:48.45', + '2009-05-19 14.5.44', + '2010-02-18T16:23.33.600', + '2010-02-18T16,25:23:48,444', + '2010-13-1', + 'nonsense2021-01-01T00:00:00Z', + '2021-01-01T00:00:00Znonsense', + ]; + + it('should validate ISO 8601 dates', () => { + // from http://www.pelagodesign.com/blog/2009/05/20/iso-8601-date-validation-that-doesnt-suck/ + test({ + validator: 'isISO8601', + valid: validISO8601, + invalid: invalidISO8601, + }); + }); + + it('should validate ISO 8601 dates, with strict = true (regression)', () => { + test({ + validator: 'isISO8601', + args: [ + { strict: true }, + ], + valid: validISO8601, + invalid: invalidISO8601, + }); + }); + + it('should validate ISO 8601 dates, with strict = true', () => { + test({ + validator: 'isISO8601', + args: [ + { strict: true }, + ], + valid: [ + '2000-02-29', + '2009-123', + '2009-222', + '2020-366', + '2400-366', + ], + invalid: [ + '2010-02-30', + '2009-02-29', + '2009-366', + '2019-02-31', + ], + }); + }); + + it('should validate ISO 8601 dates, with strictSeparator = true', () => { + test({ + validator: 'isISO8601', + args: [ + { strictSeparator: true }, + ], + valid: [ + '2009-12T12:34', + '2009', + '2009-05-19', + '2009-05-19', + '20090519', + '2009123', + '2009-05', + '2009-123', + '2009-222', + '2009-001', + '2009-W01-1', + '2009-W51-1', + '2009-W511', + '2009-W33', + '2009W511', + '2009-05-19', + '2009-05-19T14:39Z', + '2009-W21-2', + '2009-W21-2T01:22', + '2009-139', + '20090621T0545Z', + '2007-04-06T00:00', + '2007-04-05T24:00', + '2010-02-18T16:23:48.5', + '2010-02-18T16:23:48,444', + '2010-02-18T16:23:48,3-06:00', + '2010-02-18T16:23.4', + '2010-02-18T16:23,25', + '2010-02-18T16:23.33+0600', + '2010-02-18T16.23334444', + '2010-02-18T16,2283', + '2009-10-10', + '2020-366', + '2000-366', + ], + invalid: [ + '200905', + '2009367', + '2009-', + '2007-04-05T24:50', + '2009-000', + '2009-M511', + '2009M511', + '2009-05-19T14a39r', + '2009-05-19T14:3924', + '2009-0519', + '2009-05-1914:39', + '2009-05-19 14:', + '2009-05-19r14:39', + '2009-05-19 14a39a22', + '200912-01', + '2009-05-19 14:39:22+06a00', + '2009-05-19 146922.500', + '2010-02-18T16.5:23.35:48', + '2010-02-18T16:23.35:48', + '2010-02-18T16:23.35:48.45', + '2009-05-19 14.5.44', + '2010-02-18T16:23.33.600', + '2010-02-18T16,25:23:48,444', + '2010-13-1', + '2009-05-19 00:00', + // Previously valid cases + '2009-05-19 14', + '2009-05-19 14:31', + '2009-05-19 14:39:22', + '2009-05-19 14:39:22-06:00', + '2009-05-19 14:39:22+0600', + '2009-05-19 14:39:22-01', + ], + }); + }); + + it('should validate ISO 8601 dates, with strict = true and strictSeparator = true (regression)', () => { + test({ + validator: 'isISO8601', + args: [ + { strict: true, strictSeparator: true }, + ], + valid: [ + '2000-02-29', + '2009-123', + '2009-222', + '2020-366', + '2400-366', + ], + invalid: [ + '2010-02-30', + '2009-02-29', + '2009-366', + '2019-02-31', + '2009-05-19 14', + '2009-05-19 14:31', + '2009-05-19 14:39:22', + '2009-05-19 14:39:22-06:00', + '2009-05-19 14:39:22+0600', + '2009-05-19 14:39:22-01', + ], + }); + }); + + it('should validate ISO 15924 script codes', () => { + test({ + validator: 'isISO15924', + valid: [ + 'Adlm', + 'Bass', + 'Copt', + 'Dsrt', + 'Egyd', + 'Latn', + 'Zzzz', + ], + invalid: [ + '', + 'arab', + 'zzzz', + 'Qaby', + 'Lati', + ], + }); + }); + + it('should validate RFC 3339 dates', () => { + test({ + validator: 'isRFC3339', + valid: [ + '2009-05-19 14:39:22-06:00', + '2009-05-19 14:39:22+06:00', + '2009-05-19 14:39:22Z', + '2009-05-19T14:39:22-06:00', + '2009-05-19T14:39:22Z', + '2010-02-18T16:23:48.3-06:00', + '2010-02-18t16:23:33+06:00', + '2010-02-18t16:23:33+06:00', + '2010-02-18t16:12:23.23334444z', + '2010-02-18T16:23:55.2283Z', + '2009-05-19 14:39:22.500Z', + '2009-05-19 14:39:55Z', + '2009-05-31 14:39:55Z', + '2009-05-31 14:53:60Z', + '2010-02-18t00:23:23.33+06:00', + '2010-02-18t00:23:32.33+00:00', + '2010-02-18t00:23:32.33+23:00', + ], + invalid: [ + '2010-02-18t00:23:32.33+24:00', + '2009-05-31 14:60:55Z', + '2010-02-18t24:23.33+0600', + '2009-05-00 1439,55Z', + '2009-13-19 14:39:22-06:00', + '2009-05-00 14:39:22+0600', + '2009-00-1 14:39:22Z', + '2009-05-19T14:39:22', + 'nonsense2021-01-01T00:00:00Z', + '2021-01-01T00:00:00Znonsense', + ], + }); + }); + + it('should validate ISO 3166-1 alpha 2 country codes', () => { + // from https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 + test({ + validator: 'isISO31661Alpha2', + valid: [ + 'FR', + 'fR', + 'GB', + 'PT', + 'CM', + 'JP', + 'PM', + 'ZW', + 'MM', + 'cc', + 'GG', + ], + invalid: [ + '', + 'FRA', + 'AA', + 'PI', + 'RP', + 'WV', + 'WL', + 'UK', + 'ZZ', + ], + }); + }); + + it('should validate ISO 3166-1 alpha 3 country codes', () => { + // from https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3 + test({ + validator: 'isISO31661Alpha3', + valid: [ + 'ABW', + 'HND', + 'KHM', + 'RWA', + ], + invalid: [ + '', + 'FR', + 'fR', + 'GB', + 'PT', + 'CM', + 'JP', + 'PM', + 'ZW', + ], + }); + }); + + it('should validate ISO 3166-1 numeric country codes', () => { + // from https://en.wikipedia.org/wiki/ISO_3166-1_numeric + test({ + validator: 'isISO31661Numeric', + valid: [ + '076', + '208', + '276', + '348', + '380', + '410', + '440', + '528', + '554', + '826', + ], + invalid: [ + '', + 'NL', + 'NLD', + '002', + '197', + '249', + '569', + '810', + '900', + '999', + ], + }); + }); + + it('should validate ISO 4217 corrency codes', () => { + // from https://en.wikipedia.org/wiki/ISO_4217 + test({ + validator: 'isISO4217', + valid: [ + 'AED', + 'aed', + 'AUD', + 'CUP', + 'EUR', + 'GBP', + 'LYD', + 'MYR', + 'SGD', + 'SLE', + 'USD', + 'VED', + 'SLE', + ], + invalid: [ + '', + '$', + 'US', + 'us', + 'AAA', + 'aaa', + 'RWA', + 'EURO', + 'euro', + 'HRK', + 'CUC', + ], + }); + }); + + it('should validate whitelisted characters', () => { + test({ + validator: 'isWhitelisted', + args: ['abcdefghijklmnopqrstuvwxyz-'], + valid: ['foo', 'foobar', 'baz-foo'], + invalid: ['foo bar', 'fo.bar', 'türkçe'], + }); + }); + + it('should error on non-string input', () => { + test({ + validator: 'isEmpty', + error: [undefined, null, [], NaN], + }); + }); + + it('should validate dataURI', () => { + /* eslint-disable max-len */ + test({ + validator: 'isDataURI', + valid: [ + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD///+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4Ug9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC', + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIBAMAAAA2IaO4AAAAFVBMVEXk5OTn5+ft7e319fX29vb5+fn///++GUmVAAAALUlEQVQIHWNICnYLZnALTgpmMGYIFWYIZTA2ZFAzTTFlSDFVMwVyQhmAwsYMAKDaBy0axX/iAAAAAElFTkSuQmCC', + 'data:application/media_control+xml;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIBAMAAAA2IaO4AAAAFVBMVEXk5OTn5+ft7e319fX29vb5+fn///++GUmVAAAALUlEQVQIHWNICnYLZnALTgpmMGYIFWYIZTA2ZFAzTTFlSDFVMwVyQhmAwsYMAKDaBy0axX/iAAAAAElFTkSuQmCC', + ' data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIBAMAAAA2IaO4AAAAFVBMVEXk5OTn5+ft7e319fX29vb5+fn///++GUmVAAAALUlEQVQIHWNICnYLZnALTgpmMGYIFWYIZTA2ZFAzTTFlSDFVMwVyQhmAwsYMAKDaBy0axX/iAAAAAElFTkSuQmCC ', + 'data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22100%22%20height%3D%22100%22%3E%3Crect%20fill%3D%22%2300B1FF%22%20width%3D%22100%22%20height%3D%22100%22%2F%3E%3C%2Fsvg%3E', + 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAiIGhlaWdodD0iMTAwIj48cmVjdCBmaWxsPSIjMDBCMUZGIiB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIvPjwvc3ZnPg==', + ' data:,Hello%2C%20World!', + ' data:,Hello World!', + ' data:text/plain;base64,SGVsbG8sIFdvcmxkIQ%3D%3D', + ' data:text/html,%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E', + 'data:,A%20brief%20note', + 'data:text/html;charset=US-ASCII,%3Ch1%3EHello!%3C%2Fh1%3E', + 'data:application/vnd.openxmlformats-officedocument.wordprocessingml.document;base64,dGVzdC5kb2N4', + ], + invalid: [ + 'dataxbase64', + 'data:HelloWorld', + 'data:,A%20brief%20invalid%20[note', + 'file:text/plain;base64,SGVsbG8sIFdvcmxkIQ%3D%3D', + 'data:text/html;charset=,%3Ch1%3EHello!%3C%2Fh1%3E', + 'data:text/html;charset,%3Ch1%3EHello!%3C%2Fh1%3E', 'data:base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD///+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4Ug9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC', + '', + 'http://wikipedia.org', + 'base64', + 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD///+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4Ug9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC', + ], + }); + /* eslint-enable max-len */ + }); + + + it('should validate magnetURI', () => { + /* eslint-disable max-len */ + test({ + validator: 'isMagnetURI', + valid: [ + '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', + 'magnet:?tr=udp://helloworld:1337/announce&xt=urn:btih:ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678901234', + 'magnet:?xt=urn:btmh:1220caf1e1c30e81cb361b9ee167c4aa64228a7fa4fa9f6105232b28ad099f3a302e', + ], + invalid: [ + ':?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', + 'magnet:?xt=urn:btmh:1120caf1e1c30e81cb361b9ee167c4aa64228a7fa4fa9f6105232b28ad099f3a302e', + 'magnet:?ttxt=urn:btmh:1220caf1e1c30e81cb361b9ee167c4aa64228a7fa4fa9f6105232b28ad099f3a302e', + ], + }); + /* eslint-enable max-len */ + }); + + + it('should validate LatLong', () => { + test({ + validator: 'isLatLong', + valid: [ + '(-17.738223, 85.605469)', + '(-12.3456789, +12.3456789)', + '(-60.978437, -0.175781)', + '(77.719772, -37.529297)', + '(7.264394, 165.058594)', + '0.955766, -19.863281', + '(31.269161,164.355469)', + '+12.3456789, -12.3456789', + '-15.379543, -137.285156', + '(11.770570, -162.949219)', + '-55.034319, 113.027344', + '58.025555, 36.738281', + '55.720923,-28.652344', + '-90.00000,-180.00000', + '(-71, -146)', + '(-71.616864, -146.616864)', + '-0.55, +0.22', + '90, 180', + '+90, -180', + '-90,+180', + '90,180', + '0, 0', + ], + invalid: [ + '(020.000000, 010.000000000)', + '89.9999999989, 360.0000000', + '90.1000000, 180.000000', + '+90.000000, -180.00001', + '090.0000, 0180.0000', + '126, -158', + '(-126.400010, -158.400010)', + '-95, -96', + '-95.738043, -96.738043', + '137, -148', + '(-137.5942, -148.5942)', + '(-120, -203)', + '(-119, -196)', + '+119.821728, -196.821728', + '(-110, -223)', + '-110.369532, 223.369532', + '(-120.969949, +203.969949)', + '-116, -126', + '-116.894222, -126.894222', + '-112, -160', + '-112.96381, -160.96381', + '-90., -180.', + '+90.1, -180.1', + '(-17.738223, 85.605469', + '0.955766, -19.863281)', + '+,-', + '(,)', + ',', + ' ', + ], + }); + + test({ + validator: 'isLatLong', + args: [{ + checkDMS: true, + }], + valid: [ + '40° 26′ 46″ N, 79° 58′ 56″ W', + '40° 26′ 46″ S, 79° 58′ 56″ E', + '90° 0′ 0″ S, 180° 0′ 0″ E', + '40° 26′ 45.9996″ N, 79° 58′ 55.2″ E', + '40° 26′ 46″ n, 79° 58′ 56″ w', + '40°26′46″s, 79°58′56″e', + '11° 0′ 0.005″ S, 180° 0′ 0″ E', + '40°26′45.9996″N, 79°58′55.2″E', + + ], + invalid: [ + '100° 26′ 46″ N, 79° 70′ 56″ W', + '40° 89′ 46″ S, 79° 58′ 100″ E', + '40° 26.445′ 45″ N, 79° 58′ 55.2″ E', + '40° 46″ N, 79° 58′ 56″ W', + ], + }); + }); + + it('should validate postal code', () => { + const fixtures = [ + { + locale: 'AU', + valid: [ + '4000', + '2620', + '3000', + '2017', + '0800', + ], + }, + { + locale: 'BD', + valid: [ + '1000', + '1200', + '1300', + '1400', + '1500', + '2000', + '3000', + '4000', + '5000', + '6000', + '7000', + '8000', + '9000', + '9400', + '9499', + ], + invalid: [ + '0999', + '9500', + '10000', + '12345', + '123', + '123456', + 'abcd', + '123a', + 'a123', + '12 34', + '12-34', + ], + }, + { + locale: 'BY', + valid: [ + '225320', + '211120', + '247710', + '231960', + ], + invalid: [ + 'test 225320', + '211120 test', + '317543', + '267946', + ], + }, + { + locale: 'CA', + valid: [ + 'L4T 0A5', + 'G1A-0A2', + 'A1A 1A1', + 'X0A-0H0', + 'V5K 0A1', + 'A1C 3S4', + 'A1C3S4', + 'a1c 3s4', + 'V9A 7N2', + 'B3K 5X5', + 'K8N 5W6', + 'K1A 0B1', + 'B1Z 0B9', + ], + invalid: [ + ' ', + 'invalid value', + 'a1a1a', + 'A1A 1A1', + 'K1A 0D1', + 'W1A 0B1', + 'Z1A 0B1', + ], + }, + { + locale: 'CO', + valid: [ + '050034', + '110221', + '441029', + '910001', + ], + invalid: [ + '11001', + '000000', + '109999', + '329999', + ], + }, + { + locale: 'ES', + valid: [ + '01001', + '52999', + '27880', + ], + invalid: [ + '123', + '1234', + '53000', + '052999', + '0123', + 'abcde', + ], + }, + { + locale: 'JP', + valid: [ + '135-0000', + '874-8577', + '669-1161', + '470-0156', + '672-8031', + ], + }, + { + locale: 'GR', + valid: [ + '022 93', + '29934', + '90293', + '299 42', + '94944', + ], + }, + { + locale: 'GB', + valid: [ + 'TW8 9GS', + 'BS98 1TL', + 'DE99 3GG', + 'DE55 4SW', + 'DH98 1BT', + 'DH99 1NS', + 'GIR0aa', + 'SA99', + 'W1N 4DJ', + 'AA9A 9AA', + 'AA99 9AA', + 'BS98 1TL', + 'DE993GG', + ], + }, + { + locale: 'FR', + valid: [ + '75008', + '44522', + '38499', + '39940', + '01000', + ], + invalid: [ + '44 522', + '38 499', + '96000', + '98025', + ], + }, + { + locale: 'ID', + valid: [ + '10210', + '40181', + '55161', + '60233', + ], + }, + { + locale: 'IE', + valid: [ + 'A65 TF12', + 'A6W U9U9', + ], + invalid: [ + '123', + '75690HG', + 'AW5 TF12', + 'AW5 TF12', + '756 90HG', + 'A65T F12', + 'O62 O1O2', + ], + }, + { + locale: 'IN', + valid: [ + '364240', + '360005', + ], + invalid: [ + '123', + '012345', + '011111', + '101123', + '291123', + '351123', + '541123', + '551123', + '651123', + '661123', + '861123', + '871123', + '881123', + '891123', + ], + }, + { + locale: 'IL', + valid: [ + '10200', + '10292', + '10300', + '10329', + '3885500', + '4290500', + '4286000', + '7080000', + ], + invalid: [ + '123', + '012345', + '011111', + '101123', + '291123', + '351123', + '541123', + '551123', + '651123', + '661123', + '861123', + '871123', + '881123', + '891123', + ], + }, + { + locale: 'BG', + valid: [ + '1000', + ], + }, + { + locale: 'IR', + valid: [ + '4351666456', + '5614736867', + ], + invalid: [ + '43516 6456', + '123443516 6456', + '891123', + 'test 4351666456', + '4351666456 test', + 'test 4351666456 test', + ], + }, + { + locale: 'CZ', + valid: [ + '20134', + '392 90', + '39919', + '938 29', + '39949', + ], + }, + { + locale: 'NL', + valid: [ + '1012 SZ', + '3432FE', + '1118 BH', + '3950IO', + '3997 GH', + ], + invalid: [ + '1234', + '0603 JV', + '5194SA', + '9164 SD', + '1841SS', + ], + }, + { + locale: 'NP', + valid: [ + '10811', + '32600', + '56806', + '977', + ], + invalid: [ + '11977', + 'asds', + '13 32', + '-977', + '97765', + ], + }, + { + locale: 'PL', + valid: [ + '47-260', + '12-930', + '78-399', + '39-490', + '38-483', + '05-800', + '54-060', + ], + }, + { + locale: 'TW', + valid: [ + '360', + '90312', + '399', + '935', + '38842', + '546023', + ], + }, + { + locale: 'LI', + valid: [ + '9485', + '9497', + '9491', + '9489', + '9496', + ], + }, + { + locale: 'PT', + valid: [ + '4829-489', + '0294-348', + '8156-392', + ], + }, + { + locale: 'SE', + valid: [ + '12994', + '284 39', + '39556', + '489 39', + '499 49', + ], + }, + { + locale: 'AD', + valid: [ + 'AD100', + 'AD200', + 'AD300', + 'AD400', + 'AD500', + 'AD600', + 'AD700', + ], + }, + { + locale: 'UA', + valid: [ + '65000', + '65080', + '01000', + '51901', + '51909', + '49125', + ], + }, + { + locale: 'BR', + valid: [ + '39100-000', + '22040-020', + '39400-152', + '39100000', + '22040020', + '39400152', + ], + invalid: [ + '79800A12', + '13165-00', + '38175-abc', + '81470-2763', + '78908', + '13010|111', + ], + }, + { + locale: 'NZ', + valid: [ + '7843', + '3581', + '0449', + '0984', + '4144', + ], + }, + { + locale: 'PK', + valid: [ + '25000', + '44000', + '54810', + '74200', + ], + invalid: [ + '5400', + '540000', + 'NY540', + '540CA', + '540-0', + ], + }, + { + locale: 'MG', + valid: [ + '101', + '303', + '407', + '512', + ], + }, + { + locale: 'MT', + valid: [ + 'VLT2345', + 'VLT 2345', + 'ATD1234', + 'MSK8723', + ], + }, + { + locale: 'MY', + valid: [ + '56000', + '12000', + '79502', + ], + }, + { + locale: 'PR', + valid: [ + '00979', + '00631', + '00786', + '00987', + ], + }, + { + locale: 'AZ', + valid: [ + 'AZ0100', + 'AZ0121', + 'AZ3500', + ], + invalid: [ + '', + ' AZ0100', + 'AZ100', + 'AZ34340', + 'EN2020', + 'AY3030', + ], + }, + { + locale: 'DO', + valid: [ + '12345', + ], + invalid: [ + 'A1234', + '123', + '123456', + ], + }, + { + locale: 'HT', + valid: [ + 'HT1234', + ], + invalid: [ + 'HT123', + 'HT12345', + 'AA1234', + ], + }, + { + locale: 'TH', + valid: [ + '10250', + '72170', + '12140', + ], + invalid: [ + 'T1025', + 'T72170', + '12140TH', + ], + }, + { + locale: 'SG', + valid: [ + '308215', + '546080', + ], + }, + { + locale: 'CN', + valid: [ + '150237', + '100000', + ], + invalid: [ + '141234', + '386789', + 'ab1234', + ], + }, + { + locale: 'KR', + valid: [ + '17008', + '339012', + ], + invalid: [ + '1412347', + 'ab1234', + ], + }, + { + locale: 'LK', + valid: [ + '11500', + '22200', + '10370', + '43000', + ], + invalid: [ + '1234', + '789389', + '982', + ], + }, + { + locale: 'BA', + valid: [ + '76300', + '71000', + '75412', + '76100', + '88202', + '88313', + ], + invalid: [ + '1234', + '789389', + '98212', + '11000', + ], + }, + ]; + + let allValid = []; + + // Test fixtures + fixtures.forEach((fixture) => { + if (fixture.valid) allValid = allValid.concat(fixture.valid); + test({ + validator: 'isPostalCode', + valid: fixture.valid, + invalid: fixture.invalid, + args: [fixture.locale], + }); + }); + + // Test generics + test({ + validator: 'isPostalCode', + valid: [ + ...allValid, + '1234', + '6900', + '1292', + '9400', + '27616', + '90210', + '10001', + '21201', + '33142', + '060623', + '123456', + '293940', + '002920', + ], + invalid: [ + 'asdf', + '1', + 'ASDFGJKLmZXJtZtesting123', + 'Vml2YW11cyBmZXJtZtesting123', + '48380480343', + '29923-329393-2324', + '4294924224', + '13', + ], + args: ['any'], + }); + }); + + it('should error on invalid locale', () => { + test({ + validator: 'isPostalCode', + args: ['is-NOT'], + error: [ + '293940', + '1234', + ], + }); + }); + + it('should validate MIME types', () => { + test({ + validator: 'isMimeType', + valid: [ + 'application/json', + 'application/xhtml+xml', + 'audio/mp4', + 'image/bmp', + 'font/woff2', + 'message/http', + 'model/vnd.gtw', + 'application/media_control+xml', + 'multipart/form-data', + 'multipart/form-data; boundary=something', + 'multipart/form-data; charset=utf-8; boundary=something', + 'multipart/form-data; boundary=something; charset=utf-8', + 'multipart/form-data; boundary=something; charset="utf-8"', + 'multipart/form-data; boundary="something"; charset=utf-8', + 'multipart/form-data; boundary="something"; charset="utf-8"', + 'text/css', + 'text/plain; charset=utf8', + 'Text/HTML;Charset="utf-8"', + 'text/html;charset=UTF-8', + 'Text/html;charset=UTF-8', + 'text/html; charset=us-ascii', + 'text/html; charset=us-ascii (Plain text)', + 'text/html; charset="us-ascii"', + 'video/mp4', + ], + invalid: [ + '', + ' ', + '/', + 'f/b', + 'application', + 'application\\json', + 'application/json/text', + 'application/json; charset=utf-8', + 'audio/mp4; charset=utf-8', + 'image/bmp; charset=utf-8', + 'font/woff2; charset=utf-8', + 'message/http; charset=utf-8', + 'model/vnd.gtw; charset=utf-8', + 'video/mp4; charset=utf-8', + ], + }); + }); + + + it('should validate ISO6346 shipping containerID', () => { + test({ + validator: 'isISO6346', + valid: [ + 'HLXU2008419', + 'TGHU7599330', + 'ECMU4657496', + 'MEDU6246078', + 'YMLU2809976', + 'MRKU0046221', + 'EMCU3811879', + 'OOLU8643084', + 'HJCU1922713', + 'QJRZ123456', + ], + invalid: [ + 'OOLU1922713', + 'HJCU1922413', + 'FCUI985619', + 'ECMJ4657496', + 'TBJA7176445', + 'AFFU5962593', + ], + }); + }); + it('should validate ISO6346 shipping containerID', () => { + test({ + validator: 'isFreightContainerID', + valid: [ + 'HLXU2008419', + 'TGHU7599330', + 'ECMU4657496', + 'MEDU6246078', + 'YMLU2809976', + 'MRKU0046221', + 'EMCU3811879', + 'OOLU8643084', + 'HJCU1922713', + 'QJRZ123456', + ], + invalid: [ + 'OOLU1922713', + 'HJCU1922413', + 'FCUI985619', + 'ECMJ4657496', + 'TBJA7176445', + 'AFFU5962593', + ], + }); + }); + + it('should validate ISO6346 shipping container IDs with checksum digit 10 represented as 0', () => { + test({ + validator: 'isISO6346', + valid: [ + 'APZU3789870', + 'TEMU1002030', + 'DFSU1704420', + 'CMAU2221480', + 'SEGU5060260', + 'FCIU8939320', + 'TRHU3495670', + 'MEDU3871410', + 'CMAU2184010', + 'TCLU2265970', + ], + invalid: [ + 'APZU3789871', // Incorrect check digit + 'TEMU1002031', + 'DFSU1704421', + 'CMAU2221481', + 'SEGU5060261', + ], + }); + }); + it('should validate ISO6346 shipping container IDs with checksum digit 10 represented as 0', () => { + test({ + validator: 'isFreightContainerID', + valid: [ + 'APZU3789870', + 'TEMU1002030', + 'DFSU1704420', + 'CMAU2221480', + 'SEGU5060260', + 'FCIU8939320', + 'TRHU3495670', + 'MEDU3871410', + 'CMAU2184010', + 'TCLU2265970', + ], + invalid: [ + 'APZU3789871', // Incorrect check digit + 'TEMU1002031', + 'DFSU1704421', + 'CMAU2221481', + 'SEGU5060261', + ], + }); + }); + + // EU-UK valid numbers sourced from https://ec.europa.eu/taxation_customs/tin/specs/FS-TIN%20Algorithms-Public.docx or constructed by @tplessas. + it('should validate taxID', () => { + test({ + validator: 'isTaxID', + args: ['bg-BG'], + valid: [ + '7501010010', + '0101010012', + '0111010010', + '7521010014', + '7541010019'], + invalid: [ + '750101001', + '75010100101', + '75-01010/01 0', + '7521320010', + '7501010019'], + }); + test({ + validator: 'isTaxID', + args: ['cs-CZ'], + valid: [ + '530121999', + '530121/999', + '530121/9990', + '5301219990', + '1602295134', + '5451219994', + '0424175466', + '0532175468', + '7159079940'], + invalid: [ + '53-0121 999', + '530121000', + '960121999', + '0124175466', + '0472301754', + '1975116400', + '7159079945'], + }); + test({ + validator: 'isTaxID', + args: ['de-AT'], + valid: [ + '931736581', + '93-173/6581', + '93--173/6581'], + invalid: [ + '999999999', + '93 173 6581', + '93-173/65811', + '93-173/658'], + }); + test({ + validator: 'isTaxID', + args: ['de-DE'], + valid: [ + '26954371827', + '86095742719', + '65929970489', + '79608434120', + '659/299/7048/9'], + invalid: [ + '26954371828', + '86095752719', + '8609575271', + '860957527190', + '65299970489', + '65999970489', + '6592997048-9'], + }); + test({ + validator: 'isTaxID', + args: ['dk-DK'], + valid: [ + '010111-1113', + '0101110117', + '2110084008', + '2110489008', + '2110595002', + '2110197007', + '0101110117', + '0101110230'], + invalid: [ + '010111/1113', + '010111111', + '01011111133', + '2110485008', + '2902034000', + '0101110630'], + }); + test({ + validator: 'isTaxID', + args: ['el-CY'], + valid: [ + '00123123T', + '99652156X'], + invalid: [ + '99652156A', + '00124123T', + '00123123', + '001123123T', + '00 12-3123/T'], + }); + test({ + validator: 'isTaxID', + args: ['el-GR'], + valid: [ + '758426713', + '032792320', + '054100004'], + invalid: [ + '054100005', + '05410000', + '0541000055', + '05 4100005', + '05-410/0005', + '658426713', + '558426713'], + }); + test({ + validator: 'isTaxID', + args: ['en-CA'], + valid: [ + '000000000', + '521719666', + '469317481', + '120217450', + '480534858', + '325268597', + '336475660', + '744797853', + '130692544', + '046454286', + ], + invalid: [ + ' ', + 'any value', + '012345678', + '111111111', + '999999999', + '657449110', + '74 47 978 53', + '744 797 853', + '744-797-853', + '981062432', + '267500713', + '2675o0713', + '70597312', + '7058973122', + '069437151', + '046454281', + '146452286', + '30x92544', + '30692544', + ], + }); + test({ + validator: 'isTaxID', + args: ['en-GB'], + valid: [ + '1234567890', + 'AA123456A', + 'AA123456 '], + invalid: [ + 'GB123456A', + '123456789', + '12345678901', + 'NK123456A', + 'TN123456A', + 'ZZ123456A', + 'GB123456Z', + 'DM123456A', + 'AO123456A', + 'GB-123456A', + 'GB 123456 A', + 'GB123456 '], + }); + test({ + validator: 'isTaxID', + args: ['en-IE'], + valid: [ + '1234567T', + '1234567TW', + '1234577W', + '1234577WW', + '1234577IA'], + invalid: [ + '1234567', + '1234577WWW', + '1234577A', + '1234577JA'], + }); + test({ + validator: 'isTaxID', + args: ['en-US'], + valid: [ + '01-1234567', + '01 1234567', + '011234567', + '10-1234567', + '02-1234567', + '67-1234567', + '15-1234567', + '31-1234567', + '99-1234567'], + invalid: [ + '0-11234567', + '01#1234567', + '01 1234567', + '01 1234 567', + '07-1234567', + '28-1234567', + '96-1234567'], + }); + test({ + validator: 'isTaxID', + args: ['es-AR'], + valid: [ + '20271633638', + '23274986069', + '27333234519', + '30678561165', + '33693450239', + '30534868460', + '23111111129', + '34557619099'], + invalid: [ + '20-27163363-8', + '20.27163363.8', + '33693450231', + '69345023', + '693450233123123', + '3369ew50231', + '34557619095'], + }); + test({ + validator: 'isTaxID', + args: ['es-ES'], + valid: [ + '00054237A', + '54237A', + 'X1234567L', + 'Z1234567R', + 'M2812345C', + 'Y2812345B'], + invalid: [ + 'M2812345CR', + 'A2812345C', + '0/005 423-7A', + '00054237U'], + }); + test({ + validator: 'isTaxID', + args: ['et-EE'], + valid: [ + '10001010080', + '46304280206', + '37102250382', + '32708101201'], + invalid: [ + '46304280205', + '61002293333', + '4-6304 28/0206', + '4630428020', + '463042802066'], + }); + test({ + validator: 'isTaxID', + args: ['fi-FI'], + valid: [ + '131052-308T', + '131002+308W', + '131019A3089'], + invalid: [ + '131052308T', + '131052-308TT', + '131052S308T', + '13 1052-308/T', + '290219A1111'], + }); + test({ + validator: 'isTaxID', + args: ['fr-BE'], + valid: [ + '00012511119'], + }); + test({ + validator: 'isTaxID', + args: ['fr-FR'], + valid: [ + '30 23 217 600 053', + '3023217600053'], + invalid: [ + '30 2 3 217 600 053', + '3 023217-600/053', + '3023217600052', + '3023217500053', + '30232176000534', + '302321760005'], + }); + test({ + validator: 'isTaxID', + args: ['nl-BE'], + valid: [ + '00012511148', + '00/0125-11148', + '00000011115'], + invalid: [ + '00 01 2511148', + '01022911148', + '00013211148', + '0001251114', + '000125111480', + '00012511149'], + }); + test({ + validator: 'isTaxID', + args: ['fr-LU'], + valid: [ + '1893120105732'], + invalid: [ + '189312010573', + '18931201057322', + '1893 12-01057/32', + '1893120105742', + '1893120105733'], + }); + test({ + validator: 'isTaxID', + args: ['lb-LU'], + invalid: [ + '2016023005732'], + }); + test({ + validator: 'isTaxID', + args: ['hr-HR'], + valid: [ + '94577403194'], + invalid: [ + '94 57-7403/194', + '9457740319', + '945774031945', + '94577403197', + '94587403194'], + }); + test({ + validator: 'isTaxID', + args: ['hu-HU'], + valid: [ + '8071592153'], + invalid: [ + '80 71-592/153', + '80715921534', + '807159215', + '8071592152', + '8071582153'], + }); + test({ + validator: 'isTaxID', + args: ['lt-LT'], + valid: [ + '33309240064'], + }); + test({ + validator: 'isTaxID', + args: ['it-IT'], + valid: [ + 'DMLPRY77D15H501F', + 'AXXFAXTTD41H501D'], + invalid: [ + 'DML PRY/77D15H501-F', + 'DMLPRY77D15H501', + 'DMLPRY77D15H501FF', + 'AAPPRY77D15H501F', + 'DMLAXA77D15H501F', + 'AXXFAX90A01Z001F', + 'DMLPRY77B29H501F', + 'AXXFAX3TD41H501E'], + }); + test({ + validator: 'isTaxID', + args: ['lv-LV'], + valid: [ + '01011012344', + '32579461005', + '01019902341', + '325794-61005'], + invalid: [ + '010110123444', + '0101101234', + '01001612345', + '290217-22343'], + }); + test({ + validator: 'isTaxID', + args: ['mt-MT'], + valid: [ + '1234567A', + '882345608', + '34581M', + '199Z'], + invalid: [ + '812345608', + '88234560', + '8823456088', + '11234567A', + '12/34-567 A', + '88 23-456/08', + '1234560A', + '0000000M', + '3200100G'], + }); + test({ + validator: 'isTaxID', + args: ['nl-NL'], + valid: [ + '174559434'], + invalid: [ + '17455943', + '1745594344', + '17 455-94/34'], + }); + test({ + validator: 'isTaxID', + args: ['pl-PL'], + valid: [ + '2234567895', + '02070803628', + '02870803622', + '02670803626', + '01510813623'], + invalid: [ + '020708036285', + '223456789', + '22 345-678/95', + '02 070-8036/28', + '2234567855', + '02223013623'], + }); + test({ + validator: 'isTaxID', + args: ['pt-BR'], + valid: [ + '35161990910', + '74407265027', + '05423994000172', + '11867044000130'], + invalid: [ + 'ABCDEFGH', + '170.691.440-72', + '11494282142', + '74405265037', + '11111111111', + '48469799384', + '94.592.973/0001-82', + '28592361000192', + '11111111111111', + '111111111111112', + '61938188550993', + '82168365502729', + ], + }); + test({ + validator: 'isTaxID', + args: ['pt-PT'], + valid: [ + '299999998', + '299992020'], + invalid: [ + '2999999988', + '29999999', + '29 999-999/8'], + }); + test({ + validator: 'isTaxID', + args: ['ro-RO'], + valid: [ + '8001011234563', + '9000123456789', + '1001011234560', + '3001011234564', + '5001011234568'], + invalid: [ + '5001011234569', + '500 1011-234/568', + '500101123456', + '50010112345688', + '5001011504568', + '8000230234563', + '6000230234563'], + }); + test({ + validator: 'isTaxID', + args: ['sk-SK'], + valid: [ + '530121999', + '536221/999', + '031121999', + '520229999', + '1234567890'], + invalid: [ + '53012199999', + '990101999', + '530121000', + '53012199', + '53-0121 999', + '535229999'], + }); + test({ + validator: 'isTaxID', + args: ['sl-SI'], + valid: [ + '15012557', + '15012590'], + invalid: [ + '150125577', + '1501255', + '15 01-255/7'], + }); + test({ + validator: 'isTaxID', + args: ['sv-SE'], + valid: [ + '640823-3234', + '640883-3231', + '6408833231', + '19640823-3233', + '196408233233', + '19640883-3230', + '200228+5266', + '20180101-5581'], + invalid: [ + '640823+3234', + '160230-3231', + '160260-3231', + '160260-323', + '160260323', + '640823+323', + '640823323', + '640823+32344', + '64082332344', + '19640823-32333', + '1964082332333'], + }); + test({ + validator: 'isTaxID', + args: ['uk-UA'], + valid: [ + '3006321856', + '3003102490', + '2164212906'], + invalid: [ + '2565975632', + '256597563287', + 'КС00123456', + '2896235845'], + }); + test({ + validator: 'isTaxID', + valid: [ + '01-1234567'], + }); + test({ + validator: 'isTaxID', + args: ['is-NOT'], + error: [ + '01-1234567', + '01 1234567', + '011234567', + '0-11234567', + '01#1234567', + '01 1234567', + '01 1234 567', + '07-1234567', + '28-1234567', + '96-1234567', + ], + }); + }); + + + it('should validate slug', () => { + test({ + validator: 'isSlug', + valid: [ + 'foo', + 'foo-bar', + 'foo_bar', + 'foo-bar-foo', + 'foo-bar_foo', + 'foo-bar_foo*75-b4r-**_foo', + 'foo-bar_foo*75-b4r-**_foo-&&', + ], + invalid: [ + 'not-----------slug', + '@#_$@', + '-not-slug', + 'not-slug-', + '_not-slug', + 'not-slug_', + 'not slug', + ], + }); + }); + + it('should validate strong passwords', () => { + test({ + validator: 'isStrongPassword', + args: [{ + minLength: 8, + minLowercase: 1, + minUppercase: 1, + minNumbers: 1, + minSymbols: 1, + }], + valid: [ + '%2%k{7BsL"M%Kd6e', + 'EXAMPLE of very long_password123!', + 'mxH_+2vs&54_+H3P', + '+&DxJ=X7-4L8jRCD', + 'etV*p%Nr6w&H%FeF', + '£3.ndSau_7', + 'VaLIDWith\\Symb0l', + ], + invalid: [ + '', + 'password', + 'hunter2', + 'hello world', + 'passw0rd', + 'password!', + 'PASSWORD!', + ], + }); + }); + + it('should validate date', () => { + test({ + validator: 'isDate', + valid: [ + new Date(), + new Date([2014, 2, 15]), + new Date('2014-03-15'), + '2020/02/29', + '2020-02-19', + ], + invalid: [ + '', + '15072002', + null, + undefined, + { year: 2002, month: 7, day: 15 }, + 42, + { toString() { return '[object Date]'; } }, // faking + '2020-02-30', // invalid date + '2019-02-29', // non-leap year + '2020-04-31', // invalid date + '2020/03-15', // mixed delimiter + '-2020-04-19', + '-2023/05/24', + 'abc-2023/05/24', + '2024', + '2024-', + '2024-05', + '2024-05-', + '2024-05-01-', + '2024-05-01-abc', + '2024/', + '2024/05', + '2024/05/', + '2024/05/01/', + '2024/05/01/abc', + ], + }); + test({ + validator: 'isDate', + args: ['DD/MM/YYYY'], // old format for backward compatibility + valid: [ + '15-07-2002', + '15/07/2002', + ], + invalid: [ + '15/7/2002', + '15-7-2002', + '15/7/02', + '15-7-02', + '15-07/2002', + '2024', + '2024-', + '2024-05', + '2024-05-', + '2024-05-01-', + '2024-05-01-abc', + '2024/', + '2024/05', + '2024/05/', + '2024/05/01/', + '2024/05/01/abc', + '15/', + '15/07', + '15/07/', + '15/07/2024/', + ], + }); + test({ + validator: 'isDate', + args: [{ format: 'DD/MM/YYYY' }], + valid: [ + '15-07-2002', + '15/07/2002', + ], + invalid: [ + '15/7/2002', + '15-7-2002', + '15/7/02', + '15-7-02', + '15-07/2002', + '2024', + '2024-', + '2024-05', + '2024-05-', + '2024-05-01-', + '2024-05-01-abc', + '2024/', + '2024/05', + '2024/05/', + '2024/05/01/', + '2024/05/01/abc', + '15/', + '15/07', + '15/07/', + '15/07/2024/', + ], + }); + test({ + validator: 'isDate', + args: [{ format: 'DD/MM/YY' }], + valid: [ + '15-07-02', + '15/07/02', + ], + invalid: [ + '15/7/2002', + '15-7-2002', + '15/07-02', + '30/04/--', + '2024', + '2024-', + '2024-05', + '2024-05-', + '2024-05-01-', + '2024-05-01-abc', + '2024/', + '2024/05', + '2024/05/', + '2024/05/01/', + '2024/05/01/abc', + '15/', + '15/07', + '15/07/', + '15/07/2024/', + '15/07/24/', + ], + }); + test({ + validator: 'isDate', + args: [{ format: 'D/M/YY' }], + valid: [ + '5-7-02', + '5/7/02', + ], + invalid: [ + '5/07/02', + '15/7/02', + '15-7-02', + '5/7-02', + '3/4/aa', + '2024/', + '2024/05', + '2024/05/', + '2024/05/01/', + '2024/05/01/abc', + '15/', + '15/07', + '15/07/', + '15/07/2024/', + '15/07/24/', + ], + }); + test({ + validator: 'isDate', + args: [{ format: 'DD/MM/YYYY', strictMode: true }], + valid: [ + '15/07/2002', + ], + invalid: [ + '15-07-2002', + '15/7/2002', + '15-7-2002', + '15/7/02', + '15-7-02', + '15-07/2002', + '2024/', + '2024/05', + '2024/05/', + '2024/05/01/', + '2024/05/01/abc', + '15/', + '15/07', + '15/07/', + '15/07/2024/', + '15/07/24/', + ], + }); + test({ + validator: 'isDate', + args: [{ strictMode: true }], + valid: [ + '2020/01/15', + '2014/02/15', + '2014/03/15', + '2020/02/29', + ], + invalid: [ + '2014-02-15', + '2020-02-29', + '15-07/2002', + new Date(), + new Date([2014, 2, 15]), + new Date('2014-03-15'), + '-2020-04-19', + '-2023/05/24', + 'abc-2023/05/24', + '2024', + '2024-', + '2024-05', + '2024-05-', + '2024-05-01-', + '2024-05-01-abc', + '2024/', + '2024/05', + '2024/05/', + '2024/05/01/', + '2024/05/01/abc', + ], + }); + test({ + validator: 'isDate', + args: [{ delimiters: ['/', ' '] }], + valid: [ + new Date(), + new Date([2014, 2, 15]), + new Date('2014-03-15'), + '2020/02/29', + '2020 02 29', + ], + invalid: [ + '2020-02-29', + '', + '15072002', + null, + undefined, + { year: 2002, month: 7, day: 15 }, + 42, + { toString() { return '[object Date]'; } }, + '2020/02/30', + '2019/02/29', + '2020/04/31', + '2020/03-15', + '-2020-04-19', + '-2023/05/24', + 'abc-2023/05/24', + '2024', + '2024-', + '2024-05', + '2024-05-', + '2024-05-01-', + '2024-05-01-abc', + '2024/', + '2024/05', + '2024/05/', + '2024/05/01/', + '2024/05/01/abc', + '2024 05 01 abc', + ], + }); + 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', + '02.29.2020.20', + '2024-', + '2024-05', + '2024-05-', + '2024-05-01', + '-2020-04-19', + '-2023/05/24', + 'abc-2023/05/24', + '04.05.2024.', + '04.05.2024.abc', + 'abc.04.05.2024', + ], + }); + // emulating Pacific time zone offset & time + // which could potentially result in UTC conversion issues + timezone_mock.register('US/Pacific'); + test({ + validator: 'isDate', + valid: [ + new Date(2016, 2, 29), + '2017-08-04', + ], + }); + timezone_mock.unregister(); + }); + it('should validate time', () => { + test({ + validator: 'isTime', + valid: [ + '00:00', + '23:59', + '9:00', + ], + invalid: [ + '', + null, + undefined, + 0, + '07:00 PM', + '23', + '00:60', + '00:', + '01:0 ', + '001:01', + ], + }); + test({ + validator: 'isTime', + args: [{ hourFormat: 'hour24', mode: 'withSeconds' }], + valid: [ + '23:59:59', + '00:00:00', + '9:50:01', + ], + invalid: [ + '', + null, + undefined, + 23, + '01:00:01 PM', + '13:00:', + '00', + '26', + '00;01', + '0 :09', + '59:59:59', + '24:00:00', + '00:59:60', + '99:99:99', + '009:50:01', + ], + }); + test({ + validator: 'isTime', + args: [{ hourFormat: 'hour24', mode: 'withOptionalSeconds' }], + valid: [ + '23:59:59', + '00:00:00', + '9:50:01', + '00:00', + '23:59', + '9:00', + ], + invalid: [ + '', + null, + undefined, + 23, + '01:00:01 PM', + '13:00:', + '00', + '26', + '00;01', + '0 :09', + '59:59:59', + '24:00:00', + '00:59:60', + '99:99:99', + '009:50:01', + ], + }); + test({ + validator: 'isTime', + args: [{ hourFormat: 'hour12' }], + valid: [ + '12:59 PM', + '12:59 AM', + '01:00 PM', + '01:00 AM', + '7:00 AM', + ], + invalid: [ + '', + null, + undefined, + 0, + '12:59 MM', + '12:59 MA', + '12:59 PA', + '12:59 A M', + '13:00 PM', + '23', + '00:60', + '00:', + '9:00', + '01:0 ', + '001:01', + '12:59:00 PM', + '12:59:00 A M', + '12:59:00 ', + ], + }); + test({ + validator: 'isTime', + args: [{ hourFormat: 'hour12', mode: 'withSeconds' }], + valid: [ + '12:59:59 PM', + '2:34:45 AM', + '7:00:00 AM', + ], + invalid: [ + '', + null, + undefined, + 23, + '01:00: 1 PM', + '13:00:', + '13:00:00 PM', + '00', + '26', + '00;01', + '0 :09', + '59:59:59', + '24:00:00', + '00:59:60', + '99:99:99', + '9:50:01', + '009:50:01', + ], + }); + test({ + validator: 'isTime', + args: [{ hourFormat: 'hour12', mode: 'withOptionalSeconds' }], + valid: [ + '12:59:59 PM', + '2:34:45 AM', + '7:00:00 AM', + '12:59 PM', + '12:59 AM', + '01:00 PM', + '01:00 AM', + '7:00 AM', + ], + invalid: [ + '', + null, + undefined, + 23, + '01:00: 1 PM', + '13:00:', + '00', + '26', + '00;01', + '0 :09', + '59:59:59', + '24:00:00', + '00:59:60', + '99:99:99', + '9:50:01', + '009:50:01', + ], + }); + }); + it('should be valid license plate', () => { + test({ + validator: 'isLicensePlate', + args: ['es-AR'], + valid: [ + 'AB 123 CD', + 'AB123CD', + 'ABC 123', + 'ABC123', + ], + invalid: [ + '', + 'notalicenseplate', + 'AB-123-CD', + 'ABC-123', + 'AABC 123', + 'AB CDE FG', + 'ABC DEF', + '12 ABC 34', + ], + }); + test({ + validator: 'isLicensePlate', + args: ['pt-PT'], + valid: [ + 'AA-12-34', + '12-AA-34', + '12-34-AA', + 'AA-12-AA', + 'AA·12·34', + '12·AB·34', + '12·34·AB', + 'AB·34·AB', + 'AA 12 34', + '12 AA 34', + '12 34 AA', + 'AB 12 CD', + 'AA1234', + '12AA34', + '1234AA', + 'AB12CD', + ], + invalid: [ + '', + 'notalicenseplate', + 'AA-AA-00', + '00-AA-AA', + 'AA-AA-AA', + '00-00-00', + 'AA·AA·00', + '00·AA·AA', + 'AA·AA·AA', + '00·00·00', + 'AA AA 00', + '00 AA AA', + 'AA AA AA', + '00 00 00', + 'A1-B2-C3', + '1A-2B-3C', + 'ABC-1-EF', + 'AB-C1D-EF', + 'AB-C1-DEF', + ], + }); + test({ + validator: 'isLicensePlate', + args: ['de-LI'], + valid: [ + 'FL 1', + 'FL 99999', + 'FL 1337', + ], + invalid: [ + '', + 'FL 999999', + 'AB 12345', + 'FL -1', + ], + }); + test({ + validator: 'isLicensePlate', + args: ['de-DE'], + valid: [ + 'M A 1', + 'M A 12', + 'M A 123', + 'M A 1234', + 'M AB 1', + 'M AB 12', + 'M AB 123', + 'M AB 1234', + 'FS A 1', + 'FS A 12', + 'FS A 123', + 'FS A 1234', + 'FS AB 1', + 'FS AB 12', + 'FS AB 123', + 'FS AB 1234', + 'FSAB1234', + 'FS-AB-1234', + 'FS AB 1234 H', + 'FS AB 1234 E', + 'FSAB1234E', + 'FS-AB-1234-E', + 'FS AB-1234-E', + 'FSAB1234 E', + 'FS AB1234E', + 'LRO AB 123', + 'LRO-AB-123-E', + 'LRO-AB-123E', + 'LRO-AB-123 E', + 'LRO-AB-123-H', + 'LRO-AB-123H', + 'LRO-AB-123 H', + ], + invalid: [ + 'YY AB 123', + 'PAF AB 1234', + 'M ABC 123', + 'M AB 12345', + 'FS AB 1234 A', + 'LRO-AB-1234', + 'HRO ABC 123', + 'HRO ABC 1234', + 'LDK-AB-1234-E', + 'ÖHR FA 123D', + 'MZG-AB-123X', + 'OBG-ABD-123', + 'PAF-AB2-123', + ], + }); + test({ + validator: 'isLicensePlate', + args: ['fi-FI'], + valid: [ + 'ABC-123', + 'ABC 123', + 'ABC123', + 'A100', + 'A 100', + 'A-100', + 'C10001', + 'C 10001', + 'C-10001', + '123-ABC', + '123 ABC', + '123ABC', + '123-A', + '123 A', + '123A', + '199AA', + '199 AA', + '199-AA', + ], + invalid: [ + ' ', + 'A-1', + 'A1A-100', + '1-A-2', + 'C1234567', + 'A B C 1 2 3', + 'abc-123', + ], + }); + test({ + validator: 'isLicensePlate', + args: ['sq-AL'], + valid: [ + 'AA 000 AA', + 'ZZ 999 ZZ', + ], + invalid: [ + '', + 'AA 0 A', + 'AAA 00 AAA', + ], + }); + test({ + validator: 'isLicensePlate', + args: ['cs-CZ'], + valid: [ + 'ALA4011', + '4A23000', + 'DICTAT0R', + 'VETERAN', + 'AZKVIZ8', + '2A45876', + 'DIC-TAT0R', + ], + invalid: [ + '', + 'invalidlicenseplate', + 'LN5758898', + 'X-|$|-X', + 'AE0F-OP4', + 'GO0MER', + '2AAAAAAAA', + 'FS AB 1234 E', + 'GB999 9999 00', + ], + }); + + test({ + validator: 'isLicensePlate', + args: ['pt-BR'], + valid: [ + 'ABC1234', + 'ABC 1234', + 'ABC-1234', + 'ABC1D23', + 'ABC1K23', + 'ABC1Z23', + 'ABC 1D23', + 'ABC-1D23', + ], + invalid: [ + '', + 'AA 0 A', + 'AAA 00 AAA', + 'ABCD123', + 'AB12345', + 'AB123DC', + ], + }); + test({ + validator: 'isLicensePlate', + args: ['hu-HU'], + valid: [ + 'AAB-001', + 'AVC-987', + 'KOC-124', + 'JCM-871', + 'AWQ-777', + 'BPO-001', + 'BPI-002', + 'UCO-342', + 'UDO-385', + 'XAO-987', + 'AAI-789', + 'ABI-789', + 'ACI-789', + 'AAO-789', + 'ABO-789', + 'ACO-789', + 'YAA-123', + 'XAA-123', + 'WAA-258', + 'XZZ-784', + 'M123456', + 'CK 12-34', + 'DT 12-34', + 'CD 12-34', + 'HC 12-34', + 'HB 12-34', + 'HK 12-34', + 'MA 12-34', + 'OT 12-34', + 'RR 17-87', + 'CD 124-348', + 'C-C 2021', + 'C-X 2458', + 'X-A 7842', + 'E-72345', + 'Z-07458', + 'S ACF 83', + 'SP 04-68', + ], + invalid: [ + 'AAA-547', + 'aab-001', + 'AAB 001', + 'AB34', + '789-LKJ', + 'BBO-987', + 'BBI-987', + 'BWQ-777', + 'BQW-987', + 'BAI-789', + 'BBI-789', + 'BCI-789', + 'BAO-789', + 'BBO-789', + 'BCO-789', + 'ADI-789', + 'ADO-789', + 'KOC-1234', + 'M1234567', + 'W-12345', + 'S BCF 83', + 'X-D 1234', + 'C-D 1234', + 'HU 12-34', + ], + }); + test({ + validator: 'isLicensePlate', + args: ['any'], + valid: [ + 'FL 1', + 'FS AB 123', + ], + invalid: [ + '', + 'FL 999999', + 'FS AB 1234 A', + ], + }); + test({ + validator: 'isLicensePlate', + args: ['asdfasdf'], + error: [ + 'FL 1', + 'FS AB 123', + 'FL 999999', + 'FS AB 1234 A', + ], + }); + test({ + validator: 'isLicensePlate', + args: ['sv-SE'], + valid: [ + 'ABC 123', + 'ABC 12A', + 'ABC123', + 'ABC12A', + 'A WORD', + 'WORD', + 'ÅSNA', + 'EN VARG', + 'CERISE', + 'AA', + 'ABCDEFG', + 'ÅÄÖ', + 'ÅÄÖ ÅÄÖ', + ], + invalid: [ + '', + ' ', + 'IQV 123', + 'IQV123', + 'ABI 12Q', + 'ÅÄÖ 123', + 'ÅÄÖ 12A', + 'AB1 A23', + 'AB1 12A', + 'lower', + 'abc 123', + 'abc 12A', + 'abc 12a', + 'AbC 12a', + 'WORDLONGERTHANSEVENCHARACTERS', + 'A', + 'ABC-123', + ], + }); + test({ + validator: 'isLicensePlate', + args: ['en-IN'], + valid: [ + 'MH 04 AD 0001', + 'HR26DQ0001', + 'WB-04-ZU-2001', + 'KL 18 X 5800', + 'DL 4 CAF 4856', + 'KA-41CE-5289', + 'GJ 04-AD 5822', + ], + invalid: ['mh04ad0045', 'invalidlicenseplate', '4578', '', 'GJ054GH4785'], + }); + test({ + validator: 'isLicensePlate', + args: ['en-SG'], + valid: [ + 'SGX 1234 A', + 'SGX-1234-A', + 'SGB1234Z', + ], + invalid: [ + 'sg1234a', + 'invalidlicenseplate', + '4578', + '', + 'GJ054GH4785', + ], + }); + }); + it('should validate VAT numbers', () => { + test({ + validator: 'isVAT', + args: ['AT'], + valid: [ + 'ATU12345678', + 'U12345678', + ], + invalid: [ + 'AT 12345678', + '12345678', + ], + }); + test({ + validator: 'isVAT', + args: ['BE'], + valid: [ + 'BE1234567890', + '1234567890', + ], + invalid: [ + 'BE 1234567890', + '123456789', + ], + }); + test({ + validator: 'isVAT', + args: ['BG'], + valid: [ + 'BG1234567890', + '1234567890', + 'BG123456789', + '123456789', + ], + invalid: [ + 'BG 1234567890', + '12345678', + ], + }); + test({ + validator: 'isVAT', + args: ['HR'], + valid: [ + 'HR12345678901', + '12345678901', + ], + invalid: [ + 'HR 12345678901', + '1234567890', + ], + }); + test({ + validator: 'isVAT', + args: ['CY'], + valid: [ + 'CY123456789', + '123456789', + ], + invalid: [ + 'CY 123456789', + '12345678', + ], + }); + test({ + validator: 'isVAT', + args: ['CZ'], + valid: [ + 'CZ1234567890', + 'CZ123456789', + 'CZ12345678', + '1234567890', + '123456789', + '12345678', + ], + invalid: [ + 'CZ 123456789', + '1234567', + ], + }); + test({ + validator: 'isVAT', + args: ['DK'], + valid: [ + 'DK12345678', + '12345678', + ], + invalid: [ + 'DK 12345678', + '1234567', + ], + }); + test({ + validator: 'isVAT', + args: ['EE'], + valid: [ + 'EE123456789', + '123456789', + ], + invalid: [ + 'EE 123456789', + '12345678', + ], + }); + test({ + validator: 'isVAT', + args: ['FI'], + valid: [ + 'FI12345678', + '12345678', + ], + invalid: [ + 'FI 12345678', + '1234567', + ], + }); + test({ + validator: 'isVAT', + args: ['FR'], + valid: [ + 'FRAA123456789', + 'FR83404833048', + 'FR40123456789', + 'FRA1123456789', + 'FR1A123456789', + ], + invalid: [ + 'FR AA123456789', + '123456789', + 'FRAA123456789A', + 'FR123456789', + 'FR 83404833048', + 'FRaa123456789', + ], + }); + test({ + validator: 'isVAT', + args: ['DE'], + valid: [ + 'DE123456789', + '123456789', + ], + invalid: [ + 'DE 123456789', + '12345678', + ], + }); + test({ + validator: 'isVAT', + args: ['EL'], + valid: [ + 'EL123456789', + '123456789', + ], + invalid: [ + 'EL 123456789', + '12345678', + ], + }); + test({ + validator: 'isVAT', + args: ['HU'], + valid: [ + 'HU12345678', + '12345678', + ], + invalid: [ + 'HU 12345678', + '1234567', + ], + }); + test({ + validator: 'isVAT', + args: ['IE'], + valid: [ + 'IE1234567AW', + '1234567AW', + ], + invalid: [ + 'IE 1234567', + '1234567', + ], + }); + test({ + validator: 'isVAT', + args: ['IT'], + valid: [ + 'IT12345678910', + '12345678910', + ], + invalid: [ + 'IT12345678 910', + 'IT 123456789101', + 'IT123456789101', + 'GB12345678910', + 'IT123456789', + ], + }); + test({ + validator: 'isVAT', + args: ['LV'], + valid: [ + 'LV12345678901', + '12345678901', + ], + invalid: [ + 'LV 12345678901', + '1234567890', + ], + }); + test({ + validator: 'isVAT', + args: ['LT'], + valid: [ + 'LT123456789012', + '123456789012', + 'LT12345678901', + '12345678901', + 'LT1234567890', + '1234567890', + 'LT123456789', + '123456789', + ], + invalid: [ + 'LT 123456789012', + '12345678', + ], + }); + test({ + validator: 'isVAT', + args: ['LU'], + valid: [ + 'LU12345678', + '12345678', + ], + invalid: [ + 'LU 12345678', + '1234567', + ], + }); + test({ + validator: 'isVAT', + args: ['MT'], + valid: [ + 'MT12345678', + '12345678', + ], + invalid: [ + 'MT 12345678', + '1234567', + ], + }); + test({ + validator: 'isVAT', + args: ['NL'], + valid: [ + 'NL123456789B10', + '123456789B10', + ], + invalid: [ + 'NL12345678 910', + 'NL 123456789101', + 'NL123456789B1', + 'GB12345678910', + 'NL123456789', + ], + }); + test({ + validator: 'isVAT', + args: ['PL'], + valid: [ + 'PL1234567890', + '1234567890', + 'PL123-456-78-90', + '123-456-78-90', + 'PL123-45-67-890', + '123-45-67-890', + ], + invalid: [ + 'PL 1234567890', + '123456789', + ], + }); + test({ + validator: 'isVAT', + args: ['PT'], + valid: [ + 'PT123456789', + '123456789', + ], + invalid: [ + 'PT 123456789', + '000000001', + ], + }); + test({ + validator: 'isVAT', + args: ['RO'], + valid: [ + 'RO1234567890', + '1234567890', + 'RO12', + '12', + ], + invalid: [ + 'RO 12', + '1', + ], + }); + test({ + validator: 'isVAT', + args: ['SK'], + valid: [ + 'SK1234567890', + '1234567890', + ], + invalid: [ + 'SK 1234567890', + '123456789', + ], + }); + test({ + validator: 'isVAT', + args: ['SI'], + valid: [ + 'SI12345678', + '12345678', + ], + invalid: [ + 'SI 12345678', + '1234567', + ], + }); + test({ + validator: 'isVAT', + args: ['ES'], + valid: [ + 'ESA1234567A', + 'A1234567A', + ], + invalid: [ + 'ES 1234567A', + '123456789', + ], + }); + test({ + validator: 'isVAT', + args: ['SE'], + valid: [ + 'SE123456789012', + '123456789012', + ], + invalid: [ + 'SE 123456789012', + '12345678901', + ], + }); + test({ + validator: 'isVAT', + args: ['AL'], + valid: [ + 'AL123456789A', + '123456789A', + ], + invalid: [ + 'AL 123456789A', + '123456789', + ], + }); + test({ + validator: 'isVAT', + args: ['MK'], + valid: [ + 'MK1234567890123', + '1234567890123', + ], + invalid: [ + 'MK 1234567890123', + '123456789012', + ], + }); + test({ + validator: 'isVAT', + args: ['AU'], + valid: [ + 'AU53004085616', + '53004085616', + 'AU65613309809', + '65613309809', + 'AU34118972998', + '34118972998', + ], + invalid: [ + 'AU65613309808', + '65613309808', + 'AU55613309809', + '55613309809', + 'AU65613319809', + '65613319809', + 'AU34117972998', + '34117972998', + 'AU12345678901', + '12345678901', + 'AU 12345678901', + '1234567890', + ], + }); + test({ + validator: 'isVAT', + args: ['BY'], + valid: [ + 'УНП 123456789', + '123456789', + ], + invalid: [ + 'BY 123456789', + '12345678', + ], + }); + test({ + validator: 'isVAT', + args: ['CA'], + valid: [ + 'CA123456789', + '123456789', + ], + invalid: [ + 'CA 123456789', + '12345678', + ], + }); + test({ + validator: 'isVAT', + args: ['IS'], + valid: [ + 'IS123456', + '12345', + ], + invalid: [ + 'IS 12345', + '1234', + ], + }); + test({ + validator: 'isVAT', + args: ['IN'], + valid: [ + 'IN123456789012345', + '123456789012345', + ], + invalid: [ + 'IN 123456789012345', + '12345678901234', + ], + }); + test({ + validator: 'isVAT', + args: ['ID'], + valid: [ + 'ID123456789012345', + '123456789012345', + 'ID12.345.678.9-012.345', + '12.345.678.9-012.345', + ], + invalid: [ + 'ID 123456789012345', + '12345678901234', + ], + }); + test({ + validator: 'isVAT', + args: ['IL'], + valid: [ + 'IL123456789', + '123456789', + ], + invalid: [ + 'IL 123456789', + '12345678', + ], + }); + test({ + validator: 'isVAT', + args: ['KZ'], + valid: [ + 'KZ123456789012', + '123456789012', + ], + invalid: [ + 'KZ 123456789012', + '12345678', + ], + }); + test({ + validator: 'isVAT', + args: ['NZ'], + valid: [ + 'NZ123456789', + '123456789', + ], + invalid: [ + 'NZ 123456789', + '12345678', + ], + }); + test({ + validator: 'isVAT', + args: ['NG'], + valid: [ + 'NG123456789012', + '123456789012', + 'NG12345678-9012', + '12345678-9012', + ], + invalid: [ + 'NG 123456789012', + '12345678901', + ], + }); + test({ + validator: 'isVAT', + args: ['NO'], + valid: [ + 'NO123456789MVA', + '123456789MVA', + ], + invalid: [ + 'NO 123456789MVA', + '123456789', + ], + }); + test({ + validator: 'isVAT', + args: ['PH'], + valid: [ + 'PH123456789012', + '123456789012', + 'PH123 456 789 012', + '123 456 789 012', + ], + invalid: [ + 'PH 123456789012', + '12345678901', + ], + }); + test({ + validator: 'isVAT', + args: ['RU'], + valid: [ + 'RU1234567890', + '1234567890', + 'RU123456789012', + '123456789012', + ], + invalid: [ + 'RU 123456789012', + '12345678901', + ], + }); + test({ + validator: 'isVAT', + args: ['SM'], + valid: [ + 'SM12345', + '12345', + ], + invalid: [ + 'SM 12345', + '1234', + ], + }); + test({ + validator: 'isVAT', + args: ['SA'], + valid: [ + 'SA123456789012345', + '123456789012345', + ], + invalid: [ + 'SA 123456789012345', + '12345678901234', + ], + }); + test({ + validator: 'isVAT', + args: ['RS'], + valid: [ + 'RS123456789', + '123456789', + ], + invalid: [ + 'RS 123456789', + '12345678', + ], + }); + test({ + validator: 'isVAT', + args: ['CH'], + valid: [ + // strictly valid + 'CHE-116.281.710 MWST', + 'CHE-116.281.710 IVA', + 'CHE-116.281.710 TVA', + // loosely valid presentation variants + 'CHE 116 281 710 IVA', // all separators are spaces + 'CHE-191.398.369MWST', // no space before suffix + 'CHE-116281710 MWST', // no number separators + 'CHE-116281710MWST', // no number separators and no space before suffix + 'CHE105854263MWST', // no separators + 'CHE-116.285.524', // no suffix (vat abbreviation) + 'CHE116281710', // no suffix and separators + '116.281.710 TVA', // no prefix (CHE, ISO-3166-1 Alpha-3) + '116281710MWST', // no prefix and separators + '100.218.485', // no prefix and suffix + '123456788', // no prefix, separators and suffix + ], + invalid: [ + 'CH-116.281.710 MWST', // invalid prefix (should be CHE) + 'CHE-116.281 MWST', // invalid number of digits (should be 9) + 'CHE-123.456.789 MWST', // invalid last digit (should match the calculated check-number 8) + 'CHE-123.356.780 MWST', // invalid check-number (there are no swiss UIDs with the calculated check number 10) + 'CH-116.281.710 VAT', // invalid suffix (should be MWST, IVA or TVA) + 'CHE-116/281/710 IVA', // invalid number separators (should be all dots or all spaces) + ], + }); + test({ + validator: 'isVAT', + args: ['TR'], + valid: [ + 'TR1234567890', + '1234567890', + ], + invalid: [ + 'TR 1234567890', + '123456789', + ], + }); + test({ + validator: 'isVAT', + args: ['UA'], + valid: [ + 'UA123456789012', + '123456789012', + ], + invalid: [ + 'UA 123456789012', + '12345678901', + ], + }); + test({ + validator: 'isVAT', + args: ['GB'], + valid: [ + 'GB999 9999 00', + 'GB999 9999 96', + 'GB999999999 999', + 'GBGD000', + 'GBGD499', + 'GBHA500', + 'GBHA999', + ], + invalid: [ + 'GB999999900', + 'GB999999996', + 'GB999 9999 97', + 'GB999999999999', + 'GB999999999 9999', + 'GB9999999999 999', + 'GBGD 000', + 'GBGD 499', + 'GBHA 500', + 'GBHA 999', + 'GBGD500', + 'GBGD999', + 'GBHA000', + 'GBHA499', + ], + }); + test({ + validator: 'isVAT', + args: ['UZ'], + valid: [ + 'UZ123456789', + '123456789', + ], + invalid: [ + 'UZ 123456789', + '12345678', + ], + }); + test({ + validator: 'isVAT', + args: ['AR'], + valid: [ + 'AR12345678901', + '12345678901', + ], + invalid: [ + 'AR 12345678901', + '1234567890', + ], + }); + test({ + validator: 'isVAT', + args: ['BO'], + valid: [ + 'BO1234567', + '1234567', + ], + invalid: [ + 'BO 1234567', + '123456', + ], + }); + test({ + validator: 'isVAT', + args: ['BR'], + valid: [ + 'BR12.345.678/9012-34', + '12.345.678/9012-34', + 'BR123.456.789-01', + '123.456.789-01', + ], + invalid: [ + 'BR 12.345.678/9012-34', + '12345678901234', + ], + }); + test({ + validator: 'isVAT', + args: ['CL'], + valid: [ + 'CL12345678-9', + '12345678-9', + ], + invalid: [ + 'CL 12345678-9', + '12345678', + ], + }); + test({ + validator: 'isVAT', + args: ['CO'], + valid: [ + 'CO1234567890', + '1234567890', + ], + invalid: [ + 'CO 1234567890', + '123456789', + ], + }); + test({ + validator: 'isVAT', + args: ['CR'], + valid: [ + 'CR123456789012', + '123456789012', + 'CR123456789', + '123456789', + ], + invalid: [ + 'CR 123456789', + '12345678', + ], + }); + test({ + validator: 'isVAT', + args: ['EC'], + valid: [ + 'EC1234567890123', + '1234567890123', + ], + invalid: [ + 'EC 1234567890123', + '123456789012', + ], + }); + test({ + validator: 'isVAT', + args: ['SV'], + valid: [ + 'SV1234-567890-123-1', + '1234-567890-123-1', + ], + invalid: [ + 'SV 1234-567890-123-1', + '1234567890123', + ], + }); + test({ + validator: 'isVAT', + args: ['GT'], + valid: [ + 'GT1234567-8', + '1234567-8', + ], + invalid: [ + 'GT 1234567-8', + '1234567', + ], + }); + test({ + validator: 'isVAT', + args: ['HN'], + valid: [ + 'HN', + ], + invalid: [ + 'HN ', + ], + }); + test({ + validator: 'isVAT', + args: ['MX'], + valid: [ + 'MXABCD123456EFG', + 'ABCD123456EFG', + 'MXABC123456DEF', + 'ABC123456DEF', + ], + invalid: [ + 'MX ABC123456EFG', + '123456', + ], + }); + test({ + validator: 'isVAT', + args: ['NI'], + valid: [ + 'NI123-456789-0123A', + '123-456789-0123A', + ], + invalid: [ + 'NI 123-456789-0123A', + '1234567890123', + ], + }); + test({ + validator: 'isVAT', + args: ['PA'], + valid: [ + 'PA', + ], + invalid: [ + 'PA ', + ], + }); + test({ + validator: 'isVAT', + args: ['PY'], + valid: [ + 'PY12345678-9', + '12345678-9', + 'PY123456-7', + '123456-7', + ], + invalid: [ + 'PY 123456-7', + '123456', + ], + }); + test({ + validator: 'isVAT', + args: ['PE'], + valid: [ + 'PE12345678901', + '12345678901', + ], + invalid: [ + 'PE 12345678901', + '1234567890', + ], + }); + test({ + validator: 'isVAT', + args: ['DO'], + valid: [ + 'DO12345678901', + '12345678901', + 'DO123-4567890-1', + '123-4567890-1', + 'DO123456789', + '123456789', + 'DO1-23-45678-9', + '1-23-45678-9', + ], + invalid: [ + 'DO 12345678901', + '1234567890', + ], + }); + test({ + validator: 'isVAT', + args: ['UY'], + valid: [ + 'UY123456789012', + '123456789012', + ], + invalid: [ + 'UY 123456789012', + '12345678901', + ], + }); + test({ + validator: 'isVAT', + args: ['VE'], + valid: [ + 'VEJ-123456789', + 'J-123456789', + 'VEJ-12345678-9', + 'J-12345678-9', + ], + invalid: [ + 'VE J-123456789', + '12345678', + ], + }); + test({ + validator: 'isVAT', + args: ['invalidCountryCode'], + error: [ + 'GB999 9999 00', + ], + }); + }); + it('should validate mailto URI', () => { + test({ + validator: 'isMailtoURI', + valid: [ + 'mailto:?subject=something&cc=valid@mail.com', + 'mailto:?subject=something&cc=valid@mail.com,another@mail.com,', + 'mailto:?subject=something&bcc=valid@mail.com', + 'mailto:?subject=something&bcc=valid@mail.com,another@mail.com', + 'mailto:?bcc=valid@mail.com,another@mail.com', + 'mailto:?cc=valid@mail.com,another@mail.com', + 'mailto:?cc=valid@mail.com', + 'mailto:?bcc=valid@mail.com', + 'mailto:?subject=something&body=something else', + 'mailto:?subject=something&body=something else&cc=hello@mail.com,another@mail.com', + 'mailto:?subject=something&body=something else&bcc=hello@mail.com,another@mail.com', + 'mailto:?subject=something&body=something else&cc=something@mail.com&bcc=hello@mail.com,another@mail.com', + 'mailto:hello@mail.com', + 'mailto:info@mail.com?', + 'mailto:hey@mail.com?subject=something', + 'mailto:info@mail.com?subject=something&cc=valid@mail.com', + 'mailto:info@mail.com?subject=something&cc=valid@mail.com,another@mail.com,', + 'mailto:info@mail.com?subject=something&bcc=valid@mail.com', + 'mailto:info@mail.com?subject=something&bcc=valid@mail.com,another@mail.com', + 'mailto:info@mail.com?bcc=valid@mail.com,another@mail.com', + 'mailto:info@mail.com?cc=valid@mail.com,another@mail.com', + 'mailto:info@mail.com?cc=valid@mail.com', + 'mailto:info@mail.com?bcc=valid@mail.com&', + 'mailto:info@mail.com?subject=something&body=something else', + 'mailto:info@mail.com?subject=something&body=something else&cc=hello@mail.com,another@mail.com', + 'mailto:info@mail.com?subject=something&body=something else&bcc=hello@mail.com,another@mail.com', + 'mailto:info@mail.com?subject=something&body=something else&cc=something@mail.com&bcc=hello@mail.com,another@mail.com', + 'mailto:', + ], + invalid: [ + '', + 'something', + 'valid@gmail.com', + 'mailto:?subject=okay&subject=444', + 'mailto:?subject=something&wrong=888', + 'mailto:somename@gmail.com', + 'mailto:hello@world.com?cc=somename@gmail.com', + 'mailto:hello@world.com?bcc=somename@gmail.com', + 'mailto:hello@world.com?bcc=somename@gmail.com&bcc', + 'mailto:valid@gmail.com?subject=anything&body=nothing&cc=&bcc=&key=', + 'mailto:hello@world.com?cc=somename', + 'mailto:somename', + 'mailto:info@mail.com?subject=something&body=something else&cc=something@mail.com&bcc=hello@mail.com,another@mail.com&', + 'mailto:?subject=something&body=something else&cc=something@mail.com&bcc=hello@mail.com,another@mail.com&', + ], + }); + }); +}); diff --git a/test/validators/isAfter.test.js b/test/validators/isAfter.test.js new file mode 100644 index 000000000..f0daf8a17 --- /dev/null +++ b/test/validators/isAfter.test.js @@ -0,0 +1,76 @@ +import test from '../testFunctions'; + +describe('isAfter', () => { + it('should validate dates against a start date', () => { + test({ + validator: 'isAfter', + args: [{ comparisonDate: '2011-08-03' }], + valid: ['2011-08-04', new Date(2011, 8, 10).toString()], + invalid: ['2010-07-02', '2011-08-03', new Date(0).toString(), 'foo'], + }); + + test({ + validator: 'isAfter', + valid: ['2100-08-04', new Date(Date.now() + 86400000).toString()], + invalid: ['2010-07-02', new Date(0).toString()], + }); + + test({ + validator: 'isAfter', + args: [{ comparisonDate: '2011-08-03' }], + valid: ['2015-09-17'], + invalid: ['invalid date'], + }); + + test({ + validator: 'isAfter', + args: [{ comparisonDate: 'invalid date' }], + invalid: ['invalid date', '2015-09-17'], + }); + test({ + validator: 'isAfter', + args: [], // will fall back to the current date + valid: ['2100-08-04', new Date(Date.now() + 86400000).toString()], + }); + test({ + validator: 'isAfter', + args: [undefined], // will fall back to the current date + valid: ['2100-08-04', new Date(Date.now() + 86400000).toString()], + }); + test({ + validator: 'isAfter', + args: [{ comparisonDate: undefined }], // will fall back to the current date + valid: ['2100-08-04', new Date(Date.now() + 86400000).toString()], + }); + }); + + describe('(legacy syntax)', () => { + it('should validate dates against a start date', () => { + test({ + validator: 'isAfter', + args: ['2011-08-03'], + valid: ['2011-08-04', new Date(2011, 8, 10).toString()], + invalid: ['2010-07-02', '2011-08-03', new Date(0).toString(), 'foo'], + }); + + test({ + validator: 'isAfter', + valid: ['2100-08-04', new Date(Date.now() + 86400000).toString()], + invalid: ['2010-07-02', new Date(0).toString()], + }); + + test({ + validator: 'isAfter', + args: ['2011-08-03'], + valid: ['2015-09-17'], + invalid: ['invalid date'], + }); + + test({ + validator: 'isAfter', + args: ['invalid date'], + invalid: ['invalid date', '2015-09-17'], + }); + }); + }); +}); diff --git a/test/validators/isBase64.test.js b/test/validators/isBase64.test.js new file mode 100644 index 000000000..c0074343a --- /dev/null +++ b/test/validators/isBase64.test.js @@ -0,0 +1,201 @@ +import { format } from 'util'; +import test from '../testFunctions'; +import validator from '../../src'; + +describe('isBase64', () => { + it('should validate base64 strings with default options', () => { + test({ + validator: 'isBase64', + valid: [ + '', + 'Zg==', + 'Zm8=', + 'Zm9v', + 'Zm9vYg==', + 'Zm9vYmE=', + 'Zm9vYmFy', + 'TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdC4=', + 'Vml2YW11cyBmZXJtZW50dW0gc2VtcGVyIHBvcnRhLg==', + 'U3VzcGVuZGlzc2UgbGVjdHVzIGxlbw==', + 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuMPNS1Ufof9EW/M98FNw' + + 'UAKrwflsqVxaxQjBQnHQmiI7Vac40t8x7pIb8gLGV6wL7sBTJiPovJ0V7y7oc0Ye' + + 'rhKh0Rm4skP2z/jHwwZICgGzBvA0rH8xlhUiTvcwDCJ0kc+fh35hNt8srZQM4619' + + 'FTgB66Xmp4EtVyhpQV+t02g6NzK72oZI0vnAvqhpkxLeLiMCyrI416wHm5Tkukhx' + + 'QmcL2a6hNOyu0ixX/x2kSFXApEnVrJ+/IxGyfyw8kf4N2IZpW5nEP847lpfj0SZZ' + + 'Fwrd1mnfnDbYohX2zRptLy2ZUn06Qo9pkG5ntvFEPo9bfZeULtjYzIl6K8gJ2uGZ' + + 'HQIDAQAB', + ], + invalid: [ + '12345', + 'Vml2YW11cyBmZXJtZtesting123', + 'Zg=', + 'Z===', + 'Zm=8', + '=m9vYg==', + 'Zm9vYmFy====', + ], + }); + + test({ + validator: 'isBase64', + args: [{ urlSafe: true }], + valid: [ + '', + 'bGFkaWVzIGFuZCBnZW50bGVtZW4sIHdlIGFyZSBmbG9hdGluZyBpbiBzcGFjZQ', + '1234', + 'bXVtLW5ldmVyLXByb3Vk', + 'PDw_Pz8-Pg', + 'VGhpcyBpcyBhbiBlbmNvZGVkIHN0cmluZw', + ], + invalid: [ + ' AA', + '\tAA', + '\rAA', + '\nAA', + 'This+isa/bad+base64Url==', + '0K3RgtC+INC30LDQutC+0LTQuNGA0L7QstCw0L3QvdCw0Y8g0YHRgtGA0L7QutCw', + ], + error: [ + null, + undefined, + {}, + [], + 42, + ], + }); + + for (let i = 0, str = '', encoded; i < 1000; i++) { + str += String.fromCharCode(Math.random() * 26 | 97); // eslint-disable-line no-bitwise + encoded = Buffer.from(str).toString('base64'); + if (!validator.isBase64(encoded)) { + let msg = format('validator.isBase64() failed with "%s"', encoded); + throw new Error(msg); + } + } + }); + + it('should validate standard Base64 with padding', () => { + test({ + validator: 'isBase64', + args: [{ urlSafe: false, padding: true }], + valid: [ + '', + 'TWFu', + 'TWE=', + 'TQ==', + 'SGVsbG8=', + 'U29mdHdhcmU=', + 'YW55IGNhcm5hbCBwbGVhc3VyZS4=', + ], + invalid: [ + 'TWF', + 'TWE===', + 'SGVsbG8@', + 'SGVsbG8===', + 'SGVsb G8=', + '====', + ], + }); + }); + + it('should validate standard Base64 without padding', () => { + test({ + validator: 'isBase64', + args: [{ urlSafe: false, padding: false }], + valid: [ + '', + 'TWFu', + 'TWE', + 'TQ', + 'SGVsbG8', + 'U29mdHdhcmU', + 'YW55IGNhcm5hbCBwbGVhc3VyZS4', + ], + invalid: [ + 'TWE=', + 'TQ===', + 'SGVsbG8@', + 'SGVsbG8===', + 'SGVsb G8', + '====', + ], + }); + }); + + it('should validate Base64url with padding', () => { + test({ + validator: 'isBase64', + args: [{ urlSafe: true, padding: true }], + valid: [ + '', + 'SGVsbG8=', + 'U29mdHdhcmU=', + 'YW55IGNhcm5hbCBwbGVhc3VyZS4=', + 'SGVsbG8-', + 'SGVsbG8_', + ], + invalid: [ + 'SGVsbG8===', + 'SGVsbG8@', + 'SGVsb G8=', + '====', + ], + }); + }); + + it('should validate Base64url without padding', () => { + test({ + validator: 'isBase64', + args: [{ urlSafe: true, padding: false }], + valid: [ + '', + 'SGVsbG8', + 'U29mdHdhcmU', + 'YW55IGNhcm5hbCBwbGVhc3VyZS4', + 'SGVsbG8-', + 'SGVsbG8_', + ], + invalid: [ + 'SGVsbG8=', + 'SGVsbG8===', + 'SGVsbG8@', + 'SGVsb G8', + '====', + ], + }); + }); + + it('should handle mixed cases correctly', () => { + test({ + validator: 'isBase64', + args: [{ urlSafe: false, padding: true }], + valid: [ + '', + 'TWFu', + 'TWE=', + 'TQ==', + ], + invalid: [ + 'TWE', + 'TQ=', + 'TQ===', + ], + }); + + test({ + validator: 'isBase64', + args: [{ urlSafe: true, padding: false }], + valid: [ + '', + 'SGVsbG8', + 'SGVsbG8-', + 'SGVsbG8_', + ], + invalid: [ + 'SGVsbG8=', + 'SGVsbG8@', + 'SGVsb G8', + ], + }); + }); +}); diff --git a/test/validators/isBefore.test.js b/test/validators/isBefore.test.js new file mode 100644 index 000000000..298e5b410 --- /dev/null +++ b/test/validators/isBefore.test.js @@ -0,0 +1,119 @@ +import { describe } from 'mocha'; +import test from '../testFunctions'; + +describe('isBefore', () => { + describe('should validate dates a given end date', () => { + describe('new syntax', () => { + test({ + validator: 'isBefore', + args: [{ comparisonDate: '08/04/2011' }], + valid: ['2010-07-02', '2010-08-04', new Date(0).toString()], + invalid: ['08/04/2011', new Date(2011, 9, 10).toString()], + }); + test({ + validator: 'isBefore', + args: [{ comparisonDate: new Date(2011, 7, 4).toString() }], + valid: ['2010-07-02', '2010-08-04', new Date(0).toString()], + invalid: ['08/04/2011', new Date(2011, 9, 10).toString()], + }); + test({ + validator: 'isBefore', + args: [{ comparisonDate: '2011-08-03' }], + valid: ['1999-12-31'], + invalid: ['invalid date'], + }); + test({ + validator: 'isBefore', + args: [{ comparisonDate: 'invalid date' }], + invalid: ['invalid date', '1999-12-31'], + }); + }); + + describe('legacy syntax', () => { + test({ + validator: 'isBefore', + args: ['08/04/2011'], + valid: ['2010-07-02', '2010-08-04', new Date(0).toString()], + invalid: ['08/04/2011', new Date(2011, 9, 10).toString()], + }); + test({ + validator: 'isBefore', + args: [new Date(2011, 7, 4).toString()], + valid: ['2010-07-02', '2010-08-04', new Date(0).toString()], + invalid: ['08/04/2011', new Date(2011, 9, 10).toString()], + }); + test({ + validator: 'isBefore', + args: ['2011-08-03'], + valid: ['1999-12-31'], + invalid: ['invalid date'], + }); + test({ + validator: 'isBefore', + args: ['invalid date'], + invalid: ['invalid date', '1999-12-31'], + }); + }); + }); + + describe('should validate dates a default end date', () => { + describe('new syntax', () => { + test({ + validator: 'isBefore', + valid: [ + '2000-08-04', + new Date(0).toString(), + new Date(Date.now() - 86400000).toString(), + ], + invalid: ['2100-07-02', new Date(2217, 10, 10).toString()], + }); + test({ + validator: 'isBefore', + args: undefined, // will fall back to the current date + valid: ['1999-06-07'], + }); + test({ + validator: 'isBefore', + args: [], // will fall back to the current date + valid: ['1999-06-07'], + }); + test({ + validator: 'isBefore', + args: [undefined], // will fall back to the current date + valid: ['1999-06-07'], + }); + test({ + validator: 'isBefore', + args: [{ comparisonDate: undefined }], // will fall back to the current date + valid: ['1999-06-07'], + }); + }); + + describe('legacy syntax', () => { + test({ + validator: 'isBefore', + valid: [ + '2000-08-04', + new Date(0).toString(), + new Date(Date.now() - 86400000).toString(), + ], + invalid: ['2100-07-02', new Date(2217, 10, 10).toString()], + }); + test({ + validator: 'isBefore', + args: undefined, // will fall back to the current date + valid: ['1999-06-07'], + }); + test({ + validator: 'isBefore', + args: [], // will fall back to the current date + valid: ['1999-06-07'], + }); + test({ + validator: 'isBefore', + args: [undefined], // will fall back to the current date + valid: ['1999-06-07'], + }); + }); + }); +}); diff --git a/test/validators/isFQDN.test.js b/test/validators/isFQDN.test.js new file mode 100644 index 000000000..134bab005 --- /dev/null +++ b/test/validators/isFQDN.test.js @@ -0,0 +1,26 @@ +import test from '../testFunctions'; + +describe('isFQDN', () => { + it('should validate domain names.', () => { + test({ + validator: 'isFQDN', + args: [], + valid: [ + 'google.com', + ], + invalid: [ + 'google.l33t', + ], + }); + test({ + validator: 'isFQDN', + args: [{ allow_numeric_tld: true }], + valid: [ + 'google.com', + 'google.l33t', + ], + invalid: [ + ], + }); + }); +}); diff --git a/test/validators/isIP.test.js b/test/validators/isIP.test.js new file mode 100644 index 000000000..62ebbd112 --- /dev/null +++ b/test/validators/isIP.test.js @@ -0,0 +1,304 @@ +import test from '../testFunctions'; + +describe('isIP', () => { + it('should validate IP addresses', () => { + test({ + validator: 'isIP', + valid: [ + '127.0.0.1', + '0.0.0.0', + '255.255.255.255', + '1.2.3.4', + '::1', + '2001:db8:0000:1:1:1:1:1', + '2001:db8:3:4::192.0.2.33', + '2001:41d0:2:a141::1', + '::ffff:127.0.0.1', + '::0000', + '0000::', + '1::', + '1111:1:1:1:1:1:1:1', + 'fe80::a6db:30ff:fe98:e946', + '::', + '::8', + '::ffff:127.0.0.1', + '::ffff:255.255.255.255', + '::ffff:0:255.255.255.255', + '::2:3:4:5:6:7:8', + '::255.255.255.255', + '0:0:0:0:0:ffff:127.0.0.1', + '1:2:3:4:5:6:7::', + '1:2:3:4:5:6::8', + '1::7:8', + '1:2:3:4:5::7:8', + '1:2:3:4:5::8', + '1::6:7:8', + '1:2:3:4::6:7:8', + '1:2:3:4::8', + '1::5:6:7:8', + '1:2:3::5:6:7:8', + '1:2:3::8', + '1::4:5:6:7:8', + '1:2::4:5:6:7:8', + '1:2::8', + '1::3:4:5:6:7:8', + '1::8', + 'fe80::7:8%eth0', + 'fe80::7:8%1', + '64:ff9b::192.0.2.33', + '0:0:0:0:0:0:10.0.0.1', + ], + invalid: [ + 'abc', + '256.0.0.0', + '0.0.0.256', + '26.0.0.256', + '0200.200.200.200', + '200.0200.200.200', + '200.200.0200.200', + '200.200.200.0200', + '::banana', + 'banana::', + '::1banana', + '::1::', + '1:', + ':1', + ':1:1:1::2', + '1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1', + '::11111', + '11111:1:1:1:1:1:1:1', + '2001:db8:0000:1:1:1:1::1', + '0:0:0:0:0:0:ffff:127.0.0.1', + '0:0:0:0:ffff:127.0.0.1', + 'BC:e4d5:c:e7b9::%40i0nccymtl9cwfKo.5vaeXLSGRMe:EDh2qs5wkhnPws5xQKqafjfAMm6wGFCJ.bVFsZfb', + '1dC:0DF8:62D:3AC::%KTatXocjaFVioS0RTNQl4mA.V151o0RSy.JIu-D-D8.d3171ZWsSJ7PK4YjkJCRN0F', + ], + }); + + test({ + validator: 'isIP', + args: [{ version: 'invalid version' }], + valid: [], + invalid: [ + '127.0.0.1', + '0.0.0.0', + '255.255.255.255', + '1.2.3.4', + ], + }); + + test({ + validator: 'isIP', + args: [{ version: null }], + valid: [ + '127.0.0.1', + '0.0.0.0', + '255.255.255.255', + '1.2.3.4', + ], + }); + + test({ + validator: 'isIP', + args: [{ version: undefined }], + valid: [ + '127.0.0.1', + '0.0.0.0', + '255.255.255.255', + '1.2.3.4', + ], + }); + + test({ + validator: 'isIP', + args: [{ version: 4 }], + valid: [ + '127.0.0.1', + '0.0.0.0', + '255.255.255.255', + '1.2.3.4', + '255.0.0.1', + '0.0.1.1', + ], + invalid: [ + '::1', + '2001:db8:0000:1:1:1:1:1', + '::ffff:127.0.0.1', + '137.132.10.01', + '0.256.0.256', + '255.256.255.256', + ], + }); + + test({ + validator: 'isIP', + args: [{ version: 6 }], + valid: [ + '::1', + '2001:db8:0000:1:1:1:1:1', + '::ffff:127.0.0.1', + 'fe80::1234%1', + 'ff08::9abc%10', + 'ff08::9abc%interface10', + 'ff02::5678%pvc1.3', + ], + invalid: [ + '127.0.0.1', + '0.0.0.0', + '255.255.255.255', + '1.2.3.4', + '::ffff:287.0.0.1', + '%', + 'fe80::1234%', + 'fe80::1234%1%3%4', + 'fe80%fe80%', + ], + }); + + test({ + validator: 'isIP', + args: [{ version: 10 }], + valid: [], + invalid: [ + '127.0.0.1', + '0.0.0.0', + '255.255.255.255', + '1.2.3.4', + '::1', + '2001:db8:0000:1:1:1:1:1', + ], + }); + }); + + describe('legacy syntax', () => { + it('should validate IP addresses', () => { + test({ + validator: 'isIP', + valid: [ + '127.0.0.1', + '0.0.0.0', + '255.255.255.255', + '1.2.3.4', + '::1', + '2001:db8:0000:1:1:1:1:1', + '2001:db8:3:4::192.0.2.33', + '2001:41d0:2:a141::1', + '::ffff:127.0.0.1', + '::0000', + '0000::', + '1::', + '1111:1:1:1:1:1:1:1', + 'fe80::a6db:30ff:fe98:e946', + '::', + '::8', + '::ffff:127.0.0.1', + '::ffff:255.255.255.255', + '::ffff:0:255.255.255.255', + '::2:3:4:5:6:7:8', + '::255.255.255.255', + '0:0:0:0:0:ffff:127.0.0.1', + '1:2:3:4:5:6:7::', + '1:2:3:4:5:6::8', + '1::7:8', + '1:2:3:4:5::7:8', + '1:2:3:4:5::8', + '1::6:7:8', + '1:2:3:4::6:7:8', + '1:2:3:4::8', + '1::5:6:7:8', + '1:2:3::5:6:7:8', + '1:2:3::8', + '1::4:5:6:7:8', + '1:2::4:5:6:7:8', + '1:2::8', + '1::3:4:5:6:7:8', + '1::8', + 'fe80::7:8%eth0', + 'fe80::7:8%1', + '64:ff9b::192.0.2.33', + '0:0:0:0:0:0:10.0.0.1', + ], + invalid: [ + 'abc', + '256.0.0.0', + '0.0.0.256', + '26.0.0.256', + '0200.200.200.200', + '200.0200.200.200', + '200.200.0200.200', + '200.200.200.0200', + '::banana', + 'banana::', + '::1banana', + '::1::', + '1:', + ':1', + ':1:1:1::2', + '1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1', + '::11111', + '11111:1:1:1:1:1:1:1', + '2001:db8:0000:1:1:1:1::1', + '0:0:0:0:0:0:ffff:127.0.0.1', + '0:0:0:0:ffff:127.0.0.1', + ], + }); + test({ + validator: 'isIP', + args: [4], + valid: [ + '127.0.0.1', + '0.0.0.0', + '255.255.255.255', + '1.2.3.4', + '255.0.0.1', + '0.0.1.1', + ], + invalid: [ + '::1', + '2001:db8:0000:1:1:1:1:1', + '::ffff:127.0.0.1', + '137.132.10.01', + '0.256.0.256', + '255.256.255.256', + ], + }); + test({ + validator: 'isIP', + args: [6], + valid: [ + '::1', + '2001:db8:0000:1:1:1:1:1', + '::ffff:127.0.0.1', + 'fe80::1234%1', + 'ff08::9abc%10', + 'ff08::9abc%interface10', + 'ff02::5678%pvc1.3', + ], + invalid: [ + '127.0.0.1', + '0.0.0.0', + '255.255.255.255', + '1.2.3.4', + '::ffff:287.0.0.1', + '%', + 'fe80::1234%', + 'fe80::1234%1%3%4', + 'fe80%fe80%', + ], + }); + test({ + validator: 'isIP', + args: [10], + valid: [], + invalid: [ + '127.0.0.1', + '0.0.0.0', + '255.255.255.255', + '1.2.3.4', + '::1', + '2001:db8:0000:1:1:1:1:1', + ], + }); + }); + }); +}); diff --git a/test/validators/isISBN.test.js b/test/validators/isISBN.test.js new file mode 100644 index 000000000..99fb2e014 --- /dev/null +++ b/test/validators/isISBN.test.js @@ -0,0 +1,109 @@ +import test from '../testFunctions'; + +describe('isISBN', () => { + it('should validate ISBNs', () => { + test({ + validator: 'isISBN', + args: [{ version: 10 }], + valid: [ + '3836221195', '3-8362-2119-5', '3 8362 2119 5', + '1617290858', '1-61729-085-8', '1 61729 085-8', + '0007269706', '0-00-726970-6', '0 00 726970 6', + '3423214120', '3-423-21412-0', '3 423 21412 0', + '340101319X', '3-401-01319-X', '3 401 01319 X', + ], + invalid: [ + '3423214121', '3-423-21412-1', '3 423 21412 1', + '978-3836221191', '9783836221191', + '123456789a', 'foo', '', + ], + }); + test({ + validator: 'isISBN', + args: [{ version: 13 }], + valid: [ + '9783836221191', '978-3-8362-2119-1', '978 3 8362 2119 1', + '9783401013190', '978-3401013190', '978 3401013190', + '9784873113685', '978-4-87311-368-5', '978 4 87311 368 5', + ], + invalid: [ + '9783836221190', '978-3-8362-2119-0', '978 3 8362 2119 0', + '3836221195', '3-8362-2119-5', '3 8362 2119 5', + '01234567890ab', 'foo', '', + ], + }); + test({ + validator: 'isISBN', + valid: [ + '340101319X', + '9784873113685', + ], + invalid: [ + '3423214121', + '9783836221190', + ], + }); + test({ + validator: 'isISBN', + args: [{ version: 'foo' }], + invalid: [ + '340101319X', + '9784873113685', + ], + }); + }); + + describe('(legacy syntax)', () => { + it('should validate ISBNs', () => { + test({ + validator: 'isISBN', + args: [10], + valid: [ + '3836221195', '3-8362-2119-5', '3 8362 2119 5', + '1617290858', '1-61729-085-8', '1 61729 085-8', + '0007269706', '0-00-726970-6', '0 00 726970 6', + '3423214120', '3-423-21412-0', '3 423 21412 0', + '340101319X', '3-401-01319-X', '3 401 01319 X', + ], + invalid: [ + '3423214121', '3-423-21412-1', '3 423 21412 1', + '978-3836221191', '9783836221191', + '123456789a', 'foo', '', + ], + }); + test({ + validator: 'isISBN', + args: [13], + valid: [ + '9783836221191', '978-3-8362-2119-1', '978 3 8362 2119 1', + '9783401013190', '978-3401013190', '978 3401013190', + '9784873113685', '978-4-87311-368-5', '978 4 87311 368 5', + ], + invalid: [ + '9783836221190', '978-3-8362-2119-0', '978 3 8362 2119 0', + '3836221195', '3-8362-2119-5', '3 8362 2119 5', + '01234567890ab', 'foo', '', + ], + }); + test({ + validator: 'isISBN', + valid: [ + '340101319X', + '9784873113685', + ], + invalid: [ + '3423214121', + '9783836221190', + ], + }); + test({ + validator: 'isISBN', + args: ['foo'], + invalid: [ + '340101319X', + '9784873113685', + ], + }); + }); + }); +}); diff --git a/test/validators/isLength.test.js b/test/validators/isLength.test.js new file mode 100644 index 000000000..5c1444002 --- /dev/null +++ b/test/validators/isLength.test.js @@ -0,0 +1,144 @@ +import test from '../testFunctions'; + +describe('isLength', () => { + it('should return false for a string with length greater than the max', () => { + test({ + validator: 'isLength', + args: [{ max: 3 }], + invalid: ['test'], + }); + }); + + it('should return true for a string with length equal to the max', () => { + test({ + validator: 'isLength', + args: [{ max: 4 }], + valid: ['test'], + }); + }); + + it('should correctly calculate the length of a string with presentation sequences', () => { + test({ + validator: 'isLength', + args: [{ max: 4 }], + valid: ['test\uFE0F'], + }); + + test({ + validator: 'isLength', + args: [{ min: 5, max: 5 }], + valid: ['test\uFE0F\uFE0F'], + }); + + test({ + validator: 'isLength', + args: [{ min: 5, max: 5 }], + valid: ['\uFE0Ftest'], + }); + + test({ + validator: 'isLength', + args: [{ min: 9, max: 9 }], + valid: ['test\uFE0F\uFE0F\uFE0F\uFE0F\uFE0F\uFE0F'], + }); + }); + + it('should validate strings by length (deprecated api)', () => { + test({ + validator: 'isLength', + args: [2], + valid: ['abc', 'de', 'abcd'], + invalid: ['', 'a'], + }); + test({ + validator: 'isLength', + args: [2, 3], + valid: ['abc', 'de'], + invalid: ['', 'a', 'abcd'], + }); + test({ + validator: 'isLength', + args: [2, 3], + valid: ['干𩸽', '𠮷野家'], + invalid: ['', '𠀋', '千竈通り'], + }); + test({ + validator: 'isLength', + args: [0, 0], + valid: [''], + invalid: ['a', 'ab'], + }); + }); + + it('should validate strings by length', () => { + test({ + validator: 'isLength', + args: [{ min: 2 }], + valid: ['abc', 'de', 'abcd'], + invalid: ['', 'a'], + }); + test({ + validator: 'isLength', + args: [{ min: 2, max: 3 }], + valid: ['abc', 'de'], + invalid: ['', 'a', 'abcd'], + }); + test({ + validator: 'isLength', + args: [{ min: 2, max: 3 }], + valid: ['干𩸽', '𠮷野家'], + invalid: ['', '𠀋', '千竈通り'], + }); + test({ + validator: 'isLength', + args: [{ max: 3 }], + valid: ['abc', 'de', 'a', ''], + invalid: ['abcd'], + }); + test({ + validator: 'isLength', + args: [{ max: 6, discreteLengths: 5 }], + valid: ['abcd', 'vfd', 'ff', '', 'k'], + invalid: ['abcdefgh', 'hfjdksks'], + }); + test({ + validator: 'isLength', + args: [{ min: 2, max: 6, discreteLengths: 5 }], + valid: ['bsa', 'vfvd', 'ff'], + invalid: ['', ' ', 'hfskdunvc'], + }); + test({ + validator: 'isLength', + args: [{ min: 1, discreteLengths: 2 }], + valid: [' ', 'hello', 'bsa'], + invalid: [''], + }); + test({ + validator: 'isLength', + args: [{ max: 0 }], + valid: [''], + invalid: ['a', 'ab'], + }); + test({ + validator: 'isLength', + args: [{ min: 5, max: 10, discreteLengths: [2, 6, 8, 9] }], + valid: ['helloguy', 'shopping', 'validator', 'length'], + invalid: ['abcde', 'abcdefg'], + }); + test({ + validator: 'isLength', + args: [{ discreteLengths: '9' }], + valid: ['a', 'abcd', 'abcdefghijkl'], + invalid: [], + }); + test({ + validator: 'isLength', + valid: ['a', '', 'asds'], + }); + test({ + validator: 'isLength', + args: [{ max: 8 }], + valid: ['👩🦰👩👩👦👦🏳️🌈', '⏩︎⏩︎⏪︎⏪︎⏭︎⏭︎⏮︎⏮︎'], + }); + }); +}); diff --git a/validator.js b/validator.js deleted file mode 100644 index 45718aa94..000000000 --- a/validator.js +++ /dev/null @@ -1,1477 +0,0 @@ -/*! - * Copyright (c) 2016 Chris O'Hara - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : - typeof define === 'function' && define.amd ? define(factory) : - (global.validator = factory()); -}(this, (function () { 'use strict'; - -function assertString(input) { - var isString = typeof input === 'string' || input instanceof String; - - if (!isString) { - throw new TypeError('This library (validator.js) validates strings only'); - } -} - -function toDate(date) { - assertString(date); - date = Date.parse(date); - return !isNaN(date) ? new Date(date) : null; -} - -function toFloat(str) { - assertString(str); - return parseFloat(str); -} - -function toInt(str, radix) { - assertString(str); - return parseInt(str, radix || 10); -} - -function toBoolean(str, strict) { - assertString(str); - if (strict) { - return str === '1' || str === 'true'; - } - return str !== '0' && str !== 'false' && str !== ''; -} - -function equals(str, comparison) { - assertString(str); - return str === comparison; -} - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { - return typeof obj; -} : function (obj) { - return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; -}; - -function toString(input) { - if ((typeof input === 'undefined' ? 'undefined' : _typeof(input)) === 'object' && input !== null) { - if (typeof input.toString === 'function') { - input = input.toString(); - } else { - input = '[object Object]'; - } - } else if (input === null || typeof input === 'undefined' || isNaN(input) && !input.length) { - input = ''; - } - return String(input); -} - -function contains(str, elem) { - assertString(str); - return str.indexOf(toString(elem)) >= 0; -} - -function matches(str, pattern, modifiers) { - assertString(str); - if (Object.prototype.toString.call(pattern) !== '[object RegExp]') { - pattern = new RegExp(pattern, modifiers); - } - return pattern.test(str); -} - -function merge() { - var obj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; - var defaults = arguments[1]; - - for (var key in defaults) { - if (typeof obj[key] === 'undefined') { - obj[key] = defaults[key]; - } - } - return obj; -} - -/* eslint-disable prefer-rest-params */ -function isByteLength(str, options) { - assertString(str); - var min = void 0; - var max = void 0; - if ((typeof options === 'undefined' ? 'undefined' : _typeof(options)) === 'object') { - min = options.min || 0; - max = options.max; - } else { - // backwards compatibility: isByteLength(str, min [, max]) - min = arguments[1]; - max = arguments[2]; - } - var len = encodeURI(str).split(/%..|./).length - 1; - return len >= min && (typeof max === 'undefined' || len <= max); -} - -var default_fqdn_options = { - require_tld: true, - allow_underscores: false, - allow_trailing_dot: false -}; - -function isFQDN(str, options) { - assertString(str); - options = merge(options, default_fqdn_options); - - /* Remove the optional trailing dot before checking validity */ - if (options.allow_trailing_dot && str[str.length - 1] === '.') { - str = str.substring(0, str.length - 1); - } - var parts = str.split('.'); - if (options.require_tld) { - var tld = parts.pop(); - if (!parts.length || !/^([a-z\u00a1-\uffff]{2,}|xn[a-z0-9-]{2,})$/i.test(tld)) { - return false; - } - // disallow spaces - if (/[\s\u2002-\u200B\u202F\u205F\u3000\uFEFF\uDB40\uDC20]/.test(tld)) { - return false; - } - } - for (var part, i = 0; i < parts.length; i++) { - part = parts[i]; - if (options.allow_underscores) { - part = part.replace(/_/g, ''); - } - if (!/^[a-z\u00a1-\uffff0-9-]+$/i.test(part)) { - return false; - } - // disallow full-width chars - if (/[\uff01-\uff5e]/.test(part)) { - return false; - } - if (part[0] === '-' || part[part.length - 1] === '-') { - return false; - } - } - return true; -} - -var default_email_options = { - allow_display_name: false, - require_display_name: false, - allow_utf8_local_part: true, - require_tld: true -}; - -/* eslint-disable max-len */ -/* eslint-disable no-control-regex */ -var displayName = /^[a-z\d!#\$%&'\*\+\-\/=\?\^_`{\|}~\.\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+[a-z\d!#\$%&'\*\+\-\/=\?\^_`{\|}~\,\.\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF\s]*<(.+)>$/i; -var emailUserPart = /^[a-z\d!#\$%&'\*\+\-\/=\?\^_`{\|}~]+$/i; -var quotedEmailUser = /^([\s\x01-\x08\x0b\x0c\x0e-\x1f\x7f\x21\x23-\x5b\x5d-\x7e]|(\\[\x01-\x09\x0b\x0c\x0d-\x7f]))*$/i; -var emailUserUtf8Part = /^[a-z\d!#\$%&'\*\+\-\/=\?\^_`{\|}~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+$/i; -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; -/* eslint-enable max-len */ -/* eslint-enable no-control-regex */ - -function isEmail(str, options) { - assertString(str); - options = merge(options, default_email_options); - - if (options.require_display_name || options.allow_display_name) { - var display_email = str.match(displayName); - if (display_email) { - str = display_email[1]; - } else if (options.require_display_name) { - return false; - } - } - - var parts = str.split('@'); - var domain = parts.pop(); - var user = parts.join('@'); - - var lower_domain = domain.toLowerCase(); - if (lower_domain === 'gmail.com' || lower_domain === 'googlemail.com') { - user = user.replace(/\./g, '').toLowerCase(); - } - - if (!isByteLength(user, { max: 64 }) || !isByteLength(domain, { max: 254 })) { - return false; - } - - if (!isFQDN(domain, { require_tld: options.require_tld })) { - return false; - } - - if (user[0] === '"') { - user = user.slice(1, user.length - 1); - return options.allow_utf8_local_part ? quotedEmailUserUtf8.test(user) : quotedEmailUser.test(user); - } - - var pattern = options.allow_utf8_local_part ? emailUserUtf8Part : emailUserPart; - - var user_parts = user.split('.'); - for (var i = 0; i < user_parts.length; i++) { - if (!pattern.test(user_parts[i])) { - return false; - } - } - - return true; -} - -var ipv4Maybe = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/; -var ipv6Block = /^[0-9A-F]{1,4}$/i; - -function isIP(str) { - var version = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; - - assertString(str); - version = String(version); - if (!version) { - return isIP(str, 4) || isIP(str, 6); - } else if (version === '4') { - if (!ipv4Maybe.test(str)) { - return false; - } - var parts = str.split('.').sort(function (a, b) { - return a - b; - }); - return parts[3] <= 255; - } else if (version === '6') { - var blocks = str.split(':'); - var foundOmissionBlock = false; // marker to indicate :: - - // At least some OS accept the last 32 bits of an IPv6 address - // (i.e. 2 of the blocks) in IPv4 notation, and RFC 3493 says - // that '::ffff:a.b.c.d' is valid for IPv4-mapped IPv6 addresses, - // and '::a.b.c.d' is deprecated, but also valid. - var foundIPv4TransitionBlock = isIP(blocks[blocks.length - 1], 4); - var expectedNumberOfBlocks = foundIPv4TransitionBlock ? 7 : 8; - - if (blocks.length > expectedNumberOfBlocks) { - return false; - } - // initial or final :: - if (str === '::') { - return true; - } else if (str.substr(0, 2) === '::') { - blocks.shift(); - blocks.shift(); - foundOmissionBlock = true; - } else if (str.substr(str.length - 2) === '::') { - blocks.pop(); - blocks.pop(); - foundOmissionBlock = true; - } - - for (var i = 0; i < blocks.length; ++i) { - // test for a :: which can not be at the string start/end - // since those cases have been handled above - if (blocks[i] === '' && i > 0 && i < blocks.length - 1) { - if (foundOmissionBlock) { - return false; // multiple :: in address - } - foundOmissionBlock = true; - } else if (foundIPv4TransitionBlock && i === blocks.length - 1) { - // it has been checked before that the last - // block is a valid IPv4 address - } else if (!ipv6Block.test(blocks[i])) { - return false; - } - } - if (foundOmissionBlock) { - return blocks.length >= 1; - } - return blocks.length === expectedNumberOfBlocks; - } - return false; -} - -var default_url_options = { - protocols: ['http', 'https', 'ftp'], - require_tld: true, - require_protocol: false, - require_host: true, - require_valid_protocol: true, - allow_underscores: false, - allow_trailing_dot: false, - allow_protocol_relative_urls: false -}; - -var wrapped_ipv6 = /^\[([^\]]+)\](?::([0-9]+))?$/; - -function isRegExp(obj) { - return Object.prototype.toString.call(obj) === '[object RegExp]'; -} - -function checkHost(host, matches) { - for (var i = 0; i < matches.length; i++) { - var match = matches[i]; - if (host === match || isRegExp(match) && match.test(host)) { - return true; - } - } - return false; -} - -function isURL(url, options) { - assertString(url); - if (!url || url.length >= 2083 || /[\s<>]/.test(url)) { - return false; - } - if (url.indexOf('mailto:') === 0) { - return false; - } - options = merge(options, default_url_options); - var protocol = void 0, - auth = void 0, - host = void 0, - hostname = void 0, - port = void 0, - port_str = void 0, - split = void 0, - ipv6 = void 0; - - split = url.split('#'); - url = split.shift(); - - split = url.split('?'); - url = split.shift(); - - split = url.split('://'); - if (split.length > 1) { - protocol = split.shift(); - if (options.require_valid_protocol && options.protocols.indexOf(protocol) === -1) { - return false; - } - } else if (options.require_protocol) { - return false; - } else if (options.allow_protocol_relative_urls && url.substr(0, 2) === '//') { - split[0] = url.substr(2); - } - url = split.join('://'); - - if (url === '') { - return false; - } - - split = url.split('/'); - url = split.shift(); - - if (url === '' && !options.require_host) { - return true; - } - - split = url.split('@'); - if (split.length > 1) { - auth = split.shift(); - if (auth.indexOf(':') >= 0 && auth.split(':').length > 2) { - return false; - } - } - hostname = split.join('@'); - - port_str = null; - ipv6 = null; - var ipv6_match = hostname.match(wrapped_ipv6); - if (ipv6_match) { - host = ''; - ipv6 = ipv6_match[1]; - port_str = ipv6_match[2] || null; - } else { - split = hostname.split(':'); - host = split.shift(); - if (split.length) { - port_str = split.join(':'); - } - } - - if (port_str !== null) { - port = parseInt(port_str, 10); - if (!/^[0-9]+$/.test(port_str) || port <= 0 || port > 65535) { - return false; - } - } - - if (!isIP(host) && !isFQDN(host, options) && (!ipv6 || !isIP(ipv6, 6))) { - return false; - } - - host = host || ipv6; - - if (options.host_whitelist && !checkHost(host, options.host_whitelist)) { - return false; - } - if (options.host_blacklist && checkHost(host, options.host_blacklist)) { - return false; - } - - return true; -} - -var macAddress = /^([0-9a-fA-F][0-9a-fA-F]:){5}([0-9a-fA-F][0-9a-fA-F])$/; - -function isMACAddress(str) { - assertString(str); - return macAddress.test(str); -} - -function isBoolean(str) { - assertString(str); - return ['true', 'false', '1', '0'].indexOf(str) >= 0; -} - -var alpha = { - 'en-US': /^[A-Z]+$/i, - 'cs-CZ': /^[A-ZÁČĎÉĚÍŇÓŘŠŤÚŮÝŽ]+$/i, - 'da-DK': /^[A-ZÆØÅ]+$/i, - 'de-DE': /^[A-ZÄÖÜß]+$/i, - 'es-ES': /^[A-ZÁÉÍÑÓÚÜ]+$/i, - 'fr-FR': /^[A-ZÀÂÆÇÉÈÊËÏÎÔŒÙÛÜŸ]+$/i, - 'it-IT': /^[A-ZÀÉÈÌÎÓÒÙ]+$/i, - 'nb-NO': /^[A-ZÆØÅ]+$/i, - 'nl-NL': /^[A-ZÁÉËÏÓÖÜÚ]+$/i, - 'nn-NO': /^[A-ZÆØÅ]+$/i, - 'hu-HU': /^[A-ZÁÉÍÓÖŐÚÜŰ]+$/i, - 'pl-PL': /^[A-ZĄĆĘŚŁŃÓŻŹ]+$/i, - 'pt-PT': /^[A-ZÃÁÀÂÇÉÊÍÕÓÔÚÜ]+$/i, - 'ru-RU': /^[А-ЯЁ]+$/i, - 'sr-RS@latin': /^[A-ZČĆŽŠĐ]+$/i, - 'sr-RS': /^[А-ЯЂЈЉЊЋЏ]+$/i, - 'sv-SE': /^[A-ZÅÄÖ]+$/i, - 'tr-TR': /^[A-ZÇĞİıÖŞÜ]+$/i, - 'uk-UA': /^[А-ЩЬЮЯЄIЇҐі]+$/i, - ar: /^[ءآأؤإئابةتثجحخدذرزسشصضطظعغفقكلمنهوىيًٌٍَُِّْٰ]+$/ -}; - -var alphanumeric = { - 'en-US': /^[0-9A-Z]+$/i, - 'cs-CZ': /^[0-9A-ZÁČĎÉĚÍŇÓŘŠŤÚŮÝŽ]+$/i, - 'da-DK': /^[0-9A-ZÆØÅ]+$/i, - 'de-DE': /^[0-9A-ZÄÖÜß]+$/i, - 'es-ES': /^[0-9A-ZÁÉÍÑÓÚÜ]+$/i, - 'fr-FR': /^[0-9A-ZÀÂÆÇÉÈÊËÏÎÔŒÙÛÜŸ]+$/i, - 'it-IT': /^[0-9A-ZÀÉÈÌÎÓÒÙ]+$/i, - 'hu-HU': /^[0-9A-ZÁÉÍÓÖŐÚÜŰ]+$/i, - 'nb-NO': /^[0-9A-ZÆØÅ]+$/i, - 'nl-NL': /^[0-9A-ZÁÉËÏÓÖÜÚ]+$/i, - 'nn-NO': /^[0-9A-ZÆØÅ]+$/i, - 'pl-PL': /^[0-9A-ZĄĆĘŚŁŃÓŻŹ]+$/i, - 'pt-PT': /^[0-9A-ZÃÁÀÂÇÉÊÍÕÓÔÚÜ]+$/i, - 'ru-RU': /^[0-9А-ЯЁ]+$/i, - 'sr-RS@latin': /^[0-9A-ZČĆŽŠĐ]+$/i, - 'sr-RS': /^[0-9А-ЯЂЈЉЊЋЏ]+$/i, - 'sv-SE': /^[0-9A-ZÅÄÖ]+$/i, - 'tr-TR': /^[0-9A-ZÇĞİıÖŞÜ]+$/i, - 'uk-UA': /^[0-9А-ЩЬЮЯЄIЇҐі]+$/i, - ar: /^[٠١٢٣٤٥٦٧٨٩0-9ءآأؤإئابةتثجحخدذرزسشصضطظعغفقكلمنهوىيًٌٍَُِّْٰ]+$/ -}; - -var decimal = { - 'en-US': '.', - ar: '٫' -}; - -var englishLocales = ['AU', 'GB', 'HK', 'IN', 'NZ', 'ZA', 'ZM']; - -for (var locale, i = 0; i < englishLocales.length; i++) { - locale = 'en-' + englishLocales[i]; - alpha[locale] = alpha['en-US']; - alphanumeric[locale] = alphanumeric['en-US']; - decimal[locale] = decimal['en-US']; -} - -// Source: http://www.localeplanet.com/java/ -var arabicLocales = ['AE', 'BH', 'DZ', 'EG', 'IQ', 'JO', 'KW', 'LB', 'LY', 'MA', 'QM', 'QA', 'SA', 'SD', 'SY', 'TN', 'YE']; - -for (var _locale, _i = 0; _i < arabicLocales.length; _i++) { - _locale = 'ar-' + arabicLocales[_i]; - alpha[_locale] = alpha.ar; - alphanumeric[_locale] = alphanumeric.ar; - decimal[_locale] = decimal.ar; -} - -// Source: https://en.wikipedia.org/wiki/Decimal_mark -var dotDecimal = []; -var commaDecimal = ['cs-CZ', 'da-DK', 'de-DE', 'es-ES', 'fr-FR', 'it-IT', 'hu-HU', 'nb-NO', 'nn-NO', 'nl-NL', 'pl-Pl', 'pt-PT', 'ru-RU', 'sr-RS@latin', 'sr-RS', 'sv-SE', 'tr-TR', 'uk-UA']; - -for (var _i2 = 0; _i2 < dotDecimal.length; _i2++) { - decimal[dotDecimal[_i2]] = decimal['en-US']; -} - -for (var _i3 = 0; _i3 < commaDecimal.length; _i3++) { - decimal[commaDecimal[_i3]] = ','; -} - -alpha['pt-BR'] = alpha['pt-PT']; -alphanumeric['pt-BR'] = alphanumeric['pt-PT']; -decimal['pt-BR'] = decimal['pt-PT']; - -function isAlpha(str) { - var locale = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'en-US'; - - assertString(str); - if (locale in alpha) { - return alpha[locale].test(str); - } - throw new Error('Invalid locale \'' + locale + '\''); -} - -function isAlphanumeric(str) { - var locale = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'en-US'; - - assertString(str); - if (locale in alphanumeric) { - return alphanumeric[locale].test(str); - } - throw new Error('Invalid locale \'' + locale + '\''); -} - -var numeric = /^[-+]?[0-9]+$/; - -function isNumeric(str) { - assertString(str); - return numeric.test(str); -} - -var int = /^(?:[-+]?(?:0|[1-9][0-9]*))$/; -var intLeadingZeroes = /^[-+]?[0-9]+$/; - -function isInt(str, options) { - assertString(str); - options = options || {}; - - // Get the regex to use for testing, based on whether - // leading zeroes are allowed or not. - var regex = options.hasOwnProperty('allow_leading_zeroes') && !options.allow_leading_zeroes ? int : intLeadingZeroes; - - // Check min/max/lt/gt - var minCheckPassed = !options.hasOwnProperty('min') || str >= options.min; - var maxCheckPassed = !options.hasOwnProperty('max') || str <= options.max; - var ltCheckPassed = !options.hasOwnProperty('lt') || str < options.lt; - var gtCheckPassed = !options.hasOwnProperty('gt') || str > options.gt; - - return regex.test(str) && minCheckPassed && maxCheckPassed && ltCheckPassed && gtCheckPassed; -} - -function isPort(str) { - return isInt(str, { min: 0, max: 65535 }); -} - -function isLowercase(str) { - assertString(str); - return str === str.toLowerCase(); -} - -function isUppercase(str) { - assertString(str); - return str === str.toUpperCase(); -} - -/* eslint-disable no-control-regex */ -var ascii = /^[\x00-\x7F]+$/; -/* eslint-enable no-control-regex */ - -function isAscii(str) { - assertString(str); - return ascii.test(str); -} - -var fullWidth = /[^\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]/; - -function isFullWidth(str) { - assertString(str); - return fullWidth.test(str); -} - -var halfWidth = /[\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]/; - -function isHalfWidth(str) { - assertString(str); - return halfWidth.test(str); -} - -function isVariableWidth(str) { - assertString(str); - return fullWidth.test(str) && halfWidth.test(str); -} - -/* eslint-disable no-control-regex */ -var multibyte = /[^\x00-\x7F]/; -/* eslint-enable no-control-regex */ - -function isMultibyte(str) { - assertString(str); - return multibyte.test(str); -} - -var surrogatePair = /[\uD800-\uDBFF][\uDC00-\uDFFF]/; - -function isSurrogatePair(str) { - assertString(str); - return surrogatePair.test(str); -} - -function isFloat(str, options) { - assertString(str); - options = options || {}; - var float = new RegExp('^(?:[-+])?(?:[0-9]+)?(?:\\' + (options.locale ? decimal[options.locale] : '.') + '[0-9]*)?(?:[eE][\\+\\-]?(?:[0-9]+))?$'); - if (str === '' || str === '.' || str === '-' || str === '+') { - return false; - } - 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); -} - -function decimalRegExp(options) { - var regExp = new RegExp('^[-+]?([0-9]+)?(\\' + decimal[options.locale] + '[0-9]{' + options.decimal_digits + '})' + (options.force_decimal ? '' : '?') + '$'); - return regExp; -} - -var default_decimal_options = { - force_decimal: false, - decimal_digits: '1,', - locale: 'en-US' -}; - -var blacklist = ['', '-', '+']; - -function isDecimal(str, options) { - assertString(str); - options = merge(options, default_decimal_options); - if (options.locale in decimal) { - return !blacklist.includes(str.replace(/ /g, '')) && decimalRegExp(options).test(str); - } - throw new Error('Invalid locale \'' + options.locale + '\''); -} - -var hexadecimal = /^[0-9A-F]+$/i; - -function isHexadecimal(str) { - assertString(str); - return hexadecimal.test(str); -} - -function isDivisibleBy(str, num) { - assertString(str); - return toFloat(str) % parseInt(num, 10) === 0; -} - -var hexcolor = /^#?([0-9A-F]{3}|[0-9A-F]{6})$/i; - -function isHexColor(str) { - assertString(str); - return hexcolor.test(str); -} - -// see http://isrc.ifpi.org/en/isrc-standard/code-syntax -var isrc = /^[A-Z]{2}[0-9A-Z]{3}\d{2}\d{5}$/; - -function isISRC(str) { - assertString(str); - return isrc.test(str); -} - -var md5 = /^[a-f0-9]{32}$/; - -function isMD5(str) { - assertString(str); - return md5.test(str); -} - -var lengths = { - md5: 32, - md4: 32, - sha1: 40, - sha256: 64, - sha384: 96, - sha512: 128, - ripemd128: 32, - ripemd160: 40, - tiger128: 32, - tiger160: 40, - tiger192: 48, - crc32: 8, - crc32b: 8 -}; - -function isHash(str, algorithm) { - assertString(str); - var hash = new RegExp('^[a-f0-9]{' + lengths[algorithm] + '}$'); - return hash.test(str); -} - -function isJSON(str) { - assertString(str); - try { - var obj = JSON.parse(str); - return !!obj && (typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) === 'object'; - } catch (e) {/* ignore */} - return false; -} - -function isEmpty(str) { - assertString(str); - return str.length === 0; -} - -/* eslint-disable prefer-rest-params */ -function isLength(str, options) { - assertString(str); - var min = void 0; - var max = void 0; - if ((typeof options === 'undefined' ? 'undefined' : _typeof(options)) === 'object') { - min = options.min || 0; - max = options.max; - } else { - // backwards compatibility: isLength(str, min [, max]) - min = arguments[1]; - max = arguments[2]; - } - var surrogatePairs = str.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g) || []; - var len = str.length - surrogatePairs.length; - return len >= min && (typeof max === 'undefined' || len <= max); -} - -var uuid = { - 3: /^[0-9A-F]{8}-[0-9A-F]{4}-3[0-9A-F]{3}-[0-9A-F]{4}-[0-9A-F]{12}$/i, - 4: /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i, - 5: /^[0-9A-F]{8}-[0-9A-F]{4}-5[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i, - all: /^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i -}; - -function isUUID(str) { - var version = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'all'; - - assertString(str); - var pattern = uuid[version]; - return pattern && pattern.test(str); -} - -function isMongoId(str) { - assertString(str); - return isHexadecimal(str) && str.length === 24; -} - -function isAfter(str) { - var date = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : String(new Date()); - - assertString(str); - var comparison = toDate(date); - var original = toDate(str); - return !!(original && comparison && original > comparison); -} - -function isBefore(str) { - var date = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : String(new Date()); - - assertString(str); - var comparison = toDate(date); - var original = toDate(str); - return !!(original && comparison && original < comparison); -} - -function isIn(str, options) { - assertString(str); - var i = void 0; - if (Object.prototype.toString.call(options) === '[object Array]') { - var array = []; - for (i in options) { - if ({}.hasOwnProperty.call(options, i)) { - array[i] = toString(options[i]); - } - } - return array.indexOf(str) >= 0; - } else if ((typeof options === 'undefined' ? 'undefined' : _typeof(options)) === 'object') { - return options.hasOwnProperty(str); - } else if (options && typeof options.indexOf === 'function') { - return options.indexOf(str) >= 0; - } - return false; -} - -/* eslint-disable max-len */ -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})$/; -/* eslint-enable max-len */ - -function isCreditCard(str) { - assertString(str); - var sanitized = str.replace(/[- ]+/g, ''); - if (!creditCard.test(sanitized)) { - return false; - } - var sum = 0; - var digit = void 0; - var tmpNum = void 0; - var shouldDouble = void 0; - for (var i = sanitized.length - 1; i >= 0; i--) { - digit = sanitized.substring(i, i + 1); - tmpNum = parseInt(digit, 10); - if (shouldDouble) { - tmpNum *= 2; - if (tmpNum >= 10) { - sum += tmpNum % 10 + 1; - } else { - sum += tmpNum; - } - } else { - sum += tmpNum; - } - shouldDouble = !shouldDouble; - } - return !!(sum % 10 === 0 ? sanitized : false); -} - -var isin = /^[A-Z]{2}[0-9A-Z]{9}[0-9]$/; - -function isISIN(str) { - assertString(str); - if (!isin.test(str)) { - return false; - } - - var checksumStr = str.replace(/[A-Z]/g, function (character) { - return parseInt(character, 36); - }); - - var sum = 0; - var digit = void 0; - var tmpNum = void 0; - var shouldDouble = true; - for (var i = checksumStr.length - 2; i >= 0; i--) { - digit = checksumStr.substring(i, i + 1); - tmpNum = parseInt(digit, 10); - if (shouldDouble) { - tmpNum *= 2; - if (tmpNum >= 10) { - sum += tmpNum + 1; - } else { - sum += tmpNum; - } - } else { - sum += tmpNum; - } - shouldDouble = !shouldDouble; - } - - return parseInt(str.substr(str.length - 1), 10) === (10000 - sum) % 10; -} - -var isbn10Maybe = /^(?:[0-9]{9}X|[0-9]{10})$/; -var isbn13Maybe = /^(?:[0-9]{13})$/; -var factor = [1, 3]; - -function isISBN(str) { - var version = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; - - assertString(str); - version = String(version); - if (!version) { - return isISBN(str, 10) || isISBN(str, 13); - } - var sanitized = str.replace(/[\s-]+/g, ''); - var checksum = 0; - var i = void 0; - if (version === '10') { - if (!isbn10Maybe.test(sanitized)) { - return false; - } - for (i = 0; i < 9; i++) { - checksum += (i + 1) * sanitized.charAt(i); - } - if (sanitized.charAt(9) === 'X') { - checksum += 10 * 10; - } else { - checksum += 10 * sanitized.charAt(9); - } - if (checksum % 11 === 0) { - return !!sanitized; - } - } else if (version === '13') { - if (!isbn13Maybe.test(sanitized)) { - return false; - } - for (i = 0; i < 12; i++) { - checksum += factor[i % 2] * sanitized.charAt(i); - } - if (sanitized.charAt(12) - (10 - checksum % 10) % 10 === 0) { - return !!sanitized; - } - } - return false; -} - -var issn = '^\\d{4}-?\\d{3}[\\dX]$'; - -function isISSN(str) { - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - - assertString(str); - var testIssn = issn; - testIssn = options.require_hyphen ? testIssn.replace('?', '') : testIssn; - testIssn = options.case_sensitive ? new RegExp(testIssn) : new RegExp(testIssn, 'i'); - if (!testIssn.test(str)) { - return false; - } - var issnDigits = str.replace('-', ''); - var position = 8; - var checksum = 0; - var _iteratorNormalCompletion = true; - var _didIteratorError = false; - var _iteratorError = undefined; - - try { - for (var _iterator = issnDigits[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { - var digit = _step.value; - - var digitValue = digit.toUpperCase() === 'X' ? 10 : +digit; - checksum += digitValue * position; - --position; - } - } catch (err) { - _didIteratorError = true; - _iteratorError = err; - } finally { - try { - if (!_iteratorNormalCompletion && _iterator.return) { - _iterator.return(); - } - } finally { - if (_didIteratorError) { - throw _iteratorError; - } - } - } - - return checksum % 11 === 0; -} - -/* eslint-disable max-len */ -var phones = { - 'ar-AE': /^((\+?971)|0)?5[024568]\d{7}$/, - 'ar-DZ': /^(\+?213|0)(5|6|7)\d{8}$/, - 'ar-EG': /^((\+?20)|0)?1[012]\d{8}$/, - 'ar-JO': /^(\+?962|0)?7[789]\d{7}$/, - 'ar-SA': /^(!?(\+?966)|0)?5\d{8}$/, - 'ar-SY': /^(!?(\+?963)|0)?9\d{8}$/, - 'cs-CZ': /^(\+?420)? ?[1-9][0-9]{2} ?[0-9]{3} ?[0-9]{3}$/, - 'da-DK': /^(\+?45)?\s?\d{2}\s?\d{2}\s?\d{2}\s?\d{2}$/, - 'de-DE': /^(\+?49[ \.\-])?([\(]{1}[0-9]{1,6}[\)])?([0-9 \.\-\/]{3,20})((x|ext|extension)[ ]?[0-9]{1,4})?$/, - 'el-GR': /^(\+?30)?(69\d{8})$/, - 'en-AU': /^(\+?61|0)4\d{8}$/, - 'en-GB': /^(\+?44|0)7\d{9}$/, - 'en-HK': /^(\+?852\-?)?[456789]\d{3}\-?\d{4}$/, - 'en-IN': /^(\+?91|0)?[789]\d{9}$/, - 'en-KE': /^(\+?254|0)?[7]\d{8}$/, - 'en-NG': /^(\+?234|0)?[789]\d{9}$/, - 'en-NZ': /^(\+?64|0)2\d{7,9}$/, - 'en-PK': /^((\+92)|(0092))-{0,1}\d{3}-{0,1}\d{7}$|^\d{11}$|^\d{4}-\d{7}$/, - 'en-RW': /^(\+?250|0)?[7]\d{8}$/, - 'en-SG': /^(\+65)?[89]\d{7}$/, - 'en-TZ': /^(\+?255|0)?[67]\d{8}$/, - 'en-UG': /^(\+?256|0)?[7]\d{8}$/, - 'en-US': /^(\+?1)?[2-9]\d{2}[2-9](?!11)\d{6}$/, - 'en-ZA': /^(\+?27|0)\d{9}$/, - 'en-ZM': /^(\+?26)?09[567]\d{7}$/, - 'es-ES': /^(\+?34)?(6\d{1}|7[1234])\d{7}$/, - 'et-EE': /^(\+?372)?\s?(5|8[1-4])\s?([0-9]\s?){6,7}$/, - 'fa-IR': /^(\+?98[\-\s]?|0)9[0-39]\d[\-\s]?\d{3}[\-\s]?\d{4}$/, - 'fi-FI': /^(\+?358|0)\s?(4(0|1|2|4|5|6)?|50)\s?(\d\s?){4,8}\d$/, - 'fo-FO': /^(\+?298)?\s?\d{2}\s?\d{2}\s?\d{2}$/, - 'fr-FR': /^(\+?33|0)[67]\d{8}$/, - 'he-IL': /^(\+972|0)([23489]|5[0248]|77)[1-9]\d{6}/, - 'hu-HU': /^(\+?36)(20|30|70)\d{7}$/, - 'id-ID': /^(\+?62|0[1-9])[\s|\d]+$/, - 'it-IT': /^(\+?39)?\s?3\d{2} ?\d{6,7}$/, - 'ja-JP': /^(\+?81|0)[789]0[ \-]?[1-9]\d{2}[ \-]?\d{5}$/, - 'kl-GL': /^(\+?299)?\s?\d{2}\s?\d{2}\s?\d{2}$/, - 'ko-KR': /^((\+?82)[ \-]?)?0?1([0|1|6|7|8|9]{1})[ \-]?\d{3,4}[ \-]?\d{4}$/, - 'lt-LT': /^(\+370|8)\d{8}$/, - 'ms-MY': /^(\+?6?01){1}(([145]{1}(\-|\s)?\d{7,8})|([236789]{1}(\s|\-)?\d{7}))$/, - 'nb-NO': /^(\+?47)?[49]\d{7}$/, - 'nl-BE': /^(\+?32|0)4?\d{8}$/, - 'nn-NO': /^(\+?47)?[49]\d{7}$/, - 'pl-PL': /^(\+?48)? ?[5-8]\d ?\d{3} ?\d{2} ?\d{2}$/, - 'pt-BR': /^(\+?55|0)\-?[1-9]{2}\-?[2-9]{1}\d{3,4}\-?\d{4}$/, - 'pt-PT': /^(\+?351)?9[1236]\d{7}$/, - 'ro-RO': /^(\+?4?0)\s?7\d{2}(\/|\s|\.|\-)?\d{3}(\s|\.|\-)?\d{3}$/, - 'ru-RU': /^(\+?7|8)?9\d{9}$/, - 'sk-SK': /^(\+?421)? ?[1-9][0-9]{2} ?[0-9]{3} ?[0-9]{3}$/, - 'sr-RS': /^(\+3816|06)[- \d]{5,9}$/, - 'tr-TR': /^(\+?90|0)?5\d{9}$/, - 'uk-UA': /^(\+?38|8)?0\d{9}$/, - 'vi-VN': /^(\+?84|0)?((1(2([0-9])|6([2-9])|88|99))|(9((?!5)[0-9])))([0-9]{7})$/, - 'zh-CN': /^(\+?0?86\-?)?1[345789]\d{9}$/, - 'zh-TW': /^(\+?886\-?|0)?9\d{8}$/ -}; -/* eslint-enable max-len */ - -// aliases -phones['en-CA'] = phones['en-US']; -phones['fr-BE'] = phones['nl-BE']; -phones['zh-HK'] = phones['en-HK']; - -function isMobilePhone(str, locale) { - assertString(str); - if (locale in phones) { - return phones[locale].test(str); - } else if (locale === 'any') { - for (var key in phones) { - if (phones.hasOwnProperty(key)) { - var phone = phones[key]; - if (phone.test(str)) { - return true; - } - } - } - return false; - } - throw new Error('Invalid locale \'' + locale + '\''); -} - -function currencyRegex(options) { - var decimal_digits = '\\d{' + options.digits_after_decimal[0] + '}'; - options.digits_after_decimal.forEach(function (digit, index) { - if (index !== 0) decimal_digits = decimal_digits + '|\\d{' + digit + '}'; - }); - var symbol = '(\\' + options.symbol.replace(/\./g, '\\.') + ')' + (options.require_symbol ? '' : '?'), - negative = '-?', - whole_dollar_amount_without_sep = '[1-9]\\d*', - whole_dollar_amount_with_sep = '[1-9]\\d{0,2}(\\' + options.thousands_separator + '\\d{3})*', - valid_whole_dollar_amounts = ['0', whole_dollar_amount_without_sep, whole_dollar_amount_with_sep], - whole_dollar_amount = '(' + valid_whole_dollar_amounts.join('|') + ')?', - decimal_amount = '(\\' + options.decimal_separator + '(' + decimal_digits + '))' + (options.require_decimal ? '' : '?'); - var pattern = whole_dollar_amount + (options.allow_decimal || options.require_decimal ? decimal_amount : ''); - - // default is negative sign before symbol, but there are two other options (besides parens) - if (options.allow_negatives && !options.parens_for_negatives) { - if (options.negative_sign_after_digits) { - pattern += negative; - } else if (options.negative_sign_before_digits) { - pattern = negative + pattern; - } - } - - // South African Rand, for example, uses R 123 (space) and R-123 (no space) - if (options.allow_negative_sign_placeholder) { - pattern = '( (?!\\-))?' + pattern; - } else if (options.allow_space_after_symbol) { - pattern = ' ?' + pattern; - } else if (options.allow_space_after_digits) { - pattern += '( (?!$))?'; - } - - if (options.symbol_after_digits) { - pattern += symbol; - } else { - pattern = symbol + pattern; - } - - if (options.allow_negatives) { - if (options.parens_for_negatives) { - pattern = '(\\(' + pattern + '\\)|' + pattern + ')'; - } else if (!(options.negative_sign_before_digits || options.negative_sign_after_digits)) { - pattern = negative + pattern; - } - } - - // ensure there's a dollar and/or decimal amount, and that - // it doesn't start with a space or a negative sign followed by a space - return new RegExp('^(?!-? )(?=.*\\d)' + pattern + '$'); -} - -var default_currency_options = { - symbol: '$', - require_symbol: false, - allow_space_after_symbol: false, - symbol_after_digits: false, - allow_negatives: true, - parens_for_negatives: false, - negative_sign_before_digits: false, - negative_sign_after_digits: false, - allow_negative_sign_placeholder: false, - thousands_separator: ',', - decimal_separator: '.', - allow_decimal: true, - require_decimal: false, - digits_after_decimal: [2], - allow_space_after_digits: false -}; - -function isCurrency(str, options) { - assertString(str); - options = merge(options, default_currency_options); - return currencyRegex(options).test(str); -} - -/* eslint-disable max-len */ -// from http://goo.gl/0ejHHW -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)?)?)?)?$/; -/* eslint-enable max-len */ - -function isISO8601(str) { - assertString(str); - return iso8601.test(str); -} - -// from https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 -var validISO31661Alpha2CountriesCodes = ['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', 'DE', 'DJ', 'DK', 'DM', 'DO', 'DZ', 'EC', 'EE', 'EG', 'EH', 'ER', 'ES', 'ET', 'FI', 'FJ', 'FK', 'FM', 'FO', 'FR', 'GA', 'GB', 'GD', 'GE', 'GF', 'GG', 'GH', 'GI', 'GL', 'GM', 'GN', 'GP', 'GQ', 'GR', 'GS', 'GT', 'GU', 'GW', 'GY', 'HK', 'HM', 'HN', 'HR', 'HT', 'HU', 'ID', 'IE', 'IL', 'IM', 'IN', 'IO', 'IQ', 'IR', 'IS', 'IT', 'JE', 'JM', 'JO', 'JP', 'KE', 'KG', 'KH', 'KI', 'KM', 'KN', 'KP', 'KR', 'KW', 'KY', 'KZ', 'LA', 'LB', 'LC', 'LI', 'LK', 'LR', 'LS', 'LT', 'LU', 'LV', 'LY', 'MA', 'MC', 'MD', 'ME', 'MF', 'MG', 'MH', 'MK', 'ML', 'MM', 'MN', 'MO', 'MP', 'MQ', 'MR', 'MS', 'MT', 'MU', 'MV', 'MW', 'MX', 'MY', 'MZ', 'NA', 'NC', 'NE', 'NF', 'NG', 'NI', 'NL', 'NO', 'NP', 'NR', 'NU', 'NZ', 'OM', 'PA', 'PE', 'PF', 'PG', 'PH', 'PK', 'PL', 'PM', 'PN', 'PR', 'PS', 'PT', 'PW', 'PY', 'QA', 'RE', 'RO', 'RS', 'RU', 'RW', 'SA', 'SB', 'SC', 'SD', 'SE', 'SG', 'SH', 'SI', 'SJ', 'SK', 'SL', 'SM', 'SN', 'SO', 'SR', 'SS', 'ST', 'SV', 'SX', 'SY', 'SZ', 'TC', 'TD', 'TF', 'TG', 'TH', 'TJ', 'TK', 'TL', 'TM', 'TN', 'TO', 'TR', 'TT', 'TV', 'TW', 'TZ', 'UA', 'UG', 'UM', 'US', 'UY', 'UZ', 'VA', 'VC', 'VE', 'VG', 'VI', 'VN', 'VU', 'WF', 'WS', 'YE', 'YT', 'ZA', 'ZM', 'ZW']; - -function isISO31661Alpha2(str) { - assertString(str); - return validISO31661Alpha2CountriesCodes.includes(str.toUpperCase()); -} - -var notBase64 = /[^A-Z0-9+\/=]/i; - -function isBase64(str) { - assertString(str); - var len = str.length; - if (!len || len % 4 !== 0 || notBase64.test(str)) { - return false; - } - var firstPaddingChar = str.indexOf('='); - return firstPaddingChar === -1 || firstPaddingChar === len - 1 || firstPaddingChar === len - 2 && str[len - 1] === '='; -} - -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 - -function isDataURI(str) { - assertString(str); - return dataURI.test(str); -} - -var lat = /^\(?[+-]?(90(\.0+)?|[1-8]?\d(\.\d+)?)$/; -var long = /^\s?[+-]?(180(\.0+)?|1[0-7]\d(\.\d+)?|\d{1,2}(\.\d+)?)\)?$/; - -var isLatLong = function (str) { - assertString(str); - if (!str.includes(',')) return false; - var pair = str.split(','); - return lat.test(pair[0]) && long.test(pair[1]); -}; - -// common patterns -var threeDigit = /^\d{3}$/; -var fourDigit = /^\d{4}$/; -var fiveDigit = /^\d{5}$/; -var sixDigit = /^\d{6}$/; - -var patterns = { - AT: fourDigit, - AU: fourDigit, - BE: fourDigit, - CA: /^[ABCEGHJKLMNPRSTVXY]\d[ABCEGHJ-NPRSTV-Z][\s\-]?\d[ABCEGHJ-NPRSTV-Z]\d$/i, - CH: fourDigit, - CZ: /^\d{3}\s?\d{2}$/, - DE: fiveDigit, - DK: fourDigit, - DZ: fiveDigit, - ES: fiveDigit, - FI: fiveDigit, - FR: /^\d{2}\s?\d{3}$/, - GB: /^(gir\s?0aa|[a-z]{1,2}\d[\da-z]?\s?(\d[a-z]{2})?)$/i, - GR: /^\d{3}\s?\d{2}$/, - IL: fiveDigit, - IN: sixDigit, - IS: threeDigit, - IT: fiveDigit, - JP: /^\d{3}\-\d{4}$/, - KE: fiveDigit, - LI: /^(948[5-9]|949[0-7])$/, - MX: fiveDigit, - NL: /^\d{4}\s?[a-z]{2}$/i, - NO: fourDigit, - PL: /^\d{2}\-\d{3}$/, - PT: /^\d{4}(\-\d{3})?$/, - RO: sixDigit, - RU: sixDigit, - SA: fiveDigit, - SE: /^\d{3}\s?\d{2}$/, - TW: /^\d{3}(\d{2})?$/, - US: /^\d{5}(-\d{4})?$/, - ZA: fourDigit, - ZM: fiveDigit -}; - - - -var isPostalCode = function (str, locale) { - assertString(str); - if (locale in patterns) { - return patterns[locale].test(str); - } else if (locale === 'any') { - for (var key in patterns) { - if (patterns.hasOwnProperty(key)) { - var pattern = patterns[key]; - if (pattern.test(str)) { - return true; - } - } - } - return false; - } - throw new Error('Invalid locale \'' + locale + '\''); -}; - -function ltrim(str, chars) { - assertString(str); - var pattern = chars ? new RegExp('^[' + chars + ']+', 'g') : /^\s+/g; - return str.replace(pattern, ''); -} - -function rtrim(str, chars) { - assertString(str); - var pattern = chars ? new RegExp('[' + chars + ']') : /\s/; - - var idx = str.length - 1; - while (idx >= 0 && pattern.test(str[idx])) { - idx--; - } - - return idx < str.length ? str.substr(0, idx + 1) : str; -} - -function trim(str, chars) { - return rtrim(ltrim(str, chars), chars); -} - -function escape(str) { - assertString(str); - return str.replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, ''').replace(//g, '>').replace(/\//g, '/').replace(/\\/g, '\').replace(/`/g, '`'); -} - -function unescape(str) { - assertString(str); - return str.replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, "'").replace(/</g, '<').replace(/>/g, '>').replace(///g, '/').replace(/\/g, '\\').replace(/`/g, '`'); -} - -function blacklist$1(str, chars) { - assertString(str); - return str.replace(new RegExp('[' + chars + ']+', 'g'), ''); -} - -function stripLow(str, keep_new_lines) { - assertString(str); - var chars = keep_new_lines ? '\\x00-\\x09\\x0B\\x0C\\x0E-\\x1F\\x7F' : '\\x00-\\x1F\\x7F'; - return blacklist$1(str, chars); -} - -function whitelist(str, chars) { - assertString(str); - return str.replace(new RegExp('[^' + chars + ']+', 'g'), ''); -} - -function isWhitelisted(str, chars) { - assertString(str); - for (var i = str.length - 1; i >= 0; i--) { - if (chars.indexOf(str[i]) === -1) { - return false; - } - } - return true; -} - -var default_normalize_email_options = { - // The following options apply to all email addresses - // Lowercases the local part of the email address. - // Please note this may violate RFC 5321 as per http://stackoverflow.com/a/9808332/192024). - // The domain is always lowercased, as per RFC 1035 - all_lowercase: true, - - // The following conversions are specific to GMail - // Lowercases the local part of the GMail address (known to be case-insensitive) - gmail_lowercase: true, - // Removes dots from the local part of the email address, as that's ignored by GMail - gmail_remove_dots: true, - // Removes the subaddress (e.g. "+foo") from the email address - gmail_remove_subaddress: true, - // Conversts the googlemail.com domain to gmail.com - gmail_convert_googlemaildotcom: true, - - // The following conversions are specific to Outlook.com / Windows Live / Hotmail - // Lowercases the local part of the Outlook.com address (known to be case-insensitive) - outlookdotcom_lowercase: true, - // Removes the subaddress (e.g. "+foo") from the email address - outlookdotcom_remove_subaddress: true, - - // The following conversions are specific to Yahoo - // Lowercases the local part of the Yahoo address (known to be case-insensitive) - yahoo_lowercase: true, - // Removes the subaddress (e.g. "-foo") from the email address - yahoo_remove_subaddress: true, - - // The following conversions are specific to iCloud - // Lowercases the local part of the iCloud address (known to be case-insensitive) - icloud_lowercase: true, - // Removes the subaddress (e.g. "+foo") from the email address - icloud_remove_subaddress: true -}; - -// List of domains used by iCloud -var icloud_domains = ['icloud.com', 'me.com']; - -// List of domains used by Outlook.com and its predecessors -// This list is likely incomplete. -// Partial reference: -// https://blogs.office.com/2013/04/17/outlook-com-gets-two-step-verification-sign-in-by-alias-and-new-international-domains/ -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']; - -// List of domains used by Yahoo Mail -// This list is likely incomplete -var yahoo_domains = ['rocketmail.com', 'yahoo.ca', 'yahoo.co.uk', 'yahoo.com', 'yahoo.de', 'yahoo.fr', 'yahoo.in', 'yahoo.it', 'ymail.com']; - -function normalizeEmail(email, options) { - options = merge(options, default_normalize_email_options); - - var raw_parts = email.split('@'); - var domain = raw_parts.pop(); - var user = raw_parts.join('@'); - var parts = [user, domain]; - - // The domain is always lowercased, as it's case-insensitive per RFC 1035 - parts[1] = parts[1].toLowerCase(); - - if (parts[1] === 'gmail.com' || parts[1] === 'googlemail.com') { - // Address is GMail - if (options.gmail_remove_subaddress) { - parts[0] = parts[0].split('+')[0]; - } - if (options.gmail_remove_dots) { - parts[0] = parts[0].replace(/\./g, ''); - } - if (!parts[0].length) { - return false; - } - if (options.all_lowercase || options.gmail_lowercase) { - parts[0] = parts[0].toLowerCase(); - } - parts[1] = options.gmail_convert_googlemaildotcom ? 'gmail.com' : parts[1]; - } else if (~icloud_domains.indexOf(parts[1])) { - // Address is iCloud - if (options.icloud_remove_subaddress) { - parts[0] = parts[0].split('+')[0]; - } - if (!parts[0].length) { - return false; - } - if (options.all_lowercase || options.icloud_lowercase) { - parts[0] = parts[0].toLowerCase(); - } - } else if (~outlookdotcom_domains.indexOf(parts[1])) { - // Address is Outlook.com - if (options.outlookdotcom_remove_subaddress) { - parts[0] = parts[0].split('+')[0]; - } - if (!parts[0].length) { - return false; - } - if (options.all_lowercase || options.outlookdotcom_lowercase) { - parts[0] = parts[0].toLowerCase(); - } - } else if (~yahoo_domains.indexOf(parts[1])) { - // Address is Yahoo - if (options.yahoo_remove_subaddress) { - var components = parts[0].split('-'); - parts[0] = components.length > 1 ? components.slice(0, -1).join('-') : components[0]; - } - if (!parts[0].length) { - return false; - } - if (options.all_lowercase || options.yahoo_lowercase) { - parts[0] = parts[0].toLowerCase(); - } - } else if (options.all_lowercase) { - // Any other address - parts[0] = parts[0].toLowerCase(); - } - return parts.join('@'); -} - -var version = '9.1.2'; - -var validator = { - version: version, - toDate: toDate, - toFloat: toFloat, - toInt: toInt, - toBoolean: toBoolean, - equals: equals, - contains: contains, - matches: matches, - isEmail: isEmail, - isURL: isURL, - isMACAddress: isMACAddress, - isIP: isIP, - isFQDN: isFQDN, - isBoolean: isBoolean, - isAlpha: isAlpha, - isAlphanumeric: isAlphanumeric, - isNumeric: isNumeric, - isPort: isPort, - isLowercase: isLowercase, - isUppercase: isUppercase, - isAscii: isAscii, - isFullWidth: isFullWidth, - isHalfWidth: isHalfWidth, - isVariableWidth: isVariableWidth, - isMultibyte: isMultibyte, - isSurrogatePair: isSurrogatePair, - isInt: isInt, - isFloat: isFloat, - isDecimal: isDecimal, - isHexadecimal: isHexadecimal, - isDivisibleBy: isDivisibleBy, - isHexColor: isHexColor, - isISRC: isISRC, - isMD5: isMD5, - isHash: isHash, - isJSON: isJSON, - isEmpty: isEmpty, - isLength: isLength, - isByteLength: isByteLength, - isUUID: isUUID, - isMongoId: isMongoId, - isAfter: isAfter, - isBefore: isBefore, - isIn: isIn, - isCreditCard: isCreditCard, - isISIN: isISIN, - isISBN: isISBN, - isISSN: isISSN, - isMobilePhone: isMobilePhone, - isPostalCode: isPostalCode, - isCurrency: isCurrency, - isISO8601: isISO8601, - isISO31661Alpha2: isISO31661Alpha2, - isBase64: isBase64, - isDataURI: isDataURI, - isLatLong: isLatLong, - ltrim: ltrim, - rtrim: rtrim, - trim: trim, - escape: escape, - unescape: unescape, - stripLow: stripLow, - whitelist: whitelist, - blacklist: blacklist$1, - isWhitelisted: isWhitelisted, - normalizeEmail: normalizeEmail, - toString: toString -}; - -return validator; - -}))); diff --git a/validator.min.js b/validator.min.js deleted file mode 100644 index 11457d99d..000000000 --- a/validator.min.js +++ /dev/null @@ -1,23 +0,0 @@ -/*! - * Copyright (c) 2016 Chris O'Hara - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.validator=t()}(this,function(){"use strict";function e(e){if(!("string"==typeof e||e instanceof String))throw new TypeError("This library (validator.js) validates strings only")}function t(t){return e(t),t=Date.parse(t),isNaN(t)?null:new Date(t)}function r(t){return e(t),parseFloat(t)}function o(e){return"object"===(void 0===e?"undefined":_(e))&&null!==e?e="function"==typeof e.toString?e.toString():"[object Object]":(null===e||void 0===e||isNaN(e)&&!e.length)&&(e=""),String(e)}function i(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=arguments[1];for(var r in t)void 0===e[r]&&(e[r]=t[r]);return e}function n(t,r){e(t);var o=void 0,i=void 0;"object"===(void 0===r?"undefined":_(r))?(o=r.min||0,i=r.max):(o=arguments[1],i=arguments[2]);var n=encodeURI(t).split(/%..|./).length-1;return n>=o&&(void 0===i||n<=i)}function a(t,r){e(t),(r=i(r,v)).allow_trailing_dot&&"."===t[t.length-1]&&(t=t.substring(0,t.length-1));var o=t.split(".");if(r.require_tld){var n=o.pop();if(!o.length||!/^([a-z\u00a1-\uffff]{2,}|xn[a-z0-9-]{2,})$/i.test(n))return!1;if(/[\s\u2002-\u200B\u202F\u205F\u3000\uFEFF\uDB40\uDC20]/.test(n))return!1}for(var a,l=0;l1&&void 0!==arguments[1]?arguments[1]:"";if(e(t),!(r=String(r)))return l(t,4)||l(t,6);if("4"===r){if(!E.test(t))return!1;return t.split(".").sort(function(e,t){return e-t})[3]<=255}if("6"===r){var o=t.split(":"),i=!1,n=l(o[o.length-1],4),a=n?7:8;if(o.length>a)return!1;if("::"===t)return!0;"::"===t.substr(0,2)?(o.shift(),o.shift(),i=!0):"::"===t.substr(t.length-2)&&(o.pop(),o.pop(),i=!0);for(var s=0;s0&&s=1:o.length===a}return!1}function s(e){return"[object RegExp]"===Object.prototype.toString.call(e)}function u(e,t){for(var r=0;r=r.min,n=!r.hasOwnProperty("max")||t<=r.max,a=!r.hasOwnProperty("lt")||tr.gt;return o.test(t)&&i&&n&&a&&l}function c(t){return e(t),Q.test(t)}function f(t){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";if(e(t),!(r=String(r)))return f(t,10)||f(t,13);var o=t.replace(/[\s-]+/g,""),i=0,n=void 0;if("10"===r){if(!ae.test(o))return!1;for(n=0;n<9;n++)i+=(n+1)*o.charAt(n);if("X"===o.charAt(9)?i+=100:i+=10*o.charAt(9),i%11==0)return!!o}else if("13"===r){if(!le.test(o))return!1;for(n=0;n<12;n++)i+=se[n%2]*o.charAt(n);if(o.charAt(12)-(10-i%10)%10==0)return!!o}return!1}function p(t,r){e(t);var o=r?new RegExp("^["+r+"]+","g"):/^\s+/g;return t.replace(o,"")}function g(t,r){e(t);for(var o=r?new RegExp("["+r+"]"):/\s/,i=t.length-1;i>=0&&o.test(t[i]);)i--;return i$/i,A=/^[a-z\d!#\$%&'\*\+\-\/=\?\^_`{\|}~]+$/i,x=/^([\s\x01-\x08\x0b\x0c\x0e-\x1f\x7f\x21\x23-\x5b\x5d-\x7e]|(\\[\x01-\x09\x0b\x0c\x0d-\x7f]))*$/i,S=/^[a-z\d!#\$%&'\*\+\-\/=\?\^_`{\|}~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+$/i,w=/^([\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,E=/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/,y=/^[0-9A-F]{1,4}$/i,b={protocols:["http","https","ftp"],require_tld:!0,require_protocol:!1,require_host:!0,require_valid_protocol:!0,allow_underscores:!1,allow_trailing_dot:!1,allow_protocol_relative_urls:!1},Z=/^\[([^\]]+)\](?::([0-9]+))?$/,R=/^([0-9a-fA-F][0-9a-fA-F]:){5}([0-9a-fA-F][0-9a-fA-F])$/,C={"en-US":/^[A-Z]+$/i,"cs-CZ":/^[A-ZÁČĎÉĚÍŇÓŘŠŤÚŮÝŽ]+$/i,"da-DK":/^[A-ZÆØÅ]+$/i,"de-DE":/^[A-ZÄÖÜß]+$/i,"es-ES":/^[A-ZÁÉÍÑÓÚÜ]+$/i,"fr-FR":/^[A-ZÀÂÆÇÉÈÊËÏÎÔŒÙÛÜŸ]+$/i,"it-IT":/^[A-ZÀÉÈÌÎÓÒÙ]+$/i,"nb-NO":/^[A-ZÆØÅ]+$/i,"nl-NL":/^[A-ZÁÉËÏÓÖÜÚ]+$/i,"nn-NO":/^[A-ZÆØÅ]+$/i,"hu-HU":/^[A-ZÁÉÍÓÖŐÚÜŰ]+$/i,"pl-PL":/^[A-ZĄĆĘŚŁŃÓŻŹ]+$/i,"pt-PT":/^[A-ZÃÁÀÂÇÉÊÍÕÓÔÚÜ]+$/i,"ru-RU":/^[А-ЯЁ]+$/i,"sr-RS@latin":/^[A-ZČĆŽŠĐ]+$/i,"sr-RS":/^[А-ЯЂЈЉЊЋЏ]+$/i,"sv-SE":/^[A-ZÅÄÖ]+$/i,"tr-TR":/^[A-ZÇĞİıÖŞÜ]+$/i,"uk-UA":/^[А-ЩЬЮЯЄIЇҐі]+$/i,ar:/^[ءآأؤإئابةتثجحخدذرزسشصضطظعغفقكلمنهوىيًٌٍَُِّْٰ]+$/},D={"en-US":/^[0-9A-Z]+$/i,"cs-CZ":/^[0-9A-ZÁČĎÉĚÍŇÓŘŠŤÚŮÝŽ]+$/i,"da-DK":/^[0-9A-ZÆØÅ]+$/i,"de-DE":/^[0-9A-ZÄÖÜß]+$/i,"es-ES":/^[0-9A-ZÁÉÍÑÓÚÜ]+$/i,"fr-FR":/^[0-9A-ZÀÂÆÇÉÈÊËÏÎÔŒÙÛÜŸ]+$/i,"it-IT":/^[0-9A-ZÀÉÈÌÎÓÒÙ]+$/i,"hu-HU":/^[0-9A-ZÁÉÍÓÖŐÚÜŰ]+$/i,"nb-NO":/^[0-9A-ZÆØÅ]+$/i,"nl-NL":/^[0-9A-ZÁÉËÏÓÖÜÚ]+$/i,"nn-NO":/^[0-9A-ZÆØÅ]+$/i,"pl-PL":/^[0-9A-ZĄĆĘŚŁŃÓŻŹ]+$/i,"pt-PT":/^[0-9A-ZÃÁÀÂÇÉÊÍÕÓÔÚÜ]+$/i,"ru-RU":/^[0-9А-ЯЁ]+$/i,"sr-RS@latin":/^[0-9A-ZČĆŽŠĐ]+$/i,"sr-RS":/^[0-9А-ЯЂЈЉЊЋЏ]+$/i,"sv-SE":/^[0-9A-ZÅÄÖ]+$/i,"tr-TR":/^[0-9A-ZÇĞİıÖŞÜ]+$/i,"uk-UA":/^[0-9А-ЩЬЮЯЄIЇҐі]+$/i,ar:/^[٠١٢٣٤٥٦٧٨٩0-9ءآأؤإئابةتثجحخدذرزسشصضطظعغفقكلمنهوىيًٌٍَُِّْٰ]+$/},I={"en-US":".",ar:"٫"},O=["AU","GB","HK","IN","NZ","ZA","ZM"],N=0;N=0},matches:function(t,r,o){return e(t),"[object RegExp]"!==Object.prototype.toString.call(r)&&(r=new RegExp(r,o)),r.test(t)},isEmail:function(t,r){if(e(t),(r=i(r,$)).require_display_name||r.allow_display_name){var o=t.match(F);if(o)t=o[1];else if(r.require_display_name)return!1}var l=t.split("@"),s=l.pop(),u=l.join("@"),d=s.toLowerCase();if("gmail.com"!==d&&"googlemail.com"!==d||(u=u.replace(/\./g,"").toLowerCase()),!n(u,{max:64})||!n(s,{max:254}))return!1;if(!a(s,{require_tld:r.require_tld}))return!1;if('"'===u[0])return u=u.slice(1,u.length-1),r.allow_utf8_local_part?w.test(u):x.test(u);for(var c=r.allow_utf8_local_part?S:A,f=u.split("."),p=0;p=2083||/[\s<>]/.test(t))return!1;if(0===t.indexOf("mailto:"))return!1;r=i(r,b);var o=void 0,n=void 0,s=void 0,d=void 0,c=void 0,f=void 0,p=void 0,g=void 0;if(p=t.split("#"),t=p.shift(),p=t.split("?"),t=p.shift(),(p=t.split("://")).length>1){if(o=p.shift(),r.require_valid_protocol&&-1===r.protocols.indexOf(o))return!1}else{if(r.require_protocol)return!1;r.allow_protocol_relative_urls&&"//"===t.substr(0,2)&&(p[0]=t.substr(2))}if(""===(t=p.join("://")))return!1;if(p=t.split("/"),""===(t=p.shift())&&!r.require_host)return!0;if((p=t.split("@")).length>1&&(n=p.shift()).indexOf(":")>=0&&n.split(":").length>2)return!1;f=null,g=null;var h=(d=p.join("@")).match(Z);return h?(s="",g=h[1],f=h[2]||null):(s=(p=d.split(":")).shift(),p.length&&(f=p.join(":"))),!(null!==f&&(c=parseInt(f,10),!/^[0-9]+$/.test(f)||c<=0||c>65535)||!(l(s)||a(s,r)||g&&l(g,6))||(s=s||g,r.host_whitelist&&!u(s,r.host_whitelist)||r.host_blacklist&&u(s,r.host_blacklist)))},isMACAddress:function(t){return e(t),R.test(t)},isIP:l,isFQDN:a,isBoolean:function(t){return e(t),["true","false","1","0"].indexOf(t)>=0},isAlpha:function(t){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"en-US";if(e(t),r in C)return C[r].test(t);throw new Error("Invalid locale '"+r+"'")},isAlphanumeric:function(t){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"en-US";if(e(t),r in D)return D[r].test(t);throw new Error("Invalid locale '"+r+"'")},isNumeric:function(t){return e(t),G.test(t)},isPort:function(e){return d(e,{min:0,max:65535})},isLowercase:function(t){return e(t),t===t.toLowerCase()},isUppercase:function(t){return e(t),t===t.toUpperCase()},isAscii:function(t){return e(t),z.test(t)},isFullWidth:function(t){return e(t),j.test(t)},isHalfWidth:function(t){return e(t),q.test(t)},isVariableWidth:function(t){return e(t),j.test(t)&&q.test(t)},isMultibyte:function(t){return e(t),W.test(t)},isSurrogatePair:function(t){return e(t),J.test(t)},isInt:d,isFloat:function(t,r){e(t),r=r||{};var o=new RegExp("^(?:[-+])?(?:[0-9]+)?(?:\\"+(r.locale?I[r.locale]:".")+"[0-9]*)?(?:[eE][\\+\\-]?(?:[0-9]+))?$");return""!==t&&"."!==t&&"-"!==t&&"+"!==t&&o.test(t)&&(!r.hasOwnProperty("min")||t>=r.min)&&(!r.hasOwnProperty("max")||t<=r.max)&&(!r.hasOwnProperty("lt")||tr.gt)},isDecimal:function(t,r){if(e(t),(r=i(r,V)).locale in I)return!Y.includes(t.replace(/ /g,""))&&function(e){return new RegExp("^[-+]?([0-9]+)?(\\"+I[e.locale]+"[0-9]{"+e.decimal_digits+"})"+(e.force_decimal?"":"?")+"$")}(r).test(t);throw new Error("Invalid locale '"+r.locale+"'")},isHexadecimal:c,isDivisibleBy:function(t,o){return e(t),r(t)%parseInt(o,10)==0},isHexColor:function(t){return e(t),X.test(t)},isISRC:function(t){return e(t),ee.test(t)},isMD5:function(t){return e(t),te.test(t)},isHash:function(t,r){return e(t),new RegExp("^[a-f0-9]{"+re[r]+"}$").test(t)},isJSON:function(t){e(t);try{var r=JSON.parse(t);return!!r&&"object"===(void 0===r?"undefined":_(r))}catch(e){}return!1},isEmpty:function(t){return e(t),0===t.length},isLength:function(t,r){e(t);var o=void 0,i=void 0;"object"===(void 0===r?"undefined":_(r))?(o=r.min||0,i=r.max):(o=arguments[1],i=arguments[2]);var n=t.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g)||[],a=t.length-n.length;return a>=o&&(void 0===i||a<=i)},isByteLength:n,isUUID:function(t){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"all";e(t);var o=oe[r];return o&&o.test(t)},isMongoId:function(t){return e(t),c(t)&&24===t.length},isAfter:function(r){var o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:String(new Date);e(r);var i=t(o),n=t(r);return!!(n&&i&&n>i)},isBefore:function(r){var o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:String(new Date);e(r);var i=t(o),n=t(r);return!!(n&&i&&n=0}return"object"===(void 0===r?"undefined":_(r))?r.hasOwnProperty(t):!(!r||"function"!=typeof r.indexOf)&&r.indexOf(t)>=0},isCreditCard:function(t){e(t);var r=t.replace(/[- ]+/g,"");if(!ie.test(r))return!1;for(var o=0,i=void 0,n=void 0,a=void 0,l=r.length-1;l>=0;l--)i=r.substring(l,l+1),n=parseInt(i,10),o+=a&&(n*=2)>=10?n%10+1:n,a=!a;return!(o%10!=0||!r)},isISIN:function(t){if(e(t),!ne.test(t))return!1;for(var r=t.replace(/[A-Z]/g,function(e){return parseInt(e,36)}),o=0,i=void 0,n=void 0,a=!0,l=r.length-2;l>=0;l--)i=r.substring(l,l+1),n=parseInt(i,10),o+=a&&(n*=2)>=10?n+1:n,a=!a;return parseInt(t.substr(t.length-1),10)===(1e4-o)%10},isISBN:f,isISSN:function(t){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};e(t);var o=ue;if(o=r.require_hyphen?o.replace("?",""):o,!(o=r.case_sensitive?new RegExp(o):new RegExp(o,"i")).test(t))return!1;var i=t.replace("-",""),n=8,a=0,l=!0,s=!1,u=void 0;try{for(var d,c=i[Symbol.iterator]();!(l=(d=c.next()).done);l=!0){var f=d.value;a+=("X"===f.toUpperCase()?10:+f)*n,--n}}catch(e){s=!0,u=e}finally{try{!l&&c.return&&c.return()}finally{if(s)throw u}}return a%11==0},isMobilePhone:function(t,r){if(e(t),r in de)return de[r].test(t);if("any"===r){for(var o in de)if(de.hasOwnProperty(o)&&de[o].test(t))return!0;return!1}throw new Error("Invalid locale '"+r+"'")},isPostalCode:function(t,r){if(e(t),r in Ae)return Ae[r].test(t);if("any"===r){for(var o in Ae)if(Ae.hasOwnProperty(o)&&Ae[o].test(t))return!0;return!1}throw new Error("Invalid locale '"+r+"'")},isCurrency:function(t,r){return e(t),r=i(r,ce),function(e){var t="\\d{"+e.digits_after_decimal[0]+"}";e.digits_after_decimal.forEach(function(e,r){0!==r&&(t=t+"|\\d{"+e+"}")});var r="(\\"+e.symbol.replace(/\./g,"\\.")+")"+(e.require_symbol?"":"?"),o="("+["0","[1-9]\\d*","[1-9]\\d{0,2}(\\"+e.thousands_separator+"\\d{3})*"].join("|")+")?",i="(\\"+e.decimal_separator+"("+t+"))"+(e.require_decimal?"":"?"),n=o+(e.allow_decimal||e.require_decimal?i:"");return e.allow_negatives&&!e.parens_for_negatives&&(e.negative_sign_after_digits?n+="-?":e.negative_sign_before_digits&&(n="-?"+n)),e.allow_negative_sign_placeholder?n="( (?!\\-))?"+n:e.allow_space_after_symbol?n=" ?"+n:e.allow_space_after_digits&&(n+="( (?!$))?"),e.symbol_after_digits?n+=r:n=r+n,e.allow_negatives&&(e.parens_for_negatives?n="(\\("+n+"\\)|"+n+")":e.negative_sign_before_digits||e.negative_sign_after_digits||(n="-?"+n)),new RegExp("^(?!-? )(?=.*\\d)"+n+"$")}(r).test(t)},isISO8601:function(t){return e(t),fe.test(t)},isISO31661Alpha2:function(t){return e(t),pe.includes(t.toUpperCase())},isBase64:function(t){e(t);var r=t.length;if(!r||r%4!=0||ge.test(t))return!1;var o=t.indexOf("=");return-1===o||o===r-1||o===r-2&&"="===t[r-1]},isDataURI:function(t){return e(t),he.test(t)},isLatLong:function(t){if(e(t),!t.includes(","))return!1;var r=t.split(",");return me.test(r[0])&&_e.test(r[1])},ltrim:p,rtrim:g,trim:function(e,t){return g(p(e,t),t)},escape:function(t){return e(t),t.replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(//g,">").replace(/\//g,"/").replace(/\\/g,"\").replace(/`/g,"`")},unescape:function(t){return e(t),t.replace(/&/g,"&").replace(/"/g,'"').replace(/'/g,"'").replace(/</g,"<").replace(/>/g,">").replace(///g,"/").replace(/\/g,"\\").replace(/`/g,"`")},stripLow:function(t,r){return e(t),h(t,r?"\\x00-\\x09\\x0B\\x0C\\x0E-\\x1F\\x7F":"\\x00-\\x1F\\x7F")},whitelist:function(t,r){return e(t),t.replace(new RegExp("[^"+r+"]+","g"),"")},blacklist:h,isWhitelisted:function(t,r){e(t);for(var o=t.length-1;o>=0;o--)if(-1===r.indexOf(t[o]))return!1;return!0},normalizeEmail:function(e,t){t=i(t,xe);var r=e.split("@"),o=r.pop(),n=[r.join("@"),o];if(n[1]=n[1].toLowerCase(),"gmail.com"===n[1]||"googlemail.com"===n[1]){if(t.gmail_remove_subaddress&&(n[0]=n[0].split("+")[0]),t.gmail_remove_dots&&(n[0]=n[0].replace(/\./g,"")),!n[0].length)return!1;(t.all_lowercase||t.gmail_lowercase)&&(n[0]=n[0].toLowerCase()),n[1]=t.gmail_convert_googlemaildotcom?"gmail.com":n[1]}else if(~Se.indexOf(n[1])){if(t.icloud_remove_subaddress&&(n[0]=n[0].split("+")[0]),!n[0].length)return!1;(t.all_lowercase||t.icloud_lowercase)&&(n[0]=n[0].toLowerCase())}else if(~we.indexOf(n[1])){if(t.outlookdotcom_remove_subaddress&&(n[0]=n[0].split("+")[0]),!n[0].length)return!1;(t.all_lowercase||t.outlookdotcom_lowercase)&&(n[0]=n[0].toLowerCase())}else if(~Ee.indexOf(n[1])){if(t.yahoo_remove_subaddress){var a=n[0].split("-");n[0]=a.length>1?a.slice(0,-1).join("-"):a[0]}if(!n[0].length)return!1;(t.all_lowercase||t.yahoo_lowercase)&&(n[0]=n[0].toLowerCase())}else t.all_lowercase&&(n[0]=n[0].toLowerCase());return n.join("@")},toString:o}}); \ No newline at end of file