nodemailer 4.6.8 → 4.7.0
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/.prettierrc.js +5 -0
- package/CHANGELOG.md +6 -0
- package/lib/base64/index.js +2 -2
- package/lib/mailer/mail-message.js +37 -18
- package/lib/mime-funcs/index.js +1 -1
- package/lib/mime-node/index.js +35 -11
- package/lib/smtp-connection/http-proxy-client.js +60 -57
- package/lib/smtp-connection/index.js +42 -37
- package/package.json +6 -5
package/.prettierrc.js
ADDED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 4.7.0 2018-11-19
|
|
4
|
+
|
|
5
|
+
- Cleaned up List-\* header generation
|
|
6
|
+
- Fixed 'full' return option for DSN (klaronix) [23b93a3b]
|
|
7
|
+
- Support promises `for mailcomposer.build()`
|
|
8
|
+
|
|
3
9
|
## 4.6.8 2018-08-15
|
|
4
10
|
|
|
5
11
|
- Use first IP address from DNS resolution when using a proxy (Limbozz) [d4ca847c]
|
package/lib/base64/index.js
CHANGED
|
@@ -87,8 +87,8 @@ class Encoder extends Transform {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
if (chunk.length % 3) {
|
|
90
|
-
this._remainingBytes = chunk.slice(chunk.length - chunk.length % 3);
|
|
91
|
-
chunk = chunk.slice(0, chunk.length - chunk.length % 3);
|
|
90
|
+
this._remainingBytes = chunk.slice(chunk.length - (chunk.length % 3));
|
|
91
|
+
chunk = chunk.slice(0, chunk.length - (chunk.length % 3));
|
|
92
92
|
} else {
|
|
93
93
|
this._remainingBytes = false;
|
|
94
94
|
}
|
|
@@ -256,26 +256,45 @@ class MailMessage {
|
|
|
256
256
|
// make sure an url looks like <protocol:url>
|
|
257
257
|
return Object.keys(listData).map(key => ({
|
|
258
258
|
key: 'list-' + key.toLowerCase().trim(),
|
|
259
|
-
value: [].concat(listData[key] || []).map(value => {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
259
|
+
value: [].concat(listData[key] || []).map(value => ({
|
|
260
|
+
prepared: true,
|
|
261
|
+
foldLines: true,
|
|
262
|
+
value: []
|
|
263
|
+
.concat(value || [])
|
|
264
|
+
.map(value => {
|
|
265
|
+
if (typeof value === 'string') {
|
|
266
|
+
value = {
|
|
267
|
+
url: value
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (value && value.url) {
|
|
272
|
+
if (key.toLowerCase().trim() === 'id') {
|
|
273
|
+
// List-ID: "comment" <domain>
|
|
274
|
+
let comment = value.comment || '';
|
|
275
|
+
if (mimeFuncs.isPlainText(comment)) {
|
|
276
|
+
comment = '"' + comment + '"';
|
|
277
|
+
} else {
|
|
278
|
+
comment = mimeFuncs.encodeWord(comment);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return (value.comment ? comment + ' ' : '') + this._formatListUrl(value.url).replace(/^<[^:]+\/{,2}/, '');
|
|
270
282
|
}
|
|
271
|
-
|
|
272
|
-
|
|
283
|
+
|
|
284
|
+
// List-*: <http://domain> (comment)
|
|
285
|
+
let comment = value.comment || '';
|
|
286
|
+
if (!mimeFuncs.isPlainText(comment)) {
|
|
287
|
+
comment = mimeFuncs.encodeWord(comment);
|
|
273
288
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
289
|
+
|
|
290
|
+
return this._formatListUrl(value.url) + (value.comment ? ' (' + comment + ')' : '');
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return '';
|
|
294
|
+
})
|
|
295
|
+
.filter(value => value)
|
|
296
|
+
.join(', ')
|
|
297
|
+
}))
|
|
279
298
|
}));
|
|
280
299
|
}
|
|
281
300
|
|
package/lib/mime-funcs/index.js
CHANGED
|
@@ -78,7 +78,7 @@ module.exports = {
|
|
|
78
78
|
});
|
|
79
79
|
} else if (mimeWordEncoding === 'B') {
|
|
80
80
|
encodedStr = typeof data === 'string' ? data : base64.encode(data);
|
|
81
|
-
maxLength = maxLength ? Math.max(3, (maxLength - maxLength % 4) / 4 * 3) : 0;
|
|
81
|
+
maxLength = maxLength ? Math.max(3, ((maxLength - (maxLength % 4)) / 4) * 3) : 0;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
if (maxLength && (mimeWordEncoding !== 'B' ? encodedStr : base64.encode(data)).length > maxLength) {
|
package/lib/mime-node/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* eslint no-undefined: 0, prefer-spread: 0 */
|
|
1
|
+
/* eslint no-undefined: 0, prefer-spread: 0, no-control-regex: 0 */
|
|
2
2
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
@@ -7,6 +7,7 @@ const os = require('os');
|
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
const punycode = require('punycode');
|
|
9
9
|
const PassThrough = require('stream').PassThrough;
|
|
10
|
+
const shared = require('../shared');
|
|
10
11
|
|
|
11
12
|
const mimeFuncs = require('../mime-funcs');
|
|
12
13
|
const qp = require('../qp');
|
|
@@ -391,6 +392,14 @@ class MimeNode {
|
|
|
391
392
|
}
|
|
392
393
|
|
|
393
394
|
build(callback) {
|
|
395
|
+
let promise;
|
|
396
|
+
|
|
397
|
+
if (!callback && typeof Promise === 'function') {
|
|
398
|
+
promise = new Promise((resolve, reject) => {
|
|
399
|
+
callback = shared.callbackPromise(resolve, reject);
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
|
|
394
403
|
let stream = this.createReadStream();
|
|
395
404
|
let buf = [];
|
|
396
405
|
let buflen = 0;
|
|
@@ -426,6 +435,8 @@ class MimeNode {
|
|
|
426
435
|
}
|
|
427
436
|
return callback(null, Buffer.concat(buf, buflen));
|
|
428
437
|
});
|
|
438
|
+
|
|
439
|
+
return promise;
|
|
429
440
|
}
|
|
430
441
|
|
|
431
442
|
getTransferEncoding() {
|
|
@@ -513,7 +524,11 @@ class MimeNode {
|
|
|
513
524
|
|
|
514
525
|
if (options.prepared) {
|
|
515
526
|
// header value is
|
|
516
|
-
|
|
527
|
+
if (options.foldLines) {
|
|
528
|
+
headers.push(mimeFuncs.foldLines(key + ': ' + value));
|
|
529
|
+
} else {
|
|
530
|
+
headers.push(key + ': ' + value);
|
|
531
|
+
}
|
|
517
532
|
return;
|
|
518
533
|
}
|
|
519
534
|
|
|
@@ -703,9 +718,12 @@ class MimeNode {
|
|
|
703
718
|
if (['quoted-printable', 'base64'].includes(transferEncoding)) {
|
|
704
719
|
contentStream = new (transferEncoding === 'base64' ? base64 : qp).Encoder(options);
|
|
705
720
|
|
|
706
|
-
contentStream.pipe(
|
|
707
|
-
|
|
708
|
-
|
|
721
|
+
contentStream.pipe(
|
|
722
|
+
outputStream,
|
|
723
|
+
{
|
|
724
|
+
end: false
|
|
725
|
+
}
|
|
726
|
+
);
|
|
709
727
|
contentStream.once('end', finalize);
|
|
710
728
|
contentStream.once('error', err => callback(err));
|
|
711
729
|
|
|
@@ -714,9 +732,12 @@ class MimeNode {
|
|
|
714
732
|
} else {
|
|
715
733
|
// anything that is not QP or Base54 passes as-is
|
|
716
734
|
localStream = this._getStream(this.content);
|
|
717
|
-
localStream.pipe(
|
|
718
|
-
|
|
719
|
-
|
|
735
|
+
localStream.pipe(
|
|
736
|
+
outputStream,
|
|
737
|
+
{
|
|
738
|
+
end: false
|
|
739
|
+
}
|
|
740
|
+
);
|
|
720
741
|
localStream.once('end', finalize);
|
|
721
742
|
}
|
|
722
743
|
|
|
@@ -773,9 +794,12 @@ class MimeNode {
|
|
|
773
794
|
}
|
|
774
795
|
|
|
775
796
|
let raw = this._getStream(this._raw);
|
|
776
|
-
raw.pipe(
|
|
777
|
-
|
|
778
|
-
|
|
797
|
+
raw.pipe(
|
|
798
|
+
outputStream,
|
|
799
|
+
{
|
|
800
|
+
end: false
|
|
801
|
+
}
|
|
802
|
+
);
|
|
779
803
|
raw.on('error', err => outputStream.emit('error', err));
|
|
780
804
|
raw.on('end', finalize);
|
|
781
805
|
});
|
|
@@ -57,73 +57,76 @@ function httpProxyClient(proxyUrl, destinationPort, destinationHost, callback) {
|
|
|
57
57
|
callback(err);
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
-
socket = connect(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
let reqHeaders = {
|
|
66
|
-
Host: destinationHost + ':' + destinationPort,
|
|
67
|
-
Connection: 'close'
|
|
68
|
-
};
|
|
69
|
-
if (proxy.auth) {
|
|
70
|
-
reqHeaders['Proxy-Authorization'] = 'Basic ' + Buffer.from(proxy.auth).toString('base64');
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
socket.write(
|
|
74
|
-
// HTTP method
|
|
75
|
-
'CONNECT ' +
|
|
76
|
-
destinationHost +
|
|
77
|
-
':' +
|
|
78
|
-
destinationPort +
|
|
79
|
-
' HTTP/1.1\r\n' +
|
|
80
|
-
// HTTP request headers
|
|
81
|
-
Object.keys(reqHeaders)
|
|
82
|
-
.map(key => key + ': ' + reqHeaders[key])
|
|
83
|
-
.join('\r\n') +
|
|
84
|
-
// End request
|
|
85
|
-
'\r\n\r\n'
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
let headers = '';
|
|
89
|
-
let onSocketData = chunk => {
|
|
90
|
-
let match;
|
|
91
|
-
let remainder;
|
|
92
|
-
|
|
60
|
+
socket = connect(
|
|
61
|
+
options,
|
|
62
|
+
() => {
|
|
93
63
|
if (finished) {
|
|
94
64
|
return;
|
|
95
65
|
}
|
|
96
66
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
67
|
+
let reqHeaders = {
|
|
68
|
+
Host: destinationHost + ':' + destinationPort,
|
|
69
|
+
Connection: 'close'
|
|
70
|
+
};
|
|
71
|
+
if (proxy.auth) {
|
|
72
|
+
reqHeaders['Proxy-Authorization'] = 'Basic ' + Buffer.from(proxy.auth).toString('base64');
|
|
73
|
+
}
|
|
100
74
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
75
|
+
socket.write(
|
|
76
|
+
// HTTP method
|
|
77
|
+
'CONNECT ' +
|
|
78
|
+
destinationHost +
|
|
79
|
+
':' +
|
|
80
|
+
destinationPort +
|
|
81
|
+
' HTTP/1.1\r\n' +
|
|
82
|
+
// HTTP request headers
|
|
83
|
+
Object.keys(reqHeaders)
|
|
84
|
+
.map(key => key + ': ' + reqHeaders[key])
|
|
85
|
+
.join('\r\n') +
|
|
86
|
+
// End request
|
|
87
|
+
'\r\n\r\n'
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
let headers = '';
|
|
91
|
+
let onSocketData = chunk => {
|
|
92
|
+
let match;
|
|
93
|
+
let remainder;
|
|
94
|
+
|
|
95
|
+
if (finished) {
|
|
96
|
+
return;
|
|
105
97
|
}
|
|
106
98
|
|
|
107
|
-
|
|
108
|
-
|
|
99
|
+
headers += chunk.toString('binary');
|
|
100
|
+
if ((match = headers.match(/\r\n\r\n/))) {
|
|
101
|
+
socket.removeListener('data', onSocketData);
|
|
109
102
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
socket.destroy();
|
|
115
|
-
} catch (E) {
|
|
116
|
-
// ignore
|
|
103
|
+
remainder = headers.substr(match.index + match[0].length);
|
|
104
|
+
headers = headers.substr(0, match.index);
|
|
105
|
+
if (remainder) {
|
|
106
|
+
socket.unshift(Buffer.from(remainder, 'binary'));
|
|
117
107
|
}
|
|
118
|
-
return callback(new Error('Invalid response from proxy' + ((match && ': ' + match[1]) || '')));
|
|
119
|
-
}
|
|
120
108
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
109
|
+
// proxy connection is now established
|
|
110
|
+
finished = true;
|
|
111
|
+
|
|
112
|
+
// check response code
|
|
113
|
+
match = headers.match(/^HTTP\/\d+\.\d+ (\d+)/i);
|
|
114
|
+
if (!match || (match[1] || '').charAt(0) !== '2') {
|
|
115
|
+
try {
|
|
116
|
+
socket.destroy();
|
|
117
|
+
} catch (E) {
|
|
118
|
+
// ignore
|
|
119
|
+
}
|
|
120
|
+
return callback(new Error('Invalid response from proxy' + ((match && ': ' + match[1]) || '')));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
socket.removeListener('error', tempSocketErr);
|
|
124
|
+
return callback(null, socket);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
socket.on('data', onSocketData);
|
|
128
|
+
}
|
|
129
|
+
);
|
|
127
130
|
|
|
128
131
|
socket.once('error', tempSocketErr);
|
|
129
132
|
}
|
|
@@ -223,10 +223,14 @@ class SMTPConnection extends EventEmitter {
|
|
|
223
223
|
// socket object is set up but not yet connected
|
|
224
224
|
this._socket = this.options.socket;
|
|
225
225
|
try {
|
|
226
|
-
this._socket.connect(
|
|
227
|
-
this.
|
|
228
|
-
this.
|
|
229
|
-
|
|
226
|
+
this._socket.connect(
|
|
227
|
+
this.port,
|
|
228
|
+
this.host,
|
|
229
|
+
() => {
|
|
230
|
+
this._socket.setKeepAlive(true);
|
|
231
|
+
this._onConnect();
|
|
232
|
+
}
|
|
233
|
+
);
|
|
230
234
|
} catch (E) {
|
|
231
235
|
return setImmediate(() => this._onError(E, 'ECONNECTION', false, 'CONN'));
|
|
232
236
|
}
|
|
@@ -238,20 +242,28 @@ class SMTPConnection extends EventEmitter {
|
|
|
238
242
|
});
|
|
239
243
|
}
|
|
240
244
|
try {
|
|
241
|
-
this._socket = tls.connect(
|
|
242
|
-
this.
|
|
243
|
-
this.
|
|
244
|
-
|
|
245
|
+
this._socket = tls.connect(
|
|
246
|
+
this.port,
|
|
247
|
+
this.host,
|
|
248
|
+
opts,
|
|
249
|
+
() => {
|
|
250
|
+
this._socket.setKeepAlive(true);
|
|
251
|
+
this._onConnect();
|
|
252
|
+
}
|
|
253
|
+
);
|
|
245
254
|
} catch (E) {
|
|
246
255
|
return setImmediate(() => this._onError(E, 'ECONNECTION', false, 'CONN'));
|
|
247
256
|
}
|
|
248
257
|
} else {
|
|
249
258
|
// connect using plaintext
|
|
250
259
|
try {
|
|
251
|
-
this._socket = net.connect(
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
260
|
+
this._socket = net.connect(
|
|
261
|
+
opts,
|
|
262
|
+
() => {
|
|
263
|
+
this._socket.setKeepAlive(true);
|
|
264
|
+
this._onConnect();
|
|
265
|
+
}
|
|
266
|
+
);
|
|
255
267
|
} catch (E) {
|
|
256
268
|
return setImmediate(() => this._onError(E, 'ECONNECTION', false, 'CONN'));
|
|
257
269
|
}
|
|
@@ -578,19 +590,6 @@ class SMTPConnection extends EventEmitter {
|
|
|
578
590
|
|
|
579
591
|
err = this._formatError(err, type, data, command);
|
|
580
592
|
|
|
581
|
-
let entry = {
|
|
582
|
-
err
|
|
583
|
-
};
|
|
584
|
-
if (type) {
|
|
585
|
-
entry.errorType = type;
|
|
586
|
-
}
|
|
587
|
-
if (data) {
|
|
588
|
-
entry.errorData = data;
|
|
589
|
-
}
|
|
590
|
-
if (command) {
|
|
591
|
-
entry.command = command;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
593
|
this.logger.error(data, err.message);
|
|
595
594
|
|
|
596
595
|
this.emit('error', err);
|
|
@@ -703,16 +702,19 @@ class SMTPConnection extends EventEmitter {
|
|
|
703
702
|
});
|
|
704
703
|
|
|
705
704
|
this.upgrading = true;
|
|
706
|
-
this._socket = tls.connect(
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
705
|
+
this._socket = tls.connect(
|
|
706
|
+
opts,
|
|
707
|
+
() => {
|
|
708
|
+
this.secure = true;
|
|
709
|
+
this.upgrading = false;
|
|
710
|
+
this._socket.on('data', chunk => this._onData(chunk));
|
|
710
711
|
|
|
711
|
-
|
|
712
|
-
|
|
712
|
+
socketPlain.removeAllListeners('close');
|
|
713
|
+
socketPlain.removeAllListeners('end');
|
|
713
714
|
|
|
714
|
-
|
|
715
|
-
|
|
715
|
+
return callback(null, true);
|
|
716
|
+
}
|
|
717
|
+
);
|
|
716
718
|
|
|
717
719
|
this._socket.on('error', err => this._onError(err, 'ESOCKET', false, 'CONN'));
|
|
718
720
|
this._socket.once('close', errored => this._onClose(errored));
|
|
@@ -897,7 +899,7 @@ class SMTPConnection extends EventEmitter {
|
|
|
897
899
|
break;
|
|
898
900
|
case 'FULL':
|
|
899
901
|
case 'BODY':
|
|
900
|
-
ret = '
|
|
902
|
+
ret = 'FULL';
|
|
901
903
|
break;
|
|
902
904
|
}
|
|
903
905
|
}
|
|
@@ -967,9 +969,12 @@ class SMTPConnection extends EventEmitter {
|
|
|
967
969
|
});
|
|
968
970
|
}
|
|
969
971
|
|
|
970
|
-
dataStream.pipe(
|
|
971
|
-
|
|
972
|
-
|
|
972
|
+
dataStream.pipe(
|
|
973
|
+
this._socket,
|
|
974
|
+
{
|
|
975
|
+
end: false
|
|
976
|
+
}
|
|
977
|
+
);
|
|
973
978
|
|
|
974
979
|
if (this.options.debug) {
|
|
975
980
|
logStream = new PassThrough();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodemailer",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.7.0",
|
|
4
4
|
"description": "Easy as cake e-mail sending from your Node.js applications",
|
|
5
5
|
"main": "lib/nodemailer.js",
|
|
6
6
|
"scripts": {
|
|
@@ -21,10 +21,11 @@
|
|
|
21
21
|
"homepage": "https://nodemailer.com/",
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"bunyan": "^1.8.12",
|
|
24
|
-
"chai": "^4.
|
|
24
|
+
"chai": "^4.2.0",
|
|
25
25
|
"eslint-config-nodemailer": "^1.2.0",
|
|
26
|
+
"eslint-config-prettier": "^3.3.0",
|
|
26
27
|
"grunt": "^1.0.3",
|
|
27
|
-
"grunt-cli": "^1.2
|
|
28
|
+
"grunt-cli": "^1.3.2",
|
|
28
29
|
"grunt-eslint": "^21.0.0",
|
|
29
30
|
"grunt-mocha-test": "^0.13.3",
|
|
30
31
|
"libbase64": "^1.0.3",
|
|
@@ -33,8 +34,8 @@
|
|
|
33
34
|
"mocha": "^5.2.0",
|
|
34
35
|
"proxy": "^0.2.4",
|
|
35
36
|
"proxy-test-server": "^1.0.0",
|
|
36
|
-
"sinon": "^
|
|
37
|
-
"smtp-server": "^3.4.
|
|
37
|
+
"sinon": "^7.1.1",
|
|
38
|
+
"smtp-server": "^3.4.7"
|
|
38
39
|
},
|
|
39
40
|
"engines": {
|
|
40
41
|
"node": ">=6.0.0"
|