Bug 477718 Implement Phishing Protection (a.k.a. Safe Browsing) support in SeaMonkey r=Neil.
authorPhilip Chee <philip.chee@gmail.com>
Thu, 31 Jan 2013 23:38:27 +0800
changeset 14769 eb9aa93d401c684c2c5b7e229e563c6defe8711b
parent 14768 90fb92893b258e28262615d8e5019aaa0702e4c2
child 14770 45ffe028116902a163cdf5de7664e2b29a17edff
push id867
push userbugzilla@standard8.plus.com
push dateMon, 01 Apr 2013 20:44:27 +0000
treeherdercomm-beta@797726b8d244 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersNeil
bugs477718
Bug 477718 Implement Phishing Protection (a.k.a. Safe Browsing) support in SeaMonkey r=Neil.
suite/browser/browser-prefs.js
suite/browser/jar.mn
suite/browser/navigator.js
suite/browser/navigator.xul
suite/browser/safeBrowsingOverlay.js
suite/browser/safeBrowsingOverlay.xul
suite/browser/tabbrowser.xml
suite/common/bindings/notification.xml
suite/common/blockedSite.xhtml
suite/common/jar.mn
suite/common/src/SuiteCommon.manifest
suite/common/src/nsAbout.js
suite/common/utilityOverlay.js
suite/locales/en-US/chrome/common/notification.properties
suite/locales/en-US/chrome/common/safeBrowsing.dtd
suite/locales/jar.mn
suite/themes/classic/communicator/communicator.css
suite/themes/modern/communicator/communicator.css
suite/themes/modern/global/icons/blacklist_favicon.png
suite/themes/modern/global/icons/blacklist_large.png
suite/themes/modern/global/netError.css
suite/themes/modern/jar.mn
--- a/suite/browser/browser-prefs.js
+++ b/suite/browser/browser-prefs.js
@@ -392,16 +392,54 @@ pref("browser.contentHandlers.types.4.ty
 pref("browser.contentHandlers.types.5.title", "chrome://navigator-region/locale/region.properties");
 pref("browser.contentHandlers.types.5.uri", "chrome://navigator-region/locale/region.properties");
 pref("browser.contentHandlers.types.5.type", "application/vnd.mozilla.maybe.feed");
 
 pref("browser.feeds.handler", "ask");
 pref("browser.videoFeeds.handler", "ask");
 pref("browser.audioFeeds.handler", "ask");
 
+pref("browser.safebrowsing.enabled", true);
+pref("browser.safebrowsing.malware.enabled", true);
+pref("browser.safebrowsing.debug", true);
+
+// Normally the "client ID" sent in updates is appinfo.name, but
+// official Firefox releases from Mozilla use a special identifier.
+// This is currently unused as we are using the apikey method.
+pref("browser.safebrowsing.id", "SeaMonkey");
+
+pref("browser.safebrowsing.updateURL", "http://safebrowsing.clients.google.com/safebrowsing/downloads?client=api&apikey=ABQIAAAALT_LuARPWqUj7bX2mqWTJRQt2QEr-yGktcva5ZhZnWk7HItT7w&appver=%VERSION%&pver=2.2");
+pref("browser.safebrowsing.keyURL", "https://sb-ssl.google.com/safebrowsing/newkey?client=api&apikey=ABQIAAAALT_LuARPWqUj7bX2mqWTJRQt2QEr-yGktcva5ZhZnWk7HItT7w&appver=%VERSION%&pver=2.2");
+pref("browser.safebrowsing.gethashURL", "http://safebrowsing.clients.google.com/safebrowsing/gethash?client=api&apikey=ABQIAAAALT_LuARPWqUj7bX2mqWTJRQt2QEr-yGktcva5ZhZnWk7HItT7w&appver=%VERSION%&pver=2.2");
+pref("browser.safebrowsing.reportURL", "http://safebrowsing.clients.google.com/safebrowsing/report?");
+pref("browser.safebrowsing.reportGenericURL", "http://%LOCALE%.phish-generic.mozilla.com/?hl=%LOCALE%");
+pref("browser.safebrowsing.reportErrorURL", "http://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%");
+pref("browser.safebrowsing.reportPhishURL", "http://%LOCALE%.phish-report.mozilla.com/?hl=%LOCALE%");
+pref("browser.safebrowsing.reportMalwareURL", "http://%LOCALE%.malware-report.mozilla.com/?hl=%LOCALE%");
+pref("browser.safebrowsing.reportMalwareErrorURL", "http://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%");
+
+pref("browser.safebrowsing.warning.infoURL", "http://www.mozilla.org/%LOCALE%/firefox/phishing-protection/");
+pref("browser.safebrowsing.malware.reportURL", "http://safebrowsing.clients.google.com/safebrowsing/diagnostic?client=Firefox&hl=%LOCALE%&site=");
+
+// Name of the about: page contributed by safebrowsing to handle display of error
+// pages on phishing/malware hits.  (bug 399233)
+pref("urlclassifier.alternate_error_page", "blocked");
+
+// The number of random entries to send with a gethash request.
+pref("urlclassifier.gethashnoise", 4);
+
+// The list of tables that use the gethash request to confirm partial results.
+// pref("urlclassifier.gethashtables", "goog-phish-shavar,goog-malware-shavar");
+pref("urlclassifier.gethashtables", "googpub-phish-shavar,goog-malware-shavar");
+
+// If an urlclassifier table has not been updated in this number of seconds,
+// a gethash request will be forced to check that the result is still in
+// the database.
+pref("urlclassifier.max-complete-age", 2700);
+
 pref("browser.sessionstore.resume_from_crash", true);
 pref("browser.sessionstore.resume_session_once", false);
 
 // minimal interval between two save operations in milliseconds
 pref("browser.sessionstore.interval", 15000);
 // maximum amount of POSTDATA to be saved in bytes per history entry (-1 = all of it)
 // (NB: POSTDATA will be saved either entirely or not at all)
 pref("browser.sessionstore.postdata", 0);
--- a/suite/browser/jar.mn
+++ b/suite/browser/jar.mn
@@ -20,16 +20,18 @@ comm.jar:
    content/navigator/navigator.css
    content/navigator/navigator.js
    content/navigator/navigator.xul
    content/navigator/navigatorDD.js
    content/navigator/navigatorOverlay.xul
    content/navigator/nsBrowserContentListener.js
    content/navigator/nsBrowserStatusHandler.js
    content/navigator/sessionHistoryUI.js
+   content/navigator/safeBrowsingOverlay.js
+   content/navigator/safeBrowsingOverlay.xul
    content/navigator/tabbrowser.xml
    content/navigator/urlbarBindings.xml
 #ifdef XP_MACOSX
    content/navigator/platformNavigationBindings.xul                 (mac/platformNavigationBindings.xul)
    content/navigator/platformMailOverlay.xul                        (mac/platformMailOverlay.xul)
 #else
 #ifdef XP_WIN32
    content/navigator/platformNavigationBindings.xul                 (win/platformNavigationBindings.xul)
--- a/suite/browser/navigator.js
+++ b/suite/browser/navigator.js
@@ -10,16 +10,19 @@ Components.utils.import("resource:///mod
   Components.utils.import("resource://gre/modules/PluralForm.jsm");
   return this.PluralForm;
 });
 __defineSetter__("PluralForm", function (val) {
   delete this.PluralForm;
   return this.PluralForm = val;
 });
 
+XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing",
+  "resource://gre/modules/SafeBrowsing.jsm");
+
 const REMOTESERVICE_CONTRACTID = "@mozilla.org/toolkit/remote-service;1";
 const XUL_NAMESPACE = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 var gURLBar = null;
 var gProxyButton = null;
 var gProxyFavIcon = null;
 var gProxyDeck = null;
 var gNavigatorBundle;
 var gBrandBundle;
@@ -659,16 +662,19 @@ function Startup()
 
   DownloadTaskbarIntegration.onBrowserWindowLoad(window);
 
   // initialize the sync UI
   gSyncUI.init();
 
   // initialize the session-restore service
   setTimeout(InitSessionStoreCallback, 0);
+
+  // Bug 778855 - Perf regression if we do this here. To be addressed in bug 779008.
+  setTimeout(function() { SafeBrowsing.init(); }, 2000);
 }
 
 function UpdateNavBar()
 {
   var elements = getNavToolbox().getElementsByClassName("nav-bar-class");
   for (var i = 0; i < elements.length; i++) {
     var element = elements[i];
     element.classList.remove("nav-bar-last");
--- a/suite/browser/navigator.xul
+++ b/suite/browser/navigator.xul
@@ -7,17 +7,17 @@
 
 <?xml-stylesheet href="chrome://navigator/skin/" type="text/css"?>
 <?xml-stylesheet href="chrome://communicator/content/places/places.css" type="text/css"?>
 
 <?xul-overlay href="chrome://navigator/content/navigatorOverlay.xul"?>
 <?xul-overlay href="chrome://navigator/content/linkToolbarOverlay.xul"?>
 <?xul-overlay href="chrome://communicator/content/contentAreaContextOverlay.xul"?>
 <?xul-overlay href="chrome://communicator/content/sidebar/sidebarOverlay.xul"?>
-
+<?xul-overlay href="chrome://navigator/content/safeBrowsingOverlay.xul"?>
 
 <!DOCTYPE window [
 <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
 %brandDTD;
 <!ENTITY % navigatorDTD SYSTEM "chrome://navigator/locale/navigator.dtd" >
 %navigatorDTD;
 ]>
 
new file mode 100644
--- /dev/null
+++ b/suite/browser/safeBrowsingOverlay.js
@@ -0,0 +1,46 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var gSafeBrowsing = {
+  initMenuItems: function initMenuItems() {
+    // A phishing page will have a specific about:blocked content documentURI.
+    var docURI = content.document.documentURI;
+    var isPhishingPage = docURI.startsWith("about:blocked?e=phishingBlocked");
+    var isMalwarePage = docURI.startsWith("about:blocked?e=malwareBlocked");
+
+    // Show/hide the appropriate menu item.
+    document.getElementById("reportPhishing").hidden = isPhishingPage || isMalwarePage;
+    document.getElementById("reportPhishingError").hidden = !isPhishingPage;
+
+    var broadcaster = document.getElementById("safeBrowsingBroadcaster");
+    var uri = getBrowser().currentURI;
+    if (uri && (uri.schemeIs("http") || uri.schemeIs("https")))
+      broadcaster.removeAttribute("disabled");
+    else
+      broadcaster.setAttribute("disabled", true);
+  },
+
+  /**
+   * Used to report a phishing page or a false positive
+   * @param   aName
+   *          A String One of "Phish", "Error", "Malware" or "MalwareError".
+   * @returns A String containing the report phishing URL.
+   */
+  getReportURL: function getReportURL(aName) {
+    var reportUrl = SafeBrowsing.getReportURL(aName);
+
+    var pageUrl = getBrowser().currentURI.asciiSpec;
+    reportUrl += "&url=" + encodeURIComponent(pageUrl);
+
+    return reportUrl;
+  },
+
+  initOverlay: function initOverlay(aEvent) {
+    var popup = document.getElementById("helpPopup");
+    popup.addEventListener("popupshowing", gSafeBrowsing.initMenuItems, false);
+  }
+}
+
+window.addEventListener("load", gSafeBrowsing.initOverlay, false);
new file mode 100644
--- /dev/null
+++ b/suite/browser/safeBrowsingOverlay.xul
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE overlay SYSTEM "chrome://communicator/locale/safeBrowsing.dtd">
+
+<overlay id="safeBrowsingOverlay"
+         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+  <script type="application/javascript"
+          src="chrome://navigator/content/safeBrowsingOverlay.js"/>
+
+  <broadcasterset id="navBroadcasters">
+    <broadcaster id="safeBrowsingBroadcaster" disabled="true"/>
+  </broadcasterset>
+
+  <menupopup id="helpPopup">
+    <menuitem id="reportPhishing"
+              label="&reportPhishSite.label;"
+              accesskey="&reportPhishSite.accesskey;"
+              insertbefore="menu_HelpAboutSeparator"
+              observes="safeBrowsingBroadcaster"
+              oncommand="openUILink(gSafeBrowsing.getReportURL('Phish'), event);"
+              onclick="checkForMiddleClick(this, event);"/>
+    <menuitem id="reportPhishingError"
+              label="&notAForgery.label;"
+              accesskey="&notAForgery.accesskey;"
+              insertbefore="menu_HelpAboutSeparator"
+              observes="safeBrowsingBroadcaster"
+              oncommand="openUILinkIn(gSafeBrowsing.getReportURL('Error'), 'tabfocused');"/>
+  </menupopup>
+</overlay>
--- a/suite/browser/tabbrowser.xml
+++ b/suite/browser/tabbrowser.xml
@@ -1085,24 +1085,22 @@
               const nsIScriptSecurityManager =
                 Components.interfaces.nsIScriptSecurityManager;
 
               const targetDoc = event.target.ownerDocument;
               // Make a URI out of our href.
               var uri = this.mIOService.newURI(href, targetDoc.characterSet, null);
 
               // Verify that the load of this icon is legal.
-              // error pages can load their favicon, to be on the safe side,
-              // only allow chrome:// favicons
-              const aboutNeterr = /^about:neterror\?/;
-              const aboutCerterr = /^about:certerror\?/;
-              if (!(aboutNeterr.test(targetDoc.documentURI) ||
-                    aboutCerterr.test(targetDoc.documentURI)) ||
-                  !uri.schemeIs("chrome")) {
-
+              // Some error or special pages can load their favicon.
+              // To be on the safe side, only allow chrome:// favicons.
+              const re = /^about:(neterror|certerror|blocked)\?/;
+              var isAllowedPage = re.test(targetDoc.documentURI);
+
+              if (!isAllowedPage || !uri.schemeIs("chrome")) {
                 const secMan =
                   Components.classes["@mozilla.org/scriptsecuritymanager;1"]
                             .getService(nsIScriptSecurityManager);
 
                 try {
                   secMan.checkLoadURIWithPrincipal(event.target.nodePrincipal,
                                  uri, nsIScriptSecurityManager.DISALLOW_SCRIPT);
                 } catch(e) {
--- a/suite/common/bindings/notification.xml
+++ b/suite/common/bindings/notification.xml
@@ -991,17 +991,17 @@
               if (uri.asciiHost == host)
                 usage += cacheService.getActiveCache(aGroup).usage;
             });
             var warnQuota = this._prefs.getIntPref("offline-apps.quota.warn");
             if (usage < warnQuota * 1024)
               return;
 
             var message = this._stringBundle.formatStringFromName("offlineApps.quota", [host, warnQuota / 1024], 2);
-              var priority = this.PRIORITY_WARNING_MEDIUM;
+            var priority = this.PRIORITY_WARNING_MEDIUM;
             this.appendNotification(message, "offline-app-usage", null,
                                     priority, null);
             pm.add(aURI, "offline-app",
                    Components.interfaces.nsIOfflineCacheUpdateService.ALLOW_NO_WARN);
           ]]>
         </body>
       </method>
 
@@ -1644,16 +1644,79 @@
             box.installInfo = installInfo;
             installInfo.installs.forEach(function(aInstall) {
               aInstall.addListener(box);
             });
           ]]>
         </body>
       </method>
 
