source: node_modules/nodemailer/lib/mailer/mail-message.js

main
Last change on this file was 81bc7da, checked in by Klimentina Efremova <klimentina08642@…>, 3 months ago

Initial commit

  • Property mode set to 100644
File size: 11.3 KB
Line 
1'use strict';
2
3const shared = require('../shared');
4const MimeNode = require('../mime-node');
5const mimeFuncs = require('../mime-funcs');
6
7class MailMessage {
8 constructor(mailer, data) {
9 this.mailer = mailer;
10 this.data = {};
11 this.message = null;
12
13 data = data || {};
14 let options = mailer.options || {};
15 let defaults = mailer._defaults || {};
16
17 Object.keys(data).forEach(key => {
18 this.data[key] = data[key];
19 });
20
21 this.data.headers = this.data.headers || {};
22
23 // apply defaults
24 Object.keys(defaults).forEach(key => {
25 if (!(key in this.data)) {
26 this.data[key] = defaults[key];
27 } else if (key === 'headers') {
28 // headers is a special case. Allow setting individual default headers
29 Object.keys(defaults.headers).forEach(key => {
30 if (!(key in this.data.headers)) {
31 this.data.headers[key] = defaults.headers[key];
32 }
33 });
34 }
35 });
36
37 // force specific keys from transporter options
38 ['disableFileAccess', 'disableUrlAccess', 'normalizeHeaderKey'].forEach(key => {
39 if (key in options) {
40 this.data[key] = options[key];
41 }
42 });
43 }
44
45 resolveContent(...args) {
46 return shared.resolveContent(...args);
47 }
48
49 resolveAll(callback) {
50 let keys = [
51 [this.data, 'html'],
52 [this.data, 'text'],
53 [this.data, 'watchHtml'],
54 [this.data, 'amp'],
55 [this.data, 'icalEvent']
56 ];
57
58 if (this.data.alternatives && this.data.alternatives.length) {
59 this.data.alternatives.forEach((alternative, i) => {
60 keys.push([this.data.alternatives, i]);
61 });
62 }
63
64 if (this.data.attachments && this.data.attachments.length) {
65 this.data.attachments.forEach((attachment, i) => {
66 if (!attachment.filename) {
67 attachment.filename = (attachment.path || attachment.href || '').split('/').pop().split('?').shift() || 'attachment-' + (i + 1);
68 if (attachment.filename.indexOf('.') < 0) {
69 attachment.filename += '.' + mimeFuncs.detectExtension(attachment.contentType);
70 }
71 }
72
73 if (!attachment.contentType) {
74 attachment.contentType = mimeFuncs.detectMimeType(attachment.filename || attachment.path || attachment.href || 'bin');
75 }
76
77 keys.push([this.data.attachments, i]);
78 });
79 }
80
81 let mimeNode = new MimeNode();
82
83 let addressKeys = ['from', 'to', 'cc', 'bcc', 'sender', 'replyTo'];
84
85 addressKeys.forEach(address => {
86 let value;
87 if (this.message) {
88 value = [].concat(mimeNode._parseAddresses(this.message.getHeader(address === 'replyTo' ? 'reply-to' : address)) || []);
89 } else if (this.data[address]) {
90 value = [].concat(mimeNode._parseAddresses(this.data[address]) || []);
91 }
92 if (value && value.length) {
93 this.data[address] = value;
94 } else if (address in this.data) {
95 this.data[address] = null;
96 }
97 });
98
99 let singleKeys = ['from', 'sender'];
100 singleKeys.forEach(address => {
101 if (this.data[address]) {
102 this.data[address] = this.data[address].shift();
103 }
104 });
105
106 let pos = 0;
107 let resolveNext = () => {
108 if (pos >= keys.length) {
109 return callback(null, this.data);
110 }
111 let args = keys[pos++];
112 if (!args[0] || !args[0][args[1]]) {
113 return resolveNext();
114 }
115 shared.resolveContent(...args, (err, value) => {
116 if (err) {
117 return callback(err);
118 }
119
120 let node = {
121 content: value
122 };
123 if (args[0][args[1]] && typeof args[0][args[1]] === 'object' && !Buffer.isBuffer(args[0][args[1]])) {
124 Object.keys(args[0][args[1]]).forEach(key => {
125 if (!(key in node) && !['content', 'path', 'href', 'raw'].includes(key)) {
126 node[key] = args[0][args[1]][key];
127 }
128 });
129 }
130
131 args[0][args[1]] = node;
132 resolveNext();
133 });
134 };
135
136 setImmediate(() => resolveNext());
137 }
138
139 normalize(callback) {
140 let envelope = this.data.envelope || this.message.getEnvelope();
141 let messageId = this.message.messageId();
142
143 this.resolveAll((err, data) => {
144 if (err) {
145 return callback(err);
146 }
147
148 data.envelope = envelope;
149 data.messageId = messageId;
150
151 ['html', 'text', 'watchHtml', 'amp'].forEach(key => {
152 if (data[key] && data[key].content) {
153 if (typeof data[key].content === 'string') {
154 data[key] = data[key].content;
155 } else if (Buffer.isBuffer(data[key].content)) {
156 data[key] = data[key].content.toString();
157 }
158 }
159 });
160
161 if (data.icalEvent && Buffer.isBuffer(data.icalEvent.content)) {
162 data.icalEvent.content = data.icalEvent.content.toString('base64');
163 data.icalEvent.encoding = 'base64';
164 }
165
166 if (data.alternatives && data.alternatives.length) {
167 data.alternatives.forEach(alternative => {
168 if (alternative && alternative.content && Buffer.isBuffer(alternative.content)) {
169 alternative.content = alternative.content.toString('base64');
170 alternative.encoding = 'base64';
171 }
172 });
173 }
174
175 if (data.attachments && data.attachments.length) {
176 data.attachments.forEach(attachment => {
177 if (attachment && attachment.content && Buffer.isBuffer(attachment.content)) {
178 attachment.content = attachment.content.toString('base64');
179 attachment.encoding = 'base64';
180 }
181 });
182 }
183
184 data.normalizedHeaders = {};
185 Object.keys(data.headers || {}).forEach(key => {
186 let value = [].concat(data.headers[key] || []).shift();
187 value = (value && value.value) || value;
188 if (value) {
189 if (['references', 'in-reply-to', 'message-id', 'content-id'].includes(key)) {
190 value = this.message._encodeHeaderValue(key, value);
191 }
192 data.normalizedHeaders[key] = value;
193 }
194 });
195
196 if (data.list && typeof data.list === 'object') {
197 let listHeaders = this._getListHeaders(data.list);
198 listHeaders.forEach(entry => {
199 data.normalizedHeaders[entry.key] = entry.value.map(val => (val && val.value) || val).join(', ');
200 });
201 }
202
203 if (data.references) {
204 data.normalizedHeaders.references = this.message._encodeHeaderValue('references', data.references);
205 }
206
207 if (data.inReplyTo) {
208 data.normalizedHeaders['in-reply-to'] = this.message._encodeHeaderValue('in-reply-to', data.inReplyTo);
209 }
210
211 return callback(null, data);
212 });
213 }
214
215 setMailerHeader() {
216 if (!this.message || !this.data.xMailer) {
217 return;
218 }
219 this.message.setHeader('X-Mailer', this.data.xMailer);
220 }
221
222 setPriorityHeaders() {
223 if (!this.message || !this.data.priority) {
224 return;
225 }
226 switch ((this.data.priority || '').toString().toLowerCase()) {
227 case 'high':
228 this.message.setHeader('X-Priority', '1 (Highest)');
229 this.message.setHeader('X-MSMail-Priority', 'High');
230 this.message.setHeader('Importance', 'High');
231 break;
232 case 'low':
233 this.message.setHeader('X-Priority', '5 (Lowest)');
234 this.message.setHeader('X-MSMail-Priority', 'Low');
235 this.message.setHeader('Importance', 'Low');
236 break;
237 default:
238 // do not add anything, since all messages are 'Normal' by default
239 }
240 }
241
242 setListHeaders() {
243 if (!this.message || !this.data.list || typeof this.data.list !== 'object') {
244 return;
245 }
246 // add optional List-* headers
247 if (this.data.list && typeof this.data.list === 'object') {
248 this._getListHeaders(this.data.list).forEach(listHeader => {
249 listHeader.value.forEach(value => {
250 this.message.addHeader(listHeader.key, value);
251 });
252 });
253 }
254 }
255
256 _getListHeaders(listData) {
257 // make sure an url looks like <protocol:url>
258 return Object.keys(listData).map(key => ({
259 key: 'list-' + key.toLowerCase().trim(),
260 value: [].concat(listData[key] || []).map(value => ({
261 prepared: true,
262 foldLines: true,
263 value: []
264 .concat(value || [])
265 .map(value => {
266 if (typeof value === 'string') {
267 value = {
268 url: value
269 };
270 }
271
272 if (value && value.url) {
273 if (key.toLowerCase().trim() === 'id') {
274 // List-ID: "comment" <domain>
275 let comment = value.comment || '';
276 if (mimeFuncs.isPlainText(comment)) {
277 comment = '"' + comment + '"';
278 } else {
279 comment = mimeFuncs.encodeWord(comment);
280 }
281
282 return (value.comment ? comment + ' ' : '') + this._formatListUrl(value.url).replace(/^<[^:]+\/{,2}/, '');
283 }
284
285 // List-*: <http://domain> (comment)
286 let comment = value.comment || '';
287 if (!mimeFuncs.isPlainText(comment)) {
288 comment = mimeFuncs.encodeWord(comment);
289 }
290
291 return this._formatListUrl(value.url) + (value.comment ? ' (' + comment + ')' : '');
292 }
293
294 return '';
295 })
296 .filter(value => value)
297 .join(', ')
298 }))
299 }));
300 }
301
302 _formatListUrl(url) {
303 url = url.replace(/[\s<]+|[\s>]+/g, '');
304 if (/^(https?|mailto|ftp):/.test(url)) {
305 return '<' + url + '>';
306 }
307 if (/^[^@]+@[^@]+$/.test(url)) {
308 return '<mailto:' + url + '>';
309 }
310
311 return '<http://' + url + '>';
312 }
313}
314
315module.exports = MailMessage;
Note: See TracBrowser for help on using the repository browser.