test_websocket_server.js (12611B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 /* import-globals-from head_cache.js */ 8 /* import-globals-from head_cookies.js */ 9 /* import-globals-from head_channels.js */ 10 11 const { 12 NodeWebSocketPlainServer, 13 NodeWebSocketServer, 14 NodeWebSocketHttp2Server, 15 NodeHTTPProxyServer, 16 NodeHTTPSProxyServer, 17 NodeHTTP2ProxyServer, 18 WebSocketConnection, 19 } = ChromeUtils.importESModule("resource://testing-common/NodeServer.sys.mjs"); 20 21 const certOverrideService = Cc[ 22 "@mozilla.org/security/certoverride;1" 23 ].getService(Ci.nsICertOverrideService); 24 25 add_setup(async function setup() { 26 certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData( 27 true 28 ); 29 30 registerCleanupFunction(async () => { 31 certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData( 32 false 33 ); 34 }); 35 }); 36 37 function makeChan(uri) { 38 let chan = NetUtil.newChannel({ 39 uri, 40 loadUsingSystemPrincipal: true, 41 }).QueryInterface(Ci.nsIHttpChannel); 42 chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI; 43 return chan; 44 } 45 46 function httpChannelOpenPromise(chan, flags) { 47 return new Promise(resolve => { 48 function finish(req, buffer) { 49 resolve([req, buffer]); 50 } 51 chan.asyncOpen(new ChannelListener(finish, null, flags)); 52 }); 53 } 54 55 async function channelOpenPromise(url, msg) { 56 let conn = new WebSocketConnection(); 57 let statusObj = await Promise.race([conn.open(url), conn.finished()]); 58 if (statusObj && statusObj.status != Cr.NS_OK) { 59 return [statusObj.status, ""]; 60 } 61 let finalStatusPromise = conn.finished(); 62 conn.send(msg); 63 let res = await conn.receiveMessages(); 64 conn.close(); 65 let finalStatus = await finalStatusPromise; 66 return [finalStatus.status, res]; 67 } 68 69 // h1.1 direct 70 async function test_h1_plain_websocket_direct() { 71 let wss = new NodeWebSocketPlainServer(); 72 await wss.start(); 73 registerCleanupFunction(async () => wss.stop()); 74 Assert.notEqual(wss.port(), null); 75 await wss.registerMessageHandler((data, ws) => { 76 ws.send(data); 77 }); 78 let url = `ws://localhost:${wss.port()}`; 79 const msg = "test websocket"; 80 81 let conn = new WebSocketConnection(); 82 await conn.open(url); 83 conn.send(msg); 84 let mess1 = await conn.receiveMessages(); 85 Assert.deepEqual(mess1, [msg]); 86 87 // Now send 3 more, and check that we received all of them 88 conn.send(msg); 89 conn.send(msg); 90 conn.send(msg); 91 let mess2 = []; 92 while (mess2.length < 3) { 93 // receive could return 1, 2 or all 3 replies. 94 mess2 = mess2.concat(await conn.receiveMessages()); 95 } 96 Assert.deepEqual(mess2, [msg, msg, msg]); 97 98 conn.close(); 99 let { status } = await conn.finished(); 100 101 Assert.equal(status, Cr.NS_OK); 102 } 103 104 // h1.1 direct 105 async function test_h1_websocket_direct() { 106 let wss = new NodeWebSocketServer(); 107 await wss.start(); 108 registerCleanupFunction(async () => wss.stop()); 109 Assert.notEqual(wss.port(), null); 110 await wss.registerMessageHandler((data, ws) => { 111 ws.send(data); 112 }); 113 let url = `wss://localhost:${wss.port()}`; 114 const msg = "test websocket"; 115 116 let conn = new WebSocketConnection(); 117 await conn.open(url); 118 conn.send(msg); 119 let mess1 = await conn.receiveMessages(); 120 Assert.deepEqual(mess1, [msg]); 121 122 // Now send 3 more, and check that we received all of them 123 conn.send(msg); 124 conn.send(msg); 125 conn.send(msg); 126 let mess2 = []; 127 while (mess2.length < 3) { 128 // receive could return 1, 2 or all 3 replies. 129 mess2 = mess2.concat(await conn.receiveMessages()); 130 } 131 Assert.deepEqual(mess2, [msg, msg, msg]); 132 133 conn.close(); 134 let { status } = await conn.finished(); 135 136 Assert.equal(status, Cr.NS_OK); 137 } 138 139 // h1 server with secure h1.1 proxy 140 async function test_h1_ws_with_secure_h1_proxy() { 141 let proxy = new NodeHTTPSProxyServer(); 142 await proxy.start(); 143 144 let wss = new NodeWebSocketServer(); 145 await wss.start(); 146 147 registerCleanupFunction(async () => { 148 await wss.stop(); 149 await proxy.stop(); 150 }); 151 152 Assert.notEqual(wss.port(), null); 153 await wss.registerMessageHandler((data, ws) => { 154 ws.send(data); 155 }); 156 157 let url = `wss://localhost:${wss.port()}`; 158 const msg = "test h1.1 websocket with h1.1 secure proxy"; 159 let [status, res] = await channelOpenPromise(url, msg); 160 Assert.equal(status, Cr.NS_OK); 161 Assert.deepEqual(res, [msg]); 162 163 await proxy.stop(); 164 } 165 166 async function test_h2_websocket_direct() { 167 Services.prefs.setBoolPref("network.http.http2.websockets", true); 168 let wss = new NodeWebSocketHttp2Server(); 169 await wss.start(); 170 registerCleanupFunction(async () => wss.stop()); 171 172 Assert.notEqual(wss.port(), null); 173 await wss.registerMessageHandler((data, ws) => { 174 ws.send(data); 175 }); 176 let url = `wss://localhost:${wss.port()}`; 177 const msg = "test h2 websocket h2 direct"; 178 let [status, res] = await channelOpenPromise(url, msg); 179 Assert.equal(status, Cr.NS_OK); 180 Assert.deepEqual(res, [msg]); 181 } 182 183 // ws h1.1 with insecure h1.1 proxy 184 async function test_h1_ws_with_h1_insecure_proxy() { 185 Services.prefs.setBoolPref("network.http.http2.websockets", false); 186 let proxy = new NodeHTTPProxyServer(); 187 await proxy.start(); 188 189 let wss = new NodeWebSocketServer(); 190 await wss.start(); 191 192 registerCleanupFunction(async () => { 193 await wss.stop(); 194 await proxy.stop(); 195 }); 196 197 Assert.notEqual(wss.port(), null); 198 199 await wss.registerMessageHandler((data, ws) => { 200 ws.send(data); 201 }); 202 let url = `wss://localhost:${wss.port()}`; 203 const msg = "test h1 websocket with h1 insecure proxy"; 204 let [status, res] = await channelOpenPromise(url, msg); 205 Assert.equal(status, Cr.NS_OK); 206 Assert.deepEqual(res, [msg]); 207 208 await proxy.stop(); 209 } 210 211 // ws h1.1 with h2 proxy 212 async function test_h1_ws_with_h2_proxy() { 213 Services.prefs.setBoolPref("network.http.http2.websockets", false); 214 215 let proxy = new NodeHTTP2ProxyServer(); 216 await proxy.start(); 217 218 let wss = new NodeWebSocketServer(); 219 await wss.start(); 220 221 registerCleanupFunction(async () => { 222 await wss.stop(); 223 await proxy.stop(); 224 }); 225 226 Assert.notEqual(wss.port(), null); 227 await wss.registerMessageHandler((data, ws) => { 228 ws.send(data); 229 }); 230 231 let url = `wss://localhost:${wss.port()}`; 232 const msg = "test h1 websocket with h2 proxy"; 233 let [status, res] = await channelOpenPromise(url, msg); 234 Assert.equal(status, Cr.NS_OK); 235 Assert.deepEqual(res, [msg]); 236 237 await proxy.stop(); 238 } 239 240 // ws h2 with insecure h1.1 proxy 241 async function test_h2_ws_with_h1_insecure_proxy() { 242 Services.prefs.setBoolPref("network.http.http2.websockets", true); 243 244 let proxy = new NodeHTTPProxyServer(); 245 await proxy.start(); 246 247 registerCleanupFunction(async () => { 248 await wss.stop(); 249 await proxy.stop(); 250 }); 251 252 let wss = new NodeWebSocketHttp2Server(); 253 await wss.start(); 254 Assert.notEqual(wss.port(), null); 255 await wss.registerMessageHandler((data, ws) => { 256 ws.send(data); 257 }); 258 259 let url = `wss://localhost:${wss.port()}`; 260 const msg = "test h2 websocket with h1 insecure proxy"; 261 let [status, res] = await channelOpenPromise(url, msg); 262 Assert.equal(status, Cr.NS_OK); 263 Assert.deepEqual(res, [msg]); 264 265 await proxy.stop(); 266 } 267 268 // ws h2 with secure h1 proxy 269 async function test_h2_ws_with_h1_secure_proxy() { 270 Services.prefs.setBoolPref("network.http.http2.websockets", true); 271 272 let proxy = new NodeHTTPSProxyServer(); 273 await proxy.start(); 274 275 let wss = new NodeWebSocketHttp2Server(); 276 await wss.start(); 277 278 registerCleanupFunction(async () => { 279 await wss.stop(); 280 await proxy.stop(); 281 }); 282 283 Assert.notEqual(wss.port(), null); 284 await wss.registerMessageHandler((data, ws) => { 285 ws.send(data); 286 }); 287 288 let url = `wss://localhost:${wss.port()}`; 289 const msg = "test h2 websocket with h1 secure proxy"; 290 let [status, res] = await channelOpenPromise(url, msg); 291 Assert.equal(status, Cr.NS_OK); 292 Assert.deepEqual(res, [msg]); 293 294 await proxy.stop(); 295 } 296 297 // ws h2 with secure h2 proxy 298 async function test_h2_ws_with_h2_proxy() { 299 Services.prefs.setBoolPref("network.http.http2.websockets", true); 300 301 let proxy = new NodeHTTP2ProxyServer(); 302 await proxy.start(); // start and register proxy "filter" 303 304 let wss = new NodeWebSocketHttp2Server(); 305 await wss.start(); // init port 306 307 registerCleanupFunction(async () => { 308 await wss.stop(); 309 await proxy.stop(); 310 }); 311 312 Assert.notEqual(wss.port(), null); 313 await wss.registerMessageHandler((data, ws) => { 314 ws.send(data); 315 }); 316 317 let url = `wss://localhost:${wss.port()}`; 318 const msg = "test h2 websocket with h2 proxy"; 319 let [status, res] = await channelOpenPromise(url, msg); 320 Assert.equal(status, Cr.NS_OK); 321 Assert.deepEqual(res, [msg]); 322 323 await proxy.stop(); 324 } 325 326 async function test_bug_1848013() { 327 Services.prefs.setBoolPref("network.http.http2.websockets", true); 328 329 let proxy = new NodeHTTPProxyServer(); 330 await proxy.start(); 331 332 registerCleanupFunction(async () => { 333 await wss.stop(); 334 await proxy.stop(); 335 }); 336 337 let wss = new NodeWebSocketHttp2Server(); 338 await wss.start(); 339 Assert.notEqual(wss.port(), null); 340 await wss.registerMessageHandler((data, ws) => { 341 ws.send(data); 342 }); 343 344 // To create a h2 connection before the websocket one. 345 let chan = makeChan(`https://localhost:${wss.port()}/`); 346 await httpChannelOpenPromise(chan, CL_ALLOW_UNKNOWN_CL); 347 348 let url = `wss://localhost:${wss.port()}`; 349 const msg = "test h2 websocket with h1 insecure proxy"; 350 let [status, res] = await channelOpenPromise(url, msg); 351 Assert.equal(status, Cr.NS_OK); 352 Assert.deepEqual(res, [msg]); 353 354 await proxy.stop(); 355 } 356 357 function ActivityObserver() {} 358 359 ActivityObserver.prototype = { 360 activites: [], 361 observeConnectionActivity( 362 aHost, 363 aPort, 364 aSSL, 365 aHasECH, 366 aIsHttp3, 367 aActivityType, 368 aActivitySubtype, 369 aTimestamp, 370 aExtraStringData 371 ) { 372 info( 373 "*** Connection Activity 0x" + 374 aActivityType.toString(16) + 375 " 0x" + 376 aActivitySubtype.toString(16) + 377 " " + 378 aExtraStringData + 379 "\n" 380 ); 381 this.activites.push({ 382 host: aHost, 383 subType: aActivitySubtype, 384 connInfo: aExtraStringData, 385 }); 386 }, 387 }; 388 389 function checkConnectionActivities(activites, host, port) { 390 let connections = []; 391 for (let activity of activites) { 392 if ( 393 activity.host != host || 394 activity.subType != 395 Ci.nsIHttpActivityDistributor.ACTIVITY_SUBTYPE_CONNECTION_CREATED 396 ) { 397 continue; 398 } 399 connections.push(activity.connInfo); 400 } 401 402 function parseConnInfoHash(str) { 403 if (str.length < 6) { 404 throw new Error("Invalid input string"); 405 } 406 // Extract the 6th character (index 5) 407 const h2Flag = str[5]; 408 // Regular expression to extract hostname and port 409 const regex = /\[.*?\](.*?):(\d+)/; 410 const match = str.match(regex); 411 if (!match) { 412 throw new Error("Can not extract hostname and port"); 413 } 414 return { 415 h2Flag, 416 host: match[1], 417 port: match[2], 418 }; 419 } 420 421 Assert.equal(connections.length, 2); 422 423 const firstConn = parseConnInfoHash(connections[0]); 424 Assert.equal(firstConn.h2Flag, "."); 425 Assert.equal(firstConn.host, host); 426 Assert.equal(firstConn.port, port); 427 const fallbackConn = parseConnInfoHash(connections[1]); 428 Assert.equal(fallbackConn.h2Flag, "X"); 429 Assert.equal(fallbackConn.host, host); 430 Assert.equal(fallbackConn.port, port); 431 } 432 433 async function test_websocket_fallback() { 434 let observerService = Cc[ 435 "@mozilla.org/network/http-activity-distributor;1" 436 ].getService(Ci.nsIHttpActivityDistributor); 437 let observer = new ActivityObserver(); 438 observerService.addObserver(observer); 439 observerService.observeConnection = true; 440 441 Services.prefs.setBoolPref("network.http.http2.websockets", true); 442 let wss = new NodeWebSocketHttp2Server(); 443 await wss.start(0, true); 444 registerCleanupFunction(async () => wss.stop()); 445 446 Assert.notEqual(wss.port(), null); 447 await wss.registerMessageHandler((data, ws) => { 448 ws.send(data); 449 }); 450 let url = `wss://localhost:${wss.port()}`; 451 await channelOpenPromise(url, ""); 452 checkConnectionActivities(observer.activites, "localhost", wss.port()); 453 } 454 455 add_task(test_h1_plain_websocket_direct); 456 add_task(test_h1_websocket_direct); 457 add_task(test_h2_websocket_direct); 458 add_task(test_h1_ws_with_secure_h1_proxy); 459 add_task(test_h1_ws_with_h1_insecure_proxy); 460 add_task(test_h1_ws_with_h2_proxy); 461 add_task(test_h2_ws_with_h1_insecure_proxy); 462 add_task(test_h2_ws_with_h1_secure_proxy); 463 add_task(test_h2_ws_with_h2_proxy); 464 add_task(test_bug_1848013); 465 add_task(test_websocket_fallback);