tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }