nodemailer 10.1.2 → 10.1.3
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/CHANGELOG.md +7 -0
- package/dist/cjs/mailer/mail-message.js +4 -1
- package/dist/cjs/mime-node/index.js +40 -15
- package/dist/cjs/package-info.d.ts +1 -1
- package/dist/cjs/package-info.js +1 -1
- package/dist/cjs/shared/url.js +22 -5
- package/dist/esm/mailer/mail-message.js +4 -1
- package/dist/esm/mime-node/index.js +40 -15
- package/dist/esm/package-info.d.ts +1 -1
- package/dist/esm/package-info.js +1 -1
- package/dist/esm/shared/url.js +22 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## [10.1.3](https://github.com/nodemailer/nodemailer/compare/v10.1.2...v10.1.3) (2026-10-18)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* format References and strip URLs in linear time, release any number of streams ([2f18641](https://github.com/nodemailer/nodemailer/commit/2f186418cf6c8fff18f235d4318892fd42f810fa))
|
|
9
|
+
|
|
3
10
|
## [10.1.2](https://github.com/nodemailer/nodemailer/compare/v10.1.1...v10.1.2) (2026-10-16)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -183,7 +183,10 @@ class MailMessage {
|
|
|
183
183
|
const values = [data.html, data.text, data.watchHtml, data.amp, data.raw, data.icalEvent];
|
|
184
184
|
for (const key of ['attachments', 'alternatives']) {
|
|
185
185
|
if (Array.isArray(data[key])) {
|
|
186
|
-
|
|
186
|
+
// one at a time, spreading a long enough list into the arguments throws
|
|
187
|
+
for (const entry of data[key]) {
|
|
188
|
+
values.push(entry);
|
|
189
|
+
}
|
|
187
190
|
}
|
|
188
191
|
}
|
|
189
192
|
for (const value of values) {
|
|
@@ -64,6 +64,31 @@ const QUOTED_STRING = /^"(?:[^"\\]|\\[\s\S])*"$/;
|
|
|
64
64
|
// else goes into angle brackets so that the header can not be read as more addresses than
|
|
65
65
|
// the envelope carries
|
|
66
66
|
const PLAIN_ADDRESS = /^[^\s"(),:;<>@[\\\]]+@[^\s"(),:;<>@[\\\]]+$/;
|
|
67
|
+
/**
|
|
68
|
+
* Removes the whitespace inside every <...> of a References value. A "<" with no ">" after
|
|
69
|
+
* it is left as it is. An index scan, the regex /<[^>]*>/g rescans the rest of the value
|
|
70
|
+
* from every such "<"
|
|
71
|
+
*
|
|
72
|
+
* @param value References header value
|
|
73
|
+
* @returns The value with the msg-ids closed up
|
|
74
|
+
*/
|
|
75
|
+
function stripWhitespaceInAngleBrackets(value) {
|
|
76
|
+
let result = '';
|
|
77
|
+
let pos = 0;
|
|
78
|
+
for (;;) {
|
|
79
|
+
const open = value.indexOf('<', pos);
|
|
80
|
+
if (open < 0) {
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
const close = value.indexOf('>', open + 1);
|
|
84
|
+
if (close < 0) {
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
result += value.slice(pos, open) + value.slice(open, close + 1).replace(/\s/g, '');
|
|
88
|
+
pos = close + 1;
|
|
89
|
+
}
|
|
90
|
+
return result + value.slice(pos);
|
|
91
|
+
}
|
|
67
92
|
// domainToASCII and domainToUnicode are WHATWG host parsers rather than plain IDNA
|
|
68
93
|
// mappers, so they do more than map: they cut the host at '/', '\\', '?' and '#', drop C0
|
|
69
94
|
// controls, and percent-decode. Handing them 'evil.example/mail.corp.example' returns the
|
|
@@ -1357,26 +1382,26 @@ class MimeNode {
|
|
|
1357
1382
|
}
|
|
1358
1383
|
return value;
|
|
1359
1384
|
// space separated list of values enclosed in <>
|
|
1360
|
-
case 'References':
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
elm = (
|
|
1385
|
+
case 'References': {
|
|
1386
|
+
const ids = [];
|
|
1387
|
+
for (const entry of [].concat(value || '')) {
|
|
1388
|
+
const elm = (entry || '')
|
|
1364
1389
|
.toString()
|
|
1365
1390
|
.replace(/\r?\n|\r/g, ' ')
|
|
1366
1391
|
.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '')
|
|
1367
1392
|
.trim();
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1393
|
+
for (let id of stripWhitespaceInAngleBrackets(elm).split(/\s+/)) {
|
|
1394
|
+
if (id.charAt(0) !== '<') {
|
|
1395
|
+
id = '<' + id;
|
|
1396
|
+
}
|
|
1397
|
+
if (id.charAt(id.length - 1) !== '>') {
|
|
1398
|
+
id = id + '>';
|
|
1399
|
+
}
|
|
1400
|
+
ids.push(id);
|
|
1376
1401
|
}
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1402
|
+
}
|
|
1403
|
+
return ids.join(' ').trim();
|
|
1404
|
+
}
|
|
1380
1405
|
case 'Date':
|
|
1381
1406
|
if (Object.prototype.toString.call(value) === '[object Date]') {
|
|
1382
1407
|
return value.toUTCString().replace(/GMT/, '+0000');
|
package/dist/cjs/package-info.js
CHANGED
package/dist/cjs/shared/url.js
CHANGED
|
@@ -58,10 +58,27 @@ const punycode = __importStar(require("../punycode/index.js"));
|
|
|
58
58
|
// Matches a "scheme:" not followed by "//" (and with something after it), used
|
|
59
59
|
// to re-insert the authority separator the legacy parser did not require.
|
|
60
60
|
const SLASHLESS_AUTHORITY = /^([a-zA-Z][a-zA-Z0-9+.-]*:)(?!\/\/)([\s\S]+)$/;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Drops the leading and trailing C0 controls and spaces, which the WHATWG parser strips
|
|
63
|
+
* before it looks at the input. Stripped up front so that the slash-less form is recognized
|
|
64
|
+
* in a value read from a file with a trailing newline as well. An index scan, the regex
|
|
65
|
+
* /^[\x00-\x20]+|[\x00-\x20]+$/g retried a run of spaces inside the value from every
|
|
66
|
+
* position in it
|
|
67
|
+
*
|
|
68
|
+
* @param input URL string
|
|
69
|
+
* @returns The string without the surrounding controls and spaces
|
|
70
|
+
*/
|
|
71
|
+
function stripSurroundingWhitespace(input) {
|
|
72
|
+
let start = 0;
|
|
73
|
+
let end = input.length;
|
|
74
|
+
while (start < end && input.charCodeAt(start) <= 0x20) {
|
|
75
|
+
start++;
|
|
76
|
+
}
|
|
77
|
+
while (end > start && input.charCodeAt(end - 1) <= 0x20) {
|
|
78
|
+
end--;
|
|
79
|
+
}
|
|
80
|
+
return input.slice(start, end);
|
|
81
|
+
}
|
|
65
82
|
// Leading characters legacy url.parse() skips before it reads the scheme
|
|
66
83
|
const LEGACY_TRIM = /^[\x00-\x20\u00a0\ufeff]+/;
|
|
67
84
|
// The authority of a "scheme://authority/..." or a scheme-relative "//authority/..."
|
|
@@ -156,7 +173,7 @@ function normalizeHostname(raw, href) {
|
|
|
156
173
|
return mapped;
|
|
157
174
|
}
|
|
158
175
|
const parse = (input, parseQueryString) => {
|
|
159
|
-
input = (input || '')
|
|
176
|
+
input = stripSurroundingWhitespace(input || '');
|
|
160
177
|
// Legacy url.parse() parses a "user:pass@host:port" authority that follows
|
|
161
178
|
// the scheme even without the "//" separator, for schemes outside its
|
|
162
179
|
// built-in slashed-protocol list (smtp:/smtps:/socks:/...). The WHATWG
|
|
@@ -145,7 +145,10 @@ export default class MailMessage {
|
|
|
145
145
|
const values = [data.html, data.text, data.watchHtml, data.amp, data.raw, data.icalEvent];
|
|
146
146
|
for (const key of ['attachments', 'alternatives']) {
|
|
147
147
|
if (Array.isArray(data[key])) {
|
|
148
|
-
|
|
148
|
+
// one at a time, spreading a long enough list into the arguments throws
|
|
149
|
+
for (const entry of data[key]) {
|
|
150
|
+
values.push(entry);
|
|
151
|
+
}
|
|
149
152
|
}
|
|
150
153
|
}
|
|
151
154
|
for (const value of values) {
|
|
@@ -26,6 +26,31 @@ const QUOTED_STRING = /^"(?:[^"\\]|\\[\s\S])*"$/;
|
|
|
26
26
|
// else goes into angle brackets so that the header can not be read as more addresses than
|
|
27
27
|
// the envelope carries
|
|
28
28
|
const PLAIN_ADDRESS = /^[^\s"(),:;<>@[\\\]]+@[^\s"(),:;<>@[\\\]]+$/;
|
|
29
|
+
/**
|
|
30
|
+
* Removes the whitespace inside every <...> of a References value. A "<" with no ">" after
|
|
31
|
+
* it is left as it is. An index scan, the regex /<[^>]*>/g rescans the rest of the value
|
|
32
|
+
* from every such "<"
|
|
33
|
+
*
|
|
34
|
+
* @param value References header value
|
|
35
|
+
* @returns The value with the msg-ids closed up
|
|
36
|
+
*/
|
|
37
|
+
function stripWhitespaceInAngleBrackets(value) {
|
|
38
|
+
let result = '';
|
|
39
|
+
let pos = 0;
|
|
40
|
+
for (;;) {
|
|
41
|
+
const open = value.indexOf('<', pos);
|
|
42
|
+
if (open < 0) {
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
const close = value.indexOf('>', open + 1);
|
|
46
|
+
if (close < 0) {
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
result += value.slice(pos, open) + value.slice(open, close + 1).replace(/\s/g, '');
|
|
50
|
+
pos = close + 1;
|
|
51
|
+
}
|
|
52
|
+
return result + value.slice(pos);
|
|
53
|
+
}
|
|
29
54
|
// domainToASCII and domainToUnicode are WHATWG host parsers rather than plain IDNA
|
|
30
55
|
// mappers, so they do more than map: they cut the host at '/', '\\', '?' and '#', drop C0
|
|
31
56
|
// controls, and percent-decode. Handing them 'evil.example/mail.corp.example' returns the
|
|
@@ -1319,26 +1344,26 @@ class MimeNode {
|
|
|
1319
1344
|
}
|
|
1320
1345
|
return value;
|
|
1321
1346
|
// space separated list of values enclosed in <>
|
|
1322
|
-
case 'References':
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
elm = (
|
|
1347
|
+
case 'References': {
|
|
1348
|
+
const ids = [];
|
|
1349
|
+
for (const entry of [].concat(value || '')) {
|
|
1350
|
+
const elm = (entry || '')
|
|
1326
1351
|
.toString()
|
|
1327
1352
|
.replace(/\r?\n|\r/g, ' ')
|
|
1328
1353
|
.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '')
|
|
1329
1354
|
.trim();
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1355
|
+
for (let id of stripWhitespaceInAngleBrackets(elm).split(/\s+/)) {
|
|
1356
|
+
if (id.charAt(0) !== '<') {
|
|
1357
|
+
id = '<' + id;
|
|
1358
|
+
}
|
|
1359
|
+
if (id.charAt(id.length - 1) !== '>') {
|
|
1360
|
+
id = id + '>';
|
|
1361
|
+
}
|
|
1362
|
+
ids.push(id);
|
|
1338
1363
|
}
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1364
|
+
}
|
|
1365
|
+
return ids.join(' ').trim();
|
|
1366
|
+
}
|
|
1342
1367
|
case 'Date':
|
|
1343
1368
|
if (Object.prototype.toString.call(value) === '[object Date]') {
|
|
1344
1369
|
return value.toUTCString().replace(/GMT/, '+0000');
|
package/dist/esm/package-info.js
CHANGED
package/dist/esm/shared/url.js
CHANGED
|
@@ -19,10 +19,27 @@ import * as punycode from '../punycode/index.js';
|
|
|
19
19
|
// Matches a "scheme:" not followed by "//" (and with something after it), used
|
|
20
20
|
// to re-insert the authority separator the legacy parser did not require.
|
|
21
21
|
const SLASHLESS_AUTHORITY = /^([a-zA-Z][a-zA-Z0-9+.-]*:)(?!\/\/)([\s\S]+)$/;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Drops the leading and trailing C0 controls and spaces, which the WHATWG parser strips
|
|
24
|
+
* before it looks at the input. Stripped up front so that the slash-less form is recognized
|
|
25
|
+
* in a value read from a file with a trailing newline as well. An index scan, the regex
|
|
26
|
+
* /^[\x00-\x20]+|[\x00-\x20]+$/g retried a run of spaces inside the value from every
|
|
27
|
+
* position in it
|
|
28
|
+
*
|
|
29
|
+
* @param input URL string
|
|
30
|
+
* @returns The string without the surrounding controls and spaces
|
|
31
|
+
*/
|
|
32
|
+
function stripSurroundingWhitespace(input) {
|
|
33
|
+
let start = 0;
|
|
34
|
+
let end = input.length;
|
|
35
|
+
while (start < end && input.charCodeAt(start) <= 0x20) {
|
|
36
|
+
start++;
|
|
37
|
+
}
|
|
38
|
+
while (end > start && input.charCodeAt(end - 1) <= 0x20) {
|
|
39
|
+
end--;
|
|
40
|
+
}
|
|
41
|
+
return input.slice(start, end);
|
|
42
|
+
}
|
|
26
43
|
// Leading characters legacy url.parse() skips before it reads the scheme
|
|
27
44
|
const LEGACY_TRIM = /^[\x00-\x20\u00a0\ufeff]+/;
|
|
28
45
|
// The authority of a "scheme://authority/..." or a scheme-relative "//authority/..."
|
|
@@ -117,7 +134,7 @@ function normalizeHostname(raw, href) {
|
|
|
117
134
|
return mapped;
|
|
118
135
|
}
|
|
119
136
|
export const parse = (input, parseQueryString) => {
|
|
120
|
-
input = (input || '')
|
|
137
|
+
input = stripSurroundingWhitespace(input || '');
|
|
121
138
|
// Legacy url.parse() parses a "user:pass@host:port" authority that follows
|
|
122
139
|
// the scheme even without the "//" separator, for schemes outside its
|
|
123
140
|
// built-in slashed-protocol list (smtp:/smtps:/socks:/...). The WHATWG
|