Bug 280661 - Test case; r=ted
authorchrisd.mang@gmail.com
Wed, 23 Mar 2011 23:42:04 -0400
changeset 63797 2def74c0f2772aefab84802dbfc15fa71fea6d7c
parent 63796 739bfb41cd012f739ebd361418af7125e593e784
child 63798 a7df8d22d272410cfcb4a61268c6629aeb315376
child 63804 e4a5ec8c34a456c7276d29ba9f2fd4d31b6bc1f8
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersted
bugs280661
milestone2.2a1pre
Bug 280661 - Test case; r=ted
netwerk/test/unit/socks_client_subprocess.js
netwerk/test/unit/test_socks.js
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/socks_client_subprocess.js
@@ -0,0 +1,44 @@
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cu = Components.utils;
+const CC = Components.Constructor;
+
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+                             "nsIBinaryInputStream",
+                             "setInputStream");
+const ProtocolProxyService = CC("@mozilla.org/network/protocol-proxy-service;1",
+                                "nsIProtocolProxyService");
+var sts = Cc["@mozilla.org/network/socket-transport-service;1"]
+          .getService(Ci.nsISocketTransportService);
+
+function launchConnection(socks_vers, socks_port, dest_host, dest_port, dns)
+{
+  var pi_flags = 0;
+  if (dns == 'remote')
+    pi_flags = Ci.nsIProxyInfo.TRANSPARENT_PROXY_RESOLVES_HOST;
+  
+  var pps = new ProtocolProxyService();
+  var pi = pps.newProxyInfo(socks_vers, 'localhost', socks_port,
+          pi_flags, -1, null);
+  var trans = sts.createTransport(null, 0, dest_host, dest_port, pi);
+  var input = trans.openInputStream(Ci.nsITransport.OPEN_BLOCKING,0,0);
+  var output = trans.openOutputStream(Ci.nsITransport.OPEN_BLOCKING,0,0);
+  var bin = new BinaryInputStream(input);
+  var data = bin.readBytes(5);
+  if (data == 'PING!') {
+    print('client: got ping, sending pong.');
+    output.write('PONG!', 5);
+  } else {
+    print('client: wrong data from server:', data);
+    output.write('Error: wrong data received.', 27);
+  }
+  output.close();
+}
+
+for each (var arg in arguments) {
+  print('client: running test', arg);
+  test = arg.split(':');
+  launchConnection(test[0], parseInt(test[1]), test[2],
+       parseInt(test[3]), test[4]);
+}
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_socks.js
@@ -0,0 +1,487 @@
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cu = Components.utils;
+const CC = Components.Constructor;
+
+const ServerSocket = CC("@mozilla.org/network/server-socket;1",
+                        "nsIServerSocket",
+                        "init");
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+                           "nsIBinaryInputStream",
+                           "setInputStream");
+const DirectoryService = CC("@mozilla.org/file/directory_service;1",
+                            "nsIProperties");
+const Process = CC("@mozilla.org/process/util;1", "nsIProcess", "init");
+
+const currentThread = Cc["@mozilla.org/thread-manager;1"]
+                      .getService().currentThread;
+
+var socks_test_server = null;
+var socks_listen_port = -1;
+
+function getAvailableBytes(input)
+{
+  var len = 0;
+
+  try {
+    len = input.available();  
+  } catch (e) {
+  }
+  
+  return len;
+}
+
+function runScriptSubprocess(script, args)
+{
+  // logic copied from ted's crashreporter unit test
+  var ds = new DirectoryService();
+  var bin = ds.get("CurProcD", Ci.nsILocalFile);
+
+  bin.append("xpcshell");
+  if (!bin.exists()) {
+    bin.leafName = "xpshell.exe";
+    do_check_true(bin.exists());
+    if (!bin.exists())
+      do_throw("Can't find xpshell binary");
+  }
+
+  var script = do_get_file(script);
+  var proc = new Process(bin);
+  var args = [script.path].concat(args);
+
+  proc.run(false, args, args.length);
+
+  return proc;
+}
+
+function buf2ip(buf)
+{
+  // XXX this doesn't work with IPv6
+  return buf.join('.');
+}
+
+function buf2int(buf)
+{
+  var n = 0;
+  
+  for (var i in buf) {
+    n |= buf[i] << ((buf.length - i - 1) * 8);
+  }
+
+  return n;
+}
+
+function buf2str(buf)
+{
+  return String.fromCharCode.apply(null, buf);
+}
+
+const STATE_WAIT_GREETING = 1;
+const STATE_WAIT_SOCKS4_REQUEST = 2;
+const STATE_WAIT_SOCKS4_USERNAME = 3;
+const STATE_WAIT_SOCKS4_HOSTNAME = 4;
+const STATE_WAIT_SOCKS5_GREETING = 5;
+const STATE_WAIT_SOCKS5_REQUEST = 6;
+const STATE_WAIT_PONG = 7;
+const STATE_GOT_PONG = 8;
+
+function SocksClient(server, client_in, client_out)
+{
+  this.server = server;
+  this.type = '';
+  this.username = '';
+  this.dest_name = '';
+  this.dest_addr = [];
+  this.dest_port = [];
+
+  this.client_in = client_in;
+  this.client_out = client_out;
+  this.inbuf = [];
+  this.outbuf = String();
+  this.state = STATE_WAIT_GREETING;
+  this.waitRead(this.client_in);
+}
+SocksClient.prototype = {
+  onInputStreamReady: function(input)
+  {
+    var len = getAvailableBytes(input);
+
+    if (len == 0) {
+      print('server: client closed!');
+      do_check_eq(this.state, STATE_GOT_PONG);
+      this.server.testCompleted(this);
+      return;
+    }
+
+    var bin = new BinaryInputStream(input);
+    var data = bin.readByteArray(len);
+    this.inbuf = this.inbuf.concat(data);
+
+    switch (this.state) {
+      case STATE_WAIT_GREETING:
+        this.checkSocksGreeting();
+        break;
+      case STATE_WAIT_SOCKS4_REQUEST:
+        this.checkSocks4Request();
+        break;
+      case STATE_WAIT_SOCKS4_USERNAME:
+        this.checkSocks4Username();
+        break;
+      case STATE_WAIT_SOCKS4_HOSTNAME:
+        this.checkSocks4Hostname();
+        break;
+      case STATE_WAIT_SOCKS5_GREETING:
+        this.checkSocks5Greeting();
+        break;
+      case STATE_WAIT_SOCKS5_REQUEST:
+        this.checkSocks5Request();
+        break;
+      case STATE_WAIT_PONG:
+        this.checkPong();
+        break;
+      default:
+        do_throw("server: read in invalid state!");
+    }
+
+    this.waitRead(input);
+  },
+
+  onOutputStreamReady: function(output)
+  {
+    var len = output.write(this.outbuf, this.outbuf.length);
+    if (len != this.outbuf.length) {
+      this.outbuf = this.outbuf.substring(len);
+      this.waitWrite(output);
+    } else
+      this.outbuf = String();
+  },
+
+  waitRead: function(input)
+  {
+    input.asyncWait(this, 0, 0, currentThread);
+  },
+
+  waitWrite: function(output)
+  {
+    output.asyncWait(this, 0, 0, currentThread);
+  },
+
+  write: function(buf)
+  {
+    this.outbuf += buf;
+    this.waitWrite(this.client_out);
+  },
+
+  checkSocksGreeting: function()
+  {
+    if (this.inbuf.length == 0)
+      return;
+
+    if (this.inbuf[0] == 4) {
+      print('server: got socks 4');
+      this.type = 'socks4';
+      this.state = STATE_WAIT_SOCKS4_REQUEST;
+      this.checkSocks4Request();
+    } else if (this.inbuf[0] == 5) {
+      print('server: got socks 5');
+      this.type = 'socks';
+      this.state = STATE_WAIT_SOCKS5_GREETING;
+      this.checkSocks5Greeting();
+    } else {
+      do_throw("Unknown socks protocol!");
+    }
+  },
+
+  checkSocks4Request: function()
+  {
+    if (this.inbuf.length < 8)
+      return;
+
+    do_check_eq(this.inbuf[1], 0x01);
+    
+    this.dest_port = this.inbuf.slice(2, 4);
+    this.dest_addr = this.inbuf.slice(4, 8);
+
+    this.inbuf = this.inbuf.slice(8);
+    this.state = STATE_WAIT_SOCKS4_USERNAME;
+    this.checkSocks4Username();
+  },
+
+  readString: function()
+  {
+    var i = this.inbuf.indexOf(0);
+    var str = null;
+
+    if (i >= 0) {
+      var buf = this.inbuf.slice(0,i);
+      str = buf2str(buf);
+      this.inbuf = this.inbuf.slice(i+1);
+    }
+
+    return str;
+  },
+
+  checkSocks4Username: function()
+  {
+    var str = this.readString();
+
+    if (str == null)
+      return;
+
+    this.username = str;
+    if (this.dest_addr[0] == 0 &&
+        this.dest_addr[1] == 0 &&
+        this.dest_addr[2] == 0 &&
+        this.dest_addr[3] != 0) {
+      this.state = STATE_WAIT_SOCKS4_HOSTNAME;
+      this.checkSocks4Hostname();
+    } else {
+      this.sendSocks4Response();
+    }
+  },
+
+  checkSocks4Hostname: function()
+  {
+    var str = this.readString();
+
+    if (str == null)
+      return;
+
+    this.dest_name = str;
+    this.sendSocks4Response();
+  },
+
+  sendSocks4Response: function()
+  {
+    this.outbuf = '\x00\x5a\x00\x00\x00\x00\x00\x00';
+    this.sendPing();
+  },
+
+  checkSocks5Greeting: function()
+  {
+    if (this.inbuf.length < 2)
+      return;
+    var nmethods = this.inbuf[1];
+    if (this.inbuf.length < 2 + nmethods)
+      return;
+
+    do_check_true(nmethods >= 1);  
+    var methods = this.inbuf.slice(2, 2 + nmethods);
+    do_check_true(0 in methods);
+
+    this.inbuf = [];
+    this.state = STATE_WAIT_SOCKS5_REQUEST;
+    this.write('\x05\x00');
+  },
+  
+  checkSocks5Request: function()
+  {
+    if (this.inbuf.length < 4)
+      return;
+
+    do_check_eq(this.inbuf[0], 0x05);
+    do_check_eq(this.inbuf[1], 0x01);
+    do_check_eq(this.inbuf[2], 0x00);
+
+    var atype = this.inbuf[3];
+    var len;
+    var name = false;
+
+    switch (atype) {
+      case 0x01:
+        len = 4;
+        break;
+      case 0x03:
+        len = this.inbuf[4];
+        name = true;
+        break;
+      case 0x04:
+        len = 16;
+        break;  
+      default:
+        do_throw("Unknown address type " + atype);
+    }
+    
+    if (name) {
+      if (this.inbuf.length < 4 + len + 1 + 2)
+        return;
+
+      buf = this.inbuf.slice(5, 5 + len);
+      this.dest_name = buf2str(buf);
+      len += 1;
+    } else {
+      if (this.inbuf.length < 4 + len + 2)
+        return;
+
+      this.dest_addr = this.inbuf.slice(4, 4 + len);
+    }
+
+    len += 4;
+    this.dest_port = this.inbuf.slice(len, len + 2);
+    this.inbuf = this.inbuf.slice(len + 2);
+    this.sendSocks5Response();
+  },
+
+  sendSocks5Response: function()
+  {
+    // send a successful response with the address, 127.0.0.1:80
+    this.outbuf += '\x05\x00\x00\x01\x7f\x00\x00\x01\x00\x80';
+    this.sendPing();
+  },
+
+  sendPing: function()
+  {
+    print('server: sending ping');
+    this.state = STATE_WAIT_PONG;
+    this.outbuf += "PING!";
+    this.inbuf = [];
+    this.waitWrite(this.client_out);
+    this.waitRead(this.client_in);
+  },
+
+  checkPong: function()
+  {
+    var pong = buf2str(this.inbuf);
+    do_check_eq(pong, "PONG!");
+    this.state = STATE_GOT_PONG;
+    this.waitRead(this.client_in);
+  },
+  
+  close: function()
+  {
+    this.client_in.close();
+    this.client_out.close();
+  }
+};
+
+function SocksTestServer()
+{
+  this.listener = ServerSocket(-1, true, -1);
+  socks_listen_port = this.listener.port;
+  print('server: listening on', socks_listen_port);
+  this.listener.asyncListen(this);
+  this.test_cases = [];
+  this.client_connections = [];
+  this.client_subprocess = null;
+  // port is used as the ID for test cases
+  this.test_port_id = 8000;
+  this.tests_completed = 0;
+}
+SocksTestServer.prototype = {
+  addTestCase: function(test)
+  {
+    test.finished = false;
+    test.port = this.test_port_id++;
+    this.test_cases.push(test);
+  },
+
+  pickTest: function(id)
+  {
+    for (var i in this.test_cases) {
+      var test = this.test_cases[i];
+      if (test.port == id) {
+        this.tests_completed++;
+        return test;
+      }
+    }
+    do_throw("No test case with id " + id);
+  },
+
+  testCompleted: function(client)
+  {
+    var port_id = buf2int(client.dest_port);
+    var test = this.pickTest(port_id);
+    
+    print('server: test finished', test.port);
+    do_check_true(test != null);
+    do_check_eq(test.type, client.type);
+    do_check_eq(test.port, port_id);
+
+    if (test.remote_dns)
+      do_check_eq(test.host, client.dest_name);
+    else
+      do_check_eq(test.host, buf2ip(client.dest_addr));
+    
+    if (this.test_cases.length == this.tests_completed) {
+      print('server: all tests completed');
+      this.close();
+      do_test_finished();
+    }
+  },
+
+  runClientSubprocess: function()
+  {
+    var argv = [];
+
+    // marshaled: socks_ver:server_port:dest_host:dest_port:remote|local
+    for each (var test in this.test_cases) {
+      var arg = test.type + ':' +
+        String(socks_listen_port) + ':' +
+        test.host + ':' + test.port + ':';
+      if (test.remote_dns)
+        arg += 'remote';
+      else
+        arg += 'local';
+      print('server: using test case', arg);  
+      argv.push(arg);      
+    }
+
+    this.client_subprocess = runScriptSubprocess(
+        'socks_client_subprocess.js', argv);
+  },
+
+  onSocketAccepted: function(socket, trans)
+  {
+    print('server: got client connection');
+    var input = trans.openInputStream(0, 0, 0);
+    var output = trans.openOutputStream(0, 0, 0);
+    var client = new SocksClient(this, input, output);
+    this.client_connections.push(client);
+  },
+
+  close: function()
+  {
+    if (this.client_subprocess)
+      this.client_subprocess.kill();
+    for each (var client in this.client_connections)
+      client.close();
+    this.listener.close();
+  }
+};
+
+function test_timeout()
+{
+  socks_test_server.close();
+  do_throw("SOCKS test took too long!");
+}
+
+function run_test()
+{
+  socks_test_server = new SocksTestServer();
+
+  socks_test_server.addTestCase({
+        type: "socks4",
+        host: '127.0.0.1',
+        remote_dns: false,
+  });
+  socks_test_server.addTestCase({
+        type: "socks4",
+        host: '12345.xxx',
+        remote_dns: true,
+  });
+  socks_test_server.addTestCase({
+        type: "socks",
+        host: '127.0.0.1',
+        remote_dns: false,
+  });
+  socks_test_server.addTestCase({
+        type: "socks",
+        host: 'abcdefg.xxx',
+        remote_dns: true,
+  });
+  socks_test_server.runClientSubprocess();
+
+  do_timeout(120 * 1000, test_timeout);
+  do_test_pending();
+}