├── .commitlintrc.js
├── .dist.babelrc
├── .dist.eslintrc
├── .editorconfig
├── .eslintignore
├── .gitattributes
├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── .husky
├── commit-msg
└── pre-commit
├── .huskyrc
├── .lib.babelrc
├── .lib.eslintrc
├── .lintstagedrc.js
├── .npmrc
├── .nycrc
├── .prettierrc.js
├── .remarkignore
├── .remarkrc.js
├── .xo-config.js
├── LICENSE
├── README.md
├── package.json
├── src
└── index.js
└── test
├── browser.js
└── test.js
/.commitlintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['@commitlint/config-conventional']
3 | };
4 |
--------------------------------------------------------------------------------
/.dist.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["@babel/env", {
4 | "targets": {
5 | "browsers": [ "defaults, not ie 11" ]
6 | }
7 | }]
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/.dist.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["eslint:recommended"],
3 | "parser": "@babel/eslint-parser",
4 | "parserOptions": {
5 | "requireConfigFile": false,
6 | "babelOptions": {
7 | "babelrc": false,
8 | "configFile": false,
9 | "presets": ["@babel/preset-env"]
10 | }
11 | },
12 | "env": {
13 | "node": false,
14 | "browser": true,
15 | "amd": true,
16 | "es6": true,
17 | "commonjs": true
18 | },
19 | "plugins": ["compat"],
20 | "rules": {
21 | "no-unused-vars": "off"
22 | },
23 | "globals": {
24 | },
25 | "settings": {
26 | "polyfills": [
27 | ]
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | !.*.js
2 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on:
3 | - push
4 | - pull_request
5 | jobs:
6 | build:
7 | runs-on: ${{ matrix.os }}
8 | strategy:
9 | matrix:
10 | os:
11 | - ubuntu-latest
12 | node_version:
13 | - 16
14 | - 18
15 | name: Node ${{ matrix.node_version }} on ${{ matrix.os }}
16 | steps:
17 | - uses: actions/checkout@v3
18 | - name: Setup node
19 | uses: actions/setup-node@v3
20 | with:
21 | node-version: ${{ matrix.node_version }}
22 | - name: Install dependencies
23 | run: npm install
24 | - name: Run tests
25 | run: npm run test
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.log
3 | .idea
4 | node_modules
5 | coverage
6 | .nyc_output
7 | locales/
8 | package-lock.json
9 | yarn.lock
10 |
11 | Thumbs.db
12 | tmp/
13 | temp/
14 | *.lcov
15 | .env
16 | lib
17 | dist
18 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx --no-install commitlint --edit $1
5 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx --no-install lint-staged && npm test
5 |
--------------------------------------------------------------------------------
/.huskyrc:
--------------------------------------------------------------------------------
1 | {
2 | "hooks": {
3 | "pre-commit": "lint-staged",
4 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/.lib.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["@babel/env", {
4 | "targets": {
5 | "node": "14",
6 | "browsers": [ "defaults, not ie 11" ]
7 | }
8 | }]
9 | ],
10 | "sourceMaps": "both"
11 | }
12 |
--------------------------------------------------------------------------------
/.lib.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["eslint:recommended", "plugin:node/recommended"],
3 | "env": { "browser": true" },
4 | "plugins": ["compat"],
5 | "rules": {
6 | },
7 | "settings": {
8 | "polyfills": []
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/.lintstagedrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "*.md": filenames => filenames.map(filename => `remark ${filename} -qfo`),
3 | 'package.json': 'fixpack',
4 | '*.js': 'xo --fix'
5 | };
6 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=false
2 |
--------------------------------------------------------------------------------
/.nycrc:
--------------------------------------------------------------------------------
1 | {
2 | "extension": [
3 | ".js"
4 | ],
5 | "report-dir": "./coverage",
6 | "temp-dir": "./.nyc_output",
7 | "reporter": ["lcov", "html", "text"]
8 | }
9 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | singleQuote: true,
3 | bracketSpacing: true,
4 | trailingComma: 'none'
5 | };
6 |
--------------------------------------------------------------------------------
/.remarkignore:
--------------------------------------------------------------------------------
1 | test/snapshots/**/*.md
2 |
--------------------------------------------------------------------------------
/.remarkrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: ['preset-github']
3 | };
4 |
--------------------------------------------------------------------------------
/.xo-config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | prettier: true,
3 | space: true,
4 | extends: ['xo-lass']
5 | };
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Forward Email LLC
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # email-regex-safe
2 |
3 | [](https://github.com/spamscanner/email-regex-safe/actions/workflows/ci.yml)
4 | [](https://github.com/sindresorhus/xo)
5 | [](https://github.com/prettier/prettier)
6 | [](https://lass.js.org)
7 | [](LICENSE)
8 | [](https://npm.im/email-regex-safe)
9 |
10 | > Regular expression matching for email addresses. Maintained, configurable, more accurate, and browser-friendly alternative to [email-regex][]. Works in Node v14+ and browsers. **Maintained for [Spam Scanner][spam-scanner] and [Forward Email][forward-email]**.
11 |
12 |
13 | ## Table of Contents
14 |
15 | * [Foreword](#foreword)
16 | * [Install](#install)
17 | * [Usage](#usage)
18 | * [Node](#node)
19 | * [Browser](#browser)
20 | * [Options](#options)
21 | * [How to validate an email address](#how-to-validate-an-email-address)
22 | * [Limitations](#limitations)
23 | * [Contributors](#contributors)
24 | * [License](#license)
25 |
26 |
27 | ## Foreword
28 |
29 | Previously we were using [email-regex][] through our work on [Spam Scanner][spam-scanner] and [Forward Email][forward-email]. However this package has [too many issues](https://github.com/sindresorhus/email-regex/issues/9) and [false positives](https://github.com/sindresorhus/email-regex/issues/2).
30 |
31 | This package should hopefully more closely resemble real-world intended usage of an email regular expression, and also let you configure several [Options](#options). Please check out [Forward Email][forward-email] if this package helped you, and explore our source code on GitHub which shows how we use this package.
32 |
33 | **It will not perform strict email validation, but instead hints the complete matches resembling an email address. We recommend to use [validator.isEmail][validator-email] for validation (e.g. `validator.isEmail(match)`).**
34 |
35 |
36 | ## Install
37 |
38 | **NOTE:** The default behavior of this package will attempt to load [re2](https://github.com/uhop/node-re2) (it is an optional peer dependency used to prevent regular expression denial of service attacks and more). If you wish to use this behavior, you must have `re2` installed via `npm install re2` – otherwise it will fallback to using normal `RegExp` instances. As of v4.0.0 we added an option if you wish to force this package to not even attempt to load `re2` (e.g. it's in your `node_modules` [but you don't want to use it](https://github.com/spamscanner/url-regex-safe/issues/28)) – simply pass `re2: false` as an option.
39 |
40 | [npm][]:
41 |
42 | ```sh
43 | npm install email-regex-safe
44 | ```
45 |
46 |
47 | ## Usage
48 |
49 | ### Node
50 |
51 | ```js
52 | const emailRegexSafe = require('email-regex-safe');
53 |
54 | const str = 'some long string with foo@bar.com in it';
55 | const matches = str.match(emailRegexSafe());
56 |
57 | for (const match of matches) {
58 | console.log('match', match);
59 | }
60 |
61 | console.log(emailRegexSafe({ exact: true }).test('hello@example.com'));
62 | ```
63 |
64 | ### Browser
65 |
66 | Since [RE2][] is not made for the browser, it will not be used. If there were to be any regex vulnerabilities, they would only crash the user's browser tab, and not your server (as they would on the Node.js side without the use of [RE2][]).
67 |
68 | #### VanillaJS
69 |
70 | This is the solution for you if you're just using `
74 |
86 | ```
87 |
88 | #### Bundler
89 |
90 | Assuming you are using [browserify][], [webpack][], [rollup][], or another bundler, you can simply follow [Node](#node) usage above.
91 |
92 |
93 | ## Options
94 |
95 | | Property | Type | Default Value | Description |
96 | | -------------- | ------- | ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
97 | | `re2` | Boolean | `true` | Attempt to load `re2` to use instead of `RegExp` for creating new regular expression instances. If you pass `re2: false`, then `re2` will not even be attempted to be loaded. |
98 | | `exact` | Boolean | `false` | Only match an exact String. Useful with `regex.test(str)` to check if a String is an email address. We set this to `false` by default as the most common use case for a RegExp parser is to parse out emails, as opposed to check strict validity; we feel this closely more resembles real-world intended usage of this package. |
99 | | `strict` | Boolean | `false` | If `true`, then it will allow any TLD as long as it is a minimum of 2 valid characters. If it is `false`, then it will match the TLD against the list of valid TLD's using [tlds](https://github.com/stephenmathieson/node-tlds#readme). |
100 | | `gmail` | Boolean | `true` | Whether or not to abide by Gmail's rules for email usernames (see Gmail's [Create a username article](https://support.google.com/mail/answer/9211434) for more insight). Note that since [RE2][] does not support negative lookahead nor negative lookbehind, we are leaving it up to you to filter out a select few invalid matches while using `gmail: true`. Invalid matches would be those that end with a "." (period) or "+" (plus symbol), or have two or more consecutive ".." periods in a row anywhere in the username portion. We recommend to use `str.matches(emailSafeRegex())` to get an Array of all matches, and then filter those that pass [validator.isEmail][validator-email] after having end period(s) and/or plus symbol(s) stripped from them, as well as filtering out matches with repeated periods. |
101 | | `utf8` | Boolean | `true` | Whether or not to allow UTF-8 characters for email usernames. This Boolean is only applicable if `gmail` option is set to `false`. |
102 | | `localhost` | Boolean | `true` | Allows localhost in the URL hostname portion. See the [test/test.js](test/test.js) for more insight into the localhost test and how it will return a value which may be unwanted. A pull request would be considered to resolve the "pic.jp" vs. "pic.jpg" issue. |
103 | | `ipv4` | Boolean | `true` | Match against IPv4 URL's. |
104 | | `ipv6` | Boolean | `false` | Match against IPv6 URL's. This is set to `false` by default, since IPv6 is not really supported anywhere for email addresses, and it's not even included in [validator.isEmail][validator-email]'s logic. |
105 | | `tlds` | Array | [tlds](https://github.com/stephenmathieson/node-tlds#readme) | Match against a specific list of tlds, or the default list provided by [tlds](https://github.com/stephenmathieson/node-tlds#readme). |
106 | | `returnString` | Boolean | `false` | Return the RegExp as a String instead of a `RegExp` (useful for custom logic, such as we did with [Spam Scanner][spam-scanner]). |
107 |
108 |
109 | ## How to validate an email address
110 |
111 | If you would like to validate email addresses found, then you should use the [validator.isEmail][validator-email] method. This will further enforce the email RFC specification limitations of 64 characters for the username/local part of the email address, 254 for the domain/hostname portion, and 255 in total; including the "@" (at symbol).
112 |
113 |
114 | ## Limitations
115 |
116 | **This limitation only applies if you are using `re2`**: Since we cannot use regular expression's "negative lookbehinds" functionality (due to [RE2][] limitations), we could not merge the logic from this [pull request](https://github.com/kevva/url-regex/pull/67/commits/6c31d81c35c3bb72c413c6e4af92a37b2689ead2). This would have allowed us to make it so `example.jpeg` would match only if it was `example.jp`, however if you pass `example.jpeg` right now it will extract `example.jp` from it (since `.jp` is a TLD). An alternative solution may exist, and we welcome community contributions regarding this issue.
117 |
118 |
119 | ## Contributors
120 |
121 | | Name | Website |
122 | | ----------------- | -------------------------- |
123 | | **Forward Email** | |
124 |
125 |
126 | ## License
127 |
128 | [MIT](LICENSE) © [Forward Email](https://forwardemail.net)
129 |
130 |
131 | ##
132 |
133 | [npm]: https://www.npmjs.com/
134 |
135 | [re2]: https://github.com/uhop/node-re2
136 |
137 | [browserify]: https://github.com/browserify/browserify
138 |
139 | [webpack]: https://github.com/webpack/webpack
140 |
141 | [rollup]: https://github.com/rollup/rollup
142 |
143 | [email-regex]: https://github.com/sindresorhus/email-regex
144 |
145 | [spam-scanner]: https://spamscanner.net
146 |
147 | [forward-email]: https://forwardemail.net
148 |
149 | [validator-email]: https://github.com/validatorjs/validator.js/blob/master/src/lib/isEmail.js
150 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "email-regex-safe",
3 | "description": "Regular expression matching for email addresses. Maintained, configurable, more accurate, and browser-friendly alternative to email-regex. Works in Node v14+ and browsers. Made for Spam Scanner and Forward Email.",
4 | "version": "4.0.0",
5 | "author": "Forward Email (https://forwardemail.net)",
6 | "browser": {
7 | "re2": false
8 | },
9 | "bugs": {
10 | "url": "https://github.com/spamscanner/email-regex-safe/issues"
11 | },
12 | "contributors": [
13 | "Forward Email (https://forwardemail.net)"
14 | ],
15 | "dependencies": {
16 | "ip-regex": "4",
17 | "tlds": "^1.242.0"
18 | },
19 | "devDependencies": {
20 | "@babel/cli": "^7.22.10",
21 | "@babel/core": "^7.22.10",
22 | "@babel/eslint-parser": "^7.22.10",
23 | "@babel/preset-env": "^7.22.10",
24 | "@commitlint/cli": "^17.7.1",
25 | "@commitlint/config-conventional": "^17.7.0",
26 | "ava": "^4.3.0",
27 | "babelify": "^10.0.0",
28 | "browserify": "^17.0.0",
29 | "cross-env": "^7.0.3",
30 | "eslint": "^8.47.0",
31 | "eslint-config-xo-lass": "^2.0.1",
32 | "eslint-plugin-compat": "^4.1.4",
33 | "eslint-plugin-node": "^11.1.0",
34 | "fixpack": "^4.0.0",
35 | "husky": "^8.0.3",
36 | "jsdom": "15",
37 | "lint-staged": "^14.0.0",
38 | "nyc": "^15.1.0",
39 | "re2": "^1.20.1",
40 | "remark-cli": "^11.0.0",
41 | "remark-preset-github": "^4.0.4",
42 | "rimraf": "^5.0.1",
43 | "tinyify": "^3.1.0",
44 | "xo": "^0.56.0"
45 | },
46 | "engines": {
47 | "node": ">=14"
48 | },
49 | "files": [
50 | "lib",
51 | "dist"
52 | ],
53 | "homepage": "https://github.com/spamscanner/email-regex-safe",
54 | "jsdelivr": "dist/email-regex-safe.min.js",
55 | "keywords": [
56 | "2020",
57 | "7661",
58 | "CVE-2020-7661",
59 | "addr",
60 | "address",
61 | "business",
62 | "checker",
63 | "checking",
64 | "checkr",
65 | "cve",
66 | "detect",
67 | "email",
68 | "emails",
69 | "expr",
70 | "expresion",
71 | "expression",
72 | "expression",
73 | "from",
74 | "gapps",
75 | "get",
76 | "gmail",
77 | "google",
78 | "googlemail",
79 | "html",
80 | "mail",
81 | "mails",
82 | "maintained",
83 | "parse",
84 | "parser",
85 | "parsing",
86 | "regex",
87 | "regexer",
88 | "regexer",
89 | "regexes",
90 | "regexing",
91 | "regexp",
92 | "regular",
93 | "safe",
94 | "scan",
95 | "sniff",
96 | "str",
97 | "string",
98 | "syntax",
99 | "text",
100 | "url",
101 | "urls",
102 | "username",
103 | "validation",
104 | "validator"
105 | ],
106 | "license": "MIT",
107 | "main": "lib/index.js",
108 | "peerDependencies": {
109 | "re2": "^1.20.1"
110 | },
111 | "peerDependenciesMeta": {
112 | "re2": {
113 | "optional": true
114 | }
115 | },
116 | "repository": {
117 | "type": "git",
118 | "url": "https://github.com/spamscanner/email-regex-safe"
119 | },
120 | "scripts": {
121 | "browserify": "browserify src/index.js -o dist/email-regex-safe.js -s emailRegexSafe -g [ babelify --configFile ./.dist.babelrc ]",
122 | "build": "npm run build:clean && npm run build:lib && npm run build:dist",
123 | "build:clean": "rimraf lib dist",
124 | "build:dist": "npm run browserify && npm run minify",
125 | "build:lib": "babel --config-file ./.lib.babelrc src --out-dir lib",
126 | "lint": "npm run lint:js && npm run lint:md && npm run lint:pkg && npm run lint:lib && npm run lint:dist",
127 | "lint:dist": "eslint --no-inline-config -c .dist.eslintrc dist",
128 | "lint:js": "xo --fix",
129 | "lint:lib": "eslint -c .lib.eslintrc lib",
130 | "lint:md": "remark . -qfo",
131 | "lint:pkg": "fixpack",
132 | "minify": "cross-env NODE_ENV=production browserify src/index.js -o dist/email-regex-safe.min.js -s emailRegexSafe -g [ babelify --configFile ./.dist.babelrc ] -p tinyify",
133 | "prepare": "husky install",
134 | "pretest": "npm run build && npm run lint",
135 | "test": "cross-env NODE_ENV=test nyc ava"
136 | },
137 | "unpkg": "dist/email-regex-safe.min.js"
138 | }
139 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | const ipRegex = require('ip-regex');
2 | const tlds = require('tlds');
3 |
4 | const ipv4 = ipRegex.v4().source;
5 | const ipv6 = ipRegex.v6().source;
6 | const host = '(?:(?:[a-z\\u00a1-\\uffff0-9][-_]*)*[a-z\\u00a1-\\uffff0-9]+)';
7 | const domain = '(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*';
8 | const strictTld = '(?:[a-z\\u00a1-\\uffff]{2,})';
9 | const defaultTlds = `(?:${tlds.sort((a, b) => b.length - a.length).join('|')})`;
10 |
11 | let RE2;
12 | let hasRE2;
13 |
14 | module.exports = (options) => {
15 | options = {
16 | //
17 | // attempt to use re2, if set to false will use RegExp
18 | // (we did this approach because we don't want to load in-memory re2 if users don't want it)
19 | //
20 | //
21 | re2: true,
22 | exact: false,
23 | strict: false,
24 | gmail: true,
25 | utf8: true,
26 | localhost: true,
27 | ipv4: true,
28 | ipv6: false,
29 | returnString: false,
30 | ...options
31 | };
32 |
33 | /* istanbul ignore next */
34 | const SafeRegExp =
35 | options.re2 && hasRE2 !== false
36 | ? (() => {
37 | if (typeof RE2 === 'function') return RE2;
38 | try {
39 | RE2 = require('re2');
40 | return typeof RE2 === 'function' ? RE2 : RegExp;
41 | } catch {
42 | hasRE2 = false;
43 | return RegExp;
44 | }
45 | })()
46 | : RegExp;
47 |
48 | // Add ability to pass custom list of tlds
49 | //
50 | const tld = `(?:\\.${
51 | options.strict
52 | ? strictTld
53 | : options.tlds
54 | ? `(?:${options.tlds.sort((a, b) => b.length - a.length).join('|')})`
55 | : defaultTlds
56 | })`;
57 |
58 | //
59 | const emailUserPart = options.gmail
60 | ? // https://support.google.com/mail/answer/9211434?hl=en#:~:text=Usernames%20can%20contain%20letters%20(a%2Dz,in%20a%20row.
61 | // cannot contain: &, =, _, ', -, +, comma, brackets, or more than one period in a row
62 | // note that we are parsing for emails, not enforcing username match, so we allow +
63 | '[^\\W_](?:[\\w\\.\\+]+)' // NOTE: we don't end with `[^\\W]` here since Gmail doesn't do this in webmail
64 | : options.utf8
65 | ? "[^\\W_](?:[a-z\\d!#\\$%&'\\.\\*\\+\\-\\/=\\?\\^_`{\\|}~\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF]+)"
66 | : "[^\\W_](?:[a-z\\d!#\\$%&'\\.\\*\\+\\-\\/=\\?\\^_`{\\|}~]+)";
67 |
68 | let regex = `(?:${emailUserPart}@(?:`;
69 | if (options.localhost) regex += 'localhost|';
70 | if (options.ipv4) regex += `${ipv4}|`;
71 | if (options.ipv6) regex += `${ipv6}|`;
72 | regex += `${host}${domain}${tld}))`;
73 |
74 | // Add option to return the regex string instead of a RegExp
75 | if (options.returnString) return regex;
76 |
77 | return options.exact
78 | ? new SafeRegExp(`(?:^${regex}$)`, 'i')
79 | : new SafeRegExp(regex, 'ig');
80 | };
81 |
--------------------------------------------------------------------------------
/test/browser.js:
--------------------------------------------------------------------------------
1 | const path = require('node:path');
2 | const { readFileSync } = require('node:fs');
3 | const { Script } = require('node:vm');
4 | const test = require('ava');
5 | const { JSDOM, VirtualConsole } = require('jsdom');
6 |
7 | const virtualConsole = new VirtualConsole();
8 | virtualConsole.sendTo(console);
9 |
10 | const script = new Script(
11 | readFileSync(path.join(__dirname, '..', 'dist', 'email-regex-safe.min.js'))
12 | );
13 |
14 | const dom = new JSDOM(``, {
15 | url: 'http://localhost:3000/',
16 | referrer: 'http://localhost:3000/',
17 | contentType: 'text/html',
18 | includeNodeLocations: true,
19 | resources: 'usable',
20 | runScripts: 'dangerously',
21 | virtualConsole
22 | });
23 |
24 | dom.runVMScript(script);
25 |
26 | test('should work in the browser', (t) => {
27 | t.true(typeof dom.window.emailRegexSafe === 'function');
28 | t.true(dom.window.emailRegexSafe({ exact: true }).test('hello@example.com'));
29 | t.deepEqual(
30 | 'some long string with foo@bar.com in it'.match(
31 | dom.window.emailRegexSafe()
32 | ),
33 | ['foo@bar.com']
34 | );
35 | });
36 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | const test = require('ava');
2 | const emailRegexSafe = require('..');
3 |
4 | const string = `
5 | bob.foo-bar@test.com
6 | __boop@beep.com
7 | foo@foo.com
8 | foo@f.com
9 | foo@.com
10 | some@sub.domain.jpg.co.uk.com.jpeg
11 | _._boop@beep.com
12 | bepp.test@boop.com
13 | beep....foo@foo.com
14 | beep..@foo.com
15 | beep@bar.com.
16 | beep.boop.boop.@foo.com
17 | beep@boop.com .@foo.com
18 | foo@_ $foobar@gmail.com
19 | +foo@gmail.com
20 | +$foo@gmail.com
21 | +@test.com
22 | ++@test.com+@testtest.com
23 | url:www.example.com reserved.[subscribe.example.com/subscribe.aspx?foo=zaaaa@example.io&beep=foo124123@example.nl
24 | ##rfc822;beep@test.co.uk
25 | /images/some_logo@2x.jp
26 | /images/foobar@2x.jpeg ----------------------------------------[beep.boop.net/foo.cfm?email=beep@example.ai\nwww.foo-beep.es was invalid
27 | cid:image001.png@01bazz23.mx1e6980]www.facebook.com/example[cid:image002.png@03j570cf.ee1e6980]twitter.com/foobar[cid:image000.png@03j570cfzaaaazz.ee1e6980]http://www.linkedin.com/company/beep?trk=company_logo[cid:image005.png@03j570cf.es
28 | foo@bar example.@gmail.com
29 | foo+test@gmail.com
30 | f=nr@context",c=e("gos") 'text@example.com, some text'
31 | fazboop beep baz@boop.com
32 | foo@fe.com admin@2606:4700:4700::1111
33 | fe@fe az@as test@1.2.3.4 foo@com.jpeg
34 | foo@com.jpeg`;
35 |
36 | test('gmail matches', (t) => {
37 | const match = string.match(emailRegexSafe());
38 | t.log(match);
39 | t.deepEqual(match, [
40 | 'bar@test.com',
41 | 'boop@beep.com',
42 | 'foo@foo.com',
43 | 'foo@f.com',
44 | 'some@sub.domain.jpg.co.uk.com.jp',
45 | 'boop@beep.com',
46 | 'bepp.test@boop.com',
47 | 'beep....foo@foo.com',
48 | 'beep..@foo.com',
49 | 'beep@bar.com',
50 | 'beep.boop.boop.@foo.com',
51 | 'beep@boop.com',
52 | 'foobar@gmail.com',
53 | 'foo@gmail.com',
54 | 'foo@gmail.com',
55 | 'test.com+@testtest.com',
56 | 'zaaaa@example.io',
57 | 'foo124123@example.nl',
58 | 'beep@test.co.uk',
59 | 'some_logo@2x.jp',
60 | 'foobar@2x.jp',
61 | 'beep@example.ai',
62 | 'image001.png@01bazz23.mx',
63 | 'image002.png@03j570cf.ee',
64 | 'image000.png@03j570cfzaaaazz.ee',
65 | 'image005.png@03j570cf.es',
66 | 'example.@gmail.com',
67 | 'foo+test@gmail.com',
68 | 'text@example.com',
69 | 'foo@bar.com',
70 | 'baz@boop.com',
71 | 'foo@fe.com',
72 | 'test@1.2.3.4',
73 | 'foo@com.jp',
74 | 'foo@com.jp'
75 | ]);
76 | });
77 |
78 | test('non-gmail matches', (t) => {
79 | const match = string.match(emailRegexSafe({ gmail: false }));
80 | t.log(match);
81 | t.deepEqual(match, [
82 | 'bob.foo-bar@test.com',
83 | 'boop@beep.com',
84 | 'foo@foo.com',
85 | 'foo@f.com',
86 | 'some@sub.domain.jpg.co.uk.com.jp',
87 | 'boop@beep.com',
88 | 'bepp.test@boop.com',
89 | 'beep....foo@foo.com',
90 | 'beep..@foo.com',
91 | 'beep@bar.com',
92 | 'beep.boop.boop.@foo.com',
93 | 'beep@boop.com',
94 | 'foobar@gmail.com',
95 | 'foo@gmail.com',
96 | 'foo@gmail.com',
97 | 'test.com+@testtest.com',
98 | 'subscribe.example.com/subscribe.aspx?foo=zaaaa@example.io',
99 | 'beep=foo124123@example.nl',
100 | 'beep@test.co.uk',
101 | 'images/some_logo@2x.jp',
102 | 'images/foobar@2x.jp',
103 | 'beep.boop.net/foo.cfm?email=beep@example.ai',
104 | 'image001.png@01bazz23.mx',
105 | 'image002.png@03j570cf.ee',
106 | 'image000.png@03j570cfzaaaazz.ee',
107 | 'image005.png@03j570cf.es',
108 | 'example.@gmail.com',
109 | 'foo+test@gmail.com',
110 | 'text@example.com',
111 | 'foo@bar.com',
112 | 'baz@boop.com',
113 | 'foo@fe.com',
114 | 'test@1.2.3.4',
115 | 'foo@com.jp',
116 | 'foo@com.jp'
117 | ]);
118 | });
119 |
--------------------------------------------------------------------------------