3 const { HttpServer } = ChromeUtils.importESModule(
4 "resource://testing-common/httpd.sys.mjs"
8 ChromeUtils.defineLazyGetter(this, "cpURI", function () {
10 "http://localhost:" + httpserver.identity.primaryPort + "/captive.html"
14 const SUCCESS_STRING =
15 '<meta http-equiv="refresh" content="0;url=https://support.mozilla.org/kb/captive-portal"/>';
16 let cpResponse = SUCCESS_STRING;
17 function contentHandler(metadata, response) {
18 response.setHeader("Content-Type", "text/html");
19 response.bodyOutputStream.write(cpResponse, cpResponse.length);
22 const PREF_CAPTIVE_ENABLED = "network.captive-portal-service.enabled";
23 const PREF_CAPTIVE_TESTMODE = "network.captive-portal-service.testMode";
24 const PREF_CAPTIVE_ENDPOINT = "captivedetect.canonicalURL";
25 const PREF_CAPTIVE_MINTIME = "network.captive-portal-service.minInterval";
26 const PREF_CAPTIVE_MAXTIME = "network.captive-portal-service.maxInterval";
27 const PREF_DNS_NATIVE_IS_LOCALHOST = "network.dns.native-is-localhost";
29 const cps = Cc["@mozilla.org/network/captive-portal-service;1"].getService(
30 Ci.nsICaptivePortalService
33 registerCleanupFunction(async () => {
34 Services.prefs.clearUserPref(PREF_CAPTIVE_ENABLED);
35 Services.prefs.clearUserPref(PREF_CAPTIVE_TESTMODE);
36 Services.prefs.clearUserPref(PREF_CAPTIVE_ENDPOINT);
37 Services.prefs.clearUserPref(PREF_CAPTIVE_MINTIME);
38 Services.prefs.clearUserPref(PREF_CAPTIVE_MAXTIME);
39 Services.prefs.clearUserPref(PREF_DNS_NATIVE_IS_LOCALHOST);
41 await new Promise(resolve => {
42 httpserver.stop(resolve);
46 function observerPromise(topic) {
47 return new Promise(resolve => {
49 QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
50 observe(aSubject, aTopic, aData) {
51 if (aTopic == topic) {
52 Services.obs.removeObserver(observer, topic);
57 Services.obs.addObserver(observer, topic);
61 add_task(function setup() {
62 httpserver = new HttpServer();
63 httpserver.registerPathHandler("/captive.html", contentHandler);
66 Services.prefs.setCharPref(PREF_CAPTIVE_ENDPOINT, cpURI);
67 Services.prefs.setIntPref(PREF_CAPTIVE_MINTIME, 50);
68 Services.prefs.setIntPref(PREF_CAPTIVE_MAXTIME, 100);
69 Services.prefs.setBoolPref(PREF_CAPTIVE_TESTMODE, true);
70 Services.prefs.setBoolPref(PREF_DNS_NATIVE_IS_LOCALHOST, true);
73 add_task(async function test_simple() {
74 Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, false);
76 equal(cps.state, Ci.nsICaptivePortalService.UNKNOWN);
78 let notification = observerPromise("network:captive-portal-connectivity");
79 // The service is started by nsIOService when the pref becomes true.
80 // We might want to add a method to do this in the future.
81 Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, true);
83 let observerPayload = await notification;
84 equal(observerPayload, "clear");
85 equal(cps.state, Ci.nsICaptivePortalService.NOT_CAPTIVE);
88 notification = observerPromise("captive-portal-login");
89 cps.recheckCaptivePortal();
91 equal(cps.state, Ci.nsICaptivePortalService.LOCKED_PORTAL);
93 cpResponse = SUCCESS_STRING;
94 notification = observerPromise("captive-portal-login-success");
95 cps.recheckCaptivePortal();
97 equal(cps.state, Ci.nsICaptivePortalService.UNLOCKED_PORTAL);
100 // This test redirects to another URL which returns the same content.
101 // It should still be interpreted as a captive portal.
102 add_task(async function test_redirect_success() {
103 Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, false);
104 equal(cps.state, Ci.nsICaptivePortalService.UNKNOWN);
106 httpserver.registerPathHandler("/succ.txt", (metadata, response) => {
107 response.setHeader("Content-Type", "text/html");
108 response.bodyOutputStream.write(cpResponse, cpResponse.length);
110 httpserver.registerPathHandler("/captive.html", (metadata, response) => {
111 response.setStatusLine(metadata.httpVersion, 307, "Moved Temporarily");
114 `http://localhost:${httpserver.identity.primaryPort}/succ.txt`
118 let notification = observerPromise("captive-portal-login").then(
121 let succNotif = observerPromise("network:captive-portal-connectivity").then(
124 Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, true);
126 let winner = await Promise.race([notification, succNotif]);
127 equal(winner, "login", "This should have been a login, not a success");
130 Ci.nsICaptivePortalService.LOCKED_PORTAL,
131 "Should be locked after redirect to same text"
135 // This redirects to another URI with a different content.
136 // We check that it triggers a captive portal login
137 add_task(async function test_redirect_bad() {
138 Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, false);
139 equal(cps.state, Ci.nsICaptivePortalService.UNKNOWN);
141 httpserver.registerPathHandler("/bad.txt", (metadata, response) => {
142 response.setHeader("Content-Type", "text/html");
143 response.bodyOutputStream.write("bad", "bad".length);
146 httpserver.registerPathHandler("/captive.html", (metadata, response) => {
147 response.setStatusLine(metadata.httpVersion, 307, "Moved Temporarily");
150 `http://localhost:${httpserver.identity.primaryPort}/bad.txt`
154 let notification = observerPromise("captive-portal-login");
155 Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, true);
160 Ci.nsICaptivePortalService.LOCKED_PORTAL,
161 "Should be locked after redirect to bad text"
165 // This redirects to the same URI.
166 // We check that it triggers a captive portal login
167 add_task(async function test_redirect_loop() {
168 Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, false);
169 equal(cps.state, Ci.nsICaptivePortalService.UNKNOWN);
171 // This is actually a redirect loop
172 httpserver.registerPathHandler("/captive.html", (metadata, response) => {
173 response.setStatusLine(metadata.httpVersion, 307, "Moved Temporarily");
174 response.setHeader("Location", cpURI);
177 let notification = observerPromise("captive-portal-login");
178 Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, true);
181 equal(cps.state, Ci.nsICaptivePortalService.LOCKED_PORTAL);
184 // This redirects to a https URI.
185 // We check that it triggers a captive portal login
186 add_task(async function test_redirect_https() {
187 Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, false);
188 equal(cps.state, Ci.nsICaptivePortalService.UNKNOWN);
190 let h2Port = Services.env.get("MOZHTTP2_PORT");
191 Assert.notEqual(h2Port, null);
192 Assert.notEqual(h2Port, "");
194 // Any kind of redirection should trigger the captive portal login.
195 httpserver.registerPathHandler("/captive.html", (metadata, response) => {
196 response.setStatusLine(metadata.httpVersion, 307, "Moved Temporarily");
197 response.setHeader("Location", `https://foo.example.com:${h2Port}/exit`);
200 let notification = observerPromise("captive-portal-login");
201 Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, true);
206 Ci.nsICaptivePortalService.LOCKED_PORTAL,
207 "Should be locked after redirect to https"
209 Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, false);
212 // This test uses a 511 status code to request a captive portal login
213 // We check that it triggers a captive portal login
214 // See RFC 6585 for details
215 add_task(async function test_511_error() {
216 Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, false);
217 equal(cps.state, Ci.nsICaptivePortalService.UNKNOWN);
219 httpserver.registerPathHandler("/captive.html", (metadata, response) => {
220 response.setStatusLine(
221 metadata.httpVersion,
223 "Network Authentication Required"
225 cpResponse = '<meta http-equiv="refresh" content="0;url=/login">';
226 contentHandler(metadata, response);
229 let notification = observerPromise("captive-portal-login");
230 Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, true);
233 equal(cps.state, Ci.nsICaptivePortalService.LOCKED_PORTAL);
236 // Any other 5xx HTTP error, is assumed to be an issue with the
237 // canonical web server, and should not trigger a captive portal login
238 add_task(async function test_generic_5xx_error() {
239 Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, false);
240 equal(cps.state, Ci.nsICaptivePortalService.UNKNOWN);
243 httpserver.registerPathHandler("/captive.html", (metadata, response) => {
244 if (requests++ === 0) {
245 // on first attempt, send 503 error
246 response.setStatusLine(
247 metadata.httpVersion,
249 "Internal Server Error"
251 cpResponse = "<h1>Internal Server Error</h1>";
253 // on retry, send canonical reply
254 cpResponse = SUCCESS_STRING;
256 contentHandler(metadata, response);
259 let notification = observerPromise("network:captive-portal-connectivity");
260 Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, true);
264 equal(cps.state, Ci.nsICaptivePortalService.NOT_CAPTIVE);
267 add_task(async function test_changed_notification() {
268 Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, false);
269 equal(cps.state, Ci.nsICaptivePortalService.UNKNOWN);
271 httpserver.registerPathHandler("/captive.html", contentHandler);
272 cpResponse = SUCCESS_STRING;
274 let changedNotificationCount = 0;
276 QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
277 observe(aSubject, aTopic, aData) {
278 changedNotificationCount += 1;
281 Services.obs.addObserver(
283 "network:captive-portal-connectivity-changed"
286 let notification = observerPromise(
287 "network:captive-portal-connectivity-changed"
289 Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, true);
291 equal(changedNotificationCount, 1);
292 equal(cps.state, Ci.nsICaptivePortalService.NOT_CAPTIVE);
294 notification = observerPromise("network:captive-portal-connectivity");
295 cps.recheckCaptivePortal();
297 equal(changedNotificationCount, 1);
298 equal(cps.state, Ci.nsICaptivePortalService.NOT_CAPTIVE);
300 notification = observerPromise("captive-portal-login");
301 cpResponse = "you are captive";
302 cps.recheckCaptivePortal();
304 equal(changedNotificationCount, 1);
305 equal(cps.state, Ci.nsICaptivePortalService.LOCKED_PORTAL);
307 notification = observerPromise("captive-portal-login-success");
308 cpResponse = SUCCESS_STRING;
309 cps.recheckCaptivePortal();
311 equal(changedNotificationCount, 2);
312 equal(cps.state, Ci.nsICaptivePortalService.UNLOCKED_PORTAL);
314 notification = observerPromise("captive-portal-login");
315 cpResponse = "you are captive";
316 cps.recheckCaptivePortal();
318 equal(changedNotificationCount, 2);
319 equal(cps.state, Ci.nsICaptivePortalService.LOCKED_PORTAL);
321 Services.obs.removeObserver(
323 "network:captive-portal-connectivity-changed"