+      <method name="ignoreSafeBrowsingWarning">
+        <parameter name="aIsMalware"/>
+        <body>
+          <![CDATA[
+            var uri = this.activeBrowser.currentURI;
+            var asciiSpec = uri.asciiSpec;
+            var flag = Components.interfaces.nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER;
+            this.activeBrowser.loadURIWithFlags(asciiSpec, flag,
+                                                null, null, null);
+
+            const nsIPermissionManager = Components.interfaces.nsIPermissionManager;
+            var pm = Components.classes["@mozilla.org/permissionmanager;1"]
+                               .getService(nsIPermissionManager);
+            pm.add(uri, "safe-browsing", nsIPermissionManager.ALLOW_ACTION,
+                                         nsIPermissionManager.EXPIRE_SESSION);
+
+            var title, label, accessKey, reportName;
+            if (aIsMalware) {
+              title = "safebrowsing.reportedAttackSite";
+              label = "safebrowsing.notAnAttackButton.label";
+              accessKey = "safebrowsing.notAnAttackButton.accessKey";
+              reportName = "MalwareError";
+            }
+            else {
+              title = "safebrowsing.reportedWebForgery";
+              label = "safebrowsing.notAForgeryButton.label";
+              accessKey = "safebrowsing.notAForgeryButton.accessKey";
+              reportName = "Error";
+            }
+            title = this._stringBundle.GetStringFromName(title);
+
+            var tmp = {};
+            Components.utils.import("resource://gre/modules/SafeBrowsing.jsm", tmp);
+            var reportUrl = tmp.SafeBrowsing.getReportURL(reportName);
+            reportUrl += "&url=" + encodeURIComponent(asciiSpec);
+
+            var buttons = [{
+              label: this._stringBundle.GetStringFromName("safebrowsing.getMeOutOfHereButton.label"),
+              accessKey: this._stringBundle.GetStringFromName("safebrowsing.getMeOutOfHereButton.accessKey"),
+              callback: getMeOutOfHere
+            }, {
+              label: this._stringBundle.GetStringFromName(label),
+              accessKey: this._stringBundle.GetStringFromName(accessKey),
+              callback: function () { openUILinkIn(reportUrl, "tabfocused"); }
+            }];
+
+            var type = "blocked-badware-page";
+            var notification = this.getNotificationWithValue(type);
+            if (notification)
+              this.removeNotification(notification);
+
+            var box = this.appendNotification(title, type, null,
+                                              this.PRIORITY_CRITICAL_HIGH,
+                                              buttons);
+
+            // Persist the notification until the user removes so it
+            // doesn't get removed on redirects.
+            box.persistence = -1;
+
+          ]]>
+        </body>
+      </method>
+
       <constructor>
         <![CDATA[
           var os = Components.classes["@mozilla.org/observer-service;1"]
                              .getService(Components.interfaces.nsIObserverService);
           os.addObserver(this, "indexedDB-permissions-prompt", false);
           os.addObserver(this, "indexedDB-quota-prompt", false);
           os.addObserver(this, "indexedDB-quota-cancel", false);
           os.addObserver(this, "addon-install-blocked", false);
new file mode 100644
--- /dev/null
+++ b/suite/common/blockedSite.xhtml
@@ -0,0 +1,184 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!DOCTYPE html [
+  <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
+  %htmlDTD;
+  <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
+  %globalDTD;
+  <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+  %brandDTD;
+  <!ENTITY % blockedSiteDTD SYSTEM "chrome://communicator/locale/safeBrowsing.dtd">
+  %blockedSiteDTD;
+]>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<html xmlns="http://www.w3.org/1999/xhtml" class="blacklist">
+  <head>
+    <link rel="stylesheet" href="chrome://communicator/content/certError.css" type="text/css" media="all" />
+    <link rel="stylesheet" href="chrome://global/skin/netError.css" type="text/css" media="all" />
+    <link rel="icon" type="image/png" id="favicon" href="chrome://global/skin/icons/blacklist_favicon.png"/>
+
+    <script type="application/javascript"><![CDATA[
+      // Error url MUST be formatted like this:
+      //   about:blocked?e=error_code&u=url
+
+      // Note that this file uses document.documentURI to get
+      // the URL (with the format from above). This is because
+      // document.location.href gets the current URI off the docshell,
+      // which is the URL displayed in the location bar, i.e.
+      // the URI that the user attempted to load.
+
+      function getErrorCode()
+      {
+        var url = document.documentURI;
+        var error = url.indexOf("e=");
+        var duffUrl = url.indexOf("&u=");
+        return url.slice(error + 2, duffUrl);
+      }
+
+      function getURL()
+      {
+        var url = document.documentURI;
+        var match = url.match(/&u=([^&]+)&/);
+
+        // match == null if not found; if so, return an empty string
+        // instead of what would turn out to be portions of the URI
+        if (!match)
+          return "";
+
+        url = decodeURIComponent(match[1]);
+
+        // If this is a view-source page, then get then real URI of the page
+        if (url.startsWith("view-source:"))
+          url = url.slice(12);
+        return url;
+      }
+
+      /**
+       * Attempt to get the hostname via document.location.  Fail back
+       * to getURL so that we always return something meaningful.
+       */
+      function getHostString()
+      {
+        try {
+          return document.location.hostname;
+        } catch (e) {
+          return getURL();
+        }
+      }
+
+      function initPage()
+      {
+        // Handoff to the appropriate initializer, based on error code
+        switch (getErrorCode()) {
+          case "malwareBlocked" :
+            initPage_malware();
+            break;
+          case "phishingBlocked" :
+            initPage_phishing();
+            break;
+        }
+      }
+
+      /**
+       * Initialize custom strings and functionality for blocked malware case
+       */
+      function initPage_malware()
+      {
+        // Remove phishing strings
+        var el = document.getElementById("errorTitleText_phishing");
+        el.parentNode.removeChild(el);
+
+        el = document.getElementById("errorShortDescText_phishing");
+        el.parentNode.removeChild(el);
+
+        el = document.getElementById("errorLongDescText_phishing");
+        el.parentNode.removeChild(el);
+
+        // Set sitename
+        document.getElementById("malware_sitename").textContent = getHostString();
+        document.title = document.getElementById("errorTitleText_malware")
+                                 .textContent;
+      }
+
+      /**
+       * Initialize custom strings and functionality for blocked phishing case
+       */
+      function initPage_phishing()
+      {
+        // Remove malware strings
+        var el = document.getElementById("errorTitleText_malware");
+        el.parentNode.removeChild(el);
+
+        el = document.getElementById("errorShortDescText_malware");
+        el.parentNode.removeChild(el);
+
+        el = document.getElementById("errorLongDescText_malware");
+        el.parentNode.removeChild(el);
+
+        // Set sitename
+        document.getElementById("phishing_sitename").textContent = getHostString();
+        document.title = document.getElementById("errorTitleText_phishing")
+                                 .textContent;
+      }
+    ]]></script>
+    <style type="text/css">
+      #buttons > span {
+        float: left;
+      }
+
+      #buttons > #ignoreWarningButton {
+        float: right;
+        font-size: smaller;
+      }
+
+      #malware_sitename,
+      #phishing_sitename {
+        word-wrap: break-word;
+      }
+    </style>
+  </head>
+
+  <body dir="&locale.dir;">
+    <div id="errorPageContainer">
+
+      <!-- Error Title -->
+      <div id="errorTitle">
+        <h1 id="errorTitleText_phishing">&safeb.blocked.phishingPage.title;</h1>
+        <h1 id="errorTitleText_malware">&safeb.blocked.malwarePage.title;</h1>
+      </div>
+
+      <div id="errorLongContent">
+
+        <!-- Short Description -->
+        <div id="errorShortDesc">
+          <p id="errorShortDescText_phishing">&safeb.blocked.phishingPage.shortDesc;</p>
+          <p id="errorShortDescText_malware">&safeb.blocked.malwarePage.shortDesc;</p>
+        </div>
+
+        <!-- Long Description -->
+        <div id="errorLongDesc">
+          <p id="errorLongDescText_phishing">&safeb.blocked.phishingPage.longDesc;</p>
+          <p id="errorLongDescText_malware">&safeb.blocked.malwarePage.longDesc;</p>
+        </div>
+
+        <!-- Action buttons -->
+        <div id="buttons">
+          <!-- Commands handled in utilityOverlay.js -->
+          <span id="getMeOutOfHereButton" label="&safeb.palm.accept.label;"/>
+          <span id="reportButton" label="&safeb.palm.reportPage.label;"/>
+          <span id="ignoreWarningButton" label="&safeb.palm.decline.label;"/>
+        </div>
+      </div>
+    </div>
+    <!--
+    - Note: It is important to run the script this way, instead of using
+    - an onload handler. This is because error pages are loaded as
+    - LOAD_BACKGROUND, which means that onload handlers will not be executed.
+    -->
+    <script type="application/javascript">initPage();</script>
+  </body>
+</html>
--- a/suite/common/jar.mn
+++ b/suite/common/jar.mn
@@ -47,16 +47,17 @@ comm.jar:
 % content communicator-platform %content/communicator-platform/ platform
 % content communicator-region %content/communicator-region/
    content/communicator/about.xhtml
    content/communicator/aboutLife.xhtml
    content/communicator/aboutSessionRestore.js
    content/communicator/aboutSessionRestore.xhtml
    content/communicator/askViewZoom.xul
    content/communicator/askViewZoom.js
