nodemailer 10.0.3 → 10.0.5
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/addressparser/index.js +11 -1
- package/dist/cjs/fetch/cookies.d.ts +4 -2
- package/dist/cjs/fetch/cookies.js +15 -12
- package/dist/cjs/mail-composer/index.js +5 -2
- package/dist/cjs/package-info.d.ts +1 -1
- package/dist/cjs/package-info.js +1 -1
- package/dist/cjs/ses-transport/index.js +5 -1
- package/dist/cjs/well-known/services.js +2 -0
- package/dist/esm/addressparser/index.js +11 -1
- package/dist/esm/fetch/cookies.d.ts +4 -2
- package/dist/esm/fetch/cookies.js +15 -12
- package/dist/esm/mail-composer/index.js +5 -2
- package/dist/esm/package-info.d.ts +1 -1
- package/dist/esm/package-info.js +1 -1
- package/dist/esm/ses-transport/index.js +5 -1
- package/dist/esm/well-known/services.js +2 -0
- package/dist/well-known/services.json +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## [10.0.5](https://github.com/nodemailer/nodemailer/compare/v10.0.4...v10.0.5) (2026-09-11)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **addressparser:** parse comment-joined addresses in linear time ([c07f175](https://github.com/nodemailer/nodemailer/commit/c07f17518d25aca8ab2ad66968dcbca538c24b89))
|
|
9
|
+
|
|
10
|
+
## [10.0.4](https://github.com/nodemailer/nodemailer/compare/v10.0.3...v10.0.4) (2026-09-11)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **fetch:** scope a cookie without a Path to the RFC 6265 default path ([2f907cb](https://github.com/nodemailer/nodemailer/commit/2f907cb71396860d0b2534dc72c3d252ef16339d))
|
|
16
|
+
* **fetch:** send cookies set with Path back to the exact path ([#1861](https://github.com/nodemailer/nodemailer/issues/1861)) ([d557113](https://github.com/nodemailer/nodemailer/commit/d5571139750de47abbac73a3b07ae90ff39a7bdd))
|
|
17
|
+
* **mail-composer:** keep httpHeaders and tls for href alternatives and icalEvent ([#1862](https://github.com/nodemailer/nodemailer/issues/1862)) ([7f502be](https://github.com/nodemailer/nodemailer/commit/7f502bee4a6b0386099c66c0a35d92850ec462f7))
|
|
18
|
+
* resolve well-known services by their primary domains ([#1859](https://github.com/nodemailer/nodemailer/issues/1859)) ([085f525](https://github.com/nodemailer/nodemailer/commit/085f525e6b292f0ca32ab1b20d4a4ee82fc40e2d))
|
|
19
|
+
* **ses-transport:** throw a configuration error when the SES client is missing ([#1863](https://github.com/nodemailer/nodemailer/issues/1863)) ([4d9c4c9](https://github.com/nodemailer/nodemailer/commit/4d9c4c966aaca61ff6d7649a1ac0aa0ceac6ef1e))
|
|
20
|
+
|
|
3
21
|
## [10.0.3](https://github.com/nodemailer/nodemailer/compare/v10.0.2...v10.0.3) (2026-09-10)
|
|
4
22
|
|
|
5
23
|
|
|
@@ -134,6 +134,12 @@ function _handleAddress(tokens, depth) {
|
|
|
134
134
|
textWasQuoted: []
|
|
135
135
|
};
|
|
136
136
|
let insideQuotes = false;
|
|
137
|
+
// Last character of the run each state is currently accumulating. Reading it back off
|
|
138
|
+
// the accumulator with slice(-1) makes the engine flatten the whole growing string on
|
|
139
|
+
// every token, which is quadratic over an address built from many comment-joined atoms
|
|
140
|
+
// (GHSA-prgh-xp8r-p3m5). A run only ever grows by the token appended below, so the
|
|
141
|
+
// character is carried along instead of re-read.
|
|
142
|
+
const lastChars = { address: '', comment: '', group: '', text: '' };
|
|
137
143
|
// Filter out <addresses>, (comments) and regular text
|
|
138
144
|
for (let i = 0, len = tokens.length; i < len; i++) {
|
|
139
145
|
const token = tokens[i];
|
|
@@ -177,15 +183,19 @@ function _handleAddress(tokens, depth) {
|
|
|
177
183
|
const joins = prevToken &&
|
|
178
184
|
prevToken.noBreak &&
|
|
179
185
|
parts.length &&
|
|
180
|
-
(prevToken.value !== ')' ||
|
|
186
|
+
(prevToken.value !== ')' || lastChars[state] === '@' || token.value.charAt(0) === '@');
|
|
181
187
|
if (joins) {
|
|
182
188
|
data[state][data[state].length - 1] += token.value;
|
|
189
|
+
if (token.value) {
|
|
190
|
+
lastChars[state] = token.value.charAt(token.value.length - 1);
|
|
191
|
+
}
|
|
183
192
|
if (state === 'text' && insideQuotes) {
|
|
184
193
|
data.textWasQuoted[data.textWasQuoted.length - 1] = true;
|
|
185
194
|
}
|
|
186
195
|
}
|
|
187
196
|
else {
|
|
188
197
|
data[state].push(token.value);
|
|
198
|
+
lastChars[state] = token.value.charAt(token.value.length - 1);
|
|
189
199
|
if (state === 'text') {
|
|
190
200
|
data.textWasQuoted.push(insideQuotes);
|
|
191
201
|
}
|
|
@@ -85,10 +85,12 @@ export default class Cookies {
|
|
|
85
85
|
*/
|
|
86
86
|
isExpired(cookie: Cookie): boolean;
|
|
87
87
|
/**
|
|
88
|
-
* Returns
|
|
88
|
+
* Returns the default path for an URL path argument, the default-path of
|
|
89
|
+
* RFC 6265 section 5.1.4. A cookie that carries no Path attribute is scoped
|
|
90
|
+
* to the directory of the URL it was set from
|
|
89
91
|
*
|
|
90
92
|
* @param pathname
|
|
91
|
-
* @returns
|
|
93
|
+
* @returns Default path
|
|
92
94
|
*/
|
|
93
95
|
getPath(pathname?: string | null): string;
|
|
94
96
|
}
|
|
@@ -195,9 +195,13 @@ class Cookies {
|
|
|
195
195
|
('.' + urlparts.hostname).substr(-cookie.domain.length) !== cookie.domain)) {
|
|
196
196
|
return false;
|
|
197
197
|
}
|
|
198
|
-
// check if path matches
|
|
199
|
-
|
|
200
|
-
|
|
198
|
+
// check if the request path path-matches the cookie path (RFC 6265 section 5.1.4):
|
|
199
|
+
// identical paths match, otherwise the cookie path must be a directory prefix
|
|
200
|
+
const pathname = urlparts.pathname || '/';
|
|
201
|
+
const cookiePath = cookie.path;
|
|
202
|
+
const pathMatches = pathname === cookiePath ||
|
|
203
|
+
(pathname.startsWith(cookiePath) && (cookiePath.endsWith('/') || pathname.charAt(cookiePath.length) === '/'));
|
|
204
|
+
if (!pathMatches) {
|
|
201
205
|
return false;
|
|
202
206
|
}
|
|
203
207
|
// check secure argument
|
|
@@ -254,22 +258,21 @@ class Cookies {
|
|
|
254
258
|
return (cookie.expires && cookie.expires < new Date()) || !cookie.value;
|
|
255
259
|
}
|
|
256
260
|
/**
|
|
257
|
-
* Returns
|
|
261
|
+
* Returns the default path for an URL path argument, the default-path of
|
|
262
|
+
* RFC 6265 section 5.1.4. A cookie that carries no Path attribute is scoped
|
|
263
|
+
* to the directory of the URL it was set from
|
|
258
264
|
*
|
|
259
265
|
* @param pathname
|
|
260
|
-
* @returns
|
|
266
|
+
* @returns Default path
|
|
261
267
|
*/
|
|
262
268
|
getPath(pathname) {
|
|
263
269
|
const pathParts = (pathname || '/').split('/');
|
|
264
270
|
pathParts.pop(); // remove filename part
|
|
265
|
-
|
|
266
|
-
//
|
|
271
|
+
const path = pathParts.join('/').trim();
|
|
272
|
+
// a path that holds no more than one '/' is scoped to the root path, and so
|
|
273
|
+
// is one that does not start with '/' at all
|
|
267
274
|
if (path.charAt(0) !== '/') {
|
|
268
|
-
|
|
269
|
-
}
|
|
270
|
-
// ensure path suffix /
|
|
271
|
-
if (path.substr(-1) !== '/') {
|
|
272
|
-
path += '/';
|
|
275
|
+
return '/';
|
|
273
276
|
}
|
|
274
277
|
return path;
|
|
275
278
|
}
|
|
@@ -253,7 +253,8 @@ class MailComposer {
|
|
|
253
253
|
else if (icalEvent.href) {
|
|
254
254
|
icalEvent.content = {
|
|
255
255
|
href: icalEvent.href,
|
|
256
|
-
httpHeaders: icalEvent.httpHeaders
|
|
256
|
+
httpHeaders: icalEvent.httpHeaders,
|
|
257
|
+
tls: icalEvent.tls
|
|
257
258
|
};
|
|
258
259
|
icalEvent.href = undefined;
|
|
259
260
|
}
|
|
@@ -362,7 +363,9 @@ class MailComposer {
|
|
|
362
363
|
}
|
|
363
364
|
else if (alternative.href) {
|
|
364
365
|
data.content = {
|
|
365
|
-
href: alternative.href
|
|
366
|
+
href: alternative.href,
|
|
367
|
+
httpHeaders: alternative.httpHeaders,
|
|
368
|
+
tls: alternative.tls
|
|
366
369
|
};
|
|
367
370
|
}
|
|
368
371
|
else {
|
package/dist/cjs/package-info.js
CHANGED
|
@@ -62,7 +62,11 @@ function tagSesError(err) {
|
|
|
62
62
|
class SESTransport extends node_events_1.default {
|
|
63
63
|
constructor(options) {
|
|
64
64
|
super();
|
|
65
|
-
options
|
|
65
|
+
if (!options || !options.SES || !options.SES.sesClient) {
|
|
66
|
+
const error = new Error('Missing SES configuration, expecting { sesClient, SendEmailCommand } from @aws-sdk/client-sesv2, see https://nodemailer.com/transports/ses/');
|
|
67
|
+
error.code = errors.ECONFIG;
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
66
70
|
this.options = options;
|
|
67
71
|
this.ses = this.options.SES;
|
|
68
72
|
this.name = 'SESTransport';
|
|
@@ -119,6 +119,7 @@ exports.services = {
|
|
|
119
119
|
"FastMail": {
|
|
120
120
|
"description": "FastMail",
|
|
121
121
|
"domains": [
|
|
122
|
+
"fastmail.com",
|
|
122
123
|
"fastmail.fm"
|
|
123
124
|
],
|
|
124
125
|
"host": "smtp.fastmail.com",
|
|
@@ -232,6 +233,7 @@ exports.services = {
|
|
|
232
233
|
"Mac"
|
|
233
234
|
],
|
|
234
235
|
"domains": [
|
|
236
|
+
"icloud.com",
|
|
235
237
|
"me.com",
|
|
236
238
|
"mac.com"
|
|
237
239
|
],
|
|
@@ -131,6 +131,12 @@ function _handleAddress(tokens, depth) {
|
|
|
131
131
|
textWasQuoted: []
|
|
132
132
|
};
|
|
133
133
|
let insideQuotes = false;
|
|
134
|
+
// Last character of the run each state is currently accumulating. Reading it back off
|
|
135
|
+
// the accumulator with slice(-1) makes the engine flatten the whole growing string on
|
|
136
|
+
// every token, which is quadratic over an address built from many comment-joined atoms
|
|
137
|
+
// (GHSA-prgh-xp8r-p3m5). A run only ever grows by the token appended below, so the
|
|
138
|
+
// character is carried along instead of re-read.
|
|
139
|
+
const lastChars = { address: '', comment: '', group: '', text: '' };
|
|
134
140
|
// Filter out <addresses>, (comments) and regular text
|
|
135
141
|
for (let i = 0, len = tokens.length; i < len; i++) {
|
|
136
142
|
const token = tokens[i];
|
|
@@ -174,15 +180,19 @@ function _handleAddress(tokens, depth) {
|
|
|
174
180
|
const joins = prevToken &&
|
|
175
181
|
prevToken.noBreak &&
|
|
176
182
|
parts.length &&
|
|
177
|
-
(prevToken.value !== ')' ||
|
|
183
|
+
(prevToken.value !== ')' || lastChars[state] === '@' || token.value.charAt(0) === '@');
|
|
178
184
|
if (joins) {
|
|
179
185
|
data[state][data[state].length - 1] += token.value;
|
|
186
|
+
if (token.value) {
|
|
187
|
+
lastChars[state] = token.value.charAt(token.value.length - 1);
|
|
188
|
+
}
|
|
180
189
|
if (state === 'text' && insideQuotes) {
|
|
181
190
|
data.textWasQuoted[data.textWasQuoted.length - 1] = true;
|
|
182
191
|
}
|
|
183
192
|
}
|
|
184
193
|
else {
|
|
185
194
|
data[state].push(token.value);
|
|
195
|
+
lastChars[state] = token.value.charAt(token.value.length - 1);
|
|
186
196
|
if (state === 'text') {
|
|
187
197
|
data.textWasQuoted.push(insideQuotes);
|
|
188
198
|
}
|
|
@@ -85,10 +85,12 @@ export default class Cookies {
|
|
|
85
85
|
*/
|
|
86
86
|
isExpired(cookie: Cookie): boolean;
|
|
87
87
|
/**
|
|
88
|
-
* Returns
|
|
88
|
+
* Returns the default path for an URL path argument, the default-path of
|
|
89
|
+
* RFC 6265 section 5.1.4. A cookie that carries no Path attribute is scoped
|
|
90
|
+
* to the directory of the URL it was set from
|
|
89
91
|
*
|
|
90
92
|
* @param pathname
|
|
91
|
-
* @returns
|
|
93
|
+
* @returns Default path
|
|
92
94
|
*/
|
|
93
95
|
getPath(pathname?: string | null): string;
|
|
94
96
|
}
|
|
@@ -157,9 +157,13 @@ export default class Cookies {
|
|
|
157
157
|
('.' + urlparts.hostname).substr(-cookie.domain.length) !== cookie.domain)) {
|
|
158
158
|
return false;
|
|
159
159
|
}
|
|
160
|
-
// check if path matches
|
|
161
|
-
|
|
162
|
-
|
|
160
|
+
// check if the request path path-matches the cookie path (RFC 6265 section 5.1.4):
|
|
161
|
+
// identical paths match, otherwise the cookie path must be a directory prefix
|
|
162
|
+
const pathname = urlparts.pathname || '/';
|
|
163
|
+
const cookiePath = cookie.path;
|
|
164
|
+
const pathMatches = pathname === cookiePath ||
|
|
165
|
+
(pathname.startsWith(cookiePath) && (cookiePath.endsWith('/') || pathname.charAt(cookiePath.length) === '/'));
|
|
166
|
+
if (!pathMatches) {
|
|
163
167
|
return false;
|
|
164
168
|
}
|
|
165
169
|
// check secure argument
|
|
@@ -216,22 +220,21 @@ export default class Cookies {
|
|
|
216
220
|
return (cookie.expires && cookie.expires < new Date()) || !cookie.value;
|
|
217
221
|
}
|
|
218
222
|
/**
|
|
219
|
-
* Returns
|
|
223
|
+
* Returns the default path for an URL path argument, the default-path of
|
|
224
|
+
* RFC 6265 section 5.1.4. A cookie that carries no Path attribute is scoped
|
|
225
|
+
* to the directory of the URL it was set from
|
|
220
226
|
*
|
|
221
227
|
* @param pathname
|
|
222
|
-
* @returns
|
|
228
|
+
* @returns Default path
|
|
223
229
|
*/
|
|
224
230
|
getPath(pathname) {
|
|
225
231
|
const pathParts = (pathname || '/').split('/');
|
|
226
232
|
pathParts.pop(); // remove filename part
|
|
227
|
-
|
|
228
|
-
//
|
|
233
|
+
const path = pathParts.join('/').trim();
|
|
234
|
+
// a path that holds no more than one '/' is scoped to the root path, and so
|
|
235
|
+
// is one that does not start with '/' at all
|
|
229
236
|
if (path.charAt(0) !== '/') {
|
|
230
|
-
|
|
231
|
-
}
|
|
232
|
-
// ensure path suffix /
|
|
233
|
-
if (path.substr(-1) !== '/') {
|
|
234
|
-
path += '/';
|
|
237
|
+
return '/';
|
|
235
238
|
}
|
|
236
239
|
return path;
|
|
237
240
|
}
|
|
@@ -215,7 +215,8 @@ class MailComposer {
|
|
|
215
215
|
else if (icalEvent.href) {
|
|
216
216
|
icalEvent.content = {
|
|
217
217
|
href: icalEvent.href,
|
|
218
|
-
httpHeaders: icalEvent.httpHeaders
|
|
218
|
+
httpHeaders: icalEvent.httpHeaders,
|
|
219
|
+
tls: icalEvent.tls
|
|
219
220
|
};
|
|
220
221
|
icalEvent.href = undefined;
|
|
221
222
|
}
|
|
@@ -324,7 +325,9 @@ class MailComposer {
|
|
|
324
325
|
}
|
|
325
326
|
else if (alternative.href) {
|
|
326
327
|
data.content = {
|
|
327
|
-
href: alternative.href
|
|
328
|
+
href: alternative.href,
|
|
329
|
+
httpHeaders: alternative.httpHeaders,
|
|
330
|
+
tls: alternative.tls
|
|
328
331
|
};
|
|
329
332
|
}
|
|
330
333
|
else {
|
package/dist/esm/package-info.js
CHANGED
|
@@ -24,7 +24,11 @@ function tagSesError(err) {
|
|
|
24
24
|
class SESTransport extends EventEmitter {
|
|
25
25
|
constructor(options) {
|
|
26
26
|
super();
|
|
27
|
-
options
|
|
27
|
+
if (!options || !options.SES || !options.SES.sesClient) {
|
|
28
|
+
const error = new Error('Missing SES configuration, expecting { sesClient, SendEmailCommand } from @aws-sdk/client-sesv2, see https://nodemailer.com/transports/ses/');
|
|
29
|
+
error.code = errors.ECONFIG;
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
28
32
|
this.options = options;
|
|
29
33
|
this.ses = this.options.SES;
|
|
30
34
|
this.name = 'SESTransport';
|
|
@@ -116,6 +116,7 @@ export const services = {
|
|
|
116
116
|
"FastMail": {
|
|
117
117
|
"description": "FastMail",
|
|
118
118
|
"domains": [
|
|
119
|
+
"fastmail.com",
|
|
119
120
|
"fastmail.fm"
|
|
120
121
|
],
|
|
121
122
|
"host": "smtp.fastmail.com",
|
|
@@ -229,6 +230,7 @@ export const services = {
|
|
|
229
230
|
"Mac"
|
|
230
231
|
],
|
|
231
232
|
"domains": [
|
|
233
|
+
"icloud.com",
|
|
232
234
|
"me.com",
|
|
233
235
|
"mac.com"
|
|
234
236
|
],
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
|
|
108
108
|
"FastMail": {
|
|
109
109
|
"description": "FastMail",
|
|
110
|
-
"domains": ["fastmail.fm"],
|
|
110
|
+
"domains": ["fastmail.com", "fastmail.fm"],
|
|
111
111
|
"host": "smtp.fastmail.com",
|
|
112
112
|
"port": 465,
|
|
113
113
|
"secure": true
|
|
@@ -196,7 +196,7 @@
|
|
|
196
196
|
"iCloud": {
|
|
197
197
|
"description": "iCloud Mail",
|
|
198
198
|
"aliases": ["Me", "Mac"],
|
|
199
|
-
"domains": ["me.com", "mac.com"],
|
|
199
|
+
"domains": ["icloud.com", "me.com", "mac.com"],
|
|
200
200
|
"host": "smtp.mail.me.com",
|
|
201
201
|
"port": 587
|
|
202
202
|
},
|