nodemailer 10.0.7 → 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 CHANGED
@@ -1,5 +1,13 @@
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
+
3
11
  ## [10.0.7](https://github.com/nodemailer/nodemailer/compare/v10.0.6...v10.0.7) (2026-09-11)
4
12
 
5
13
 
@@ -100,13 +100,21 @@ function normalizeDomain(domain, toUnicode) {
100
100
  return toUnicode ? punycode.toUnicode(domain) : punycode.toASCII(domain);
101
101
  }
102
102
  /**
103
- * Removes the line breaks that would split a value across lines once it is written out.
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.
104
112
  *
105
113
  * @param value Value to clean
106
- * @return Value with every CR and LF removed
114
+ * @return Value with every control character removed
107
115
  */
108
- function _stripLineBreaks(value) {
109
- return value.replace(/[\r\n]+/g, '');
116
+ function _stripBoundaryControls(value) {
117
+ return value.replace(/[\x00-\x1f\x7f]+/g, '');
110
118
  }
111
119
  /**
112
120
  * Creates a new mime tree node. Assumes 'multipart/*' as the content type
@@ -128,12 +136,11 @@ class MimeNode {
128
136
  this.nodeCounter = 0;
129
137
  options = options || {};
130
138
  /**
131
- * shared part of the unique multipart boundary. A line break here would split
132
- * the delimiter lines the tree is streamed with, so the declared boundary could
133
- * never match them again and the remainder would go out as body lines
139
+ * shared part of the unique multipart boundary. Control characters are dropped
140
+ * here rather than at the delimiter, see _stripBoundaryControls
134
141
  */
135
- this.baseBoundary = _stripLineBreaks(options.baseBoundary || node_crypto_1.default.randomBytes(8).toString('hex'));
136
- this.boundaryPrefix = _stripLineBreaks(options.boundaryPrefix || '--_NmP');
142
+ this.baseBoundary = _stripBoundaryControls(options.baseBoundary || node_crypto_1.default.randomBytes(8).toString('hex'));
143
+ this.boundaryPrefix = _stripBoundaryControls(options.boundaryPrefix || '--_NmP');
137
144
  this.disableFileAccess = !!options.disableFileAccess;
138
145
  this.disableUrlAccess = !!options.disableUrlAccess;
139
146
  this.normalizeHeaderKey = options.normalizeHeaderKey;
@@ -1180,19 +1187,20 @@ class MimeNode {
1180
1187
  this.contentType = structured.value.trim().toLowerCase();
1181
1188
  this.multipart = /^multipart\//i.test(this.contentType) ? this.contentType.substr(this.contentType.indexOf('/') + 1) : false;
1182
1189
  if (this.multipart) {
1183
- // A line break in the boundary would split the delimiter lines the tree is
1184
- // streamed with, so the declared boundary could never match them again and
1185
- // the remainder would go out as body lines. The declared value and the
1186
- // delimiters are assigned from the same expression here, so whatever the
1187
- // header emitter then does with it, the two sides cannot disagree.
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.
1188
1193
  //
1189
1194
  // Stripping runs before the fallback rather than over the whole chain: a
1190
- // boundary that was nothing but line breaks would otherwise strip to '' and
1195
+ // boundary made only of control characters would otherwise strip to '' and
1191
1196
  // leave the node declaring no boundary and streaming bare '--' delimiters.
1192
- // The generated boundary is stripped too, since baseBoundary and
1193
- // boundaryPrefix are public and may have been written after construction.
1194
- const declared = _stripLineBreaks(structured.params.boundary || this.boundary || '');
1195
- this.boundary = structured.params.boundary = declared || _stripLineBreaks(this._generateBoundary());
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 || '');
1202
+ this.boundary = structured.params.boundary =
1203
+ declared || _stripBoundaryControls(this._generateBoundary());
1196
1204
  }
1197
1205
  else {
1198
1206
  this.boundary = false;
@@ -1205,7 +1213,9 @@ class MimeNode {
1205
1213
  * @internal
1206
1214
  */
1207
1215
  _generateBoundary() {
1208
- return this.rootNode.boundaryPrefix + '-' + this.rootNode.baseBoundary + '-Part_' + this._nodeId;
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;
1209
1219
  }
1210
1220
  /**
1211
1221
  * Encodes a header value for use in the generated rfc2822 email.
@@ -1,3 +1,3 @@
1
1
  export declare const name = "nodemailer";
2
- export declare const version = "10.0.7";
2
+ export declare const version = "10.0.8";
3
3
  export declare const homepage = "https://nodemailer.com/";
@@ -3,5 +3,5 @@
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.homepage = exports.version = exports.name = void 0;
5
5
  exports.name = 'nodemailer';
6
- exports.version = '10.0.7';
6
+ exports.version = '10.0.8';
7
7
  exports.homepage = 'https://nodemailer.com/';
@@ -62,13 +62,21 @@ function normalizeDomain(domain, toUnicode) {
62
62
  return toUnicode ? punycode.toUnicode(domain) : punycode.toASCII(domain);
63
63
  }
64
64
  /**
65
- * Removes the line breaks that would split a value across lines once it is written out.
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.
66
74
  *
67
75
  * @param value Value to clean
68
- * @return Value with every CR and LF removed
76
+ * @return Value with every control character removed
69
77
  */
70
- function _stripLineBreaks(value) {
71
- return value.replace(/[\r\n]+/g, '');
78
+ function _stripBoundaryControls(value) {
79
+ return value.replace(/[\x00-\x1f\x7f]+/g, '');
72
80
  }
73
81
  /**
74
82
  * Creates a new mime tree node. Assumes 'multipart/*' as the content type
@@ -90,12 +98,11 @@ class MimeNode {
90
98
  this.nodeCounter = 0;
91
99
  options = options || {};
92
100
  /**
93
- * shared part of the unique multipart boundary. A line break here would split
94
- * the delimiter lines the tree is streamed with, so the declared boundary could
95
- * never match them again and the remainder would go out as body lines
101
+ * shared part of the unique multipart boundary. Control characters are dropped
102
+ * here rather than at the delimiter, see _stripBoundaryControls
96
103
  */
97
- this.baseBoundary = _stripLineBreaks(options.baseBoundary || crypto.randomBytes(8).toString('hex'));
98
- this.boundaryPrefix = _stripLineBreaks(options.boundaryPrefix || '--_NmP');
104
+ this.baseBoundary = _stripBoundaryControls(options.baseBoundary || crypto.randomBytes(8).toString('hex'));
105
+ this.boundaryPrefix = _stripBoundaryControls(options.boundaryPrefix || '--_NmP');
99
106
  this.disableFileAccess = !!options.disableFileAccess;
100
107
  this.disableUrlAccess = !!options.disableUrlAccess;
101
108
  this.normalizeHeaderKey = options.normalizeHeaderKey;
@@ -1142,19 +1149,20 @@ class MimeNode {
1142
1149
  this.contentType = structured.value.trim().toLowerCase();
1143
1150
  this.multipart = /^multipart\//i.test(this.contentType) ? this.contentType.substr(this.contentType.indexOf('/') + 1) : false;
1144
1151
  if (this.multipart) {
1145
- // A line break in the boundary would split the delimiter lines the tree is
1146
- // streamed with, so the declared boundary could never match them again and
1147
- // the remainder would go out as body lines. The declared value and the
1148
- // delimiters are assigned from the same expression here, so whatever the
1149
- // header emitter then does with it, the two sides cannot disagree.
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.
1150
1155
  //
1151
1156
  // Stripping runs before the fallback rather than over the whole chain: a
1152
- // boundary that was nothing but line breaks would otherwise strip to '' and
1157
+ // boundary made only of control characters would otherwise strip to '' and
1153
1158
  // leave the node declaring no boundary and streaming bare '--' delimiters.
1154
- // The generated boundary is stripped too, since baseBoundary and
1155
- // boundaryPrefix are public and may have been written after construction.
1156
- const declared = _stripLineBreaks(structured.params.boundary || this.boundary || '');
1157
- this.boundary = structured.params.boundary = declared || _stripLineBreaks(this._generateBoundary());
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 || '');
1164
+ this.boundary = structured.params.boundary =
1165
+ declared || _stripBoundaryControls(this._generateBoundary());
1158
1166
  }
1159
1167
  else {
1160
1168
  this.boundary = false;
@@ -1167,7 +1175,9 @@ class MimeNode {
1167
1175
  * @internal
1168
1176
  */
1169
1177
  _generateBoundary() {
1170
- return this.rootNode.boundaryPrefix + '-' + this.rootNode.baseBoundary + '-Part_' + this._nodeId;
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;
1171
1181
  }
1172
1182
  /**
1173
1183
  * Encodes a header value for use in the generated rfc2822 email.
@@ -1,3 +1,3 @@
1
1
  export declare const name = "nodemailer";
2
- export declare const version = "10.0.7";
2
+ export declare const version = "10.0.8";
3
3
  export declare const homepage = "https://nodemailer.com/";
@@ -1,4 +1,4 @@
1
1
  // Generated by scripts/build.js from package.json. Do not edit by hand.
2
2
  export const name = 'nodemailer';
3
- export const version = '10.0.7';
3
+ export const version = '10.0.8';
4
4
  export const homepage = 'https://nodemailer.com/';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodemailer",
3
- "version": "10.0.7",
3
+ "version": "10.0.8",
4
4
  "description": "Easy as cake e-mail sending from your Node.js applications",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/nodemailer.js",