HEX
Server: Microsoft-IIS/8.5
System: Windows NT YDAWBH120 6.3 build 9600 (Windows Server 2012 R2 Standard Edition) AMD64
User: tentjecom_web (0)
PHP: 7.4.14
Disabled: NONE
Upload Files
File: D:/HostingSpaces/RImmers2/portal.photomenu.nl/wwwroot/node_modules/total.js/mail.js
// Copyright 2012-2016 (c) Peter Širka <petersirka@gmail.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

/**
 * @module FrameworkMail
 * @version 2.3.0
 */

'use strict'

const net = require('net');
const tls = require('tls');
const events = require('events');
const fs = require('fs');

const CRLF = '\r\n';
const REG_ESMTP = /\besmtp\b/i;
const REG_STATE = /\d+/;
const EMPTYARRAY = [];

const errors = {
	notvalid: 'E-mail address is not valid',
	resolve: 'Cannot resolve MX of ',
	connection: 'Cannot connect to any SMTP server.'
};

if (!global.framework_utils)
	global.framework_utils = require('./utils');

/**
 * Mailer
 * @class
 * @property {Boolean} debug Debug mode (true/false).
 */
function Mailer() {
	this.debug = false;
	this.Message = Message;
	this.Mail = Message;
	this.connections = {};
}

Mailer.prototype.__proto__ = Object.create(events.EventEmitter.prototype, {
	constructor: {
		value: Mailer,
		enumberable: false
	}
});

/**
 * Create Mail Message
 * @param {String} subject
 * @param {String} body
 * @return {MailMessage}
 */
Mailer.prototype.create = function(subject, body) {
	return new Message(subject, body);
};

/**
 * Message send callback
 * @callback ResolveMxCallback
 * @param {Error} err Error handling.
 * @param {Socket} socket Net socket.
 */

/**
 * Mail Message
 * @param {String} subject
 * @param {String} body
 * @property {String} subject
 * @property {String} body
 */
function Message(subject, body) {
	this.subject = subject || '';
	this.body = body || '';
	this.files;
	this.addressTo = [];
	this.addressReply;
	this.addressCC;
	this.addressBCC;
	this.addressFrom = { name: '', address: '' };
	this.closed = false;
	this.tls = false;
	this.$callback;
	// Supports (but it's hidden):
	// this.headers;
}

Message.prototype.callback = function(fn) {
	this.$callback = fn;
	return this;
}

/**
 * Set sender
 * @param {String} address A valid e-mail address.
 * @param {String} name User name.
 * @return {Message}
 */
Message.prototype.sender = function(address, name) {
	return this.from(address, name);
};

/**
 * Set sender email and name
 * @param {String} address A valid e-mail address.
 * @param {String} name An user name.
 * @return {Message}
 */
Message.prototype.from = function(address, name) {

	if (address[address.length - 1] === '>') {
		var index = address.indexOf('<');
		name = address.substring(0, index - 1);
		address = address.substring(index + 1, address.length - 1);
	}

	var self = this;
	self.addressFrom.name = name || '';
	self.addressFrom.address = address;
	return self;

};

/**
 * Add a recipient
 * @param {String} address A valid e-mail address.
 * @param {String} name An user name (optional).
 * @param {Boolean} clear Clear all "to" address (optional, default: false).
 * @return {Message}
 */
Message.prototype.to = function(address, name, clear) {

	if (typeof(name) === 'boolean') {
		clear = name;
		name = undefined;
	}

	if (address[address.length - 1] === '>') {
		var index = address.indexOf('<');
		name = address.substring(0, index - 1);
		address = address.substring(index + 1, address.length - 1);
	}

	var self = this;

	if (clear)
		self.addressTo = [];

	if (name)
		self.addressTo.push({ email: address, name: name });
	else
		self.addressTo.push(address);

	return self;
};

/**
 * Add a CC recipient
 * @param {String} address A valid e-mail address.
 * @param {String} name An user name (optional).
 * @param {Boolean} clear Clear all "cc" address (optional, default: false).
 * @return {Message}
 */
Message.prototype.cc = function(address, name, clear) {

	if (typeof(name) === 'boolean') {
		clear = name;
		name = undefined;
	}

	if (address[address.length - 1] === '>') {
		var index = address.indexOf('<');
		name = address.substring(0, index - 1);
		address = address.substring(index + 1, address.length - 1);
	}

	var self = this;

	if (clear || !self.addressCC)
		self.addressCC = [];

	if (name)
		self.addressCC.push({ email: address, name: name });
	else
		self.addressCC.push(address);

	return self;
};

