michael@0: const CC = Components.Constructor; michael@0: michael@0: const ServerSocket = CC("@mozilla.org/network/server-socket;1", michael@0: "nsIServerSocket", michael@0: "init"); michael@0: const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1", michael@0: "nsIBinaryInputStream", michael@0: "setInputStream"); michael@0: const DirectoryService = CC("@mozilla.org/file/directory_service;1", michael@0: "nsIProperties"); michael@0: const Process = CC("@mozilla.org/process/util;1", "nsIProcess", "init"); michael@0: michael@0: const currentThread = Cc["@mozilla.org/thread-manager;1"] michael@0: .getService().currentThread; michael@0: michael@0: var socks_test_server = null; michael@0: var socks_listen_port = -1; michael@0: michael@0: function getAvailableBytes(input) michael@0: { michael@0: var len = 0; michael@0: michael@0: try { michael@0: len = input.available(); michael@0: } catch (e) { michael@0: } michael@0: michael@0: return len; michael@0: } michael@0: michael@0: function runScriptSubprocess(script, args) michael@0: { michael@0: // logic copied from ted's crashreporter unit test michael@0: var ds = new DirectoryService(); michael@0: var bin = ds.get("CurProcD", Ci.nsILocalFile); michael@0: michael@0: bin.append("xpcshell"); michael@0: if (!bin.exists()) { michael@0: bin.leafName = "xpcshell.exe"; michael@0: do_check_true(bin.exists()); michael@0: if (!bin.exists()) michael@0: do_throw("Can't find xpcshell binary"); michael@0: } michael@0: michael@0: var script = do_get_file(script); michael@0: var proc = new Process(bin); michael@0: var args = [script.path].concat(args); michael@0: michael@0: proc.run(false, args, args.length); michael@0: michael@0: return proc; michael@0: } michael@0: michael@0: function buf2ip(buf) michael@0: { michael@0: if (buf.length == 16) { michael@0: var ip = (buf[0] << 4 | buf[1]).toString(16) + ':' + michael@0: (buf[2] << 4 | buf[3]).toString(16) + ':' + michael@0: (buf[4] << 4 | buf[5]).toString(16) + ':' + michael@0: (buf[6] << 4 | buf[7]).toString(16) + ':' + michael@0: (buf[8] << 4 | buf[9]).toString(16) + ':' + michael@0: (buf[10] << 4 | buf[11]).toString(16) + ':' + michael@0: (buf[12] << 4 | buf[13]).toString(16) + ':' + michael@0: (buf[14] << 4 | buf[15]).toString(16); michael@0: for (var i = 8; i >= 2; i--) { michael@0: var re = new RegExp("(^|:)(0(:|$)){" + i + "}"); michael@0: var shortip = ip.replace(re, '::'); michael@0: if (shortip != ip) { michael@0: return shortip; michael@0: } michael@0: } michael@0: return ip; michael@0: } else { michael@0: return buf.join('.'); michael@0: } michael@0: } michael@0: michael@0: function buf2int(buf) michael@0: { michael@0: var n = 0; michael@0: michael@0: for (var i in buf) { michael@0: n |= buf[i] << ((buf.length - i - 1) * 8); michael@0: } michael@0: michael@0: return n; michael@0: } michael@0: michael@0: function buf2str(buf) michael@0: { michael@0: return String.fromCharCode.apply(null, buf); michael@0: } michael@0: michael@0: const STATE_WAIT_GREETING = 1; michael@0: const STATE_WAIT_SOCKS4_REQUEST = 2; michael@0: const STATE_WAIT_SOCKS4_USERNAME = 3; michael@0: const STATE_WAIT_SOCKS4_HOSTNAME = 4; michael@0: const STATE_WAIT_SOCKS5_GREETING = 5; michael@0: const STATE_WAIT_SOCKS5_REQUEST = 6; michael@0: const STATE_WAIT_PONG = 7; michael@0: const STATE_GOT_PONG = 8; michael@0: michael@0: function SocksClient(server, client_in, client_out) michael@0: { michael@0: this.server = server; michael@0: this.type = ''; michael@0: this.username = ''; michael@0: this.dest_name = ''; michael@0: this.dest_addr = []; michael@0: this.dest_port = []; michael@0: michael@0: this.client_in = client_in; michael@0: this.client_out = client_out; michael@0: this.inbuf = []; michael@0: this.outbuf = String(); michael@0: this.state = STATE_WAIT_GREETING; michael@0: this.waitRead(this.client_in); michael@0: } michael@0: SocksClient.prototype = { michael@0: onInputStreamReady: function(input) michael@0: { michael@0: var len = getAvailableBytes(input); michael@0: michael@0: if (len == 0) { michael@0: print('server: client closed!'); michael@0: do_check_eq(this.state, STATE_GOT_PONG); michael@0: this.server.testCompleted(this); michael@0: return; michael@0: } michael@0: michael@0: var bin = new BinaryInputStream(input); michael@0: var data = bin.readByteArray(len); michael@0: this.inbuf = this.inbuf.concat(data); michael@0: michael@0: switch (this.state) { michael@0: case STATE_WAIT_GREETING: michael@0: this.checkSocksGreeting(); michael@0: break; michael@0: case STATE_WAIT_SOCKS4_REQUEST: michael@0: this.checkSocks4Request(); michael@0: break; michael@0: case STATE_WAIT_SOCKS4_USERNAME: michael@0: this.checkSocks4Username(); michael@0: break; michael@0: case STATE_WAIT_SOCKS4_HOSTNAME: michael@0: this.checkSocks4Hostname(); michael@0: break; michael@0: case STATE_WAIT_SOCKS5_GREETING: michael@0: this.checkSocks5Greeting(); michael@0: break; michael@0: case STATE_WAIT_SOCKS5_REQUEST: michael@0: this.checkSocks5Request(); michael@0: break; michael@0: case STATE_WAIT_PONG: michael@0: this.checkPong(); michael@0: break; michael@0: default: michael@0: do_throw("server: read in invalid state!"); michael@0: } michael@0: michael@0: this.waitRead(input); michael@0: }, michael@0: michael@0: onOutputStreamReady: function(output) michael@0: { michael@0: var len = output.write(this.outbuf, this.outbuf.length); michael@0: if (len != this.outbuf.length) { michael@0: this.outbuf = this.outbuf.substring(len); michael@0: this.waitWrite(output); michael@0: } else michael@0: this.outbuf = String(); michael@0: }, michael@0: michael@0: waitRead: function(input) michael@0: { michael@0: input.asyncWait(this, 0, 0, currentThread); michael@0: }, michael@0: michael@0: waitWrite: function(output) michael@0: { michael@0: output.asyncWait(this, 0, 0, currentThread); michael@0: }, michael@0: michael@0: write: function(buf) michael@0: { michael@0: this.outbuf += buf; michael@0: this.waitWrite(this.client_out); michael@0: }, michael@0: michael@0: checkSocksGreeting: function() michael@0: { michael@0: if (this.inbuf.length == 0) michael@0: return; michael@0: michael@0: if (this.inbuf[0] == 4) { michael@0: print('server: got socks 4'); michael@0: this.type = 'socks4'; michael@0: this.state = STATE_WAIT_SOCKS4_REQUEST; michael@0: this.checkSocks4Request(); michael@0: } else if (this.inbuf[0] == 5) { michael@0: print('server: got socks 5'); michael@0: this.type = 'socks'; michael@0: this.state = STATE_WAIT_SOCKS5_GREETING; michael@0: this.checkSocks5Greeting(); michael@0: } else { michael@0: do_throw("Unknown socks protocol!"); michael@0: } michael@0: }, michael@0: michael@0: checkSocks4Request: function() michael@0: { michael@0: if (this.inbuf.length < 8) michael@0: return; michael@0: michael@0: do_check_eq(this.inbuf[1], 0x01); michael@0: michael@0: this.dest_port = this.inbuf.slice(2, 4); michael@0: this.dest_addr = this.inbuf.slice(4, 8); michael@0: michael@0: this.inbuf = this.inbuf.slice(8); michael@0: this.state = STATE_WAIT_SOCKS4_USERNAME; michael@0: this.checkSocks4Username(); michael@0: }, michael@0: michael@0: readString: function() michael@0: { michael@0: var i = this.inbuf.indexOf(0); michael@0: var str = null; michael@0: michael@0: if (i >= 0) { michael@0: var buf = this.inbuf.slice(0,i); michael@0: str = buf2str(buf); michael@0: this.inbuf = this.inbuf.slice(i+1); michael@0: } michael@0: michael@0: return str; michael@0: }, michael@0: michael@0: checkSocks4Username: function() michael@0: { michael@0: var str = this.readString(); michael@0: michael@0: if (str == null) michael@0: return; michael@0: michael@0: this.username = str; michael@0: if (this.dest_addr[0] == 0 && michael@0: this.dest_addr[1] == 0 && michael@0: this.dest_addr[2] == 0 && michael@0: this.dest_addr[3] != 0) { michael@0: this.state = STATE_WAIT_SOCKS4_HOSTNAME; michael@0: this.checkSocks4Hostname(); michael@0: } else { michael@0: this.sendSocks4Response(); michael@0: } michael@0: }, michael@0: michael@0: checkSocks4Hostname: function() michael@0: { michael@0: var str = this.readString(); michael@0: michael@0: if (str == null) michael@0: return; michael@0: michael@0: this.dest_name = str; michael@0: this.sendSocks4Response(); michael@0: }, michael@0: michael@0: sendSocks4Response: function() michael@0: { michael@0: this.outbuf = '\x00\x5a\x00\x00\x00\x00\x00\x00'; michael@0: this.sendPing(); michael@0: }, michael@0: michael@0: checkSocks5Greeting: function() michael@0: { michael@0: if (this.inbuf.length < 2) michael@0: return; michael@0: var nmethods = this.inbuf[1]; michael@0: if (this.inbuf.length < 2 + nmethods) michael@0: return; michael@0: michael@0: do_check_true(nmethods >= 1); michael@0: var methods = this.inbuf.slice(2, 2 + nmethods); michael@0: do_check_true(0 in methods); michael@0: michael@0: this.inbuf = []; michael@0: this.state = STATE_WAIT_SOCKS5_REQUEST; michael@0: this.write('\x05\x00'); michael@0: }, michael@0: michael@0: checkSocks5Request: function() michael@0: { michael@0: if (this.inbuf.length < 4) michael@0: return; michael@0: michael@0: do_check_eq(this.inbuf[0], 0x05); michael@0: do_check_eq(this.inbuf[1], 0x01); michael@0: do_check_eq(this.inbuf[2], 0x00); michael@0: michael@0: var atype = this.inbuf[3]; michael@0: var len; michael@0: var name = false; michael@0: michael@0: switch (atype) { michael@0: case 0x01: michael@0: len = 4; michael@0: break; michael@0: case 0x03: michael@0: len = this.inbuf[4]; michael@0: name = true; michael@0: break; michael@0: case 0x04: michael@0: len = 16; michael@0: break; michael@0: default: michael@0: do_throw("Unknown address type " + atype); michael@0: } michael@0: michael@0: if (name) { michael@0: if (this.inbuf.length < 4 + len + 1 + 2) michael@0: return; michael@0: michael@0: buf = this.inbuf.slice(5, 5 + len); michael@0: this.dest_name = buf2str(buf); michael@0: len += 1; michael@0: } else { michael@0: if (this.inbuf.length < 4 + len + 2) michael@0: return; michael@0: michael@0: this.dest_addr = this.inbuf.slice(4, 4 + len); michael@0: } michael@0: michael@0: len += 4; michael@0: this.dest_port = this.inbuf.slice(len, len + 2); michael@0: this.inbuf = this.inbuf.slice(len + 2); michael@0: this.sendSocks5Response(); michael@0: }, michael@0: michael@0: sendSocks5Response: function() michael@0: { michael@0: if (this.dest_addr.length == 16) { michael@0: // send a successful response with the address, [::1]:80 michael@0: this.outbuf += '\x05\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x80'; michael@0: } else { michael@0: // send a successful response with the address, 127.0.0.1:80 michael@0: this.outbuf += '\x05\x00\x00\x01\x7f\x00\x00\x01\x00\x80'; michael@0: } michael@0: this.sendPing(); michael@0: }, michael@0: michael@0: sendPing: function() michael@0: { michael@0: print('server: sending ping'); michael@0: this.state = STATE_WAIT_PONG; michael@0: this.outbuf += "PING!"; michael@0: this.inbuf = []; michael@0: this.waitWrite(this.client_out); michael@0: this.waitRead(this.client_in); michael@0: }, michael@0: michael@0: checkPong: function() michael@0: { michael@0: var pong = buf2str(this.inbuf); michael@0: do_check_eq(pong, "PONG!"); michael@0: this.state = STATE_GOT_PONG; michael@0: this.waitRead(this.client_in); michael@0: }, michael@0: michael@0: close: function() michael@0: { michael@0: this.client_in.close(); michael@0: this.client_out.close(); michael@0: } michael@0: }; michael@0: michael@0: function SocksTestServer() michael@0: { michael@0: this.listener = ServerSocket(-1, true, -1); michael@0: socks_listen_port = this.listener.port; michael@0: print('server: listening on', socks_listen_port); michael@0: this.listener.asyncListen(this); michael@0: this.test_cases = []; michael@0: this.client_connections = []; michael@0: this.client_subprocess = null; michael@0: // port is used as the ID for test cases michael@0: this.test_port_id = 8000; michael@0: this.tests_completed = 0; michael@0: } michael@0: SocksTestServer.prototype = { michael@0: addTestCase: function(test) michael@0: { michael@0: test.finished = false; michael@0: test.port = this.test_port_id++; michael@0: this.test_cases.push(test); michael@0: }, michael@0: michael@0: pickTest: function(id) michael@0: { michael@0: for (var i in this.test_cases) { michael@0: var test = this.test_cases[i]; michael@0: if (test.port == id) { michael@0: this.tests_completed++; michael@0: return test; michael@0: } michael@0: } michael@0: do_throw("No test case with id " + id); michael@0: }, michael@0: michael@0: testCompleted: function(client) michael@0: { michael@0: var port_id = buf2int(client.dest_port); michael@0: var test = this.pickTest(port_id); michael@0: michael@0: print('server: test finished', test.port); michael@0: do_check_true(test != null); michael@0: do_check_eq(test.expectedType || test.type, client.type); michael@0: do_check_eq(test.port, port_id); michael@0: michael@0: if (test.remote_dns) michael@0: do_check_eq(test.host, client.dest_name); michael@0: else michael@0: do_check_eq(test.host, buf2ip(client.dest_addr)); michael@0: michael@0: if (this.test_cases.length == this.tests_completed) { michael@0: print('server: all tests completed'); michael@0: this.close(); michael@0: do_test_finished(); michael@0: } michael@0: }, michael@0: michael@0: runClientSubprocess: function() michael@0: { michael@0: var argv = []; michael@0: michael@0: // marshaled: socks_ver|server_port|dest_host|dest_port| michael@0: for each (var test in this.test_cases) { michael@0: var arg = test.type + '|' + michael@0: String(socks_listen_port) + '|' + michael@0: test.host + '|' + test.port + '|'; michael@0: if (test.remote_dns) michael@0: arg += 'remote'; michael@0: else michael@0: arg += 'local'; michael@0: print('server: using test case', arg); michael@0: argv.push(arg); michael@0: } michael@0: michael@0: this.client_subprocess = runScriptSubprocess( michael@0: 'socks_client_subprocess.js', argv); michael@0: }, michael@0: michael@0: onSocketAccepted: function(socket, trans) michael@0: { michael@0: print('server: got client connection'); michael@0: var input = trans.openInputStream(0, 0, 0); michael@0: var output = trans.openOutputStream(0, 0, 0); michael@0: var client = new SocksClient(this, input, output); michael@0: this.client_connections.push(client); michael@0: }, michael@0: michael@0: onStopListening: function(socket) michael@0: { michael@0: }, michael@0: michael@0: close: function() michael@0: { michael@0: if (this.client_subprocess) { michael@0: try { michael@0: this.client_subprocess.kill(); michael@0: } catch (x) { michael@0: do_note_exception(x, 'Killing subprocess failed'); michael@0: } michael@0: this.client_subprocess = null; michael@0: } michael@0: for each (var client in this.client_connections) michael@0: client.close(); michael@0: this.client_connections = []; michael@0: if (this.listener) { michael@0: this.listener.close(); michael@0: this.listener = null; michael@0: } michael@0: } michael@0: }; michael@0: michael@0: function test_timeout() michael@0: { michael@0: socks_test_server.close(); michael@0: do_throw("SOCKS test took too long!"); michael@0: } michael@0: michael@0: function run_test() michael@0: { michael@0: socks_test_server = new SocksTestServer(); michael@0: michael@0: socks_test_server.addTestCase({ michael@0: type: "socks4", michael@0: host: '127.0.0.1', michael@0: remote_dns: false, michael@0: }); michael@0: socks_test_server.addTestCase({ michael@0: type: "socks4", michael@0: host: '12345.xxx', michael@0: remote_dns: true, michael@0: }); michael@0: socks_test_server.addTestCase({ michael@0: type: "socks4", michael@0: expectedType: "socks", michael@0: host: '::1', michael@0: remote_dns: false, michael@0: }); michael@0: socks_test_server.addTestCase({ michael@0: type: "socks", michael@0: host: '127.0.0.1', michael@0: remote_dns: false, michael@0: }); michael@0: socks_test_server.addTestCase({ michael@0: type: "socks", michael@0: host: 'abcdefg.xxx', michael@0: remote_dns: true, michael@0: }); michael@0: socks_test_server.addTestCase({ michael@0: type: "socks", michael@0: host: '::1', michael@0: remote_dns: false, michael@0: }); michael@0: socks_test_server.runClientSubprocess(); michael@0: michael@0: do_timeout(120 * 1000, test_timeout); michael@0: do_test_pending(); michael@0: }