"use strict";
const { HttpServer } = ChromeUtils.importESModule(
"resource://testing-common/httpd.sys.mjs"
);
let httpserver =
null;
ChromeUtils.defineLazyGetter(
this,
"cpURI",
function () {
return (
"http://localhost:" + httpserver.identity.primaryPort + "/captive.html"
);
});
const SUCCESS_STRING =
'';
let cpResponse = SUCCESS_STRING;
function contentHandler(metadata, response) {
response.setHeader(
"Content-Type",
"text/html");
response.bodyOutputStream.write(cpResponse, cpResponse.length);
}
const PREF_CAPTIVE_ENABLED =
"network.captive-portal-service.enabled";
const PREF_CAPTIVE_TESTMODE =
"network.captive-portal-service.testMode";
const PREF_CAPTIVE_ENDPOINT =
"captivedetect.canonicalURL";
const PREF_CAPTIVE_MINTIME =
"network.captive-portal-service.minInterval";
const PREF_CAPTIVE_MAXTIME =
"network.captive-portal-service.maxInterval";
const PREF_DNS_NATIVE_IS_LOCALHOST =
"network.dns.native-is-localhost";
const cps = Cc[
"@mozilla.org/network/captive-portal-service;1"].getService(
Ci.nsICaptivePortalService
);
registerCleanupFunction(async () => {
Services.prefs.clearUserPref(PREF_CAPTIVE_ENABLED);
Services.prefs.clearUserPref(PREF_CAPTIVE_TESTMODE);
Services.prefs.clearUserPref(PREF_CAPTIVE_ENDPOINT);
Services.prefs.clearUserPref(PREF_CAPTIVE_MINTIME);
Services.prefs.clearUserPref(PREF_CAPTIVE_MAXTIME);
Services.prefs.clearUserPref(PREF_DNS_NATIVE_IS_LOCALHOST);
await
new Promise(resolve => {
httpserver.stop(resolve);
});
});
function observerPromise(topic) {
return new Promise(resolve => {
let observer = {
QueryInterface: ChromeUtils.generateQI([
"nsIObserver"]),
observe(aSubject, aTopic, aData) {
if (aTopic == topic) {
Services.obs.removeObserver(observer, topic);
resolve(aData);
}
},
};
Services.obs.addObserver(observer, topic);
});
}
add_task(
function setup() {
httpserver =
new HttpServer();
httpserver.registerPathHandler(
"/captive.html", contentHandler);
httpserver.start(-1);
Services.prefs.setCharPref(PREF_CAPTIVE_ENDPOINT, cpURI);
Services.prefs.setIntPref(PREF_CAPTIVE_MINTIME, 50);
Services.prefs.setIntPref(PREF_CAPTIVE_MAXTIME, 100);
Services.prefs.setBoolPref(PREF_CAPTIVE_TESTMODE,
true);
Services.prefs.setBoolPref(PREF_DNS_NATIVE_IS_LOCALHOST,
true);
});
add_task(async
function test_simple() {
Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED,
false);
equal(cps.state, Ci.nsICaptivePortalService.UNKNOWN);
let notification = observerPromise(
"network:captive-portal-connectivity");
// The service is started by nsIOService when the pref becomes true.
// We might want to add a method to do this in the future.
Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED,
true);
let observerPayload = await notification;
equal(observerPayload,
"clear");
equal(cps.state, Ci.nsICaptivePortalService.NOT_CAPTIVE);
cpResponse =
"other";
notification = observerPromise(
"captive-portal-login");
cps.recheckCaptivePortal();
await notification;
equal(cps.state, Ci.nsICaptivePortalService.LOCKED_PORTAL);
cpResponse = SUCCESS_STRING;
notification = observerPromise(
"captive-portal-login-success");
cps.recheckCaptivePortal();
await notification;
equal(cps.state, Ci.nsICaptivePortalService.UNLOCKED_PORTAL);
});
// This test redirects to another URL which returns the same content.
// It should still be interpreted as a captive portal.
add_task(async
function test_redirect_success() {
Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED,
false);
equal(cps.state, Ci.nsICaptivePortalService.UNKNOWN);
httpserver.registerPathHandler(
"/succ.txt", (metadata, response) => {
response.setHeader(
"Content-Type",
"text/html");
response.bodyOutputStream.write(cpResponse, cpResponse.length);
});
httpserver.registerPathHandler(
"/captive.html", (metadata, response) => {
response.setStatusLine(metadata.httpVersion, 307,
"Moved Temporarily");
response.setHeader(
"Location",
`http:
//localhost:${httpserver.identity.primaryPort}/succ.txt`
);
});
let notification = observerPromise(
"captive-portal-login").then(
() =>
"login"
);
let succNotif = observerPromise(
"network:captive-portal-connectivity").then(
() =>
"connectivity"
);
Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED,
true);
let winner = await Promise.race([notification, succNotif]);
equal(winner,
"login",
"This should have been a login, not a success");
equal(
cps.state,
Ci.nsICaptivePortalService.LOCKED_PORTAL,
"Should be locked after redirect to same text"
);
});
// This redirects to another URI with a different content.
// We check that it triggers a captive portal login
add_task(async
function test_redirect_bad() {
Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED,
false);
equal(cps.state, Ci.nsICaptivePortalService.UNKNOWN);
httpserver.registerPathHandler(
"/bad.txt", (metadata, response) => {
response.setHeader(
"Content-Type",
"text/html");
response.bodyOutputStream.write(
"bad",
"bad".length);
});
httpserver.registerPathHandler(
"/captive.html", (metadata, response) => {
response.setStatusLine(metadata.httpVersion, 307,
"Moved Temporarily");
response.setHeader(
"Location",
`http:
//localhost:${httpserver.identity.primaryPort}/bad.txt`
);
});
let notification = observerPromise(
"captive-portal-login");
Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED,
true);
await notification;
equal(
cps.state,
Ci.nsICaptivePortalService.LOCKED_PORTAL,
"Should be locked after redirect to bad text"
);
});
// This redirects to the same URI.
// We check that it triggers a captive portal login
add_task(async
function test_redirect_loop() {
Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED,
false);
equal(cps.state, Ci.nsICaptivePortalService.UNKNOWN);
// This is actually a redirect loop
httpserver.registerPathHandler(
"/captive.html", (metadata, response) => {
response.setStatusLine(metadata.httpVersion, 307,
"Moved Temporarily");
response.setHeader(
"Location", cpURI);
});
let notification = observerPromise(
"captive-portal-login");
Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED,
true);
await notification;
equal(cps.state, Ci.nsICaptivePortalService.LOCKED_PORTAL);
});
// This redirects to a https URI.
// We check that it triggers a captive portal login
add_task(async
function test_redirect_https() {
Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED,
false);
equal(cps.state, Ci.nsICaptivePortalService.UNKNOWN);
let h2Port = Services.env.get(
"MOZHTTP2_PORT");
Assert.notEqual(h2Port,
null);
Assert.notEqual(h2Port,
"");
// Any kind of redirection should trigger the captive portal login.
httpserver.registerPathHandler(
"/captive.html", (metadata, response) => {
response.setStatusLine(metadata.httpVersion, 307,
"Moved Temporarily");
response.setHeader(
"Location", `https:
//foo.example.com:${h2Port}/exit`);
});
let notification = observerPromise(
"captive-portal-login");
Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED,
true);
await notification;
equal(
cps.state,
Ci.nsICaptivePortalService.LOCKED_PORTAL,
"Should be locked after redirect to https"
);
Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED,
false);
});
// This test uses a 511 status code to request a captive portal login
// We check that it triggers a captive portal login
// See RFC 6585 for details
add_task(async
function test_511_error() {
Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED,
false);
equal(cps.state, Ci.nsICaptivePortalService.UNKNOWN);
httpserver.registerPathHandler(
"/captive.html", (metadata, response) => {
response.setStatusLine(
metadata.httpVersion,
511,
"Network Authentication Required"
);
cpResponse =
'';
contentHandler(metadata, response);
});
let notification = observerPromise(
"captive-portal-login");
Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED,
true);
await notification;
equal(cps.state, Ci.nsICaptivePortalService.LOCKED_PORTAL);
});
// Any other 5xx HTTP error, is assumed to be an issue with the
// canonical web server, and should not trigger a captive portal login
add_task(async
function test_generic_5xx_error() {
Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED,
false);
equal(cps.state, Ci.nsICaptivePortalService.UNKNOWN);
let requests = 0;
httpserver.registerPathHandler(
"/captive.html", (metadata, response) => {
if (requests++ === 0) {
// on first attempt, send 503 error
response.setStatusLine(
metadata.httpVersion,
503,
"Internal Server Error"
);
cpResponse =
"Internal Server Error
";
}
else {
// on retry, send canonical reply
cpResponse = SUCCESS_STRING;
}
contentHandler(metadata, response);
});
let notification = observerPromise(
"network:captive-portal-connectivity");
Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED,
true);
await notification;
equal(requests, 2);
equal(cps.state, Ci.nsICaptivePortalService.NOT_CAPTIVE);
});
add_task(async
function test_changed_notification() {
Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED,
false);
equal(cps.state, Ci.nsICaptivePortalService.UNKNOWN);
httpserver.registerPathHandler(
"/captive.html", contentHandler);
cpResponse = SUCCESS_STRING;
let changedNotificationCount = 0;
let observer = {
QueryInterface: ChromeUtils.generateQI([
"nsIObserver"]),
observe() {
changedNotificationCount += 1;
},
};
Services.obs.addObserver(
observer,
"network:captive-portal-connectivity-changed"
);
let notification = observerPromise(
"network:captive-portal-connectivity-changed"
);
Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED,
true);
await notification;
equal(changedNotificationCount, 1);
equal(cps.state, Ci.nsICaptivePortalService.NOT_CAPTIVE);
notification = observerPromise(
"network:captive-portal-connectivity");
cps.recheckCaptivePortal();
await notification;
equal(changedNotificationCount, 1);
equal(cps.state, Ci.nsICaptivePortalService.NOT_CAPTIVE);
notification = observerPromise(
"captive-portal-login");
cpResponse =
"you are captive";
cps.recheckCaptivePortal();
await notification;
equal(changedNotificationCount, 1);
equal(cps.state, Ci.nsICaptivePortalService.LOCKED_PORTAL);
notification = observerPromise(
"captive-portal-login-success");
cpResponse = SUCCESS_STRING;
cps.recheckCaptivePortal();
await notification;
equal(changedNotificationCount, 2);
equal(cps.state, Ci.nsICaptivePortalService.UNLOCKED_PORTAL);
notification = observerPromise(
"captive-portal-login");
cpResponse =
"you are captive";
cps.recheckCaptivePortal();
await notification;
equal(changedNotificationCount, 2);
equal(cps.state, Ci.nsICaptivePortalService.LOCKED_PORTAL);
Services.obs.removeObserver(
observer,
"network:captive-portal-connectivity-changed"
);
});