/**
 * Add a BCC recipient
 * @param  {String} address A valid e-mail address.
 * @param {Boolean} clear Clear all "bcc" address (optional, default: false).
 * @return {Message}
 */
Message.prototype.bcc = function(address, clear) {

	var self = this;

	if (clear || !self.addressBCC)
		self.addressBCC = [];

	self.addressBCC.push(address);
	return self;
};

/**
 * Add a reply to address
 * @param {String} address A valid e-mail address.
 * @param {String} name Optional, a custom attachment name.
 * @return {Message}
 */
Message.prototype.reply = function(address, clear) {

	var self = this;

	if (clear || !self.addressReply)
		self.addressReply = [];

	self.addressReply.push(address);
	return self;
};

/**
 * Add an attachment
 * @param {String} filename Filename with extension.
 * @return {Message}
 */
Message.prototype.attachment = function(filename, name) {
	var self = this;
	if (!name)
		name = framework_utils.getName(filename);
	var extension = framework_utils.getExtension(name);
	if (!self.files)
		self.files = [];
	self.files.push({ name: name, filename: filename, contentType: framework_utils.getContentType(extension), extension: extension });
	return self;
};

/**
 * Clears a timeout for sending emails (if the email is sent through the F.onMail)
 * @return {Message}
 */
Message.prototype.manually = function() {
	var self = this;
	self.$sending && clearTimeout(self.$sending);
	return self;
};

/**
 * Adds an inline attachment.
 * Inline attachments are exactly like normal attachments except that they are represented with the 'Content-ID' (cid)
 * which can be referenced in the email's html body. For example an inline attachments (image) with a contentId of 'AB435BH'
 * can be used inside the html body as "<img src='cid:AB435BH'>". An enabled web client then can render and show the embedded image.
 *
 * @param {String} filename Filename with extension (e.g. '/local/path/123.jpg')
 * @param {String} name the optional filename (e.g. '123.jpg')
 * @param {String} contentId the Content-ID (e.g. 'AB435BH'), must be unique across the email
 * @returns {Message}
 */
Message.prototype.attachmentInline = function(filename, name, contentId) {
	var self = this;
	if (!name)
		name = framework_utils.getName(filename);
	if (!self.files)
		self.files = [];
	var extension = framework_utils.getExtension(name);
	self.files.push({ name: name, filename: filename, contentType: framework_utils.getContentType(extension), disposition: 'inline', contentId: contentId, extension: extension });
	return self;
};

/**
 * Send e-mail
 * @param {String} smtp SMTP server / hostname.
 * @param {Object} options Options (optional).
 * @param {Function(err)} fnCallback
 * @return {Message}
 */
Message.prototype.send = function(smtp, options, callback) {
	mailer.send(smtp, options, this, callback);
	return this;
};

Mailer.prototype.switchToTLS = function(obj, options) {

	var self = this;

	obj.tls = true;
	obj.socket.removeAllListeners();
    // obj.socket.removeAllListeners('data');
    // obj.socket.removeAllListeners('error');
    // obj.socket.removeAllListeners('clientError');
    // obj.socket.removeAllListeners('line');

	var opt = framework_utils.copy(options.tls, { socket: obj.socket, host: obj.socket.$host, ciphers: 'SSLv3' });
	obj.socket2 = tls.connect(opt, () => self._send(obj, options, true));

	obj.socket2.on('error', function(err) {

		mailer.destroy(obj);
		self.closed = true;
		self.callback && self.callback(err);
		self.callback = null;

		if (obj.try || err.stack.indexOf('ECONNRESET') !== -1)
			return;

		try {
			mailer.emit('error', err, obj);
		} catch(e) {
			F.error(err, 'FrameworkMail');
		}
	});

	obj.socket2.on('clientError', function(err) {
		mailer.destroy(obj);
		self.callback && self.callback(err);
		self.callback = null;

		if (obj.try)
			return;

		try {
			mailer.emit('error', err, obj);
		} catch(e) {
			F.error(err, 'FrameworkMail');
		}
	});

	obj.socket2.on('connect', () => !options.secure && self._send(obj, options));
};

Mailer.prototype.destroy = function(obj) {

	if (obj.destroyed)
		return this;

	obj.destroyed = true;
	obj.closed = true;

	if (obj.socket) {
		obj.socket.removeAllListeners();
		obj.socket.end();
		obj.socket.destroy();
		obj.socket = null;
	}

	if (obj.socket2) {
		obj.socket2.removeAllListeners();
		obj.socket2.end();
		obj.socket2.destroy();
		obj.socket2 = null;
	}

	delete this.connections[obj.id];
	return this;
};

