Bug 1857386 [wpt PR 42383] - Update wpt metadata, a=testonly
[gecko.git] / netwerk / test / unit / test_httpssvc_https_upgrade.js
blob837006767c1ed9c8759e8394b9a51ab841ac6899
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/. */
5 "use strict";
7 const certOverrideService = Cc[
8   "@mozilla.org/security/certoverride;1"
9 ].getService(Ci.nsICertOverrideService);
10 const { HttpServer } = ChromeUtils.importESModule(
11   "resource://testing-common/httpd.sys.mjs"
13 const { TestUtils } = ChromeUtils.importESModule(
14   "resource://testing-common/TestUtils.sys.mjs"
17 const ReferrerInfo = Components.Constructor(
18   "@mozilla.org/referrer-info;1",
19   "nsIReferrerInfo",
20   "init"
23 add_setup(async function setup() {
24   trr_test_setup();
26   let h2Port = Services.env.get("MOZHTTP2_PORT");
27   Assert.notEqual(h2Port, null);
28   Assert.notEqual(h2Port, "");
30   Services.prefs.setCharPref(
31     "network.trr.uri",
32     "https://foo.example.com:" + h2Port + "/httpssvc_as_altsvc"
33   );
35   Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
36   Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
38   Services.prefs.setBoolPref(
39     "network.dns.use_https_rr_for_speculative_connection",
40     true
41   );
43   registerCleanupFunction(async () => {
44     trr_clear_prefs();
45     Services.prefs.clearUserPref("network.dns.upgrade_with_https_rr");
46     Services.prefs.clearUserPref("network.dns.use_https_rr_as_altsvc");
47     Services.prefs.clearUserPref(
48       "network.dns.use_https_rr_for_speculative_connection"
49     );
50     Services.prefs.clearUserPref("network.dns.notifyResolution");
51     Services.prefs.clearUserPref("network.dns.disablePrefetch");
52   });
54   if (mozinfo.socketprocess_networking) {
55     Services.dns; // Needed to trigger socket process.
56     await TestUtils.waitForCondition(() => Services.io.socketProcessLaunched);
57   }
59   Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRFIRST);
60 });
62 function makeChan(url) {
63   let chan = NetUtil.newChannel({
64     uri: url,
65     loadUsingSystemPrincipal: true,
66     contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
67   }).QueryInterface(Ci.nsIHttpChannel);
68   return chan;
71 // When observer is specified, the channel will be suspended when receiving
72 // "http-on-modify-request".
73 function channelOpenPromise(chan, flags, observer) {
74   return new Promise(resolve => {
75     function finish(req, buffer) {
76       certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
77         false
78       );
79       resolve([req, buffer]);
80     }
81     certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
82       true
83     );
85     if (observer) {
86       let topic = "http-on-modify-request";
87       Services.obs.addObserver(observer, topic);
88     }
89     chan.asyncOpen(new ChannelListener(finish, null, flags));
90   });
93 class EventSinkListener {
94   getInterface(iid) {
95     if (iid.equals(Ci.nsIChannelEventSink)) {
96       return this;
97     }
98     throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
99   }
100   asyncOnChannelRedirect(oldChan, newChan, flags, callback) {
101     Assert.equal(oldChan.URI.hostPort, newChan.URI.hostPort);
102     Assert.equal(oldChan.URI.scheme, "http");
103     Assert.equal(newChan.URI.scheme, "https");
104     callback.onRedirectVerifyCallback(Cr.NS_OK);
105   }
108 EventSinkListener.prototype.QueryInterface = ChromeUtils.generateQI([
109   "nsIInterfaceRequestor",
110   "nsIChannelEventSink",
113 // Test if the request is upgraded to https with a HTTPSSVC record.
114 add_task(async function testUseHTTPSSVCAsHSTS() {
115   Services.dns.clearCache(true);
116   // Do DNS resolution before creating the channel, so the HTTPSSVC record will
117   // be resolved from the cache.
118   await new TRRDNSListener("test.httpssvc.com", {
119     type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
120   });
122   // Since the HTTPS RR should be served from cache, the DNS record is available
123   // before nsHttpChannel::MaybeUseHTTPSRRForUpgrade() is called.
124   let chan = makeChan(`http://test.httpssvc.com:80/server-timing`);
125   let listener = new EventSinkListener();
126   chan.notificationCallbacks = listener;
128   let [req] = await channelOpenPromise(chan);
130   req.QueryInterface(Ci.nsIHttpChannel);
131   Assert.equal(req.getResponseHeader("x-connection-http2"), "yes");
133   chan = makeChan(`http://test.httpssvc.com:80/server-timing`);
134   listener = new EventSinkListener();
135   chan.notificationCallbacks = listener;
137   [req] = await channelOpenPromise(chan);
139   req.QueryInterface(Ci.nsIHttpChannel);
140   Assert.equal(req.getResponseHeader("x-connection-http2"), "yes");
143 // Test the case that we got an invalid DNS response. In this case,
144 // nsHttpChannel::OnHTTPSRRAvailable is called after
145 // nsHttpChannel::MaybeUseHTTPSRRForUpgrade.
146 add_task(async function testInvalidDNSResult() {
147   Services.dns.clearCache(true);
149   let httpserv = new HttpServer();
150   let content = "ok";
151   httpserv.registerPathHandler("/", function handler(metadata, response) {
152     response.setHeader("Content-Length", `${content.length}`);
153     response.bodyOutputStream.write(content, content.length);
154   });
155   httpserv.start(-1);
156   httpserv.identity.setPrimary(
157     "http",
158     "foo.notexisted.com",
159     httpserv.identity.primaryPort
160   );
162   let chan = makeChan(
163     `http://foo.notexisted.com:${httpserv.identity.primaryPort}/`
164   );
165   let [, response] = await channelOpenPromise(chan);
166   Assert.equal(response, content);
167   await new Promise(resolve => httpserv.stop(resolve));
170 // The same test as above, but nsHttpChannel::MaybeUseHTTPSRRForUpgrade is
171 // called after nsHttpChannel::OnHTTPSRRAvailable.
172 add_task(async function testInvalidDNSResult1() {
173   Services.dns.clearCache(true);
175   let httpserv = new HttpServer();
176   let content = "ok";
177   httpserv.registerPathHandler("/", function handler(metadata, response) {
178     response.setHeader("Content-Length", `${content.length}`);
179     response.bodyOutputStream.write(content, content.length);
180   });
181   httpserv.start(-1);
182   httpserv.identity.setPrimary(
183     "http",
184     "foo.notexisted.com",
185     httpserv.identity.primaryPort
186   );
188   let chan = makeChan(
189     `http://foo.notexisted.com:${httpserv.identity.primaryPort}/`
190   );
192   let topic = "http-on-modify-request";
193   let observer = {
194     QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
195     observe(aSubject, aTopic, aData) {
196       if (aTopic == topic) {
197         Services.obs.removeObserver(observer, topic);
198         let channel = aSubject.QueryInterface(Ci.nsIChannel);
199         channel.suspend();
201         new TRRDNSListener("foo.notexisted.com", {
202           type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
203           expectedSuccess: false,
204         }).then(() => channel.resume());
205       }
206     },
207   };
209   let [, response] = await channelOpenPromise(chan, 0, observer);
210   Assert.equal(response, content);
211   await new Promise(resolve => httpserv.stop(resolve));
214 add_task(async function testLiteralIP() {
215   let httpserv = new HttpServer();
216   let content = "ok";
217   httpserv.registerPathHandler("/", function handler(metadata, response) {
218     response.setHeader("Content-Length", `${content.length}`);
219     response.bodyOutputStream.write(content, content.length);
220   });
221   httpserv.start(-1);
223   let chan = makeChan(`http://127.0.0.1:${httpserv.identity.primaryPort}/`);
224   let [, response] = await channelOpenPromise(chan);
225   Assert.equal(response, content);
226   await new Promise(resolve => httpserv.stop(resolve));
229 // Test the case that an HTTPS RR is available and the server returns a 307
230 // for redirecting back to http.
231 add_task(async function testEndlessUpgradeDowngrade() {
232   Services.dns.clearCache(true);
234   let httpserv = new HttpServer();
235   let content = "okok";
236   httpserv.start(-1);
237   let port = httpserv.identity.primaryPort;
238   httpserv.registerPathHandler(
239     `/redirect_to_http`,
240     function handler(metadata, response) {
241       response.setHeader("Content-Length", `${content.length}`);
242       response.bodyOutputStream.write(content, content.length);
243     }
244   );
245   httpserv.identity.setPrimary("http", "test.httpsrr.redirect.com", port);
247   let chan = makeChan(
248     `http://test.httpsrr.redirect.com:${port}/redirect_to_http?port=${port}`
249   );
251   let [, response] = await channelOpenPromise(chan);
252   Assert.equal(response, content);
253   await new Promise(resolve => httpserv.stop(resolve));
256 add_task(async function testHttpRequestBlocked() {
257   Services.dns.clearCache(true);
259   let dnsRequestObserver = {
260     register() {
261       this.obs = Services.obs;
262       this.obs.addObserver(this, "dns-resolution-request");
263     },
264     unregister() {
265       if (this.obs) {
266         this.obs.removeObserver(this, "dns-resolution-request");
267       }
268     },
269     observe(subject, topic, data) {
270       if (topic == "dns-resolution-request") {
271         Assert.ok(false, "unreachable");
272       }
273     },
274   };
276   dnsRequestObserver.register();
277   Services.prefs.setBoolPref("network.dns.notifyResolution", true);
278   Services.prefs.setBoolPref("network.dns.disablePrefetch", true);
280   let httpserv = new HttpServer();
281   httpserv.registerPathHandler("/", function handler(metadata, response) {
282     Assert.ok(false, "unreachable");
283   });
284   httpserv.start(-1);
285   httpserv.identity.setPrimary(
286     "http",
287     "foo.blocked.com",
288     httpserv.identity.primaryPort
289   );
291   let chan = makeChan(
292     `http://foo.blocked.com:${httpserv.identity.primaryPort}/`
293   );
295   let topic = "http-on-modify-request";
296   let observer = {
297     QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
298     observe(aSubject, aTopic, aData) {
299       if (aTopic == topic) {
300         Services.obs.removeObserver(observer, topic);
301         let channel = aSubject.QueryInterface(Ci.nsIChannel);
302         channel.cancel(Cr.NS_BINDING_ABORTED);
303       }
304     },
305   };
307   let [request] = await channelOpenPromise(chan, CL_EXPECT_FAILURE, observer);
308   request.QueryInterface(Ci.nsIHttpChannel);
309   Assert.equal(request.status, Cr.NS_BINDING_ABORTED);
310   dnsRequestObserver.unregister();
311   await new Promise(resolve => httpserv.stop(resolve));
314 function createPrincipal(url) {
315   return Services.scriptSecurityManager.createContentPrincipal(
316     Services.io.newURI(url),
317     {}
318   );
321 // Test if the Origin header stays the same after an internal HTTPS upgrade
322 // caused by HTTPS RR.
323 add_task(async function testHTTPSRRUpgradeWithOriginHeader() {
324   Services.dns.clearCache(true);
326   const url = "http://test.httpssvc.com:80/origin_header";
327   const originURL = "http://example.com";
328   let chan = Services.io
329     .newChannelFromURIWithProxyFlags(
330       Services.io.newURI(url),
331       null,
332       Ci.nsIProtocolProxyService.RESOLVE_ALWAYS_TUNNEL,
333       null,
334       createPrincipal(originURL),
335       createPrincipal(url),
336       Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
337       Ci.nsIContentPolicy.TYPE_DOCUMENT
338     )
339     .QueryInterface(Ci.nsIHttpChannel);
340   chan.referrerInfo = new ReferrerInfo(
341     Ci.nsIReferrerInfo.EMPTY,
342     true,
343     NetUtil.newURI(url)
344   );
345   chan.setRequestHeader("Origin", originURL, false);
347   let [req, buf] = await channelOpenPromise(chan);
349   req.QueryInterface(Ci.nsIHttpChannel);
350   Assert.equal(req.getResponseHeader("x-connection-http2"), "yes");
351   Assert.equal(buf, originURL);