source: node_modules/nodemailer/lib/mailer/index.js@ 62b2964

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

Initial commit

  • Property mode set to 100644
File size: 14.3 KB
Line 
1'use strict';
2
3const EventEmitter = require('events');
4const shared = require('../shared');
5const mimeTypes = require('../mime-funcs/mime-types');
6const MailComposer = require('../mail-composer');
7const DKIM = require('../dkim');
8const httpProxyClient = require('../smtp-connection/http-proxy-client');
9const util = require('util');
10const urllib = require('url');
11const packageData = require('../../package.json');
12const MailMessage = require('./mail-message');
13const net = require('net');
14const dns = require('dns');
15const crypto = require('crypto');
16
17/**
18 * Creates an object for exposing the Mail API
19 *
20 * @constructor
21 * @param {Object} transporter Transport object instance to pass the mails to
22 */
23class Mail extends EventEmitter {
24 constructor(transporter, options, defaults) {
25 super();
26
27 this.options = options || {};
28 this._defaults = defaults || {};
29
30 this._defaultPlugins = {
31 compile: [(...args) => this._convertDataImages(...args)],
32 stream: []
33 };
34
35 this._userPlugins = {
36 compile: [],
37 stream: []
38 };
39
40 this.meta = new Map();
41
42 this.dkim = this.options.dkim ? new DKIM(this.options.dkim) : false;
43
44 this.transporter = transporter;
45 this.transporter.mailer = this;
46
47 this.logger = shared.getLogger(this.options, {
48 component: this.options.component || 'mail'
49 });
50
51 this.logger.debug(
52 {
53 tnx: 'create'
54 },
55 'Creating transport: %s',
56 this.getVersionString()
57 );
58
59 // setup emit handlers for the transporter
60 if (typeof this.transporter.on === 'function') {
61 // deprecated log interface
62 this.transporter.on('log', log => {
63 this.logger.debug(
64 {
65 tnx: 'transport'
66 },
67 '%s: %s',
68 log.type,
69 log.message
70 );
71 });
72
73 // transporter errors
74 this.transporter.on('error', err => {
75 this.logger.error(
76 {
77 err,
78 tnx: 'transport'
79 },
80 'Transport Error: %s',
81 err.message
82 );
83 this.emit('error', err);
84 });
85
86 // indicates if the sender has became idle
87 this.transporter.on('idle', (...args) => {
88 this.emit('idle', ...args);
89 });
90 }
91
92 /**
93 * Optional methods passed to the underlying transport object
94 */
95 ['close', 'isIdle', 'verify'].forEach(method => {
96 this[method] = (...args) => {
97 if (typeof this.transporter[method] === 'function') {
98 if (method === 'verify' && typeof this.getSocket === 'function') {
99 this.transporter.getSocket = this.getSocket;
100 this.getSocket = false;
101 }
102 return this.transporter[method](...args);
103 } else {
104 this.logger.warn(
105 {
106 tnx: 'transport',
107 methodName: method
108 },
109 'Non existing method %s called for transport',
110 method
111 );
112 return false;
113 }
114 };
115 });
116
117 // setup proxy handling
118 if (this.options.proxy && typeof this.options.proxy === 'string') {
119 this.setupProxy(this.options.proxy);
120 }
121 }
122
123 use(step, plugin) {
124 step = (step || '').toString();
125 if (!this._userPlugins.hasOwnProperty(step)) {
126 this._userPlugins[step] = [plugin];
127 } else {
128 this._userPlugins[step].push(plugin);
129 }
130
131 return this;
132 }
133
134 /**
135 * Sends an email using the preselected transport object
136 *
137 * @param {Object} data E-data description
138 * @param {Function?} callback Callback to run once the sending succeeded or failed
139 */
140 sendMail(data, callback = null) {
141 let promise;
142
143 if (!callback) {
144 promise = new Promise((resolve, reject) => {
145 callback = shared.callbackPromise(resolve, reject);
146 });
147 }
148
149 if (typeof this.getSocket === 'function') {
150 this.transporter.getSocket = this.getSocket;
151 this.getSocket = false;
152 }
153
154 let mail = new MailMessage(this, data);
155
156 this.logger.debug(
157 {
158 tnx: 'transport',
159 name: this.transporter.name,
160 version: this.transporter.version,
161 action: 'send'
162 },
163 'Sending mail using %s/%s',
164 this.transporter.name,
165 this.transporter.version
166 );
167
168 this._processPlugins('compile', mail, err => {
169 if (err) {
170 this.logger.error(
171 {
172 err,
173 tnx: 'plugin',
174 action: 'compile'
175 },
176 'PluginCompile Error: %s',
177 err.message
178 );
179 return callback(err);
180 }
181
182 mail.message = new MailComposer(mail.data).compile();
183
184 mail.setMailerHeader();
185 mail.setPriorityHeaders();
186 mail.setListHeaders();
187
188 this._processPlugins('stream', mail, err => {
189 if (err) {
190 this.logger.error(
191 {
192 err,
193 tnx: 'plugin',
194 action: 'stream'
195 },
196 'PluginStream Error: %s',
197 err.message
198 );
199 return callback(err);
200 }
201
202 if (mail.data.dkim || this.dkim) {
203 mail.message.processFunc(input => {
204 let dkim = mail.data.dkim ? new DKIM(mail.data.dkim) : this.dkim;
205 this.logger.debug(
206 {
207 tnx: 'DKIM',
208 messageId: mail.message.messageId(),
209 dkimDomains: dkim.keys.map(key => key.keySelector + '.' + key.domainName).join(', ')
210 },
211 'Signing outgoing message with %s keys',
212 dkim.keys.length
213 );
214 return dkim.sign(input, mail.data._dkim);
215 });
216 }
217
218 this.transporter.send(mail, (...args) => {
219 if (args[0]) {
220 this.logger.error(
221 {
222 err: args[0],
223 tnx: 'transport',
224 action: 'send'
225 },
226 'Send Error: %s',
227 args[0].message
228 );
229 }
230 callback(...args);
231 });
232 });
233 });
234
235 return promise;
236 }
237
238 getVersionString() {
239 return util.format('%s (%s; +%s; %s/%s)', packageData.name, packageData.version, packageData.homepage, this.transporter.name, this.transporter.version);
240 }
241
242 _processPlugins(step, mail, callback) {
243 step = (step || '').toString();
244
245 if (!this._userPlugins.hasOwnProperty(step)) {
246 return callback();
247 }
248
249 let userPlugins = this._userPlugins[step] || [];
250 let defaultPlugins = this._defaultPlugins[step] || [];
251
252 if (userPlugins.length) {
253 this.logger.debug(
254 {
255 tnx: 'transaction',
256 pluginCount: userPlugins.length,
257 step
258 },
259 'Using %s plugins for %s',
260 userPlugins.length,
261 step
262 );
263 }
264
265 if (userPlugins.length + defaultPlugins.length === 0) {
266 return callback();
267 }
268
269 let pos = 0;
270 let block = 'default';
271 let processPlugins = () => {
272 let curplugins = block === 'default' ? defaultPlugins : userPlugins;
273 if (pos >= curplugins.length) {
274 if (block === 'default' && userPlugins.length) {
275 block = 'user';
276 pos = 0;
277 curplugins = userPlugins;
278 } else {
279 return callback();
280 }
281 }
282 let plugin = curplugins[pos++];
283 plugin(mail, err => {
284 if (err) {
285 return callback(err);
286 }
287 processPlugins();
288 });
289 };
290
291 processPlugins();
292 }
293
294 /**
295 * Sets up proxy handler for a Nodemailer object
296 *
297 * @param {String} proxyUrl Proxy configuration url
298 */
299 setupProxy(proxyUrl) {
300 let proxy = urllib.parse(proxyUrl);
301
302 // setup socket handler for the mailer object
303 this.getSocket = (options, callback) => {
304 let protocol = proxy.protocol.replace(/:$/, '').toLowerCase();
305
306 if (this.meta.has('proxy_handler_' + protocol)) {
307 return this.meta.get('proxy_handler_' + protocol)(proxy, options, callback);
308 }
309
310 switch (protocol) {
311 // Connect using a HTTP CONNECT method
312 case 'http':
313 case 'https':
314 httpProxyClient(proxy.href, options.port, options.host, (err, socket) => {
315 if (err) {
316 return callback(err);
317 }
318 return callback(null, {
319 connection: socket
320 });
321 });
322 return;
323 case 'socks':
324 case 'socks5':
325 case 'socks4':
326 case 'socks4a': {
327 if (!this.meta.has('proxy_socks_module')) {
328 return callback(new Error('Socks module not loaded'));
329 }
330 let connect = ipaddress => {
331 let proxyV2 = !!this.meta.get('proxy_socks_module').SocksClient;
332 let socksClient = proxyV2 ? this.meta.get('proxy_socks_module').SocksClient : this.meta.get('proxy_socks_module');
333 let proxyType = Number(proxy.protocol.replace(/\D/g, '')) || 5;
334 let connectionOpts = {
335 proxy: {
336 ipaddress,
337 port: Number(proxy.port),
338 type: proxyType
339 },
340 [proxyV2 ? 'destination' : 'target']: {
341 host: options.host,
342 port: options.port
343 },
344 command: 'connect'
345 };
346
347 if (proxy.auth) {
348 let username = decodeURIComponent(proxy.auth.split(':').shift());
349 let password = decodeURIComponent(proxy.auth.split(':').pop());
350 if (proxyV2) {
351 connectionOpts.proxy.userId = username;
352 connectionOpts.proxy.password = password;
353 } else if (proxyType === 4) {
354 connectionOpts.userid = username;
355 } else {
356 connectionOpts.authentication = {
357 username,
358 password
359 };
360 }
361 }
362
363 socksClient.createConnection(connectionOpts, (err, info) => {
364 if (err) {
365 return callback(err);
366 }
367 return callback(null, {
368 connection: info.socket || info
369 });
370 });
371 };
372
373 if (net.isIP(proxy.hostname)) {
374 return connect(proxy.hostname);
375 }
376
377 return dns.resolve(proxy.hostname, (err, address) => {
378 if (err) {
379 return callback(err);
380 }
381 connect(Array.isArray(address) ? address[0] : address);
382 });
383 }
384 }
385 callback(new Error('Unknown proxy configuration'));
386 };
387 }
388
389 _convertDataImages(mail, callback) {
390 if ((!this.options.attachDataUrls && !mail.data.attachDataUrls) || !mail.data.html) {
391 return callback();
392 }
393 mail.resolveContent(mail.data, 'html', (err, html) => {
394 if (err) {
395 return callback(err);
396 }
397 let cidCounter = 0;
398 html = (html || '')
399 .toString()
400 .replace(/(<img\b[^<>]{0,1024} src\s{0,20}=[\s"']{0,20})(data:([^;]+);[^"'>\s]+)/gi, (match, prefix, dataUri, mimeType) => {
401 let cid = crypto.randomBytes(10).toString('hex') + '@localhost';
402 if (!mail.data.attachments) {
403 mail.data.attachments = [];
404 }
405 if (!Array.isArray(mail.data.attachments)) {
406 mail.data.attachments = [].concat(mail.data.attachments || []);
407 }
408 mail.data.attachments.push({
409 path: dataUri,
410 cid,
411 filename: 'image-' + ++cidCounter + '.' + mimeTypes.detectExtension(mimeType)
412 });
413 return prefix + 'cid:' + cid;
414 });
415 mail.data.html = html;
416 callback();
417 });
418 }
419
420 set(key, value) {
421 return this.meta.set(key, value);
422 }
423
424 get(key) {
425 return this.meta.get(key);
426 }
427}
428
429module.exports = Mail;
Note: See TracBrowser for help on using the repository browser.