Comparing version 0.14.0 to 1.0.0-rc1
76
index.js
@@ -9,14 +9,12 @@ | ||
var program = require('commander'); | ||
var async = require('async'); | ||
var pkg = require('./package.json'); | ||
var web = require('./lib/web'); | ||
var mailserver = require('./lib/mailserver'); | ||
var logger = require('./lib/logger'); | ||
const program = require('commander') | ||
const async = require('async') | ||
const pkg = require('./package.json') | ||
const web = require('./lib/web') | ||
const mailserver = require('./lib/mailserver') | ||
const logger = require('./lib/logger') | ||
module.exports = function (config) { | ||
const version = pkg.version | ||
module.exports = function(config) { | ||
var version = pkg.version; | ||
if (!config) { | ||
@@ -42,16 +40,21 @@ // CLI | ||
.option('--base-pathname <path>', 'base path for URLs') | ||
.option('--disable-web', 'Disable the use of the web interface. Useful for unit testing') | ||
.option('--hide-extensions <extensions>', | ||
'Comma separated list of SMTP extensions to NOT advertise (STARTTLS, SMTPUTF8, PIPELINING, 8BITMIME)', | ||
function (val) { return val.split(',') } | ||
) | ||
.option('-o, --open', 'Open the Web GUI after startup') | ||
.option('-v, --verbose') | ||
.option('--silent') | ||
.parse(process.argv); | ||
.parse(process.argv) | ||
} | ||
if (config.verbose) { | ||
logger.setLevel(2); | ||
logger.setLevel(2) | ||
} else if (config.silent) { | ||
logger.setLevel(0); | ||
logger.setLevel(0) | ||
} | ||
// Start the Mailserver & Web GUI | ||
mailserver.create(config.smtp, config.ip, config.incomingUser, config.incomingPass); | ||
mailserver.create(config.smtp, config.ip, config.incomingUser, config.incomingPass, config.hideExtensions) | ||
@@ -63,3 +66,2 @@ if (config.outgoingHost || | ||
config.outgoingSecure) { | ||
mailserver.setupOutgoing( | ||
@@ -71,29 +73,37 @@ config.outgoingHost, | ||
config.outgoingSecure | ||
); | ||
) | ||
} | ||
if (config.autoRelay){ | ||
mailserver.setAutoRelayMode(true, config.autoRelayRules); | ||
if (config.autoRelay) { | ||
mailserver.setAutoRelayMode(true, config.autoRelayRules) | ||
} | ||
// Default to run on same IP as smtp | ||
var webIp = config.webIp ? config.webIp : config.ip; | ||
web.start(config.web, webIp, mailserver, config.webUser, config.webPass, config.basePathname); | ||
if (!config.disableWeb) { | ||
// Default to run on same IP as smtp | ||
const webIp = config.webIp ? config.webIp : config.ip | ||
web.start(config.web, webIp, mailserver, config.webUser, config.webPass, config.basePathname) | ||
if (config.open){ | ||
var open = require('open'); | ||
open('http://' + (config.ip === '0.0.0.0' ? 'localhost' : config.ip) + ':' + config.web); | ||
if (config.open) { | ||
const open = require('open') | ||
open('http://' + (config.ip === '0.0.0.0' ? 'localhost' : config.ip) + ':' + config.web) | ||
} | ||
// Close the web server when the mailserver closes | ||
mailserver.on('close', web.close) | ||
} | ||
process.on('SIGTERM', function () { | ||
function shutdown () { | ||
logger.info(`Recieved shutdown signal, shutting down now...`) | ||
async.parallel([ | ||
mailserver.end, | ||
mailserver.close, | ||
web.close | ||
], function(err, results) { | ||
logger.info('Shutting down...'); | ||
process.exit(0); | ||
}); | ||
}); | ||
], function () { | ||
process.exit(0) | ||
}) | ||
} | ||
return mailserver; | ||
}; | ||
process.on('SIGTERM', shutdown) | ||
process.on('SIGINT', shutdown) | ||
return mailserver | ||
} |
@@ -0,1 +1,2 @@ | ||
'use strict' | ||
@@ -6,19 +7,17 @@ /** | ||
module.exports = function(user, password) { | ||
return function(req, res, next) { | ||
var auth; | ||
module.exports = function (user, password) { | ||
return function (req, res, next) { | ||
var auth | ||
if (req.headers.authorization) { | ||
auth = new Buffer(req.headers.authorization.substring(6), 'base64').toString().split(':'); | ||
auth = Buffer.from(req.headers.authorization.substring(6), 'base64').toString().split(':') | ||
} | ||
if (!auth || auth[0] !== user || auth[1] !== password) { | ||
res.statusCode = 401; | ||
res.setHeader('WWW-Authenticate', 'Basic realm="Authentication required"'); | ||
res.send('Unauthorized'); | ||
res.statusCode = 401 | ||
res.setHeader('WWW-Authenticate', 'Basic realm="Authentication required"') | ||
res.send('Unauthorized') | ||
} else { | ||
next(); | ||
next() | ||
} | ||
}; | ||
}; | ||
} | ||
} |
@@ -0,1 +1,2 @@ | ||
'use strict' | ||
@@ -6,7 +7,6 @@ /** | ||
var logLevel = 1; | ||
let logLevel = 1 | ||
module.exports = {}; | ||
module.exports = {} | ||
/** | ||
@@ -16,5 +16,5 @@ * Initialize the logger | ||
module.exports.setLevel = function(level) { | ||
logLevel = level; | ||
}; | ||
module.exports.setLevel = function (level) { | ||
logLevel = level | ||
} | ||
@@ -25,8 +25,6 @@ /** | ||
module.exports.info = function() { | ||
if (logLevel > 0) | ||
console.info.apply(console, arguments); | ||
module.exports.info = function () { | ||
if (logLevel > 0) { console.info.apply(console, arguments) } | ||
}; | ||
/** | ||
@@ -36,10 +34,8 @@ * Extend the basic console.x functions, checking if the logging is on | ||
['log', 'dir', 'warn', 'error'].forEach(function(fn){ | ||
module.exports[ fn ] = function(){ | ||
['log', 'dir', 'warn', 'error'].forEach(function (fn) { | ||
module.exports[ fn ] = function () { | ||
if (logLevel > 1) { | ||
console[ fn ].apply(console, arguments); | ||
console[ fn ].apply(console, arguments) | ||
} | ||
}; | ||
}); | ||
} | ||
}) |
@@ -0,1 +1,2 @@ | ||
'use strict' | ||
@@ -6,27 +7,19 @@ /** | ||
// NOTE - simplesmtp is for backwards compatibility with 0.10.x | ||
var simplesmtp = require('simplesmtp'); | ||
var SMTPServer = require('smtp-server').SMTPServer; | ||
var MailParser = require('mailparser').MailParser; | ||
var events = require('events'); | ||
var fs = require('fs'); | ||
var os = require('os'); | ||
var path = require('path'); | ||
var logger = require('./logger'); | ||
var outgoing = require('./outgoing'); | ||
const SMTPServer = require('smtp-server').SMTPServer | ||
const MailParser = require('mailparser').MailParser | ||
const events = require('events') | ||
const fs = require('fs') | ||
const os = require('os') | ||
const path = require('path') | ||
const utils = require('./utils') | ||
const logger = require('./logger') | ||
const smtpHelpers = require('./helpers/smtp') | ||
const outgoing = require('./outgoing') | ||
const defaultPort = 1025 | ||
const defaultHost = '0.0.0.0' | ||
const store = [] | ||
const tempDir = path.join(os.tmpdir(), 'maildev', process.pid.toString()) | ||
const eventEmitter = new events.EventEmitter() | ||
var version = process.version.replace(/v/, '') | ||
.split(/\./) | ||
.map(function(n) { return parseInt(n, 10); }); | ||
var legacy = version[0] === 0 && version[1] <= 10; | ||
var defaultPort = 1025; | ||
var defaultHost = '0.0.0.0'; | ||
var config = {}; | ||
var store = []; | ||
var tempDir = path.join(os.tmpdir(), 'maildev', process.pid.toString()); | ||
var eventEmitter = new events.EventEmitter(); | ||
var smtp; | ||
/** | ||
@@ -36,7 +29,6 @@ * Mail Server exports | ||
var mailServer = module.exports = {}; | ||
const mailServer = module.exports = {} | ||
mailServer.store = store; | ||
mailServer.store = store | ||
/** | ||
@@ -47,97 +39,58 @@ * SMTP Server stream and helper functions | ||
// Clone object | ||
function clone(object){ | ||
return JSON.parse(JSON.stringify(object)); | ||
function clone (object) { | ||
return JSON.parse(JSON.stringify(object)) | ||
} | ||
// Create an unique id, length 8 characters | ||
function makeId(){ | ||
var text = ''; | ||
var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; | ||
for (var i = 0; i < 8; i++){ | ||
text += possible.charAt(Math.floor(Math.random() * possible.length)); | ||
} | ||
return text; | ||
} | ||
// Save an email object on stream end | ||
function saveEmail(id, envelope, mailObject){ | ||
function saveEmail (id, envelope, mailObject) { | ||
// remove stream object from attachments (fix the JSON.stringify) | ||
if (mailObject.attachments instanceof Array) { | ||
mailObject.attachments.forEach(function(attachment) { | ||
delete attachment.stream; | ||
}); | ||
mailObject.attachments.forEach(function (attachment) { | ||
delete attachment.stream | ||
}) | ||
} | ||
var object = clone(mailObject); | ||
var object = clone(mailObject) | ||
object.id = id; | ||
object.time = new Date(); | ||
object.read = false; | ||
object.envelope = envelope; | ||
object.source = path.join(tempDir, id + '.eml'); | ||
object.id = id | ||
object.time = new Date() | ||
object.read = false | ||
object.envelope = envelope | ||
object.source = path.join(tempDir, id + '.eml') | ||
store.push(object); | ||
store.push(object) | ||
logger.log('Saving email: ', mailObject.subject); | ||
logger.log('Saving email: ', mailObject.subject) | ||
if (outgoing.isAutoRelayEnabled()) { | ||
mailServer.relayMail(object, true, function (err) { | ||
if (err) logger.error('Error when relaying email', err); | ||
}); | ||
if (err) logger.error('Error when relaying email', err) | ||
}) | ||
} | ||
eventEmitter.emit('new', object); | ||
eventEmitter.emit('new', object) | ||
} | ||
// Save an attachment | ||
function saveAttachment(attachment){ | ||
var output = fs.createWriteStream(path.join(tempDir, attachment.contentId)); | ||
attachment.stream.pipe(output); | ||
function saveAttachment (attachment) { | ||
var output = fs.createWriteStream(path.join(tempDir, attachment.contentId)) | ||
attachment.stream.pipe(output) | ||
} | ||
function createSaveStream(id, emailData) { | ||
function createSaveStream (id, emailData) { | ||
var parseStream = new MailParser({ | ||
streamAttachments: true | ||
}); | ||
parseStream.on('end', saveEmail.bind(null, id, emailData)); | ||
parseStream.on('attachment', saveAttachment); | ||
return parseStream; | ||
}) | ||
parseStream.on('end', saveEmail.bind(null, id, emailData)) | ||
parseStream.on('attachment', saveAttachment) | ||
return parseStream | ||
} | ||
function createRawStream(id) { | ||
return fs.createWriteStream(path.join(tempDir, id + '.eml')); | ||
function createRawStream (id) { | ||
return fs.createWriteStream(path.join(tempDir, id + '.eml')) | ||
} | ||
// Legacy methods are used with the Node 0.10 compatible smtpserver | ||
function handleLegacyDataStream(connection) { | ||
var id = makeId(); | ||
function handleDataStream (stream, session, callback) { | ||
const id = utils.makeId() | ||
connection.saveStream = createSaveStream(id, { | ||
from: connection.from, | ||
to: connection.to, | ||
host: connection.host, | ||
remoteAddress: connection.remoteAddress | ||
}); | ||
connection.saveRawStream = createRawStream(id); | ||
} | ||
function handleLegacyChunk(connection, chunk) { | ||
connection.saveStream.write(chunk); | ||
connection.saveRawStream.write(chunk); | ||
} | ||
function handleLegacyDataStreamEnd(connection, done) { | ||
connection.saveStream.end(); | ||
connection.saveRawStream.end(); | ||
// ABC is the queue id to be advertised to the client | ||
// There is no current significance to this. | ||
done(null, 'ABC'); | ||
} | ||
function handleDataStream(stream, session, callback) { | ||
var id = makeId(); | ||
session.saveStream = createSaveStream(id, { | ||
@@ -148,16 +101,15 @@ from: session.envelope.mailFrom, | ||
remoteAddress: session.remoteAddress | ||
}); | ||
}) | ||
session.saveRawStream = createRawStream(id); | ||
session.saveRawStream = createRawStream(id) | ||
stream.pipe(session.saveStream); | ||
stream.pipe(session.saveRawStream); | ||
stream.pipe(session.saveStream) | ||
stream.pipe(session.saveRawStream) | ||
stream.on('end', function(){ | ||
session.saveRawStream.end(); | ||
callback(null, 'Message queued as ' + id); | ||
}); | ||
stream.on('end', function () { | ||
session.saveRawStream.end() | ||
callback(null, 'Message queued as ' + id) | ||
}) | ||
} | ||
/** | ||
@@ -167,16 +119,14 @@ * Delete everything in the temp folder | ||
function clearTempFolder(){ | ||
function clearTempFolder () { | ||
fs.readdir(tempDir, function (err, files) { | ||
if (err) throw err | ||
fs.readdir(tempDir, function(err, files){ | ||
if (err) throw err; | ||
files.forEach(function(file){ | ||
fs.unlink( path.join(tempDir, file), function(err) { | ||
if (err) throw err; | ||
}); | ||
}); | ||
}); | ||
files.forEach(function (file) { | ||
fs.unlink(path.join(tempDir, file), function (err) { | ||
if (err) throw err | ||
}) | ||
}) | ||
}) | ||
} | ||
/** | ||
@@ -186,12 +136,10 @@ * Delete a single email's attachments | ||
function deleteAttachments(attachments) { | ||
attachments.forEach(function(attachment){ | ||
fs.unlink( path.join(tempDir, attachment.contentId), function (err) { | ||
if (err) logger.error(err); | ||
}); | ||
}); | ||
function deleteAttachments (attachments) { | ||
attachments.forEach(function (attachment) { | ||
fs.unlink(path.join(tempDir, attachment.contentId), function (err) { | ||
if (err) logger.error(err) | ||
}) | ||
}) | ||
} | ||
/** | ||
@@ -201,17 +149,16 @@ * Create temp folder | ||
function createTempFolder() { | ||
function createTempFolder () { | ||
if (fs.existsSync(tempDir)) { | ||
clearTempFolder(); | ||
return; | ||
clearTempFolder() | ||
return | ||
} | ||
if (!fs.existsSync(path.dirname(tempDir))) { | ||
fs.mkdirSync(path.dirname(tempDir)); | ||
logger.log('Temporary directory created at %s', path.dirname(tempDir)); | ||
fs.mkdirSync(path.dirname(tempDir)) | ||
logger.log('Temporary directory created at %s', path.dirname(tempDir)) | ||
} | ||
if (!fs.existsSync(tempDir)) { | ||
fs.mkdirSync(tempDir); | ||
logger.log('Temporary directory created at %s', tempDir); | ||
fs.mkdirSync(tempDir) | ||
logger.log('Temporary directory created at %s', tempDir) | ||
} | ||
@@ -221,67 +168,51 @@ } | ||
/** | ||
* Authorize callback for smtp server | ||
* Create and configure the mailserver | ||
*/ | ||
function authorizeUser(auth, session, callback) { | ||
var username = auth.username; | ||
var password = auth.password; | ||
mailServer.create = function (port, host, user, password, hideExtensions) { | ||
const hideExtensionOptions = getHideExtensionOptions(hideExtensions) | ||
const smtpServerConfig = Object.assign({ | ||
onAuth: smtpHelpers.createOnAuthCallback(user, password), | ||
onData: handleDataStream, | ||
logger: false, | ||
disabledCommands: (user && password) ? ['STARTTLS'] : ['AUTH'] | ||
}, hideExtensionOptions) | ||
// conn, username, password, callback | ||
if (legacy) { | ||
username = arguments[1]; | ||
password = arguments[2]; | ||
callback = arguments[3]; | ||
} | ||
const smtp = new SMTPServer(smtpServerConfig) | ||
if (this.options.incomingUser && this.options.incomingPassword) { | ||
if (username !== this.options.incomingUser || | ||
password !== this.options.incomingPassword) { | ||
return callback(new Error('Invalid username or password')); | ||
} | ||
} | ||
callback(null, { user: this.options.incomingUser }); | ||
} | ||
smtp.on('error', mailServer.onSmtpError) | ||
// Setup temp folder for attachments | ||
createTempFolder() | ||
/** | ||
* Create and configure the mailserver | ||
*/ | ||
mailServer.port = port || defaultPort | ||
mailServer.host = host || defaultHost | ||
mailServer.create = function(port, host, user, password) { | ||
// testability requires this to be exposed. | ||
// otherwise we cannot test whether error handling works | ||
mailServer.smtp = smtp | ||
} | ||
// Start the server & Disable DNS checking | ||
if (legacy) { | ||
smtp = simplesmtp.createServer({ | ||
disableDNSValidation: true, | ||
requireAuthentication: !!(user && password), | ||
incomingUser: user, | ||
incomingPassword: password | ||
}); | ||
const HIDEABLE_EXTENSIONS = [ | ||
'STARTTLS', | ||
'PIPELINING', | ||
'8BITMIME', | ||
'SMTPUTF8' | ||
] | ||
smtp.on('startData', handleLegacyDataStream); | ||
smtp.on('data', handleLegacyChunk); | ||
smtp.on('dataReady', handleLegacyDataStreamEnd); | ||
if (user && password) { | ||
smtp.on('authorizeUser', authorizeUser); | ||
function getHideExtensionOptions (extensions) { | ||
if (!extensions) { | ||
return {} | ||
} | ||
return extensions.reduce(function (options, extension) { | ||
const ext = extension.toUpperCase() | ||
if (HIDEABLE_EXTENSIONS.indexOf(ext) > -1) { | ||
options[`hide${ext}`] = true | ||
} else { | ||
throw new Error(`Invalid hideable extension: ${ext}`) | ||
} | ||
} else { | ||
smtp = new SMTPServer({ | ||
incomingUser: user, | ||
incomingPassword: password, | ||
onAuth: authorizeUser, | ||
onData: handleDataStream, | ||
logger: false, | ||
disabledCommands: !!(user && password)?['STARTTLS']:['AUTH'] | ||
}); | ||
} | ||
return options | ||
}, {}) | ||
} | ||
// Setup temp folder for attachments | ||
createTempFolder(); | ||
mailServer.port = port || defaultPort; | ||
mailServer.host = host || defaultHost; | ||
}; | ||
/** | ||
@@ -291,22 +222,28 @@ * Start the mailServer | ||
mailServer.listen = function(callback) { | ||
mailServer.listen = function (callback) { | ||
if (typeof callback !== 'function') callback = null | ||
if (typeof callback !== 'function') callback = null; | ||
// Listen on the specified port | ||
smtp.listen(mailServer.port, mailServer.host, function(err) { | ||
mailServer.smtp.listen(mailServer.port, mailServer.host, function (err) { | ||
if (err) { | ||
if (callback) { | ||
callback(err); | ||
callback(err) | ||
} else { | ||
throw err; | ||
throw err | ||
} | ||
} | ||
if (callback) callback(); | ||
if (callback) callback() | ||
logger.info('MailDev SMTP Server running at %s:%s', mailServer.host, mailServer.port); | ||
}); | ||
}; | ||
logger.info('MailDev SMTP Server running at %s:%s', mailServer.host, mailServer.port) | ||
}) | ||
} | ||
mailServer.onSmtpError = function (e) { | ||
logger.info( | ||
`Could not start mail server on ${e.address}:${e.port}\n` + | ||
`Port already in use or insufficient rights to bind port` | ||
) | ||
process.emit('SIGTERM') | ||
} | ||
@@ -317,9 +254,8 @@ /** | ||
mailServer.end = function(callback) { | ||
var method = legacy ? 'end' : 'close'; | ||
smtp[method](callback); | ||
outgoing.close(); | ||
}; | ||
mailServer.close = function (callback) { | ||
mailServer.emit('close') | ||
mailServer.smtp.close(callback) | ||
outgoing.close() | ||
} | ||
/** | ||
@@ -331,8 +267,7 @@ * Extend Event Emitter methods | ||
mailServer.on = eventEmitter.on.bind(eventEmitter); | ||
mailServer.emit = eventEmitter.emit.bind(eventEmitter); | ||
mailServer.removeListener = eventEmitter.removeListener.bind(eventEmitter); | ||
mailServer.removeAllListeners = eventEmitter.removeAllListeners.bind(eventEmitter); | ||
mailServer.on = eventEmitter.on.bind(eventEmitter) | ||
mailServer.emit = eventEmitter.emit.bind(eventEmitter) | ||
mailServer.removeListener = eventEmitter.removeListener.bind(eventEmitter) | ||
mailServer.removeAllListeners = eventEmitter.removeAllListeners.bind(eventEmitter) | ||
/** | ||
@@ -342,14 +277,13 @@ * Get an email by id | ||
mailServer.getEmail = function(id, done){ | ||
mailServer.getEmail = function (id, done) { | ||
var email = store.filter(function (element) { | ||
return element.id === id | ||
})[0] | ||
var email = store.filter(function(element){ | ||
return element.id === id; | ||
})[0]; | ||
if (email) { | ||
done(null, email); | ||
done(null, email) | ||
} else { | ||
done(new Error('Email was not found')); | ||
done(new Error('Email was not found')) | ||
} | ||
}; | ||
} | ||
@@ -360,11 +294,10 @@ /** | ||
mailServer.getRawEmail = function(id, done){ | ||
mailServer.getRawEmail = function (id, done) { | ||
mailServer.getEmail(id, function (err, email) { | ||
if (err) return done(err) | ||
mailServer.getEmail(id, function(err, email){ | ||
if (err) return done(err); | ||
done(null, fs.createReadStream(path.join(tempDir, id + '.eml'))) | ||
}) | ||
} | ||
done(null, fs.createReadStream( path.join(tempDir, id + '.eml') ) ); | ||
}); | ||
}; | ||
/** | ||
@@ -374,39 +307,36 @@ * Returns the html of a given email | ||
mailServer.getEmailHTML = function(id, baseUrl, done) { | ||
mailServer.getEmailHTML = function (id, baseUrl, done) { | ||
if (!done && typeof baseUrl === 'function') { | ||
done = baseUrl; | ||
baseUrl = null; | ||
done = baseUrl | ||
baseUrl = null | ||
} | ||
if (baseUrl) | ||
baseUrl = '//' + baseUrl; | ||
if (baseUrl) { baseUrl = '//' + baseUrl } | ||
mailServer.getEmail(id, function(err, email) { | ||
if (err) return done(err); | ||
mailServer.getEmail(id, function (err, email) { | ||
if (err) return done(err) | ||
var html = email.html; | ||
var html = email.html | ||
if (!email.attachments) | ||
return done(null, html); | ||
if (!email.attachments) { return done(null, html) } | ||
var embeddedAttachments = email.attachments.filter(function(attachment) { | ||
return attachment.contentId; | ||
}); | ||
var embeddedAttachments = email.attachments.filter(function (attachment) { | ||
return attachment.contentId | ||
}) | ||
var getFileUrl = function(id, baseUrl, filename) { | ||
return (baseUrl || '') + '/email/' + id + '/attachment/' + filename; | ||
}; | ||
var getFileUrl = function (id, baseUrl, filename) { | ||
return (baseUrl || '') + '/email/' + id + '/attachment/' + filename | ||
} | ||
if (embeddedAttachments.length) { | ||
embeddedAttachments.forEach(function(attachment) { | ||
var regex = new RegExp('src=("|\')cid:' + attachment.contentId + '("|\')', 'g'); | ||
var replacement = 'src="' + getFileUrl(id, baseUrl, attachment.generatedFileName) + '"'; | ||
html = html.replace(regex, replacement); | ||
}); | ||
embeddedAttachments.forEach(function (attachment) { | ||
var regex = new RegExp('src=("|\')cid:' + attachment.contentId + '("|\')', 'g') | ||
var replacement = 'src="' + getFileUrl(id, baseUrl, attachment.generatedFileName) + '"' | ||
html = html.replace(regex, replacement) | ||
}) | ||
} | ||
done(null, html); | ||
}); | ||
}; | ||
done(null, html) | ||
}) | ||
} | ||
@@ -417,7 +347,6 @@ /** | ||
mailServer.getAllEmail = function(done){ | ||
done(null, store); | ||
}; | ||
mailServer.getAllEmail = function (done) { | ||
done(null, store) | ||
} | ||
/** | ||
@@ -427,38 +356,37 @@ * Delete an email by id | ||
mailServer.deleteEmail = function(id, done){ | ||
var email = null; | ||
var emailIndex = null; | ||
mailServer.deleteEmail = function (id, done) { | ||
var email = null | ||
var emailIndex = null | ||
store.forEach(function(element, index){ | ||
if (element.id === id){ | ||
email = element; | ||
emailIndex = index; | ||
store.forEach(function (element, index) { | ||
if (element.id === id) { | ||
email = element | ||
emailIndex = index | ||
} | ||
}); | ||
}) | ||
if (emailIndex === null){ | ||
return done(new Error('Email not found')); | ||
if (emailIndex === null) { | ||
return done(new Error('Email not found')) | ||
} | ||
//delete raw email | ||
fs.unlink( path.join(tempDir, id + '.eml'), function (err) { | ||
// delete raw email | ||
fs.unlink(path.join(tempDir, id + '.eml'), function (err) { | ||
if (err) { | ||
logger.error(err); | ||
logger.error(err) | ||
} else { | ||
eventEmitter.emit('delete', {id:id, index:emailIndex}); | ||
eventEmitter.emit('delete', {id: id, index: emailIndex}) | ||
} | ||
}); | ||
}) | ||
if (email.attachments){ | ||
deleteAttachments(email.attachments); | ||
if (email.attachments) { | ||
deleteAttachments(email.attachments) | ||
} | ||
logger.warn('Deleting email - %s', email.subject); | ||
logger.warn('Deleting email - %s', email.subject) | ||
store.splice(emailIndex, 1); | ||
store.splice(emailIndex, 1) | ||
done(null, true); | ||
}; | ||
done(null, true) | ||
} | ||
/** | ||
@@ -468,14 +396,12 @@ * Delete all emails in the store | ||
mailServer.deleteAllEmail = function(done){ | ||
mailServer.deleteAllEmail = function (done) { | ||
logger.warn('Deleting all email') | ||
logger.warn('Deleting all email'); | ||
clearTempFolder() | ||
store.length = 0 | ||
eventEmitter.emit('delete', {id: 'all'}) | ||
clearTempFolder(); | ||
store.length = 0; | ||
eventEmitter.emit('delete', {id:'all'}); | ||
done(null, true) | ||
} | ||
done(null, true); | ||
}; | ||
/** | ||
@@ -485,41 +411,37 @@ * Returns the content type and a readable stream of the file | ||
mailServer.getEmailAttachment = function(id, filename, done){ | ||
mailServer.getEmailAttachment = function (id, filename, done) { | ||
mailServer.getEmail(id, function (err, email) { | ||
if (err) return done(err) | ||
mailServer.getEmail(id, function(err, email){ | ||
if (err) return done(err); | ||
if (!email.attachments || !email.attachments.length) { | ||
return done(new Error('Email has no attachments')); | ||
return done(new Error('Email has no attachments')) | ||
} | ||
var match = email.attachments.filter(function(attachment){ | ||
return attachment.generatedFileName === filename; | ||
})[0]; | ||
var match = email.attachments.filter(function (attachment) { | ||
return attachment.generatedFileName === filename | ||
})[0] | ||
if (!match) { | ||
return done(new Error('Attachment not found')); | ||
return done(new Error('Attachment not found')) | ||
} | ||
done(null, match.contentType, fs.createReadStream( path.join(tempDir, match.contentId) ) ); | ||
done(null, match.contentType, fs.createReadStream(path.join(tempDir, match.contentId))) | ||
}) | ||
} | ||
}); | ||
}; | ||
/** | ||
* Setup outgoing | ||
*/ | ||
mailServer.setupOutgoing = function(host, port, user, pass, secure) { | ||
outgoing.setup(host, port, user, pass, secure); | ||
}; | ||
mailServer.setupOutgoing = function (host, port, user, pass, secure) { | ||
outgoing.setup(host, port, user, pass, secure) | ||
} | ||
mailServer.isOutgoingEnabled = function() { | ||
return outgoing.isEnabled(); | ||
}; | ||
mailServer.isOutgoingEnabled = function () { | ||
return outgoing.isEnabled() | ||
} | ||
mailServer.getOutgoingHost = function() { | ||
return outgoing.getOutgoingHost(); | ||
}; | ||
mailServer.getOutgoingHost = function () { | ||
return outgoing.getOutgoingHost() | ||
} | ||
/** | ||
@@ -529,5 +451,5 @@ * Set Auto Relay Mode, automatic send email to recipient | ||
mailServer.setAutoRelayMode = function(enabled, rules) { | ||
outgoing.setAutoRelayMode(enabled, rules); | ||
}; | ||
mailServer.setAutoRelayMode = function (enabled, rules) { | ||
outgoing.setAutoRelayMode(enabled, rules) | ||
} | ||
@@ -538,11 +460,9 @@ /** | ||
mailServer.relayMail = function(idOrMailObject, isAutoRelay, done) { | ||
mailServer.relayMail = function (idOrMailObject, isAutoRelay, done) { | ||
if (!outgoing.isEnabled()) { return done(new Error('Outgoing mail not configured')) } | ||
if (!outgoing.isEnabled()) | ||
return done(new Error('Outgoing mail not configured')); | ||
// isAutoRelay is an option argument | ||
if (typeof isAutoRelay === 'function') { | ||
done = isAutoRelay; | ||
isAutoRelay = false; | ||
done = isAutoRelay | ||
isAutoRelay = false | ||
} | ||
@@ -552,20 +472,20 @@ | ||
if (typeof idOrMailObject === 'string') { | ||
mailServer.getEmail(idOrMailObject, function(err, email) { | ||
if (err) return done(err); | ||
mailServer.relayMail(email, done); | ||
}); | ||
return; | ||
mailServer.getEmail(idOrMailObject, function (err, email) { | ||
if (err) return done(err) | ||
mailServer.relayMail(email, done) | ||
}) | ||
return | ||
} | ||
var mail = idOrMailObject; | ||
var mail = idOrMailObject | ||
mailServer.getRawEmail(mail.id, function(err, rawEmailStream) { | ||
mailServer.getRawEmail(mail.id, function (err, rawEmailStream) { | ||
if (err) { | ||
logger.error('Mail Stream Error: ', err); | ||
return done(err); | ||
logger.error('Mail Stream Error: ', err) | ||
return done(err) | ||
} | ||
outgoing.relayMail(mail, rawEmailStream, isAutoRelay, done); | ||
}); | ||
}; | ||
outgoing.relayMail(mail, rawEmailStream, isAutoRelay, done) | ||
}) | ||
} | ||
@@ -575,10 +495,10 @@ /** | ||
*/ | ||
mailServer.getEmailEml = function(id, done) { | ||
mailServer.getEmail(id, function(err, email){ | ||
if (err) return done(err); | ||
mailServer.getEmailEml = function (id, done) { | ||
mailServer.getEmail(id, function (err, email) { | ||
if (err) return done(err) | ||
var filename = email.id + '.eml'; | ||
var filename = email.id + '.eml' | ||
done(null, 'message/rfc822', filename, fs.createReadStream(path.join(tempDir, id + '.eml'))); | ||
}); | ||
}; | ||
done(null, 'message/rfc822', filename, fs.createReadStream(path.join(tempDir, id + '.eml'))) | ||
}) | ||
} |
@@ -0,1 +1,2 @@ | ||
'use strict' | ||
@@ -6,17 +7,17 @@ /** | ||
var SMTPConnection = require('smtp-connection'); | ||
var wildstring = require('wildstring'); | ||
var async = require('async'); | ||
var fs = require('fs'); | ||
var logger = require('./logger'); | ||
const SMTPConnection = require('smtp-connection') | ||
const wildstring = require('wildstring') | ||
const async = require('async') | ||
const fs = require('fs') | ||
const logger = require('./logger') | ||
wildstring.caseSensitive = false; | ||
wildstring.caseSensitive = false | ||
var config = { | ||
autoRelay: false | ||
}; | ||
} | ||
// The SMTP connection client | ||
var client; | ||
var emailQueue; | ||
let client | ||
let emailQueue | ||
@@ -27,25 +28,24 @@ /** | ||
var outgoing = module.exports = {}; | ||
const outgoing = module.exports = {} | ||
outgoing.setup = function(host, port, user, pass, secure) { | ||
outgoing.setup = function (host, port, user, pass, secure) { | ||
// defaults | ||
port = port || (secure ? 465 : 25) | ||
host = host || 'localhost' | ||
secure = secure || false | ||
//defaults | ||
port = port || (secure ? 465 : 25); | ||
host = host || 'localhost'; | ||
secure = secure || false; | ||
config.host = host | ||
config.port = port | ||
config.user = user | ||
config.pass = pass | ||
config.secure = secure | ||
config.host = host; | ||
config.port = port; | ||
config.user = user; | ||
config.pass = pass; | ||
config.secure = secure; | ||
this._createClient() | ||
this._createClient(); | ||
// Create a queue to sent out the emails | ||
// We use a queue so we don't run into concurrency issues | ||
emailQueue = async.queue(relayWorker, 1); | ||
}; | ||
emailQueue = async.queue(relayWorker, 1) | ||
} | ||
outgoing._createClient = function() { | ||
outgoing._createClient = function () { | ||
try { | ||
@@ -56,8 +56,8 @@ client = new SMTPConnection({ | ||
secure: config.secure, | ||
auth: (config.pass && config.user) ? | ||
{ user: config.user, pass: config.pass } : | ||
false, | ||
auth: (config.pass && config.user) | ||
? { user: config.user, pass: config.pass } | ||
: false, | ||
tls: { rejectUnauthorized: false }, | ||
debug: true | ||
}); | ||
}) | ||
@@ -71,41 +71,38 @@ logger.info( | ||
config.secure ? 'yes' : 'no' | ||
); | ||
} catch (err){ | ||
logger.error('Error during configuration of SMTP Server for outgoing email', err); | ||
) | ||
} catch (err) { | ||
logger.error('Error during configuration of SMTP Server for outgoing email', err) | ||
} | ||
}; | ||
} | ||
outgoing.close = function() { | ||
if (this.isEnabled()) | ||
client.close(); | ||
}; | ||
outgoing.close = function () { | ||
if (this.isEnabled()) { client.close() } | ||
} | ||
outgoing.isEnabled = function() { | ||
return !!client; | ||
}; | ||
outgoing.isEnabled = function () { | ||
return !!client | ||
} | ||
outgoing.getOutgoingHost = function() { | ||
return config.host; | ||
}; | ||
outgoing.getOutgoingHost = function () { | ||
return config.host | ||
} | ||
outgoing.isAutoRelayEnabled = function() { | ||
return config.autoRelay; | ||
}; | ||
outgoing.isAutoRelayEnabled = function () { | ||
return config.autoRelay | ||
} | ||
outgoing.setAutoRelayMode = function(enabled, rules) { | ||
if (!client){ | ||
config.autoRelay = false; | ||
logger.info('Outgoing mail not configured - Auto relay mode ignored'); | ||
return; | ||
outgoing.setAutoRelayMode = function (enabled, rules) { | ||
if (!client) { | ||
config.autoRelay = false | ||
logger.info('Outgoing mail not configured - Auto relay mode ignored') | ||
return | ||
} | ||
if (rules) { | ||
if (typeof rules === 'string') { | ||
try { | ||
rules = JSON.parse(fs.readFileSync(rules, 'utf8')); | ||
rules = JSON.parse(fs.readFileSync(rules, 'utf8')) | ||
} catch (err) { | ||
logger.error('Error reading config file at ' + rules); | ||
throw err; | ||
logger.error('Error reading config file at ' + rules) | ||
throw err | ||
} | ||
@@ -115,7 +112,7 @@ } | ||
if (Array.isArray(rules)) { | ||
config.autoRelayRules = rules; | ||
config.autoRelayRules = rules | ||
} | ||
} | ||
config.autoRelay = enabled; | ||
config.autoRelay = enabled | ||
@@ -126,8 +123,7 @@ if (config.autoRelay) { | ||
JSON.stringify(config.autoRelayRules) | ||
); | ||
) | ||
} | ||
}; | ||
} | ||
outgoing.relayMail = function(emailObject, emailStream, isAutoRelay, callback) { | ||
outgoing.relayMail = function (emailObject, emailStream, isAutoRelay, callback) { | ||
emailQueue.push({ | ||
@@ -138,27 +134,24 @@ emailObject: emailObject, | ||
callback: callback | ||
}); | ||
}; | ||
}) | ||
} | ||
function relayMail (emailObject, emailStream, isAutoRelay, done) { | ||
if (!client) { return done(new Error('Outgoing mail not configured')) } | ||
function relayMail(emailObject, emailStream, isAutoRelay, done) { | ||
if (!client) | ||
return done(new Error('Outgoing mail not configured')); | ||
// Fallback to the object if the address key isn't defined | ||
var getAddress = function(addressObj) { | ||
return typeof addressObj.address !== 'undefined' ? addressObj.address : addressObj; | ||
}; | ||
const getAddress = function (addressObj) { | ||
return typeof addressObj.address !== 'undefined' ? addressObj.address : addressObj | ||
} | ||
var mailSendCallback = function(err) { | ||
const mailSendCallback = function (err) { | ||
if (err) { | ||
logger.error('Outgoing client login error: ', err); | ||
return done(err); | ||
logger.error('Outgoing client login error: ', err) | ||
return done(err) | ||
} | ||
var recipients = emailObject.envelope.to.map(getAddress); | ||
var sender = getAddress(emailObject.envelope.from); | ||
let recipients = emailObject.envelope.to.map(getAddress) | ||
const sender = getAddress(emailObject.envelope.from) | ||
if (isAutoRelay && config.autoRelayRules) { | ||
recipients = getAutoRelayableRecipients(recipients); | ||
recipients = getAutoRelayableRecipients(recipients) | ||
} | ||
@@ -168,3 +161,3 @@ | ||
if (recipients.length === 0) { | ||
return done('Email had no recipients'); | ||
return done('Email had no recipients') | ||
} | ||
@@ -176,64 +169,61 @@ | ||
}, emailStream, function (err) { | ||
client.quit() | ||
outgoing._createClient() | ||
client.quit(); | ||
outgoing._createClient(); | ||
if (err) { | ||
logger.error('Mail Delivery Error: ', err); | ||
return done(err); | ||
logger.error('Mail Delivery Error: ', err) | ||
return done(err) | ||
} | ||
logger.log('Mail Delivered: ', emailObject.subject); | ||
logger.log('Mail Delivered: ', emailObject.subject) | ||
return done(); | ||
}); | ||
}; | ||
return done() | ||
}) | ||
} | ||
var mailConnectCallback = function(err) { | ||
var mailConnectCallback = function (err) { | ||
if (err) { | ||
logger.error('Outgoing connection error: ', err); | ||
return done(err); | ||
logger.error('Outgoing connection error: ', err) | ||
return done(err) | ||
} | ||
if (client.options.auth) { | ||
client.login(client.options.auth, mailSendCallback); | ||
client.login(client.options.auth, mailSendCallback) | ||
} else { | ||
mailSendCallback(err); | ||
mailSendCallback(err) | ||
} | ||
}; | ||
} | ||
client.connect(mailConnectCallback); | ||
client.connect(mailConnectCallback) | ||
} | ||
function relayWorker(task, callback) { | ||
function relayWorker (task, callback) { | ||
const relayCallback = function (err, result) { | ||
task.callback && task.callback(err, result) | ||
callback(err, result) | ||
} | ||
var relayCallback = function(err, result){ | ||
task.callback && task.callback(err, result); | ||
callback(err, result); | ||
}; | ||
relayMail(task.emailObject, task.emailStream, task.isAutoRelay, relayCallback); | ||
relayMail(task.emailObject, task.emailStream, task.isAutoRelay, relayCallback) | ||
} | ||
function getAutoRelayableRecipients(recipients) { | ||
return recipients.filter(function(email) { | ||
return validateAutoRelayRules(email); | ||
}); | ||
function getAutoRelayableRecipients (recipients) { | ||
return recipients.filter(function (email) { | ||
return validateAutoRelayRules(email) | ||
}) | ||
} | ||
function validateAutoRelayRules(email) { | ||
function validateAutoRelayRules (email) { | ||
if (!config.autoRelayRules) { | ||
return true; | ||
return true | ||
} | ||
return config.autoRelayRules.reduce(function(result, rule) { | ||
var toMatch = rule.allow || rule.deny || ''; | ||
var isMatch = wildstring.match(toMatch, email); | ||
return config.autoRelayRules.reduce(function (result, rule) { | ||
const toMatch = rule.allow || rule.deny || '' | ||
const isMatch = wildstring.match(toMatch, email) | ||
// Override previous rule if it matches | ||
return isMatch ? | ||
(rule.allow ? true : false) : | ||
result; | ||
}, true); | ||
return isMatch | ||
? (!!rule.allow) | ||
: result | ||
}, true) | ||
} |
@@ -0,1 +1,2 @@ | ||
'use strict' | ||
@@ -5,144 +6,123 @@ /** | ||
*/ | ||
var express = require('express'); | ||
var pkg = require('../package.json'); | ||
const express = require('express') | ||
const pkg = require('../package.json') | ||
var emailRegexp = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; | ||
const emailRegexp = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ | ||
module.exports = function(app, mailserver, basePathname) { | ||
var router = express.Router(); | ||
module.exports = function (app, mailserver, basePathname) { | ||
var router = express.Router() | ||
// Get all emails | ||
router.get('/email', function(req, res){ | ||
router.get('/email', function (req, res) { | ||
mailserver.getAllEmail(function (err, emailList) { | ||
if (err) return res.status(404).json([]) | ||
mailserver.getAllEmail(function(err, emailList){ | ||
if (err) return res.status(404).json([]); | ||
res.json(emailList) | ||
}) | ||
}) | ||
res.json(emailList); | ||
}); | ||
}); | ||
// Get single email | ||
router.get('/email/:id', function(req, res){ | ||
router.get('/email/:id', function (req, res) { | ||
mailserver.getEmail(req.params.id, function (err, email) { | ||
if (err) return res.status(404).json({ error: err.message }) | ||
mailserver.getEmail( req.params.id, function(err, email){ | ||
if (err) return res.status(404).json({ error: err.message }); | ||
// Mark the email as 'read' | ||
email.read = true; | ||
email.read = true | ||
res.json(email); | ||
}); | ||
res.json(email) | ||
}) | ||
}) | ||
}); | ||
// Delete all emails | ||
router.delete('/email/all', function(req, res){ | ||
router.delete('/email/all', function (req, res) { | ||
mailserver.deleteAllEmail(function (err) { | ||
if (err) return res.status(500).json({ error: err.message }) | ||
mailserver.deleteAllEmail(function(err){ | ||
if (err) return res.status(500).json({ error: err.message }); | ||
res.json(true) | ||
}) | ||
}) | ||
res.json(true); | ||
}); | ||
}); | ||
// Delete email by id | ||
router.delete('/email/:id', function(req, res){ | ||
router.delete('/email/:id', function (req, res) { | ||
mailserver.deleteEmail(req.params.id, function (err) { | ||
if (err) return res.status(500).json({ error: err.message }) | ||
mailserver.deleteEmail(req.params.id, function(err){ | ||
if (err) return res.status(500).json({ error: err.message }); | ||
res.json(true) | ||
}) | ||
}) | ||
res.json(true); | ||
}); | ||
}); | ||
// Get Email HTML | ||
router.get('/email/:id/html', function(req, res){ | ||
router.get('/email/:id/html', function (req, res) { | ||
// Use the headers over hostname to include any port | ||
var baseUrl = req.headers.host + (req.baseUrl || ''); | ||
var baseUrl = req.headers.host + (req.baseUrl || '') | ||
mailserver.getEmailHTML(req.params.id, baseUrl, function(err, html){ | ||
if (err) return res.status(404).json({ error: err.message }); | ||
mailserver.getEmailHTML(req.params.id, baseUrl, function (err, html) { | ||
if (err) return res.status(404).json({ error: err.message }) | ||
res.send(html); | ||
}); | ||
res.send(html) | ||
}) | ||
}) | ||
}); | ||
// Serve Attachments | ||
router.get('/email/:id/attachment/:filename', function(req, res){ | ||
router.get('/email/:id/attachment/:filename', function (req, res) { | ||
mailserver.getEmailAttachment(req.params.id, req.params.filename, function (err, contentType, readStream) { | ||
if (err) return res.status(404).json('File not found') | ||
mailserver.getEmailAttachment(req.params.id, req.params.filename, function(err, contentType, readStream){ | ||
if (err) return res.status(404).json('File not found'); | ||
res.contentType(contentType) | ||
readStream.pipe(res) | ||
}) | ||
}) | ||
res.contentType(contentType); | ||
readStream.pipe(res); | ||
}); | ||
}); | ||
// Serve email.eml | ||
router.get('/email/:id/download', function(req, res){ | ||
router.get('/email/:id/download', function (req, res) { | ||
mailserver.getEmailEml(req.params.id, function (err, contentType, filename, readStream) { | ||
if (err) return res.status(404).json('File not found') | ||
mailserver.getEmailEml(req.params.id, function(err, contentType, filename, readStream){ | ||
if (err) return res.status(404).json('File not found'); | ||
res.setHeader('Content-disposition', 'attachment; filename=' + filename) | ||
res.contentType(contentType) | ||
readStream.pipe(res) | ||
}) | ||
}) | ||
res.setHeader('Content-disposition', 'attachment; filename=' + filename); | ||
res.contentType(contentType); | ||
readStream.pipe(res); | ||
}); | ||
}); | ||
// Get email source from .eml file | ||
router.get('/email/:id/source', function(req, res){ | ||
router.get('/email/:id/source', function (req, res) { | ||
mailserver.getRawEmail(req.params.id, function (err, readStream) { | ||
if (err) return res.status(404).json('File not found') | ||
readStream.pipe(res) | ||
}) | ||
}) | ||
mailserver.getRawEmail( req.params.id, function(err, readStream){ | ||
if (err) return res.status(404).json('File not found'); | ||
readStream.pipe(res); | ||
}); | ||
}); | ||
// Get any config settings for display | ||
router.get('/config', function(req, res){ | ||
router.get('/config', function (req, res) { | ||
res.json({ | ||
version: pkg.version, | ||
version: pkg.version, | ||
smtpPort: mailserver.port, | ||
isOutgoingEnabled: mailserver.isOutgoingEnabled(), | ||
outgoingHost: mailserver.getOutgoingHost() | ||
}); | ||
}) | ||
}) | ||
}); | ||
// Relay the email | ||
router.post('/email/:id/relay/:relayTo?', function(req, res){ | ||
mailserver.getEmail(req.params.id, function(err, email){ | ||
if (err) return res.status(404).json({ error: err.message }); | ||
router.post('/email/:id/relay/:relayTo?', function (req, res) { | ||
mailserver.getEmail(req.params.id, function (err, email) { | ||
if (err) return res.status(404).json({ error: err.message }) | ||
if (req.params.relayTo) { | ||
if (emailRegexp.test(req.params.relayTo)) { | ||
email.to = [{address: req.params.relayTo}]; | ||
email.envelope.to = [ { address: req.params.relayTo, args: false }] | ||
email.to = [{address: req.params.relayTo}] | ||
email.envelope.to = [{ address: req.params.relayTo, args: false }] | ||
} else { | ||
return res.status(400).json({ error: 'Incorrect email address provided :' + req.params.relayTo }) | ||
} | ||
else { | ||
return res.status(400).json({ error: 'Incorrect email address provided :' + req.params.relayTo }); | ||
} | ||
} | ||
mailserver.relayMail(email, function(err) { | ||
if (err) return res.status(500).json({ error: err.message }); | ||
mailserver.relayMail(email, function (err) { | ||
if (err) return res.status(500).json({ error: err.message }) | ||
res.json(true); | ||
}); | ||
res.json(true) | ||
}) | ||
}) | ||
}) | ||
}); | ||
}); | ||
app.use(basePathname, router); | ||
}; | ||
app.use(basePathname, router) | ||
} |
121
lib/web.js
@@ -0,1 +1,2 @@ | ||
'use strict' | ||
@@ -6,43 +7,65 @@ /** | ||
var express = require('express'); | ||
var app = express(); | ||
var server = require('http').createServer(app); | ||
var io = require('socket.io')(); | ||
var routes = require('./routes'); | ||
var auth = require('./auth'); | ||
var logger = require('./logger'); | ||
var path = require('path'); | ||
const express = require('express') | ||
const http = require('http') | ||
const io = require('socket.io')() | ||
const routes = require('./routes') | ||
const auth = require('./auth') | ||
const logger = require('./logger') | ||
const path = require('path') | ||
const web = module.exports = {} | ||
/** | ||
* WebSockets | ||
* Keep record of all connections to close them on shutdown | ||
*/ | ||
const connections = {} | ||
function emitNewMail(socket) { | ||
return function(email) { | ||
socket.emit('newMail', email); | ||
}; | ||
function handleConnection (socket) { | ||
const key = `${socket.remoteAddress}:${socket.remotePort}` | ||
connections[key] = socket | ||
socket.on('close', function () { | ||
delete connections[key] | ||
}) | ||
} | ||
function emitDeleteMail(socket) { | ||
return function(email) { | ||
socket.emit('deleteMail', email); | ||
}; | ||
function closeConnections () { | ||
for (let key in connections) { | ||
connections[key].destroy() | ||
} | ||
} | ||
function webSocketConnection(mailserver) { | ||
/** | ||
* WebSockets | ||
*/ | ||
return function onConnection(socket) { | ||
function emitNewMail (socket) { | ||
return function (email) { | ||
socket.emit('newMail', email) | ||
} | ||
} | ||
// When a new email arrives, the 'new' event will be emitted | ||
mailserver.on('new', emitNewMail(socket)); | ||
mailserver.on('delete', emitDeleteMail(socket)); | ||
function emitDeleteMail (socket) { | ||
return function (email) { | ||
socket.emit('deleteMail', email) | ||
} | ||
} | ||
socket.on('disconnect', function(){ | ||
mailserver.removeListener('new', emitNewMail(socket)); | ||
mailserver.removeListener('delete', emitDeleteMail(socket)); | ||
}); | ||
function webSocketConnection (mailserver) { | ||
return function onConnection (socket) { | ||
const newHandlers = emitNewMail(socket) | ||
const deleteHandler = emitDeleteMail(socket) | ||
mailserver.on('new', newHandlers) | ||
mailserver.on('delete', deleteHandler) | ||
}; | ||
function removeListeners () { | ||
mailserver.removeListener('new', newHandlers) | ||
mailserver.removeListener('delete', deleteHandler) | ||
} | ||
socket.on('disconnect', removeListeners) | ||
} | ||
} | ||
web.server = null | ||
/** | ||
@@ -52,32 +75,44 @@ * Start the web server | ||
module.exports.start = function(port, host, mailserver, user, password, basePathname) { | ||
web.start = function (port, host, mailserver, user, password, basePathname) { | ||
const app = express() | ||
web.server = http.createServer(app) | ||
if (user && password) { | ||
app.use(auth(user, password)); | ||
app.use(auth(user, password)) | ||
} | ||
if (basePathname) { | ||
io.path(basePathname + '/socket.io'); | ||
io.path(basePathname + '/socket.io') | ||
} else { | ||
basePathname = '/'; | ||
basePathname = '/' | ||
} | ||
app.use(basePathname, express.static(path.join(__dirname, '../app'))); | ||
app.use(basePathname, express.static(path.join(__dirname, '../app'))) | ||
routes(app, mailserver, basePathname); | ||
routes(app, mailserver, basePathname) | ||
io.attach(server); | ||
io.on('connection', webSocketConnection(mailserver)); | ||
io.attach(web.server) | ||
io.on('connection', webSocketConnection(mailserver)) | ||
port = port || 1080; | ||
host = host || '0.0.0.0'; | ||
port = port || 1080 | ||
host = host || '0.0.0.0' | ||
server.listen(port, host); | ||
web.server.listen(port, host) | ||
logger.info('MailDev app running at %s:%s', host, port); | ||
web.server.on('connection', handleConnection) | ||
}; | ||
web.server.on('error', function (err) { | ||
logger.info('Could not start web server on ' + err.address + ':' + err.port + '\nPort already in use or insufficient rights to bind port') | ||
process.emit('SIGTERM') | ||
}) | ||
module.exports.close = function(callback) { | ||
server.close(callback); | ||
}; | ||
logger.info('MailDev webapp running at http://%s:%s', host, port) | ||
} | ||
web.close = function (callback) { | ||
if (!web.server) { | ||
return callback() | ||
} | ||
closeConnections() | ||
io.close(callback) | ||
} |
{ | ||
"name": "maildev", | ||
"description": "SMTP Server and Web Interface for reading and testing emails during development", | ||
"version": "0.14.0", | ||
"version": "1.0.0-rc1", | ||
"keywords": [ | ||
@@ -9,3 +9,7 @@ "email", | ||
"mail", | ||
"mailcatcher" | ||
"maildev", | ||
"mailcatcher", | ||
"testing", | ||
"development", | ||
"smtp" | ||
], | ||
@@ -25,3 +29,7 @@ "author": "Dan Farrelly", | ||
"scripts": { | ||
"test": "./node_modules/.bin/istanbul cover _mocha" | ||
"test": "standard && istanbul cover _mocha", | ||
"lint": "standard", | ||
"dev": "node ./scripts/dev.js & npm run css-watch", | ||
"css": "node-sass --output-style compressed -o app/styles assets/styles/style.scss", | ||
"css-watch": "node-sass -wr --output-style compressed -o app/styles assets/styles/style.scss" | ||
}, | ||
@@ -37,27 +45,28 @@ "main": "./index.js", | ||
"express": "4.13.4", | ||
"mailparser": "0.5.3", | ||
"mailparser": "0.6.2", | ||
"open": "0.0.5", | ||
"simplesmtp": "0.3.35", | ||
"smtp-connection": "2.3.1", | ||
"smtp-server": "1.4.0", | ||
"socket.io": "1.4.5", | ||
"smtp-server": "1.16.1", | ||
"socket.io": "1.7.3", | ||
"wildstring": "1.0.8" | ||
}, | ||
"devDependencies": { | ||
"grunt": "0.4.5", | ||
"grunt-concurrent": "2.2.1", | ||
"grunt-contrib-jshint": "1.0.0", | ||
"grunt-contrib-watch": "1.0.0", | ||
"grunt-nodemon": "0.4.1", | ||
"grunt-sass": "1.1.0", | ||
"expect": "1.20.2", | ||
"got": "^6.7.1", | ||
"http-proxy-middleware": "0.12.0", | ||
"istanbul": "0.4.4", | ||
"matchdep": "1.0.1", | ||
"mocha": "2.2.5", | ||
"istanbul": "^0.4.5", | ||
"mocha": "^3.3.0", | ||
"node-sass": "^4.5.2", | ||
"nodemailer": "2.3.0", | ||
"request": "2.69.0" | ||
"nodemon": "^1.11.0", | ||
"standard": "^10.0.2" | ||
}, | ||
"engines": { | ||
"node": ">=0.10.0" | ||
"node": ">=4.0.0" | ||
}, | ||
"standard": { | ||
"ignore": [ | ||
"app/components/" | ||
] | ||
} | ||
} |
@@ -6,6 +6,7 @@ # MailDev | ||
[![NPM Version](https://img.shields.io/npm/v/maildev.svg)](https://www.npmjs.com/package/maildev) | ||
[![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) | ||
**MailDev** is a simple way to test your project's generated emails during development with an easy to use web interface that runs on your machine built on top of [Node.js](http://www.nodejs.org). | ||
![MailDev Screenshot](https://dl.dropboxusercontent.com/u/50627698/maildev/screenshot-2015-03-29.png) | ||
![MailDev Screenshot](https://www.dropbox.com/s/xo7nk8zmtno53om/maildev-04-12-13.png?dl=1) | ||
@@ -30,21 +31,26 @@ ## Install & Run | ||
-h, --help output usage information | ||
-V, --version output the version number | ||
-s, --smtp <port> SMTP port to catch emails [1025] | ||
-w, --web <port> Port to run the Web GUI [1080] | ||
--ip <ip address> IP Address to bind services to [0.0.0.0] | ||
--outgoing-host <host> SMTP host for outgoing emails | ||
--outgoing-port <port> SMTP port for outgoing emails | ||
--outgoing-user <user> SMTP user for outgoing emails | ||
--outgoing-pass <pass> SMTP password for outgoing emails | ||
--outgoing-secure Use SMTP SSL for outgoing emails | ||
--auto-relay Use auto relay mode | ||
--auto-relay-rules <file> Filter rules for auto relay mode | ||
--incoming-user <user> SMTP user for incoming emails | ||
--incoming-pass <pass> SMTP password for incoming emails | ||
--web-ip <ip address> IP Address to bind HTTP service to, defaults to --ip | ||
--web-user <user> HTTP basic auth username | ||
--web-pass <pass> HTTP basic auth password | ||
-o, --open Open the Web GUI after startup | ||
-h, --help output usage information | ||
-V, --version output the version number | ||
-s, --smtp <port> SMTP port to catch emails [1025] | ||
-w, --web <port> Port to run the Web GUI [1080] | ||
--ip <ip address> IP Address to bind SMTP service to | ||
--outgoing-host <host> SMTP host for outgoing emails | ||
--outgoing-port <port> SMTP port for outgoing emails | ||
--outgoing-user <user> SMTP user for outgoing emails | ||
--outgoing-pass <password> SMTP password for outgoing emails | ||
--outgoing-secure Use SMTP SSL for outgoing emails | ||
--auto-relay Use auto-relay mode | ||
--auto-relay-rules <file> Filter rules for auto relay mode | ||
--incoming-user <user> SMTP user for incoming emails | ||
--incoming-pass <pass> SMTP password for incoming emails | ||
--web-ip <ip address> IP Address to bind HTTP service to, defaults to --ip | ||
--web-user <user> HTTP user for GUI | ||
--web-pass <password> HTTP password for GUI | ||
--base-pathname <path> base path for URLs | ||
--disable-web Disable the use of the web interface. Useful for unit testing | ||
--hide-extensions <extensions> Comma separated list of SMTP extensions to NOT advertise | ||
(STARTTLS, SMTPUTF8, PIPELINING, 8BITMIME) | ||
-o, --open Open the Web GUI after startup | ||
-v, --verbose | ||
--silent | ||
@@ -57,11 +63,11 @@ ## API | ||
```javascript | ||
var MailDev = require('maildev'); | ||
const MailDev = require('maildev') | ||
var maildev = new MailDev(); | ||
const maildev = new MailDev() | ||
maildev.listen(); | ||
maildev.listen() | ||
maildev.on('new', function(email){ | ||
maildev.on('new', function (email) { | ||
// We got a new email! | ||
}); | ||
}) | ||
``` | ||
@@ -124,7 +130,7 @@ | ||
```javascript | ||
var transport = nodemailer.createTransport({ | ||
const transport = nodemailer.createTransport({ | ||
port: 1025, | ||
ignoreTLS: true, | ||
// other settings... | ||
}); | ||
}) | ||
``` | ||
@@ -135,6 +141,6 @@ | ||
```javascript | ||
var transport = nodemailer.createTransport("SMTP", { | ||
const transport = nodemailer.createTransport('SMTP', { | ||
port: 1025, | ||
// other settings... | ||
}); | ||
}) | ||
``` | ||
@@ -175,14 +181,16 @@ | ||
# grunt-cli is needed by grunt; ignore this if already installed | ||
npm install -g grunt-cli | ||
npm install | ||
grunt dev | ||
npm run dev | ||
The "dev" task will run MailDev using nodemon and restart automatically when | ||
changes are detected. On `*.scss` file save, the css will also be recompiled. | ||
Using `test/send.js`, a few test emails will be sent every time the application | ||
restarts. | ||
The `grunt dev` task will run the project using nodemon and restart automatically when changes are detected. SASS files will be compiled automatically on save also. To trigger some emails for testing run `node test/send.js` in a separate shell. Please run jshint to your lint code before submitting a pull request; run `grunt jshint`. | ||
The project uses the [JavaScript Standard coding style](https://standardjs.com). | ||
To lint your code before submitting your PR, run `npm run lint`. | ||
To run the test suite: | ||
$ npm run test | ||
$ npm test | ||
@@ -193,3 +201,11 @@ ## [Changelog](https://github.com/djfarrelly/MailDev/releases) | ||
**MailDev** is built on using great open source projects including [Express](http://expressjs.com), [AngularJS](http://angularjs.org/), [Font Awesome](http://fontawesome.io/) and two great projects from [Andris Reinman](https://github.com/andris9): [Simple SMTP](https://github.com/andris9/simplesmtp) and [Mailparser](https://github.com/andris9/mailparser). Many thanks to Andris as his projects are the backbone of this app and to [MailCatcher](http://mailcatcher.me/) for the inspiration. | ||
**MailDev** is built on using great open source projects including | ||
[Express](http://expressjs.com), | ||
[AngularJS](http://angularjs.org/), | ||
[Font Awesome](http://fontawesome.io/) and two great projects from | ||
[Andris Reinman](https://github.com/andris9): | ||
[smtp-server](https://github.com/nodemailer/smtp-server) | ||
and [mailparser](https://github.com/nodemailer/mailparser). | ||
Many thanks to Andris as his projects are the backbone of this app and to | ||
[MailCatcher](http://mailcatcher.me/) for the inspiration. | ||
@@ -196,0 +212,0 @@ Additionally, thanks to all the awesome [contributors](https://github.com/djfarrelly/MailDev/graphs/contributors) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
9
9
211
1961857
50
21970
+ Addedaccepts@1.3.3(transitive)
+ Addedaddressparser@1.0.1(transitive)
+ Addedafter@0.8.2(transitive)
+ Addedbase64-arraybuffer@0.1.5(transitive)
+ Addedbase64id@1.0.0(transitive)
+ Addedcomponent-emitter@1.2.1(transitive)
+ Addedcookie@0.3.1(transitive)
+ Addeddebug@2.3.3(transitive)
+ Addedengine.io@1.8.3(transitive)
+ Addedengine.io-client@1.8.3(transitive)
+ Addedengine.io-parser@1.3.2(transitive)
+ Addedipv6-normalize@1.0.1(transitive)
+ Addedmailparser@0.6.2(transitive)
+ Addedmimelib@0.3.1(transitive)
+ Addedms@0.7.2(transitive)
+ Addednegotiator@0.6.1(transitive)
+ Addednodemailer-fetch@1.6.0(transitive)
+ Addednodemailer-shared@1.1.0(transitive)
+ Addedobject-assign@4.1.0(transitive)
+ Addedparsejson@0.0.3(transitive)
+ Addedparseqs@0.0.5(transitive)
+ Addedparseuri@0.0.5(transitive)
+ Addedsmtp-server@1.16.1(transitive)
+ Addedsocket.io@1.7.3(transitive)
+ Addedsocket.io-adapter@0.5.0(transitive)
+ Addedsocket.io-client@1.7.3(transitive)
+ Addedsocket.io-parser@2.3.1(transitive)
+ Addedws@1.1.2(transitive)
+ Addedwtf-8@1.0.0(transitive)
+ Addedxmlhttprequest-ssl@1.5.3(transitive)
- Removedsimplesmtp@0.3.35
- Removedaccepts@1.1.4(transitive)
- Removedaddressparser@0.3.2(transitive)
- Removedafter@0.8.1(transitive)
- Removedbase64-arraybuffer@0.1.2(transitive)
- Removedbase64id@0.1.0(transitive)
- Removedbenchmark@1.0.0(transitive)
- Removedcomponent-emitter@1.2.0(transitive)
- Removeddebug@0.7.4(transitive)
- Removedengine.io@1.6.8(transitive)
- Removedengine.io-client@1.6.8(transitive)
- Removedengine.io-parser@1.2.4(transitive)
- Removedhas-binary@0.1.6(transitive)
- Removedjson3@3.2.6(transitive)
- Removedmailparser@0.5.3(transitive)
- Removedmime-db@1.12.0(transitive)
- Removedmime-types@2.0.14(transitive)
- Removedmimelib@0.2.19(transitive)
- Removednegotiator@0.4.9(transitive)
- Removedparsejson@0.0.1(transitive)
- Removedparseqs@0.0.2(transitive)
- Removedparseuri@0.0.4(transitive)
- Removedrai@0.1.12(transitive)
- Removedsimplesmtp@0.3.35(transitive)
- Removedsmtp-server@1.4.0(transitive)
- Removedsocket.io@1.4.5(transitive)
- Removedsocket.io-adapter@0.4.0(transitive)
- Removedsocket.io-client@1.4.5(transitive)
- Removedsocket.io-parser@2.2.22.2.6(transitive)
- Removedutf8@2.1.0(transitive)
- Removedws@1.0.1(transitive)
- Removedxmlhttprequest-ssl@1.5.1(transitive)
- Removedxoauth2@0.1.8(transitive)
Updatedmailparser@0.6.2
Updatedsmtp-server@1.16.1
Updatedsocket.io@1.7.3