eml-parser
Version:
Parse .eml and .msg files or convert to pdf, html, jpeg or png format. Extract headers and attachments from .eml and msg files.
406 lines (375 loc) • 17 kB
JavaScript
const simpleParser = require('mailparser').simpleParser;
const pdf = require('html-pdf');
const MsgReader = require('@kenjiuno/msgreader').default
const decompressRTF = require('@kenjiuno/decompressrtf').decompressRTF;
const iconv = require("iconv-lite");
const rtfParser = require("rtf-stream-parser");
const isStringsArray = arr => arr.every(i => typeof i === "string");
function stream2buffer(stream) {
return new Promise((resolve, reject) => {
const _buf = [];
stream.on("data", (chunk) => _buf.push(chunk));
stream.on("end", () => resolve(Buffer.concat(_buf)));
stream.on("error", (err) => reject(err));
});
}
module.exports = EmlParser = function (fileReadStream) {
this.parsedEmail;
this.parseEml = (options) => {
return new Promise((resolve, reject) => {
if (this.parsedEmail) {
resolve(this.parsedEmail);
} else {
simpleParser(fileReadStream, {})
.then(result => {
if (options && options.ignoreEmbedded) {
result.attachments = result.attachments.filter(att => att.contentDisposition === 'attachment');
}
if (options && options.highlightKeywords) {
if (!Array.isArray(options.highlightKeywords)) throw new Error('err: highlightKeywords is not an array, expected: String[]');
if (!isStringsArray(options.highlightKeywords)) throw new Error('err: highlightKeywords contains non-string values, expected: String[]');
let flags = 'gi';
if (options.highlightCaseSensitive) flags = 'g';
options.highlightKeywords.forEach(keyword => {
if (result.html) {
result.html = result.html.replace(new RegExp(keyword, flags), function (str) { return `<mark>${str}</mark>` });
} else if (result.textAsHtml) {
result.textAsHtml = result.textAsHtml.replace(new RegExp(keyword, flags), function (str) { return `<mark>${str}</mark>` });
}
});
}
this.parsedEmail = result;
resolve(this.parsedEmail);
})
.catch(err => {
reject(err);
});
}
})
}
this.parseMsg = async (options) => {
let buffer = await stream2buffer(fileReadStream)
let emailData = new MsgReader(buffer)
this.parsedEmail = emailData.getFileData();
if (this.parsedEmail.compressedRtf) {
let outputArray = decompressRTF(this.parsedEmail.compressedRtf);
let decompressedRtf = Buffer.from(outputArray).toString("ascii");
this.parsedEmail.html = rtfParser.deEncapsulateSync(decompressedRtf, { decode: iconv.decode }).text;
} else if (this.parsedEmail.bodyHtml) {
this.parsedEmail.html = this.parsedEmail.bodyHtml;
} else if (this.parsedEmail.body) {
this.parsedEmail.html = `<pre>${this.parsedEmail.body}</pre>`;
} else if (this.parsedEmail.html && this.parsedEmail.html instanceof Uint8Array) {
// decode Uint8Array html to string
this.parsedEmail.html = Buffer.from(this.parsedEmail.html).toString('utf8');
} else if (this.parsedEmail.html && typeof this.parsedEmail.html === 'string') {
// already a string, use as is
// nothing to do
} else {
this.parsedEmail.html = '';
}
if (this.parsedEmail.attachments && Array.isArray(this.parsedEmail.attachments)) {
this.parsedEmail.attachments = this.parsedEmail.attachments.map(att => {
att.content = emailData.getAttachment(att).content;
return att;
})
} else {
this.parsedEmail.attachments = [];
}
if (options && options.highlightKeywords) {
if (!Array.isArray(options.highlightKeywords)) throw new Error('err: highlightKeywords is not an array, expected: String[]');
if (!isStringsArray(options.highlightKeywords)) throw new Error('err: highlightKeywords contains non-string values, expected: String[]');
let flags = 'gi';
if (options.highlightCaseSensitive) flags = 'g';
options.highlightKeywords.forEach(keyword => {
this.parsedEmail.html = this.parsedEmail.html.replace(new RegExp(keyword, flags), function (str) { return `<mark>${str}</mark>` });
});
}
delete this.parsedEmail.compressedRtf;
return this.parsedEmail;
}
this.getEmailHeaders = () => {
return new Promise((resolve, reject) => {
this.parseEml()
.then(result => {
let headers = {
subject: result.subject,
from: result.from.value,
to: result.to.value,
cc: result.cc?.value,
date: result.date,
inReplyTo: result?.inReplyTo,
messageId: result.messageId
}
resolve(headers)
})
.catch(err => {
reject(err);
})
})
}
this.getMessageHeaders = () => {
return new Promise((resolve, reject) => {
this.parseMsg()
.then(result => {
let headers = {
subject: result.subject,
from: [{
name: result.senderName,
address: result.senderEmail
}],
to: result.recipients.filter(recipient => recipient.recipType === 'to').map(recipient => { return { name: recipient.name, address: recipient.email } }),
cc: result.recipients.filter(recipient => recipient.recipType === 'cc').map(recipient => { return { name: recipient.name, address: recipient.email } }),
date: result.messageDeliveryTime
}
resolve(headers)
})
.catch(err => {
reject(err);
})
})
}
this.getEmailBodyHtml = (options) => {
let replacements = {
"’": "'",
"–": "─"
}
return new Promise((resolve, reject) => {
this.parseEml(options)
.then(result => {
let htmlString = result.html || result.textAsHtml;
if (!htmlString) {
resolve('');
}
for (var key in replacements) {
let re = new RegExp(key, 'gi')
htmlString = htmlString.replace(re, replacements[key]);
}
resolve(htmlString);
})
.catch(err => {
reject(err);
})
})
}
this.getMessageBodyHtml = (options) => {
return new Promise((resolve, reject) => {
this.parseMsg(options)
.then(result => {
resolve(result.html)
})
.catch(err => {
reject(err)
})
})
}
this.getEmailAsHtml = (options) => {
return new Promise((resolve, reject) => {
this.parseEml(options)
.then(result => {
let headerHtml = `
<div style="border:1px solid gray;margin-bottom:5px;padding:5px">
<h2>${result.subject}</h2>
<div style="display:flex;width:100%;">
<span style="font-weight:600;">From: ${result.from.html}</span>
<span style="flex: 1 1 auto;"></span>
<span style="color:silver;font-weight:600">${new Date(result.date).toLocaleString()}</span>
</div>
`
if (result.to) {
headerHtml = headerHtml + `<div style="font-size:12px;">To: ${result.to.html}</div>`
}
if (result.cc) {
headerHtml = headerHtml + `<div style="font-size:12px;">Cc: ${result.cc.html}</div></div>`
} else {
headerHtml = headerHtml + `</div>`;
}
this.getEmailBodyHtml()
.then(bodyHtml => {
resolve(headerHtml + `<p>${bodyHtml}</p>`)
})
.catch(err => {
reject(err);
})
})
.catch(err => {
reject(err);
})
})
}
this.getMessageAsHtml = (options) => {
return new Promise((resolve, reject) => {
this.parseMsg(options)
.then(result => {
let toRecipients = result.recipients.filter(recipient => recipient.recipType === 'to').map(recipient => { return { name: recipient.name, address: recipient.email } })
let ccRecipients = result.recipients.filter(recipient => recipient.recipType === 'cc').map(recipient => { return { name: recipient.name, address: recipient.email } })
let toHtml = '';
let ccHtml = '';
toRecipients.forEach(recipient => {
toHtml += `<span>${recipient.name}</span> <<a href=\"mailto:${recipient.address}\" class=\"mp_address_email\">${recipient.address}</a>>` + ';'
});
ccRecipients.forEach(recipient => {
ccHtml += `<span>${recipient.name}</span> <<a href=\"mailto:${recipient.address}\" class=\"mp_address_email\">${recipient.address}</a>>` + ';'
});
let headerHtml = `
<div style="border:1px solid gray;margin-bottom:5px;padding:5px">
<h2>${result.subject}</h2>
<div style="display:flex;width:100%;">
<span style="font-weight:600;">From: <span>${result.senderName}</span> <<a href=\"mailto:${result.senderEmail}\" class=\"mp_address_email\">${result.senderEmail}</a>></span>
<span style="flex: 1 1 auto;"></span>
<span style="color:silver;font-weight:600">${new Date(result.messageDeliveryTime).toLocaleString()}</span>
</div>
`
if (toHtml) {
headerHtml = headerHtml + `<div style="font-size:12px;">To: ${toHtml}</div>`
}
if (ccHtml) {
headerHtml = headerHtml + `<div style="font-size:12px;">Cc: ${ccHtml}</div>`
} else {
headerHtml = headerHtml + `</div>`;
}
resolve(headerHtml + `<p>${result.html}</p>`)
})
.catch(err => {
reject(err);
})
})
}
this.convertEmailToStream = (type, orientation, format, outerOptions) => {
return new Promise((resolve, reject) => {
let options = {
orientation: orientation || 'landscape' // potrait | landscape
};
if (type) {
options.type = type;
}
if (format) {
options.format = format // A3, A4, A5, Legal, Letter, Tabloid
}
this.getEmailAsHtml(outerOptions)
.then(html => {
pdf.create(html, options).toStream(function (err, res) {
if (err) {
reject(err);
}
resolve(res);
});
})
.catch(err => {
reject(err);
})
})
}
this.convertMessageToStream = (type, orientation, format, outerOptions) => {
return new Promise((resolve, reject) => {
let options = {
orientation: orientation || 'landscape' // potrait | landscape
};
if (type) {
options.type = type;
}
if (format) {
options.format = format // A3, A4, A5, Legal, Letter, Tabloid
}
this.getMessageAsHtml(outerOptions)
.then(html => {
pdf.create(html, options).toStream(function (err, res) {
if (err) {
reject(err);
}
resolve(res);
});
})
.catch(err => {
reject(err);
})
})
}
this.convertEmailToBuffer = (type, orientation, format, outerOptions) => {
return new Promise((resolve, reject) => {
let options = {
orientation: orientation || 'landscape' // potrait | landscape
};
if (type) {
options.type = type;
}
if (format) {
options.format = format // A3, A4, A5, Legal, Letter, Tabloid
}
this.getEmailAsHtml(outerOptions)
.then(html => {
pdf.create(html, options).toBuffer(function (err, res) {
if (err) {
reject(err);
}
resolve(res);
});
})
.catch(err => {
reject(err);
})
})
}
this.convertMessageToBuffer = (type, orientation, format, outerOptions) => {
return new Promise((resolve, reject) => {
let options = {
orientation: orientation || 'landscape' // potrait | landscape
};
if (type) {
options.type = type;
}
if (format) {
options.format = format // A3, A4, A5, Legal, Letter, Tabloid
}
this.getMessageAsHtml(outerOptions)
.then(html => {
pdf.create(html, options).toBuffer(function (err, res) {
if (err) {
reject(err);
}
resolve(res);
});
})
.catch(err => {
reject(err);
})
})
}
this.getEmailAttachments = (options) => {
return new Promise((resolve, reject) => {
this.parseEml()
.then(result => {
if (options && options.ignoreEmbedded) {
result.attachments = result.attachments.filter(att => att.contentDisposition === 'attachment');
}
resolve(result.attachments);
})
.catch(err => {
reject(err);
})
})
}
this.getMessageAttachments = () => {
return new Promise((resolve, reject) => {
this.parseMsg()
.then(result => {
resolve(result.attachments);
})
.catch(err => {
reject(err);
})
})
}
this.getEmailEmbeddedFiles = () => {
return new Promise((resolve, reject) => {
this.parseEml()
.then(result => {
result.attachments = result.attachments.filter(att => att.contentDisposition !== 'attachment');
resolve(result.attachments);
})
.catch(err => {
reject(err);
})
})
}
}