New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

mailparser

Package Overview
Dependencies
Maintainers
1
Versions
112
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

mailparser - npm Package Compare versions

Comparing version 2.0.5 to 2.1.0

537

lib/mail-parser.js

@@ -14,3 +14,12 @@ 'use strict';

const he = require('he');
const linkify = require('linkify-it')();
const tlds = require('tlds');
linkify
.tlds(tlds) // Reload with full tlds list
.tlds('onion', true) // Add unofficial `.onion` domain
.add('git:', 'http:') // Add `git:` ptotocol as "alias"
.add('ftp:', null) // Disable `ftp:` ptotocol
.set({ fuzzyIP: true });
class MailParser extends Transform {

@@ -147,3 +156,3 @@ constructor(config) {

value = libmime.parseHeaderValue(value);
Object.keys(value && value.params || {}).forEach(key => {
Object.keys((value && value.params) || {}).forEach(key => {
try {

@@ -222,3 +231,20 @@ value.params[key] = libmime.decodeWords(value.params[key]);

// keep only the first value
let singleKeys = ['message-id', 'content-id', 'from', 'sender', 'in-reply-to', 'reply-to', 'subject', 'date', 'content-disposition', 'content-type', 'content-transfer-encoding', 'priority', 'mime-version', 'content-description', 'precedence', 'errors-to'];
let singleKeys = [
'message-id',
'content-id',
'from',
'sender',
'in-reply-to',
'reply-to',
'subject',
'date',
'content-disposition',
'content-type',
'content-transfer-encoding',
'priority',
'mime-version',
'content-description',
'precedence',
'errors-to'
];

@@ -252,20 +278,22 @@ headers.forEach((value, key) => {

let response = {};
let data = addresses.map(address => {
if (/^https?:/i.test(address.name)) {
response.url = address.name;
} else if (address.name) {
response.name = address.name;
}
if (/^mailto:/.test(address.address)) {
response.mail = address.address.substr(7);
} else if (address.address && address.address.indexOf('@') < 0) {
response.id = address.address;
} else if (address.address) {
response.mail = address.address;
}
if (Object.keys(response).length) {
return response;
}
return false;
}).filter(address => address);
let data = addresses
.map(address => {
if (/^https?:/i.test(address.name)) {
response.url = address.name;
} else if (address.name) {
response.name = address.name;
}
if (/^mailto:/.test(address.address)) {
response.mail = address.address.substr(7);
} else if (address.address && address.address.indexOf('@') < 0) {
response.id = address.address;
} else if (address.address) {
response.mail = address.address;
}
if (Object.keys(response).length) {
return response;
}
return false;
})
.filter(address => address);
if (data.length) {

@@ -281,3 +309,4 @@ return {

value = value.toLowerCase().trim();
if (!isNaN(parseInt(value, 10))) { // support "X-Priority: 1 (Highest)"
if (!isNaN(parseInt(value, 10))) {
// support "X-Priority: 1 (Highest)"
value = parseInt(value, 10) || 0;

@@ -330,4 +359,6 @@ if (value === 3) {

}
if (/@xn\-\-/.test(address.address)) {
address.address = address.address.substr(0, address.address.lastIndexOf('@') + 1) + punycode.toUnicode(address.address.substr(address.address.lastIndexOf('@') + 1));
if (/@xn--/.test(address.address)) {
address.address =
address.address.substr(0, address.address.lastIndexOf('@') + 1) +
punycode.toUnicode(address.address.substr(address.address.lastIndexOf('@') + 1));
}

@@ -341,3 +372,2 @@ if (address.group) {

createNode(node) {
let contentType = node.contentType;

@@ -360,3 +390,2 @@ let disposition = node.disposition;

if (!/^multipart\//i.test(contentType)) {
if (disposition && !['attachment', 'inline'].includes(disposition)) {

@@ -452,56 +481,69 @@ disposition = 'attachment';

if (node.showMeta) {
let meta = ['From', 'Subject', 'Date', 'To', 'Cc', 'Bcc'].map(fkey => {
let key = fkey.toLowerCase();
if (!node.headers.has(key)) {
return false;
}
let value = node.headers.get(key);
if (!value) {
return false;
}
return {
key: fkey,
value: Array.isArray(value) ? value[value.length - 1] : value
};
}).filter(entry => entry);
let meta = ['From', 'Subject', 'Date', 'To', 'Cc', 'Bcc']
.map(fkey => {
let key = fkey.toLowerCase();
if (!node.headers.has(key)) {
return false;
}
let value = node.headers.get(key);
if (!value) {
return false;
}
return {
key: fkey,
value: Array.isArray(value) ? value[value.length - 1] : value
};
})
.filter(entry => entry);
if (this.hasHtml) {
html.push('<table class="mp_head">' + meta.map(entry => {
html.push(
'<table class="mp_head">' +
meta
.map(entry => {
let value = entry.value;
switch (entry.key) {
case 'From':
case 'To':
case 'Cc':
case 'Bcc':
value = value.html;
break;
case 'Date':
value = this.options.formatDateString ? this.options.formatDateString(value) : value.toUTCString();
break;
case 'Subject':
value = '<strong>' + he.encode(value) + '</strong>';
break;
default:
value = he.encode(value);
}
let value = entry.value;
switch (entry.key) {
case 'From':
case 'To':
case 'Cc':
case 'Bcc':
value = value.html;
break;
case 'Date':
value = this.options.formatDateString ? this.options.formatDateString(value) : value.toUTCString();
break;
case 'Subject':
value = '<strong>' + he.encode(value) + '</strong>';
break;
default:
value = he.encode(value);
}
return '<tr><td class="mp_head_key">' + he.encode(entry.key) + ':</td><td class="mp_head_value">' + value + '<td></tr>';
}).join('\n') + '<table>');
return '<tr><td class="mp_head_key">' + he.encode(entry.key) + ':</td><td class="mp_head_value">' + value + '<td></tr>';
})
.join('\n') +
'<table>'
);
}
if (this.hasText) {
text.push('\n' + meta.map(entry => {
let value = entry.value;
switch (entry.key) {
case 'From':
case 'To':
case 'Cc':
case 'Bcc':
value = value.text;
break;
case 'Date':
value = this.options.formatDateString ? this.options.formatDateString(value) : value.toUTCString();
break;
}
return entry.key + ': ' + value;
}).join('\n') + '\n');
text.push(
'\n' +
meta
.map(entry => {
let value = entry.value;
switch (entry.key) {
case 'From':
case 'To':
case 'Cc':
case 'Bcc':
value = value.text;
break;
case 'Date':
value = this.options.formatDateString ? this.options.formatDateString(value) : value.toUTCString();
break;
}
return entry.key + ': ' + value;
})
.join('\n') +
'\n'
);
}

@@ -545,121 +587,130 @@ }

switch (data.type) {
case 'node':
{
let node = this.createNode(data);
if (node === this.tree) {
['subject', 'references', 'date', 'to', 'from', 'to', 'cc', 'bcc', 'message-id', 'in-reply-to', 'reply-to'].forEach(key => {
if (node.headers.has(key)) {
this[key.replace(/\-([a-z])/g, (m, c) => c.toUpperCase())] = node.headers.get(key);
}
});
this.emit('headers', node.headers);
}
case 'node': {
let node = this.createNode(data);
if (node === this.tree) {
['subject', 'references', 'date', 'to', 'from', 'to', 'cc', 'bcc', 'message-id', 'in-reply-to', 'reply-to'].forEach(key => {
if (node.headers.has(key)) {
this[key.replace(/-([a-z])/g, (m, c) => c.toUpperCase())] = node.headers.get(key);
}
});
this.emit('headers', node.headers);
}
if (data.contentType === 'message/rfc822' && data.messageNode) {
break;
}
if (data.contentType === 'message/rfc822' && data.messageNode) {
break;
}
if (data.parentNode && data.parentNode.contentType === 'message/rfc822') {
node.showMeta = true;
if (data.parentNode && data.parentNode.contentType === 'message/rfc822') {
node.showMeta = true;
}
if (node.isAttachment) {
let contentType = node.contentType;
if (node.contentType === 'application/octet-stream' && data.filename) {
contentType = libmime.detectMimeType(data.filename) || 'application/octet-stream';
}
if (node.isAttachment) {
let contentType = node.contentType;
if (node.contentType === 'application/octet-stream' && data.filename) {
contentType = libmime.detectMimeType(data.filename) || 'application/octet-stream';
let attachment = {
type: 'attachment',
content: null,
contentType,
release: () => {
attachment.release = null;
if (this.waitUntilAttachmentEnd && typeof this.attachmentCallback === 'function') {
setImmediate(this.attachmentCallback);
}
this.attachmentCallback = false;
this.waitUntilAttachmentEnd = false;
}
};
let attachment = {
type: 'attachment',
content: null,
contentType,
release: () => {
attachment.release = null;
if (this.waitUntilAttachmentEnd && typeof this.attachmentCallback === 'function') {
setImmediate(this.attachmentCallback);
}
this.attachmentCallback = false;
this.waitUntilAttachmentEnd = false;
}
};
let hasher = new StreamHash(attachment, 'md5');
node.decoder.on('error', err => {
hasher.emit('error', err);
});
node.decoder.pipe(hasher);
attachment.content = hasher;
let hasher = new StreamHash(attachment, 'md5');
node.decoder.on('error', err => {
hasher.emit('error', err);
});
node.decoder.pipe(hasher);
attachment.content = hasher;
this.waitUntilAttachmentEnd = true;
if (data.disposition) {
attachment.contentDisposition = data.disposition;
}
if (data.filename) {
attachment.filename = data.filename;
}
if (node.headers.has('content-id')) {
attachment.contentId = [].concat(node.headers.get('content-id') || []).shift();
attachment.cid = attachment.contentId.trim().replace(/^<|>$/g, '').trim();
let parentNode = node;
while ((parentNode = parentNode.parent)) {
if (parentNode.contentType === 'multipart/related') {
attachment.related = true;
}
this.waitUntilAttachmentEnd = true;
if (data.disposition) {
attachment.contentDisposition = data.disposition;
}
if (data.filename) {
attachment.filename = data.filename;
}
if (node.headers.has('content-id')) {
attachment.contentId = [].concat(node.headers.get('content-id') || []).shift();
attachment.cid = attachment.contentId
.trim()
.replace(/^<|>$/g, '')
.trim();
let parentNode = node;
while ((parentNode = parentNode.parent)) {
if (parentNode.contentType === 'multipart/related') {
attachment.related = true;
}
}
}
attachment.headers = node.headers;
this.push(attachment);
this.attachmentList.push(attachment);
attachment.headers = node.headers;
this.push(attachment);
this.attachmentList.push(attachment);
} else if (node.disposition === 'inline') {
let chunks = [];
let chunklen = 0;
let contentStream = node.decoder;
} else if (node.disposition === 'inline') {
let chunks = [];
let chunklen = 0;
let contentStream = node.decoder;
if (node.contentType === 'text/plain') {
this.hasText = true;
} else if (node.contentType === 'text/html') {
this.hasHtml = true;
}
if (node.contentType === 'text/plain') {
this.hasText = true;
} else if (node.contentType === 'text/html') {
this.hasHtml = true;
let charset = node.charset || 'windows-1257';
//charset = charset || 'windows-1257';
if (
!['ascii', 'usascii', 'utf8'].includes(
charset
.replace(/[^a-z0-9]+/g, '')
.trim()
.toLowerCase()
)
) {
try {
let decodeStream = iconv.decodeStream(charset);
contentStream.on('error', err => {
decodeStream.emit('error', err);
});
contentStream.pipe(decodeStream);
contentStream = decodeStream;
} catch (E) {
// do not decode charset
}
}
let charset = node.charset || 'windows-1257';
//charset = charset || 'windows-1257';
if (!['ascii', 'usascii', 'utf8'].includes(charset.replace(/[^a-z0-9]+/g, '').trim().toLowerCase())) {
try {
let decodeStream = iconv.decodeStream(charset);
contentStream.on('error', err => {
decodeStream.emit('error', err);
});
contentStream.pipe(decodeStream);
contentStream = decodeStream;
} catch (E) {
// do not decode charset
contentStream.on('readable', () => {
let chunk;
while ((chunk = contentStream.read()) !== null) {
if (typeof chunk === 'string') {
chunk = Buffer.from(chunk);
}
chunks.push(chunk);
chunklen += chunk.length;
}
});
contentStream.on('readable', () => {
let chunk;
while ((chunk = contentStream.read()) !== null) {
if (typeof chunk === 'string') {
chunk = Buffer.from(chunk);
}
chunks.push(chunk);
chunklen += chunk.length;
}
});
contentStream.once('end', () => {
node.textContent = Buffer.concat(chunks, chunklen)
.toString()
.replace(/\r?\n/g, '\n');
});
contentStream.once('end', () => {
node.textContent = Buffer.concat(chunks, chunklen).toString().replace(/\r?\n/g, '\n');
});
contentStream.once('error', err => {
this.emit('error', err);
});
}
contentStream.once('error', err => {
this.emit('error', err);
});
}
break;
}
break;
}
case 'data':

@@ -694,20 +745,23 @@ if (this.curnode && this.curnode.decoder) {

getAddressesHTML(value) {
let formatSingleLevel = addresses => addresses.map(address => {
let str = '<span class="mp_address_group">';
if (address.name) {
str += '<span class="mp_address_name">' + he.encode(address.name) + (address.group ? ': ' : '') + '</span>';
}
if (address.address) {
let link = '<a href="mailto:' + he.encode(address.address) + '" class="mp_address_email">' + he.encode(address.address) + '</a>';
if (address.name) {
str += ' &lt;' + link + '&gt;';
} else {
str += link;
}
}
if (address.group) {
str += formatSingleLevel(address.group) + ';';
}
return str + '</span>';
}).join(', ');
let formatSingleLevel = addresses =>
addresses
.map(address => {
let str = '<span class="mp_address_group">';
if (address.name) {
str += '<span class="mp_address_name">' + he.encode(address.name) + (address.group ? ': ' : '') + '</span>';
}
if (address.address) {
let link = '<a href="mailto:' + he.encode(address.address) + '" class="mp_address_email">' + he.encode(address.address) + '</a>';
if (address.name) {
str += ' &lt;' + link + '&gt;';
} else {
str += link;
}
}
if (address.group) {
str += formatSingleLevel(address.group) + ';';
}
return str + '</span>';
})
.join(', ');
return formatSingleLevel([].concat(value || []));

@@ -717,20 +771,23 @@ }

getAddressesText(value) {
let formatSingleLevel = addresses => addresses.map(address => {
let str = '';
if (address.name) {
str += address.name + (address.group ? ': ' : '');
}
if (address.address) {
let link = address.address;
if (address.name) {
str += ' <' + link + '>';
} else {
str += link;
}
}
if (address.group) {
str += formatSingleLevel(address.group) + ';';
}
return str;
}).join(', ');
let formatSingleLevel = addresses =>
addresses
.map(address => {
let str = '';
if (address.name) {
str += address.name + (address.group ? ': ' : '');
}
if (address.address) {
let link = address.address;
if (address.name) {
str += ' <' + link + '>';
} else {
str += link;
}
}
if (address.group) {
str += formatSingleLevel(address.group) + ';';
}
return str;
})
.join(', ');
return formatSingleLevel([].concat(value || []));

@@ -795,13 +852,51 @@ }

function textToHtml(str) {
let encoded = he
// encode special chars
.encode(str, {
useNamedReferences: true
});
try {
if (linkify.pretest(encoded)) {
let links = linkify.match(encoded) || [];
let result = [];
let last = 0;
links.forEach(link => {
if (last < link.index) {
result.push(encoded.slice(last, link.index));
}
let text = '<p>' + he.
// encode special chars
encode(
str, {
useNamedReferences: true
}).
replace(/\r?\n/g, '\n').trim(). // normalize line endings
replace(/[ \t]+$/mg, '').trim(). // trim empty line endings
replace(/\n\n+/g, '</p><p>').trim(). // insert <p> to multiple linebreaks
replace(/\n/g, '<br/>') + // insert <br> to single linebreaks
let url = he
// encode special chars
.encode(link.url, {
useNamedReferences: true
});
let text = he
// encode special chars
.encode(link.text, {
useNamedReferences: true
});
result.push(`<a href="${url}">${text}</a>`);
last = link.lastIndex;
});
result.push(encoded.slice(last));
encoded = result.join('');
}
} catch (E) {
// failed, don't linkify
}
let text =
'<p>' +
encoded
.replace(/\r?\n/g, '\n')
.trim() // normalize line endings
.replace(/[ \t]+$/gm, '')
.trim() // trim empty line endings
.replace(/\n\n+/g, '</p><p>')
.trim() // insert <p> to multiple linebreaks
.replace(/\n/g, '<br/>') + // insert <br> to single linebreaks
'</p>';

@@ -808,0 +903,0 @@

'use strict';
const MailParser = require('./mail-parser.js');
module.exports = (input, callback) => {
module.exports = (input, options, callback) => {
if (!callback && typeof options === 'function') {
callback = options;
options = false;
}
let promise;

@@ -14,2 +18,5 @@ if (!callback) {

options = options || {};
let keepCidLinks = !!options.keepCidLinks;
let mail = {

@@ -57,14 +64,20 @@ attachments: []

if (mail.headers.has(key)) {
mail[key.replace(/\-([a-z])/g, (m, c) => c.toUpperCase())] = mail.headers.get(key);
mail[key.replace(/-([a-z])/g, (m, c) => c.toUpperCase())] = mail.headers.get(key);
}
});
parser.updateImageLinks((attachment, done) => done(false, 'data:' + attachment.contentType + ';base64,' + attachment.content.toString('base64')), (err, html) => {
if (err) {
return callback(err);
if (keepCidLinks) {
return callback(null, mail);
}
parser.updateImageLinks(
(attachment, done) => done(false, 'data:' + attachment.contentType + ';base64,' + attachment.content.toString('base64')),
(err, html) => {
if (err) {
return callback(err);
}
mail.html = html;
callback(null, mail);
}
mail.html = html;
callback(null, mail);
});
);
});

@@ -84,3 +97,3 @@

function callbackPromise(resolve, reject) {
return function (...args) {
return function(...args) {
let err = args.shift();

@@ -87,0 +100,0 @@ if (err) {

{
"name": "mailparser",
"version": "2.0.5",
"description": "Parse e-mails",
"main": "index.js",
"scripts": {
"test": "grunt"
},
"author": "Andris Reinman",
"license": "EUPL-1.1",
"dependencies": {
"addressparser": "1.0.1",
"he": "^1.1.1",
"html-to-text": "3.2.0",
"iconv-lite": "0.4.17",
"libmime": "3.1.0",
"mailsplit": "4.0.2"
},
"devDependencies": {
"eslint-config-nodemailer": "^1.0.0",
"grunt": "^1.0.1",
"grunt-cli": "^1.2.0",
"grunt-contrib-nodeunit": "^1.0.0",
"grunt-eslint": "^19.0.0",
"libbase64": "^0.1.0",
"libqp": "^1.1.0",
"random-message": "^1.1.0"
},
"engines": {
"node": ">=6.0.0"
},
"repository": {
"type": "git",
"url": "https://github.com/andris9/mailparser.git"
},
"bugs": {
"url": "https://github.com/andris9/mailparser/issues"
}
"name": "mailparser",
"version": "2.1.0",
"description": "Parse e-mails",
"main": "index.js",
"scripts": {
"test": "grunt"
},
"author": "Andris Reinman",
"license": "EUPL-1.1",
"dependencies": {
"addressparser": "1.0.1",
"he": "^1.1.1",
"html-to-text": "3.3.0",
"iconv-lite": "0.4.19",
"libmime": "3.1.0",
"linkify-it": "2.0.3",
"mailsplit": "4.0.2",
"tlds": "1.197.0"
},
"devDependencies": {
"eslint-config-nodemailer": "^1.2.0",
"grunt": "^1.0.1",
"grunt-cli": "^1.2.0",
"grunt-contrib-nodeunit": "^1.0.0",
"grunt-eslint": "^20.1.0",
"libbase64": "^0.2.0",
"libqp": "^1.1.0",
"random-message": "^1.1.0"
},
"engines": {
"node": ">=6.0.0"
},
"repository": {
"type": "git",
"url": "https://github.com/andris9/mailparser.git"
},
"bugs": {
"url": "https://github.com/andris9/mailparser/issues"
}
}

@@ -8,1 +8,9 @@ # mailparser

See [mailparser homepage](https://nodemailer.com/extras/mailparser/) for documentation and terms.
<a target='_blank' rel='nofollow' href='https://app.codesponsor.io/link/riRUvXLoy7hDEa8ptqPY9cHd/nodemailer/mailparser'>
<img alt='Sponsor' width='888' height='68' src='https://app.codesponsor.io/embed/riRUvXLoy7hDEa8ptqPY9cHd/nodemailer/mailparser.svg' />
</a>
### License
**EUPL-v1.1**
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc