nodemailer 8.0.2 → 8.0.4
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 +17 -0
- package/lib/addressparser/index.js +68 -83
- package/lib/base64/index.js +6 -5
- package/lib/dkim/index.js +8 -16
- package/lib/dkim/message-parser.js +12 -13
- package/lib/dkim/relaxed-body.js +2 -2
- package/lib/dkim/sign.js +13 -14
- package/lib/errors.js +4 -7
- package/lib/fetch/cookies.js +13 -18
- package/lib/fetch/index.js +12 -15
- package/lib/json-transport/index.js +4 -4
- package/lib/mail-composer/index.js +86 -116
- package/lib/mailer/index.js +26 -26
- package/lib/mailer/mail-message.js +17 -21
- package/lib/mime-funcs/index.js +25 -40
- package/lib/mime-funcs/mime-types.js +7 -11
- package/lib/mime-node/index.js +81 -72
- package/lib/mime-node/last-newline.js +1 -1
- package/lib/mime-node/le-unix.js +4 -7
- package/lib/mime-node/le-windows.js +1 -4
- package/lib/nodemailer.js +11 -18
- package/lib/qp/index.js +24 -21
- package/lib/sendmail-transport/index.js +30 -41
- package/lib/ses-transport/index.js +23 -34
- package/lib/shared/index.js +94 -140
- package/lib/smtp-connection/data-stream.js +3 -6
- package/lib/smtp-connection/http-proxy-client.js +11 -13
- package/lib/smtp-connection/index.js +118 -182
- package/lib/smtp-pool/index.js +20 -32
- package/lib/smtp-pool/pool-resource.js +8 -12
- package/lib/smtp-transport/index.js +22 -42
- package/lib/stream-transport/index.js +7 -7
- package/lib/well-known/index.js +7 -7
- package/lib/xoauth2/index.js +22 -28
- package/package.json +4 -3
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const Transform = stream.Transform;
|
|
3
|
+
const { Transform } = require('stream');
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
* Escapes dots in the beginning of lines. Ends the stream with <CR><LF>.<CR><LF>
|
|
@@ -12,9 +11,7 @@ const Transform = stream.Transform;
|
|
|
12
11
|
class DataStream extends Transform {
|
|
13
12
|
constructor(options) {
|
|
14
13
|
super(options);
|
|
15
|
-
// init Transform
|
|
16
14
|
this.options = options || {};
|
|
17
|
-
this._curLine = '';
|
|
18
15
|
|
|
19
16
|
this.inByteCount = 0;
|
|
20
17
|
this.outByteCount = 0;
|
|
@@ -25,7 +22,7 @@ class DataStream extends Transform {
|
|
|
25
22
|
* Escapes dots
|
|
26
23
|
*/
|
|
27
24
|
_transform(chunk, encoding, done) {
|
|
28
|
-
|
|
25
|
+
const chunks = [];
|
|
29
26
|
let chunklen = 0;
|
|
30
27
|
let i,
|
|
31
28
|
len,
|
|
@@ -53,7 +50,7 @@ class DataStream extends Transform {
|
|
|
53
50
|
lastPos = i + 1;
|
|
54
51
|
}
|
|
55
52
|
} else if (chunk[i] === 0x0a) {
|
|
56
|
-
//
|
|
53
|
+
// \n
|
|
57
54
|
if ((i && chunk[i - 1] !== 0x0d) || (!i && this.lastByte !== 0x0d)) {
|
|
58
55
|
if (i > lastPos) {
|
|
59
56
|
buf = chunk.slice(lastPos, i);
|
|
@@ -22,18 +22,14 @@ const errors = require('../errors');
|
|
|
22
22
|
* @param {Function} callback Callback to run with the rocket object once connection is established
|
|
23
23
|
*/
|
|
24
24
|
function httpProxyClient(proxyUrl, destinationPort, destinationHost, callback) {
|
|
25
|
-
|
|
25
|
+
const proxy = urllib.parse(proxyUrl);
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
let options;
|
|
29
|
-
let connect;
|
|
30
|
-
let socket;
|
|
31
|
-
|
|
32
|
-
options = {
|
|
27
|
+
const options = {
|
|
33
28
|
host: proxy.hostname,
|
|
34
29
|
port: Number(proxy.port) ? Number(proxy.port) : proxy.protocol === 'https:' ? 443 : 80
|
|
35
30
|
};
|
|
36
31
|
|
|
32
|
+
let connect;
|
|
37
33
|
if (proxy.protocol === 'https:') {
|
|
38
34
|
// we can use untrusted proxies as long as we verify actual SMTP certificates
|
|
39
35
|
options.rejectUnauthorized = false;
|
|
@@ -42,10 +38,12 @@ function httpProxyClient(proxyUrl, destinationPort, destinationHost, callback) {
|
|
|
42
38
|
connect = net.connect.bind(net);
|
|
43
39
|
}
|
|
44
40
|
|
|
41
|
+
let socket;
|
|
42
|
+
|
|
45
43
|
// Error harness for initial connection. Once connection is established, the responsibility
|
|
46
44
|
// to handle errors is passed to whoever uses this socket
|
|
47
45
|
let finished = false;
|
|
48
|
-
|
|
46
|
+
const tempSocketErr = err => {
|
|
49
47
|
if (finished) {
|
|
50
48
|
return;
|
|
51
49
|
}
|
|
@@ -58,8 +56,8 @@ function httpProxyClient(proxyUrl, destinationPort, destinationHost, callback) {
|
|
|
58
56
|
callback(err);
|
|
59
57
|
};
|
|
60
58
|
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
const timeoutErr = () => {
|
|
60
|
+
const err = new Error('Proxy socket timed out');
|
|
63
61
|
err.code = 'ETIMEDOUT';
|
|
64
62
|
tempSocketErr(err);
|
|
65
63
|
};
|
|
@@ -69,7 +67,7 @@ function httpProxyClient(proxyUrl, destinationPort, destinationHost, callback) {
|
|
|
69
67
|
return;
|
|
70
68
|
}
|
|
71
69
|
|
|
72
|
-
|
|
70
|
+
const reqHeaders = {
|
|
73
71
|
Host: destinationHost + ':' + destinationPort,
|
|
74
72
|
Connection: 'close'
|
|
75
73
|
};
|
|
@@ -93,7 +91,7 @@ function httpProxyClient(proxyUrl, destinationPort, destinationHost, callback) {
|
|
|
93
91
|
);
|
|
94
92
|
|
|
95
93
|
let headers = '';
|
|
96
|
-
|
|
94
|
+
const onSocketData = chunk => {
|
|
97
95
|
let match;
|
|
98
96
|
let remainder;
|
|
99
97
|
|
|
@@ -122,7 +120,7 @@ function httpProxyClient(proxyUrl, destinationPort, destinationHost, callback) {
|
|
|
122
120
|
} catch (_E) {
|
|
123
121
|
// ignore
|
|
124
122
|
}
|
|
125
|
-
|
|
123
|
+
const err = new Error('Invalid response from proxy' + ((match && ': ' + match[1]) || ''));
|
|
126
124
|
err.code = errors.EPROXY;
|
|
127
125
|
return callback(err);
|
|
128
126
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const packageInfo = require('../../package.json');
|
|
4
|
-
const EventEmitter = require('events')
|
|
4
|
+
const { EventEmitter } = require('events');
|
|
5
5
|
const net = require('net');
|
|
6
6
|
const tls = require('tls');
|
|
7
7
|
const os = require('os');
|
|
8
8
|
const crypto = require('crypto');
|
|
9
9
|
const DataStream = require('./data-stream');
|
|
10
|
-
const PassThrough = require('stream')
|
|
10
|
+
const { PassThrough } = require('stream');
|
|
11
11
|
const shared = require('../shared');
|
|
12
12
|
|
|
13
13
|
// default timeout values in ms
|
|
@@ -76,13 +76,12 @@ class SMTPConnection extends EventEmitter {
|
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
this.customAuth = new Map();
|
|
79
|
-
Object.keys(this.options.customAuth || {})
|
|
80
|
-
|
|
81
|
-
if (
|
|
82
|
-
|
|
79
|
+
for (const key of Object.keys(this.options.customAuth || {})) {
|
|
80
|
+
const mapKey = (key || '').toString().trim().toUpperCase();
|
|
81
|
+
if (mapKey) {
|
|
82
|
+
this.customAuth.set(mapKey, this.options.customAuth[key]);
|
|
83
83
|
}
|
|
84
|
-
|
|
85
|
-
});
|
|
84
|
+
}
|
|
86
85
|
|
|
87
86
|
/**
|
|
88
87
|
* Expose version nr, just for the reference
|
|
@@ -266,27 +265,7 @@ class SMTPConnection extends EventEmitter {
|
|
|
266
265
|
} else if (this.options.socket) {
|
|
267
266
|
// socket object is set up but not yet connected
|
|
268
267
|
this._socket = this.options.socket;
|
|
269
|
-
return
|
|
270
|
-
if (err) {
|
|
271
|
-
return setImmediate(() => this._onError(err, 'EDNS', false, 'CONN'));
|
|
272
|
-
}
|
|
273
|
-
this.logger.debug(
|
|
274
|
-
{
|
|
275
|
-
tnx: 'dns',
|
|
276
|
-
source: opts.host,
|
|
277
|
-
resolved: resolved.host,
|
|
278
|
-
cached: !!resolved.cached
|
|
279
|
-
},
|
|
280
|
-
'Resolved %s as %s [cache %s]',
|
|
281
|
-
opts.host,
|
|
282
|
-
resolved.host,
|
|
283
|
-
resolved.cached ? 'hit' : 'miss'
|
|
284
|
-
);
|
|
285
|
-
Object.keys(resolved).forEach(key => {
|
|
286
|
-
if (key.charAt(0) !== '_' && resolved[key]) {
|
|
287
|
-
opts[key] = resolved[key];
|
|
288
|
-
}
|
|
289
|
-
});
|
|
268
|
+
return this._resolveAndConnect(opts, _resolved => {
|
|
290
269
|
try {
|
|
291
270
|
this._socket.connect(this.port, this.host, () => {
|
|
292
271
|
this._socket.setKeepAlive(true);
|
|
@@ -297,80 +276,59 @@ class SMTPConnection extends EventEmitter {
|
|
|
297
276
|
return setImmediate(() => this._onError(E, 'ECONNECTION', false, 'CONN'));
|
|
298
277
|
}
|
|
299
278
|
});
|
|
300
|
-
} else if (this.secureConnection) {
|
|
301
|
-
// connect using tls
|
|
302
|
-
if (this.options.tls) {
|
|
303
|
-
Object.keys(this.options.tls).forEach(key => {
|
|
304
|
-
opts[key] = this.options.tls[key];
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// ensure servername for SNI
|
|
309
|
-
if (this.servername && !opts.servername) {
|
|
310
|
-
opts.servername = this.servername;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
return shared.resolveHostname(opts, (err, resolved) => {
|
|
314
|
-
if (err) {
|
|
315
|
-
return setImmediate(() => this._onError(err, 'EDNS', false, 'CONN'));
|
|
316
|
-
}
|
|
317
|
-
this.logger.debug(
|
|
318
|
-
{
|
|
319
|
-
tnx: 'dns',
|
|
320
|
-
source: opts.host,
|
|
321
|
-
resolved: resolved.host,
|
|
322
|
-
cached: !!resolved.cached
|
|
323
|
-
},
|
|
324
|
-
'Resolved %s as %s [cache %s]',
|
|
325
|
-
opts.host,
|
|
326
|
-
resolved.host,
|
|
327
|
-
resolved.cached ? 'hit' : 'miss'
|
|
328
|
-
);
|
|
329
|
-
Object.keys(resolved).forEach(key => {
|
|
330
|
-
if (key.charAt(0) !== '_' && resolved[key]) {
|
|
331
|
-
opts[key] = resolved[key];
|
|
332
|
-
}
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
// Store fallback addresses for retry on connection failure
|
|
336
|
-
this._fallbackAddresses = (resolved._addresses || []).filter(addr => addr !== opts.host);
|
|
337
|
-
this._connectOpts = Object.assign({}, opts);
|
|
338
|
-
|
|
339
|
-
this._connectToHost(opts, true);
|
|
340
|
-
});
|
|
341
279
|
} else {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
280
|
+
if (this.secureConnection) {
|
|
281
|
+
Object.assign(opts, this.options.tls || {});
|
|
282
|
+
|
|
283
|
+
// ensure servername for SNI
|
|
284
|
+
if (this.servername && !opts.servername) {
|
|
285
|
+
opts.servername = this.servername;
|
|
346
286
|
}
|
|
347
|
-
|
|
348
|
-
{
|
|
349
|
-
tnx: 'dns',
|
|
350
|
-
source: opts.host,
|
|
351
|
-
resolved: resolved.host,
|
|
352
|
-
cached: !!resolved.cached
|
|
353
|
-
},
|
|
354
|
-
'Resolved %s as %s [cache %s]',
|
|
355
|
-
opts.host,
|
|
356
|
-
resolved.host,
|
|
357
|
-
resolved.cached ? 'hit' : 'miss'
|
|
358
|
-
);
|
|
359
|
-
Object.keys(resolved).forEach(key => {
|
|
360
|
-
if (key.charAt(0) !== '_' && resolved[key]) {
|
|
361
|
-
opts[key] = resolved[key];
|
|
362
|
-
}
|
|
363
|
-
});
|
|
287
|
+
}
|
|
364
288
|
|
|
289
|
+
return this._resolveAndConnect(opts, resolved => {
|
|
365
290
|
// Store fallback addresses for retry on connection failure
|
|
366
291
|
this._fallbackAddresses = (resolved._addresses || []).filter(addr => addr !== opts.host);
|
|
367
292
|
this._connectOpts = Object.assign({}, opts);
|
|
368
293
|
|
|
369
|
-
this._connectToHost(opts,
|
|
294
|
+
this._connectToHost(opts, this.secureConnection);
|
|
370
295
|
});
|
|
371
296
|
}
|
|
372
297
|
}
|
|
373
298
|
|
|
299
|
+
/**
|
|
300
|
+
* Resolves the hostname and applies resolved values to opts,
|
|
301
|
+
* then calls the provided callback with the resolved data
|
|
302
|
+
*
|
|
303
|
+
* @param {Object} opts Connection options (modified in place)
|
|
304
|
+
* @param {Function} callback Called with resolved data on success
|
|
305
|
+
*/
|
|
306
|
+
_resolveAndConnect(opts, callback) {
|
|
307
|
+
return shared.resolveHostname(opts, (err, resolved) => {
|
|
308
|
+
if (err) {
|
|
309
|
+
return setImmediate(() => this._onError(err, 'EDNS', false, 'CONN'));
|
|
310
|
+
}
|
|
311
|
+
this.logger.debug(
|
|
312
|
+
{
|
|
313
|
+
tnx: 'dns',
|
|
314
|
+
source: opts.host,
|
|
315
|
+
resolved: resolved.host,
|
|
316
|
+
cached: !!resolved.cached
|
|
317
|
+
},
|
|
318
|
+
'Resolved %s as %s [cache %s]',
|
|
319
|
+
opts.host,
|
|
320
|
+
resolved.host,
|
|
321
|
+
resolved.cached ? 'hit' : 'miss'
|
|
322
|
+
);
|
|
323
|
+
for (const key of Object.keys(resolved)) {
|
|
324
|
+
if (key.charAt(0) !== '_' && resolved[key]) {
|
|
325
|
+
opts[key] = resolved[key];
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
callback(resolved);
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
374
332
|
/**
|
|
375
333
|
* Attempts to connect to the specified host address
|
|
376
334
|
*
|
|
@@ -381,7 +339,7 @@ class SMTPConnection extends EventEmitter {
|
|
|
381
339
|
this._connectionAttemptId++;
|
|
382
340
|
const currentAttemptId = this._connectionAttemptId;
|
|
383
341
|
|
|
384
|
-
|
|
342
|
+
const connectFn = secure ? tls.connect : net.connect;
|
|
385
343
|
try {
|
|
386
344
|
this._socket = connectFn(opts, () => {
|
|
387
345
|
// Ignore callback if this is a stale connection attempt
|
|
@@ -418,7 +376,7 @@ class SMTPConnection extends EventEmitter {
|
|
|
418
376
|
clearTimeout(this._connectionTimeout);
|
|
419
377
|
|
|
420
378
|
// Check if we have fallback addresses to try
|
|
421
|
-
|
|
379
|
+
const canFallback = this._fallbackAddresses && this._fallbackAddresses.length && this.stage === 'init' && !this._destroyed;
|
|
422
380
|
|
|
423
381
|
if (!canFallback) {
|
|
424
382
|
// No more fallback addresses, report the error
|
|
@@ -426,7 +384,7 @@ class SMTPConnection extends EventEmitter {
|
|
|
426
384
|
return;
|
|
427
385
|
}
|
|
428
386
|
|
|
429
|
-
|
|
387
|
+
const nextHost = this._fallbackAddresses.shift();
|
|
430
388
|
|
|
431
389
|
this.logger.info(
|
|
432
390
|
{
|
|
@@ -478,12 +436,7 @@ class SMTPConnection extends EventEmitter {
|
|
|
478
436
|
}
|
|
479
437
|
this._closing = true;
|
|
480
438
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
if (this.stage === 'init') {
|
|
484
|
-
// Close the socket immediately when connection timed out
|
|
485
|
-
closeMethod = 'destroy';
|
|
486
|
-
}
|
|
439
|
+
const closeMethod = this.stage === 'init' ? 'destroy' : 'end';
|
|
487
440
|
|
|
488
441
|
this.logger.debug(
|
|
489
442
|
{
|
|
@@ -493,7 +446,7 @@ class SMTPConnection extends EventEmitter {
|
|
|
493
446
|
closeMethod
|
|
494
447
|
);
|
|
495
448
|
|
|
496
|
-
|
|
449
|
+
const socket = (this._socket && this._socket.socket) || this._socket;
|
|
497
450
|
|
|
498
451
|
if (socket && !socket.destroyed) {
|
|
499
452
|
try {
|
|
@@ -551,11 +504,11 @@ class SMTPConnection extends EventEmitter {
|
|
|
551
504
|
}
|
|
552
505
|
|
|
553
506
|
if (this.customAuth.has(this._authMethod)) {
|
|
554
|
-
|
|
507
|
+
const handler = this.customAuth.get(this._authMethod);
|
|
555
508
|
let lastResponse;
|
|
556
509
|
let returned = false;
|
|
557
510
|
|
|
558
|
-
|
|
511
|
+
const resolve = () => {
|
|
559
512
|
if (returned) {
|
|
560
513
|
return;
|
|
561
514
|
}
|
|
@@ -574,7 +527,7 @@ class SMTPConnection extends EventEmitter {
|
|
|
574
527
|
callback(null, true);
|
|
575
528
|
};
|
|
576
529
|
|
|
577
|
-
|
|
530
|
+
const reject = err => {
|
|
578
531
|
if (returned) {
|
|
579
532
|
return;
|
|
580
533
|
}
|
|
@@ -582,7 +535,7 @@ class SMTPConnection extends EventEmitter {
|
|
|
582
535
|
callback(this._formatError(err, 'EAUTH', lastResponse, 'AUTH ' + this._authMethod));
|
|
583
536
|
};
|
|
584
537
|
|
|
585
|
-
|
|
538
|
+
const handlerResponse = handler({
|
|
586
539
|
auth: this._auth,
|
|
587
540
|
method: this._authMethod,
|
|
588
541
|
|
|
@@ -709,7 +662,7 @@ class SMTPConnection extends EventEmitter {
|
|
|
709
662
|
|
|
710
663
|
// ensure that callback is only called once
|
|
711
664
|
let returned = false;
|
|
712
|
-
|
|
665
|
+
const callback = function () {
|
|
713
666
|
if (returned) {
|
|
714
667
|
return;
|
|
715
668
|
}
|
|
@@ -722,11 +675,11 @@ class SMTPConnection extends EventEmitter {
|
|
|
722
675
|
message.on('error', err => callback(this._formatError(err, 'ESTREAM', false, 'API')));
|
|
723
676
|
}
|
|
724
677
|
|
|
725
|
-
|
|
678
|
+
const startTime = Date.now();
|
|
726
679
|
this._setEnvelope(envelope, (err, info) => {
|
|
727
680
|
if (err) {
|
|
728
681
|
// create passthrough stream to consume to prevent OOM
|
|
729
|
-
|
|
682
|
+
const stream = new PassThrough();
|
|
730
683
|
if (typeof message.pipe === 'function') {
|
|
731
684
|
message.pipe(stream);
|
|
732
685
|
} else {
|
|
@@ -736,8 +689,8 @@ class SMTPConnection extends EventEmitter {
|
|
|
736
689
|
|
|
737
690
|
return callback(err);
|
|
738
691
|
}
|
|
739
|
-
|
|
740
|
-
|
|
692
|
+
const envelopeTime = Date.now();
|
|
693
|
+
const stream = this._createSendStream((err, str) => {
|
|
741
694
|
if (err) {
|
|
742
695
|
return callback(err);
|
|
743
696
|
}
|
|
@@ -921,7 +874,7 @@ class SMTPConnection extends EventEmitter {
|
|
|
921
874
|
err.message += ': ' + response;
|
|
922
875
|
}
|
|
923
876
|
|
|
924
|
-
|
|
877
|
+
const responseCode = (typeof response === 'string' && Number((response.match(/^\d+/) || [])[0])) || false;
|
|
925
878
|
if (responseCode) {
|
|
926
879
|
err.responseCode = responseCode;
|
|
927
880
|
}
|
|
@@ -1016,15 +969,14 @@ class SMTPConnection extends EventEmitter {
|
|
|
1016
969
|
this._socket.removeListener('data', this._onSocketData); // incoming data is going to be gibberish from this point onwards
|
|
1017
970
|
this._socket.removeListener('timeout', this._onSocketTimeout); // timeout will be re-set for the new socket object
|
|
1018
971
|
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
});
|
|
972
|
+
const socketPlain = this._socket;
|
|
973
|
+
const opts = Object.assign(
|
|
974
|
+
{
|
|
975
|
+
socket: this._socket,
|
|
976
|
+
host: this.host
|
|
977
|
+
},
|
|
978
|
+
this.options.tls || {}
|
|
979
|
+
);
|
|
1028
980
|
|
|
1029
981
|
// ensure servername for SNI
|
|
1030
982
|
if (this.servername && !opts.servername) {
|
|
@@ -1092,7 +1044,7 @@ class SMTPConnection extends EventEmitter {
|
|
|
1092
1044
|
setImmediate(() => this._processResponse());
|
|
1093
1045
|
}
|
|
1094
1046
|
|
|
1095
|
-
|
|
1047
|
+
const action = this._responseActions.shift();
|
|
1096
1048
|
|
|
1097
1049
|
if (typeof action === 'function') {
|
|
1098
1050
|
action.call(this, str);
|
|
@@ -1140,7 +1092,7 @@ class SMTPConnection extends EventEmitter {
|
|
|
1140
1092
|
* {from:{address:'...',name:'...'}, to:[address:'...',name:'...']}
|
|
1141
1093
|
*/
|
|
1142
1094
|
_setEnvelope(envelope, callback) {
|
|
1143
|
-
|
|
1095
|
+
const args = [];
|
|
1144
1096
|
let useSmtpUtf8 = false;
|
|
1145
1097
|
|
|
1146
1098
|
this._envelope = envelope || {};
|
|
@@ -1175,7 +1127,7 @@ class SMTPConnection extends EventEmitter {
|
|
|
1175
1127
|
}
|
|
1176
1128
|
|
|
1177
1129
|
// clone the recipients array for latter manipulation
|
|
1178
|
-
this._envelope.rcptQueue =
|
|
1130
|
+
this._envelope.rcptQueue = [].concat(this._envelope.to || []);
|
|
1179
1131
|
this._envelope.rejected = [];
|
|
1180
1132
|
this._envelope.rejectedErrors = [];
|
|
1181
1133
|
this._envelope.accepted = [];
|
|
@@ -1207,7 +1159,10 @@ class SMTPConnection extends EventEmitter {
|
|
|
1207
1159
|
}
|
|
1208
1160
|
|
|
1209
1161
|
if (this._envelope.size && this._supportedExtensions.includes('SIZE')) {
|
|
1210
|
-
|
|
1162
|
+
const sizeValue = Number(this._envelope.size) || 0;
|
|
1163
|
+
if (sizeValue > 0) {
|
|
1164
|
+
args.push('SIZE=' + sizeValue);
|
|
1165
|
+
}
|
|
1211
1166
|
}
|
|
1212
1167
|
|
|
1213
1168
|
// If the server supports DSN and the envelope includes an DSN prop
|
|
@@ -1260,7 +1215,7 @@ class SMTPConnection extends EventEmitter {
|
|
|
1260
1215
|
throw new Error('ret: ' + JSON.stringify(ret));
|
|
1261
1216
|
}
|
|
1262
1217
|
|
|
1263
|
-
|
|
1218
|
+
const envid = (params.envid || params.id || '').toString() || null;
|
|
1264
1219
|
|
|
1265
1220
|
let notify = params.notify || null;
|
|
1266
1221
|
if (notify) {
|
|
@@ -1268,8 +1223,8 @@ class SMTPConnection extends EventEmitter {
|
|
|
1268
1223
|
notify = notify.split(',');
|
|
1269
1224
|
}
|
|
1270
1225
|
notify = notify.map(n => n.trim().toUpperCase());
|
|
1271
|
-
|
|
1272
|
-
|
|
1226
|
+
const validNotify = ['NEVER', 'SUCCESS', 'FAILURE', 'DELAY'];
|
|
1227
|
+
const invalidNotify = notify.filter(n => !validNotify.includes(n));
|
|
1273
1228
|
if (invalidNotify.length || (notify.length > 1 && notify.includes('NEVER'))) {
|
|
1274
1229
|
throw new Error('notify: ' + JSON.stringify(notify.join(',')));
|
|
1275
1230
|
}
|
|
@@ -1290,7 +1245,7 @@ class SMTPConnection extends EventEmitter {
|
|
|
1290
1245
|
}
|
|
1291
1246
|
|
|
1292
1247
|
_getDsnRcptToArgs() {
|
|
1293
|
-
|
|
1248
|
+
const args = [];
|
|
1294
1249
|
// If the server supports DSN and the envelope includes an DSN prop
|
|
1295
1250
|
// then append DSN params to the RCPT TO command
|
|
1296
1251
|
if (this._envelope.dsn && this._supportedExtensions.includes('DSN')) {
|
|
@@ -1305,12 +1260,11 @@ class SMTPConnection extends EventEmitter {
|
|
|
1305
1260
|
}
|
|
1306
1261
|
|
|
1307
1262
|
_createSendStream(callback) {
|
|
1308
|
-
|
|
1309
|
-
let logStream;
|
|
1263
|
+
const dataStream = new DataStream();
|
|
1310
1264
|
|
|
1311
1265
|
if (this.options.lmtp) {
|
|
1312
1266
|
this._envelope.accepted.forEach((recipient, i) => {
|
|
1313
|
-
|
|
1267
|
+
const final = i === this._envelope.accepted.length - 1;
|
|
1314
1268
|
this._responseActions.push(str => {
|
|
1315
1269
|
this._actionLMTPStream(recipient, final, str, callback);
|
|
1316
1270
|
});
|
|
@@ -1326,7 +1280,7 @@ class SMTPConnection extends EventEmitter {
|
|
|
1326
1280
|
});
|
|
1327
1281
|
|
|
1328
1282
|
if (this.options.debug) {
|
|
1329
|
-
logStream = new PassThrough();
|
|
1283
|
+
const logStream = new PassThrough();
|
|
1330
1284
|
logStream.on('readable', () => {
|
|
1331
1285
|
let chunk;
|
|
1332
1286
|
while ((chunk = logStream.read())) {
|
|
@@ -1604,24 +1558,21 @@ class SMTPConnection extends EventEmitter {
|
|
|
1604
1558
|
* @param {String} str Message from the server
|
|
1605
1559
|
*/
|
|
1606
1560
|
_actionAUTH_CRAM_MD5(str, callback) {
|
|
1607
|
-
|
|
1608
|
-
let challengeString = '';
|
|
1561
|
+
const challengeMatch = str.match(/^334\s+(.+)$/);
|
|
1609
1562
|
|
|
1610
1563
|
if (!challengeMatch) {
|
|
1611
1564
|
return callback(
|
|
1612
1565
|
this._formatError('Invalid login sequence while waiting for server challenge string', 'EAUTH', str, 'AUTH CRAM-MD5')
|
|
1613
1566
|
);
|
|
1614
|
-
} else {
|
|
1615
|
-
challengeString = challengeMatch[1];
|
|
1616
1567
|
}
|
|
1617
1568
|
|
|
1618
1569
|
// Decode from base64
|
|
1619
|
-
|
|
1620
|
-
|
|
1570
|
+
const base64decoded = Buffer.from(challengeMatch[1], 'base64').toString('ascii');
|
|
1571
|
+
const hmacMD5 = crypto.createHmac('md5', this._auth.credentials.pass);
|
|
1621
1572
|
|
|
1622
1573
|
hmacMD5.update(base64decoded);
|
|
1623
1574
|
|
|
1624
|
-
|
|
1575
|
+
const prepended = this._auth.credentials.user + ' ' + hmacMD5.digest('hex');
|
|
1625
1576
|
|
|
1626
1577
|
this._responseActions.push(str => {
|
|
1627
1578
|
this._actionAUTH_CRAM_MD5_PASS(str, callback);
|
|
@@ -1742,39 +1693,29 @@ class SMTPConnection extends EventEmitter {
|
|
|
1742
1693
|
* @param {String} str Message from the server
|
|
1743
1694
|
*/
|
|
1744
1695
|
_actionMAIL(str, callback) {
|
|
1745
|
-
let message, curRecipient;
|
|
1746
1696
|
if (Number(str.charAt(0)) !== 2) {
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
}
|
|
1697
|
+
const message =
|
|
1698
|
+
this._usingSmtpUtf8 && /^550 /.test(str) && /[\x80-\uFFFF]/.test(this._envelope.from)
|
|
1699
|
+
? 'Internationalized mailbox name not allowed'
|
|
1700
|
+
: 'Mail command failed';
|
|
1752
1701
|
return callback(this._formatError(message, 'EENVELOPE', str, 'MAIL FROM'));
|
|
1753
1702
|
}
|
|
1754
1703
|
|
|
1755
1704
|
if (!this._envelope.rcptQueue.length) {
|
|
1756
1705
|
return callback(this._formatError("Can't send mail - no recipients defined", 'EENVELOPE', false, 'API'));
|
|
1757
|
-
} else {
|
|
1758
|
-
this._recipientQueue = [];
|
|
1759
|
-
|
|
1760
|
-
if (this._supportedExtensions.includes('PIPELINING')) {
|
|
1761
|
-
while (this._envelope.rcptQueue.length) {
|
|
1762
|
-
curRecipient = this._envelope.rcptQueue.shift();
|
|
1763
|
-
this._recipientQueue.push(curRecipient);
|
|
1764
|
-
this._responseActions.push(str => {
|
|
1765
|
-
this._actionRCPT(str, callback);
|
|
1766
|
-
});
|
|
1767
|
-
this._sendCommand('RCPT TO:<' + curRecipient + '>' + this._getDsnRcptToArgs());
|
|
1768
|
-
}
|
|
1769
|
-
} else {
|
|
1770
|
-
curRecipient = this._envelope.rcptQueue.shift();
|
|
1771
|
-
this._recipientQueue.push(curRecipient);
|
|
1772
|
-
this._responseActions.push(str => {
|
|
1773
|
-
this._actionRCPT(str, callback);
|
|
1774
|
-
});
|
|
1775
|
-
this._sendCommand('RCPT TO:<' + curRecipient + '>' + this._getDsnRcptToArgs());
|
|
1776
|
-
}
|
|
1777
1706
|
}
|
|
1707
|
+
|
|
1708
|
+
this._recipientQueue = [];
|
|
1709
|
+
const usePipelining = this._supportedExtensions.includes('PIPELINING');
|
|
1710
|
+
|
|
1711
|
+
do {
|
|
1712
|
+
const curRecipient = this._envelope.rcptQueue.shift();
|
|
1713
|
+
this._recipientQueue.push(curRecipient);
|
|
1714
|
+
this._responseActions.push(str => {
|
|
1715
|
+
this._actionRCPT(str, callback);
|
|
1716
|
+
});
|
|
1717
|
+
this._sendCommand('RCPT TO:<' + curRecipient + '>' + this._getDsnRcptToArgs());
|
|
1718
|
+
} while (usePipelining && this._envelope.rcptQueue.length);
|
|
1778
1719
|
}
|
|
1779
1720
|
|
|
1780
1721
|
/**
|
|
@@ -1783,16 +1724,14 @@ class SMTPConnection extends EventEmitter {
|
|
|
1783
1724
|
* @param {String} str Message from the server
|
|
1784
1725
|
*/
|
|
1785
1726
|
_actionRCPT(str, callback) {
|
|
1786
|
-
let
|
|
1787
|
-
|
|
1788
|
-
curRecipient = this._recipientQueue.shift();
|
|
1727
|
+
let err;
|
|
1728
|
+
const curRecipient = this._recipientQueue.shift();
|
|
1789
1729
|
if (Number(str.charAt(0)) !== 2) {
|
|
1790
1730
|
// this is a soft error
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
}
|
|
1731
|
+
const message =
|
|
1732
|
+
this._usingSmtpUtf8 && /^553 /.test(str) && /[\x80-\uFFFF]/.test(curRecipient)
|
|
1733
|
+
? 'Internationalized mailbox name not allowed'
|
|
1734
|
+
: 'Recipient command failed';
|
|
1796
1735
|
this._envelope.rejected.push(curRecipient);
|
|
1797
1736
|
// store error for the failed recipient
|
|
1798
1737
|
err = this._formatError(message, 'EENVELOPE', str, 'RCPT TO');
|
|
@@ -1815,12 +1754,12 @@ class SMTPConnection extends EventEmitter {
|
|
|
1815
1754
|
return callback(err);
|
|
1816
1755
|
}
|
|
1817
1756
|
} else if (this._envelope.rcptQueue.length) {
|
|
1818
|
-
|
|
1819
|
-
this._recipientQueue.push(
|
|
1757
|
+
const nextRecipient = this._envelope.rcptQueue.shift();
|
|
1758
|
+
this._recipientQueue.push(nextRecipient);
|
|
1820
1759
|
this._responseActions.push(str => {
|
|
1821
1760
|
this._actionRCPT(str, callback);
|
|
1822
1761
|
});
|
|
1823
|
-
this._sendCommand('RCPT TO:<' +
|
|
1762
|
+
this._sendCommand('RCPT TO:<' + nextRecipient + '>' + this._getDsnRcptToArgs());
|
|
1824
1763
|
}
|
|
1825
1764
|
}
|
|
1826
1765
|
|
|
@@ -1836,7 +1775,7 @@ class SMTPConnection extends EventEmitter {
|
|
|
1836
1775
|
return callback(this._formatError('Data command failed', 'EENVELOPE', str, 'DATA'));
|
|
1837
1776
|
}
|
|
1838
1777
|
|
|
1839
|
-
|
|
1778
|
+
const response = {
|
|
1840
1779
|
accepted: this._envelope.accepted,
|
|
1841
1780
|
rejected: this._envelope.rejected
|
|
1842
1781
|
};
|
|
@@ -1860,12 +1799,9 @@ class SMTPConnection extends EventEmitter {
|
|
|
1860
1799
|
*/
|
|
1861
1800
|
_actionSMTPStream(str, callback) {
|
|
1862
1801
|
if (Number(str.charAt(0)) !== 2) {
|
|
1863
|
-
// Message failed
|
|
1864
1802
|
return callback(this._formatError('Message failed', 'EMESSAGE', str, 'DATA'));
|
|
1865
|
-
} else {
|
|
1866
|
-
// Message sent succesfully
|
|
1867
|
-
return callback(null, str);
|
|
1868
1803
|
}
|
|
1804
|
+
return callback(null, str);
|
|
1869
1805
|
}
|
|
1870
1806
|
|
|
1871
1807
|
/**
|