browser_test_local_network_trackers.js (8650B)
1 /* Any copyright is dedicated to the Public Domain. 2 https://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 const { UrlClassifierTestUtils } = ChromeUtils.importESModule( 7 "resource://testing-common/UrlClassifierTestUtils.sys.mjs" 8 ); 9 10 const { HttpServer } = ChromeUtils.importESModule( 11 "resource://testing-common/httpd.sys.mjs" 12 ); 13 14 const baseURL = getRootDirectory(gTestPath).replace( 15 "chrome://mochitests/content", 16 "https://example.com" 17 ); 18 19 const { PermissionTestUtils } = ChromeUtils.importESModule( 20 "resource://testing-common/PermissionTestUtils.sys.mjs" 21 ); 22 23 const { RemoteSettings } = ChromeUtils.importESModule( 24 "resource://services-settings/remote-settings.sys.mjs" 25 ); 26 27 const COLLECTION_NAME = "remote-permissions"; 28 let rs = RemoteSettings(COLLECTION_NAME); 29 30 async function remoteSettingsSync({ created, updated, deleted }) { 31 await rs.emit("sync", { 32 data: { 33 created, 34 updated, 35 deleted, 36 }, 37 }); 38 } 39 40 async function restorePermissions() { 41 info("Restoring permissions"); 42 Services.obs.notifyObservers(null, "testonly-reload-permissions-from-disk"); 43 Services.perms.removeAll(); 44 } 45 46 add_setup(async function () { 47 await SpecialPowers.pushPrefEnv({ 48 set: [ 49 ["permissions.manager.defaultsUrl", ""], 50 ["network.websocket.delay-failed-reconnects", false], 51 ["network.websocket.max-connections", 1000], 52 ["network.lna.block_trackers", true], 53 ["network.lna.address_space.public.override", "127.0.0.1:4443"], 54 ["network.lna.blocking", true], 55 ["network.lna.websocket.enabled", true], 56 // always select allow actions for user prompts 57 ["network.localhost.prompt.testing", true], 58 ["network.localnetwork.prompt.testing", true], 59 ["network.localhost.prompt.testing.allow", true], 60 ["network.localnetwork.prompt.testing.allow", true], 61 ], 62 }); 63 64 // Make sure we start off "empty". Any RemoteSettings values must be 65 // purged now to comply with test expectations. 66 Services.obs.notifyObservers(null, "testonly-reload-permissions-from-disk"); 67 registerCleanupFunction(restorePermissions); 68 }); 69 70 requestLongerTimeout(10); 71 72 function observeAndCheck(testType, rand, expectedStatus, message) { 73 return new Promise(resolve => { 74 let observer = { 75 observe(subject, topic) { 76 if (topic !== "http-on-stop-request") { 77 return; 78 } 79 80 let url = `http://localhost:21555/?type=${testType}&rand=${rand}`; 81 let channel = subject.QueryInterface(Ci.nsIHttpChannel); 82 if (!channel || channel.URI.spec !== url) { 83 return; 84 } 85 86 is(channel.status, expectedStatus, message); 87 88 Services.obs.removeObserver(observer, "http-on-stop-request"); 89 resolve(); 90 }, 91 }; 92 Services.obs.addObserver(observer, "http-on-stop-request"); 93 }); 94 } 95 96 let testCases = [ 97 { 98 type: "fetch", 99 nonTrackerStatus: Cr.NS_OK, 100 trackerStatus: Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED, 101 }, 102 { 103 type: "xhr", 104 nonTrackerStatus: Cr.NS_OK, 105 trackerStatus: Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED, 106 }, 107 { 108 type: "img", 109 nonTrackerStatus: Cr.NS_OK, 110 trackerStatus: Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED, 111 }, 112 { 113 type: "video", 114 nonTrackerStatus: Cr.NS_OK, 115 trackerStatus: Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED, 116 }, 117 { 118 type: "audio", 119 nonTrackerStatus: Cr.NS_OK, 120 trackerStatus: Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED, 121 }, 122 { 123 type: "iframe", 124 nonTrackerStatus: Cr.NS_OK, 125 trackerStatus: Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED, 126 }, 127 { 128 type: "script", 129 nonTrackerStatus: Cr.NS_OK, 130 trackerStatus: Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED, 131 }, 132 { type: "font", nonTrackerStatus: Cr.NS_OK, trackerStatus: Cr.NS_OK }, // TODO 133 { 134 type: "websocket", 135 nonTrackerStatus: Cr.NS_ERROR_WEBSOCKET_CONNECTION_REFUSED, 136 trackerStatus: Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED, 137 }, 138 ]; 139 140 // Tests that a public->private fetch load initiated from 141 // a tracking script will fail. 142 add_task(async function test_tracker_initiated_lna_fetch() { 143 let server = new HttpServer(); 144 server.start(21555); 145 registerCleanupFunction(async () => { 146 await server.stop(); 147 }); 148 server.registerPathHandler("/", (request, response) => { 149 const params = new URLSearchParams(request.queryString); 150 const type = params.get("type"); 151 152 response.setHeader("Access-Control-Allow-Origin", "*", false); 153 154 switch (type) { 155 case "img": 156 response.setHeader("Content-Type", "image/gif", false); 157 response.setStatusLine(request.httpVersion, 200, "OK"); 158 // 1x1 transparent GIF 159 response.write( 160 atob("R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==") 161 ); 162 return; 163 164 case "audio": 165 response.setHeader("Content-Type", "audio/wav", false); 166 response.setStatusLine(request.httpVersion, 200, "OK"); 167 // Silent WAV (44-byte header + no data) 168 response.write( 169 atob("UklGRhYAAABXQVZFZm10IBAAAAABAAEAIlYAAESsAAACABAAZGF0YQAAAAA=") 170 ); 171 return; 172 173 case "video": 174 response.setHeader("Content-Type", "video/mp4", false); 175 response.setStatusLine(request.httpVersion, 200, "OK"); 176 // Minimal MP4 file header; may not render but satisfies loader 177 response.write( 178 atob( 179 "GkXfo0AgQoaBAUL3gQFC8oEEQvOBCEKCQAR3ZWJtQoeBAkKFgQIYU4BnQI0VSalmQCgq17FAAw9CQE2AQAZ3aGFtbXlXQUAGd2hhbW15RIlACECPQAAAAAAAFlSua0AxrkAu14EBY8WBAZyBACK1nEADdW5khkAFVl9WUDglhohAA1ZQOIOBAeBABrCBCLqBCB9DtnVAIueBAKNAHIEAAIAwAQCdASoIAAgAAUAmJaQAA3AA/vz0AAA=" 180 ) 181 ); 182 return; 183 184 default: 185 response.setHeader("Content-Type", "text/plain", false); 186 response.setStatusLine(request.httpVersion, 200, "OK"); 187 response.write("hello"); 188 } 189 }); 190 191 for (let test of testCases) { 192 let rand = Math.random(); 193 let promise = observeAndCheck( 194 test.type, 195 rand, 196 test.nonTrackerStatus, 197 `expected correct status for non-tracker ${test.type} test` 198 ); 199 let tab = await BrowserTestUtils.openNewForegroundTab( 200 gBrowser, 201 baseURL + `page_with_trackers.html?test=${test.type}&rand=${rand}` 202 ); 203 204 await promise; 205 gBrowser.removeTab(tab); 206 } 207 208 registerCleanupFunction(UrlClassifierTestUtils.cleanupTestTrackers); 209 await UrlClassifierTestUtils.addTestTrackers(); 210 211 for (let test of testCases) { 212 Services.fog.testResetFOG(); 213 let rand = Math.random(); 214 let promise = observeAndCheck( 215 test.type, 216 rand, 217 test.trackerStatus, 218 `expected correct status for tracker ${test.type} test` 219 ); 220 let tab = await BrowserTestUtils.openNewForegroundTab( 221 gBrowser, 222 baseURL + `page_with_trackers.html?test=${test.type}&rand=${rand}` 223 ); 224 225 await promise; 226 gBrowser.removeTab(tab); 227 is( 228 await Glean.networking.localNetworkBlockedTracker.testGetValue(), 229 test.trackerStatus == Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED ? 1 : null 230 ); 231 } 232 233 // check that when adding the permission the fetch req succeeds. 234 PermissionTestUtils.add( 235 baseURL + "page_with_trackers.html", 236 "localhost", 237 Services.perms.ALLOW_ACTION, 238 Services.perms.EXPIRE_NEVER 239 ); 240 241 for (let test of testCases) { 242 let rand = Math.random(); 243 let promise = observeAndCheck( 244 test.type, 245 rand, 246 test.nonTrackerStatus, 247 `expected correct status for tracker ${test.type} test with permission` 248 ); 249 let tab = await BrowserTestUtils.openNewForegroundTab( 250 gBrowser, 251 baseURL + `page_with_trackers.html?test=${test.type}&rand=${rand}` 252 ); 253 254 await promise; 255 gBrowser.removeTab(tab); 256 } 257 258 PermissionTestUtils.remove(baseURL + "page_with_trackers.html", "localhost"); 259 260 // This time check that the remote permission service can automatically set up the permission for this domain. 261 const ORIGIN_1 = "https://example.com"; 262 const TEST_PERMISSION_1 = "localhost"; 263 await remoteSettingsSync({ 264 created: [ 265 { 266 origin: ORIGIN_1, 267 type: TEST_PERMISSION_1, 268 capability: Ci.nsIPermissionManager.ALLOW_ACTION, 269 }, 270 ], 271 }); 272 273 for (let test of testCases) { 274 let rand = Math.random(); 275 let promise = observeAndCheck( 276 test.type, 277 rand, 278 test.nonTrackerStatus, 279 `expected correct status for tracker ${test.type} test with permission from remote-settings` 280 ); 281 let tab = await BrowserTestUtils.openNewForegroundTab( 282 gBrowser, 283 baseURL + `page_with_trackers.html?test=${test.type}&rand=${rand}` 284 ); 285 286 await promise; 287 gBrowser.removeTab(tab); 288 } 289 290 restorePermissions(); 291 });