nodemailer 10.0.6 → 10.0.8
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 +18 -0
- package/dist/cjs/mime-funcs/index.js +4 -1
- package/dist/cjs/mime-node/index.js +37 -5
- package/dist/cjs/package-info.d.ts +1 -1
- package/dist/cjs/package-info.js +1 -1
- package/dist/cjs/smtp-pool/index.js +4 -2
- package/dist/esm/mime-funcs/index.js +4 -1
- package/dist/esm/mime-node/index.js +37 -5
- package/dist/esm/package-info.d.ts +1 -1
- package/dist/esm/package-info.js +1 -1
- package/dist/esm/smtp-pool/index.js +4 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## [10.0.8](https://github.com/nodemailer/nodemailer/compare/v10.0.7...v10.0.8) (2026-09-11)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **mime-node:** clean the boundary where it is written, not only where it is built ([e14278d](https://github.com/nodemailer/nodemailer/commit/e14278d2dae9427280c79450605a27b1c5d2c355))
|
|
9
|
+
* **mime-node:** drop every control character from multipart boundary material ([a82a355](https://github.com/nodemailer/nodemailer/commit/a82a35554848a2e07485ff81f80aefe2736e5a04))
|
|
10
|
+
|
|
11
|
+
## [10.0.7](https://github.com/nodemailer/nodemailer/compare/v10.0.6...v10.0.7) (2026-09-11)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
* **mime-funcs:** do not double encode Buffer input when chunking base64 mime words ([#1865](https://github.com/nodemailer/nodemailer/issues/1865)) ([4327a59](https://github.com/nodemailer/nodemailer/commit/4327a59939748a7f9394b0a029495fd476f68f30))
|
|
17
|
+
* **mime-node:** keep a boundary that is only line breaks from stripping to empty ([ec46800](https://github.com/nodemailer/nodemailer/commit/ec46800cdcab734d9aa79e1278e819962b3b8f09))
|
|
18
|
+
* **mime-node:** strip line breaks from multipart boundary material ([#1867](https://github.com/nodemailer/nodemailer/issues/1867)) ([03c1a5c](https://github.com/nodemailer/nodemailer/commit/03c1a5c48b6828d9392983d8718fdb1fd583a06c))
|
|
19
|
+
* **smtp-pool:** release rate-limited connections on close ([#1866](https://github.com/nodemailer/nodemailer/issues/1866)) ([7f5c7a4](https://github.com/nodemailer/nodemailer/commit/7f5c7a46b61da9f5c5feaffd921c55b9d5892ee5))
|
|
20
|
+
|
|
3
21
|
## [10.0.6](https://github.com/nodemailer/nodemailer/compare/v10.0.5...v10.0.6) (2026-09-11)
|
|
4
22
|
|
|
5
23
|
|
|
@@ -125,7 +125,10 @@ function encodeWord(data, mimeWordEncoding, maxLength) {
|
|
|
125
125
|
});
|
|
126
126
|
}
|
|
127
127
|
else if (mimeWordEncoding === 'B') {
|
|
128
|
-
|
|
128
|
+
// the chunking loop below splits raw text and base64 encodes each part, so a
|
|
129
|
+
// Buffer goes in as its UTF-8 string: handing it the base64 of the whole input
|
|
130
|
+
// would encode the encoding itself and decode back to base64 text
|
|
131
|
+
encodedStr = typeof data === 'string' ? data : data.toString('utf-8');
|
|
129
132
|
maxLength = maxLength ? Math.max(3, ((maxLength - (maxLength % 4)) / 4) * 3) : 0;
|
|
130
133
|
}
|
|
131
134
|
if (maxLength && (mimeWordEncoding !== 'B' ? encodedStr : base64.encode(data)).length > maxLength) {
|
|
@@ -99,6 +99,23 @@ function normalizeDomain(domain, toUnicode) {
|
|
|
99
99
|
}
|
|
100
100
|
return toUnicode ? punycode.toUnicode(domain) : punycode.toASCII(domain);
|
|
101
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Removes the characters that must never reach a multipart delimiter line.
|
|
104
|
+
*
|
|
105
|
+
* A line break splits the delimiter, so the boundary declared in the header can never
|
|
106
|
+
* match it again and the parts go out as body lines instead. The other C0 controls and
|
|
107
|
+
* DEL do not split anything but must not be written either: RFC 5321 does not allow a
|
|
108
|
+
* NUL in DATA at all, and an MTA or a scanner that stops at one reads a different
|
|
109
|
+
* message than a client that does not, which is the same parser disagreement a split
|
|
110
|
+
* delimiter creates. Both sides are cleaned together, since the declared value and the
|
|
111
|
+
* delimiters come from this one result.
|
|
112
|
+
*
|
|
113
|
+
* @param value Value to clean
|
|
114
|
+
* @return Value with every control character removed
|
|
115
|
+
*/
|
|
116
|
+
function _stripBoundaryControls(value) {
|
|
117
|
+
return value.replace(/[\x00-\x1f\x7f]+/g, '');
|
|
118
|
+
}
|
|
102
119
|
/**
|
|
103
120
|
* Creates a new mime tree node. Assumes 'multipart/*' as the content type
|
|
104
121
|
* if it is a branch, anything else counts as leaf. If rootNode is missing from
|
|
@@ -119,10 +136,11 @@ class MimeNode {
|
|
|
119
136
|
this.nodeCounter = 0;
|
|
120
137
|
options = options || {};
|
|
121
138
|
/**
|
|
122
|
-
* shared part of the unique multipart boundary
|
|
139
|
+
* shared part of the unique multipart boundary. Control characters are dropped
|
|
140
|
+
* here rather than at the delimiter, see _stripBoundaryControls
|
|
123
141
|
*/
|
|
124
|
-
this.baseBoundary = options.baseBoundary || node_crypto_1.default.randomBytes(8).toString('hex');
|
|
125
|
-
this.boundaryPrefix = options.boundaryPrefix || '--_NmP';
|
|
142
|
+
this.baseBoundary = _stripBoundaryControls(options.baseBoundary || node_crypto_1.default.randomBytes(8).toString('hex'));
|
|
143
|
+
this.boundaryPrefix = _stripBoundaryControls(options.boundaryPrefix || '--_NmP');
|
|
126
144
|
this.disableFileAccess = !!options.disableFileAccess;
|
|
127
145
|
this.disableUrlAccess = !!options.disableUrlAccess;
|
|
128
146
|
this.normalizeHeaderKey = options.normalizeHeaderKey;
|
|
@@ -1169,8 +1187,20 @@ class MimeNode {
|
|
|
1169
1187
|
this.contentType = structured.value.trim().toLowerCase();
|
|
1170
1188
|
this.multipart = /^multipart\//i.test(this.contentType) ? this.contentType.substr(this.contentType.indexOf('/') + 1) : false;
|
|
1171
1189
|
if (this.multipart) {
|
|
1190
|
+
// The declared value and the delimiters are assigned from the same expression
|
|
1191
|
+
// here, so whatever the header emitter then does with it, the two sides cannot
|
|
1192
|
+
// disagree about which characters the boundary is made of.
|
|
1193
|
+
//
|
|
1194
|
+
// Stripping runs before the fallback rather than over the whole chain: a
|
|
1195
|
+
// boundary made only of control characters would otherwise strip to '' and
|
|
1196
|
+
// leave the node declaring no boundary and streaming bare '--' delimiters.
|
|
1197
|
+
// _generateBoundary cleans what it builds, but this is the one place the boundary
|
|
1198
|
+
// is written, so it cleans the value it is about to write rather than trusting
|
|
1199
|
+
// where it came from. MimeNode is exported and subclassable, so the generator is
|
|
1200
|
+
// not necessarily the one below. Stripping twice costs nothing, it is idempotent.
|
|
1201
|
+
const declared = _stripBoundaryControls(structured.params.boundary || this.boundary || '');
|
|
1172
1202
|
this.boundary = structured.params.boundary =
|
|
1173
|
-
|
|
1203
|
+
declared || _stripBoundaryControls(this._generateBoundary());
|
|
1174
1204
|
}
|
|
1175
1205
|
else {
|
|
1176
1206
|
this.boundary = false;
|
|
@@ -1183,7 +1213,9 @@ class MimeNode {
|
|
|
1183
1213
|
* @internal
|
|
1184
1214
|
*/
|
|
1185
1215
|
_generateBoundary() {
|
|
1186
|
-
|
|
1216
|
+
// baseBoundary and boundaryPrefix are public, so they may hold something the
|
|
1217
|
+
// constructor never cleaned, and the -Part_ suffix keeps the result non empty
|
|
1218
|
+
return _stripBoundaryControls(this.rootNode.boundaryPrefix + '-' + this.rootNode.baseBoundary) + '-Part_' + this._nodeId;
|
|
1187
1219
|
}
|
|
1188
1220
|
/**
|
|
1189
1221
|
* Encodes a header value for use in the generated rfc2822 email.
|
package/dist/cjs/package-info.js
CHANGED
|
@@ -138,8 +138,10 @@ class SMTPPool extends node_events_1.EventEmitter {
|
|
|
138
138
|
let connection;
|
|
139
139
|
const len = this._connections.length;
|
|
140
140
|
this._closed = true;
|
|
141
|
-
//
|
|
142
|
-
|
|
141
|
+
// release connections gated by the rate limiter so they become
|
|
142
|
+
// available and are torn down below instead of leaking with a
|
|
143
|
+
// cleared timer that never fires
|
|
144
|
+
this._clearRateLimit();
|
|
143
145
|
if (!len && !this._queue.length) {
|
|
144
146
|
return;
|
|
145
147
|
}
|
|
@@ -76,7 +76,10 @@ export function encodeWord(data, mimeWordEncoding, maxLength) {
|
|
|
76
76
|
});
|
|
77
77
|
}
|
|
78
78
|
else if (mimeWordEncoding === 'B') {
|
|
79
|
-
|
|
79
|
+
// the chunking loop below splits raw text and base64 encodes each part, so a
|
|
80
|
+
// Buffer goes in as its UTF-8 string: handing it the base64 of the whole input
|
|
81
|
+
// would encode the encoding itself and decode back to base64 text
|
|
82
|
+
encodedStr = typeof data === 'string' ? data : data.toString('utf-8');
|
|
80
83
|
maxLength = maxLength ? Math.max(3, ((maxLength - (maxLength % 4)) / 4) * 3) : 0;
|
|
81
84
|
}
|
|
82
85
|
if (maxLength && (mimeWordEncoding !== 'B' ? encodedStr : base64.encode(data)).length > maxLength) {
|
|
@@ -61,6 +61,23 @@ function normalizeDomain(domain, toUnicode) {
|
|
|
61
61
|
}
|
|
62
62
|
return toUnicode ? punycode.toUnicode(domain) : punycode.toASCII(domain);
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Removes the characters that must never reach a multipart delimiter line.
|
|
66
|
+
*
|
|
67
|
+
* A line break splits the delimiter, so the boundary declared in the header can never
|
|
68
|
+
* match it again and the parts go out as body lines instead. The other C0 controls and
|
|
69
|
+
* DEL do not split anything but must not be written either: RFC 5321 does not allow a
|
|
70
|
+
* NUL in DATA at all, and an MTA or a scanner that stops at one reads a different
|
|
71
|
+
* message than a client that does not, which is the same parser disagreement a split
|
|
72
|
+
* delimiter creates. Both sides are cleaned together, since the declared value and the
|
|
73
|
+
* delimiters come from this one result.
|
|
74
|
+
*
|
|
75
|
+
* @param value Value to clean
|
|
76
|
+
* @return Value with every control character removed
|
|
77
|
+
*/
|
|
78
|
+
function _stripBoundaryControls(value) {
|
|
79
|
+
return value.replace(/[\x00-\x1f\x7f]+/g, '');
|
|
80
|
+
}
|
|
64
81
|
/**
|
|
65
82
|
* Creates a new mime tree node. Assumes 'multipart/*' as the content type
|
|
66
83
|
* if it is a branch, anything else counts as leaf. If rootNode is missing from
|
|
@@ -81,10 +98,11 @@ class MimeNode {
|
|
|
81
98
|
this.nodeCounter = 0;
|
|
82
99
|
options = options || {};
|
|
83
100
|
/**
|
|
84
|
-
* shared part of the unique multipart boundary
|
|
101
|
+
* shared part of the unique multipart boundary. Control characters are dropped
|
|
102
|
+
* here rather than at the delimiter, see _stripBoundaryControls
|
|
85
103
|
*/
|
|
86
|
-
this.baseBoundary = options.baseBoundary || crypto.randomBytes(8).toString('hex');
|
|
87
|
-
this.boundaryPrefix = options.boundaryPrefix || '--_NmP';
|
|
104
|
+
this.baseBoundary = _stripBoundaryControls(options.baseBoundary || crypto.randomBytes(8).toString('hex'));
|
|
105
|
+
this.boundaryPrefix = _stripBoundaryControls(options.boundaryPrefix || '--_NmP');
|
|
88
106
|
this.disableFileAccess = !!options.disableFileAccess;
|
|
89
107
|
this.disableUrlAccess = !!options.disableUrlAccess;
|
|
90
108
|
this.normalizeHeaderKey = options.normalizeHeaderKey;
|
|
@@ -1131,8 +1149,20 @@ class MimeNode {
|
|
|
1131
1149
|
this.contentType = structured.value.trim().toLowerCase();
|
|
1132
1150
|
this.multipart = /^multipart\//i.test(this.contentType) ? this.contentType.substr(this.contentType.indexOf('/') + 1) : false;
|
|
1133
1151
|
if (this.multipart) {
|
|
1152
|
+
// The declared value and the delimiters are assigned from the same expression
|
|
1153
|
+
// here, so whatever the header emitter then does with it, the two sides cannot
|
|
1154
|
+
// disagree about which characters the boundary is made of.
|
|
1155
|
+
//
|
|
1156
|
+
// Stripping runs before the fallback rather than over the whole chain: a
|
|
1157
|
+
// boundary made only of control characters would otherwise strip to '' and
|
|
1158
|
+
// leave the node declaring no boundary and streaming bare '--' delimiters.
|
|
1159
|
+
// _generateBoundary cleans what it builds, but this is the one place the boundary
|
|
1160
|
+
// is written, so it cleans the value it is about to write rather than trusting
|
|
1161
|
+
// where it came from. MimeNode is exported and subclassable, so the generator is
|
|
1162
|
+
// not necessarily the one below. Stripping twice costs nothing, it is idempotent.
|
|
1163
|
+
const declared = _stripBoundaryControls(structured.params.boundary || this.boundary || '');
|
|
1134
1164
|
this.boundary = structured.params.boundary =
|
|
1135
|
-
|
|
1165
|
+
declared || _stripBoundaryControls(this._generateBoundary());
|
|
1136
1166
|
}
|
|
1137
1167
|
else {
|
|
1138
1168
|
this.boundary = false;
|
|
@@ -1145,7 +1175,9 @@ class MimeNode {
|
|
|
1145
1175
|
* @internal
|
|
1146
1176
|
*/
|
|
1147
1177
|
_generateBoundary() {
|
|
1148
|
-
|
|
1178
|
+
// baseBoundary and boundaryPrefix are public, so they may hold something the
|
|
1179
|
+
// constructor never cleaned, and the -Part_ suffix keeps the result non empty
|
|
1180
|
+
return _stripBoundaryControls(this.rootNode.boundaryPrefix + '-' + this.rootNode.baseBoundary) + '-Part_' + this._nodeId;
|
|
1149
1181
|
}
|
|
1150
1182
|
/**
|
|
1151
1183
|
* Encodes a header value for use in the generated rfc2822 email.
|
package/dist/esm/package-info.js
CHANGED
|
@@ -100,8 +100,10 @@ class SMTPPool extends EventEmitter {
|
|
|
100
100
|
let connection;
|
|
101
101
|
const len = this._connections.length;
|
|
102
102
|
this._closed = true;
|
|
103
|
-
//
|
|
104
|
-
|
|
103
|
+
// release connections gated by the rate limiter so they become
|
|
104
|
+
// available and are torn down below instead of leaking with a
|
|
105
|
+
// cleared timer that never fires
|
|
106
|
+
this._clearRateLimit();
|
|
105
107
|
if (!len && !this._queue.length) {
|
|
106
108
|
return;
|
|
107
109
|
}
|