UNPKG

sgapps-server

Version:
444 lines (379 loc) 10.6 kB
//@ts-nocheck // - node-email Copyright Aaron Heckmann <aaron.heckmann+github@gmail.com> (MIT Licensed) /** * Module dependencies. */ var _childProcess = require('child_process'); /** * Generates a boundry string. * @return {String} */ var boundryidx = 0; function genBoundry () { return 'part_' + Date.now() + "_" + boundryidx++; } /** * Email : Sends email using the sendmail command. * * Note: sendmail must be installed: see http://www.sendmail.org/ * * @param {Object} config - optional configuration object * - to {array|string} Email address(es) to which this msg will be sent * - from {string} Email address from which this msg is sent. If not set * defaults to the `exports.from` global setting. * - replyTo {string} Email address to which replies will be sent. If not * set defaults to `from` * - cc {array|string} Email address(es) who receive a copy * - bcc {array|string} Email address(es) who receive a blind copy * - subject {string} The subject of the email * - body {string} The message of the email * - bodyType {string} Content type of body. Only valid option is * 'html' (for now). Defaults to text/plain. * - altText {string} If `bodyType` is set to 'html', this will be sent * as the alternative text. * - timeout {number} Duration in milliseconds to wait before killing the * process. If not set, defaults to `exports.timeout` global setting. * - path {string} Optional path to the sendmail executable. * * Global settings * - exports.timeout {number} Duration in milliseconds to wait before * killing the process. Defaults to 3000. Used when `timeout` is not set * on a message. * - exports.from {string} Email address from which messages are sent. Used * when `from` was not set on a message. * * Example: * var Email = require('path/to/email').Email * var myMsg = new Email( * { from: 'me@example.com' * , to: 'you@example.com' * , subject: 'Knock knock...' * , body: "Who's there?" * }) * myMsg.send(function(err){ * ... * }) */ function Email (config) { config = config || {}; ; ['to' ,'from' ,'cc' ,'bcc' ,'replyTo' ,'subject' ,'body' ,'bodyType' ,'altText' ,'debug' ,'timeout' ].forEach(function (key) { this[key] = config[key]; }, this); this.path = config.path || "sendmail"; } Email.prototype = { send: function (cb) { var callback = (function () { var sent = false; return function (err) { if (sent) return; sent = true; cb(err); }; })(); var _this = this; _this.valid(function (err) { if (err) { return callback(err); } var args = ['-i', '-t']; if (_this.debug) { args.push('-v'); } var sendmail = _childProcess.spawn( _this.path, args, Object.assign( {}, _this.options, { stdio: 'pipe', detached: false, shell: false } ) ); sendmail.on('error', function(err) { if (_this.debug) { console.error('Sendmail Error: ', err); } callback(err); }); sendmail.on('exit', function(code) { var err = null; if (code !== 0) { err = new Error("Sendmail exited with code: " + code); } if (callback) { callback(err); } }); var _err = null; ['stdout', 'stdin', 'stderr'].forEach(function (item) { if ( (item in sendmail) && sendmail[item] && typeof(sendmail[item].on) === "function" ) { sendmail[item].on('error', function (err) { _err = err; }); } }); if (_this.debug) { if (sendmail.stdout) { sendmail.stdout.on('data', function (chunk) { process.stdout.write('\033[0mmail:: >> \033[36m' + chunk.toString() + '\033[0m'); }); } if (sendmail.stdin) { sendmail.stdin.on('data', function (chunk) { process.stdout.write('\033[0mmail:: << \033[36m' + chunk.toString() + '\033[0m'); }); } if (sendmail.stderr) { sendmail.stderr.on('data', function (chunk) { process.stdout.write('\033[0mmail:: ?> \033[36m' + chunk.toString() + '\033[0m'); }); } } if (!sendmail.stdin) { callback(_err || Error('incorrent sendmail process stdin')); } else { sendmail.stdin.end(_this.msg); } sendmail.on('close', function () { callback(_err); }); }); }, get options () { return { timeout: this.timeout || exports.timeout }; }, get msg () { var msg = new Msg(), boundry = genBoundry() to = formatAddress(this.to), cc = formatAddress(this.cc), bcc = formatAddress(this.bcc), html = this.bodyType && 'html' === this.bodyType.toLowerCase(), plaintext = !html ? this.body : this.altText ? this.altText : ''; msg.line('To: ' + to); msg.line('From: '+ (this.from || exports.from)); msg.line('Reply-To: ' + (this.replyTo || this.from || exports.from)); msg.line('Subject: '+ this.subject); if (cc) msg.line('CC: ' + cc); if (bcc) msg.line('BCC: ' + bcc); msg.line('Mime-Version: 1.0'); msg.line('Content-Type: multipart/alternative; boundary=' + boundry); msg.line(); if (plaintext) { msg.line('--' + boundry); msg.line('Content-Type: text/plain; charset=utf-8'); msg.line('Content-Disposition: inline'); msg.line(); msg.line(plaintext); msg.line(); } if (html) { msg.line('--' + boundry); msg.line('Content-Type: text/html; charset=utf-8'); msg.line('Content-Transfer-Encoding: Base64'); msg.line('Content-Disposition: inline'); msg.line(); msg.line(this.encodedBody); msg.line(); } return msg.toString(); }, get encodedBody () { var encoded = Buffer.from(this.body, 'utf8').toString('base64'), len = encoded.length, size = 100, start = 0, ret = '', chunk; // jshint -W084 while (chunk = encoded.substring(start, start + size > len ? len : start + size)) { ret += chunk + '\n'; start += size; } return ret; }, valid: function (callback) { if (!requiredFieldsExist(this, callback)) return false; if (!fieldsAreClean(this, callback)) return false; var validatedHeaders = ['to','from','cc','bcc','replyTo'] , len = validatedHeaders.length , self = this , addresses , addLen , key; while (len--) { key = validatedHeaders[len]; if (self[key]) { addresses = toArray(self[key]); addLen = addresses.length; while (addLen--) { if (!isValidAddress(addresses[addLen])) { error("invalid email address : " + addresses[addLen], callback); } } } } callback(); return true; } } /** * Email message constructor. * * @return {Msg} */ function Msg () { this.lines = []; } Msg.prototype = { line: function (text) { this.lines.push(text || ''); } , toString: function () { return this.lines.join('\n').replace(/"/g, '\\"'); } } /** * Validation helpers. */ var cleanHeaders = ['to','from','cc','bcc','replyTo','subject'] , injectionrgx = new RegExp(cleanHeaders.join(':|') + ':|content\-type:', 'i'); /** * Determines if any email headers contain vulnerabilities. * * @param {Email} email * @param {Function} callback * @return {Bool} */ function fieldsAreClean (email, callback) { var len = cleanHeaders.length , header , vlen , vals , val; while (len--) { header = cleanHeaders[len]; if (!email[header]) { continue; } vals = toArray(email[header]); vlen = vals.length; while (vlen--) { val = vals[vlen]; if (val) { if (injectionrgx.test(val) || ~val.indexOf("%0a") || ~val.indexOf("%0d")) { return error("Header injection detected in [" + header + "]", callback); } vals[vlen] = val.replace(/\n|\r/ig, ''); } } email[header] = 2 > vals.length ? vals[0] : vals; } return true; } /** * Determines if all required email fields exist. * * @param {Email} email * @param {Function} callback * @return {Bool} */ function requiredFieldsExist (email, callback) { if (!email.from && !exports.from) { return error('from is required', callback); } if (!email.to) { return error('to is required', callback); } if (!email.subject) { return error('subject is required', callback); } return true; } /** * Error helper that throws if no callback is passed. Else * executes the callback passing the err as the first argument. * * @param {String} msg * @param {Function} callback * @return {Bool|undefined} */ function error (msg, callback) { var err = new Error('node-email error: ' + msg); if (callback) { callback(err); return false; } throw err; } /** * Formats an array of addresses as a string. * * @param {Array|String} what * @return {String} */ function formatAddress (what) { return Array.isArray(what) ? what.join(', ') : what; } /** * Converts `what` to an array. * * @param {Mixed} what * @return {Array} */ function toArray (what) { return Array.isArray(what) ? what : [what]; } /** * Email validation regexps. * @see http://fightingforalostcause.net/misc/2006/compare-email-regex.php */ var emailrgx = /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+)*\.(aero|arpa|biz|com|coop|edu|gov|info|int|mil|museum|name|net|org|pro|travel|mobi|[a-zрф]{2,6})|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i; var capturergx = /<([^>].*)>$/; /** * Determines if `rawAddress` is a valid email address. * * @param {String} rawAddress * @return {Bool} */ function isValidAddress (rawAddress) { // john smith <email@domain.com> | email@domain.com var address = capturergx.exec(rawAddress); return address && address[1] ? emailrgx.test(address[1]) : emailrgx.test(rawAddress); } /** * Exports. */ exports.Email = Email; exports.version = '0.2.4'; exports.from = undefined; exports.timeout = 3000; exports.isValidAddress = isValidAddress;