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
package/lib/mime-node/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
const crypto = require('crypto');
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const punycode = require('../punycode');
|
|
8
|
-
const PassThrough = require('stream')
|
|
8
|
+
const { PassThrough } = require('stream');
|
|
9
9
|
const shared = require('../shared');
|
|
10
10
|
|
|
11
11
|
const mimeFuncs = require('../mime-funcs');
|
|
@@ -19,6 +19,8 @@ const LastNewline = require('./last-newline');
|
|
|
19
19
|
const LeWindows = require('./le-windows');
|
|
20
20
|
const LeUnix = require('./le-unix');
|
|
21
21
|
|
|
22
|
+
const FORMATTED_HEADERS = ['From', 'Sender', 'To', 'Cc', 'Bcc', 'Reply-To', 'Date', 'References'];
|
|
23
|
+
|
|
22
24
|
/**
|
|
23
25
|
* Creates a new mime tree node. Assumes 'multipart/*' as the content type
|
|
24
26
|
* if it is a branch, anything else counts as leaf. If rootNode is missing from
|
|
@@ -54,7 +56,7 @@ class MimeNode {
|
|
|
54
56
|
/**
|
|
55
57
|
* If date headers is missing and current node is the root, this value is used instead
|
|
56
58
|
*/
|
|
57
|
-
this.date = new Date();
|
|
59
|
+
this.date = options.parentNode ? null : new Date();
|
|
58
60
|
|
|
59
61
|
/**
|
|
60
62
|
* Root node for current mime tree
|
|
@@ -175,7 +177,7 @@ class MimeNode {
|
|
|
175
177
|
options = contentType;
|
|
176
178
|
contentType = undefined;
|
|
177
179
|
}
|
|
178
|
-
|
|
180
|
+
const node = new MimeNode(contentType, options);
|
|
179
181
|
this.appendChild(node);
|
|
180
182
|
return node;
|
|
181
183
|
}
|
|
@@ -256,8 +258,7 @@ class MimeNode {
|
|
|
256
258
|
* @return {Object} current node
|
|
257
259
|
*/
|
|
258
260
|
setHeader(key, value) {
|
|
259
|
-
let added = false
|
|
260
|
-
headerValue;
|
|
261
|
+
let added = false;
|
|
261
262
|
|
|
262
263
|
// Allow setting multiple headers at once
|
|
263
264
|
if (!value && key && typeof key === 'object') {
|
|
@@ -280,7 +281,7 @@ class MimeNode {
|
|
|
280
281
|
|
|
281
282
|
key = this._normalizeHeaderKey(key);
|
|
282
283
|
|
|
283
|
-
headerValue = {
|
|
284
|
+
const headerValue = {
|
|
284
285
|
key,
|
|
285
286
|
value
|
|
286
287
|
};
|
|
@@ -404,8 +405,8 @@ class MimeNode {
|
|
|
404
405
|
});
|
|
405
406
|
}
|
|
406
407
|
|
|
407
|
-
|
|
408
|
-
|
|
408
|
+
const stream = this.createReadStream();
|
|
409
|
+
const buf = [];
|
|
409
410
|
let buflen = 0;
|
|
410
411
|
let returned = false;
|
|
411
412
|
|
|
@@ -445,7 +446,7 @@ class MimeNode {
|
|
|
445
446
|
|
|
446
447
|
getTransferEncoding() {
|
|
447
448
|
let transferEncoding = false;
|
|
448
|
-
|
|
449
|
+
const contentType = (this.getHeader('Content-Type') || '').toString().toLowerCase().trim();
|
|
449
450
|
|
|
450
451
|
if (this.content) {
|
|
451
452
|
transferEncoding = (this.getHeader('Content-Transfer-Encoding') || '').toString().toLowerCase().trim();
|
|
@@ -475,8 +476,8 @@ class MimeNode {
|
|
|
475
476
|
* @returns {String} Headers
|
|
476
477
|
*/
|
|
477
478
|
buildHeaders() {
|
|
478
|
-
|
|
479
|
-
|
|
479
|
+
const transferEncoding = this.getTransferEncoding();
|
|
480
|
+
const headers = [];
|
|
480
481
|
|
|
481
482
|
if (transferEncoding) {
|
|
482
483
|
this.setHeader('Content-Transfer-Encoding', transferEncoding);
|
|
@@ -501,7 +502,7 @@ class MimeNode {
|
|
|
501
502
|
|
|
502
503
|
// Ensure that Content-Type is the last header for the root node
|
|
503
504
|
for (let i = this._headers.length - 2; i >= 0; i--) {
|
|
504
|
-
|
|
505
|
+
const header = this._headers[i];
|
|
505
506
|
if (header.key === 'Content-Type') {
|
|
506
507
|
this._headers.splice(i, 1);
|
|
507
508
|
this._headers.push(header);
|
|
@@ -514,8 +515,8 @@ class MimeNode {
|
|
|
514
515
|
let value = header.value;
|
|
515
516
|
let structured;
|
|
516
517
|
let param;
|
|
517
|
-
|
|
518
|
-
|
|
518
|
+
const options = {};
|
|
519
|
+
const formattedHeaders = FORMATTED_HEADERS;
|
|
519
520
|
|
|
520
521
|
if (value && typeof value === 'object' && !formattedHeaders.includes(key)) {
|
|
521
522
|
Object.keys(value).forEach(key => {
|
|
@@ -593,7 +594,7 @@ class MimeNode {
|
|
|
593
594
|
}
|
|
594
595
|
|
|
595
596
|
if (typeof this.normalizeHeaderKey === 'function') {
|
|
596
|
-
|
|
597
|
+
const normalized = this.normalizeHeaderKey(key, value);
|
|
597
598
|
if (normalized && typeof normalized === 'string' && normalized.length) {
|
|
598
599
|
key = normalized;
|
|
599
600
|
}
|
|
@@ -614,7 +615,7 @@ class MimeNode {
|
|
|
614
615
|
createReadStream(options) {
|
|
615
616
|
options = options || {};
|
|
616
617
|
|
|
617
|
-
|
|
618
|
+
const stream = new PassThrough(options);
|
|
618
619
|
let outputStream = stream;
|
|
619
620
|
let transform;
|
|
620
621
|
|
|
@@ -682,13 +683,13 @@ class MimeNode {
|
|
|
682
683
|
}
|
|
683
684
|
|
|
684
685
|
stream(outputStream, options, done) {
|
|
685
|
-
|
|
686
|
+
const transferEncoding = this.getTransferEncoding();
|
|
686
687
|
let contentStream;
|
|
687
688
|
let localStream;
|
|
688
689
|
|
|
689
690
|
// protect actual callback against multiple triggering
|
|
690
691
|
let returned = false;
|
|
691
|
-
|
|
692
|
+
const callback = err => {
|
|
692
693
|
if (returned) {
|
|
693
694
|
return;
|
|
694
695
|
}
|
|
@@ -698,14 +699,14 @@ class MimeNode {
|
|
|
698
699
|
|
|
699
700
|
// for multipart nodes, push child nodes
|
|
700
701
|
// for content nodes end the stream
|
|
701
|
-
|
|
702
|
+
const finalize = () => {
|
|
702
703
|
let childId = 0;
|
|
703
|
-
|
|
704
|
+
const processChildNode = () => {
|
|
704
705
|
if (childId >= this.childNodes.length) {
|
|
705
706
|
outputStream.write('\r\n--' + this.boundary + '--\r\n');
|
|
706
707
|
return callback();
|
|
707
708
|
}
|
|
708
|
-
|
|
709
|
+
const child = this.childNodes[childId++];
|
|
709
710
|
outputStream.write((childId > 1 ? '\r\n' : '') + '--' + this.boundary + '\r\n');
|
|
710
711
|
child.stream(outputStream, options, err => {
|
|
711
712
|
if (err) {
|
|
@@ -723,7 +724,7 @@ class MimeNode {
|
|
|
723
724
|
};
|
|
724
725
|
|
|
725
726
|
// pushes node content
|
|
726
|
-
|
|
727
|
+
const sendContent = () => {
|
|
727
728
|
if (this.content) {
|
|
728
729
|
if (Object.prototype.toString.call(this.content) === '[object Error]') {
|
|
729
730
|
// content is already errored
|
|
@@ -736,7 +737,7 @@ class MimeNode {
|
|
|
736
737
|
this.content.once('error', this._contentErrorHandler);
|
|
737
738
|
}
|
|
738
739
|
|
|
739
|
-
|
|
740
|
+
const createStream = () => {
|
|
740
741
|
if (['quoted-printable', 'base64'].includes(transferEncoding)) {
|
|
741
742
|
contentStream = new (transferEncoding === 'base64' ? base64 : qp).Encoder(options);
|
|
742
743
|
|
|
@@ -761,10 +762,10 @@ class MimeNode {
|
|
|
761
762
|
};
|
|
762
763
|
|
|
763
764
|
if (this.content._resolve) {
|
|
764
|
-
|
|
765
|
+
const chunks = [];
|
|
765
766
|
let chunklen = 0;
|
|
766
767
|
let returned = false;
|
|
767
|
-
|
|
768
|
+
const sourceStream = this._getStream(this.content);
|
|
768
769
|
sourceStream.on('error', err => {
|
|
769
770
|
if (returned) {
|
|
770
771
|
return;
|
|
@@ -792,9 +793,8 @@ class MimeNode {
|
|
|
792
793
|
setImmediate(createStream);
|
|
793
794
|
}
|
|
794
795
|
return;
|
|
795
|
-
} else {
|
|
796
|
-
return setImmediate(finalize);
|
|
797
796
|
}
|
|
797
|
+
return setImmediate(finalize);
|
|
798
798
|
};
|
|
799
799
|
|
|
800
800
|
if (this._raw) {
|
|
@@ -809,7 +809,7 @@ class MimeNode {
|
|
|
809
809
|
this._raw.removeListener('error', this._contentErrorHandler);
|
|
810
810
|
}
|
|
811
811
|
|
|
812
|
-
|
|
812
|
+
const raw = this._getStream(this._raw);
|
|
813
813
|
raw.pipe(outputStream, {
|
|
814
814
|
end: false
|
|
815
815
|
});
|
|
@@ -851,7 +851,7 @@ class MimeNode {
|
|
|
851
851
|
|
|
852
852
|
this._envelope.to = this._envelope.to.map(to => to.address).filter(address => address);
|
|
853
853
|
|
|
854
|
-
|
|
854
|
+
const standardFields = ['to', 'cc', 'bcc', 'from'];
|
|
855
855
|
Object.keys(envelope).forEach(key => {
|
|
856
856
|
if (!standardFields.includes(key)) {
|
|
857
857
|
this._envelope[key] = envelope[key];
|
|
@@ -867,10 +867,10 @@ class MimeNode {
|
|
|
867
867
|
* @return {Object} Address object
|
|
868
868
|
*/
|
|
869
869
|
getAddresses() {
|
|
870
|
-
|
|
870
|
+
const addresses = {};
|
|
871
871
|
|
|
872
872
|
this._headers.forEach(header => {
|
|
873
|
-
|
|
873
|
+
const key = header.key.toLowerCase();
|
|
874
874
|
if (['from', 'sender', 'reply-to', 'to', 'cc', 'bcc'].includes(key)) {
|
|
875
875
|
if (!Array.isArray(addresses[key])) {
|
|
876
876
|
addresses[key] = [];
|
|
@@ -893,12 +893,12 @@ class MimeNode {
|
|
|
893
893
|
return this._envelope;
|
|
894
894
|
}
|
|
895
895
|
|
|
896
|
-
|
|
896
|
+
const envelope = {
|
|
897
897
|
from: false,
|
|
898
898
|
to: []
|
|
899
899
|
};
|
|
900
900
|
this._headers.forEach(header => {
|
|
901
|
-
|
|
901
|
+
const list = [];
|
|
902
902
|
if (header.key === 'From' || (!envelope.from && ['Reply-To', 'Sender'].includes(header.key))) {
|
|
903
903
|
this._convertAddresses(this._parseAddresses(header.value), list);
|
|
904
904
|
if (list.length && list[0]) {
|
|
@@ -974,14 +974,18 @@ class MimeNode {
|
|
|
974
974
|
});
|
|
975
975
|
|
|
976
976
|
return contentStream;
|
|
977
|
-
}
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
if (typeof content.pipe === 'function') {
|
|
978
980
|
// assume as stream
|
|
979
981
|
return content;
|
|
980
|
-
}
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
if (content && typeof content.path === 'string' && !content.href) {
|
|
981
985
|
if (this.disableFileAccess) {
|
|
982
986
|
contentStream = new PassThrough();
|
|
983
987
|
setImmediate(() => {
|
|
984
|
-
|
|
988
|
+
const err = new Error('File access rejected for ' + content.path);
|
|
985
989
|
err.code = errors.EFILEACCESS;
|
|
986
990
|
contentStream.emit('error', err);
|
|
987
991
|
});
|
|
@@ -989,11 +993,13 @@ class MimeNode {
|
|
|
989
993
|
}
|
|
990
994
|
// read file
|
|
991
995
|
return fs.createReadStream(content.path);
|
|
992
|
-
}
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
if (content && typeof content.href === 'string') {
|
|
993
999
|
if (this.disableUrlAccess) {
|
|
994
1000
|
contentStream = new PassThrough();
|
|
995
1001
|
setImmediate(() => {
|
|
996
|
-
|
|
1002
|
+
const err = new Error('Url access rejected for ' + content.href);
|
|
997
1003
|
err.code = errors.EURLACCESS;
|
|
998
1004
|
contentStream.emit('error', err);
|
|
999
1005
|
});
|
|
@@ -1001,19 +1007,19 @@ class MimeNode {
|
|
|
1001
1007
|
}
|
|
1002
1008
|
// fetch URL
|
|
1003
1009
|
return nmfetch(content.href, { headers: content.httpHeaders });
|
|
1004
|
-
} else {
|
|
1005
|
-
// pass string or buffer content as a stream
|
|
1006
|
-
contentStream = new PassThrough();
|
|
1007
|
-
|
|
1008
|
-
setImmediate(() => {
|
|
1009
|
-
try {
|
|
1010
|
-
contentStream.end(content || '');
|
|
1011
|
-
} catch (_err) {
|
|
1012
|
-
contentStream.emit('error', _err);
|
|
1013
|
-
}
|
|
1014
|
-
});
|
|
1015
|
-
return contentStream;
|
|
1016
1010
|
}
|
|
1011
|
+
|
|
1012
|
+
// pass string or buffer content as a stream
|
|
1013
|
+
contentStream = new PassThrough();
|
|
1014
|
+
|
|
1015
|
+
setImmediate(() => {
|
|
1016
|
+
try {
|
|
1017
|
+
contentStream.end(content || '');
|
|
1018
|
+
} catch (_err) {
|
|
1019
|
+
contentStream.emit('error', _err);
|
|
1020
|
+
}
|
|
1021
|
+
});
|
|
1022
|
+
return contentStream;
|
|
1017
1023
|
}
|
|
1018
1024
|
|
|
1019
1025
|
/**
|
|
@@ -1172,7 +1178,7 @@ class MimeNode {
|
|
|
1172
1178
|
* @return {String} address string
|
|
1173
1179
|
*/
|
|
1174
1180
|
_convertAddresses(addresses, uniqueList) {
|
|
1175
|
-
|
|
1181
|
+
const values = [];
|
|
1176
1182
|
|
|
1177
1183
|
uniqueList = uniqueList || [];
|
|
1178
1184
|
|
|
@@ -1182,17 +1188,15 @@ class MimeNode {
|
|
|
1182
1188
|
|
|
1183
1189
|
if (!address.name) {
|
|
1184
1190
|
values.push(address.address.indexOf(' ') >= 0 ? `<${address.address}>` : `${address.address}`);
|
|
1185
|
-
} else
|
|
1191
|
+
} else {
|
|
1186
1192
|
values.push(`${this._encodeAddressName(address.name)} <${address.address}>`);
|
|
1187
1193
|
}
|
|
1188
1194
|
|
|
1189
|
-
if (address.address) {
|
|
1190
|
-
|
|
1191
|
-
uniqueList.push(address);
|
|
1192
|
-
}
|
|
1195
|
+
if (!uniqueList.some(a => a.address === address.address)) {
|
|
1196
|
+
uniqueList.push(address);
|
|
1193
1197
|
}
|
|
1194
1198
|
} else if (address.group) {
|
|
1195
|
-
|
|
1199
|
+
const groupListAddresses = (address.group.length ? this._convertAddresses(address.group, uniqueList) : '').trim();
|
|
1196
1200
|
values.push(`${this._encodeAddressName(address.name)}:${groupListAddresses};`);
|
|
1197
1201
|
}
|
|
1198
1202
|
});
|
|
@@ -1212,14 +1216,14 @@ class MimeNode {
|
|
|
1212
1216
|
.replace(/[\x00-\x1F<>]+/g, ' ') // remove unallowed characters
|
|
1213
1217
|
.trim();
|
|
1214
1218
|
|
|
1215
|
-
|
|
1219
|
+
const lastAt = address.lastIndexOf('@');
|
|
1216
1220
|
if (lastAt < 0) {
|
|
1217
1221
|
// Bare username
|
|
1218
1222
|
return address;
|
|
1219
1223
|
}
|
|
1220
1224
|
|
|
1221
1225
|
let user = address.substr(0, lastAt);
|
|
1222
|
-
|
|
1226
|
+
const domain = address.substr(lastAt + 1);
|
|
1223
1227
|
|
|
1224
1228
|
// Usernames are not touched and are kept as is even if these include unicode
|
|
1225
1229
|
// Domains are punycoded by default
|
|
@@ -1285,20 +1289,25 @@ class MimeNode {
|
|
|
1285
1289
|
_getTextEncoding(value) {
|
|
1286
1290
|
value = (value || '').toString();
|
|
1287
1291
|
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1292
|
+
if (this.textEncoding) {
|
|
1293
|
+
return this.textEncoding;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
// count latin alphabet symbols and 8-bit range symbols + control symbols
|
|
1297
|
+
// if there are more latin characters, then use quoted-printable
|
|
1298
|
+
// encoding, otherwise use base64
|
|
1299
|
+
let nonLatinLen = 0;
|
|
1300
|
+
let latinLen = 0;
|
|
1301
|
+
for (let i = 0, len = value.length; i < len; i++) {
|
|
1302
|
+
const code = value.charCodeAt(i);
|
|
1303
|
+
if ((code >= 0x00 && code <= 0x08) || code === 0x0b || code === 0x0c || (code >= 0x0e && code <= 0x1f) || code >= 0x80) {
|
|
1304
|
+
nonLatinLen++;
|
|
1305
|
+
} else if ((code >= 0x41 && code <= 0x5a) || (code >= 0x61 && code <= 0x7a)) {
|
|
1306
|
+
latinLen++;
|
|
1307
|
+
}
|
|
1300
1308
|
}
|
|
1301
|
-
|
|
1309
|
+
// if there are more latin symbols than binary/unicode, then prefer Q, otherwise B
|
|
1310
|
+
return nonLatinLen < latinLen ? 'Q' : 'B';
|
|
1302
1311
|
}
|
|
1303
1312
|
|
|
1304
1313
|
/**
|
package/lib/mime-node/le-unix.js
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const Transform = stream.Transform;
|
|
3
|
+
const { Transform } = require('stream');
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
* Ensures that only <LF> is used for linebreaks
|
|
8
7
|
*
|
|
9
8
|
* @param {Object} options Stream options
|
|
10
9
|
*/
|
|
11
|
-
class
|
|
10
|
+
class LeUnix extends Transform {
|
|
12
11
|
constructor(options) {
|
|
13
12
|
super(options);
|
|
14
|
-
// init Transform
|
|
15
|
-
this.options = options || {};
|
|
16
13
|
}
|
|
17
14
|
|
|
18
15
|
/**
|
|
@@ -24,7 +21,7 @@ class LeWindows extends Transform {
|
|
|
24
21
|
|
|
25
22
|
for (let i = 0, len = chunk.length; i < len; i++) {
|
|
26
23
|
if (chunk[i] === 0x0d) {
|
|
27
|
-
// \
|
|
24
|
+
// \r
|
|
28
25
|
buf = chunk.slice(lastPos, i);
|
|
29
26
|
lastPos = i + 1;
|
|
30
27
|
this.push(buf);
|
|
@@ -40,4 +37,4 @@ class LeWindows extends Transform {
|
|
|
40
37
|
}
|
|
41
38
|
}
|
|
42
39
|
|
|
43
|
-
module.exports =
|
|
40
|
+
module.exports = LeUnix;
|
|
@@ -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
|
* Ensures that only <CR><LF> sequences are used for linebreaks
|
|
@@ -11,8 +10,6 @@ const Transform = stream.Transform;
|
|
|
11
10
|
class LeWindows extends Transform {
|
|
12
11
|
constructor(options) {
|
|
13
12
|
super(options);
|
|
14
|
-
// init Transform
|
|
15
|
-
this.options = options || {};
|
|
16
13
|
this.lastByte = false;
|
|
17
14
|
}
|
|
18
15
|
|
package/lib/nodemailer.js
CHANGED
|
@@ -20,9 +20,7 @@ const ETHEREAL_CACHE = ['true', 'yes', 'y', '1'].includes((process.env.ETHEREAL_
|
|
|
20
20
|
let testAccount = false;
|
|
21
21
|
|
|
22
22
|
module.exports.createTransport = function (transporter, defaults) {
|
|
23
|
-
let urlConfig;
|
|
24
23
|
let options;
|
|
25
|
-
let mailer;
|
|
26
24
|
|
|
27
25
|
if (
|
|
28
26
|
// provided transporter is a configuration object, not transporter plugin
|
|
@@ -30,7 +28,8 @@ module.exports.createTransport = function (transporter, defaults) {
|
|
|
30
28
|
// provided transporter looks like a connection url
|
|
31
29
|
(typeof transporter === 'string' && /^(smtps?|direct):/i.test(transporter))
|
|
32
30
|
) {
|
|
33
|
-
|
|
31
|
+
const urlConfig = typeof transporter === 'string' ? transporter : transporter.url;
|
|
32
|
+
if (urlConfig) {
|
|
34
33
|
// parse a configuration URL into configuration options
|
|
35
34
|
options = shared.parseConnectionUrl(urlConfig);
|
|
36
35
|
} else {
|
|
@@ -47,7 +46,7 @@ module.exports.createTransport = function (transporter, defaults) {
|
|
|
47
46
|
transporter = new JSONTransport(options);
|
|
48
47
|
} else if (options.SES) {
|
|
49
48
|
if (options.SES.ses && options.SES.aws) {
|
|
50
|
-
|
|
49
|
+
const error = new Error(
|
|
51
50
|
'Using legacy SES configuration, expecting @aws-sdk/client-sesv2, see https://nodemailer.com/transports/ses/'
|
|
52
51
|
);
|
|
53
52
|
error.code = errors.ECONFIG;
|
|
@@ -59,9 +58,7 @@ module.exports.createTransport = function (transporter, defaults) {
|
|
|
59
58
|
}
|
|
60
59
|
}
|
|
61
60
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return mailer;
|
|
61
|
+
return new Mailer(transporter, options, defaults);
|
|
65
62
|
};
|
|
66
63
|
|
|
67
64
|
module.exports.createTestAccount = function (apiUrl, callback) {
|
|
@@ -85,11 +82,11 @@ module.exports.createTestAccount = function (apiUrl, callback) {
|
|
|
85
82
|
|
|
86
83
|
apiUrl = apiUrl || ETHEREAL_API;
|
|
87
84
|
|
|
88
|
-
|
|
85
|
+
const chunks = [];
|
|
89
86
|
let chunklen = 0;
|
|
90
87
|
|
|
91
|
-
|
|
92
|
-
|
|
88
|
+
const requestHeaders = {};
|
|
89
|
+
const requestBody = {
|
|
93
90
|
requestor: packageData.name,
|
|
94
91
|
version: packageData.version
|
|
95
92
|
};
|
|
@@ -98,7 +95,7 @@ module.exports.createTestAccount = function (apiUrl, callback) {
|
|
|
98
95
|
requestHeaders.Authorization = 'Bearer ' + ETHEREAL_API_KEY;
|
|
99
96
|
}
|
|
100
97
|
|
|
101
|
-
|
|
98
|
+
const req = nmfetch(apiUrl + '/user', {
|
|
102
99
|
contentType: 'application/json',
|
|
103
100
|
method: 'POST',
|
|
104
101
|
headers: requestHeaders,
|
|
@@ -116,16 +113,12 @@ module.exports.createTestAccount = function (apiUrl, callback) {
|
|
|
116
113
|
req.once('error', err => callback(err));
|
|
117
114
|
|
|
118
115
|
req.once('end', () => {
|
|
119
|
-
|
|
116
|
+
const res = Buffer.concat(chunks, chunklen);
|
|
120
117
|
let data;
|
|
121
|
-
let err;
|
|
122
118
|
try {
|
|
123
119
|
data = JSON.parse(res.toString());
|
|
124
120
|
} catch (E) {
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
if (err) {
|
|
128
|
-
return callback(err);
|
|
121
|
+
return callback(E);
|
|
129
122
|
}
|
|
130
123
|
if (data.status !== 'success' || data.error) {
|
|
131
124
|
return callback(new Error(data.error || 'Request failed'));
|
|
@@ -143,7 +136,7 @@ module.exports.getTestMessageUrl = function (info) {
|
|
|
143
136
|
return false;
|
|
144
137
|
}
|
|
145
138
|
|
|
146
|
-
|
|
139
|
+
const infoProps = new Map();
|
|
147
140
|
info.response.replace(/\[([^\]]+)\]$/, (m, props) => {
|
|
148
141
|
props.replace(/\b([A-Z0-9]+)=([^\s]+)/g, (m, key, value) => {
|
|
149
142
|
infoProps.set(key, value);
|
package/lib/qp/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const Transform = require('stream')
|
|
3
|
+
const { Transform } = require('stream');
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Encodes a Buffer into a Quoted-Printable encoded string
|
|
@@ -8,20 +8,21 @@ const Transform = require('stream').Transform;
|
|
|
8
8
|
* @param {Buffer} buffer Buffer to convert
|
|
9
9
|
* @returns {String} Quoted-Printable encoded string
|
|
10
10
|
*/
|
|
11
|
+
// usable characters that do not need encoding
|
|
12
|
+
// https://tools.ietf.org/html/rfc2045#section-6.7
|
|
13
|
+
const QP_RANGES = [
|
|
14
|
+
[0x09], // <TAB>
|
|
15
|
+
[0x0a], // <LF>
|
|
16
|
+
[0x0d], // <CR>
|
|
17
|
+
[0x20, 0x3c], // <SP>!"#$%&'()*+,-./0123456789:;
|
|
18
|
+
[0x3e, 0x7e] // >?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}
|
|
19
|
+
];
|
|
20
|
+
|
|
11
21
|
function encode(buffer) {
|
|
12
22
|
if (typeof buffer === 'string') {
|
|
13
23
|
buffer = Buffer.from(buffer, 'utf-8');
|
|
14
24
|
}
|
|
15
25
|
|
|
16
|
-
// usable characters that do not need encoding
|
|
17
|
-
let ranges = [
|
|
18
|
-
// https://tools.ietf.org/html/rfc2045#section-6.7
|
|
19
|
-
[0x09], // <TAB>
|
|
20
|
-
[0x0a], // <LF>
|
|
21
|
-
[0x0d], // <CR>
|
|
22
|
-
[0x20, 0x3c], // <SP>!"#$%&'()*+,-./0123456789:;
|
|
23
|
-
[0x3e, 0x7e] // >?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}
|
|
24
|
-
];
|
|
25
26
|
let result = '';
|
|
26
27
|
let ord;
|
|
27
28
|
|
|
@@ -29,7 +30,7 @@ function encode(buffer) {
|
|
|
29
30
|
ord = buffer[i];
|
|
30
31
|
// if the char is in allowed range, then keep as is, unless it is a WS in the end of a line
|
|
31
32
|
if (
|
|
32
|
-
checkRanges(ord,
|
|
33
|
+
checkRanges(ord, QP_RANGES) &&
|
|
33
34
|
!((ord === 0x20 || ord === 0x09) && (i === len - 1 || buffer[i + 1] === 0x0a || buffer[i + 1] === 0x0d))
|
|
34
35
|
) {
|
|
35
36
|
result += String.fromCharCode(ord);
|
|
@@ -57,9 +58,9 @@ function wrap(str, lineLength) {
|
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
let pos = 0;
|
|
60
|
-
|
|
61
|
+
const len = str.length;
|
|
61
62
|
let match, code, line;
|
|
62
|
-
|
|
63
|
+
const lineMargin = Math.floor(lineLength / 3);
|
|
63
64
|
let result = '';
|
|
64
65
|
|
|
65
66
|
// insert soft linebreaks where needed
|
|
@@ -73,17 +74,20 @@ function wrap(str, lineLength) {
|
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
if (line.substr(-1) === '\n') {
|
|
76
|
-
// nothing to change here
|
|
77
77
|
result += line;
|
|
78
78
|
pos += line.length;
|
|
79
79
|
continue;
|
|
80
|
-
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if ((match = line.substr(-lineMargin).match(/\n.*?$/))) {
|
|
81
83
|
// truncate to nearest line break
|
|
82
84
|
line = line.substr(0, line.length - (match[0].length - 1));
|
|
83
85
|
result += line;
|
|
84
86
|
pos += line.length;
|
|
85
87
|
continue;
|
|
86
|
-
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (line.length > lineLength - lineMargin && (match = line.substr(-lineMargin).match(/[ \t.,!?][^ \t.,!?]*$/))) {
|
|
87
91
|
// truncate to nearest space
|
|
88
92
|
line = line.substr(0, line.length - (match[0].length - 1));
|
|
89
93
|
} else if (line.match(/[=][\da-f]{0,2}$/i)) {
|
|
@@ -139,13 +143,14 @@ function wrap(str, lineLength) {
|
|
|
139
143
|
*/
|
|
140
144
|
function checkRanges(nr, ranges) {
|
|
141
145
|
for (let i = ranges.length - 1; i >= 0; i--) {
|
|
142
|
-
|
|
146
|
+
const range = ranges[i];
|
|
147
|
+
if (!range.length) {
|
|
143
148
|
continue;
|
|
144
149
|
}
|
|
145
|
-
if (
|
|
150
|
+
if (range.length === 1 && nr === range[0]) {
|
|
146
151
|
return true;
|
|
147
152
|
}
|
|
148
|
-
if (
|
|
153
|
+
if (range.length === 2 && nr >= range[0] && nr <= range[1]) {
|
|
149
154
|
return true;
|
|
150
155
|
}
|
|
151
156
|
}
|
|
@@ -163,7 +168,6 @@ class Encoder extends Transform {
|
|
|
163
168
|
constructor(options) {
|
|
164
169
|
super();
|
|
165
170
|
|
|
166
|
-
// init Transform
|
|
167
171
|
this.options = options || {};
|
|
168
172
|
|
|
169
173
|
if (this.options.lineLength !== false) {
|
|
@@ -219,7 +223,6 @@ class Encoder extends Transform {
|
|
|
219
223
|
}
|
|
220
224
|
}
|
|
221
225
|
|
|
222
|
-
// expose to the world
|
|
223
226
|
module.exports = {
|
|
224
227
|
encode,
|
|
225
228
|
wrap,
|