mailparser
Advanced tools
Comparing version 2.0.5 to 2.1.0
@@ -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 += ' <' + link + '>'; | ||
} 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 += ' <' + link + '>'; | ||
} 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** |
50824
956
16
8
+ Addedlinkify-it@2.0.3
+ Addedtlds@1.197.0
+ Addedhtml-to-text@3.3.0(transitive)
+ Addediconv-lite@0.4.19(transitive)
+ Addedlinkify-it@2.0.3(transitive)
+ Addedtlds@1.197.0(transitive)
+ Addeduc.micro@1.0.6(transitive)
- Removedhtml-to-text@3.2.0(transitive)
- Removediconv-lite@0.4.17(transitive)
Updatedhtml-to-text@3.3.0
Updatediconv-lite@0.4.19