283 lines
7.8 KiB
JavaScript
283 lines
7.8 KiB
JavaScript
|
'use strict';
|
||
|
|
||
|
var urllib = require('url');
|
||
|
var util = require('util');
|
||
|
var fs = require('fs');
|
||
|
var fetch = require('nodemailer-fetch');
|
||
|
|
||
|
/**
|
||
|
* Parses connection url to a structured configuration object
|
||
|
*
|
||
|
* @param {String} str Connection url
|
||
|
* @return {Object} Configuration object
|
||
|
*/
|
||
|
module.exports.parseConnectionUrl = function (str) {
|
||
|
str = str || '';
|
||
|
var options = {};
|
||
|
|
||
|
[urllib.parse(str, true)].forEach(function (url) {
|
||
|
var auth;
|
||
|
|
||
|
switch (url.protocol) {
|
||
|
case 'smtp:':
|
||
|
options.secure = false;
|
||
|
break;
|
||
|
case 'smtps:':
|
||
|
options.secure = true;
|
||
|
break;
|
||
|
case 'direct:':
|
||
|
options.direct = true;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (!isNaN(url.port) && Number(url.port)) {
|
||
|
options.port = Number(url.port);
|
||
|
}
|
||
|
|
||
|
if (url.hostname) {
|
||
|
options.host = url.hostname;
|
||
|
}
|
||
|
|
||
|
if (url.auth) {
|
||
|
auth = url.auth.split(':');
|
||
|
|
||
|
if (!options.auth) {
|
||
|
options.auth = {};
|
||
|
}
|
||
|
|
||
|
options.auth.user = auth.shift();
|
||
|
options.auth.pass = auth.join(':');
|
||
|
}
|
||
|
|
||
|
Object.keys(url.query || {}).forEach(function (key) {
|
||
|
var obj = options;
|
||
|
var lKey = key;
|
||
|
var value = url.query[key];
|
||
|
|
||
|
if (!isNaN(value)) {
|
||
|
value = Number(value);
|
||
|
}
|
||
|
|
||
|
switch (value) {
|
||
|
case 'true':
|
||
|
value = true;
|
||
|
break;
|
||
|
case 'false':
|
||
|
value = false;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// tls is nested object
|
||
|
if (key.indexOf('tls.') === 0) {
|
||
|
lKey = key.substr(4);
|
||
|
if (!options.tls) {
|
||
|
options.tls = {};
|
||
|
}
|
||
|
obj = options.tls;
|
||
|
} else if (key.indexOf('.') >= 0) {
|
||
|
// ignore nested properties besides tls
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!(lKey in obj)) {
|
||
|
obj[lKey] = value;
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
|
||
|
return options;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Returns a bunyan-compatible logger interface. Uses either provided logger or
|
||
|
* creates a default console logger
|
||
|
*
|
||
|
* @param {Object} [options] Options object that might include 'logger' value
|
||
|
* @return {Object} bunyan compatible logger
|
||
|
*/
|
||
|
module.exports.getLogger = function (options) {
|
||
|
options = options || {};
|
||
|
|
||
|
if (!options.logger) {
|
||
|
// use vanity logger
|
||
|
return {
|
||
|
info: function () {},
|
||
|
debug: function () {},
|
||
|
error: function () {}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
if (options.logger === true) {
|
||
|
// create console logger
|
||
|
return createDefaultLogger();
|
||
|
}
|
||
|
|
||
|
// return whatever was passed
|
||
|
return options.logger;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Wrapper for creating a callback than either resolves or rejects a promise
|
||
|
* based on input
|
||
|
*
|
||
|
* @param {Function} resolve Function to run if callback is called
|
||
|
* @param {Function} reject Function to run if callback ends with an error
|
||
|
*/
|
||
|
module.exports.callbackPromise = function (resolve, reject) {
|
||
|
return function () {
|
||
|
var args = Array.prototype.slice.call(arguments);
|
||
|
var err = args.shift();
|
||
|
if (err) {
|
||
|
reject(err);
|
||
|
} else {
|
||
|
resolve.apply(null, args);
|
||
|
}
|
||
|
};
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Resolves a String or a Buffer value for content value. Useful if the value
|
||
|
* is a Stream or a file or an URL. If the value is a Stream, overwrites
|
||
|
* the stream object with the resolved value (you can't stream a value twice).
|
||
|
*
|
||
|
* This is useful when you want to create a plugin that needs a content value,
|
||
|
* for example the `html` or `text` value as a String or a Buffer but not as
|
||
|
* a file path or an URL.
|
||
|
*
|
||
|
* @param {Object} data An object or an Array you want to resolve an element for
|
||
|
* @param {String|Number} key Property name or an Array index
|
||
|
* @param {Function} callback Callback function with (err, value)
|
||
|
*/
|
||
|
module.exports.resolveContent = function (data, key, callback) {
|
||
|
var promise;
|
||
|
|
||
|
if (!callback && typeof Promise === 'function') {
|
||
|
promise = new Promise(function (resolve, reject) {
|
||
|
callback = module.exports.callbackPromise(resolve, reject);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
var content = data && data[key] && data[key].content || data[key];
|
||
|
var contentStream;
|
||
|
var encoding = (typeof data[key] === 'object' && data[key].encoding || 'utf8')
|
||
|
.toString()
|
||
|
.toLowerCase()
|
||
|
.replace(/[-_\s]/g, '');
|
||
|
|
||
|
if (!content) {
|
||
|
return callback(null, content);
|
||
|
}
|
||
|
|
||
|
if (typeof content === 'object') {
|
||
|
if (typeof content.pipe === 'function') {
|
||
|
return resolveStream(content, function (err, value) {
|
||
|
if (err) {
|
||
|
return callback(err);
|
||
|
}
|
||
|
// we can't stream twice the same content, so we need
|
||
|
// to replace the stream object with the streaming result
|
||
|
data[key] = value;
|
||
|
callback(null, value);
|
||
|
});
|
||
|
} else if (/^https?:\/\//i.test(content.path || content.href)) {
|
||
|
contentStream = fetch(content.path || content.href);
|
||
|
return resolveStream(contentStream, callback);
|
||
|
} else if (/^data:/i.test(content.path || content.href)) {
|
||
|
var parts = (content.path || content.href).match(/^data:((?:[^;]*;)*(?:[^,]*)),(.*)$/i);
|
||
|
if (!parts) {
|
||
|
return callback(null, new Buffer(0));
|
||
|
}
|
||
|
return callback(null, /\bbase64$/i.test(parts[1]) ? new Buffer(parts[2], 'base64') : new Buffer(decodeURIComponent(parts[2])));
|
||
|
} else if (content.path) {
|
||
|
return resolveStream(fs.createReadStream(content.path), callback);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (typeof data[key].content === 'string' && ['utf8', 'usascii', 'ascii'].indexOf(encoding) < 0) {
|
||
|
content = new Buffer(data[key].content, encoding);
|
||
|
}
|
||
|
|
||
|
// default action, return as is
|
||
|
setImmediate(callback.bind(null, null, content));
|
||
|
|
||
|
return promise;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Streams a stream value into a Buffer
|
||
|
*
|
||
|
* @param {Object} stream Readable stream
|
||
|
* @param {Function} callback Callback function with (err, value)
|
||
|
*/
|
||
|
function resolveStream(stream, callback) {
|
||
|
var responded = false;
|
||
|
var chunks = [];
|
||
|
var chunklen = 0;
|
||
|
|
||
|
stream.on('error', function (err) {
|
||
|
if (responded) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
responded = true;
|
||
|
callback(err);
|
||
|
});
|
||
|
|
||
|
stream.on('readable', function () {
|
||
|
var chunk;
|
||
|
while ((chunk = stream.read()) !== null) {
|
||
|
chunks.push(chunk);
|
||
|
chunklen += chunk.length;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
stream.on('end', function () {
|
||
|
if (responded) {
|
||
|
return;
|
||
|
}
|
||
|
responded = true;
|
||
|
|
||
|
var value;
|
||
|
|
||
|
try {
|
||
|
value = Buffer.concat(chunks, chunklen);
|
||
|
} catch (E) {
|
||
|
return callback(E);
|
||
|
}
|
||
|
callback(null, value);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Generates a bunyan-like logger that prints to console
|
||
|
*
|
||
|
* @returns {Object} Bunyan logger instance
|
||
|
*/
|
||
|
function createDefaultLogger() {
|
||
|
|
||
|
var logger = {
|
||
|
_print: function ( /* level, message */ ) {
|
||
|
var args = Array.prototype.slice.call(arguments);
|
||
|
var level = args.shift();
|
||
|
var message;
|
||
|
|
||
|
if (args.length > 1) {
|
||
|
message = util.format.apply(util, args);
|
||
|
} else {
|
||
|
message = args.shift();
|
||
|
}
|
||
|
|
||
|
console.log('[%s] %s: %s',
|
||
|
new Date().toISOString().substr(0, 19).replace(/T/, ' '),
|
||
|
level.toUpperCase(),
|
||
|
message);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
logger.info = logger._print.bind(null, 'info');
|
||
|
logger.debug = logger._print.bind(null, 'debug');
|
||
|
logger.error = logger._print.bind(null, 'error');
|
||
|
|
||
|
return logger;
|
||
|
}
|