+   content/communicator/blockedSite.xhtml
    content/communicator/certError.css
    content/communicator/certError.xhtml
    content/communicator/certError.xml
    content/communicator/communicator.css
    content/communicator/consoleOverlay.xul
    content/communicator/contentAreaClick.js
    content/communicator/contentAreaContextOverlay.xul
    content/communicator/defaultClientDialog.js
--- a/suite/common/src/SuiteCommon.manifest
+++ b/suite/common/src/SuiteCommon.manifest
@@ -1,12 +1,13 @@
 component {08bbb4af-7bff-4b16-8ff7-d62f3ec5aa0c} nsSuiteDownloadManagerUI.js
 contract @mozilla.org/download-manager-ui;1 {08bbb4af-7bff-4b16-8ff7-d62f3ec5aa0c}
 component {d54f2c89-8fd6-4eeb-a7a4-51d4dcdf460f} nsAbout.js
 contract @mozilla.org/network/protocol/about;1?what= {d54f2c89-8fd6-4eeb-a7a4-51d4dcdf460f}
+contract @mozilla.org/network/protocol/about;1?what=blocked {d54f2c89-8fd6-4eeb-a7a4-51d4dcdf460f}
 contract @mozilla.org/network/protocol/about;1?what=certerror {d54f2c89-8fd6-4eeb-a7a4-51d4dcdf460f}
 contract @mozilla.org/network/protocol/about;1?what=data {d54f2c89-8fd6-4eeb-a7a4-51d4dcdf460f}
 contract @mozilla.org/network/protocol/about;1?what=feeds {d54f2c89-8fd6-4eeb-a7a4-51d4dcdf460f}
 contract @mozilla.org/network/protocol/about;1?what=life {d54f2c89-8fd6-4eeb-a7a4-51d4dcdf460f}
 contract @mozilla.org/network/protocol/about;1?what=rights {d54f2c89-8fd6-4eeb-a7a4-51d4dcdf460f}
 contract @mozilla.org/network/protocol/about;1?what=sessionrestore {d54f2c89-8fd6-4eeb-a7a4-51d4dcdf460f}
 contract @mozilla.org/network/protocol/about;1?what=sync-tabs {d54f2c89-8fd6-4eeb-a7a4-51d4dcdf460f}
 component {4e6c1112-57b6-44ba-adf9-99fb573b0a30} nsSessionStartup.js
