Bug 1857386 [wpt PR 42383] - Update wpt metadata, a=testonly
[gecko.git] / netwerk / test / unit / test_altsvc.js
blobea9ac3ade04b07b6d16a67950ffc5abfb981c00d
1 "use strict";
3 const { HttpServer } = ChromeUtils.importESModule(
4   "resource://testing-common/httpd.sys.mjs"
5 );
7 var h2Port;
8 var prefs;
9 var http2pref;
10 var altsvcpref1;
11 var altsvcpref2;
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)
18 var otherServer; // server socket listening for other connection.
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/
28 function run_test() {
29   h2Port = Services.env.get("MOZHTTP2_PORT");
30   Assert.notEqual(h2Port, null);
31   Assert.notEqual(h2Port, "");
33   // Set to allow the cert presented by our H2 server
34   do_get_profile();
35   prefs = Services.prefs;
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);
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   );
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");
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   );
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   );
77   h2FooRoute = "foo.example.com:" + h2Port;
78   h2BarRoute = "bar.example.com:" + h2Port;
79   h2Route = ":" + h2Port;
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   );
100   doTest1();
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);
112   try {
113     var hval = "h2=" + metadata.getHeader("x-altsvc");
114     response.setHeader("Alt-Svc", hval, false);
115   } catch (e) {}
117   var body = "Q: What did 0 say to 8? A: Nice Belt!\n";
118   response.bodyOutputStream.write(body, body.length);
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);
130   var body = '["http://foo.example.com:' + h1Foo.identity.primaryPort + '"]';
131   response.bodyOutputStream.write(body, body.length);
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");
142 function makeChan(origin) {
143   return NetUtil.newChannel({
144     uri: origin + "altsvc-test",
145     loadUsingSystemPrincipal: true,
146   }).QueryInterface(Ci.nsIHttpChannel);
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 = {};
159 var Listener = function () {};
160 Listener.prototype = {
161   onStartRequest: function testOnStartRequest(request) {
162     Assert.ok(request instanceof Ci.nsIHttpChannel);
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   },
176   onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
177     read_stream(stream, cnt);
178   },
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);
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     }
208     do_test_finished();
209   },
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);
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);
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)
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
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;
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();
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();
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();
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();
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();
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();
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();
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;
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();
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();
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;
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();
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();
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;
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();
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;
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();
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.ok(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();
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.ok(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();
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.ok(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();