/**
 * Internal: Write attachment into the current socket
 * @param  {Function} write  Write function.
 * @param  {String} boundary Boundary.
 * @param  {Socket} socket   Current socket.
 */
Mailer.prototype._writeattachment = function(obj) {

	var attachment = obj.files ? obj.files.shift() : false;
	if (!attachment) {
		mailer._writeline(obj, '--' + obj.boundary + '--', '', '.');
		return this;
	}

	var name = attachment.name;
	var stream = fs.createReadStream(attachment.filename, { encoding: 'base64' });
	var message = [];
	var extension = attachment.extension;
	var isCalendar = extension === 'ics';

	message.push('--' + obj.boundary);

	if (!isCalendar) {
		if (attachment.contentId) {
			message.push('Content-Disposition: inline; filename="' + name + '"');
			message.push('Content-ID: <' + attachment.contentId + '>');
		} else
			message.push('Content-Disposition: attachment; filename="' + name + '"');
	}

	message.push('Content-Type: ' + extension + ';' + (isCalendar ? ' charset="utf-8"; method=REQUEST' : ''));
	message.push('Content-Transfer-Encoding: base64');
	message.push(CRLF);
	mailer._writeline(obj, message.join(CRLF));

	stream.on('data', function(buf) {

		var length = buf.length;
		var count = 0;
		var beg = 0;

		while (count < length) {

			count += 68;
			if (count > length)
				count = length;

			mailer._writeline(obj, buf.slice(beg, count).toString('base64'));
			beg = count;
		}
	});

	CLEANUP(stream, function() {
		mailer._writeline(obj, CRLF);
		mailer._writeattachment(obj);
	});

	return this;
};

Mailer.prototype.try = function(smtp, options, callback) {
	return this.send(smtp, options, undefined, callback);
};

Mailer.prototype.send2 = function(messages, callback) {

	var opt = framework.temporary['mail-settings'];

	if (!opt) {
		var config = framework.config['mail.smtp.options'];
		if (config) {
			if (typeof(config) === 'object')
				opt = config;
			else
				opt = config.toString().parseJSON();
		}

		if (!opt)
			opt = {};

		framework.temporary['mail-settings'] = opt;
	}

	return this.send(framework.config['mail.smtp'], opt, messages, callback);
};

Mailer.prototype.send = function(smtp, options, messages, callback) {

	if (options instanceof Array) {
		callback = messages;
		messages = options;
		options = {};
	} else if (typeof(options) === 'function') {
		callback = options;
		options = {};
	}

	var self = this;
	var id = framework_utils.GUID(10);

	self.connections[id] = {};
	var obj = self.connections[id];

    obj.id = id;
	obj.try = messages === undefined;
	obj.messages = obj.try ? EMPTYARRAY : messages instanceof Array ? messages : [messages];
	obj.callback = callback;
	obj.closed = false;
	obj.message = null;
	obj.files = null;
	obj.count = 0;
	obj.socket;
	obj.tls = false;
	obj.date = new Date();

	smtp = smtp || null;

	if (options && options.secure && !options.port)
		options.port = 465;

	options = framework_utils.copy(options, { secure: false, port: 25, user: '', password: '', timeout: 10000, tls: null });

	if (options.secure) {
		var internal = framework_utils.copy(options);
		internal.host = smtp;
		obj.socket = tls.connect(internal, () => mailer._send(obj, options));
	} else
		obj.socket = net.createConnection(options.port, smtp);

	obj.socket.$host = smtp;
	obj.host = smtp.substring(smtp.lastIndexOf('.', smtp.lastIndexOf('.') - 1) + 1);
	obj.socket.on('error', function(err) {

		mailer.destroy(obj);
		obj.callback && obj.callback(err);
		obj.callback = null;

		if (obj.try || err.stack.indexOf('ECONNRESET') !== -1)
			return;

		try {
			mailer.emit('error', err, obj);
		} catch(e) {
			F.error(err, 'FrameworkMail');
		}
	});

	obj.socket.on('clientError', function(err) {

		mailer.destroy(obj);
		obj.callback && obj.callback(err);
		obj.callback = null;

		if (obj.try)
			return;

		try {
			mailer.emit('error', err, obj);
		} catch(e) {
			F.error(err, 'FrameworkMail');
		}
	});

	obj.socket.on('connect', () => !options.secure && mailer._send(obj, options));
	return self;
};

