email-bounce-parser 1.0.21 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -1
- package/lib/parser.js +8 -8
- package/lib/utils.js +51 -9
- package/package.json +17 -6
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ Parses bounce emails and extracts details.
|
|
|
10
10
|
|
|
11
11
|
<table>
|
|
12
12
|
<tr>
|
|
13
|
-
<td align="center"><a href="https://crisp.chat/"><img src="https://crisp.chat/favicon-256x256.png" height="64" /></a></td>
|
|
13
|
+
<td align="center"><a href="https://crisp.chat/"><img src="https://crisp.chat/favicons/favicon-256x256.png" height="64" /></a></td>
|
|
14
14
|
</tr>
|
|
15
15
|
<tr>
|
|
16
16
|
<td align="center">Crisp</td>
|
|
@@ -24,6 +24,22 @@ _👋 You use this library and you want to be listed there? [Contact us](https:/
|
|
|
24
24
|
This library is used at [Crisp](https://crisp.chat/) everyday to parse bounce emails.
|
|
25
25
|
It supports most variations of bounce emails (Postfix, Dovecat, Gmail, Outlook).
|
|
26
26
|
|
|
27
|
+
## RE2 Support
|
|
28
|
+
|
|
29
|
+
This library uses the [RE2](https://github.com/uhop/node-re2) regex engine when available for better performance and [ReDoS](https://en.wikipedia.org/wiki/ReDoS) protection. It automatically falls back to native JavaScript `RegExp` if RE2 is unavailable or fails to load.
|
|
30
|
+
|
|
31
|
+
RE2 is an optional dependency. To use it, install it alongside this library:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install re2
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
After a Node.js upgrade, you may need to rebuild RE2:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm rebuild re2
|
|
41
|
+
```
|
|
42
|
+
|
|
27
43
|
## Usage
|
|
28
44
|
|
|
29
45
|
```js
|
package/lib/parser.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
var
|
|
5
|
-
var { loopRegexes, trimString } = require("./utils");
|
|
4
|
+
var { buildSafeRegExp, loopRegexes, trimString } = require("./utils");
|
|
6
5
|
|
|
7
6
|
var ERROR_CODES_TYPES = {
|
|
8
7
|
"421" : "service_not_available",
|
|
@@ -113,7 +112,7 @@ var REGEXES = {
|
|
|
113
112
|
|
|
114
113
|
recipient : [
|
|
115
114
|
/the following addresses had permanent fatal errors (?:.*?)\s\<(.+?)\>/i,
|
|
116
|
-
/there was a temporary problem delivering your message to (.+?). Gmail will retry/i,
|
|
115
|
+
/there was a temporary problem (?:while )?delivering your message to (.+?). Gmail will retry/i,
|
|
117
116
|
/the following message to \<(.+?)\> was undeliverable/i,
|
|
118
117
|
/to the following addresses:\s*(.+)/i,
|
|
119
118
|
/^your message wasn't delivered to (.+?)\s/mi, // "Your message wasn't delivered to john@acme.com"
|
|
@@ -265,13 +264,13 @@ class Parser {
|
|
|
265
264
|
|
|
266
265
|
for (var _i = 0; _i < _entry.length; _i++) {
|
|
267
266
|
_regexes[_key].push(
|
|
268
|
-
|
|
267
|
+
buildSafeRegExp(_entry[_i])
|
|
269
268
|
);
|
|
270
269
|
}
|
|
271
270
|
} else if (typeof _entry === "object" && Object.keys(_entry).length > 0) {
|
|
272
271
|
_regexes[_key] = this.__initRegexes(_entry);
|
|
273
272
|
} else {
|
|
274
|
-
_regexes[_key] =
|
|
273
|
+
_regexes[_key] = buildSafeRegExp(_entry);
|
|
275
274
|
}
|
|
276
275
|
}
|
|
277
276
|
|
|
@@ -316,7 +315,7 @@ class Parser {
|
|
|
316
315
|
// In cases where the bounce is detected as permanent, or the \
|
|
317
316
|
// temporary / permanent status is still unclear, run additional detection
|
|
318
317
|
if (_permanent === true || (_temporary === false && _permanent === false)) {
|
|
319
|
-
var _match = this.__regexes.temporary_safe
|
|
318
|
+
var _match = str.match(this.__regexes.temporary_safe);
|
|
320
319
|
|
|
321
320
|
if (_match && _match.length > 1) {
|
|
322
321
|
_temporary = true;
|
|
@@ -415,14 +414,14 @@ class Parser {
|
|
|
415
414
|
}
|
|
416
415
|
|
|
417
416
|
// Check if blocked
|
|
418
|
-
var _blocked_match = this.__regexes.blocked
|
|
417
|
+
var _blocked_match = str.match(this.__regexes.blocked);
|
|
419
418
|
|
|
420
419
|
if (_blocked_match && _blocked_match.length > 1) {
|
|
421
420
|
_blocked = true;
|
|
422
421
|
}
|
|
423
422
|
|
|
424
423
|
// Check if spam
|
|
425
|
-
var _spam_match = this.__regexes.spam
|
|
424
|
+
var _spam_match = str.match(this.__regexes.spam);
|
|
426
425
|
|
|
427
426
|
if (_spam_match && _spam_match.length > 1) {
|
|
428
427
|
_spam = true;
|
|
@@ -555,6 +554,7 @@ class Parser {
|
|
|
555
554
|
_str = _str.replace(this.__regexes.line_break_double_indent_single, "\n\n");
|
|
556
555
|
|
|
557
556
|
// Replace single Line-break with regular space
|
|
557
|
+
// TODO: re2 doesn't support lookbehinds, so for now we use a pure-JS regex
|
|
558
558
|
_str = _str.replace(/(?<!\n)\n(?!\n)/gm, " ");
|
|
559
559
|
|
|
560
560
|
// Replace long indent with double Line-break
|
package/lib/utils.js
CHANGED
|
@@ -4,12 +4,45 @@
|
|
|
4
4
|
* IMPORTS
|
|
5
5
|
***************************************************************************/
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
/**************************************************************************
|
|
8
|
+
* INSTANCES
|
|
9
|
+
***************************************************************************/
|
|
10
|
+
|
|
11
|
+
var RE2Engine = detectRE2();
|
|
8
12
|
|
|
9
13
|
/**************************************************************************
|
|
10
14
|
* FUNCTIONS
|
|
11
15
|
***************************************************************************/
|
|
12
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Detects whether the RE2 engine is available
|
|
19
|
+
* @private
|
|
20
|
+
* @return {function|null} The RE2 engine, or null if unavailable
|
|
21
|
+
*/
|
|
22
|
+
function detectRE2() {
|
|
23
|
+
// Notice: RE2 can be explicitly disabled, to test the RegExp fallback
|
|
24
|
+
if (process.env.EMAIL_BOUNCE_PARSER_DISABLE_RE2) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
return require("re2");
|
|
30
|
+
} catch (error) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Builds a safe regular expression (RE2 when available, RegExp otherwise)
|
|
37
|
+
* @public
|
|
38
|
+
* @param {string|RegExp} source
|
|
39
|
+
* @return {RegExp} The regular expression
|
|
40
|
+
*/
|
|
41
|
+
var buildSafeRegExp = function(source) {
|
|
42
|
+
return (RE2Engine !== null) ? new RE2Engine(source) : new RegExp(source);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
13
46
|
/**
|
|
14
47
|
* Executes multiple regular expressions until one succeeds
|
|
15
48
|
* @public
|
|
@@ -31,12 +64,19 @@ var loopRegexes = function(
|
|
|
31
64
|
|
|
32
65
|
if (typeof regexes[_i] === "string") {
|
|
33
66
|
// Build the regex
|
|
34
|
-
_regex =
|
|
67
|
+
_regex = buildSafeRegExp(regexes[_i]);
|
|
35
68
|
}
|
|
36
69
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
70
|
+
// Notice: call String methods, as they support both RE2 and RegExp
|
|
71
|
+
var _currentMatch;
|
|
72
|
+
|
|
73
|
+
if (mode === "replace") {
|
|
74
|
+
_currentMatch = (str || "").replace(_regex, "");
|
|
75
|
+
} else if (mode === "split") {
|
|
76
|
+
_currentMatch = (str || "").split(_regex);
|
|
77
|
+
} else {
|
|
78
|
+
_currentMatch = (str || "").match(_regex);
|
|
79
|
+
}
|
|
40
80
|
|
|
41
81
|
if (mode === "replace") {
|
|
42
82
|
_match = _currentMatch;
|
|
@@ -98,10 +138,12 @@ var trimString = function(str) {
|
|
|
98
138
|
* EXPORTS
|
|
99
139
|
***************************************************************************/
|
|
100
140
|
|
|
101
|
-
exports.
|
|
102
|
-
exports.
|
|
141
|
+
exports.buildSafeRegExp = buildSafeRegExp;
|
|
142
|
+
exports.loopRegexes = loopRegexes;
|
|
143
|
+
exports.trimString = trimString;
|
|
103
144
|
|
|
104
145
|
exports.default = {
|
|
105
|
-
|
|
106
|
-
|
|
146
|
+
buildSafeRegExp : buildSafeRegExp,
|
|
147
|
+
loopRegexes : loopRegexes,
|
|
148
|
+
trimString : trimString
|
|
107
149
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "email-bounce-parser",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Parses bounce emails and extract errors",
|
|
5
5
|
"author": "Eliott Vincent <eliott@crisp.chat>",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -14,16 +14,27 @@
|
|
|
14
14
|
},
|
|
15
15
|
"engineStrict": true,
|
|
16
16
|
"engines": {
|
|
17
|
-
"node": ">=
|
|
17
|
+
"node": ">= 22.0.0"
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
|
20
|
-
"
|
|
20
|
+
"unit": "nodeunit ./test/index.js",
|
|
21
|
+
"unit:regexp": "EMAIL_BOUNCE_PARSER_DISABLE_RE2=1 npm run unit",
|
|
22
|
+
"test": "npm run unit && npm run unit:regexp"
|
|
21
23
|
},
|
|
22
24
|
"devDependencies": {
|
|
23
|
-
"nodeunit-x": "0.15.0"
|
|
25
|
+
"nodeunit-x": "0.15.0",
|
|
26
|
+
"re2": "1.27.0"
|
|
24
27
|
},
|
|
25
|
-
"
|
|
26
|
-
"re2": "1.
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"re2": "1.x"
|
|
30
|
+
},
|
|
31
|
+
"peerDependenciesMeta": {
|
|
32
|
+
"re2": {
|
|
33
|
+
"optional": true
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"allowScripts": {
|
|
37
|
+
"re2@1.27.0": true
|
|
27
38
|
},
|
|
28
39
|
"keywords": [
|
|
29
40
|
"parser",
|