source: node_modules/nodemailer/lib/smtp-transport/index.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: 13.1 KB
Line 
1'use strict';
2
3const EventEmitter = require('events');
4const SMTPConnection = require('../smtp-connection');
5const wellKnown = require('../well-known');
6const shared = require('../shared');
7const XOAuth2 = require('../xoauth2');
8const packageData = require('../../package.json');
9
10/**
11 * Creates a SMTP transport object for Nodemailer
12 *
13 * @constructor
14 * @param {Object} options Connection options
15 */
16class SMTPTransport extends EventEmitter {
17 constructor(options) {
18 super();
19
20 options = options || {};
21
22 if (typeof options === 'string') {
23 options = {
24 url: options
25 };
26 }
27
28 let urlData;
29 let service = options.service;
30
31 if (typeof options.getSocket === 'function') {
32 this.getSocket = options.getSocket;
33 }
34
35 if (options.url) {
36 urlData = shared.parseConnectionUrl(options.url);
37 service = service || urlData.service;
38 }
39
40 this.options = shared.assign(
41 false, // create new object
42 options, // regular options
43 urlData, // url options
44 service && wellKnown(service) // wellknown options
45 );
46
47 this.logger = shared.getLogger(this.options, {
48 component: this.options.component || 'smtp-transport'
49 });
50
51 // temporary object
52 let connection = new SMTPConnection(this.options);
53
54 this.name = 'SMTP';
55 this.version = packageData.version + '[client:' + connection.version + ']';
56
57 if (this.options.auth) {
58 this.auth = this.getAuth({});
59 }
60 }
61
62 /**
63 * Placeholder function for creating proxy sockets. This method immediatelly returns
64 * without a socket
65 *
66 * @param {Object} options Connection options
67 * @param {Function} callback Callback function to run with the socket keys
68 */
69 getSocket(options, callback) {
70 // return immediatelly
71 return setImmediate(() => callback(null, false));
72 }
73
74 getAuth(authOpts) {
75 if (!authOpts) {
76 return this.auth;
77 }
78
79 let hasAuth = false;
80 let authData = {};
81
82 if (this.options.auth && typeof this.options.auth === 'object') {
83 Object.keys(this.options.auth).forEach(key => {
84 hasAuth = true;
85 authData[key] = this.options.auth[key];
86 });
87 }
88
89 if (authOpts && typeof authOpts === 'object') {
90 Object.keys(authOpts).forEach(key => {
91 hasAuth = true;
92 authData[key] = authOpts[key];
93 });
94 }
95
96 if (!hasAuth) {
97 return false;
98 }
99
100 switch ((authData.type || '').toString().toUpperCase()) {
101 case 'OAUTH2': {
102 if (!authData.service && !authData.user) {
103 return false;
104 }
105 let oauth2 = new XOAuth2(authData, this.logger);
106 oauth2.provisionCallback = (this.mailer && this.mailer.get('oauth2_provision_cb')) || oauth2.provisionCallback;
107 oauth2.on('token', token => this.mailer.emit('token', token));
108 oauth2.on('error', err => this.emit('error', err));
109 return {
110 type: 'OAUTH2',
111 user: authData.user,
112 oauth2,
113 method: 'XOAUTH2'
114 };
115 }
116 default:
117 return {
118 type: (authData.type || '').toString().toUpperCase() || 'LOGIN',
119 user: authData.user,
120 credentials: {
121 user: authData.user || '',
122 pass: authData.pass,
123 options: authData.options
124 },
125 method: (authData.method || '').trim().toUpperCase() || this.options.authMethod || false
126 };
127 }
128 }
129
130 /**
131 * Sends an e-mail using the selected settings
132 *
133 * @param {Object} mail Mail object
134 * @param {Function} callback Callback function
135 */
136 send(mail, callback) {
137 this.getSocket(this.options, (err, socketOptions) => {
138 if (err) {
139 return callback(err);
140 }
141
142 let returned = false;
143 let options = this.options;
144 if (socketOptions && socketOptions.connection) {
145 this.logger.info(
146 {
147 tnx: 'proxy',
148 remoteAddress: socketOptions.connection.remoteAddress,
149 remotePort: socketOptions.connection.remotePort,
150 destHost: options.host || '',
151 destPort: options.port || '',
152 action: 'connected'
153 },
154 'Using proxied socket from %s:%s to %s:%s',
155 socketOptions.connection.remoteAddress,
156 socketOptions.connection.remotePort,
157 options.host || '',
158 options.port || ''
159 );
160
161 // only copy options if we need to modify it
162 options = shared.assign(false, options);
163 Object.keys(socketOptions).forEach(key => {
164 options[key] = socketOptions[key];
165 });
166 }
167
168 let connection = new SMTPConnection(options);
169
170 connection.once('error', err => {
171 if (returned) {
172 return;
173 }
174 returned = true;
175 connection.close();
176 return callback(err);
177 });
178
179 connection.once('end', () => {
180 if (returned) {
181 return;
182 }
183
184 let timer = setTimeout(() => {
185 if (returned) {
186 return;
187 }
188 returned = true;
189 // still have not returned, this means we have an unexpected connection close
190 let err = new Error('Unexpected socket close');
191 if (connection && connection._socket && connection._socket.upgrading) {
192 // starttls connection errors
193 err.code = 'ETLS';
194 }
195 callback(err);
196 }, 1000);
197
198 try {
199 timer.unref();
200 } catch (E) {
201 // Ignore. Happens on envs with non-node timer implementation
202 }
203 });
204
205 let sendMessage = () => {
206 let envelope = mail.message.getEnvelope();
207 let messageId = mail.message.messageId();
208
209 let recipients = [].concat(envelope.to || []);
210 if (recipients.length > 3) {
211 recipients.push('...and ' + recipients.splice(2).length + ' more');
212 }
213
214 if (mail.data.dsn) {
215 envelope.dsn = mail.data.dsn;
216 }
217
218 this.logger.info(
219 {
220 tnx: 'send',
221 messageId
222 },
223 'Sending message %s to <%s>',
224 messageId,
225 recipients.join(', ')
226 );
227
228 connection.send(envelope, mail.message.createReadStream(), (err, info) => {
229 returned = true;
230 connection.close();
231 if (err) {
232 this.logger.error(
233 {
234 err,
235 tnx: 'send'
236 },
237 'Send error for %s: %s',
238 messageId,
239 err.message
240 );
241 return callback(err);
242 }
243 info.envelope = {
244 from: envelope.from,
245 to: envelope.to
246 };
247 info.messageId = messageId;
248 try {
249 return callback(null, info);
250 } catch (E) {
251 this.logger.error(
252 {
253 err: E,
254 tnx: 'callback'
255 },
256 'Callback error for %s: %s',
257 messageId,
258 E.message
259 );
260 }
261 });
262 };
263
264 connection.connect(() => {
265 if (returned) {
266 return;
267 }
268
269 let auth = this.getAuth(mail.data.auth);
270
271 if (auth && (connection.allowsAuth || options.forceAuth)) {
272 connection.login(auth, err => {
273 if (auth && auth !== this.auth && auth.oauth2) {
274 auth.oauth2.removeAllListeners();
275 }
276 if (returned) {
277 return;
278 }
279
280 if (err) {
281 returned = true;
282 connection.close();
283 return callback(err);
284 }
285
286 sendMessage();
287 });
288 } else {
289 sendMessage();
290 }
291 });
292 });
293 }
294
295 /**
296 * Verifies SMTP configuration
297 *
298 * @param {Function} callback Callback function
299 */
300 verify(callback) {
301 let promise;
302
303 if (!callback) {
304 promise = new Promise((resolve, reject) => {
305 callback = shared.callbackPromise(resolve, reject);
306 });
307 }
308
309 this.getSocket(this.options, (err, socketOptions) => {
310 if (err) {
311 return callback(err);
312 }
313
314 let options = this.options;
315 if (socketOptions && socketOptions.connection) {
316 this.logger.info(
317 {
318 tnx: 'proxy',
319 remoteAddress: socketOptions.connection.remoteAddress,
320 remotePort: socketOptions.connection.remotePort,
321 destHost: options.host || '',
322 destPort: options.port || '',
323 action: 'connected'
324 },
325 'Using proxied socket from %s:%s to %s:%s',
326 socketOptions.connection.remoteAddress,
327 socketOptions.connection.remotePort,
328 options.host || '',
329 options.port || ''
330 );
331
332 options = shared.assign(false, options);
333 Object.keys(socketOptions).forEach(key => {
334 options[key] = socketOptions[key];
335 });
336 }
337
338 let connection = new SMTPConnection(options);
339 let returned = false;
340
341 connection.once('error', err => {
342 if (returned) {
343 return;
344 }
345 returned = true;
346 connection.close();
347 return callback(err);
348 });
349
350 connection.once('end', () => {
351 if (returned) {
352 return;
353 }
354 returned = true;
355 return callback(new Error('Connection closed'));
356 });
357
358 let finalize = () => {
359 if (returned) {
360 return;
361 }
362 returned = true;
363 connection.quit();
364 return callback(null, true);
365 };
366
367 connection.connect(() => {
368 if (returned) {
369 return;
370 }
371
372 let authData = this.getAuth({});
373
374 if (authData && (connection.allowsAuth || options.forceAuth)) {
375 connection.login(authData, err => {
376 if (returned) {
377 return;
378 }
379
380 if (err) {
381 returned = true;
382 connection.close();
383 return callback(err);
384 }
385
386 finalize();
387 });
388 } else if (!authData && connection.allowsAuth && options.forceAuth) {
389 let err = new Error('Authentication info was not provided');
390 err.code = 'NoAuth';
391
392 returned = true;
393 connection.close();
394 return callback(err);
395 } else {
396 finalize();
397 }
398 });
399 });
400
401 return promise;
402 }
403
404 /**
405 * Releases resources
406 */
407 close() {
408 if (this.auth && this.auth.oauth2) {
409 this.auth.oauth2.removeAllListeners();
410 }
411 this.emit('close');
412 }
413}
414
415// expose to the world
416module.exports = SMTPTransport;
Note: See TracBrowser for help on using the repository browser.