Mailer.prototype._writemessage = function(obj, buffer) {

	var self = this;
	var msg = obj.messages.shift();
	var message = [];

	obj.boundary = '--totaljs' + obj.date.getTime() + obj.count;
	obj.files = msg.files;
	obj.count++;

	buffer.push('MAIL FROM: <' + msg.addressFrom.address + '>');
	message.push('Message-ID: <' + framework_utils.GUID() + '@WIN-' + framework_utils.GUID(4) + '>');
	message.push('MIME-Version: 1.0');
	message.push('From: ' + (msg.addressFrom.name ? unicode_encode(msg.addressFrom.name) + ' <' + msg.addressFrom.address + '>' : msg.addressFrom.address));

	var length;

	if (msg.headers) {
		var headers = Object.keys(msg.headers);
		for (var i = 0, length = headers.length; i < length; i++)
			message.push(headers[i] + ': ' + msg.headers[headers[i]]);
	}

	length = msg.addressTo.length;

	var builder = '';
	var mail;
	var item;

	if (length) {
		for (var i = 0; i < length; i++) {
			item = msg.addressTo[i];
			if (item instanceof Object)
				mail = '<' + item.email + '>';
			else
				mail = '<' + item + '>';
			buffer.push('RCPT TO: ' + mail);
			builder += (builder ? ', ' : '') + (item instanceof Object ? unicode_encode(item.name) + ' ' : '') + mail;
		}
		message.push('To: ' + builder);
		builder = '';
	}


	if (msg.addressCC) {
		length = msg.addressCC.length;
		for (var i = 0; i < length; i++) {
			item = msg.addressCC[i];
			if (item instanceof Object)
				mail = '<' + item.email  + '>';
			else
				mail = '<' + item + '>';
			buffer.push('RCPT TO: ' + mail);
			builder += (builder ? ', ' : '') + (item instanceof Object ? unicode_encode(item.name) + ' ' : '') + mail;
		}
		message.push('Cc: ' + builder);
		builder = '';
	}

	if (msg.addressBCC) {
		length = msg.addressBCC.length;
		for (var i = 0; i < length; i++)
			buffer.push('RCPT TO: <' + msg.addressBCC[i] + '>');
	}

	buffer.push('DATA');
	// buffer.push('QUIT');
	buffer.push('');

	message.push('Date: ' + obj.date.toUTCString());
	message.push('Subject: ' + unicode_encode(msg.subject));

	if (msg.addressReply) {
		length = msg.addressReply.length;
		for (var i = 0; i < length; i++)
			builder += (builder !== '' ? ', ' : '') + '<' + msg.addressReply[i] + '>';
		message.push('Reply-To: ' + builder);
		builder = '';
	}

	message.push('Content-Type: multipart/mixed; boundary=' + obj.boundary);
	message.push('');
	message.push('--' + obj.boundary);
	message.push('Content-Type: ' + (msg.body.indexOf('<') !== -1 && msg.body.lastIndexOf('>') !== -1 ? 'text/html' : 'text/plain') + '; charset=utf-8');
	message.push('Content-Transfer-Encoding: base64');
	message.push('');
	message.push(prepareBASE64(new Buffer(msg.body.replace(/\r\n/g, '\n').replace(/\n/g, CRLF)).toString('base64')));

	obj.message = message.join(CRLF);
	obj.messagecallback = msg.$callback;

	message = null;
	return self;
};

Mailer.prototype._writeline = function(obj) {

	if (obj.closed)
		return false;

	var socket = obj.socket2 ? obj.socket2 : obj.socket;

	for (var i = 1; i < arguments.length; i++) {
		var line = arguments[i];
		if (!line)
			continue;
		if (mailer.debug)
			console.log('SEND', line);
		socket.write(line + CRLF);
	}

	return true;
};

