BlockedSiteParent.sys.mjs (9599B)
1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 import { EscapablePageParent } from "resource://gre/actors/NetErrorParent.sys.mjs"; 7 8 let lazy = {}; 9 10 ChromeUtils.defineESModuleGetters(lazy, { 11 SafeBrowsing: "resource://gre/modules/SafeBrowsing.sys.mjs", 12 URILoadingHelper: "resource:///modules/URILoadingHelper.sys.mjs", 13 }); 14 15 ChromeUtils.defineLazyGetter(lazy, "browserBundle", () => { 16 return Services.strings.createBundle( 17 "chrome://browser/locale/browser.properties" 18 ); 19 }); 20 21 class SafeBrowsingNotificationBox { 22 _currentURIBaseDomain = null; 23 24 browser = null; 25 26 constructor(browser, title, buttons) { 27 this.browser = browser; 28 29 let uri = browser.currentURI; 30 // start tracking host so that we know when we leave the domain 31 this._currentURIBaseDomain = this.#getDomainForComparison(uri); 32 33 browser.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION); 34 35 this.show(title, buttons); 36 } 37 38 async show(title, buttons) { 39 let gBrowser = this.browser.getTabBrowser(); 40 let notificationBox = gBrowser.getNotificationBox(this.browser); 41 let value = "blocked-badware-page"; 42 43 let previousNotification = notificationBox.getNotificationWithValue(value); 44 if (previousNotification) { 45 notificationBox.removeNotification(previousNotification); 46 } 47 48 let notification = await notificationBox.appendNotification( 49 value, 50 { 51 label: title, 52 image: "chrome://global/skin/icons/blocked.svg", 53 priority: notificationBox.PRIORITY_CRITICAL_HIGH, 54 }, 55 buttons 56 ); 57 // Persist the notification until the user removes so it 58 // doesn't get removed on redirects. 59 notification.persistence = -1; 60 } 61 62 onLocationChange(webProgress, request, newURI) { 63 if (webProgress && !webProgress.isTopLevel) { 64 return; 65 } 66 let newURIBaseDomain = this.#getDomainForComparison(newURI); 67 68 if ( 69 !this._currentURIBaseDomain || 70 newURIBaseDomain !== this._currentURIBaseDomain 71 ) { 72 this.cleanup(); 73 } 74 } 75 76 cleanup() { 77 if (this.browser) { 78 let gBrowser = this.browser.getTabBrowser(); 79 let notificationBox = gBrowser.getNotificationBox(this.browser); 80 let notification = notificationBox.getNotificationWithValue( 81 "blocked-badware-page" 82 ); 83 if (notification) { 84 notificationBox.removeNotification(notification, false); 85 } 86 this.browser.removeProgressListener( 87 this, 88 Ci.nsIWebProgress.NOTIFY_LOCATION 89 ); 90 this.browser.safeBrowsingNotification = null; 91 this.browser = null; 92 } 93 this._currentURIBaseDomain = null; 94 } 95 96 #getDomainForComparison(uri) { 97 try { 98 return Services.eTLD.getBaseDomain(uri); 99 } catch (e) { 100 // If we can't get the base domain, fallback to use host instead. However, 101 // host is sometimes empty when the scheme is file. In this case, just use 102 // spec. 103 return uri.asciiHost || uri.asciiSpec; 104 } 105 } 106 } 107 108 SafeBrowsingNotificationBox.prototype.QueryInterface = ChromeUtils.generateQI([ 109 "nsIWebProgressListener", 110 "nsISupportsWeakReference", 111 ]); 112 113 export class BlockedSiteParent extends EscapablePageParent { 114 receiveMessage(msg) { 115 switch (msg.name) { 116 case "Browser:SiteBlockedError": 117 this._onAboutBlocked( 118 msg.data.elementId, 119 msg.data.reason, 120 this.browsingContext === this.browsingContext.top, 121 msg.data.blockedInfo 122 ); 123 break; 124 } 125 } 126 127 _onAboutBlocked(elementId, reason, isTopFrame, blockedInfo) { 128 let browser = this.browsingContext.top.embedderElement; 129 if (!browser) { 130 return; 131 } 132 // Depending on what page we are displaying here (malware/phishing/unwanted) 133 // use the right strings and links for each. 134 let bucketName = ""; 135 let sendTelemetry = false; 136 if (reason === "malware") { 137 sendTelemetry = true; 138 bucketName = "WARNING_MALWARE_PAGE_"; 139 } else if (reason === "phishing") { 140 sendTelemetry = true; 141 bucketName = "WARNING_PHISHING_PAGE_"; 142 } else if (reason === "unwanted") { 143 sendTelemetry = true; 144 bucketName = "WARNING_UNWANTED_PAGE_"; 145 } else if (reason === "harmful") { 146 sendTelemetry = true; 147 bucketName = "WARNING_HARMFUL_PAGE_"; 148 } 149 let nsISecTel = Ci.IUrlClassifierUITelemetry; 150 bucketName += isTopFrame ? "TOP_" : "FRAME_"; 151 152 switch (elementId) { 153 case "goBackButton": 154 if (sendTelemetry) { 155 Glean.urlclassifier.uiEvents.accumulateSingleSample( 156 nsISecTel[bucketName + "GET_ME_OUT_OF_HERE"] 157 ); 158 } 159 this.leaveErrorPage(browser, /* Never go back */ false); 160 break; 161 case "ignore_warning_link": 162 if (Services.prefs.getBoolPref("browser.safebrowsing.allowOverride")) { 163 if (sendTelemetry) { 164 Glean.urlclassifier.uiEvents.accumulateSingleSample( 165 nsISecTel[bucketName + "IGNORE_WARNING"] 166 ); 167 } 168 this.ignoreWarningLink(reason, blockedInfo); 169 } 170 break; 171 } 172 } 173 174 ignoreWarningLink(reason, blockedInfo) { 175 let { browsingContext } = this; 176 // Add a notify bar before allowing the user to continue through to the 177 // site, so that they don't lose track after, e.g., tab switching. 178 // We can't use browser.contentPrincipal which is principal of about:blocked 179 // Create one from uri with current principal origin attributes 180 let principal = Services.scriptSecurityManager.createContentPrincipal( 181 Services.io.newURI(blockedInfo.uri), 182 browsingContext.currentWindowGlobal.documentPrincipal.originAttributes 183 ); 184 Services.perms.addFromPrincipal( 185 principal, 186 "safe-browsing", 187 Ci.nsIPermissionManager.ALLOW_ACTION, 188 Ci.nsIPermissionManager.EXPIRE_SESSION 189 ); 190 191 let buttons = [ 192 { 193 label: lazy.browserBundle.GetStringFromName( 194 "safebrowsing.getMeOutOfHereButton.label" 195 ), 196 accessKey: lazy.browserBundle.GetStringFromName( 197 "safebrowsing.getMeOutOfHereButton.accessKey" 198 ), 199 callback: () => { 200 let browser = browsingContext.top.embedderElement; 201 this.leaveErrorPage(browser, /* Never go back */ false); 202 }, 203 }, 204 ]; 205 206 let title; 207 let chromeWin = browsingContext.topChromeWindow; 208 if (reason === "malware") { 209 let reportUrl = lazy.SafeBrowsing.getReportURL( 210 "MalwareMistake", 211 blockedInfo 212 ); 213 title = lazy.browserBundle.GetStringFromName( 214 "safebrowsing.reportedAttackSite" 215 ); 216 // There's no button if we can not get report url, for example if the provider 217 // of blockedInfo is not Google 218 if (reportUrl) { 219 buttons[1] = { 220 label: lazy.browserBundle.GetStringFromName( 221 "safebrowsing.notAnAttackButton.label" 222 ), 223 accessKey: lazy.browserBundle.GetStringFromName( 224 "safebrowsing.notAnAttackButton.accessKey" 225 ), 226 callback() { 227 lazy.URILoadingHelper.openTrustedLinkIn( 228 chromeWin, 229 reportUrl, 230 "tab" 231 ); 232 }, 233 }; 234 } 235 } else if (reason === "phishing") { 236 let reportUrl = lazy.SafeBrowsing.getReportURL( 237 "PhishMistake", 238 blockedInfo 239 ); 240 title = lazy.browserBundle.GetStringFromName( 241 "safebrowsing.deceptiveSite" 242 ); 243 // There's no button if we can not get report url, for example if the provider 244 // of blockedInfo is not Google 245 if (reportUrl) { 246 buttons[1] = { 247 label: lazy.browserBundle.GetStringFromName( 248 "safebrowsing.notADeceptiveSiteButton.label" 249 ), 250 accessKey: lazy.browserBundle.GetStringFromName( 251 "safebrowsing.notADeceptiveSiteButton.accessKey" 252 ), 253 callback() { 254 lazy.URILoadingHelper.openTrustedLinkIn( 255 chromeWin, 256 reportUrl, 257 "tab" 258 ); 259 }, 260 }; 261 } 262 } else if (reason === "unwanted") { 263 title = lazy.browserBundle.GetStringFromName( 264 "safebrowsing.reportedUnwantedSite" 265 ); 266 // There is no button for reporting errors since Google doesn't currently 267 // provide a URL endpoint for these reports. 268 } else if (reason === "harmful") { 269 title = lazy.browserBundle.GetStringFromName( 270 "safebrowsing.reportedHarmfulSite" 271 ); 272 // There is no button for reporting errors since Google doesn't currently 273 // provide a URL endpoint for these reports. 274 } 275 276 let browser = browsingContext.top.embedderElement; 277 browser.safeBrowsingNotification?.cleanup(); 278 browser.safeBrowsingNotification = new SafeBrowsingNotificationBox( 279 browser, 280 title, 281 buttons 282 ); 283 284 // Allow users to override and continue through to the site. 285 // Note that we have to use the passed URI info and can't just 286 // rely on the document URI, because the latter contains 287 // additional query parameters that should be stripped. 288 let triggeringPrincipal = 289 blockedInfo.triggeringPrincipal || 290 Services.scriptSecurityManager.createNullPrincipal({}); 291 292 browsingContext.fixupAndLoadURIString(blockedInfo.uri, { 293 triggeringPrincipal, 294 loadFlags: Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER, 295 }); 296 } 297 }