browser_test_mixed_content_download.js (10133B)
1 ChromeUtils.defineESModuleGetters(this, { 2 Downloads: "resource://gre/modules/Downloads.sys.mjs", 3 DownloadsCommon: 4 "moz-src:///browser/components/downloads/DownloadsCommon.sys.mjs", 5 }); 6 7 const HandlerService = Cc[ 8 "@mozilla.org/uriloader/handler-service;1" 9 ].getService(Ci.nsIHandlerService); 10 11 const MIMEService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService); 12 13 let INSECURE_BASE_URL = 14 getRootDirectory(gTestPath).replace( 15 "chrome://mochitests/content/", 16 "http://example.com/" 17 ) + "download_page.html"; 18 let SECURE_BASE_URL = 19 getRootDirectory(gTestPath).replace( 20 "chrome://mochitests/content/", 21 "https://example.com/" 22 ) + "download_page.html"; 23 24 function promiseFocus() { 25 return new Promise(resolve => { 26 waitForFocus(resolve); 27 }); 28 } 29 30 function promisePanelOpened() { 31 if (DownloadsPanel.panel && DownloadsPanel.panel.state == "open") { 32 return Promise.resolve(); 33 } 34 return BrowserTestUtils.waitForEvent(DownloadsPanel.panel, "popupshown"); 35 } 36 37 async function task_openPanel() { 38 await promiseFocus(); 39 40 let promise = promisePanelOpened(); 41 DownloadsPanel.showPanel(); 42 await promise; 43 } 44 45 const downloadMonitoringView = { 46 _listeners: [], 47 onDownloadAdded(download) { 48 for (let listener of this._listeners) { 49 listener(download); 50 } 51 this._listeners = []; 52 }, 53 waitForDownload(listener) { 54 this._listeners.push(listener); 55 }, 56 }; 57 58 /** 59 * Waits until a download is triggered. 60 * Unless the always_ask_before_handling_new_types pref is true, the download 61 * will simply be saved, so resolve when the view is notified of the new 62 * download. Otherwise, it waits until a prompt is shown, selects the choosen 63 * <action>, then accepts the dialog 64 * 65 * @param [action] Which action to select, either: 66 * "handleInternally", "save" or "open". 67 * @returns {Promise} Resolved once done. 68 */ 69 70 function shouldTriggerDownload(action = "save") { 71 if ( 72 Services.prefs.getBoolPref( 73 "browser.download.always_ask_before_handling_new_types" 74 ) 75 ) { 76 return new Promise((resolve, reject) => { 77 Services.wm.addListener({ 78 onOpenWindow(xulWin) { 79 Services.wm.removeListener(this); 80 let win = xulWin.docShell.domWindow; 81 waitForFocus(() => { 82 if ( 83 win.location == 84 "chrome://mozapps/content/downloads/unknownContentType.xhtml" 85 ) { 86 let dialog = win.document.getElementById("unknownContentType"); 87 let button = dialog.getButton("accept"); 88 let actionRadio = win.document.getElementById(action); 89 actionRadio.click(); 90 button.disabled = false; 91 dialog.acceptDialog(); 92 resolve(); 93 } else { 94 reject(); 95 } 96 }, win); 97 }, 98 }); 99 }); 100 } 101 return new Promise(res => { 102 downloadMonitoringView.waitForDownload(res); 103 }); 104 } 105 106 const CONSOLE_ERROR_MESSAGE = "Blocked downloading insecure content"; 107 108 function shouldConsoleError() { 109 // Waits until CONSOLE_ERROR_MESSAGE was logged 110 return new Promise((resolve, reject) => { 111 function listener(msgObj) { 112 let text = msgObj.message; 113 if (text.includes(CONSOLE_ERROR_MESSAGE)) { 114 Services.console.unregisterListener(listener); 115 resolve(); 116 } 117 } 118 Services.console.registerListener(listener); 119 }); 120 } 121 122 async function resetDownloads() { 123 // Removes all downloads from the download List 124 const types = new Set(); 125 let publicList = await Downloads.getList(Downloads.PUBLIC); 126 let downloads = await publicList.getAll(); 127 for (let download of downloads) { 128 if (download.contentType) { 129 types.add(download.contentType); 130 } 131 publicList.remove(download); 132 await download.finalize(true); 133 } 134 135 if (types.size) { 136 // reset handlers for the contentTypes of any files previously downloaded 137 for (let type of types) { 138 const mimeInfo = MIMEService.getFromTypeAndExtension(type, ""); 139 info("resetting handler for type: " + type); 140 HandlerService.remove(mimeInfo); 141 } 142 } 143 } 144 145 function shouldNotifyDownloadUI() { 146 return new Promise(res => { 147 downloadMonitoringView.waitForDownload(async aDownload => { 148 let { error } = aDownload; 149 if ( 150 error.becauseBlockedByReputationCheck && 151 error.reputationCheckVerdict == Downloads.Error.BLOCK_VERDICT_INSECURE 152 ) { 153 // It's an insecure Download, now Check that it has been cleaned up properly 154 if ((await IOUtils.stat(aDownload.target.path)).size != 0) { 155 throw new Error(`Download target is not empty!`); 156 } 157 if ((await IOUtils.stat(aDownload.target.path)).size != 0) { 158 throw new Error(`Download partFile was not cleaned up properly`); 159 } 160 // Assert that the Referrer is presnt 161 if (!aDownload.source.referrerInfo) { 162 throw new Error("The Blocked download is missing the ReferrerInfo"); 163 } 164 165 res(aDownload); 166 } else { 167 ok(false, "No error for download that was expected to error!"); 168 } 169 }); 170 }); 171 } 172 173 async function runTest(url, link, checkFunction, description) { 174 await SpecialPowers.pushPrefEnv({ 175 set: [["dom.block_download_insecure", true]], 176 }); 177 await resetDownloads(); 178 179 let tab = BrowserTestUtils.addTab(gBrowser, url); 180 gBrowser.selectedTab = tab; 181 182 let browser = gBrowser.getBrowserForTab(tab); 183 await BrowserTestUtils.browserLoaded(browser); 184 185 info("Checking: " + description); 186 187 let checkPromise = checkFunction(); 188 // Click the Link to trigger the download 189 SpecialPowers.spawn(gBrowser.selectedBrowser, [link], contentLink => { 190 content.document.getElementById(contentLink).click(); 191 }); 192 193 await checkPromise; 194 195 ok(true, description); 196 BrowserTestUtils.removeTab(tab); 197 198 await SpecialPowers.popPrefEnv(); 199 } 200 201 add_setup(async () => { 202 let list = await Downloads.getList(Downloads.ALL); 203 list.addView(downloadMonitoringView); 204 registerCleanupFunction(() => list.removeView(downloadMonitoringView)); 205 }); 206 207 // Test Blocking 208 add_task(async function test_blocking() { 209 for (let prefVal of [true, false]) { 210 await SpecialPowers.pushPrefEnv({ 211 set: [["browser.download.always_ask_before_handling_new_types", prefVal]], 212 }); 213 await runTest( 214 INSECURE_BASE_URL, 215 "insecure", 216 shouldTriggerDownload, 217 "Insecure -> Insecure should download" 218 ); 219 await runTest( 220 INSECURE_BASE_URL, 221 "secure", 222 shouldTriggerDownload, 223 "Insecure -> Secure should download" 224 ); 225 await runTest( 226 SECURE_BASE_URL, 227 "insecure", 228 () => 229 Promise.all([ 230 shouldTriggerDownload(), 231 shouldNotifyDownloadUI(), 232 shouldConsoleError(), 233 ]), 234 "Secure -> Insecure should Error" 235 ); 236 await runTest( 237 SECURE_BASE_URL, 238 "secure", 239 shouldTriggerDownload, 240 "Secure -> Secure should Download" 241 ); 242 await SpecialPowers.popPrefEnv(); 243 } 244 }); 245 246 // Test Manual Unblocking 247 add_task(async function test_manual_unblocking() { 248 for (let prefVal of [true, false]) { 249 await SpecialPowers.pushPrefEnv({ 250 set: [["browser.download.always_ask_before_handling_new_types", prefVal]], 251 }); 252 await runTest( 253 SECURE_BASE_URL, 254 "insecure", 255 async () => { 256 let [, download] = await Promise.all([ 257 shouldTriggerDownload(), 258 shouldNotifyDownloadUI(), 259 ]); 260 await download.unblock(); 261 Assert.equal( 262 download.error, 263 null, 264 "There should be no error after unblocking" 265 ); 266 }, 267 "A Blocked Download Should succeeded to Download after a Manual unblock" 268 ); 269 await SpecialPowers.popPrefEnv(); 270 } 271 }); 272 273 // Test Unblock Download Visible 274 add_task(async function test_unblock_download_visible() { 275 for (let prefVal of [true, false]) { 276 await SpecialPowers.pushPrefEnv({ 277 set: [["browser.download.always_ask_before_handling_new_types", prefVal]], 278 }); 279 // Focus, open and close the panel once 280 // to make sure the panel is loaded and ready 281 await promiseFocus(); 282 await runTest( 283 SECURE_BASE_URL, 284 "insecure", 285 async () => { 286 let panelHasOpened = promisePanelOpened(); 287 info("awaiting that the download is triggered and added to the list"); 288 await Promise.all([shouldTriggerDownload(), shouldNotifyDownloadUI()]); 289 info("awaiting that the Download list shows itself"); 290 await panelHasOpened; 291 DownloadsPanel.hidePanel(); 292 ok(true, "The Download Panel should have opened on blocked download"); 293 }, 294 "A Blocked Download Should open the Download Panel" 295 ); 296 await SpecialPowers.popPrefEnv(); 297 } 298 }); 299 300 // Test Download an insecure svg and choose "Open with Firefox" 301 add_task(async function download_open_insecure_SVG() { 302 const mimeInfo = MIMEService.getFromTypeAndExtension("image/svg+xml", "svg"); 303 mimeInfo.alwaysAskBeforeHandling = false; 304 mimeInfo.preferredAction = mimeInfo.handleInternally; 305 HandlerService.store(mimeInfo); 306 307 await SpecialPowers.pushPrefEnv({ 308 set: [["browser.download.always_ask_before_handling_new_types", false]], 309 }); 310 await promiseFocus(); 311 await runTest( 312 SECURE_BASE_URL, 313 "insecureSVG", 314 async () => { 315 info("awaiting that the download is triggered and added to the list"); 316 let [_, download] = await Promise.all([ 317 shouldTriggerDownload("handleInternally"), 318 shouldNotifyDownloadUI(), 319 ]); 320 321 let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser); 322 await download.unblock(); 323 Assert.equal( 324 download.error, 325 null, 326 "There should be no error after unblocking" 327 ); 328 329 let tab = await newTabPromise; 330 331 ok( 332 tab.linkedBrowser._documentURI.filePath.includes(".svg"), 333 "The download target was opened" 334 ); 335 BrowserTestUtils.removeTab(tab); 336 ok(true, "The Content was opened in a new tab"); 337 await SpecialPowers.popPrefEnv(); 338 }, 339 "A Blocked SVG can be opened internally" 340 ); 341 342 HandlerService.remove(mimeInfo); 343 });