Mailer.prototype._send = function(obj, options, autosend) {

	var self = this;
	var buffer = [];
	var date = new Date();
	var boundary = '--totaljs' + date.getTime();
	var isAuthenticated = false;
	var isAuthorization = false;
	var authType = '';
	var command = '';
	var auth = [];
	var ending = null;
	var response = '';
	var socket = obj.socket2 ? obj.socket2 : obj.socket;
	var host = obj.host;

	var isAttach = !options.tls || (obj.tls && options.tls);

	isAttach && mailer.emit('send', obj);
	socket.setEncoding('utf8');
	socket.setTimeout(options.timeout || 8000, function() {

		var err = new Error(framework_utils.httpStatus(408));

		mailer.destroy(obj);
		obj.callback && obj.callback(err);
		obj.callback = null;

		if (obj.try)
			return;

		try {
			mailer.emit('error', err, obj);
		} catch(e) {
			F.error(err, 'FrameworkMail');
		}
	});

	socket.on('end', function() {
		mailer.destroy(obj);
		obj.callback && obj.callback();
		obj.callback = null;
	});

	socket.on('data', function(data) {

		if (obj.closed)
			return;

		data = data.toString('utf8');

		if (!data.endsWith(CRLF)) {
			response += data;
			return;
		}

		var res = (response + data).split(CRLF);

		if (response)
			response = '';

		for (var i = 0, length = res.length; i < length; i++) {
			var line = res[i];
			if (line && socket)
				socket.emit('line', line);
		}
	});

	socket.on('line', function(line) {

		line = line.toUpperCase();

		if (mailer.debug)
			console.log('<---', line);

		var code = +line.match(REG_STATE)[0];

		if (code === 250 && !isAuthorization) {
			if ((line.indexOf('AUTH LOGIN PLAIN') !== -1 || line.indexOf('AUTH PLAIN LOGIN') !== -1) || (options.user && options.password)) {
				authType = 'plain';
				isAuthorization = true;
				if (line.indexOf('XOAUTH') === -1) {
					auth.push('AUTH LOGIN');
					auth.push(new Buffer(options.user).toString('base64'));
					auth.push(new Buffer(options.password).toString('base64'));
				} else
					auth.push('AUTH PLAIN ' + new Buffer('\0'+ options.user + '\0' + options.password).toString('base64'));
			}
		}

		// help
		if (line.substring(3, 4) === '-')
			return;

		if (!isAuthenticated && isAuthorization) {
			isAuthenticated = true;
			code = 334;
		}

		switch (code) {
			case 220:

				if (obj.isTLS) {
					mailer.switchToTLS(obj, options);
					return;
				}

				command = obj.isTLS || (options.user && options.password) || REG_ESMTP.test(line) ? 'EHLO' : 'HELO';
				mailer._writeline(obj, command + ' ' + host);
				break;

			case 250: // OPERATION
			case 251: // FORWARD
			case 235: // VERIFY
			case 999: // total.js again

				obj.messagecallback && obj.messagecallback();
				obj.messagecallback = null;
				mailer._writeline(obj, buffer.shift());

				if (buffer.length)
					return;

				// NEW MESSAGE
				if (obj.messages.length) {
					mailer._writemessage(obj, buffer);
					mailer._writeline(obj, buffer.shift());
					return;
				}

				// end
				mailer._writeline(obj, 'QUIT');
				return;

			case 221: // BYE
				mailer.destroy(obj);
				obj.callback && obj.callback(null, obj.try ? true : obj.count);
				obj.callback = null;
				return;

			case 334: // LOGIN

				if (!self.tls && !obj.isTLS && options.tls) {
					obj.isTLS = true;
					mailer._writeline(obj, 'STARTTLS');
					return;
				}

				var value = auth.shift();
				if (!value) {

					var err = new Error('Forbidden.');

					mailer.destroy(obj);
					obj.callback && obj.callback(err);
					obj.callback = null;

					if (obj.try)
						return;

					try {
						mailer.emit('error', err, obj);
					} catch(e) {
						F.error(err, 'FrameworkMail');
					}

					return;
				}

				mailer._writeline(obj, value);
				return;

			case 354:
				mailer._writeline(obj, obj.message);
				mailer._writeattachment(obj);
				obj.message = null;
				return;

			default:

				if (code < 400)
					return;

				var err = new Error(line);

				if (!obj.try) {
					try {
						mailer.emit('error', err, obj);
					} catch(e) {
						F.error(err, 'FrameworkMail');
					}
				}

				obj.messagecallback && obj.messagecallback(err);
				obj.messagecallback = null;

				if (obj.message) {
					// a problem
					buffer = [];
					obj.count--;
					socket.emit('line', '999 TRY NEXT MESSAGE');
					return;
				}

				mailer.destroy(obj);
				obj.callback && obj.callback(err);
				obj.callback = null;
				return;
		}
	});

	autosend && self._writeline(obj, 'EHLO ' + host);
};

Mailer.prototype.restart = function() {
	var self = this;
	self.removeAllListeners();
	self.debug = false;
};

/**
 * Split Base64 to lines with 68 characters
 * @private
 * @param  {String} value Base64 message.
 * @return {String}
 */
function prepareBASE64(value) {

	var index = 0;
	var output = '';
	var length = value.length;

	while (index < length) {
		var max = index + 68;
		if (max > length)
			max = length;
		output += value.substring(index, max) + CRLF;
		index = max;
	}

	return output;
}

function unicode_encode(val) {
	return val ? '=?utf-8?B?' + new Buffer(val.toString()).toString('base64') + '?=' : '';
}

// ======================================================
// EXPORTS
// ======================================================

var mailer = new Mailer();
module.exports = mailer;