mailparser 3.9.2 → 3.9.4
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/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +16 -0
- package/lib/mail-parser.js +44 -13
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [3.9.4](https://github.com/nodemailer/mailparser/compare/v3.9.3...v3.9.4) (2026-03-09)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* bump nodemailer to 8.0.2 for unquoted comma-in-display-name parsing ([dd71f08](https://github.com/nodemailer/mailparser/commit/dd71f0817fb229aa1ffb421b9384704e550993ca))
|
|
9
|
+
* filter false values from empty References headers ([9884e5d](https://github.com/nodemailer/mailparser/commit/9884e5dd34b9dff9a45f6c205281a50d702a23b1)), closes [#385](https://github.com/nodemailer/mailparser/issues/385)
|
|
10
|
+
* prevent RFC 2047 encoded-word address fabrication in decodeAddresses ([08b800c](https://github.com/nodemailer/mailparser/commit/08b800cd1c5a2ec2341ad4c7f25dc65f584b3999))
|
|
11
|
+
|
|
12
|
+
## [3.9.3](https://github.com/nodemailer/mailparser/compare/v3.9.2...v3.9.3) (2026-01-28)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* escape URLs and link text in textToHtml to prevent XSS ([921a67d](https://github.com/nodemailer/mailparser/commit/921a67df4cfb38f0b411037d7b26fbd4d5411b08)), closes [#412](https://github.com/nodemailer/mailparser/issues/412)
|
|
18
|
+
|
|
3
19
|
## [3.9.2](https://github.com/nodemailer/mailparser/compare/v3.9.1...v3.9.2) (2026-01-28)
|
|
4
20
|
|
|
5
21
|
|
package/lib/mail-parser.js
CHANGED
|
@@ -348,7 +348,7 @@ class MailParser extends Transform {
|
|
|
348
348
|
} catch (E) {
|
|
349
349
|
// ignore
|
|
350
350
|
}
|
|
351
|
-
value = value.split(/\s+/).map(this.ensureMessageIDFormat);
|
|
351
|
+
value = value.split(/\s+/).map(this.ensureMessageIDFormat).filter(val => val);
|
|
352
352
|
break;
|
|
353
353
|
case 'message-id':
|
|
354
354
|
case 'in-reply-to':
|
|
@@ -526,18 +526,30 @@ class MailParser extends Transform {
|
|
|
526
526
|
address.name = (address.name || '').toString().trim();
|
|
527
527
|
|
|
528
528
|
if (!address.address && /^(=\?([^?]+)\?[Bb]\?[^?]*\?=)(\s*=\?([^?]+)\?[Bb]\?[^?]*\?=)*$/.test(address.name) && !processedAddress.has(address)) {
|
|
529
|
-
let
|
|
530
|
-
if
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
529
|
+
let decoded = this.libmime.decodeWords(address.name);
|
|
530
|
+
// Security fix: only re-parse if decoded text contains angle-bracket-delimited
|
|
531
|
+
// addresses (e.g. "Name <user@domain>"). Bare decoded text like "attacker@evil.com"
|
|
532
|
+
// must not be re-interpreted as an address -- RFC 2047 Section 5 prohibits
|
|
533
|
+
// encoded-words in addr-spec, and re-parsing fabricates addresses not present
|
|
534
|
+
// in the original header.
|
|
535
|
+
if (/<[^<>]+@[^<>]+>/.test(decoded)) {
|
|
536
|
+
let parsed = addressparser(decoded);
|
|
537
|
+
if (parsed.length) {
|
|
538
|
+
parsed.forEach(entry => {
|
|
539
|
+
processedAddress.add(entry);
|
|
540
|
+
addresses.push(entry);
|
|
541
|
+
});
|
|
542
|
+
}
|
|
536
543
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
544
|
+
// remove current element
|
|
545
|
+
addresses.splice(i, 1);
|
|
546
|
+
i--;
|
|
547
|
+
continue;
|
|
548
|
+
} else {
|
|
549
|
+
// Treat decoded content as display name only
|
|
550
|
+
address.name = decoded;
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
541
553
|
}
|
|
542
554
|
|
|
543
555
|
if (address.name) {
|
|
@@ -547,6 +559,21 @@ class MailParser extends Transform {
|
|
|
547
559
|
//ignore, keep as is
|
|
548
560
|
}
|
|
549
561
|
}
|
|
562
|
+
// Security fix: detect RFC 2047 encoded-words in the address field.
|
|
563
|
+
// Per RFC 2047 Section 5, encoded-words MUST NOT appear in addr-spec.
|
|
564
|
+
// If found, decode and validate; reject if result is not a simple local@domain.
|
|
565
|
+
if (address.address && /[=]\?[^?]+\?[BbQq]\?[^?]*\?[=]/.test(address.address)) {
|
|
566
|
+
try {
|
|
567
|
+
let decodedAddr = this.libmime.decodeWords(address.address);
|
|
568
|
+
if (/^[^\s@]+@[^\s@]+$/.test(decodedAddr) && !/[=]\?/.test(decodedAddr)) {
|
|
569
|
+
address.address = decodedAddr;
|
|
570
|
+
} else {
|
|
571
|
+
address.address = '';
|
|
572
|
+
}
|
|
573
|
+
} catch (E) {
|
|
574
|
+
address.address = '';
|
|
575
|
+
}
|
|
576
|
+
}
|
|
550
577
|
if (/@xn--/.test(address.address)) {
|
|
551
578
|
try {
|
|
552
579
|
address.address =
|
|
@@ -1129,7 +1156,11 @@ class MailParser extends Transform {
|
|
|
1129
1156
|
result.push(textPart);
|
|
1130
1157
|
}
|
|
1131
1158
|
|
|
1132
|
-
|
|
1159
|
+
// Escape quotes in URL to prevent XSS
|
|
1160
|
+
let safeUrl = link.url.replace(/"/g, '"');
|
|
1161
|
+
// Escape HTML entities in link text
|
|
1162
|
+
let safeText = he.encode(link.text, { useNamedReferences: true });
|
|
1163
|
+
result.push(`<a href="${safeUrl}">${safeText}</a>`);
|
|
1133
1164
|
|
|
1134
1165
|
last = link.lastIndex;
|
|
1135
1166
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mailparser",
|
|
3
|
-
"version": "3.9.
|
|
3
|
+
"version": "3.9.4",
|
|
4
4
|
"description": "Parse e-mails",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -25,15 +25,15 @@
|
|
|
25
25
|
"libmime": "5.3.7",
|
|
26
26
|
"linkify-it": "5.0.0",
|
|
27
27
|
"@zone-eu/mailsplit": "5.4.8",
|
|
28
|
-
"nodemailer": "
|
|
28
|
+
"nodemailer": "8.0.2",
|
|
29
29
|
"punycode.js": "2.3.1",
|
|
30
30
|
"tlds": "1.261.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@eslint/eslintrc": "3.3.
|
|
34
|
-
"@eslint/js": "
|
|
35
|
-
"ajv": "8.
|
|
36
|
-
"eslint": "
|
|
33
|
+
"@eslint/eslintrc": "3.3.5",
|
|
34
|
+
"@eslint/js": "10.0.1",
|
|
35
|
+
"ajv": "8.18.0",
|
|
36
|
+
"eslint": "10.0.3",
|
|
37
37
|
"eslint-config-nodemailer": "1.2.0",
|
|
38
38
|
"eslint-config-prettier": "10.1.8",
|
|
39
39
|
"grunt": "1.6.1",
|