diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /netwerk/test/unit/test_captive_portal_service.js | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream.tar.xz firefox-esr-upstream.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'netwerk/test/unit/test_captive_portal_service.js')
-rw-r--r-- | netwerk/test/unit/test_captive_portal_service.js | 323 |
1 files changed, 323 insertions, 0 deletions
diff --git a/netwerk/test/unit/test_captive_portal_service.js b/netwerk/test/unit/test_captive_portal_service.js new file mode 100644 index 0000000000..1488e9d41f --- /dev/null +++ b/netwerk/test/unit/test_captive_portal_service.js @@ -0,0 +1,323 @@ +"use strict"; + +const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js"); + +let httpserver = null; +XPCOMUtils.defineLazyGetter(this, "cpURI", function () { + return ( + "http://localhost:" + httpserver.identity.primaryPort + "/captive.html" + ); +}); + +const SUCCESS_STRING = + '<meta http-equiv="refresh" content="0;url=https://support.mozilla.org/kb/captive-portal"/>'; +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 = '<meta http-equiv="refresh" content="0;url=/login">'; + 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 = "<h1>Internal Server Error</h1>"; + } 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(aSubject, aTopic, aData) { + 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" + ); +}); |