mirror of
https://github.com/dawidd6/action-send-mail.git
synced 2026-09-09 05:56:37 +07:00
node_modules: update (#314)
Co-authored-by: dawidd6 <9713907+dawidd6@users.noreply.github.com>
This commit is contained in:
+27
@@ -15,6 +15,13 @@ const net = require('net');
|
||||
const dns = require('dns');
|
||||
const crypto = require('crypto');
|
||||
|
||||
/**
|
||||
* Recipients allowed on one message unless the caller sets its own maxRecipients. A backstop
|
||||
* against a runaway or hostile recipient list rather than a delivery policy: RFC 5321 only
|
||||
* asks a server to accept 100, so a real send is bounded far below this.
|
||||
*/
|
||||
const DEFAULT_MAX_RECIPIENTS = 100000;
|
||||
|
||||
/**
|
||||
* Creates an object for exposing the Mail API
|
||||
*
|
||||
@@ -191,6 +198,26 @@ class Mail extends EventEmitter {
|
||||
mail.setPriorityHeaders();
|
||||
mail.setListHeaders();
|
||||
|
||||
const maxRecipients = mail.data.maxRecipients === undefined ? DEFAULT_MAX_RECIPIENTS : mail.data.maxRecipients;
|
||||
const recipientCount = mail.message.getEnvelope().to.length;
|
||||
|
||||
if (maxRecipients && recipientCount > maxRecipients) {
|
||||
const err = new Error(
|
||||
`Message has ${recipientCount} recipients, which is over the ${maxRecipients} allowed by maxRecipients`
|
||||
);
|
||||
err.code = errors.EMAXRECIPIENTS;
|
||||
this.logger.error(
|
||||
{
|
||||
err,
|
||||
tnx: 'transport',
|
||||
action: 'send'
|
||||
},
|
||||
'Send Error: %s',
|
||||
err.message
|
||||
);
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
this._processPlugins('stream', mail, err => {
|
||||
if (err) {
|
||||
this.logger.error(
|
||||
|
||||
+68
-37
@@ -4,6 +4,11 @@ const shared = require('../shared');
|
||||
const MimeNode = require('../mime-node');
|
||||
const mimeFuncs = require('../mime-funcs');
|
||||
|
||||
// Only an own key counts as already set. `key in obj` also matches every member of
|
||||
// Object.prototype, which silently drops a transporter default legitimately named
|
||||
// toString or constructor.
|
||||
const hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
|
||||
|
||||
class MailMessage {
|
||||
constructor(mailer, data) {
|
||||
this.mailer = mailer;
|
||||
@@ -14,34 +19,53 @@ class MailMessage {
|
||||
const options = mailer.options || {};
|
||||
const defaults = mailer._defaults || {};
|
||||
|
||||
Object.assign(this.data, data);
|
||||
shared.copyOwnKeys(this.data, data);
|
||||
|
||||
this.data.headers = this.data.headers || {};
|
||||
|
||||
// apply defaults
|
||||
Object.keys(defaults).forEach(key => {
|
||||
if (!(key in this.data)) {
|
||||
this.data[key] = defaults[key];
|
||||
} else if (key === 'headers') {
|
||||
// headers is a special case. Allow setting individual default headers
|
||||
Object.keys(defaults.headers).forEach(key => {
|
||||
if (!(key in this.data.headers)) {
|
||||
this.data.headers[key] = defaults.headers[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
// Apply defaults. `_defaults` is caller supplied too, it is the second argument of
|
||||
// createTransport, so it needs the same treatment as `data` above
|
||||
shared.copyOwnKeys(this.data, defaults, key => hasOwn(this.data, key));
|
||||
|
||||
// headers is a special case. Allow setting individual default headers
|
||||
shared.copyOwnKeys(this.data.headers, defaults.headers, key => hasOwn(this.data.headers, key));
|
||||
|
||||
// force specific keys from transporter options
|
||||
['disableFileAccess', 'disableUrlAccess', 'normalizeHeaderKey'].forEach(key => {
|
||||
['disableFileAccess', 'disableUrlAccess', 'normalizeHeaderKey', 'maxRecipients'].forEach(key => {
|
||||
if (key in options) {
|
||||
this.data[key] = options[key];
|
||||
}
|
||||
});
|
||||
|
||||
// The access flags are a sandbox rather than a message field, so `defaults` counts as
|
||||
// transporter configuration for them. For a transporter plugin it is the only channel
|
||||
// there is, createTransport leaves `options` undefined for one, and the defaults copy
|
||||
// above yields to anything the message already set, which let message data switch the
|
||||
// sandbox back off. Closing is one way here, same as in resolveContent below: either
|
||||
// side may switch a flag on, neither can switch off what the other closed.
|
||||
['disableFileAccess', 'disableUrlAccess'].forEach(key => {
|
||||
if (!(key in options) && hasOwn(defaults, key)) {
|
||||
this.data[key] = this.data[key] || defaults[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
resolveContent(...args) {
|
||||
return shared.resolveContent(...args);
|
||||
resolveContent(data, key, options, callback) {
|
||||
// Most plugins call this with the legacy (data, key, callback) signature, which carries
|
||||
// no access policy. The policy belongs to the message, so apply it here. Explicit
|
||||
// options may only tighten it, never reopen what the transporter closed.
|
||||
if (!callback && typeof options === 'function') {
|
||||
callback = options;
|
||||
options = false;
|
||||
}
|
||||
options = options || {};
|
||||
|
||||
const policy = {
|
||||
disableFileAccess: this.data.disableFileAccess || options.disableFileAccess,
|
||||
disableUrlAccess: this.data.disableUrlAccess || options.disableUrlAccess
|
||||
};
|
||||
|
||||
return shared.resolveContent(data, key, policy, callback);
|
||||
}
|
||||
|
||||
resolveAll(callback) {
|
||||
@@ -123,11 +147,12 @@ class MailMessage {
|
||||
content: value
|
||||
};
|
||||
if (args[0][args[1]] && typeof args[0][args[1]] === 'object' && !Buffer.isBuffer(args[0][args[1]])) {
|
||||
Object.keys(args[0][args[1]]).forEach(key => {
|
||||
if (!(key in node) && !['content', 'path', 'href', 'raw'].includes(key)) {
|
||||
node[key] = args[0][args[1]][key];
|
||||
}
|
||||
});
|
||||
// The keys are the caller's, so copying them takes the same "__proto__"
|
||||
// rule as the constructor. `key in node` stays as the already-set test
|
||||
// here, unlike for the defaults: it also skips the Object.prototype
|
||||
// member names, and letting message data land a `toString` string on a
|
||||
// node only buys a TypeError the first time something stringifies it.
|
||||
shared.copyOwnKeys(node, args[0][args[1]], key => key in node || ['content', 'path', 'href', 'raw'].includes(key));
|
||||
}
|
||||
|
||||
args[0][args[1]] = node;
|
||||
@@ -140,7 +165,7 @@ class MailMessage {
|
||||
}
|
||||
|
||||
normalize(callback) {
|
||||
const envelope = this.data.envelope || this.message.getEnvelope();
|
||||
const envelope = this.message.getEnvelope();
|
||||
const messageId = this.message.messageId();
|
||||
|
||||
this.resolveAll((err, data) => {
|
||||
@@ -186,6 +211,9 @@ class MailMessage {
|
||||
|
||||
data.normalizedHeaders = {};
|
||||
Object.keys(data.headers || {}).forEach(key => {
|
||||
if (shared.isProtoKey(key)) {
|
||||
return;
|
||||
}
|
||||
let value = [].concat(data.headers[key] || []).shift();
|
||||
value = (value && value.value) || value;
|
||||
if (value) {
|
||||
@@ -271,15 +299,16 @@ class MailMessage {
|
||||
}
|
||||
|
||||
if (value && value.url) {
|
||||
// strip CR/LF so a comment can't inject extra header lines. DEL is neither
|
||||
// qtext nor ctext, so it can not be carried literally by either construct
|
||||
// and has to become an encoded word like any other non-plaintext value
|
||||
let comment = (value.comment || '').toString().replace(/\r?\n|\r/g, ' ');
|
||||
const needsEncoding = !mimeFuncs.isPlainText(comment) || /\x7f/.test(comment);
|
||||
|
||||
if (key.toLowerCase().trim() === 'id') {
|
||||
// List-ID: "comment" <domain>
|
||||
// strip CR/LF so a comment can't inject extra header lines
|
||||
let comment = (value.comment || '').toString().replace(/\r?\n|\r/g, ' ');
|
||||
if (mimeFuncs.isPlainText(comment)) {
|
||||
comment = '"' + comment + '"';
|
||||
} else {
|
||||
comment = mimeFuncs.encodeWord(comment);
|
||||
}
|
||||
// List-ID: "comment" <domain>, where an unescaped quote or a trailing
|
||||
// backslash in the comment would swallow the <domain> behind it
|
||||
comment = needsEncoding ? mimeFuncs.encodeWord(comment) : mimeFuncs.quoteString(comment);
|
||||
|
||||
// List-ID expects a bare domain-like identifier, so strip the
|
||||
// scheme prefix that _formatListUrl adds or passes through
|
||||
@@ -289,11 +318,11 @@ class MailMessage {
|
||||
}
|
||||
|
||||
// List-*: <http://domain> (comment)
|
||||
// strip CR/LF so a comment can't inject extra header lines
|
||||
let comment = (value.comment || '').toString().replace(/\r?\n|\r/g, ' ');
|
||||
if (!mimeFuncs.isPlainText(comment)) {
|
||||
comment = mimeFuncs.encodeWord(comment);
|
||||
}
|
||||
// the ctext specials go out as quoted-pairs, otherwise a ")" closes the
|
||||
// comment early and leaves the rest as junk, an unpaired "(" opens a
|
||||
// nested comment that never closes, and a trailing backslash escapes
|
||||
// the closing ")" so the comment swallows whatever follows it
|
||||
comment = needsEncoding ? mimeFuncs.encodeWord(comment) : comment.replace(/[()\\]/g, '\\$&');
|
||||
|
||||
return this._formatListUrl(value.url) + (value.comment ? ' (' + comment + ')' : '');
|
||||
}
|
||||
@@ -307,7 +336,9 @@ class MailMessage {
|
||||
}
|
||||
|
||||
_formatListUrl(url) {
|
||||
url = url.replace(/[\s<]+|[\s>]+/g, '');
|
||||
// a url has no way to carry a control char or DEL, and the angle brackets around it
|
||||
// are not a quoting construct, so anything left here lands in the header raw
|
||||
url = url.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '').replace(/[\s<]+|[\s>]+/g, '');
|
||||
if (/^(https?|mailto|ftp):/.test(url)) {
|
||||
return '<' + url + '>';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user