123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314 |
- 'use strict';
- const shared = require('../shared');
- const MimeNode = require('../mime-node');
- const mimeFuncs = require('../mime-funcs');
- class MailMessage {
- constructor(mailer, data) {
- this.mailer = mailer;
- this.data = {};
- this.message = null;
- data = data || {};
- let options = mailer.options || {};
- let defaults = mailer._defaults || {};
- Object.keys(data).forEach(key => {
- this.data[key] = data[key];
- });
- this.data.headers = this.data.headers || {};
- // apply defaults
- Object.keys(defaults).forEach(key => {
- if (!(key in this.data)) {
- this.data[key] = defaults[key];
- } else if (key === 'headers') {
- // headers is a special case. Allow setting individual default headers
- Object.keys(defaults.headers).forEach(key => {
- if (!(key in this.data.headers)) {
- this.data.headers[key] = defaults.headers[key];
- }
- });
- }
- });
- // force specific keys from transporter options
- ['disableFileAccess', 'disableUrlAccess', 'normalizeHeaderKey'].forEach(key => {
- if (key in options) {
- this.data[key] = options[key];
- }
- });
- }
- resolveContent(...args) {
- return shared.resolveContent(...args);
- }
- resolveAll(callback) {
- let keys = [[this.data, 'html'], [this.data, 'text'], [this.data, 'watchHtml'], [this.data, 'amp'], [this.data, 'icalEvent']];
- if (this.data.alternatives && this.data.alternatives.length) {
- this.data.alternatives.forEach((alternative, i) => {
- keys.push([this.data.alternatives, i]);
- });
- }
- if (this.data.attachments && this.data.attachments.length) {
- this.data.attachments.forEach((attachment, i) => {
- if (!attachment.filename) {
- attachment.filename =
- (attachment.path || attachment.href || '')
- .split('/')
- .pop()
- .split('?')
- .shift() || 'attachment-' + (i + 1);
- if (attachment.filename.indexOf('.') < 0) {
- attachment.filename += '.' + mimeFuncs.detectExtension(attachment.contentType);
- }
- }
- if (!attachment.contentType) {
- attachment.contentType = mimeFuncs.detectMimeType(attachment.filename || attachment.path || attachment.href || 'bin');
- }
- keys.push([this.data.attachments, i]);
- });
- }
- let mimeNode = new MimeNode();
- let addressKeys = ['from', 'to', 'cc', 'bcc', 'sender', 'replyTo'];
- addressKeys.forEach(address => {
- let value;
- if (this.message) {
- value = [].concat(mimeNode._parseAddresses(this.message.getHeader(address === 'replyTo' ? 'reply-to' : address)) || []);
- } else if (this.data[address]) {
- value = [].concat(mimeNode._parseAddresses(this.data[address]) || []);
- }
- if (value && value.length) {
- this.data[address] = value;
- } else if (address in this.data) {
- this.data[address] = null;
- }
- });
- let singleKeys = ['from', 'sender', 'replyTo'];
- singleKeys.forEach(address => {
- if (this.data[address]) {
- this.data[address] = this.data[address].shift();
- }
- });
- let pos = 0;
- let resolveNext = () => {
- if (pos >= keys.length) {
- return callback(null, this.data);
- }
- let args = keys[pos++];
- if (!args[0] || !args[0][args[1]]) {
- return resolveNext();
- }
- shared.resolveContent(...args, (err, value) => {
- if (err) {
- return callback(err);
- }
- let node = {
- content: value
- };
- if (args[0][args[1]] && typeof args[0][args[1]] === 'object' && !Buffer.isBuffer(args[0][args[1]])) {
- Object.keys(args[0][args[1]]).forEach(key => {
- if (!(key in node) && !['content', 'path', 'href', 'raw'].includes(key)) {
- node[key] = args[0][args[1]][key];
- }
- });
- }
- args[0][args[1]] = node;
- resolveNext();
- });
- };
- setImmediate(() => resolveNext());
- }
- normalize(callback) {
- let envelope = this.data.envelope || this.message.getEnvelope();
- let messageId = this.message.messageId();
- this.resolveAll((err, data) => {
- if (err) {
- return callback(err);
- }
- data.envelope = envelope;
- data.messageId = messageId;
- ['html', 'text', 'watchHtml', 'amp'].forEach(key => {
- if (data[key] && data[key].content) {
- if (typeof data[key].content === 'string') {
- data[key] = data[key].content;
- } else if (Buffer.isBuffer(data[key].content)) {
- data[key] = data[key].content.toString();
- }
- }
- });
- if (data.icalEvent && Buffer.isBuffer(data.icalEvent.content)) {
- data.icalEvent.content = data.icalEvent.content.toString('base64');
- data.icalEvent.encoding = 'base64';
- }
- if (data.alternatives && data.alternatives.length) {
- data.alternatives.forEach(alternative => {
- if (alternative && alternative.content && Buffer.isBuffer(alternative.content)) {
- alternative.content = alternative.content.toString('base64');
- alternative.encoding = 'base64';
- }
- });
- }
- if (data.attachments && data.attachments.length) {
- data.attachments.forEach(attachment => {
- if (attachment && attachment.content && Buffer.isBuffer(attachment.content)) {
- attachment.content = attachment.content.toString('base64');
- attachment.encoding = 'base64';
- }
- });
- }
- data.normalizedHeaders = {};
- Object.keys(data.headers || {}).forEach(key => {
- let value = [].concat(data.headers[key] || []).shift();
- value = (value && value.value) || value;
- if (value) {
- if (['references', 'in-reply-to', 'message-id', 'content-id'].includes(key)) {
- value = this.message._encodeHeaderValue(key, value);
- }
- data.normalizedHeaders[key] = value;
- }
- });
- if (data.list && typeof data.list === 'object') {
- let listHeaders = this._getListHeaders(data.list);
- listHeaders.forEach(entry => {
- data.normalizedHeaders[entry.key] = entry.value.map(val => (val && val.value) || val).join(', ');
- });
- }
- if (data.references) {
- data.normalizedHeaders.references = this.message._encodeHeaderValue('references', data.references);
- }
- if (data.inReplyTo) {
- data.normalizedHeaders['in-reply-to'] = this.message._encodeHeaderValue('in-reply-to', data.inReplyTo);
- }
- return callback(null, data);
- });
- }
- setMailerHeader() {
- if (!this.message || !this.data.xMailer) {
- return;
- }
- this.message.setHeader('X-Mailer', this.data.xMailer);
- }
- setPriorityHeaders() {
- if (!this.message || !this.data.priority) {
- return;
- }
- switch ((this.data.priority || '').toString().toLowerCase()) {
- case 'high':
- this.message.setHeader('X-Priority', '1 (Highest)');
- this.message.setHeader('X-MSMail-Priority', 'High');
- this.message.setHeader('Importance', 'High');
- break;
- case 'low':
- this.message.setHeader('X-Priority', '5 (Lowest)');
- this.message.setHeader('X-MSMail-Priority', 'Low');
- this.message.setHeader('Importance', 'Low');
- break;
- default:
- // do not add anything, since all messages are 'Normal' by default
- }
- }
- setListHeaders() {
- if (!this.message || !this.data.list || typeof this.data.list !== 'object') {
- return;
- }
- // add optional List-* headers
- if (this.data.list && typeof this.data.list === 'object') {
- this._getListHeaders(this.data.list).forEach(listHeader => {
- listHeader.value.forEach(value => {
- this.message.addHeader(listHeader.key, value);
- });
- });
- }
- }
- _getListHeaders(listData) {
- // make sure an url looks like <protocol:url>
- return Object.keys(listData).map(key => ({
- key: 'list-' + key.toLowerCase().trim(),
- value: [].concat(listData[key] || []).map(value => ({
- prepared: true,
- foldLines: true,
- value: []
- .concat(value || [])
- .map(value => {
- if (typeof value === 'string') {
- value = {
- url: value
- };
- }
- if (value && value.url) {
- if (key.toLowerCase().trim() === 'id') {
- // List-ID: "comment" <domain>
- let comment = value.comment || '';
- if (mimeFuncs.isPlainText(comment)) {
- comment = '"' + comment + '"';
- } else {
- comment = mimeFuncs.encodeWord(comment);
- }
- return (value.comment ? comment + ' ' : '') + this._formatListUrl(value.url).replace(/^<[^:]+\/{,2}/, '');
- }
- // List-*: <http://domain> (comment)
- let comment = value.comment || '';
- if (!mimeFuncs.isPlainText(comment)) {
- comment = mimeFuncs.encodeWord(comment);
- }
- return this._formatListUrl(value.url) + (value.comment ? ' (' + comment + ')' : '');
- }
- return '';
- })
- .filter(value => value)
- .join(', ')
- }))
- }));
- }
- _formatListUrl(url) {
- url = url.replace(/[\s<]+|[\s>]+/g, '');
- if (/^(https?|mailto|ftp):/.test(url)) {
- return '<' + url + '>';
- }
- if (/^[^@]+@[^@]+$/.test(url)) {
- return '<mailto:' + url + '>';
- }
- return '<http://' + url + '>';
- }
- }
- module.exports = MailMessage;
|