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.
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "3.9.2"
2
+ ".": "3.9.4"
3
3
  }
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
 
@@ -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 parsed = addressparser(this.libmime.decodeWords(address.name));
530
- if (parsed.length) {
531
- parsed.forEach(entry => {
532
- processedAddress.add(entry);
533
- addresses.push(entry);
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
- // remove current element
538
- addresses.splice(i, 1);
539
- i--;
540
- continue;
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
- result.push(`<a href="${link.url}">${link.text}</a>`);
1159
+ // Escape quotes in URL to prevent XSS
1160
+ let safeUrl = link.url.replace(/"/g, '&quot;');
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.2",
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": "7.0.13",
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.3",
34
- "@eslint/js": "9.39.2",
35
- "ajv": "8.17.1",
36
- "eslint": "9.39.2",
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",