Bug 1796551 [wpt PR 36570] - WebKit export of https://bugs.webkit.org/show_bug.cgi...
[gecko.git] / netwerk / test / unit / test_httpssvc_https_upgrade.js
blob61f02cb0ae2337499d5190e022034fe05cc52468
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 ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
9 const dns = Cc["@mozilla.org/network/dns-service;1"].getService(
10   Ci.nsIDNSService
13 const certOverrideService = Cc[
14   "@mozilla.org/security/certoverride;1"
15 ].getService(Ci.nsICertOverrideService);
16 const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
17 const { TestUtils } = ChromeUtils.import(
18   "resource://testing-common/TestUtils.jsm"
21 add_setup(async function setup() {
22   trr_test_setup();
24   let env = Cc["@mozilla.org/process/environment;1"].getService(
25     Ci.nsIEnvironment
26   );
27   let h2Port = env.get("MOZHTTP2_PORT");
28   Assert.notEqual(h2Port, null);
29   Assert.notEqual(h2Port, "");
31   Services.prefs.setCharPref(
32     "network.trr.uri",
33     "https://foo.example.com:" + h2Port + "/httpssvc_as_altsvc"
34   );
36   Services.prefs.setBoolPref("network.dns.upgrade_with_https_rr", true);
37   Services.prefs.setBoolPref("network.dns.use_https_rr_as_altsvc", true);
39   Services.prefs.setBoolPref(
40     "network.dns.use_https_rr_for_speculative_connection",
41     true
42   );
44   registerCleanupFunction(async () => {
45     trr_clear_prefs();
46     Services.prefs.clearUserPref("network.dns.upgrade_with_https_rr");
47     Services.prefs.clearUserPref("network.dns.use_https_rr_as_altsvc");
48     Services.prefs.clearUserPref(
49       "network.dns.use_https_rr_for_speculative_connection"
50     );
51     Services.prefs.clearUserPref("network.dns.notifyResolution");
52     Services.prefs.clearUserPref("network.dns.disablePrefetch");
53   });
55   if (mozinfo.socketprocess_networking) {
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   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   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   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: dns.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   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(`/redirect_to_http`, function handler(
239     metadata,
240     response
241   ) {
242     response.setHeader("Content-Length", `${content.length}`);
243     response.bodyOutputStream.write(content, content.length);
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   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));