@nocobase/plugin-workflow-mailer 2.2.16 → 2.2.18
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/dist/externalVersion.js +6 -6
- package/dist/node_modules/joi/dist/joi-browser.min.js +1 -1
- package/dist/node_modules/joi/lib/index.js +1 -1
- package/dist/node_modules/joi/lib/messages.js +35 -6
- package/dist/node_modules/joi/lib/types/keys.js +18 -1
- package/dist/node_modules/joi/lib/types/link.js +18 -2
- package/dist/node_modules/joi/lib/types/string.js +2 -2
- package/dist/node_modules/joi/package.json +1 -1
- package/dist/node_modules/nodemailer/.ncurc.js +2 -0
- package/dist/node_modules/nodemailer/.prettierignore +8 -0
- package/dist/node_modules/nodemailer/.prettierrc +12 -0
- package/dist/node_modules/nodemailer/.prettierrc.js +2 -0
- package/dist/node_modules/nodemailer/.release-please-config.json +9 -0
- package/dist/node_modules/nodemailer/eslint.config.js +88 -0
- package/dist/node_modules/nodemailer/lib/addressparser/index.js +314 -71
- package/dist/node_modules/nodemailer/lib/base64/index.js +9 -13
- package/dist/node_modules/nodemailer/lib/dkim/index.js +17 -18
- package/dist/node_modules/nodemailer/lib/dkim/message-parser.js +12 -13
- package/dist/node_modules/nodemailer/lib/dkim/relaxed-body.js +3 -3
- package/dist/node_modules/nodemailer/lib/dkim/sign.js +22 -18
- package/dist/node_modules/nodemailer/lib/errors.js +59 -0
- package/dist/node_modules/nodemailer/lib/fetch/cookies.js +14 -19
- package/dist/node_modules/nodemailer/lib/fetch/index.js +164 -27
- package/dist/node_modules/nodemailer/lib/json-transport/index.js +4 -4
- package/dist/node_modules/nodemailer/lib/mail-composer/index.js +229 -155
- package/dist/node_modules/nodemailer/lib/mailer/index.js +106 -54
- package/dist/node_modules/nodemailer/lib/mailer/mail-message.js +106 -68
- package/dist/node_modules/nodemailer/lib/mime-funcs/index.js +105 -59
- package/dist/node_modules/nodemailer/lib/mime-funcs/mime-types.js +21 -14
- package/dist/node_modules/nodemailer/lib/mime-node/index.js +389 -137
- package/dist/node_modules/nodemailer/lib/mime-node/last-newline.js +1 -1
- package/dist/node_modules/nodemailer/lib/mime-node/le-unix.js +4 -7
- package/dist/node_modules/nodemailer/lib/mime-node/le-windows.js +1 -4
- package/dist/node_modules/nodemailer/lib/nodemailer.js +1 -1
- package/dist/node_modules/nodemailer/lib/punycode/index.js +1 -1
- package/dist/node_modules/nodemailer/lib/qp/index.js +33 -22
- package/dist/node_modules/nodemailer/lib/sendmail-transport/index.js +54 -44
- package/dist/node_modules/nodemailer/lib/ses-transport/index.js +110 -193
- package/dist/node_modules/nodemailer/lib/shared/index.js +299 -240
- package/dist/node_modules/nodemailer/lib/shared/objects.js +42 -0
- package/dist/node_modules/nodemailer/lib/shared/url.js +151 -0
- package/dist/node_modules/nodemailer/lib/smtp-connection/data-stream.js +3 -6
- package/dist/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js +50 -19
- package/dist/node_modules/nodemailer/lib/smtp-connection/index.js +415 -223
- package/dist/node_modules/nodemailer/lib/smtp-pool/index.js +27 -34
- package/dist/node_modules/nodemailer/lib/smtp-pool/pool-resource.js +19 -16
- package/dist/node_modules/nodemailer/lib/smtp-transport/index.js +66 -53
- package/dist/node_modules/nodemailer/lib/stream-transport/index.js +16 -7
- package/dist/node_modules/nodemailer/lib/well-known/index.js +7 -7
- package/dist/node_modules/nodemailer/lib/well-known/services.json +328 -38
- package/dist/node_modules/nodemailer/lib/xoauth2/index.js +105 -33
- package/dist/node_modules/nodemailer/package.json +1 -1
- package/package.json +4 -4
|
@@ -6,14 +6,22 @@ const mimeTypes = require('../mime-funcs/mime-types');
|
|
|
6
6
|
const MailComposer = require('../mail-composer');
|
|
7
7
|
const DKIM = require('../dkim');
|
|
8
8
|
const httpProxyClient = require('../smtp-connection/http-proxy-client');
|
|
9
|
+
const errors = require('../errors');
|
|
9
10
|
const util = require('util');
|
|
10
|
-
const urllib = require('url');
|
|
11
|
+
const urllib = require('../shared/url');
|
|
11
12
|
const packageData = require('../../package.json');
|
|
12
13
|
const MailMessage = require('./mail-message');
|
|
13
14
|
const net = require('net');
|
|
14
15
|
const dns = require('dns');
|
|
15
16
|
const crypto = require('crypto');
|
|
16
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Recipients allowed on one message unless the caller sets its own maxRecipients. A backstop
|
|
20
|
+
* against a runaway or hostile recipient list rather than a delivery policy: RFC 5321 only
|
|
21
|
+
* asks a server to accept 100, so a real send is bounded far below this.
|
|
22
|
+
*/
|
|
23
|
+
const DEFAULT_MAX_RECIPIENTS = 100000;
|
|
24
|
+
|
|
17
25
|
/**
|
|
18
26
|
* Creates an object for exposing the Mail API
|
|
19
27
|
*
|
|
@@ -87,6 +95,11 @@ class Mail extends EventEmitter {
|
|
|
87
95
|
this.transporter.on('idle', (...args) => {
|
|
88
96
|
this.emit('idle', ...args);
|
|
89
97
|
});
|
|
98
|
+
|
|
99
|
+
// indicates if the sender has became idle and all connections are terminated
|
|
100
|
+
this.transporter.on('clear', (...args) => {
|
|
101
|
+
this.emit('clear', ...args);
|
|
102
|
+
});
|
|
90
103
|
}
|
|
91
104
|
|
|
92
105
|
/**
|
|
@@ -100,17 +113,17 @@ class Mail extends EventEmitter {
|
|
|
100
113
|
this.getSocket = false;
|
|
101
114
|
}
|
|
102
115
|
return this.transporter[method](...args);
|
|
103
|
-
} else {
|
|
104
|
-
this.logger.warn(
|
|
105
|
-
{
|
|
106
|
-
tnx: 'transport',
|
|
107
|
-
methodName: method
|
|
108
|
-
},
|
|
109
|
-
'Non existing method %s called for transport',
|
|
110
|
-
method
|
|
111
|
-
);
|
|
112
|
-
return false;
|
|
113
116
|
}
|
|
117
|
+
|
|
118
|
+
this.logger.warn(
|
|
119
|
+
{
|
|
120
|
+
tnx: 'transport',
|
|
121
|
+
methodName: method
|
|
122
|
+
},
|
|
123
|
+
'Non existing method %s called for transport',
|
|
124
|
+
method
|
|
125
|
+
);
|
|
126
|
+
return false;
|
|
114
127
|
};
|
|
115
128
|
});
|
|
116
129
|
|
|
@@ -151,7 +164,7 @@ class Mail extends EventEmitter {
|
|
|
151
164
|
this.getSocket = false;
|
|
152
165
|
}
|
|
153
166
|
|
|
154
|
-
|
|
167
|
+
const mail = new MailMessage(this, data);
|
|
155
168
|
|
|
156
169
|
this.logger.debug(
|
|
157
170
|
{
|
|
@@ -185,6 +198,26 @@ class Mail extends EventEmitter {
|
|
|
185
198
|
mail.setPriorityHeaders();
|
|
186
199
|
mail.setListHeaders();
|
|
187
200
|
|
|
201
|
+
const maxRecipients = mail.data.maxRecipients === undefined ? DEFAULT_MAX_RECIPIENTS : mail.data.maxRecipients;
|
|
202
|
+
const recipientCount = mail.message.getEnvelope().to.length;
|
|
203
|
+
|
|
204
|
+
if (maxRecipients && recipientCount > maxRecipients) {
|
|
205
|
+
const err = new Error(
|
|
206
|
+
`Message has ${recipientCount} recipients, which is over the ${maxRecipients} allowed by maxRecipients`
|
|
207
|
+
);
|
|
208
|
+
err.code = errors.EMAXRECIPIENTS;
|
|
209
|
+
this.logger.error(
|
|
210
|
+
{
|
|
211
|
+
err,
|
|
212
|
+
tnx: 'transport',
|
|
213
|
+
action: 'send'
|
|
214
|
+
},
|
|
215
|
+
'Send Error: %s',
|
|
216
|
+
err.message
|
|
217
|
+
);
|
|
218
|
+
return callback(err);
|
|
219
|
+
}
|
|
220
|
+
|
|
188
221
|
this._processPlugins('stream', mail, err => {
|
|
189
222
|
if (err) {
|
|
190
223
|
this.logger.error(
|
|
@@ -201,7 +234,7 @@ class Mail extends EventEmitter {
|
|
|
201
234
|
|
|
202
235
|
if (mail.data.dkim || this.dkim) {
|
|
203
236
|
mail.message.processFunc(input => {
|
|
204
|
-
|
|
237
|
+
const dkim = mail.data.dkim ? new DKIM(mail.data.dkim) : this.dkim;
|
|
205
238
|
this.logger.debug(
|
|
206
239
|
{
|
|
207
240
|
tnx: 'DKIM',
|
|
@@ -236,7 +269,14 @@ class Mail extends EventEmitter {
|
|
|
236
269
|
}
|
|
237
270
|
|
|
238
271
|
getVersionString() {
|
|
239
|
-
return util.format(
|
|
272
|
+
return util.format(
|
|
273
|
+
'%s (%s; +%s; %s/%s)',
|
|
274
|
+
packageData.name,
|
|
275
|
+
packageData.version,
|
|
276
|
+
packageData.homepage,
|
|
277
|
+
this.transporter.name,
|
|
278
|
+
this.transporter.version
|
|
279
|
+
);
|
|
240
280
|
}
|
|
241
281
|
|
|
242
282
|
_processPlugins(step, mail, callback) {
|
|
@@ -246,8 +286,8 @@ class Mail extends EventEmitter {
|
|
|
246
286
|
return callback();
|
|
247
287
|
}
|
|
248
288
|
|
|
249
|
-
|
|
250
|
-
|
|
289
|
+
const userPlugins = this._userPlugins[step] || [];
|
|
290
|
+
const defaultPlugins = this._defaultPlugins[step] || [];
|
|
251
291
|
|
|
252
292
|
if (userPlugins.length) {
|
|
253
293
|
this.logger.debug(
|
|
@@ -268,7 +308,7 @@ class Mail extends EventEmitter {
|
|
|
268
308
|
|
|
269
309
|
let pos = 0;
|
|
270
310
|
let block = 'default';
|
|
271
|
-
|
|
311
|
+
const processPlugins = () => {
|
|
272
312
|
let curplugins = block === 'default' ? defaultPlugins : userPlugins;
|
|
273
313
|
if (pos >= curplugins.length) {
|
|
274
314
|
if (block === 'default' && userPlugins.length) {
|
|
@@ -279,7 +319,7 @@ class Mail extends EventEmitter {
|
|
|
279
319
|
return callback();
|
|
280
320
|
}
|
|
281
321
|
}
|
|
282
|
-
|
|
322
|
+
const plugin = curplugins[pos++];
|
|
283
323
|
plugin(mail, err => {
|
|
284
324
|
if (err) {
|
|
285
325
|
return callback(err);
|
|
@@ -297,11 +337,11 @@ class Mail extends EventEmitter {
|
|
|
297
337
|
* @param {String} proxyUrl Proxy configuration url
|
|
298
338
|
*/
|
|
299
339
|
setupProxy(proxyUrl) {
|
|
300
|
-
|
|
340
|
+
const proxy = urllib.parse(proxyUrl);
|
|
301
341
|
|
|
302
342
|
// setup socket handler for the mailer object
|
|
303
343
|
this.getSocket = (options, callback) => {
|
|
304
|
-
|
|
344
|
+
const protocol = proxy.protocol.replace(/:$/, '').toLowerCase();
|
|
305
345
|
|
|
306
346
|
if (this.meta.has('proxy_handler_' + protocol)) {
|
|
307
347
|
return this.meta.get('proxy_handler_' + protocol)(proxy, options, callback);
|
|
@@ -311,7 +351,7 @@ class Mail extends EventEmitter {
|
|
|
311
351
|
// Connect using a HTTP CONNECT method
|
|
312
352
|
case 'http':
|
|
313
353
|
case 'https':
|
|
314
|
-
httpProxyClient(proxy.href, options.port, options.host, (err, socket) => {
|
|
354
|
+
httpProxyClient(proxy.href, options.port, options.host, this.options.tls || {}, (err, socket) => {
|
|
315
355
|
if (err) {
|
|
316
356
|
return callback(err);
|
|
317
357
|
}
|
|
@@ -325,13 +365,15 @@ class Mail extends EventEmitter {
|
|
|
325
365
|
case 'socks4':
|
|
326
366
|
case 'socks4a': {
|
|
327
367
|
if (!this.meta.has('proxy_socks_module')) {
|
|
328
|
-
|
|
368
|
+
let err = new Error('Socks module not loaded');
|
|
369
|
+
err.code = errors.EPROXY;
|
|
370
|
+
return callback(err);
|
|
329
371
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
372
|
+
const connect = ipaddress => {
|
|
373
|
+
const proxyV2 = !!this.meta.get('proxy_socks_module').SocksClient;
|
|
374
|
+
const socksClient = proxyV2 ? this.meta.get('proxy_socks_module').SocksClient : this.meta.get('proxy_socks_module');
|
|
375
|
+
const proxyType = Number(proxy.protocol.replace(/\D/g, '')) || 5;
|
|
376
|
+
const connectionOpts = {
|
|
335
377
|
proxy: {
|
|
336
378
|
ipaddress,
|
|
337
379
|
port: Number(proxy.port),
|
|
@@ -345,8 +387,8 @@ class Mail extends EventEmitter {
|
|
|
345
387
|
};
|
|
346
388
|
|
|
347
389
|
if (proxy.auth) {
|
|
348
|
-
|
|
349
|
-
|
|
390
|
+
const username = decodeURIComponent(proxy.auth.split(':').shift());
|
|
391
|
+
const password = decodeURIComponent(proxy.auth.split(':').pop());
|
|
350
392
|
if (proxyV2) {
|
|
351
393
|
connectionOpts.proxy.userId = username;
|
|
352
394
|
connectionOpts.proxy.password = password;
|
|
@@ -382,7 +424,9 @@ class Mail extends EventEmitter {
|
|
|
382
424
|
});
|
|
383
425
|
}
|
|
384
426
|
}
|
|
385
|
-
|
|
427
|
+
let err = new Error('Unknown proxy configuration');
|
|
428
|
+
err.code = errors.EPROXY;
|
|
429
|
+
callback(err);
|
|
386
430
|
};
|
|
387
431
|
}
|
|
388
432
|
|
|
@@ -390,31 +434,39 @@ class Mail extends EventEmitter {
|
|
|
390
434
|
if ((!this.options.attachDataUrls && !mail.data.attachDataUrls) || !mail.data.html) {
|
|
391
435
|
return callback();
|
|
392
436
|
}
|
|
393
|
-
mail.resolveContent(
|
|
394
|
-
|
|
395
|
-
|
|
437
|
+
mail.resolveContent(
|
|
438
|
+
mail.data,
|
|
439
|
+
'html',
|
|
440
|
+
{ disableFileAccess: mail.data.disableFileAccess, disableUrlAccess: mail.data.disableUrlAccess },
|
|
441
|
+
(err, html) => {
|
|
442
|
+
if (err) {
|
|
443
|
+
return callback(err);
|
|
444
|
+
}
|
|
445
|
+
let cidCounter = 0;
|
|
446
|
+
html = (html || '')
|
|
447
|
+
.toString()
|
|
448
|
+
.replace(
|
|
449
|
+
/(<img\b[^<>]{0,1024} src\s{0,20}=[\s"']{0,20})(data:([^;]+);[^"'>\s]+)/gi,
|
|
450
|
+
(match, prefix, dataUri, mimeType) => {
|
|
451
|
+
const cid = crypto.randomBytes(10).toString('hex') + '@localhost';
|
|
452
|
+
if (!mail.data.attachments) {
|
|
453
|
+
mail.data.attachments = [];
|
|
454
|
+
}
|
|
455
|
+
if (!Array.isArray(mail.data.attachments)) {
|
|
456
|
+
mail.data.attachments = [].concat(mail.data.attachments || []);
|
|
457
|
+
}
|
|
458
|
+
mail.data.attachments.push({
|
|
459
|
+
path: dataUri,
|
|
460
|
+
cid,
|
|
461
|
+
filename: 'image-' + ++cidCounter + '.' + mimeTypes.detectExtension(mimeType)
|
|
462
|
+
});
|
|
463
|
+
return prefix + 'cid:' + cid;
|
|
464
|
+
}
|
|
465
|
+
);
|
|
466
|
+
mail.data.html = html;
|
|
467
|
+
callback();
|
|
396
468
|
}
|
|
397
|
-
|
|
398
|
-
html = (html || '')
|
|
399
|
-
.toString()
|
|
400
|
-
.replace(/(<img\b[^<>]{0,1024} src\s{0,20}=[\s"']{0,20})(data:([^;]+);[^"'>\s]+)/gi, (match, prefix, dataUri, mimeType) => {
|
|
401
|
-
let cid = crypto.randomBytes(10).toString('hex') + '@localhost';
|
|
402
|
-
if (!mail.data.attachments) {
|
|
403
|
-
mail.data.attachments = [];
|
|
404
|
-
}
|
|
405
|
-
if (!Array.isArray(mail.data.attachments)) {
|
|
406
|
-
mail.data.attachments = [].concat(mail.data.attachments || []);
|
|
407
|
-
}
|
|
408
|
-
mail.data.attachments.push({
|
|
409
|
-
path: dataUri,
|
|
410
|
-
cid,
|
|
411
|
-
filename: 'image-' + ++cidCounter + '.' + mimeTypes.detectExtension(mimeType)
|
|
412
|
-
});
|
|
413
|
-
return prefix + 'cid:' + cid;
|
|
414
|
-
});
|
|
415
|
-
mail.data.html = html;
|
|
416
|
-
callback();
|
|
417
|
-
});
|
|
469
|
+
);
|
|
418
470
|
}
|
|
419
471
|
|
|
420
472
|
set(key, value) {
|
|
@@ -4,6 +4,11 @@ const shared = require('../shared');
|
|
|
4
4
|
const MimeNode = require('../mime-node');
|
|
5
5
|
const mimeFuncs = require('../mime-funcs');
|
|
6
6
|
|
|
7
|
+
// Only an own key counts as already set. `key in obj` also matches every member of
|
|
8
|
+
// Object.prototype, which silently drops a transporter default legitimately named
|
|
9
|
+
// toString or constructor.
|
|
10
|
+
const hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
|
|
11
|
+
|
|
7
12
|
class MailMessage {
|
|
8
13
|
constructor(mailer, data) {
|
|
9
14
|
this.mailer = mailer;
|
|
@@ -11,43 +16,60 @@ class MailMessage {
|
|
|
11
16
|
this.message = null;
|
|
12
17
|
|
|
13
18
|
data = data || {};
|
|
14
|
-
|
|
15
|
-
|
|
19
|
+
const options = mailer.options || {};
|
|
20
|
+
const defaults = mailer._defaults || {};
|
|
16
21
|
|
|
17
|
-
|
|
18
|
-
this.data[key] = data[key];
|
|
19
|
-
});
|
|
22
|
+
shared.copyOwnKeys(this.data, data);
|
|
20
23
|
|
|
21
24
|
this.data.headers = this.data.headers || {};
|
|
22
25
|
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
Object.keys(defaults.headers).forEach(key => {
|
|
30
|
-
if (!(key in this.data.headers)) {
|
|
31
|
-
this.data.headers[key] = defaults.headers[key];
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
});
|
|
26
|
+
// Apply defaults. `_defaults` is caller supplied too, it is the second argument of
|
|
27
|
+
// createTransport, so it needs the same treatment as `data` above
|
|
28
|
+
shared.copyOwnKeys(this.data, defaults, key => hasOwn(this.data, key));
|
|
29
|
+
|
|
30
|
+
// headers is a special case. Allow setting individual default headers
|
|
31
|
+
shared.copyOwnKeys(this.data.headers, defaults.headers, key => hasOwn(this.data.headers, key));
|
|
36
32
|
|
|
37
33
|
// force specific keys from transporter options
|
|
38
|
-
['disableFileAccess', 'disableUrlAccess', 'normalizeHeaderKey'].forEach(key => {
|
|
34
|
+
['disableFileAccess', 'disableUrlAccess', 'normalizeHeaderKey', 'maxRecipients'].forEach(key => {
|
|
39
35
|
if (key in options) {
|
|
40
36
|
this.data[key] = options[key];
|
|
41
37
|
}
|
|
42
38
|
});
|
|
39
|
+
|
|
40
|
+
// The access flags are a sandbox rather than a message field, so `defaults` counts as
|
|
41
|
+
// transporter configuration for them. For a transporter plugin it is the only channel
|
|
42
|
+
// there is, createTransport leaves `options` undefined for one, and the defaults copy
|
|
43
|
+
// above yields to anything the message already set, which let message data switch the
|
|
44
|
+
// sandbox back off. Closing is one way here, same as in resolveContent below: either
|
|
45
|
+
// side may switch a flag on, neither can switch off what the other closed.
|
|
46
|
+
['disableFileAccess', 'disableUrlAccess'].forEach(key => {
|
|
47
|
+
if (!(key in options) && hasOwn(defaults, key)) {
|
|
48
|
+
this.data[key] = this.data[key] || defaults[key];
|
|
49
|
+
}
|
|
50
|
+
});
|
|
43
51
|
}
|
|
44
52
|
|
|
45
|
-
resolveContent(
|
|
46
|
-
|
|
53
|
+
resolveContent(data, key, options, callback) {
|
|
54
|
+
// Most plugins call this with the legacy (data, key, callback) signature, which carries
|
|
55
|
+
// no access policy. The policy belongs to the message, so apply it here. Explicit
|
|
56
|
+
// options may only tighten it, never reopen what the transporter closed.
|
|
57
|
+
if (!callback && typeof options === 'function') {
|
|
58
|
+
callback = options;
|
|
59
|
+
options = false;
|
|
60
|
+
}
|
|
61
|
+
options = options || {};
|
|
62
|
+
|
|
63
|
+
const policy = {
|
|
64
|
+
disableFileAccess: this.data.disableFileAccess || options.disableFileAccess,
|
|
65
|
+
disableUrlAccess: this.data.disableUrlAccess || options.disableUrlAccess
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
return shared.resolveContent(data, key, policy, callback);
|
|
47
69
|
}
|
|
48
70
|
|
|
49
71
|
resolveAll(callback) {
|
|
50
|
-
|
|
72
|
+
const keys = [
|
|
51
73
|
[this.data, 'html'],
|
|
52
74
|
[this.data, 'text'],
|
|
53
75
|
[this.data, 'watchHtml'],
|
|
@@ -64,7 +86,8 @@ class MailMessage {
|
|
|
64
86
|
if (this.data.attachments && this.data.attachments.length) {
|
|
65
87
|
this.data.attachments.forEach((attachment, i) => {
|
|
66
88
|
if (!attachment.filename) {
|
|
67
|
-
attachment.filename =
|
|
89
|
+
attachment.filename =
|
|
90
|
+
(attachment.path || attachment.href || '').split('/').pop().split('?').shift() || 'attachment-' + (i + 1);
|
|
68
91
|
if (attachment.filename.indexOf('.') < 0) {
|
|
69
92
|
attachment.filename += '.' + mimeFuncs.detectExtension(attachment.contentType);
|
|
70
93
|
}
|
|
@@ -78,9 +101,9 @@ class MailMessage {
|
|
|
78
101
|
});
|
|
79
102
|
}
|
|
80
103
|
|
|
81
|
-
|
|
104
|
+
const mimeNode = new MimeNode();
|
|
82
105
|
|
|
83
|
-
|
|
106
|
+
const addressKeys = ['from', 'to', 'cc', 'bcc', 'sender', 'replyTo'];
|
|
84
107
|
|
|
85
108
|
addressKeys.forEach(address => {
|
|
86
109
|
let value;
|
|
@@ -96,7 +119,7 @@ class MailMessage {
|
|
|
96
119
|
}
|
|
97
120
|
});
|
|
98
121
|
|
|
99
|
-
|
|
122
|
+
const singleKeys = ['from', 'sender'];
|
|
100
123
|
singleKeys.forEach(address => {
|
|
101
124
|
if (this.data[address]) {
|
|
102
125
|
this.data[address] = this.data[address].shift();
|
|
@@ -104,41 +127,46 @@ class MailMessage {
|
|
|
104
127
|
});
|
|
105
128
|
|
|
106
129
|
let pos = 0;
|
|
107
|
-
|
|
130
|
+
const resolveNext = () => {
|
|
108
131
|
if (pos >= keys.length) {
|
|
109
132
|
return callback(null, this.data);
|
|
110
133
|
}
|
|
111
|
-
|
|
134
|
+
const args = keys[pos++];
|
|
112
135
|
if (!args[0] || !args[0][args[1]]) {
|
|
113
136
|
return resolveNext();
|
|
114
137
|
}
|
|
115
|
-
shared.resolveContent(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
138
|
+
shared.resolveContent(
|
|
139
|
+
...args,
|
|
140
|
+
{ disableFileAccess: this.data.disableFileAccess, disableUrlAccess: this.data.disableUrlAccess },
|
|
141
|
+
(err, value) => {
|
|
142
|
+
if (err) {
|
|
143
|
+
return callback(err);
|
|
144
|
+
}
|
|
119
145
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
146
|
+
const node = {
|
|
147
|
+
content: value
|
|
148
|
+
};
|
|
149
|
+
if (args[0][args[1]] && typeof args[0][args[1]] === 'object' && !Buffer.isBuffer(args[0][args[1]])) {
|
|
150
|
+
// The keys are the caller's, so copying them takes the same "__proto__"
|
|
151
|
+
// rule as the constructor. `key in node` stays as the already-set test
|
|
152
|
+
// here, unlike for the defaults: it also skips the Object.prototype
|
|
153
|
+
// member names, and letting message data land a `toString` string on a
|
|
154
|
+
// node only buys a TypeError the first time something stringifies it.
|
|
155
|
+
shared.copyOwnKeys(node, args[0][args[1]], key => key in node || ['content', 'path', 'href', 'raw'].includes(key));
|
|
156
|
+
}
|
|
130
157
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
158
|
+
args[0][args[1]] = node;
|
|
159
|
+
resolveNext();
|
|
160
|
+
}
|
|
161
|
+
);
|
|
134
162
|
};
|
|
135
163
|
|
|
136
164
|
setImmediate(() => resolveNext());
|
|
137
165
|
}
|
|
138
166
|
|
|
139
167
|
normalize(callback) {
|
|
140
|
-
|
|
141
|
-
|
|
168
|
+
const envelope = this.message.getEnvelope();
|
|
169
|
+
const messageId = this.message.messageId();
|
|
142
170
|
|
|
143
171
|
this.resolveAll((err, data) => {
|
|
144
172
|
if (err) {
|
|
@@ -183,6 +211,9 @@ class MailMessage {
|
|
|
183
211
|
|
|
184
212
|
data.normalizedHeaders = {};
|
|
185
213
|
Object.keys(data.headers || {}).forEach(key => {
|
|
214
|
+
if (shared.isProtoKey(key)) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
186
217
|
let value = [].concat(data.headers[key] || []).shift();
|
|
187
218
|
value = (value && value.value) || value;
|
|
188
219
|
if (value) {
|
|
@@ -194,7 +225,7 @@ class MailMessage {
|
|
|
194
225
|
});
|
|
195
226
|
|
|
196
227
|
if (data.list && typeof data.list === 'object') {
|
|
197
|
-
|
|
228
|
+
const listHeaders = this._getListHeaders(data.list);
|
|
198
229
|
listHeaders.forEach(entry => {
|
|
199
230
|
data.normalizedHeaders[entry.key] = entry.value.map(val => (val && val.value) || val).join(', ');
|
|
200
231
|
});
|
|
@@ -244,13 +275,11 @@ class MailMessage {
|
|
|
244
275
|
return;
|
|
245
276
|
}
|
|
246
277
|
// add optional List-* headers
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
this.message.addHeader(listHeader.key, value);
|
|
251
|
-
});
|
|
278
|
+
this._getListHeaders(this.data.list).forEach(listHeader => {
|
|
279
|
+
listHeader.value.forEach(value => {
|
|
280
|
+
this.message.addHeader(listHeader.key, value);
|
|
252
281
|
});
|
|
253
|
-
}
|
|
282
|
+
});
|
|
254
283
|
}
|
|
255
284
|
|
|
256
285
|
_getListHeaders(listData) {
|
|
@@ -270,23 +299,30 @@ class MailMessage {
|
|
|
270
299
|
}
|
|
271
300
|
|
|
272
301
|
if (value && value.url) {
|
|
302
|
+
// strip CR/LF so a comment can't inject extra header lines. DEL is neither
|
|
303
|
+
// qtext nor ctext, so it can not be carried literally by either construct
|
|
304
|
+
// and has to become an encoded word like any other non-plaintext value
|
|
305
|
+
let comment = (value.comment || '').toString().replace(/\r?\n|\r/g, ' ');
|
|
306
|
+
const needsEncoding = !mimeFuncs.isPlainText(comment) || /\x7f/.test(comment);
|
|
307
|
+
|
|
273
308
|
if (key.toLowerCase().trim() === 'id') {
|
|
274
|
-
// List-ID: "comment" <domain
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
309
|
+
// List-ID: "comment" <domain>, where an unescaped quote or a trailing
|
|
310
|
+
// backslash in the comment would swallow the <domain> behind it
|
|
311
|
+
comment = needsEncoding ? mimeFuncs.encodeWord(comment) : mimeFuncs.quoteString(comment);
|
|
312
|
+
|
|
313
|
+
// List-ID expects a bare domain-like identifier, so strip the
|
|
314
|
+
// scheme prefix that _formatListUrl adds or passes through
|
|
315
|
+
return (
|
|
316
|
+
(value.comment ? comment + ' ' : '') + this._formatListUrl(value.url).replace(/^<[^:]+:\/{0,2}/, '<')
|
|
317
|
+
);
|
|
283
318
|
}
|
|
284
319
|
|
|
285
320
|
// List-*: <http://domain> (comment)
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
321
|
+
// the ctext specials go out as quoted-pairs, otherwise a ")" closes the
|
|
322
|
+
// comment early and leaves the rest as junk, an unpaired "(" opens a
|
|
323
|
+
// nested comment that never closes, and a trailing backslash escapes
|
|
324
|
+
// the closing ")" so the comment swallows whatever follows it
|
|
325
|
+
comment = needsEncoding ? mimeFuncs.encodeWord(comment) : comment.replace(/[()\\]/g, '\\$&');
|
|
290
326
|
|
|
291
327
|
return this._formatListUrl(value.url) + (value.comment ? ' (' + comment + ')' : '');
|
|
292
328
|
}
|
|
@@ -300,7 +336,9 @@ class MailMessage {
|
|
|
300
336
|
}
|
|
301
337
|
|
|
302
338
|
_formatListUrl(url) {
|
|
303
|
-
url
|
|
339
|
+
// a url has no way to carry a control char or DEL, and the angle brackets around it
|
|
340
|
+
// are not a quoting construct, so anything left here lands in the header raw
|
|
341
|
+
url = url.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '').replace(/[\s<]+|[\s>]+/g, '');
|
|
304
342
|
if (/^(https?|mailto|ftp):/.test(url)) {
|
|
305
343
|
return '<' + url + '>';
|
|
306
344
|
}
|