mirror of
https://github.com/dawidd6/action-send-mail.git
synced 2026-05-06 17:30:35 +07:00
node_modules: update (#290)
Co-authored-by: dawidd6 <9713907+dawidd6@users.noreply.github.com>
This commit is contained in:
151
node_modules/nodemailer/lib/addressparser/index.js
generated
vendored
151
node_modules/nodemailer/lib/addressparser/index.js
generated
vendored
@@ -10,23 +10,20 @@
|
||||
function _handleAddress(tokens, depth) {
|
||||
let isGroup = false;
|
||||
let state = 'text';
|
||||
let address;
|
||||
let addresses = [];
|
||||
let data = {
|
||||
const addresses = [];
|
||||
const data = {
|
||||
address: [],
|
||||
comment: [],
|
||||
group: [],
|
||||
text: [],
|
||||
textWasQuoted: [] // Track which text tokens came from inside quotes
|
||||
textWasQuoted: []
|
||||
};
|
||||
let i;
|
||||
let len;
|
||||
let insideQuotes = false; // Track if we're currently inside a quoted string
|
||||
let insideQuotes = false;
|
||||
|
||||
// Filter out <addresses>, (comments) and regular text
|
||||
for (i = 0, len = tokens.length; i < len; i++) {
|
||||
let token = tokens[i];
|
||||
let prevToken = i ? tokens[i - 1] : null;
|
||||
for (let i = 0, len = tokens.length; i < len; i++) {
|
||||
const token = tokens[i];
|
||||
const prevToken = i ? tokens[i - 1] : null;
|
||||
if (token.type === 'operator') {
|
||||
switch (token.value) {
|
||||
case '<':
|
||||
@@ -43,7 +40,6 @@ function _handleAddress(tokens, depth) {
|
||||
insideQuotes = false;
|
||||
break;
|
||||
case '"':
|
||||
// Track quote state for text tokens
|
||||
insideQuotes = !insideQuotes;
|
||||
state = 'text';
|
||||
break;
|
||||
@@ -54,14 +50,12 @@ function _handleAddress(tokens, depth) {
|
||||
}
|
||||
} else if (token.value) {
|
||||
if (state === 'address') {
|
||||
// handle use case where unquoted name includes a "<"
|
||||
// Apple Mail truncates everything between an unexpected < and an address
|
||||
// and so will we
|
||||
// Handle unquoted name that includes a "<".
|
||||
// Apple Mail truncates everything between an unexpected < and an address.
|
||||
token.value = token.value.replace(/^[^<]*<\s*/, '');
|
||||
}
|
||||
|
||||
if (prevToken && prevToken.noBreak && data[state].length) {
|
||||
// join values
|
||||
data[state][data[state].length - 1] += token.value;
|
||||
if (state === 'text' && insideQuotes) {
|
||||
data.textWasQuoted[data.textWasQuoted.length - 1] = true;
|
||||
@@ -88,11 +82,9 @@ function _handleAddress(tokens, depth) {
|
||||
// Parse group members, but flatten any nested groups (RFC 5322 doesn't allow nesting)
|
||||
let groupMembers = [];
|
||||
if (data.group.length) {
|
||||
let parsedGroup = addressparser(data.group.join(','), { _depth: depth + 1 });
|
||||
// Flatten: if any member is itself a group, extract its members into the sequence
|
||||
const parsedGroup = addressparser(data.group.join(','), { _depth: depth + 1 });
|
||||
parsedGroup.forEach(member => {
|
||||
if (member.group) {
|
||||
// Nested group detected - flatten it by adding its members directly
|
||||
groupMembers = groupMembers.concat(member.group);
|
||||
} else {
|
||||
groupMembers.push(member);
|
||||
@@ -101,40 +93,40 @@ function _handleAddress(tokens, depth) {
|
||||
}
|
||||
|
||||
addresses.push({
|
||||
name: data.text || (address && address.name),
|
||||
name: data.text || '',
|
||||
group: groupMembers
|
||||
});
|
||||
} else {
|
||||
// If no address was found, try to detect one from regular text
|
||||
if (!data.address.length && data.text.length) {
|
||||
for (i = data.text.length - 1; i >= 0; i--) {
|
||||
// Security fix: Do not extract email addresses from quoted strings
|
||||
// RFC 5321 allows @ inside quoted local-parts like "user@domain"@example.com
|
||||
// Extracting emails from quoted text leads to misrouting vulnerabilities
|
||||
if (!data.textWasQuoted[i] && data.text[i].match(/^[^@\s]+@[^@\s]+$/)) {
|
||||
for (let i = data.text.length - 1; i >= 0; i--) {
|
||||
// Security: Do not extract email addresses from quoted strings.
|
||||
// RFC 5321 allows @ inside quoted local-parts like "user@domain"@example.com.
|
||||
// Extracting emails from quoted text leads to misrouting vulnerabilities.
|
||||
if (!data.textWasQuoted[i] && /^[^@\s]+@[^@\s]+$/.test(data.text[i])) {
|
||||
data.address = data.text.splice(i, 1);
|
||||
data.textWasQuoted.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let _regexHandler = function (address) {
|
||||
if (!data.address.length) {
|
||||
data.address = [address.trim()];
|
||||
return ' ';
|
||||
} else {
|
||||
return address;
|
||||
}
|
||||
};
|
||||
|
||||
// still no address
|
||||
// Try a looser regex match if strict match found nothing
|
||||
if (!data.address.length) {
|
||||
for (i = data.text.length - 1; i >= 0; i--) {
|
||||
// Security fix: Do not extract email addresses from quoted strings
|
||||
let extracted = false;
|
||||
for (let i = data.text.length - 1; i >= 0; i--) {
|
||||
// Security: Do not extract email addresses from quoted strings
|
||||
if (!data.textWasQuoted[i]) {
|
||||
// fixed the regex to parse email address correctly when email address has more than one @
|
||||
data.text[i] = data.text[i].replace(/\s*\b[^@\s]+@[^\s]+\b\s*/, _regexHandler).trim();
|
||||
if (data.address.length) {
|
||||
data.text[i] = data.text[i]
|
||||
.replace(/\s*\b[^@\s]+@[^\s]+\b\s*/, match => {
|
||||
if (!extracted) {
|
||||
data.address = [match.trim()];
|
||||
extracted = true;
|
||||
return ' ';
|
||||
}
|
||||
return match;
|
||||
})
|
||||
.trim();
|
||||
if (extracted) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -142,13 +134,13 @@ function _handleAddress(tokens, depth) {
|
||||
}
|
||||
}
|
||||
|
||||
// If there's still is no text but a comment exixts, replace the two
|
||||
// If there's still no text but a comment exists, replace the two
|
||||
if (!data.text.length && data.comment.length) {
|
||||
data.text = data.comment;
|
||||
data.comment = [];
|
||||
}
|
||||
|
||||
// Keep only the first address occurence, push others to regular text
|
||||
// Keep only the first address occurrence, push others to regular text
|
||||
if (data.address.length > 1) {
|
||||
data.text = data.text.concat(data.address.splice(1));
|
||||
}
|
||||
@@ -157,24 +149,20 @@ function _handleAddress(tokens, depth) {
|
||||
data.text = data.text.join(' ');
|
||||
data.address = data.address.join(' ');
|
||||
|
||||
if (!data.address && isGroup) {
|
||||
return [];
|
||||
} else {
|
||||
address = {
|
||||
address: data.address || data.text || '',
|
||||
name: data.text || data.address || ''
|
||||
};
|
||||
const address = {
|
||||
address: data.address || data.text || '',
|
||||
name: data.text || data.address || ''
|
||||
};
|
||||
|
||||
if (address.address === address.name) {
|
||||
if ((address.address || '').match(/@/)) {
|
||||
address.name = '';
|
||||
} else {
|
||||
address.address = '';
|
||||
}
|
||||
if (address.address === address.name) {
|
||||
if (/@/.test(address.address || '')) {
|
||||
address.name = '';
|
||||
} else {
|
||||
address.address = '';
|
||||
}
|
||||
|
||||
addresses.push(address);
|
||||
}
|
||||
|
||||
addresses.push(address);
|
||||
}
|
||||
|
||||
return addresses;
|
||||
@@ -220,11 +208,11 @@ class Tokenizer {
|
||||
* @return {Array} An array of operator|text tokens
|
||||
*/
|
||||
tokenize() {
|
||||
let list = [];
|
||||
const list = [];
|
||||
|
||||
for (let i = 0, len = this.str.length; i < len; i++) {
|
||||
let chr = this.str.charAt(i);
|
||||
let nextChr = i < len - 1 ? this.str.charAt(i + 1) : null;
|
||||
const chr = this.str.charAt(i);
|
||||
const nextChr = i < len - 1 ? this.str.charAt(i + 1) : null;
|
||||
this.checkChar(chr, nextChr);
|
||||
}
|
||||
|
||||
@@ -325,17 +313,17 @@ const MAX_NESTED_GROUP_DEPTH = 50;
|
||||
*/
|
||||
function addressparser(str, options) {
|
||||
options = options || {};
|
||||
let depth = options._depth || 0;
|
||||
const depth = options._depth || 0;
|
||||
|
||||
// Prevent stack overflow from deeply nested groups (DoS protection)
|
||||
if (depth > MAX_NESTED_GROUP_DEPTH) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let tokenizer = new Tokenizer(str);
|
||||
let tokens = tokenizer.tokenize();
|
||||
const tokenizer = new Tokenizer(str);
|
||||
const tokens = tokenizer.tokenize();
|
||||
|
||||
let addresses = [];
|
||||
const addresses = [];
|
||||
let address = [];
|
||||
let parsedAddresses = [];
|
||||
|
||||
@@ -354,44 +342,41 @@ function addressparser(str, options) {
|
||||
addresses.push(address);
|
||||
}
|
||||
|
||||
addresses.forEach(address => {
|
||||
address = _handleAddress(address, depth);
|
||||
if (address.length) {
|
||||
parsedAddresses = parsedAddresses.concat(address);
|
||||
addresses.forEach(addr => {
|
||||
const handled = _handleAddress(addr, depth);
|
||||
if (handled.length) {
|
||||
parsedAddresses = parsedAddresses.concat(handled);
|
||||
}
|
||||
});
|
||||
|
||||
// Merge fragments from unquoted display names containing commas/semicolons.
|
||||
// When "Joe Foo, PhD <joe@example.com>" is split on the comma, it produces
|
||||
// Merge fragments produced when unquoted display names contain commas.
|
||||
// "Joe Foo, PhD <joe@example.com>" is split on the comma into
|
||||
// [{name:"Joe Foo", address:""}, {name:"PhD", address:"joe@example.com"}].
|
||||
// Detect this pattern and recombine: a name-only entry followed by an entry
|
||||
// that has both a name and an address (from angle-bracket notation).
|
||||
// Recombine: a name-only entry followed by an entry with both name and address.
|
||||
for (let i = parsedAddresses.length - 2; i >= 0; i--) {
|
||||
let current = parsedAddresses[i];
|
||||
let next = parsedAddresses[i + 1];
|
||||
if (current.address === '' && current.name && !current.group && next.address && next.name && !next.group) {
|
||||
const current = parsedAddresses[i];
|
||||
const next = parsedAddresses[i + 1];
|
||||
if (current.address === '' && current.name && !current.group && next.address && next.name) {
|
||||
next.name = current.name + ', ' + next.name;
|
||||
parsedAddresses.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.flatten) {
|
||||
let addresses = [];
|
||||
let walkAddressList = list => {
|
||||
list.forEach(address => {
|
||||
if (address.group) {
|
||||
return walkAddressList(address.group);
|
||||
} else {
|
||||
addresses.push(address);
|
||||
const flatAddresses = [];
|
||||
const walkAddressList = list => {
|
||||
list.forEach(entry => {
|
||||
if (entry.group) {
|
||||
return walkAddressList(entry.group);
|
||||
}
|
||||
flatAddresses.push(entry);
|
||||
});
|
||||
};
|
||||
walkAddressList(parsedAddresses);
|
||||
return addresses;
|
||||
return flatAddresses;
|
||||
}
|
||||
|
||||
return parsedAddresses;
|
||||
}
|
||||
|
||||
// expose to the world
|
||||
module.exports = addressparser;
|
||||
|
||||
Reference in New Issue
Block a user