postmailer
Version:
HTTP POST -> SMTP proxy, as Express middleware
142 lines (126 loc) • 3.84 kB
JavaScript
var crypto = require('crypto');
var WebSocketServer = require('ws').Server;
function parseHeaders(headers) {
var matches = headers.match(/(^|\n)[a-z\-]+:([^\r\n]|\r?\n[ \t])+/ig);
if (!matches) return null;
var result = {};
matches.forEach(function (match) {
var key = match.split(':', 1)[0];
var value = match.substring(key.length + 1).replace(/(^\s+)/, '').replace(/\s+$/, '');
key = key.toLowerCase().replace('\n', '');
value = value.replace(/\r?\n[ \t]+/g, ' ');
result[key] = value;
});
return result;
}
function headersToString(headers) {
var result = '';
var keys = Object.keys(headers);
keys.sort();
keys.forEach(function (key) {
var value = headers[key].replace(/(^\s+|\s+$)/g, '');
key = key.replace(/(^|\-)./g, function (s) {
return s.toUpperCase();
});
result += key + ': ' + value + '\r\n';
});
return result;
}
function matchRequest(hub, request) {
// TODO: additionally add 'wss' if it's secure
var url = 'ws://' + (request.headers['host']) + request.url.replace(/\?.*/, ''); // Reconstruct (without query)
return hub.match(url);
return url;
}
function handleChat(hub, options) {
options = options || {};
options.verifyClient = function (info) {
return !!matchRequest(hub, info.req);
};
var wsServer = new WebSocketServer(options);
var connectionIdMap = {};
hub.handle('chat', function (chat, next) {
var targetUrls = chat.target.urls;
var connection = null;
for (var i = 0; i < targetUrls.length; i++) {
connection = connectionIdMap[targetUrls[i]];
if (connection) break;
}
if (!connection) return next();
var headers = headersToString(chat.headers);
var prefix = '';
var message = chat.message;
if (headers && headers !== connection.sentHeaders) {
connection.sentHeaders = headers;
prefix = headers + '\r\n';
} else if (/^([^\r\n]+\r?\n)*\r?\n/.test(message.toString('ascii'))) {
prefix = '\r\n';
}
if (prefix) {
if (typeof message === 'string') {
message = prefix + message;
} else {
message = Buffer.concat([new Buffer(prefix, 'ascii'), message]);
}
}
connection.socket.send(message);
});
wsServer.on('connection', function (ws) {
// TODO: assemble ws:// URL from host and match on that
var target = matchRequest(hub, ws.upgradeReq);
if (!target) return ws.close();
// Events, and management within the map
var connectionId = 'chat:' + crypto.randomBytes(24).toString('base64').replace(/\//g, '_').replace(/\+/g, '-');
connectionIdMap[connectionId] = {
socket: ws,
sentHeaders: 'Content-Type: text/plain'
};
var source = {
url: connectionId,
name: 'WebSocket chat'
};
hub.emit('chat-open', {
source: source,
target: target
});
ws.on('close', function () {
hub.emit('chat-close', {
source: source,
target: target
});
delete connectionIdMap[connectionId];
});
ws.on('error', function (error) {
hub.emit('chat-error', {
source: source,
target: target,
error: error
});
hub.emit('error', error);
delete connectionIdMap[connectionId];
});
var latestHeaders = parseHeaders('Content-Type: text/plain');
var messageCounter = 0;
ws.on('message', function (message) {
var stringMessage = (typeof message === 'string') ? message : message.toString('ascii');
var headerMatch = stringMessage.match(/^([^\r\n]+\r?\n)*\r?\n/);
if (headerMatch) {
latestHeaders = parseHeaders(headerMatch[0]) || latestHeaders;
if (typeof message === 'string') {
message = message.substring(headerMatch[0].length);
} else {
message = message.slice(headerMatch[0].length);
}
}
stringMessage = null;
hub.emit('chat', {
target: target,
source: source,
headers: JSON.parse(JSON.stringify(latestHeaders)),
message: message
});
});
});
}
module.exports = handleChat;
return;