Thu, 15 Jan 2015 21:03:48 +0100
Integrate friendly tips from Tor colleagues to make (or not) 4.5 alpha 3;
This includes removal of overloaded (but unused) methods, and addition of
a overlooked call to DataStruct::SetData(nsISupports, uint32_t, bool.)
1 const CC = Components.Constructor;
3 const ServerSocket = CC("@mozilla.org/network/server-socket;1",
4 "nsIServerSocket",
5 "init");
6 const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
7 "nsIBinaryInputStream",
8 "setInputStream");
9 const DirectoryService = CC("@mozilla.org/file/directory_service;1",
10 "nsIProperties");
11 const Process = CC("@mozilla.org/process/util;1", "nsIProcess", "init");
13 const currentThread = Cc["@mozilla.org/thread-manager;1"]
14 .getService().currentThread;
16 var socks_test_server = null;
17 var socks_listen_port = -1;
19 function getAvailableBytes(input)
20 {
21 var len = 0;
23 try {
24 len = input.available();
25 } catch (e) {
26 }
28 return len;
29 }
31 function runScriptSubprocess(script, args)
32 {
33 // logic copied from ted's crashreporter unit test
34 var ds = new DirectoryService();
35 var bin = ds.get("CurProcD", Ci.nsILocalFile);
37 bin.append("xpcshell");
38 if (!bin.exists()) {
39 bin.leafName = "xpcshell.exe";
40 do_check_true(bin.exists());
41 if (!bin.exists())
42 do_throw("Can't find xpcshell binary");
43 }
45 var script = do_get_file(script);
46 var proc = new Process(bin);
47 var args = [script.path].concat(args);
49 proc.run(false, args, args.length);
51 return proc;
52 }
54 function buf2ip(buf)
55 {
56 if (buf.length == 16) {
57 var ip = (buf[0] << 4 | buf[1]).toString(16) + ':' +
58 (buf[2] << 4 | buf[3]).toString(16) + ':' +
59 (buf[4] << 4 | buf[5]).toString(16) + ':' +
60 (buf[6] << 4 | buf[7]).toString(16) + ':' +
61 (buf[8] << 4 | buf[9]).toString(16) + ':' +
62 (buf[10] << 4 | buf[11]).toString(16) + ':' +
63 (buf[12] << 4 | buf[13]).toString(16) + ':' +
64 (buf[14] << 4 | buf[15]).toString(16);
65 for (var i = 8; i >= 2; i--) {
66 var re = new RegExp("(^|:)(0(:|$)){" + i + "}");
67 var shortip = ip.replace(re, '::');
68 if (shortip != ip) {
69 return shortip;
70 }
71 }
72 return ip;
73 } else {
74 return buf.join('.');
75 }
76 }
78 function buf2int(buf)
79 {
80 var n = 0;
82 for (var i in buf) {
83 n |= buf[i] << ((buf.length - i - 1) * 8);
84 }
86 return n;
87 }
89 function buf2str(buf)
90 {
91 return String.fromCharCode.apply(null, buf);
92 }
94 const STATE_WAIT_GREETING = 1;
95 const STATE_WAIT_SOCKS4_REQUEST = 2;
96 const STATE_WAIT_SOCKS4_USERNAME = 3;
97 const STATE_WAIT_SOCKS4_HOSTNAME = 4;
98 const STATE_WAIT_SOCKS5_GREETING = 5;
99 const STATE_WAIT_SOCKS5_REQUEST = 6;
100 const STATE_WAIT_PONG = 7;
101 const STATE_GOT_PONG = 8;
103 function SocksClient(server, client_in, client_out)
104 {
105 this.server = server;
106 this.type = '';
107 this.username = '';
108 this.dest_name = '';
109 this.dest_addr = [];
110 this.dest_port = [];
112 this.client_in = client_in;
113 this.client_out = client_out;
114 this.inbuf = [];
115 this.outbuf = String();
116 this.state = STATE_WAIT_GREETING;
117 this.waitRead(this.client_in);
118 }
119 SocksClient.prototype = {
120 onInputStreamReady: function(input)
121 {
122 var len = getAvailableBytes(input);
124 if (len == 0) {
125 print('server: client closed!');
126 do_check_eq(this.state, STATE_GOT_PONG);
127 this.server.testCompleted(this);
128 return;
129 }
131 var bin = new BinaryInputStream(input);
132 var data = bin.readByteArray(len);
133 this.inbuf = this.inbuf.concat(data);
135 switch (this.state) {
136 case STATE_WAIT_GREETING:
137 this.checkSocksGreeting();
138 break;
139 case STATE_WAIT_SOCKS4_REQUEST:
140 this.checkSocks4Request();
141 break;
142 case STATE_WAIT_SOCKS4_USERNAME:
143 this.checkSocks4Username();
144 break;
145 case STATE_WAIT_SOCKS4_HOSTNAME:
146 this.checkSocks4Hostname();
147 break;
148 case STATE_WAIT_SOCKS5_GREETING:
149 this.checkSocks5Greeting();
150 break;
151 case STATE_WAIT_SOCKS5_REQUEST:
152 this.checkSocks5Request();
153 break;
154 case STATE_WAIT_PONG:
155 this.checkPong();
156 break;
157 default:
158 do_throw("server: read in invalid state!");
159 }
161 this.waitRead(input);
162 },
164 onOutputStreamReady: function(output)
165 {
166 var len = output.write(this.outbuf, this.outbuf.length);
167 if (len != this.outbuf.length) {
168 this.outbuf = this.outbuf.substring(len);
169 this.waitWrite(output);
170 } else
171 this.outbuf = String();
172 },
174 waitRead: function(input)
175 {
176 input.asyncWait(this, 0, 0, currentThread);
177 },
179 waitWrite: function(output)
180 {
181 output.asyncWait(this, 0, 0, currentThread);
182 },
184 write: function(buf)
185 {
186 this.outbuf += buf;
187 this.waitWrite(this.client_out);
188 },
190 checkSocksGreeting: function()
191 {
192 if (this.inbuf.length == 0)
193 return;
195 if (this.inbuf[0] == 4) {
196 print('server: got socks 4');
197 this.type = 'socks4';
198 this.state = STATE_WAIT_SOCKS4_REQUEST;
199 this.checkSocks4Request();
200 } else if (this.inbuf[0] == 5) {
201 print('server: got socks 5');
202 this.type = 'socks';
203 this.state = STATE_WAIT_SOCKS5_GREETING;
204 this.checkSocks5Greeting();
205 } else {
206 do_throw("Unknown socks protocol!");
207 }
208 },
210 checkSocks4Request: function()
211 {
212 if (this.inbuf.length < 8)
213 return;
215 do_check_eq(this.inbuf[1], 0x01);
217 this.dest_port = this.inbuf.slice(2, 4);
218 this.dest_addr = this.inbuf.slice(4, 8);
220 this.inbuf = this.inbuf.slice(8);
221 this.state = STATE_WAIT_SOCKS4_USERNAME;
222 this.checkSocks4Username();
223 },
225 readString: function()
226 {
227 var i = this.inbuf.indexOf(0);
228 var str = null;
230 if (i >= 0) {
231 var buf = this.inbuf.slice(0,i);
232 str = buf2str(buf);
233 this.inbuf = this.inbuf.slice(i+1);
234 }
236 return str;
237 },
239 checkSocks4Username: function()
240 {
241 var str = this.readString();
243 if (str == null)
244 return;
246 this.username = str;
247 if (this.dest_addr[0] == 0 &&
248 this.dest_addr[1] == 0 &&
249 this.dest_addr[2] == 0 &&
250 this.dest_addr[3] != 0) {
251 this.state = STATE_WAIT_SOCKS4_HOSTNAME;
252 this.checkSocks4Hostname();
253 } else {
254 this.sendSocks4Response();
255 }
256 },
258 checkSocks4Hostname: function()
259 {
260 var str = this.readString();
262 if (str == null)
263 return;
265 this.dest_name = str;
266 this.sendSocks4Response();
267 },
269 sendSocks4Response: function()
270 {
271 this.outbuf = '\x00\x5a\x00\x00\x00\x00\x00\x00';
272 this.sendPing();
273 },
275 checkSocks5Greeting: function()
276 {
277 if (this.inbuf.length < 2)
278 return;
279 var nmethods = this.inbuf[1];
280 if (this.inbuf.length < 2 + nmethods)
281 return;
283 do_check_true(nmethods >= 1);
284 var methods = this.inbuf.slice(2, 2 + nmethods);
285 do_check_true(0 in methods);
287 this.inbuf = [];
288 this.state = STATE_WAIT_SOCKS5_REQUEST;
289 this.write('\x05\x00');
290 },
292 checkSocks5Request: function()
293 {
294 if (this.inbuf.length < 4)
295 return;
297 do_check_eq(this.inbuf[0], 0x05);
298 do_check_eq(this.inbuf[1], 0x01);
299 do_check_eq(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;
324 buf = this.inbuf.slice(5, 5 + len);
325 this.dest_name = buf2str(buf);
326 len += 1;
327 } else {
328 if (this.inbuf.length < 4 + len + 2)
329 return;
331 this.dest_addr = this.inbuf.slice(4, 4 + len);
332 }
334 len += 4;
335 this.dest_port = this.inbuf.slice(len, len + 2);
336 this.inbuf = this.inbuf.slice(len + 2);
337 this.sendSocks5Response();
338 },
340 sendSocks5Response: function()
341 {
342 if (this.dest_addr.length == 16) {
343 // send a successful response with the address, [::1]:80
344 this.outbuf += '\x05\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x80';
345 } else {
346 // send a successful response with the address, 127.0.0.1:80
347 this.outbuf += '\x05\x00\x00\x01\x7f\x00\x00\x01\x00\x80';
348 }
349 this.sendPing();
350 },
352 sendPing: function()
353 {
354 print('server: sending ping');
355 this.state = STATE_WAIT_PONG;
356 this.outbuf += "PING!";
357 this.inbuf = [];
358 this.waitWrite(this.client_out);
359 this.waitRead(this.client_in);
360 },
362 checkPong: function()
363 {
364 var pong = buf2str(this.inbuf);
365 do_check_eq(pong, "PONG!");
366 this.state = STATE_GOT_PONG;
367 this.waitRead(this.client_in);
368 },
370 close: function()
371 {
372 this.client_in.close();
373 this.client_out.close();
374 }
375 };
377 function SocksTestServer()
378 {
379 this.listener = ServerSocket(-1, true, -1);
380 socks_listen_port = this.listener.port;
381 print('server: listening on', socks_listen_port);
382 this.listener.asyncListen(this);
383 this.test_cases = [];
384 this.client_connections = [];
385 this.client_subprocess = null;
386 // port is used as the ID for test cases
387 this.test_port_id = 8000;
388 this.tests_completed = 0;
389 }
390 SocksTestServer.prototype = {
391 addTestCase: function(test)
392 {
393 test.finished = false;
394 test.port = this.test_port_id++;
395 this.test_cases.push(test);
396 },
398 pickTest: function(id)
399 {
400 for (var i in this.test_cases) {
401 var test = this.test_cases[i];
402 if (test.port == id) {
403 this.tests_completed++;
404 return test;
405 }
406 }
407 do_throw("No test case with id " + id);
408 },
410 testCompleted: function(client)
411 {
412 var port_id = buf2int(client.dest_port);
413 var test = this.pickTest(port_id);
415 print('server: test finished', test.port);
416 do_check_true(test != null);
417 do_check_eq(test.expectedType || test.type, client.type);
418 do_check_eq(test.port, port_id);
420 if (test.remote_dns)
421 do_check_eq(test.host, client.dest_name);
422 else
423 do_check_eq(test.host, buf2ip(client.dest_addr));
425 if (this.test_cases.length == this.tests_completed) {
426 print('server: all tests completed');
427 this.close();
428 do_test_finished();
429 }
430 },
432 runClientSubprocess: function()
433 {
434 var argv = [];
436 // marshaled: socks_ver|server_port|dest_host|dest_port|<remote|local>
437 for each (var test in this.test_cases) {
438 var arg = test.type + '|' +
439 String(socks_listen_port) + '|' +
440 test.host + '|' + test.port + '|';
441 if (test.remote_dns)
442 arg += 'remote';
443 else
444 arg += 'local';
445 print('server: using test case', arg);
446 argv.push(arg);
447 }
449 this.client_subprocess = runScriptSubprocess(
450 'socks_client_subprocess.js', argv);
451 },
453 onSocketAccepted: function(socket, trans)
454 {
455 print('server: got client connection');
456 var input = trans.openInputStream(0, 0, 0);
457 var output = trans.openOutputStream(0, 0, 0);
458 var client = new SocksClient(this, input, output);
459 this.client_connections.push(client);
460 },
462 onStopListening: function(socket)
463 {
464 },
466 close: function()
467 {
468 if (this.client_subprocess) {
469 try {
470 this.client_subprocess.kill();
471 } catch (x) {
472 do_note_exception(x, 'Killing subprocess failed');
473 }
474 this.client_subprocess = null;
475 }
476 for each (var client in this.client_connections)
477 client.close();
478 this.client_connections = [];
479 if (this.listener) {
480 this.listener.close();
481 this.listener = null;
482 }
483 }
484 };
486 function test_timeout()
487 {
488 socks_test_server.close();
489 do_throw("SOCKS test took too long!");
490 }
492 function run_test()
493 {
494 socks_test_server = new SocksTestServer();
496 socks_test_server.addTestCase({
497 type: "socks4",
498 host: '127.0.0.1',
499 remote_dns: false,
500 });
501 socks_test_server.addTestCase({
502 type: "socks4",
503 host: '12345.xxx',
504 remote_dns: true,
505 });
506 socks_test_server.addTestCase({
507 type: "socks4",
508 expectedType: "socks",
509 host: '::1',
510 remote_dns: false,
511 });
512 socks_test_server.addTestCase({
513 type: "socks",
514 host: '127.0.0.1',
515 remote_dns: false,
516 });
517 socks_test_server.addTestCase({
518 type: "socks",
519 host: 'abcdefg.xxx',
520 remote_dns: true,
521 });
522 socks_test_server.addTestCase({
523 type: "socks",
524 host: '::1',
525 remote_dns: false,
526 });
527 socks_test_server.runClientSubprocess();
529 do_timeout(120 * 1000, test_timeout);
530 do_test_pending();
531 }