Node impl to use einaros/ws and support HyBi.
Also adds web serving functionality and Flash policy service.
This commit is contained in:
parent
9decfe1c95
commit
7ea468b5d7
|
@ -1,227 +1,102 @@
|
||||||
// A WebSocket to TCP socket proxy
|
// A WebSocket to TCP socket proxy
|
||||||
// Copyright 2010 Joel Martin
|
// Copyright 2012 Joel Martin
|
||||||
// Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
|
// Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
|
||||||
|
|
||||||
var net = require('net'),
|
// Known to work with node 0.6
|
||||||
sys = require('sys'),
|
// Requires node modules: ws, base64, optimist and policyfile
|
||||||
crypto = require('crypto'),
|
// npm install ws base64 optimist policyfile
|
||||||
source_arg, source_host, source_port,
|
|
||||||
target_arg, target_host, target_port;
|
|
||||||
|
|
||||||
// md5 calculation borrowed from Socket.IO (MIT license)
|
var argv = require('optimist').argv,
|
||||||
function gen_md5(headers, k3) {
|
net = require('net'),
|
||||||
var k1 = headers['sec-websocket-key1'],
|
http = require('http'),
|
||||||
k2 = headers['sec-websocket-key2'],
|
url = require('url'),
|
||||||
md5 = crypto.createHash('md5');
|
path = require('path'),
|
||||||
|
fs = require('fs'),
|
||||||
|
policyfile = require('policyfile'),
|
||||||
|
|
||||||
[k1, k2].forEach(function(k){
|
base64 = require('base64/build/Release/base64'),
|
||||||
var n = parseInt(k.replace(/[^\d]/g, '')),
|
Buffer = require('buffer').Buffer,
|
||||||
spaces = k.replace(/[^ ]/g, '').length;
|
WebSocketServer = require('ws').Server,
|
||||||
|
|
||||||
if (spaces === 0 || n % spaces !== 0){
|
httpServer, wsServer,
|
||||||
return false;
|
source_host, source_port, target_host, target_port,
|
||||||
}
|
web_path = null;
|
||||||
|
|
||||||
n /= spaces;
|
|
||||||
|
|
||||||
md5.update(String.fromCharCode(
|
// Handle new WebSocket client
|
||||||
n >> 24 & 0xFF,
|
new_client = function(client) {
|
||||||
n >> 16 & 0xFF,
|
console.log('WebSocket client connected');
|
||||||
n >> 8 & 0xFF,
|
//console.log('protocol: ' + client.protocol);
|
||||||
n & 0xFF));
|
|
||||||
|
var target = net.createConnection(target_port,target_host);
|
||||||
|
target.on('begin', function() {
|
||||||
|
console.log('connected to target');
|
||||||
|
});
|
||||||
|
target.on('data', function(data) {
|
||||||
|
client.send(base64.encode(new Buffer(data)));
|
||||||
|
});
|
||||||
|
target.on('end', function() {
|
||||||
|
console.log('target disconnected');
|
||||||
});
|
});
|
||||||
|
|
||||||
md5.update(k3.toString('binary'));
|
client.on('message', function(msg) {
|
||||||
|
//console.log('got some message');
|
||||||
|
target.write(base64.decode(msg),'binary');
|
||||||
|
});
|
||||||
|
client.on('close', function(code, reason) {
|
||||||
|
console.log('WebSocket client disconnected: ' + code + ' [' + reason + ']');
|
||||||
|
});
|
||||||
|
client.on('error', function(a) {
|
||||||
|
console.log('WebSocket client error: ' + a);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return md5.digest('binary');
|
|
||||||
|
// Send an HTTP error response
|
||||||
|
http_error = function (response, code, msg) {
|
||||||
|
response.writeHead(code, {"Content-Type": "text/plain"});
|
||||||
|
response.write(msg + "\n");
|
||||||
|
response.end();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function encode(buf) {
|
// Process an HTTP static file request
|
||||||
return String.fromCharCode(0) +
|
http_request = function (request, response) {
|
||||||
buf.toString('base64', 0) +
|
// console.log("pathname: " + url.parse(req.url).pathname);
|
||||||
String.fromCharCode(255);
|
// res.writeHead(200, {'Content-Type': 'text/plain'});
|
||||||
}
|
// res.end('okay');
|
||||||
|
|
||||||
function decode(data) {
|
if (! argv.web) {
|
||||||
var i, len = 0, strs, retstrs = [],
|
return http_error(response, 403, "403 Permission Denied");
|
||||||
buf = new Buffer(data.length),
|
|
||||||
str = data.toString('binary', 1, data.length-1);
|
|
||||||
|
|
||||||
if (str.indexOf('\xff') > -1) {
|
|
||||||
// We've gotten multiple frames at once
|
|
||||||
strs = str.split('\xff\x00')
|
|
||||||
for (i = 0; i < strs.length; i++) {
|
|
||||||
len = buf.write(strs[i], 0, 'base64');
|
|
||||||
retstrs.push(buf.toString('binary', 0, len));
|
|
||||||
}
|
|
||||||
return retstrs.join("");
|
|
||||||
} else {
|
|
||||||
len = buf.write(str, 0, 'base64');
|
|
||||||
return buf.toString('binary', 0, len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var server = net.createServer(function (client) {
|
|
||||||
var handshake = "", headers = {}, header,
|
|
||||||
version, path, k1, k2, k3, target = null;
|
|
||||||
|
|
||||||
function cleanup() {
|
|
||||||
client.end();
|
|
||||||
if (target) {
|
|
||||||
target.end();
|
|
||||||
target = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function do_handshake(data) {
|
var uri = url.parse(request.url).pathname
|
||||||
var i, idx, dlen = data.length, lines, location, rheaders,
|
, filename = path.join(argv.web, uri);
|
||||||
sec_hdr;
|
|
||||||
//sys.log("received handshake data: " + data);
|
path.exists(filename, function(exists) {
|
||||||
handshake += data.toString('utf8');
|
if(!exists) {
|
||||||
if ((data[dlen-12] != 13) ||
|
return http_error(response, 404, "404 Not Found");
|
||||||
(data[dlen-11] != 10) ||
|
|
||||||
(data[dlen-10] != 13) ||
|
|
||||||
(data[dlen-9] != 10)) {
|
|
||||||
//sys.log("Got partial handshake");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
//sys.log("Got whole handshake");
|
|
||||||
|
|
||||||
if (handshake.indexOf('GET ') != 0) {
|
|
||||||
sys.error("Got invalid handshake");
|
|
||||||
client.end();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lines = handshake.split('\r\n');
|
if (fs.statSync(filename).isDirectory()) {
|
||||||
path = lines[0].split(' ')[1];
|
filename += '/index.html';
|
||||||
//sys.log("path: " + path);
|
}
|
||||||
|
|
||||||
k3 = data.slice(dlen-8, dlen);
|
fs.readFile(filename, "binary", function(err, file) {
|
||||||
for (i = 1; i < lines.length; i++) {
|
if(err) {
|
||||||
//sys.log("lines[i]: " + lines[i]);
|
return http_error(response, 500, err);
|
||||||
if (lines[i].length == 0) { break; }
|
|
||||||
idx = lines[i].indexOf(': ');
|
|
||||||
if (idx < 0) {
|
|
||||||
sys.error("Got invalid handshake header");
|
|
||||||
client.end();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
header = lines[i].slice(0, idx).toLowerCase();
|
|
||||||
headers[header] = lines[i].slice(idx+2);
|
|
||||||
}
|
|
||||||
//console.dir(headers);
|
|
||||||
//sys.log("k3: " + k3 + ", k3.length: " + k3.length);
|
|
||||||
|
|
||||||
if (headers.upgrade !== 'WebSocket') {
|
response.writeHead(200);
|
||||||
sys.error("Upgrade header is not 'WebSocket'");
|
response.write(file, "binary");
|
||||||
client.end();
|
response.end();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
location = (headers.origin.substr(0, 5) == 'https' ? 'wss' : 'ws')
|
|
||||||
+ '://' + headers.host + path;
|
|
||||||
//sys.log("location: " + location);
|
|
||||||
|
|
||||||
if ('sec-websocket-key1' in headers) {
|
|
||||||
version = 76;
|
|
||||||
sec_hdr = "Sec-";
|
|
||||||
} else {
|
|
||||||
version = 75;
|
|
||||||
sec_hdr = "";
|
|
||||||
}
|
|
||||||
sys.log("using protocol version " + version);
|
|
||||||
|
|
||||||
rheaders = [
|
|
||||||
'HTTP/1.1 101 WebSocket Protocol Handshake',
|
|
||||||
'Upgrade: WebSocket',
|
|
||||||
'Connection: Upgrade',
|
|
||||||
sec_hdr + 'WebSocket-Origin: ' + headers.origin,
|
|
||||||
sec_hdr + 'WebSocket-Location: ' + location
|
|
||||||
];
|
|
||||||
if ('sec-websocket-protocol' in headers) {
|
|
||||||
rheaders.push('Sec-WebSocket-Protocol: ' + headers['sec-websocket-protocol']);
|
|
||||||
}
|
|
||||||
rheaders.push('');
|
|
||||||
if (version === 76) {
|
|
||||||
rheaders.push(gen_md5(headers, k3));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Switch listener to normal data path
|
|
||||||
client.on('data', client_data);
|
|
||||||
//client.setEncoding('utf8');
|
|
||||||
client.removeListener('data', do_handshake);
|
|
||||||
// Do not delay writes
|
|
||||||
client.setNoDelay(true);
|
|
||||||
|
|
||||||
// Send the handshake response
|
|
||||||
try {
|
|
||||||
//sys.log("response: " + rheaders.join('\r\n'));
|
|
||||||
client.write(rheaders.join('\r\n'), 'binary');
|
|
||||||
} catch(e) {
|
|
||||||
sys.error("Failed to send handshake response");
|
|
||||||
client.end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a connection to the target
|
|
||||||
target = net.createConnection(target_port, target_host);
|
|
||||||
target.on('data', target_data);
|
|
||||||
target.on('end', function () {
|
|
||||||
sys.log("received target end");
|
|
||||||
cleanup();
|
|
||||||
});
|
});
|
||||||
target.on('error', function (exc) {
|
|
||||||
sys.log("received target error: " + exc);
|
|
||||||
cleanup();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function client_data(data) {
|
|
||||||
var ret;
|
|
||||||
//sys.log("received client data: " + data);
|
|
||||||
//sys.log(" decoded: " + decode(data));
|
|
||||||
try {
|
|
||||||
ret = target.write(decode(data), 'binary');
|
|
||||||
if (! ret) {
|
|
||||||
sys.log("target write returned false");
|
|
||||||
}
|
|
||||||
} catch(e) {
|
|
||||||
sys.log("fatal error writing to target");
|
|
||||||
cleanup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function target_data(data) {
|
|
||||||
//sys.log("received target data: " + data);
|
|
||||||
//sys.log(" encoded: " + encode(data));
|
|
||||||
try {
|
|
||||||
client.write(encode(data), 'binary');
|
|
||||||
} catch(e) {
|
|
||||||
sys.log("fatal error writing to client");
|
|
||||||
cleanup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.on('connect', function () {
|
|
||||||
sys.log("Got client connection");
|
|
||||||
});
|
});
|
||||||
client.on('data', do_handshake);
|
};
|
||||||
client.on('end', function () {
|
|
||||||
sys.log("recieved client end");
|
|
||||||
cleanup();
|
|
||||||
});
|
|
||||||
client.on('error', function (exc) {
|
|
||||||
sys.log("recieved client error: " + exc);
|
|
||||||
cleanup();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// parse source and target arguments into parts
|
||||||
// parse source and target into parts
|
source_arg = argv._[0].toString();
|
||||||
source_arg = process.argv[2];
|
target_arg = argv._[1].toString();
|
||||||
target_arg = process.argv[3];
|
|
||||||
try {
|
try {
|
||||||
var idx;
|
var idx;
|
||||||
idx = source_arg.indexOf(":");
|
idx = source_arg.indexOf(":");
|
||||||
|
@ -248,6 +123,18 @@ try {
|
||||||
process.exit(2);
|
process.exit(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
sys.log("source: " + source_host + ":" + source_port);
|
console.log("WebSocket settings: ");
|
||||||
sys.log("target: " + target_host + ":" + target_port);
|
console.log(" - proxying from " + source_host + ":" + source_port +
|
||||||
server.listen(source_port, source_host);
|
" to " + target_host + ":" + target_port);
|
||||||
|
if (argv.web) {
|
||||||
|
console.log(" - Web server active. Serving: " + argv.web);
|
||||||
|
}
|
||||||
|
|
||||||
|
httpServer = http.createServer(http_request);
|
||||||
|
httpServer.listen(source_port, function() {
|
||||||
|
wsServer = new WebSocketServer({server: httpServer});
|
||||||
|
wsServer.on('connection', new_client);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Attach Flash policyfile answer service
|
||||||
|
policyfile.createServer().listen(-1, httpServer);
|
||||||
|
|
Loading…
Reference in New Issue