UNPKG

postmailer

Version:

HTTP POST -> SMTP proxy, as Express middleware

127 lines (112 loc) 4.18 kB
var fs = require('fs'); var stream = require('stream'); var parseHeader = require('parse-http-header'); function htmlEscape(value) { return value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/"/g, '&quot;'); } function enableCors(request, response) { response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Link, From, Subject, CC"); if (request.method === 'OPTIONS') { var methods = "HEAD,GET,POST,OPTIONS"; response.setHeader("Access-Control-Allow-Methods", methods); response.setHeader("Access-Control-Allow-Headers", request.header("Access-Control-Request-Headers")); } } function loadHtmlTemplateSync(file, minify) { var template = fs.readFileSync(file, {encoding: 'utf-8'}); if (minify) { try { template = require('html-minifier').minify(template, { collapseWhitespace: true, minifyJS: true, minifyCSS: true }); } catch (e) { console.error(e); // Nothing - the HTML minifier module might not be installed } } return function (match, httpUrl, wsUrl) { var subs = { url: httpUrl, name: match.name, 'ws-url': wsUrl } return template.replace(/\{\{(.*?)\}\}/g, function (match, key) { return htmlEscape((subs[key] || "") + ""); }); }; } // Pipe through to another stream without changes // - otherwise the "path" property can screw things up function copyStream(source) { var content = new stream.Transform(); content._transform = function (chunk, encoding, callback) { this.push(chunk, encoding); callback(); }; source.pipe(content); return content; } function expressHandler(hub, options) { options = options || {}; var htmlTemplate = null; var form = options.webForm || options.form; if (form !== false) { if (typeof form === 'function') { htmlTemplate = form; } else { var templateFile = (typeof form === 'string') ? form : 'rich'; if (/^[^\/]+$/.test(templateFile) && fs.existsSync(__dirname + '/templates/' + templateFile + '.html')) { templateFile = __dirname + '/templates/' + templateFile + '.html'; } htmlTemplate = loadHtmlTemplateSync(templateFile, options.minifyForm); } } return function (request, response, next) { // TODO: additionally add 'https' if it's secure var url = 'http://' + request.get('host') + request.originalUrl.replace(/\?.*/, ''); // Reconstruct (without query) // WebSocket version (used for template only) var wsUrl = 'ws://' + request.get('host') + request.originalUrl.replace(/\?.*/, ''); var match = hub.match(url); // TODO: filter on "acceptPost" flag if (!match) return next(); enableCors(request, response); function textResult(code, text) { response.setHeader('Content-Type', 'text/plain'); response.status(code).send(text); } if (request.method === 'OPTIONS') { return textResult(200, 'This endpoint implements the "HTTP POST mail" principle.\r\n\r\nhttps://www.npmjs.com/package/postmailer'); } else if (request.method === 'GET') { if (!htmlTemplate) return next(); response.setHeader('Content-Type', 'text/html'); var wsMatch = hub.match(wsUrl); response.send(htmlTemplate(match, url, wsMatch && wsUrl)); } else if (request.method === 'POST') { var fromHeader = request.header('from'); if (!fromHeader) { return textResult(400, '"From" header is required'); } else if (!/@/.test(fromHeader) && !/\:\/\//.test(fromHeader)) { return textResult(400, '"From" header must contain URL or email address'); } var filename = null; if (request.header('content-disposition')) { var parsed = parseHeader(request.header('content-disposition')); if (parsed[0] !== 'inline' && parsed.filename) filename = parsed.filename; } hub.emit('mail', { target: match, headers: request.headers, filename: filename, message: copyStream(request), length: parseFloat(request.header['content-length']) || null }); var messageHeaders = request.headers; return textResult(200, "Message accepted"); } else { return textResult(405, 'Unknown HTTP method: ' + request.method); } } } module.exports = expressHandler;