node_modules: update (#314)

Co-authored-by: dawidd6 <9713907+dawidd6@users.noreply.github.com>
This commit is contained in:
Dawid Dziurla
2026-09-07 07:05:39 +02:00
committed by GitHub
parent 40eba5cee7
commit d35571df19
23 changed files with 938 additions and 176 deletions
+27
View File
@@ -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
View File
@@ -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 + '>';
}