Bug 1857386 [wpt PR 42383] - Update wpt metadata, a=testonly
[gecko.git] / netwerk / test / unit / test_altsvc_http3.js
blobc80afdf1161269b452d7a91fccc7bb232ffca045
1 "use strict";
3 const { HttpServer } = ChromeUtils.importESModule(
4   "resource://testing-common/httpd.sys.mjs"
5 );
7 var h3Port;
9 // https://foo.example.com:(h3Port)
10 // https://bar.example.com:(h3Port) <- invalid for bar, but ok for foo
11 var h1Foo; // server http://foo.example.com:(h1Foo.identity.primaryPort)
12 var h1Bar; // server http://bar.example.com:(h1bar.identity.primaryPort)
14 var otherServer; // server socket listening for other connection.
16 var h3FooRoute; // foo.example.com:H3PORT
17 var h3BarRoute; // bar.example.com:H3PORT
18 var h3Route; // :H3PORT
19 var httpFooOrigin; // http://foo.exmaple.com:PORT/
20 var httpsFooOrigin; // https://foo.exmaple.com:PORT/
21 var httpBarOrigin; // http://bar.example.com:PORT/
22 var httpsBarOrigin; // https://bar.example.com:PORT/
24 function run_test() {
25   h3Port = Services.env.get("MOZHTTP3_PORT");
26   Assert.notEqual(h3Port, null);
27   Assert.notEqual(h3Port, "");
29   // Set to allow the cert presented by our H3 server
30   do_get_profile();
32   Services.prefs.setBoolPref("network.http.http3.enable", true);
33   Services.prefs.setBoolPref("network.http.altsvc.enabled", true);
34   Services.prefs.setBoolPref("network.http.altsvc.oe", true);
35   Services.prefs.setCharPref(
36     "network.dns.localDomains",
37     "foo.example.com, bar.example.com"
38   );
40   // The moz-http2 cert is for foo.example.com and is signed by http2-ca.pem
41   // so add that cert to the trust list as a signing cert. The same cert is used
42   // for both h3FooRoute and h3BarRoute though it is only valid for
43   // the foo.example.com domain name.
44   let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
45     Ci.nsIX509CertDB
46   );
47   addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
49   h1Foo = new HttpServer();
50   h1Foo.registerPathHandler("/altsvc-test", h1Server);
51   h1Foo.registerPathHandler("/.well-known/http-opportunistic", h1ServerWK);
52   h1Foo.start(-1);
53   h1Foo.identity.setPrimary(
54     "http",
55     "foo.example.com",
56     h1Foo.identity.primaryPort
57   );
59   h1Bar = new HttpServer();
60   h1Bar.registerPathHandler("/altsvc-test", h1Server);
61   h1Bar.start(-1);
62   h1Bar.identity.setPrimary(
63     "http",
64     "bar.example.com",
65     h1Bar.identity.primaryPort
66   );
68   h3FooRoute = "foo.example.com:" + h3Port;
69   h3BarRoute = "bar.example.com:" + h3Port;
70   h3Route = ":" + h3Port;
72   httpFooOrigin = "http://foo.example.com:" + h1Foo.identity.primaryPort + "/";
73   httpsFooOrigin = "https://" + h3FooRoute + "/";
74   httpBarOrigin = "http://bar.example.com:" + h1Bar.identity.primaryPort + "/";
75   httpsBarOrigin = "https://" + h3BarRoute + "/";
76   dump(
77     "http foo - " +
78       httpFooOrigin +
79       "\n" +
80       "https foo - " +
81       httpsFooOrigin +
82       "\n" +
83       "http bar - " +
84       httpBarOrigin +
85       "\n" +
86       "https bar - " +
87       httpsBarOrigin +
88       "\n"
89   );
91   doTest1();
94 function h1Server(metadata, response) {
95   response.setStatusLine(metadata.httpVersion, 200, "OK");
96   response.setHeader("Content-Type", "text/plain", false);
97   response.setHeader("Connection", "close", false);
98   response.setHeader("Cache-Control", "no-cache", false);
99   response.setHeader("Access-Control-Allow-Origin", "*", false);
100   response.setHeader("Access-Control-Allow-Method", "GET", false);
101   response.setHeader("Access-Control-Allow-Headers", "x-altsvc", false);
103   try {
104     var hval = "h3-29=" + metadata.getHeader("x-altsvc");
105     response.setHeader("Alt-Svc", hval, false);
106   } catch (e) {}
108   var body = "Q: What did 0 say to 8? A: Nice Belt!\n";
109   response.bodyOutputStream.write(body, body.length);
112 function h1ServerWK(metadata, response) {
113   response.setStatusLine(metadata.httpVersion, 200, "OK");
114   response.setHeader("Content-Type", "application/json", false);
115   response.setHeader("Connection", "close", false);
116   response.setHeader("Cache-Control", "no-cache", false);
117   response.setHeader("Access-Control-Allow-Origin", "*", false);
118   response.setHeader("Access-Control-Allow-Method", "GET", false);
119   response.setHeader("Access-Control-Allow-Headers", "x-altsvc", false);
121   var body = '["http://foo.example.com:' + h1Foo.identity.primaryPort + '"]';
122   response.bodyOutputStream.write(body, body.length);
125 function resetPrefs() {
126   Services.prefs.clearUserPref("network.http.http3.enable");
127   Services.prefs.clearUserPref("network.dns.localDomains");
128   Services.prefs.clearUserPref("network.http.altsvc.enabled");
129   Services.prefs.clearUserPref("network.http.altsvc.oe");
130   Services.prefs.clearUserPref("network.dns.localDomains");
131   Services.prefs.clearUserPref("network.security.ports.banned");
134 function makeChan(origin) {
135   return NetUtil.newChannel({
136     uri: origin + "altsvc-test",
137     loadUsingSystemPrincipal: true,
138   }).QueryInterface(Ci.nsIHttpChannel);
141 var origin;
142 var xaltsvc;
143 var loadWithoutClearingMappings = false;
144 var disallowH3 = false;
145 var disallowH2 = false;
146 var testKeepAliveNotSet = false;
147 var nextTest;
148 var expectPass = true;
149 var waitFor = 0;
150 var originAttributes = {};
152 var Listener = function () {};
153 Listener.prototype = {
154   onStartRequest: function testOnStartRequest(request) {
155     Assert.ok(request instanceof Ci.nsIHttpChannel);
157     if (expectPass) {
158       if (!Components.isSuccessCode(request.status)) {
159         do_throw(
160           "Channel should have a success code! (" + request.status + ")"
161         );
162       }
163       Assert.equal(request.responseStatus, 200);
164     } else {
165       Assert.equal(Components.isSuccessCode(request.status), false);
166     }
167   },
169   onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
170     read_stream(stream, cnt);
171   },
173   onStopRequest: function testOnStopRequest(request, status) {
174     var routed = "";
175     try {
176       routed = request.getRequestHeader("Alt-Used");
177     } catch (e) {}
178     dump("routed is " + routed + "\n");
179     Assert.equal(Components.isSuccessCode(status), expectPass);
181     if (waitFor != 0) {
182       Assert.equal(routed, "");
183       do_test_pending();
184       loadWithoutClearingMappings = true;
185       do_timeout(waitFor, doTest);
186       waitFor = 0;
187       xaltsvc = "NA";
188     } else if (xaltsvc == "NA") {
189       Assert.equal(routed, "");
190       nextTest();
191     } else if (routed == xaltsvc) {
192       Assert.equal(routed, xaltsvc); // always true, but a useful log
193       nextTest();
194     } else {
195       dump("poll later for alt svc mapping\n");
196       do_test_pending();
197       loadWithoutClearingMappings = true;
198       do_timeout(500, doTest);
199     }
201     do_test_finished();
202   },
205 function testsDone() {
206   dump("testDone\n");
207   resetPrefs();
208   do_test_pending();
209   otherServer.close();
210   do_test_pending();
211   h1Foo.stop(do_test_finished);
212   do_test_pending();
213   h1Bar.stop(do_test_finished);
216 function doTest() {
217   dump("execute doTest " + origin + "\n");
218   var chan = makeChan(origin);
219   var listener = new Listener();
220   if (xaltsvc != "NA") {
221     chan.setRequestHeader("x-altsvc", xaltsvc, false);
222   }
223   if (testKeepAliveNotSet) {
224     chan.setRequestHeader("Connection", "close", false);
225     testKeepAliveNotSet = false;
226   }
227   if (loadWithoutClearingMappings) {
228     chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
229   } else {
230     chan.loadFlags =
231       Ci.nsIRequest.LOAD_FRESH_CONNECTION |
232       Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
233   }
234   if (disallowH3) {
235     let internalChannel = chan.QueryInterface(Ci.nsIHttpChannelInternal);
236     internalChannel.allowHttp3 = false;
237     disallowH3 = false;
238   }
239   if (disallowH2) {
240     let internalChannel = chan.QueryInterface(Ci.nsIHttpChannelInternal);
241     internalChannel.allowSpdy = false;
242     disallowH2 = false;
243   }
244   loadWithoutClearingMappings = false;
245   chan.loadInfo.originAttributes = originAttributes;
246   chan.asyncOpen(listener);
249 // xaltsvc is overloaded to do two things..
250 // 1] it is sent in the x-altsvc request header, and the response uses the value in the Alt-Svc response header
251 // 2] the test polls until necko sets Alt-Used to that value (i.e. it uses that route)
253 // When xaltsvc is set to h3Route (i.e. :port with the implied hostname) it doesn't match the alt-used,
254 // which is always explicit, so it needs to be changed after the channel is created but before the
255 // listener is invoked
257 // http://foo served from h3-29=:port
258 function doTest1() {
259   dump("doTest1()\n");
260   origin = httpFooOrigin;
261   xaltsvc = h3Route;
262   nextTest = doTest2;
263   do_test_pending();
264   doTest();
265   xaltsvc = h3FooRoute;
268 // http://foo served from h3-29=foo:port
269 function doTest2() {
270   dump("doTest2()\n");
271   origin = httpFooOrigin;
272   xaltsvc = h3FooRoute;
273   nextTest = doTest3;
274   do_test_pending();
275   doTest();
278 // http://foo served from h3-29=bar:port
279 // requires cert for foo
280 function doTest3() {
281   dump("doTest3()\n");
282   origin = httpFooOrigin;
283   xaltsvc = h3BarRoute;
284   nextTest = doTest4;
285   do_test_pending();
286   doTest();
289 // https://bar should fail because host bar has cert for foo
290 function doTest4() {
291   dump("doTest4()\n");
292   origin = httpsBarOrigin;
293   xaltsvc = "";
294   expectPass = false;
295   nextTest = doTest5;
296   do_test_pending();
297   doTest();
300 // http://bar via h3 on bar
301 // should not use TLS/h3 because h3BarRoute is not auth'd for bar
302 // however the test ought to PASS (i.e. get a 200) because fallback
303 // to plaintext happens.. thus the timeout
304 function doTest5() {
305   dump("doTest5()\n");
306   origin = httpBarOrigin;
307   xaltsvc = h3BarRoute;
308   expectPass = true;
309   waitFor = 500;
310   nextTest = doTest6;
311   do_test_pending();
312   doTest();
315 // http://bar served from h3-29=:port, which is like the bar route in 8
316 function doTest6() {
317   dump("doTest6()\n");
318   origin = httpBarOrigin;
319   xaltsvc = h3Route;
320   expectPass = true;
321   waitFor = 500;
322   nextTest = doTest7;
323   do_test_pending();
324   doTest();
325   xaltsvc = h3BarRoute;
328 // check again https://bar should fail because host bar has cert for foo
329 function doTest7() {
330   dump("doTest7()\n");
331   origin = httpsBarOrigin;
332   xaltsvc = "";
333   expectPass = false;
334   nextTest = doTest8;
335   do_test_pending();
336   doTest();
339 // http://bar served from h3-29=foo, should fail because host foo only has
340 // cert for foo. Fail in this case means alt-svc is not used, but content
341 // is served
342 function doTest8() {
343   dump("doTest8()\n");
344   origin = httpBarOrigin;
345   xaltsvc = h3FooRoute;
346   expectPass = true;
347   waitFor = 500;
348   nextTest = doTest9;
349   do_test_pending();
350   doTest();
353 // Test 9-12:
354 // Insert a cache of http://foo served from h3-29=:port with origin attributes.
355 function doTest9() {
356   dump("doTest9()\n");
357   origin = httpFooOrigin;
358   xaltsvc = h3Route;
359   originAttributes = {
360     userContextId: 1,
361     firstPartyDomain: "a.com",
362   };
363   nextTest = doTest10;
364   do_test_pending();
365   doTest();
366   xaltsvc = h3FooRoute;
369 // Make sure we get a cache miss with a different userContextId.
370 function doTest10() {
371   dump("doTest10()\n");
372   origin = httpFooOrigin;
373   xaltsvc = "NA";
374   originAttributes = {
375     userContextId: 2,
376     firstPartyDomain: "a.com",
377   };
378   loadWithoutClearingMappings = true;
379   nextTest = doTest11;
380   do_test_pending();
381   doTest();
384 // Make sure we get a cache miss with a different firstPartyDomain.
385 function doTest11() {
386   dump("doTest11()\n");
387   origin = httpFooOrigin;
388   xaltsvc = "NA";
389   originAttributes = {
390     userContextId: 1,
391     firstPartyDomain: "b.com",
392   };
393   loadWithoutClearingMappings = true;
394   nextTest = doTest12;
395   do_test_pending();
396   doTest();
399 // Make sure we get a cache hit with the same origin attributes.
400 function doTest12() {
401   dump("doTest12()\n");
402   origin = httpFooOrigin;
403   xaltsvc = "NA";
404   originAttributes = {
405     userContextId: 1,
406     firstPartyDomain: "a.com",
407   };
408   loadWithoutClearingMappings = true;
409   nextTest = doTest13;
410   do_test_pending();
411   doTest();
412   // This ensures a cache hit.
413   xaltsvc = h3FooRoute;
416 // Make sure we do not use H3 if it is disabled on a channel.
417 function doTest13() {
418   dump("doTest13()\n");
419   origin = httpFooOrigin;
420   xaltsvc = "NA";
421   disallowH3 = true;
422   originAttributes = {
423     userContextId: 1,
424     firstPartyDomain: "a.com",
425   };
426   loadWithoutClearingMappings = true;
427   nextTest = doTest14;
428   do_test_pending();
429   doTest();
432 // Make sure we use H3 if only Http2 is disabled on a channel.
433 function doTest14() {
434   dump("doTest14()\n");
435   origin = httpFooOrigin;
436   xaltsvc = "NA";
437   disallowH2 = true;
438   originAttributes = {
439     userContextId: 1,
440     firstPartyDomain: "a.com",
441   };
442   loadWithoutClearingMappings = true;
443   nextTest = doTest15;
444   do_test_pending();
445   doTest();
446   // This should ensures a cache hit.
447   xaltsvc = h3FooRoute;
450 // Make sure we do not use H3 if NS_HTTP_ALLOW_KEEPALIVE is not set.
451 function doTest15() {
452   dump("doTest15()\n");
453   origin = httpFooOrigin;
454   xaltsvc = "NA";
455   testKeepAliveNotSet = true;
456   originAttributes = {
457     userContextId: 1,
458     firstPartyDomain: "a.com",
459   };
460   loadWithoutClearingMappings = true;
461   nextTest = doTest16;
462   do_test_pending();
463   doTest();
466 // Check we don't connect to blocked ports
467 function doTest16() {
468   dump("doTest16()\n");
469   origin = httpFooOrigin;
470   nextTest = testsDone;
471   otherServer = Cc["@mozilla.org/network/server-socket;1"].createInstance(
472     Ci.nsIServerSocket
473   );
474   otherServer.init(-1, true, -1);
475   xaltsvc = "localhost:" + otherServer.port;
476   Services.prefs.setCharPref(
477     "network.security.ports.banned",
478     "" + otherServer.port
479   );
480   dump("Blocked port: " + otherServer.port);
481   waitFor = 500;
482   otherServer.asyncListen({
483     onSocketAccepted() {
484       Assert.ok(false, "Got connection to socket when we didn't expect it!");
485     },
486     onStopListening() {
487       // We get closed when the entire file is done, which guarantees we get the socket accept
488       // if we do connect to the alt-svc header
489       do_test_finished();
490     },
491   });
492   do_test_pending();
493   doTest();