fix cloudflare temp email aws code parsing
This commit is contained in:
+188
-19
@@ -11,6 +11,7 @@
|
||||
function firstNonEmptyString(values) {
|
||||
for (const value of values) {
|
||||
if (value === undefined || value === null) continue;
|
||||
if (typeof value === 'object') continue;
|
||||
const normalized = String(value).trim();
|
||||
if (normalized) return normalized;
|
||||
}
|
||||
@@ -87,18 +88,35 @@
|
||||
if (Array.isArray(payload)) return payload;
|
||||
if (!payload || typeof payload !== 'object') return [];
|
||||
|
||||
const candidates = [
|
||||
payload.data,
|
||||
payload.items,
|
||||
payload.messages,
|
||||
payload.mails,
|
||||
payload.results,
|
||||
payload.rows,
|
||||
const rowKeys = [
|
||||
'data',
|
||||
'items',
|
||||
'messages',
|
||||
'mails',
|
||||
'results',
|
||||
'result',
|
||||
'rows',
|
||||
'list',
|
||||
'hydra:member',
|
||||
];
|
||||
const queue = [payload];
|
||||
const seen = new Set();
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (Array.isArray(candidate)) {
|
||||
return candidate;
|
||||
while (queue.length) {
|
||||
const current = queue.shift();
|
||||
if (!current || typeof current !== 'object' || seen.has(current)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(current);
|
||||
|
||||
for (const key of rowKeys) {
|
||||
const candidate = current[key];
|
||||
if (Array.isArray(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
if (candidate && typeof candidate === 'object') {
|
||||
queue.push(candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +124,47 @@
|
||||
}
|
||||
|
||||
function normalizeCloudflareTempEmailAddress(value) {
|
||||
return String(value || '').trim().toLowerCase();
|
||||
const source = String(value || '').trim();
|
||||
if (!source) return '';
|
||||
const bracketMatch = source.match(/<\s*([^<>\s]+@[^<>\s]+)\s*>/);
|
||||
const directMatch = source.match(/[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}/i);
|
||||
return String(bracketMatch?.[1] || directMatch?.[0] || source).trim().toLowerCase();
|
||||
}
|
||||
|
||||
function collectTextCandidates(value, keys = []) {
|
||||
const results = [];
|
||||
const visit = (entry) => {
|
||||
if (entry === undefined || entry === null) return;
|
||||
if (typeof entry === 'string' || typeof entry === 'number') {
|
||||
const normalized = String(entry).trim();
|
||||
if (normalized) results.push(normalized);
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(entry)) {
|
||||
entry.forEach(visit);
|
||||
return;
|
||||
}
|
||||
if (typeof entry === 'object') {
|
||||
const objectKeys = keys.length ? keys : Object.keys(entry);
|
||||
for (const key of objectKeys) {
|
||||
if (Object.prototype.hasOwnProperty.call(entry, key)) {
|
||||
visit(entry[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
visit(value);
|
||||
return results;
|
||||
}
|
||||
|
||||
function firstTextCandidate(values, keys = []) {
|
||||
for (const value of values) {
|
||||
const candidates = collectTextCandidates(value, keys);
|
||||
for (const candidate of candidates) {
|
||||
if (candidate) return candidate;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function splitRawMessage(raw = '') {
|
||||
@@ -245,19 +303,53 @@
|
||||
return match ? match[1] : '';
|
||||
}
|
||||
|
||||
function decodeHtmlEntityCode(value, radix = 10) {
|
||||
const codePoint = parseInt(value, radix);
|
||||
if (!Number.isFinite(codePoint) || codePoint < 0 || codePoint > 0x10FFFF) {
|
||||
return ' ';
|
||||
}
|
||||
return String.fromCodePoint(codePoint);
|
||||
}
|
||||
|
||||
function stripHtmlTags(value = '') {
|
||||
return String(value || '')
|
||||
.replace(/<style[\s\S]*?<\/style>/gi, ' ')
|
||||
.replace(/<script[\s\S]*?<\/script>/gi, ' ')
|
||||
.replace(/<[^>]+>/g, ' ')
|
||||
.replace(/&#x([0-9a-f]+);/gi, (_match, code) => decodeHtmlEntityCode(code, 16))
|
||||
.replace(/&#(\d+);/g, (_match, code) => decodeHtmlEntityCode(code, 10))
|
||||
.replace(/ /gi, ' ')
|
||||
.replace(/&/gi, '&')
|
||||
.replace(/</gi, '<')
|
||||
.replace(/>/gi, '>')
|
||||
.replace(/"/gi, '"')
|
||||
.replace(/'/g, "'")
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
function normalizeBodyText(value = '', options = {}) {
|
||||
const { html = false } = options;
|
||||
const source = String(value || '').trim();
|
||||
if (!source) return '';
|
||||
const text = html || /<\/?[a-z][\s\S]*>/i.test(source)
|
||||
? stripHtmlTags(source)
|
||||
: source;
|
||||
return text.replace(/\s+/g, ' ').trim();
|
||||
}
|
||||
|
||||
function firstBodyTextCandidate(values, options = {}) {
|
||||
const { keys = [], html = false } = options;
|
||||
for (const value of values) {
|
||||
const candidates = collectTextCandidates(value, keys);
|
||||
for (const candidate of candidates) {
|
||||
const normalized = normalizeBodyText(candidate, { html });
|
||||
if (normalized) return normalized;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function decodeMimeBody(bodyText = '', headers = {}) {
|
||||
const contentType = String(headers['content-type'] || '');
|
||||
const transferEncoding = String(headers['content-transfer-encoding'] || '').trim().toLowerCase();
|
||||
@@ -321,40 +413,114 @@
|
||||
function normalizeCloudflareTempEmailMessage(row = {}) {
|
||||
if (!row || typeof row !== 'object') return null;
|
||||
|
||||
const address = normalizeCloudflareTempEmailAddress(firstNonEmptyString([
|
||||
const raw = firstTextCandidate([row.raw, row.source, row.mime, row.message], [
|
||||
'raw',
|
||||
'source',
|
||||
'mime',
|
||||
'message',
|
||||
'content',
|
||||
'value',
|
||||
]);
|
||||
const parsedMime = raw ? extractTextFromMime(raw) : { headers: {}, text: '' };
|
||||
const address = normalizeCloudflareTempEmailAddress(firstTextCandidate([
|
||||
row.address,
|
||||
row.mail_address,
|
||||
row.mailAddress,
|
||||
row.email,
|
||||
row.recipient,
|
||||
row.to,
|
||||
row.mailTo,
|
||||
row.receiver,
|
||||
row.receivers,
|
||||
row.envelope_to,
|
||||
row.envelopeTo,
|
||||
parsedMime.headers.to,
|
||||
], [
|
||||
'emailAddress',
|
||||
'address',
|
||||
'email',
|
||||
'value',
|
||||
'recipient',
|
||||
'mailbox',
|
||||
'to',
|
||||
]));
|
||||
const originalRecipient = normalizeCloudflareTempEmailAddress(firstNonEmptyString([
|
||||
const originalRecipient = normalizeCloudflareTempEmailAddress(firstTextCandidate([
|
||||
row.original_recipient,
|
||||
row.originalRecipient,
|
||||
row.original_recipient_email,
|
||||
row.originalRecipientEmail,
|
||||
row.envelope_to,
|
||||
row.envelopeTo,
|
||||
], [
|
||||
'emailAddress',
|
||||
'address',
|
||||
'email',
|
||||
'value',
|
||||
'recipient',
|
||||
'mailbox',
|
||||
'to',
|
||||
]));
|
||||
const raw = firstNonEmptyString([row.raw, row.source, row.mime, row.message]);
|
||||
const parsedMime = raw ? extractTextFromMime(raw) : { headers: {}, text: '' };
|
||||
const subject = decodeMimeEncodedWords(firstNonEmptyString([
|
||||
const subject = decodeMimeEncodedWords(firstTextCandidate([
|
||||
row.subject,
|
||||
row.title,
|
||||
parsedMime.headers.subject,
|
||||
]));
|
||||
const fromAddress = decodeMimeEncodedWords(firstNonEmptyString([
|
||||
const fromAddress = decodeMimeEncodedWords(firstTextCandidate([
|
||||
row.from,
|
||||
row.sender,
|
||||
row.mail_from,
|
||||
row.mailFrom,
|
||||
row.from_email,
|
||||
row.fromEmail,
|
||||
row.sender_email,
|
||||
row.senderEmail,
|
||||
parsedMime.headers.from,
|
||||
], [
|
||||
'emailAddress',
|
||||
'address',
|
||||
'email',
|
||||
'value',
|
||||
'name',
|
||||
'from',
|
||||
'sender',
|
||||
]));
|
||||
const bodyPreview = firstNonEmptyString([
|
||||
const bodyText = firstBodyTextCandidate([
|
||||
row.bodyPreview,
|
||||
row.snippet,
|
||||
row.text,
|
||||
row.text_content,
|
||||
row.textContent,
|
||||
row.preview,
|
||||
row.body,
|
||||
row.content,
|
||||
], {
|
||||
keys: ['content', 'text', 'plain', 'value', 'body', 'bodyPreview'],
|
||||
});
|
||||
const htmlText = firstBodyTextCandidate([
|
||||
row.html_content,
|
||||
row.htmlContent,
|
||||
row.html_body,
|
||||
row.htmlBody,
|
||||
row.body_html,
|
||||
row.bodyHtml,
|
||||
row.content_html,
|
||||
row.contentHtml,
|
||||
row.raw_html,
|
||||
row.rawHtml,
|
||||
row.html,
|
||||
], {
|
||||
html: true,
|
||||
keys: ['content', 'html', 'value', 'body'],
|
||||
});
|
||||
const bodyPreview = firstNonEmptyString([
|
||||
bodyText,
|
||||
htmlText,
|
||||
parsedMime.text,
|
||||
raw,
|
||||
]).replace(/\s+/g, ' ').trim();
|
||||
|
||||
return {
|
||||
id: firstNonEmptyString([row.id, row.mail_id]),
|
||||
id: firstNonEmptyString([row.id, row._id, row.mail_id, row.mailId, row.message_id, row.messageId, row.msgid]),
|
||||
address,
|
||||
originalRecipient,
|
||||
addressId: firstNonEmptyString([row.address_id, row.addressId]),
|
||||
@@ -369,10 +535,13 @@
|
||||
receivedDateTime: normalizeReceivedDateTime(firstNonEmptyString([
|
||||
row.receivedDateTime,
|
||||
row.received_at,
|
||||
row.receivedAt,
|
||||
row.created_at,
|
||||
row.createdAt,
|
||||
row.updated_at,
|
||||
row.updatedAt,
|
||||
row.date,
|
||||
row.timestamp,
|
||||
])),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user