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/. */
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",
23 add_setup(async function setup() {
26 let h2Port = Services.env.get("MOZHTTP2_PORT");
27 Assert.notEqual(h2Port, null);
28 Assert.notEqual(h2Port, "");
30 Services.prefs.setCharPref(
32 "https://foo.example.com:" + h2Port + "/httpssvc_as_altsvc"
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",
43 registerCleanupFunction(async () => {
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"
50 Services.prefs.clearUserPref("network.dns.notifyResolution");
51 Services.prefs.clearUserPref("network.dns.disablePrefetch");
54 if (mozinfo.socketprocess_networking) {
55 Services.dns; // Needed to trigger socket process.
56 await TestUtils.waitForCondition(() => Services.io.socketProcessLaunched);
59 Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRFIRST);
62 function makeChan(url) {
63 let chan = NetUtil.newChannel({
65 loadUsingSystemPrincipal: true,
66 contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
67 }).QueryInterface(Ci.nsIHttpChannel);
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(
79 resolve([req, buffer]);
81 certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
86 let topic = "http-on-modify-request";
87 Services.obs.addObserver(observer, topic);
89 chan.asyncOpen(new ChannelListener(finish, null, flags));
93 class EventSinkListener {
95 if (iid.equals(Ci.nsIChannelEventSink)) {
98 throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
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);
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,
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();
151 httpserv.registerPathHandler("/", function handler(metadata, response) {
152 response.setHeader("Content-Length", `${content.length}`);
153 response.bodyOutputStream.write(content, content.length);
156 httpserv.identity.setPrimary(
158 "foo.notexisted.com",
159 httpserv.identity.primaryPort
163 `http://foo.notexisted.com:${httpserv.identity.primaryPort}/`
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();
177 httpserv.registerPathHandler("/", function handler(metadata, response) {
178 response.setHeader("Content-Length", `${content.length}`);
179 response.bodyOutputStream.write(content, content.length);
182 httpserv.identity.setPrimary(
184 "foo.notexisted.com",
185 httpserv.identity.primaryPort
189 `http://foo.notexisted.com:${httpserv.identity.primaryPort}/`
192 let topic = "http-on-modify-request";
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);
201 new TRRDNSListener("foo.notexisted.com", {
202 type: Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC,
203 expectedSuccess: false,
204 }).then(() => channel.resume());
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();
217 httpserv.registerPathHandler("/", function handler(metadata, response) {
218 response.setHeader("Content-Length", `${content.length}`);
219 response.bodyOutputStream.write(content, content.length);
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";
237 let port = httpserv.identity.primaryPort;
238 httpserv.registerPathHandler(
240 function handler(metadata, response) {
241 response.setHeader("Content-Length", `${content.length}`);
242 response.bodyOutputStream.write(content, content.length);
245 httpserv.identity.setPrimary("http", "test.httpsrr.redirect.com", port);
248 `http://test.httpsrr.redirect.com:${port}/redirect_to_http?port=${port}`
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 = {
261 this.obs = Services.obs;
262 this.obs.addObserver(this, "dns-resolution-request");
266 this.obs.removeObserver(this, "dns-resolution-request");
269 observe(subject, topic, data) {
270 if (topic == "dns-resolution-request") {
271 Assert.ok(false, "unreachable");
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");
285 httpserv.identity.setPrimary(
288 httpserv.identity.primaryPort
292 `http://foo.blocked.com:${httpserv.identity.primaryPort}/`
295 let topic = "http-on-modify-request";
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);
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),
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),
332 Ci.nsIProtocolProxyService.RESOLVE_ALWAYS_TUNNEL,
334 createPrincipal(originURL),
335 createPrincipal(url),
336 Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
337 Ci.nsIContentPolicy.TYPE_DOCUMENT
339 .QueryInterface(Ci.nsIHttpChannel);
340 chan.referrerInfo = new ReferrerInfo(
341 Ci.nsIReferrerInfo.EMPTY,
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);