test_altsvc.js (16552B)
1 "use strict"; 2 3 const { HttpServer } = ChromeUtils.importESModule( 4 "resource://testing-common/httpd.sys.mjs" 5 ); 6 7 var h2Port; 8 var prefs; 9 var http2pref; 10 var altsvcpref1; 11 var altsvcpref2; 12 13 // https://foo.example.com:(h2Port) 14 // https://bar.example.com:(h2Port) <- invalid for bar, but ok for foo 15 var h1Foo; // server http://foo.example.com:(h1Foo.identity.primaryPort) 16 var h1Bar; // server http://bar.example.com:(h1bar.identity.primaryPort) 17 18 var otherServer; // server socket listening for other connection. 19 20 var h2FooRoute; // foo.example.com:H2PORT 21 var h2BarRoute; // bar.example.com:H2PORT 22 var h2Route; // :H2PORT 23 var httpFooOrigin; // http://foo.exmaple.com:PORT/ 24 var httpsFooOrigin; // https://foo.exmaple.com:PORT/ 25 var httpBarOrigin; // http://bar.example.com:PORT/ 26 var httpsBarOrigin; // https://bar.example.com:PORT/ 27 28 function run_test() { 29 h2Port = Services.env.get("MOZHTTP2_PORT"); 30 Assert.notEqual(h2Port, null); 31 Assert.notEqual(h2Port, ""); 32 33 // Set to allow the cert presented by our H2 server 34 do_get_profile(); 35 prefs = Services.prefs; 36 37 http2pref = prefs.getBoolPref("network.http.http2.enabled"); 38 altsvcpref1 = prefs.getBoolPref("network.http.altsvc.enabled"); 39 altsvcpref2 = prefs.getBoolPref("network.http.altsvc.oe", true); 40 41 prefs.setBoolPref("network.http.http2.enabled", true); 42 prefs.setBoolPref("network.http.altsvc.enabled", true); 43 prefs.setBoolPref("network.http.altsvc.oe", true); 44 prefs.setCharPref( 45 "network.dns.localDomains", 46 "foo.example.com, bar.example.com" 47 ); 48 49 // The moz-http2 cert is for foo.example.com and is signed by http2-ca.pem 50 // so add that cert to the trust list as a signing cert. The same cert is used 51 // for both h2FooRoute and h2BarRoute though it is only valid for 52 // the foo.example.com domain name. 53 let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService( 54 Ci.nsIX509CertDB 55 ); 56 addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u"); 57 58 h1Foo = new HttpServer(); 59 h1Foo.registerPathHandler("/altsvc-test", h1Server); 60 h1Foo.registerPathHandler("/.well-known/http-opportunistic", h1ServerWK); 61 h1Foo.start(-1); 62 h1Foo.identity.setPrimary( 63 "http", 64 "foo.example.com", 65 h1Foo.identity.primaryPort 66 ); 67 68 h1Bar = new HttpServer(); 69 h1Bar.registerPathHandler("/altsvc-test", h1Server); 70 h1Bar.start(-1); 71 h1Bar.identity.setPrimary( 72 "http", 73 "bar.example.com", 74 h1Bar.identity.primaryPort 75 ); 76 77 h2FooRoute = "foo.example.com:" + h2Port; 78 h2BarRoute = "bar.example.com:" + h2Port; 79 h2Route = ":" + h2Port; 80 81 httpFooOrigin = "http://foo.example.com:" + h1Foo.identity.primaryPort + "/"; 82 httpsFooOrigin = "https://" + h2FooRoute + "/"; 83 httpBarOrigin = "http://bar.example.com:" + h1Bar.identity.primaryPort + "/"; 84 httpsBarOrigin = "https://" + h2BarRoute + "/"; 85 dump( 86 "http foo - " + 87 httpFooOrigin + 88 "\n" + 89 "https foo - " + 90 httpsFooOrigin + 91 "\n" + 92 "http bar - " + 93 httpBarOrigin + 94 "\n" + 95 "https bar - " + 96 httpsBarOrigin + 97 "\n" 98 ); 99 100 doTest1(); 101 } 102 103 function h1Server(metadata, response) { 104 response.setStatusLine(metadata.httpVersion, 200, "OK"); 105 response.setHeader("Content-Type", "text/plain", false); 106 response.setHeader("Connection", "close", false); 107 response.setHeader("Cache-Control", "no-cache", false); 108 response.setHeader("Access-Control-Allow-Origin", "*", false); 109 response.setHeader("Access-Control-Allow-Method", "GET", false); 110 response.setHeader("Access-Control-Allow-Headers", "x-altsvc", false); 111 112 try { 113 var hval = "h2=" + metadata.getHeader("x-altsvc"); 114 response.setHeader("Alt-Svc", hval, false); 115 } catch (e) {} 116 117 var body = "Q: What did 0 say to 8? A: Nice Belt!\n"; 118 response.bodyOutputStream.write(body, body.length); 119 } 120 121 function h1ServerWK(metadata, response) { 122 response.setStatusLine(metadata.httpVersion, 200, "OK"); 123 response.setHeader("Content-Type", "application/json", false); 124 response.setHeader("Connection", "close", false); 125 response.setHeader("Cache-Control", "no-cache", false); 126 response.setHeader("Access-Control-Allow-Origin", "*", false); 127 response.setHeader("Access-Control-Allow-Method", "GET", false); 128 response.setHeader("Access-Control-Allow-Headers", "x-altsvc", false); 129 130 var body = '["http://foo.example.com:' + h1Foo.identity.primaryPort + '"]'; 131 response.bodyOutputStream.write(body, body.length); 132 } 133 134 function resetPrefs() { 135 prefs.setBoolPref("network.http.http2.enabled", http2pref); 136 prefs.setBoolPref("network.http.altsvc.enabled", altsvcpref1); 137 prefs.setBoolPref("network.http.altsvc.oe", altsvcpref2); 138 prefs.clearUserPref("network.dns.localDomains"); 139 prefs.clearUserPref("network.security.ports.banned"); 140 } 141 142 function makeChan(origin) { 143 return NetUtil.newChannel({ 144 uri: origin + "altsvc-test", 145 loadUsingSystemPrincipal: true, 146 }).QueryInterface(Ci.nsIHttpChannel); 147 } 148 149 var origin; 150 var xaltsvc; 151 var loadWithoutClearingMappings = false; 152 var disallowH3 = false; 153 var disallowH2 = false; 154 var nextTest; 155 var expectPass = true; 156 var waitFor = 0; 157 var originAttributes = {}; 158 159 var Listener = function () {}; 160 Listener.prototype = { 161 onStartRequest: function testOnStartRequest(request) { 162 Assert.ok(request instanceof Ci.nsIHttpChannel); 163 164 if (expectPass) { 165 if (!Components.isSuccessCode(request.status)) { 166 do_throw( 167 "Channel should have a success code! (" + request.status + ")" 168 ); 169 } 170 Assert.equal(request.responseStatus, 200); 171 } else { 172 Assert.equal(Components.isSuccessCode(request.status), false); 173 } 174 }, 175 176 onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) { 177 read_stream(stream, cnt); 178 }, 179 180 onStopRequest: function testOnStopRequest(request, status) { 181 var routed = ""; 182 try { 183 routed = request.getRequestHeader("Alt-Used"); 184 } catch (e) {} 185 dump("routed is " + routed + "\n"); 186 Assert.equal(Components.isSuccessCode(status), expectPass); 187 188 if (waitFor != 0) { 189 Assert.equal(routed, ""); 190 do_test_pending(); 191 loadWithoutClearingMappings = true; 192 do_timeout(waitFor, doTest); 193 waitFor = 0; 194 xaltsvc = "NA"; 195 } else if (xaltsvc == "NA") { 196 Assert.equal(routed, ""); 197 nextTest(); 198 } else if (routed == xaltsvc) { 199 Assert.equal(routed, xaltsvc); // always true, but a useful log 200 nextTest(); 201 } else { 202 dump("poll later for alt svc mapping\n"); 203 do_test_pending(); 204 loadWithoutClearingMappings = true; 205 do_timeout(500, doTest); 206 } 207 208 do_test_finished(); 209 }, 210 }; 211 212 function testsDone() { 213 dump("testDone\n"); 214 resetPrefs(); 215 do_test_pending(); 216 otherServer.close(); 217 do_test_pending(); 218 h1Foo.stop(do_test_finished); 219 do_test_pending(); 220 h1Bar.stop(do_test_finished); 221 } 222 223 function doTest() { 224 dump("execute doTest " + origin + "\n"); 225 var chan = makeChan(origin); 226 var listener = new Listener(); 227 if (xaltsvc != "NA") { 228 chan.setRequestHeader("x-altsvc", xaltsvc, false); 229 } 230 if (loadWithoutClearingMappings) { 231 chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI; 232 } else { 233 chan.loadFlags = 234 Ci.nsIRequest.LOAD_FRESH_CONNECTION | 235 Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI; 236 } 237 if (disallowH3) { 238 let internalChannel = chan.QueryInterface(Ci.nsIHttpChannelInternal); 239 internalChannel.allowHttp3 = false; 240 disallowH3 = false; 241 } 242 if (disallowH2) { 243 let internalChannel = chan.QueryInterface(Ci.nsIHttpChannelInternal); 244 internalChannel.allowSpdy = false; 245 disallowH2 = false; 246 } 247 loadWithoutClearingMappings = false; 248 chan.loadInfo.originAttributes = originAttributes; 249 chan.asyncOpen(listener); 250 } 251 252 // xaltsvc is overloaded to do two things.. 253 // 1] it is sent in the x-altsvc request header, and the response uses the value in the Alt-Svc response header 254 // 2] the test polls until necko sets Alt-Used to that value (i.e. it uses that route) 255 // 256 // When xaltsvc is set to h2Route (i.e. :port with the implied hostname) it doesn't match the alt-used, 257 // which is always explicit, so it needs to be changed after the channel is created but before the 258 // listener is invoked 259 260 // http://foo served from h2=:port 261 function doTest1() { 262 dump("doTest1()\n"); 263 origin = httpFooOrigin; 264 xaltsvc = h2Route; 265 nextTest = doTest2; 266 do_test_pending(); 267 doTest(); 268 xaltsvc = h2FooRoute; 269 } 270 271 // http://foo served from h2=foo:port 272 function doTest2() { 273 dump("doTest2()\n"); 274 origin = httpFooOrigin; 275 xaltsvc = h2FooRoute; 276 nextTest = doTest3; 277 do_test_pending(); 278 doTest(); 279 } 280 281 // http://foo served from h2=bar:port 282 // requires cert for foo 283 function doTest3() { 284 dump("doTest3()\n"); 285 origin = httpFooOrigin; 286 xaltsvc = h2BarRoute; 287 nextTest = doTest4; 288 do_test_pending(); 289 doTest(); 290 } 291 292 // https://bar should fail because host bar has cert for foo 293 function doTest4() { 294 dump("doTest4()\n"); 295 origin = httpsBarOrigin; 296 xaltsvc = ""; 297 expectPass = false; 298 nextTest = doTest5; 299 do_test_pending(); 300 doTest(); 301 } 302 303 // https://foo no alt-svc (just check cert setup) 304 function doTest5() { 305 dump("doTest5()\n"); 306 origin = httpsFooOrigin; 307 xaltsvc = "NA"; 308 expectPass = true; 309 nextTest = doTest6; 310 do_test_pending(); 311 doTest(); 312 } 313 314 // https://foo via bar (bar has cert for foo) 315 function doTest6() { 316 dump("doTest6()\n"); 317 origin = httpsFooOrigin; 318 xaltsvc = h2BarRoute; 319 nextTest = doTest7; 320 do_test_pending(); 321 doTest(); 322 } 323 324 // check again https://bar should fail because host bar has cert for foo 325 function doTest7() { 326 dump("doTest7()\n"); 327 origin = httpsBarOrigin; 328 xaltsvc = ""; 329 expectPass = false; 330 nextTest = doTest8; 331 do_test_pending(); 332 doTest(); 333 } 334 335 // http://bar via h2 on bar 336 // should not use TLS/h2 because h2BarRoute is not auth'd for bar 337 // however the test ought to PASS (i.e. get a 200) because fallback 338 // to plaintext happens.. thus the timeout 339 function doTest8() { 340 dump("doTest8()\n"); 341 origin = httpBarOrigin; 342 xaltsvc = h2BarRoute; 343 expectPass = true; 344 waitFor = 500; 345 nextTest = doTest9; 346 do_test_pending(); 347 doTest(); 348 } 349 350 // http://bar served from h2=:port, which is like the bar route in 8 351 function doTest9() { 352 dump("doTest9()\n"); 353 origin = httpBarOrigin; 354 xaltsvc = h2Route; 355 expectPass = true; 356 waitFor = 500; 357 nextTest = doTest10; 358 do_test_pending(); 359 doTest(); 360 xaltsvc = h2BarRoute; 361 } 362 363 // check again https://bar should fail because host bar has cert for foo 364 function doTest10() { 365 dump("doTest10()\n"); 366 origin = httpsBarOrigin; 367 xaltsvc = ""; 368 expectPass = false; 369 nextTest = doTest11; 370 do_test_pending(); 371 doTest(); 372 } 373 374 // http://bar served from h2=foo, should fail because host foo only has 375 // cert for foo. Fail in this case means alt-svc is not used, but content 376 // is served 377 function doTest11() { 378 dump("doTest11()\n"); 379 origin = httpBarOrigin; 380 xaltsvc = h2FooRoute; 381 expectPass = true; 382 waitFor = 500; 383 nextTest = doTest12; 384 do_test_pending(); 385 doTest(); 386 } 387 388 // Test 12-15: 389 // Insert a cache of http://foo served from h2=:port with origin attributes. 390 function doTest12() { 391 dump("doTest12()\n"); 392 origin = httpFooOrigin; 393 xaltsvc = h2Route; 394 originAttributes = { 395 userContextId: 1, 396 firstPartyDomain: "a.com", 397 }; 398 nextTest = doTest13; 399 do_test_pending(); 400 doTest(); 401 xaltsvc = h2FooRoute; 402 } 403 404 // Make sure we get a cache miss with a different userContextId. 405 function doTest13() { 406 dump("doTest13()\n"); 407 origin = httpFooOrigin; 408 xaltsvc = "NA"; 409 originAttributes = { 410 userContextId: 2, 411 firstPartyDomain: "a.com", 412 }; 413 loadWithoutClearingMappings = true; 414 nextTest = doTest14; 415 do_test_pending(); 416 doTest(); 417 } 418 419 // Make sure we get a cache miss with a different firstPartyDomain. 420 function doTest14() { 421 dump("doTest14()\n"); 422 origin = httpFooOrigin; 423 xaltsvc = "NA"; 424 originAttributes = { 425 userContextId: 1, 426 firstPartyDomain: "b.com", 427 }; 428 loadWithoutClearingMappings = true; 429 nextTest = doTest15; 430 do_test_pending(); 431 doTest(); 432 } 433 // 434 // Make sure we get a cache hit with the same origin attributes. 435 function doTest15() { 436 dump("doTest15()\n"); 437 origin = httpFooOrigin; 438 xaltsvc = "NA"; 439 originAttributes = { 440 userContextId: 1, 441 firstPartyDomain: "a.com", 442 }; 443 loadWithoutClearingMappings = true; 444 nextTest = doTest16; 445 do_test_pending(); 446 doTest(); 447 // This ensures a cache hit. 448 xaltsvc = h2FooRoute; 449 } 450 451 // Make sure we do not use H2 if it is disabled on a channel. 452 function doTest16() { 453 dump("doTest16()\n"); 454 origin = httpFooOrigin; 455 xaltsvc = "NA"; 456 disallowH2 = true; 457 originAttributes = { 458 userContextId: 1, 459 firstPartyDomain: "a.com", 460 }; 461 loadWithoutClearingMappings = true; 462 nextTest = doTest17; 463 do_test_pending(); 464 doTest(); 465 } 466 467 // Make sure we use H2 if only Http3 is disabled on a channel. 468 function doTest17() { 469 dump("doTest17()\n"); 470 origin = httpFooOrigin; 471 xaltsvc = h2Route; 472 disallowH3 = true; 473 originAttributes = { 474 userContextId: 1, 475 firstPartyDomain: "a.com", 476 }; 477 loadWithoutClearingMappings = true; 478 nextTest = doTest18; 479 do_test_pending(); 480 doTest(); 481 // This should ensures a cache hit. 482 xaltsvc = h2FooRoute; 483 } 484 485 // Check we don't connect to blocked ports 486 function doTest18() { 487 dump("doTest18()\n"); 488 origin = httpFooOrigin; 489 nextTest = testsDone; 490 otherServer = Cc["@mozilla.org/network/server-socket;1"].createInstance( 491 Ci.nsIServerSocket 492 ); 493 otherServer.init(-1, true, -1); 494 xaltsvc = "localhost:" + otherServer.port; 495 Services.prefs.setCharPref( 496 "network.security.ports.banned", 497 "" + otherServer.port 498 ); 499 dump("Blocked port: " + otherServer.port); 500 waitFor = 500; 501 otherServer.asyncListen({ 502 onSocketAccepted() { 503 Assert.ok(false, "Got connection to socket when we didn't expect it!"); 504 }, 505 onStopListening() { 506 // We get closed when the entire file is done, which guarantees we get the socket accept 507 // if we do connect to the alt-svc header 508 do_test_finished(); 509 }, 510 }); 511 nextTest = doTest19; 512 do_test_pending(); 513 doTest(); 514 } 515 516 // Check we don't connect to blocked ports 517 function doTest19() { 518 dump("doTest19()\n"); 519 origin = httpFooOrigin; 520 nextTest = testsDone; 521 otherServer = Cc["@mozilla.org/network/server-socket;1"].createInstance( 522 Ci.nsIServerSocket 523 ); 524 const BAD_PORT_U32 = 6667 + 65536; 525 otherServer.init(BAD_PORT_U32, true, -1); 526 Assert.equal(otherServer.port, 6667, "Trying to listen on port 6667"); 527 xaltsvc = "localhost:" + BAD_PORT_U32; 528 dump("Blocked port: " + otherServer.port); 529 waitFor = 500; 530 otherServer.asyncListen({ 531 onSocketAccepted() { 532 Assert.ok(false, "Got connection to socket when we didn't expect it!"); 533 }, 534 onStopListening() { 535 // We get closed when the entire file is done, which guarantees we get the socket accept 536 // if we do connect to the alt-svc header 537 do_test_finished(); 538 }, 539 }); 540 nextTest = doTest20; 541 do_test_pending(); 542 doTest(); 543 } 544 function doTest20() { 545 dump("doTest20()\n"); 546 origin = httpFooOrigin; 547 nextTest = testsDone; 548 otherServer = Cc["@mozilla.org/network/server-socket;1"].createInstance( 549 Ci.nsIServerSocket 550 ); 551 const BAD_PORT_U64 = 6666 + 429496729; 552 otherServer.init(6666, true, -1); 553 Assert.equal(otherServer.port, 6666, "Trying to listen on port 6666"); 554 xaltsvc = "localhost:" + BAD_PORT_U64; 555 dump("Blocked port: " + otherServer.port); 556 waitFor = 500; 557 otherServer.asyncListen({ 558 onSocketAccepted() { 559 Assert.ok(false, "Got connection to socket when we didn't expect it!"); 560 }, 561 onStopListening() { 562 // We get closed when the entire file is done, which guarantees we get the socket accept 563 // if we do connect to the alt-svc header 564 do_test_finished(); 565 }, 566 }); 567 nextTest = doTest21; 568 do_test_pending(); 569 doTest(); 570 } 571 // Port 65535 should be OK 572 function doTest21() { 573 dump("doTest21()\n"); 574 origin = httpFooOrigin; 575 nextTest = testsDone; 576 otherServer = Cc["@mozilla.org/network/server-socket;1"].createInstance( 577 Ci.nsIServerSocket 578 ); 579 const GOOD_PORT = 65535; 580 otherServer.init(65535, true, -1); 581 Assert.equal(otherServer.port, 65535, "Trying to listen on port 65535"); 582 xaltsvc = "localhost:" + GOOD_PORT; 583 dump("Allowed port: " + otherServer.port); 584 waitFor = 500; 585 otherServer.asyncListen({ 586 onSocketAccepted() { 587 Assert.ok(true, "Got connection to socket when we didn't expect it!"); 588 }, 589 onStopListening() { 590 // We get closed when the entire file is done, which guarantees we get the socket accept 591 // if we do connect to the alt-svc header 592 do_test_finished(); 593 }, 594 }); 595 do_test_pending(); 596 doTest(); 597 }