Bug 1857386 [wpt PR 42383] - Update wpt metadata, a=testonly
[gecko.git] / netwerk / test / unit / test_socks.js
blob6ce9f4895e6ae863f01ad4165248a0a46c6ec4a4
1 "use strict";
3 var CC = Components.Constructor;
5 const ServerSocket = CC(
6   "@mozilla.org/network/server-socket;1",
7   "nsIServerSocket",
8   "init"
9 );
10 const BinaryInputStream = CC(
11   "@mozilla.org/binaryinputstream;1",
12   "nsIBinaryInputStream",
13   "setInputStream"
15 const DirectoryService = CC(
16   "@mozilla.org/file/directory_service;1",
17   "nsIProperties"
19 const Process = CC("@mozilla.org/process/util;1", "nsIProcess", "init");
21 const currentThread =
22   Cc["@mozilla.org/thread-manager;1"].getService().currentThread;
24 var socks_test_server = null;
25 var socks_listen_port = -1;
27 function getAvailableBytes(input) {
28   var len = 0;
30   try {
31     len = input.available();
32   } catch (e) {}
34   return len;
37 function runScriptSubprocess(script, args) {
38   var ds = new DirectoryService();
39   var bin = ds.get("XREExeF", Ci.nsIFile);
40   if (!bin.exists()) {
41     do_throw("Can't find xpcshell binary");
42   }
44   var file = do_get_file(script);
45   var proc = new Process(bin);
46   var procArgs = [file.path].concat(args);
48   proc.run(false, procArgs, procArgs.length);
50   return proc;
53 function buf2ip(buf) {
54   if (buf.length == 16) {
55     var ip =
56       ((buf[0] << 4) | buf[1]).toString(16) +
57       ":" +
58       ((buf[2] << 4) | buf[3]).toString(16) +
59       ":" +
60       ((buf[4] << 4) | buf[5]).toString(16) +
61       ":" +
62       ((buf[6] << 4) | buf[7]).toString(16) +
63       ":" +
64       ((buf[8] << 4) | buf[9]).toString(16) +
65       ":" +
66       ((buf[10] << 4) | buf[11]).toString(16) +
67       ":" +
68       ((buf[12] << 4) | buf[13]).toString(16) +
69       ":" +
70       ((buf[14] << 4) | buf[15]).toString(16);
71     for (var i = 8; i >= 2; i--) {
72       var re = new RegExp("(^|:)(0(:|$)){" + i + "}");
73       var shortip = ip.replace(re, "::");
74       if (shortip != ip) {
75         return shortip;
76       }
77     }
78     return ip;
79   }
80   return buf.join(".");
83 function buf2int(buf) {
84   var n = 0;
86   for (var i in buf) {
87     n |= buf[i] << ((buf.length - i - 1) * 8);
88   }
90   return n;
93 function buf2str(buf) {
94   return String.fromCharCode.apply(null, buf);
97 const STATE_WAIT_GREETING = 1;
98 const STATE_WAIT_SOCKS4_REQUEST = 2;
99 const STATE_WAIT_SOCKS4_USERNAME = 3;
100 const STATE_WAIT_SOCKS4_HOSTNAME = 4;
101 const STATE_WAIT_SOCKS5_GREETING = 5;
102 const STATE_WAIT_SOCKS5_REQUEST = 6;
103 const STATE_WAIT_PONG = 7;
104 const STATE_GOT_PONG = 8;
106 function SocksClient(server, client_in, client_out) {
107   this.server = server;
108   this.type = "";
109   this.username = "";
110   this.dest_name = "";
111   this.dest_addr = [];
112   this.dest_port = [];
114   this.client_in = client_in;
115   this.client_out = client_out;
116   this.inbuf = [];
117   this.outbuf = String();
118   this.state = STATE_WAIT_GREETING;
119   this.waitRead(this.client_in);
121 SocksClient.prototype = {
122   onInputStreamReady(input) {
123     var len = getAvailableBytes(input);
125     if (len == 0) {
126       print("server: client closed!");
127       Assert.equal(this.state, STATE_GOT_PONG);
128       this.close();
129       this.server.testCompleted(this);
130       return;
131     }
133     var bin = new BinaryInputStream(input);
134     var data = bin.readByteArray(len);
135     this.inbuf = this.inbuf.concat(data);
137     switch (this.state) {
138       case STATE_WAIT_GREETING:
139         this.checkSocksGreeting();
140         break;
141       case STATE_WAIT_SOCKS4_REQUEST:
142         this.checkSocks4Request();
143         break;
144       case STATE_WAIT_SOCKS4_USERNAME:
145         this.checkSocks4Username();
146         break;
147       case STATE_WAIT_SOCKS4_HOSTNAME:
148         this.checkSocks4Hostname();
149         break;
150       case STATE_WAIT_SOCKS5_GREETING:
151         this.checkSocks5Greeting();
152         break;
153       case STATE_WAIT_SOCKS5_REQUEST:
154         this.checkSocks5Request();
155         break;
156       case STATE_WAIT_PONG:
157         this.checkPong();
158         break;
159       default:
160         do_throw("server: read in invalid state!");
161     }
163     this.waitRead(input);
164   },
166   onOutputStreamReady(output) {
167     var len = output.write(this.outbuf, this.outbuf.length);
168     if (len != this.outbuf.length) {
169       this.outbuf = this.outbuf.substring(len);
170       this.waitWrite(output);
171     } else {
172       this.outbuf = String();
173     }
174   },
176   waitRead(input) {
177     input.asyncWait(this, 0, 0, currentThread);
178   },
180   waitWrite(output) {
181     output.asyncWait(this, 0, 0, currentThread);
182   },
184   write(buf) {
185     this.outbuf += buf;
186     this.waitWrite(this.client_out);
187   },
189   checkSocksGreeting() {
190     if (!this.inbuf.length) {
191       return;
192     }
194     if (this.inbuf[0] == 4) {
195       print("server: got socks 4");
196       this.type = "socks4";
197       this.state = STATE_WAIT_SOCKS4_REQUEST;
198       this.checkSocks4Request();
199     } else if (this.inbuf[0] == 5) {
200       print("server: got socks 5");
201       this.type = "socks";
202       this.state = STATE_WAIT_SOCKS5_GREETING;
203       this.checkSocks5Greeting();
204     } else {
205       do_throw("Unknown socks protocol!");
206     }
207   },
209   checkSocks4Request() {
210     if (this.inbuf.length < 8) {
211       return;
212     }
214     Assert.equal(this.inbuf[1], 0x01);
216     this.dest_port = this.inbuf.slice(2, 4);
217     this.dest_addr = this.inbuf.slice(4, 8);
219     this.inbuf = this.inbuf.slice(8);
220     this.state = STATE_WAIT_SOCKS4_USERNAME;
221     this.checkSocks4Username();
222   },
224   readString() {
225     var i = this.inbuf.indexOf(0);
226     var str = null;
228     if (i >= 0) {
229       var buf = this.inbuf.slice(0, i);
230       str = buf2str(buf);
231       this.inbuf = this.inbuf.slice(i + 1);
232     }
234     return str;
235   },
237   checkSocks4Username() {
238     var str = this.readString();
240     if (str == null) {
241       return;
242     }
244     this.username = str;
245     if (
246       this.dest_addr[0] == 0 &&
247       this.dest_addr[1] == 0 &&
248       this.dest_addr[2] == 0 &&
249       this.dest_addr[3] != 0
250     ) {
251       this.state = STATE_WAIT_SOCKS4_HOSTNAME;
252       this.checkSocks4Hostname();
253     } else {
254       this.sendSocks4Response();
255     }
256   },
258   checkSocks4Hostname() {
259     var str = this.readString();
261     if (str == null) {
262       return;
263     }
265     this.dest_name = str;
266     this.sendSocks4Response();
267   },
269   sendSocks4Response() {
270     this.outbuf = "\x00\x5a\x00\x00\x00\x00\x00\x00";
271     this.sendPing();
272   },
274   checkSocks5Greeting() {
275     if (this.inbuf.length < 2) {
276       return;
277     }
278     var nmethods = this.inbuf[1];
279     if (this.inbuf.length < 2 + nmethods) {
280       return;
281     }
283     Assert.ok(nmethods >= 1);
284     var methods = this.inbuf.slice(2, 2 + nmethods);
285     Assert.ok(0 in methods);
287     this.inbuf = [];
288     this.state = STATE_WAIT_SOCKS5_REQUEST;
289     this.write("\x05\x00");
290   },
292   checkSocks5Request() {
293     if (this.inbuf.length < 4) {
294       return;
295     }
297     Assert.equal(this.inbuf[0], 0x05);
298     Assert.equal(this.inbuf[1], 0x01);
299     Assert.equal(this.inbuf[2], 0x00);
301     var atype = this.inbuf[3];
302     var len;
303     var name = false;
305     switch (atype) {
306       case 0x01:
307         len = 4;
308         break;
309       case 0x03:
310         len = this.inbuf[4];
311         name = true;
312         break;
313       case 0x04:
314         len = 16;
315         break;
316       default:
317         do_throw("Unknown address type " + atype);
318     }
320     if (name) {
321       if (this.inbuf.length < 4 + len + 1 + 2) {
322         return;
323       }
325       let buf = this.inbuf.slice(5, 5 + len);
326       this.dest_name = buf2str(buf);
327       len += 1;
328     } else {
329       if (this.inbuf.length < 4 + len + 2) {
330         return;
331       }
333       this.dest_addr = this.inbuf.slice(4, 4 + len);
334     }
336     len += 4;
337     this.dest_port = this.inbuf.slice(len, len + 2);
338     this.inbuf = this.inbuf.slice(len + 2);
339     this.sendSocks5Response();
340   },
342   sendSocks5Response() {
343     if (this.dest_addr.length == 16) {
344       // send a successful response with the address, [::1]:80
345       this.outbuf +=
346         "\x05\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x80";
347     } else {
348       // send a successful response with the address, 127.0.0.1:80
349       this.outbuf += "\x05\x00\x00\x01\x7f\x00\x00\x01\x00\x80";
350     }
351     this.sendPing();
352   },
354   sendPing() {
355     print("server: sending ping");
356     this.state = STATE_WAIT_PONG;
357     this.outbuf += "PING!";
358     this.inbuf = [];
359     this.waitWrite(this.client_out);
360   },
362   checkPong() {
363     var pong = buf2str(this.inbuf);
364     Assert.equal(pong, "PONG!");
365     this.state = STATE_GOT_PONG;
366   },
368   close() {
369     this.client_in.close();
370     this.client_out.close();
371   },
374 function SocksTestServer() {
375   this.listener = ServerSocket(-1, true, -1);
376   socks_listen_port = this.listener.port;
377   print("server: listening on", socks_listen_port);
378   this.listener.asyncListen(this);
379   this.test_cases = [];
380   this.client_connections = [];
381   this.client_subprocess = null;
382   // port is used as the ID for test cases
383   this.test_port_id = 8000;
384   this.tests_completed = 0;
386 SocksTestServer.prototype = {
387   addTestCase(test) {
388     test.finished = false;
389     test.port = this.test_port_id++;
390     this.test_cases.push(test);
391   },
393   pickTest(id) {
394     for (var i in this.test_cases) {
395       var test = this.test_cases[i];
396       if (test.port == id) {
397         this.tests_completed++;
398         return test;
399       }
400     }
401     do_throw("No test case with id " + id);
402     return null;
403   },
405   testCompleted(client) {
406     var port_id = buf2int(client.dest_port);
407     var test = this.pickTest(port_id);
409     print("server: test finished", test.port);
410     Assert.ok(test != null);
411     Assert.equal(test.expectedType || test.type, client.type);
412     Assert.equal(test.port, port_id);
414     if (test.remote_dns) {
415       Assert.equal(test.host, client.dest_name);
416     } else {
417       Assert.equal(test.host, buf2ip(client.dest_addr));
418     }
420     if (this.test_cases.length == this.tests_completed) {
421       print("server: all tests completed");
422       this.close();
423       do_test_finished();
424     }
425   },
427   runClientSubprocess() {
428     var argv = [];
430     // marshaled: socks_ver|server_port|dest_host|dest_port|<remote|local>
431     for (var test of this.test_cases) {
432       var arg =
433         test.type +
434         "|" +
435         String(socks_listen_port) +
436         "|" +
437         test.host +
438         "|" +
439         test.port +
440         "|";
441       if (test.remote_dns) {
442         arg += "remote";
443       } else {
444         arg += "local";
445       }
446       print("server: using test case", arg);
447       argv.push(arg);
448     }
450     this.client_subprocess = runScriptSubprocess(
451       "socks_client_subprocess.js",
452       argv
453     );
454   },
456   onSocketAccepted(socket, trans) {
457     print("server: got client connection");
458     var input = trans.openInputStream(0, 0, 0);
459     var output = trans.openOutputStream(0, 0, 0);
460     var client = new SocksClient(this, input, output);
461     this.client_connections.push(client);
462   },
464   onStopListening(socket) {},
466   close() {
467     if (this.client_subprocess) {
468       try {
469         this.client_subprocess.kill();
470       } catch (x) {
471         do_note_exception(x, "Killing subprocess failed");
472       }
473       this.client_subprocess = null;
474     }
475     this.client_connections = [];
476     if (this.listener) {
477       this.listener.close();
478       this.listener = null;
479     }
480   },
483 function run_test() {
484   socks_test_server = new SocksTestServer();
486   socks_test_server.addTestCase({
487     type: "socks4",
488     host: "127.0.0.1",
489     remote_dns: false,
490   });
491   socks_test_server.addTestCase({
492     type: "socks4",
493     host: "12345.xxx",
494     remote_dns: true,
495   });
496   socks_test_server.addTestCase({
497     type: "socks4",
498     expectedType: "socks",
499     host: "::1",
500     remote_dns: false,
501   });
502   socks_test_server.addTestCase({
503     type: "socks",
504     host: "127.0.0.1",
505     remote_dns: false,
506   });
507   socks_test_server.addTestCase({
508     type: "socks",
509     host: "abcdefg.xxx",
510     remote_dns: true,
511   });
512   socks_test_server.addTestCase({
513     type: "socks",
514     host: "::1",
515     remote_dns: false,
516   });
517   socks_test_server.runClientSubprocess();
519   do_test_pending();