--- a/suite/common/src/nsAbout.js
+++ b/suite/common/src/nsAbout.js
@@ -8,16 +8,18 @@ Components.utils.import("resource://gre/
 const SCRIPT = Components.interfaces.nsIAboutModule.ALLOW_SCRIPT;
 const UNTRUSTED = Components.interfaces.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT;
 const HIDE = Components.interfaces.nsIAboutModule.HIDE_FROM_ABOUTABOUT;
 
 function About() { }
 About.prototype = {
   Flags: SCRIPT,
   URI: "chrome://communicator/content/about.xhtml",
+  blockedFlags: SCRIPT | UNTRUSTED | HIDE,
+  blockedURI: "chrome://communicator/content/blockedSite.xhtml",
   certerrorFlags: SCRIPT | UNTRUSTED | HIDE,
   certerrorURI: "chrome://communicator/content/certError.xhtml",
   dataFlags: SCRIPT,
   dataURI: "chrome://communicator/content/dataman/dataman.xul",
   feedsFlags: SCRIPT | UNTRUSTED | HIDE,
   feedsURI: "chrome://communicator/content/feeds/subscribe.xhtml",
   lifeFlags: SCRIPT | HIDE,
   lifeURI: "chrome://communicator/content/aboutLife.xhtml",
--- a/suite/common/utilityOverlay.js
+++ b/suite/common/utilityOverlay.js
@@ -1087,22 +1087,23 @@ function openNewTabWindowOrExistingWith(
 function BrowserOnCommand(event)
 {
   // Don't trust synthetic events
   if (!event.isTrusted)
     return;
 
   const ot = event.originalTarget;
   const ownerDoc = ot.ownerDocument;
+  const docURI = ownerDoc.documentURI;
+  const buttonID = ot.getAttribute("anonid");
 
   // If the event came from an ssl error page, it is probably either the "Add
   // Exception" or "Get Me Out Of Here" button
-  if (/^about:neterror\?e=nssBadCert/.test(ownerDoc.documentURI) ||
-      /^about:certerror\?/.test(ownerDoc.documentURI)) {
-    if (ot.getAttribute('anonid') == 'exceptionDialogButton') {
+  if (docURI.startsWith("about:certerror?")) {
+    if (buttonID == "exceptionDialogButton") {
       var params = { exceptionAdded : false };
 
       switch (GetIntPref("browser.ssl_override_behavior", 2)) {
         case 2 : // Pre-fetch & pre-populate.
           params.prefetchCert = true;
           // Fall through.
         case 1 : // Pre-populate.
           params.location = ownerDoc.location.href;
@@ -1110,22 +1111,80 @@ function BrowserOnCommand(event)
 
       window.openDialog('chrome://pippki/content/exceptionDialog.xul',
                         '', 'chrome,centerscreen,modal', params);
 
       // If the user added the exception cert, attempt to reload the page
       if (params.exceptionAdded)
         ownerDoc.location.reload();
     }
-    else if (ot.getAttribute('anonid') == 'getMeOutOfHereButton') {
+    else if (buttonID == "getMeOutOfHereButton") {
       // Redirect them to a known-functioning page, default start page
-      content.location = GetLocalizedStringPref("browser.startup.homepage",
-                                                "about:blank");
+      getMeOutOfHere();
     }
   }
+  else if (docURI.startsWith("about:blocked")) {
+    // The event came from a button on a malware/phishing block page
+    // First check whether it's malware or phishing, so that we can
+    // use the right strings/links
+    let isMalware = /e=malwareBlocked/.test(docURI);
+
+    switch (buttonID) {
+      case "getMeOutOfHereButton":
+        getMeOutOfHere();
+        break;
+
+      case "reportButton":
+        // This is the "Why is this site blocked" button.  For malware,
+        // we can fetch a site-specific report, for phishing, we redirect
+        // to the generic page describing phishing protection.
+
+        if (isMalware) {
+          // Get the stop badware "why is this blocked" report url,
+          // append the current url, and go there.
+          try {
+            let reportURL = Services.urlFormatter.formatURLPref("browser.safebrowsing.malware.reportURL");
+            reportURL += ownerDoc.location.href;
+            loadURI(reportURL);
+          } catch (e) {
+            Components.utils.reportError("Couldn't get malware report URL: " + e);
+          }
+        }
+        else { // It's a phishing site, not malware
+          try {
+            loadURI(Services.urlFormatter.formatURLPref("browser.safebrowsing.warning.infoURL"));
+          } catch (e) {
+            Components.utils.reportError("Couldn't get phishing info URL: " + e);
+          }
+        }
+        break;
+
+      case "ignoreWarningButton":
+        getBrowser().getNotificationBox().ignoreSafeBrowsingWarning(isMalware);
+        break;
+    }
+  }
+}
+
+/**
+ * Re-direct the browser to a known-safe page.  This function is
+ * used when, for example, the user browses to a known malware page
+ * and is presented with about:blocked.  The "Get me out of here!"
+ * button should take the user to the default start page so that even
+ * when their own homepage is infected, we can get them somewhere safe.
+ */
+function getMeOutOfHere() {
+  // Get the start page from the *default* pref branch, not the user's
+  var prefs = Services.prefs.getDefaultBranch(null);
+  var url = "about:blank";
+  try {
+    url = prefs.getComplexValue("browser.startup.homepage",
+                                Components.interfaces.nsIPrefLocalizedString).data;
+  } catch(e) {}
+  loadURI(url);
 }
 
 function popupNotificationMenuShowing(event)
 {
   var notificationbox = document.popupNode.parentNode.control;
   var uri = notificationbox.activeBrowser.currentURI;
   var allowPopupsForSite = document.getElementById("allowPopupsForSite");
   allowPopupsForSite.notificationbox = notificationbox;
--- a/suite/locales/en-US/chrome/common/notification.properties
+++ b/suite/locales/en-US/chrome/common/notification.properties
@@ -158,8 +158,21 @@ SecurityTitle=Security Warning
 MixedContentMessage=You have requested an encrypted page that contains some unencrypted information. Information that you see or enter on this page could easily be read by a third party.
 EnterInsecureMessage=You have left an encrypted page. Information you send or receive from now on could easily be read by a third party.
 EnterSecureMessage=You have requested an encrypted page. The website has identified itself correctly, and information you see or enter on this page can't easily be read by a third party.
 SecurityPreferences.label=Preferences
 SecurityPreferences.accesskey=P
 PostToInsecureFromInsecureMessage=The information you have entered is to be sent over an unencrypted connection and could easily be read by a third party.\nAre you sure you want to continue sending this information?
 PostToInsecureFromInsecureShowAgain=Alert me whenever I submit information that's not encrypted.
 PostToInsecureContinue=Continue
+
+# Phishing/Malware Notification Bar.
+# LOCALIZATION NOTE (notAForgery, notAnAttack)
+# The two button strings will never be shown at the same time, so
+# it's okay for them to have the same access key.
+safebrowsing.getMeOutOfHereButton.label=Get me out of here!
+safebrowsing.getMeOutOfHereButton.accessKey=G
+safebrowsing.reportedWebForgery=Reported Web Forgery!
+safebrowsing.notAForgeryButton.label=This isn't a web forgery…
+safebrowsing.notAForgeryButton.accessKey=s
+safebrowsing.reportedAttackSite=Reported Attack Site!
+safebrowsing.notAnAttackButton.label=This isn't an attack site…
+safebrowsing.notAnAttackButton.accessKey=a
new file mode 100644
--- /dev/null
+++ b/suite/locales/en-US/chrome/common/safeBrowsing.dtd
@@ -0,0 +1,22 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!ENTITY safeb.palm.accept.label "Get me out of here!">
+<!ENTITY safeb.palm.decline.label "Ignore this warning">
+<!ENTITY safeb.palm.reportPage.label "Why was this page blocked?">
+
+<!ENTITY safeb.blocked.malwarePage.title "Reported Attack Page!">
+<!-- Localization note (safeb.blocked.malware.shortDesc) - Please don't translate the contents of the <span id="malware_sitename"/> tag.  It will be replaced at runtime with a domain name (e.g. www.badsite.com) -->
+<!ENTITY safeb.blocked.malwarePage.shortDesc "This web page at <span id='malware_sitename'/> has been reported as an attack page and has been blocked based on your security preferences.">
+<!ENTITY safeb.blocked.malwarePage.longDesc "<p>Attack pages try to install programs that steal private information, use your computer to attack others, or damage your system.</p><p>Some attack pages intentionally distribute harmful software, but many are compromised without the knowledge or permission of their owners.</p>">
+
+<!ENTITY safeb.blocked.phishingPage.title "Reported Web Forgery!">
+<!-- Localization note (safeb.blocked.phishing.shortDesc) - Please don't translate the contents of the <span id="phishing_sitename"/> tag. It will be replaced at runtime with a domain name (e.g. www.badsite.com) -->
+<!ENTITY safeb.blocked.phishingPage.shortDesc "This web page at <span id='phishing_sitename'/> has been reported as a web forgery and has been blocked based on your security preferences.">
+<!ENTITY safeb.blocked.phishingPage.longDesc "<p>Web forgeries are designed to trick you into revealing personal or financial information by imitating sources you may trust.</p><p>Entering any information on this web page may result in identity theft or other fraud.</p>">
+
+<!ENTITY reportPhishSite.label         "Report Web Forgery…">
+<!ENTITY reportPhishSite.accesskey     "F">
+<!ENTITY notAForgery.label             "This isn't a web forgery…">
+<!ENTITY notAForgery.accesskey         "f">
--- a/suite/locales/jar.mn
+++ b/suite/locales/jar.mn
@@ -32,16 +32,17 @@
   locale/@AB_CD@/communicator/defaultClientDialog.dtd                       (%chrome/common/defaultClientDialog.dtd)
   locale/@AB_CD@/communicator/feeds/subscribe.dtd                           (%chrome/common/feeds/subscribe.dtd)
   locale/@AB_CD@/communicator/feeds/subscribe.properties                    (%chrome/common/feeds/subscribe.properties)
   locale/@AB_CD@/communicator/notification.properties                       (%chrome/common/notification.properties)
   locale/@AB_CD@/communicator/openLocation.dtd                              (%chrome/common/openLocation.dtd)
   locale/@AB_CD@/communicator/openLocation.properties                       (%chrome/common/openLocation.properties)
   locale/@AB_CD@/communicator/passwordManager.dtd                           (%chrome/common/passwordManager.dtd)
   locale/@AB_CD@/communicator/printPreview.dtd                              (%chrome/common/printPreview.dtd)
+  locale/@AB_CD@/communicator/safeBrowsing.dtd                              (%chrome/common/safeBrowsing.dtd)
   locale/@AB_CD@/communicator/quitDialog.properties                         (%chrome/common/quitDialog.properties)
   locale/@AB_CD@/communicator/shellservice.properties                       (%chrome/common/shellservice.properties)
   locale/@AB_CD@/communicator/sanitize.dtd                                  (%chrome/common/sanitize.dtd)
   locale/@AB_CD@/communicator/setDesktopBackground.dtd                      (%chrome/common/setDesktopBackground.dtd)
   locale/@AB_CD@/communicator/tasksOverlay.dtd                              (%chrome/common/tasksOverlay.dtd)
   locale/@AB_CD@/communicator/typeaheadfind.properties                      (%chrome/common/typeaheadfind.properties)
   locale/@AB_CD@/communicator/utilityOverlay.dtd                            (%chrome/common/utilityOverlay.dtd)
   locale/@AB_CD@/communicator/utilityOverlay.properties                     (%chrome/common/utilityOverlay.properties)
--- a/suite/themes/classic/communicator/communicator.css
+++ b/suite/themes/classic/communicator/communicator.css
@@ -157,8 +157,12 @@ treecols:-moz-lwtheme {
 .messageImage[value="lwtheme-install-request"],
 .messageImage[value="lwtheme-install-notification"] {
   list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric-16.png");
 }
 
 .messageImage[value="popup-blocked"] {
   list-style-image: url("chrome://navigator/skin/icons/popup-blocked.png");
 }
+
+.messageImage[value="blocked-badware-page"] {
+  list-style-image: url("chrome://global/skin/icons/blacklist_favicon.png");
+}
--- a/suite/themes/modern/communicator/communicator.css
+++ b/suite/themes/modern/communicator/communicator.css
@@ -124,8 +124,12 @@ toolbar[iconsize="small"] > #print-butto
 .messageImage[value="lwtheme-install-request"],
 .messageImage[value="lwtheme-install-notification"] {
   list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric-16.png");
 }
 
 .messageImage[value="popup-blocked"] {
   list-style-image: url("chrome://navigator/skin/icons/popup-blocked.png");
 }
+
+.messageImage[value="blocked-badware-page"] {
+  list-style-image: url("chrome://global/skin/icons/blacklist_favicon.png");
+}
new file mode 100644
index 0000000000000000000000000000000000000000..c347fb36aae9618bf74073f72aa63450c413c089
GIT binary patch
literal 591
zc$@)G0<iswP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0006ONkl<Zc-pO!
z%}bPF6vcn{d1rLq8OJnjqZ2a~6hk0Ui<X8#gh*}LMPMJBw)F?J$VE_pKv0Vek`}q?
zKj>pCT1@I9lSo{Il}z(G@8@${jPs6&xv2|h^>EHT=bi`taRgwYTt<Mb2w500B9p**
zpbW%7O<~3)$&1hV9`9lGG@)^LGdoRYr<%gu2=1X;+_h*_U^J_GLy_Bw$b9?3Y!gwq
z6RAE5REwc1yAi0eC7dmW{_8=%c`6a<2!yI91J$Wem1qxg8$LoIXqGOneVH~&5okpS
zRd1o{HX98RYY}bv()N*doXmB*IkvemGVIl_0*^BQv9%YzT8kMUr*NRRMbkD%Q56Ib
z1KGtTx@t8J*MD6}1^g%~HTwFw{(PFeli9bnwb@rJO-|wlel?8{i1=Mm*jry(#taU2
z8Ws@M)C{WBS6A^7LSu5NfNxB#x7i$fH!*>6oP8Z6L4a{xHr&E`$ARigQ5{py>umeW
zWPnD#z_-4Ex1AcToe5OuLJ=Zkh#Y56#72m%WnriiHo9|9Q-Mg;i&Ss=B5O$;6WWB(
zT7qqa))FT%NgVNMsLb+k<$lK6`80x{B*=%4l@phWUj50?_w_T`I6|})$Qr|zTVSPh
zbZce!$OA>DjM_9(>r1NgyHK28tQ<c#Q2KH0;O5WE-GN`V#^k(w&-zCHq4&+4`^>S)
dT$}t~<_|6wA<<5}_woP$002ovPDHLkV1j`h5W)Ze
new file mode 100644
index 0000000000000000000000000000000000000000..96ff341c0abaa7fc265ea712bd80252e6f49a498
GIT binary patch
literal 3587
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
JPDHLkV1mbq#H9cL
--- a/suite/themes/modern/global/netError.css
+++ b/suite/themes/modern/global/netError.css
@@ -57,17 +57,17 @@ ul {
   background: url("chrome://global/skin/icons/alert-exclam.gif") left 0 no-repeat #C7D0D9;
   background-origin: content-box;
 }
 
 #errorPageContainer.certerror {
   background-image: url("chrome://global/skin/icons/alert-security.gif");
 }
 
-body[dir="rtl"] #errorPageContainer {
+#errorPageContainer:-moz-dir(rtl) {
   background-position: right 0;
 }
 
 #errorTitle {
   -moz-margin-start: 80px;
 }
 
 #errorLongContent {
@@ -95,17 +95,17 @@ body[dir="rtl"] #errorPageContainer {
 #brand {
   position: absolute;
   right: 0;
   bottom: -1.5em;
   -moz-margin-end: 10px;
   opacity: .6;
 }
 
-body[dir="rtl"] #brand {
+#brand:-moz-dir(rtl) {
   right: auto;
   left: 0;
 }
 
 #brand > p {
   margin: 0;
 }
 
@@ -118,8 +118,19 @@ body[dir="rtl"] #brand {
 }
 
 #securityOverrideContent {
   background-color: #FFFFE7;
   color: #000000;
   padding: 10px;
   border-radius: 10px;
 }
+
+/* Custom styling for 'blacklist' error class */
+:root.blacklist #errorPageContainer {
+  background-image: url("chrome://global/skin/icons/blacklist_large.png");
+  background-color: #772222;
+  color: #FFFFFF;
+}
+
+:root.blacklist {
+  background: #333333;
+}
--- a/suite/themes/modern/jar.mn
+++ b/suite/themes/modern/jar.mn
@@ -265,16 +265,18 @@ modern.jar:
   skin/modern/global/filepicker/folder-home.gif                    (global/filepicker/folder-home.gif)
   skin/modern/global/filepicker/folder-new.gif                     (global/filepicker/folder-new.gif)
   skin/modern/global/icons/alert-error.gif                         (global/icons/alert-error.gif)
   skin/modern/global/icons/alert-exclam.gif                        (global/icons/alert-exclam.gif)
   skin/modern/global/icons/alert-message.gif                       (global/icons/alert-message.gif)
   skin/modern/global/icons/alert-question.gif                      (global/icons/alert-question.gif)
   skin/modern/global/icons/alert-security.gif                      (global/icons/alert-security.gif)
   skin/modern/global/icons/autoscroll.png                          (/mozilla/toolkit/themes/winstripe/global/icons/autoscroll.png)
+  skin/modern/global/icons/blacklist_favicon.png                   (global/icons/blacklist_favicon.png)
+  skin/modern/global/icons/blacklist_large.png                     (global/icons/blacklist_large.png)
   skin/modern/global/icons/close-act.gif                           (global/icons/close-act.gif)
   skin/modern/global/icons/close-dis.gif                           (global/icons/close-dis.gif)
   skin/modern/global/icons/close-hov.gif                           (global/icons/close-hov.gif)
   skin/modern/global/icons/close.gif                               (global/icons/close.gif)
   skin/modern/global/icons/closebox.gif                            (global/icons/closebox.gif)
   skin/modern/global/icons/Error.png                               (global/icons/Error.png)
   skin/modern/global/icons/error-16.png                            (global/icons/error-16.png)
   skin/modern/global/icons/find.png                                (global/icons/find.png)