Finished coding the safe browsing module of the new web browser. safe_browsing

Fri, 04 Aug 2017 18:38:45 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Fri, 04 Aug 2017 18:38:45 +0200
branch
safe_browsing
changeset 5829
d3448873ced3
parent 5821
6c7766cde4c1
child 5830
e91a1a8c0a5d
child 5833
e46c177917c8

Finished coding the safe browsing module of the new web browser.

Preferences/__init__.py file | annotate | diff | comparison | revisions
WebBrowser/Download/DownloadManager.py file | annotate | diff | comparison | revisions
WebBrowser/SafeBrowsing/SafeBrowsingAPIClient.py file | annotate | diff | comparison | revisions
WebBrowser/SafeBrowsing/SafeBrowsingCache.py file | annotate | diff | comparison | revisions
WebBrowser/SafeBrowsing/SafeBrowsingDialog.py file | annotate | diff | comparison | revisions
WebBrowser/SafeBrowsing/SafeBrowsingDialog.ui file | annotate | diff | comparison | revisions
WebBrowser/SafeBrowsing/SafeBrowsingInfoWidget.py file | annotate | diff | comparison | revisions
WebBrowser/SafeBrowsing/SafeBrowsingLabel.py file | annotate | diff | comparison | revisions
WebBrowser/SafeBrowsing/SafeBrowsingManager.py file | annotate | diff | comparison | revisions
WebBrowser/SafeBrowsing/SafeBrowsingUrl.py file | annotate | diff | comparison | revisions
WebBrowser/UrlBar/UrlBar.py file | annotate | diff | comparison | revisions
WebBrowser/WebBrowserPage.py file | annotate | diff | comparison | revisions
WebBrowser/WebBrowserView.py file | annotate | diff | comparison | revisions
eric6.e4p file | annotate | diff | comparison | revisions
--- a/Preferences/__init__.py	Sun Jul 30 19:56:04 2017 +0200
+++ b/Preferences/__init__.py	Fri Aug 04 18:38:45 2017 +0200
@@ -1122,6 +1122,7 @@
         # Google Safe Browsing
         "SafeBrowsingEnabled": True,
         "SafeBrowsingApiKey": "",       # API key
+        "SafeBrowsingFilterPlatform": True,
     }
     if QWebEngineSettings:
         webBrowserDefaults["HelpViewerType"] = 1      # eric browser
@@ -2922,7 +2923,7 @@
                  "SpellCheckEnabled", "ShowToolbars", "MenuBarVisible",
                  "BookmarksToolBarVisible", "StatusBarVisible",
                  "SessionAutoSave", "LoadTabOnActivation",
-                 "SafeBrowsingEnabled",
+                 "SafeBrowsingEnabled", "SafeBrowsingFilterPlatform",
                  ]:
         return toBool(prefClass.settings.value(
             "WebBrowser/" + key, prefClass.webBrowserDefaults[key]))
--- a/WebBrowser/Download/DownloadManager.py	Sun Jul 30 19:56:04 2017 +0200
+++ b/WebBrowser/Download/DownloadManager.py	Fri Aug 04 18:38:45 2017 +0200
@@ -167,9 +167,29 @@
         download data.
         @type QWebEngineDownloadItem
         """
-        if downloadItem.url().isEmpty():
+        url = downloadItem.url()
+        if url.isEmpty():
             return
         
+        # Safe Browsing
+        threatLists = WebBrowserWindow.safeBrowsingManager().lookupUrl(url)
+        if threatLists:
+            threatMessages = WebBrowserWindow.safeBrowsingManager()\
+                .getThreatMessages(threatLists)
+            res = E5MessageBox.warning(
+                WebBrowserWindow.getWindow(),
+                self.tr("Suspicuous URL detected"),
+                self.tr("<p>The URL <b>{0}</b> was found in the Safe Browsing"
+                        " database.</p>{1}").format(url.toString(),
+                                                    "".join(threatMessages)),
+                E5MessageBox.StandardButtons(
+                    E5MessageBox.Abort |
+                    E5MessageBox.Ignore),
+                E5MessageBox.Abort)
+            if res == E5MessageBox.Abort:
+                downloadItem.cancel()
+                return
+        
         from .DownloadItem import DownloadItem
         itm = DownloadItem(downloadItem, parent=self)
         self.__addItem(itm)
--- a/WebBrowser/SafeBrowsing/SafeBrowsingAPIClient.py	Sun Jul 30 19:56:04 2017 +0200
+++ b/WebBrowser/SafeBrowsing/SafeBrowsingAPIClient.py	Fri Aug 04 18:38:45 2017 +0200
@@ -174,7 +174,7 @@
         
         for (threatType, platformType, threatEntryType), currentState in \
                 clientState.items():
-            requestBody["clientStates"].append(clientState)
+            requestBody["clientStates"].append(currentState)
             if threatType not in requestBody["threatInfo"]["threatTypes"]:
                 requestBody["threatInfo"]["threatTypes"].append(threatType)
             if platformType not in \
@@ -229,7 +229,7 @@
         if not self.__fairUse or minimumWaitDuration is None:
             self.__nextRequestNoSoonerThan = QDateTime()
         else:
-            waitDuration = int(minimumWaitDuration.rstrip("s"))
+            waitDuration = int(float(minimumWaitDuration.rstrip("s")))
             self.__nextRequestNoSoonerThan = \
                 QDateTime.currentDateTime().addSecs(waitDuration)
     
@@ -244,3 +244,169 @@
             self.__fairUse and
             QDateTime.currentDateTime() >= self.__nextRequestNoSoonerThan
         ) or not self.__fairUse
+    
+    def getFairUseDelayExpirationDateTime(self):
+        """
+        Public method to get the date and time the fair use delay will expire.
+        
+        @return fair use delay expiration date and time
+        @rtype QDateTime
+        """
+        return self.__nextRequestNoSoonerThan
+    
+    @classmethod
+    def getThreatMessage(cls, threatType):
+        """
+        Class method to get a warning message for the given threat type.
+        
+        @param threatType threat type to get the message for
+        @type str
+        @return threat message
+        @rtype str
+        """
+        threatType = threatType.lower()
+        if threatType == "malware":
+            msg = QCoreApplication.translate(
+                "SafeBrowsingAPI",
+                "<h3>Malware Warning</h3>"
+                "<p>The web site you are about to visit may try to install"
+                " harmful programs on your computer in order to steal or"
+                " destroy your data.</p>")
+        elif threatType == "social_engineering":
+            msg = QCoreApplication.translate(
+                "SafeBrowsingAPI",
+                "<h3>Phishing Warning</h3>"
+                "<p>The web site you are about to visit may try to trick you"
+                " into doing something dangerous online, such as revealing"
+                " passwords or personal information, usually through a fake"
+                " website.</p>")
+        elif threatType == "unwanted_software":
+            msg = QCoreApplication.translate(
+                "SafeBrowsingAPI",
+                "<h3>Unwanted Software Warning</h3>"
+                "<p>The software you are about to download may negatively"
+                " affect your browsing or computing experience.</p>")
+        elif threatType == "potentially_harmful_application":
+            msg = QCoreApplication.translate(
+                "SafeBrowsingAPI",
+                "<h3>Potentially Harmful Application</h3>"
+                "<p>The web site you are about to visit may try to trick you"
+                " into installing applications, that may negatively affect"
+                " your browsing experience.</p>")
+        else:
+            # unknow threat
+            msg = QCoreApplication.translate(
+                "SafeBrowsingAPI",
+                "<h3>Unknown Threat Warning</h3>"
+                "<p>The web site you are about to visit was found in the Safe"
+                " Browsing Database but was not classified yet.</p>")
+        
+        return msg
+    
+    @classmethod
+    def getThreatType(cls, threatType):
+        """
+        Class method to get a display string for a given threat type.
+        
+        @param threatType threat type to get display string for
+        @type str
+        @return display string
+        @rtype str
+        """
+        threatType = threatType.lower()
+        if threatType == "malware":
+            displayString = QCoreApplication.translate(
+                "SafeBrowsingAPI", "Malware")
+        elif threatType == "social_engineering":
+            displayString = QCoreApplication.translate(
+                "SafeBrowsingAPI", "Phishing")
+        elif threatType == "unwanted_software":
+            displayString = QCoreApplication.translate(
+                "SafeBrowsingAPI", "Unwanted Software")
+        elif threatType == "potentially_harmful_application":
+            displayString = QCoreApplication.translate(
+                "SafeBrowsingAPI", "Harmful Application")
+        elif threatType == "malcious_binary":
+            displayString = QCoreApplication.translate(
+                "SafeBrowsingAPI", "Malicious Binary")
+        else:
+            displayString = QCoreApplication.translate(
+                "SafeBrowsingAPI", "Unknown Threat")
+        
+        return displayString
+    
+    @classmethod
+    def getPlatformString(cls, platformType):
+        """
+        Class method to get the platform string for a given platform type.
+        
+        @param platformType platform type as defined in the v4 API
+        @type str
+        @return platform string
+        @rtype str
+        """
+        platformStrings = {
+            "WINDOWS": "Windows",
+            "LINUX": "Linux",
+            "ANDROID": "Android",
+            "OSX": "macOS",
+            "IOS": "iOS",
+            "CHROME": "Chrome OS",
+        }
+        if platformType in platformStrings:
+            return platformStrings[platformType]
+        
+        if platformType == "ANY_PLATFORM":
+            return QCoreApplication.translate(
+                "SafeBrowsingAPI", "any defined platform")
+        elif platformType == "ALL_PLATFORMS":
+            return QCoreApplication.translate(
+                "SafeBrowsingAPI", "all defined platforms")
+        else:
+            return QCoreApplication.translate(
+                "SafeBrowsingAPI", "unknown platform")
+    
+    @classmethod
+    def getThreatEntryString(cls, threatEntry):
+        """
+        Class method to get the threat entry string.
+        
+        @param threatEntry threat entry type as defined in the v4 API
+        @type str
+        @return threat entry string
+        @rtype str
+        """
+        if threatEntry == "URL":
+            return "URL"
+        elif threatEntry == "EXECUTABLE":
+            return QCoreApplication.translate(
+                "SafeBrowsingAPI", "executable program")
+        else:
+            return QCoreApplication.translate(
+                "SafeBrowsingAPI", "unknown type")
+    
+    @classmethod
+    def getPlatformTypes(cls, platform):
+        """
+        Class method to get the platform types for a given platform.
+        
+        @param platform platform string
+        @type str (one of 'linux', 'windows', 'macos')
+        @return list of platform types as defined in the v4 API for the
+            given platform
+        @rtype list of str
+        @exception ValueError raised to indicate an invalid platform string
+        """
+        platform = platform.lower()
+        
+        platformTypes = ["ANY_PLATFORM", "ALL_PLATFORMS"]
+        if platform == "linux":
+            platformTypes.append("LINUX")
+        elif platform == "windows":
+            platformTypes.append("WINDOWS")
+        elif platform == "macos":
+            platformTypes.append("OSX")
+        else:
+            raise ValueError("Unsupported platform")
+        
+        return platformTypes
--- a/WebBrowser/SafeBrowsing/SafeBrowsingCache.py	Sun Jul 30 19:56:04 2017 +0200
+++ b/WebBrowser/SafeBrowsing/SafeBrowsingCache.py	Fri Aug 04 18:38:45 2017 +0200
@@ -18,7 +18,8 @@
 
 import os
 
-from PyQt5.QtCore import QObject, QByteArray, QCryptographicHash
+from PyQt5.QtCore import QObject, QByteArray, QCryptographicHash, \
+    QCoreApplication, QEventLoop
 from PyQt5.QtSql import QSql, QSqlDatabase, QSqlQuery
 
 from .SafeBrowsingUtilities import toHex
@@ -283,7 +284,7 @@
             try:
                 query = QSqlQuery(db)
                 query.prepare(
-                    queryStr.format(",".join(["?" * len(hashValues)])))
+                    queryStr.format(",".join(["?"] * len(hashValues))))
                 for hashValue in hashValues:
                     query.addBindValue(QByteArray(hashValue),
                                        QSql.In | QSql.Binary)
@@ -294,7 +295,7 @@
                     threatType = query.value(0)
                     platformType = query.value(1)
                     threatEntryType = query.value(2)
-                    hasExpired = query.value(3)     # TODO: check if bool
+                    hasExpired = bool(query.value(3))
                     threatList = ThreatList(threatType, platformType,
                                             threatEntryType)
                     output.append((threatList, hasExpired))
@@ -327,7 +328,7 @@
             try:
                 query = QSqlQuery(db)
                 query.prepare(
-                    queryStr.format(",".join(["?" * len(prefixes)])))
+                    queryStr.format(",".join(["?"] * len(prefixes))))
                 for prefix in prefixes:
                     query.addBindValue(prefix)
                 
@@ -338,7 +339,7 @@
                     threatType = query.value(1)
                     platformType = query.value(2)
                     threatEntryType = query.value(3)
-                    negativeCacheExpired = query.value(4)  # TODO: check if bool
+                    negativeCacheExpired = bool(query.value(4))
                     threatList = ThreatList(threatType, platformType,
                                             threatEntryType)
                     output.append((threatList, fullHash, negativeCacheExpired))
@@ -627,7 +628,7 @@
         db = QSqlDatabase.database(self.__connectionName)
         if db.isOpen():
             db.transaction()
-            hash = QCryptographicHash(QCryptographicHash.Sha256)
+            sha256Hash = QCryptographicHash(QCryptographicHash.Sha256)
             try:
                 query = QSqlQuery(db)
                 query.prepare(queryStr)
@@ -638,12 +639,13 @@
                 query.exec_()
                 
                 while query.next():
-                    hash.addData(query.value(0))
+                    sha256Hash.addData(query.value(0))
+                    QCoreApplication.processEvents(QEventLoop.AllEvents, 200)
                 del query
             finally:
                 db.commit()
             
-            checksum = bytes(hash.result())
+            checksum = bytes(sha256Hash.result())
         
         return checksum
     
--- a/WebBrowser/SafeBrowsing/SafeBrowsingDialog.py	Sun Jul 30 19:56:04 2017 +0200
+++ b/WebBrowser/SafeBrowsing/SafeBrowsingDialog.py	Fri Aug 04 18:38:45 2017 +0200
@@ -9,7 +9,7 @@
 
 from __future__ import unicode_literals
 
-from PyQt5.QtCore import pyqtSlot, Qt
+from PyQt5.QtCore import pyqtSlot, Qt, QUrl
 from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QAbstractButton, \
     QApplication
 
@@ -42,9 +42,6 @@
         self.__manager.progressMessage.connect(self.__setProgressMessage)
         self.__manager.progress.connect(self.__setProgress)
         
-        self.__saveButton = self.buttonBox.addButton(
-            self.tr("Save"), QDialogButtonBox.ActionRole)
-        
         self.iconLabel.setPixmap(
             UI.PixmapCache.getPixmap("safeBrowsing48.png"))
         
@@ -52,6 +49,8 @@
         
         self.__enabled = Preferences.getWebBrowser("SafeBrowsingEnabled")
         self.__apiKey = Preferences.getWebBrowser("SafeBrowsingApiKey")
+        self.__filterPlatform = Preferences.getWebBrowser(
+            "SafeBrowsingFilterPlatform")
         
         self.buttonBox.setFocus()
         
@@ -64,6 +63,7 @@
         """
         self.gsbGroupBox.setChecked(self.__enabled)
         self.gsbApiKeyEdit.setText(self.__apiKey)
+        self.gsbFilterPlatformCheckBox.setChecked(self.__filterPlatform)
         
         self.__updateCacheButtons()
         
@@ -95,8 +95,6 @@
         """
         if button == self.buttonBox.button(QDialogButtonBox.Close):
             self.close()
-        elif button == self.__saveButton:
-            self.__save()
     
     @pyqtSlot()
     def __save(self):
@@ -108,9 +106,12 @@
         """
         self.__enabled = self.gsbGroupBox.isChecked()
         self.__apiKey = self.gsbApiKeyEdit.text()
+        self.__filterPlatform = self.gsbFilterPlatformCheckBox.isChecked()
         
         Preferences.setWebBrowser("SafeBrowsingEnabled", self.__enabled)
         Preferences.setWebBrowser("SafeBrowsingApiKey", self.__apiKey)
+        Preferences.setWebBrowser("SafeBrowsingFilterPlatform",
+                                  self.__filterPlatform)
         
         self.__manager.configurationChanged()
         
@@ -139,7 +140,8 @@
         """
         return (
             self.__enabled != self.gsbGroupBox.isChecked() or
-            self.__apiKey != self.gsbApiKeyEdit.text()
+            self.__apiKey != self.gsbApiKeyEdit.text() or
+            self.__filterPlatform != self.gsbFilterPlatformCheckBox.isChecked()
         )
     
     def __okToClose(self):
@@ -149,6 +151,7 @@
         @return flag indicating safe to close
         @rtype bool
         """
+        QApplication.restoreOverrideCursor()
         if self.__isModified():
             res = E5MessageBox.okToClearData(
                 self,
@@ -173,6 +176,12 @@
         """
         Private slot to update the local cache database.
         """
+        E5MessageBox.information(
+            self,
+            self.tr("Update Safe Browsing Cache"),
+            self.tr("""Updating the Safe Browsing cache might be a lengthy"""
+                    """ operation. Please be patient!"""))
+        
         QApplication.setOverrideCursor(Qt.WaitCursor)
         ok, error = self.__manager.updateHashPrefixCache()
         self.__resetProgress()
@@ -196,7 +205,15 @@
         """
         Private slot to clear the local cache database.
         """
-        self.__manager.fullCacheCleanup()
+        res = E5MessageBox.yesNo(
+            self,
+            self.tr("Clear Safe Browsing Cache"),
+            self.tr("""Do you really want to clear the Safe Browsing cache?"""
+                    """ Re-populating it might take some time."""))
+        if res:
+            QApplication.setOverrideCursor(Qt.WaitCursor)
+            self.__manager.fullCacheCleanup()
+            QApplication.restoreOverrideCursor()
     
     @pyqtSlot(str, int)
     def __setProgressMessage(self, message, maximum):
@@ -229,3 +246,57 @@
         self.progressLabel.clear()
         self.progressBar.setMaximum(100)
         self.progressBar.setValue(0)
+    
+    @pyqtSlot(str)
+    def on_urlEdit_textChanged(self, text):
+        """
+        Private slot to handle changes of the entered URL text.
+        
+        @param text entered URL text
+        @type str
+        """
+        url = QUrl.fromUserInput(text)
+        enable = (
+            url.isValid() and
+            bool(url.scheme()) and
+            url.scheme() not in self.__manager.getIgnoreSchemes()
+        )
+        self.urlCheckButton.setEnabled(enable)
+    
+    @pyqtSlot()
+    def on_urlCheckButton_clicked(self):
+        """
+        Private slot to check the entered URL.
+        """
+        # Malicious URL for testing:
+        # http://malware.testing.google.test/testing/malware/*
+        # http://ianfette.org
+        #
+        urlStr = self.urlEdit.text()
+        url = QUrl.fromUserInput(urlStr)
+        threatLists = self.__manager.lookupUrl(url)
+        
+        if threatLists:
+            threatMessages = self.__manager.getThreatMessages(threatLists)
+            E5MessageBox.warning(
+                self,
+                self.tr("Check URL"),
+                self.tr("<p>The URL <b>{0}</b> was found in the Safe Browsing"
+                        " Database.</p>{1}").format(urlStr,
+                                                    "".join(threatMessages))
+            )
+        else:
+            E5MessageBox.information(
+                self,
+                self.tr("Check URL"),
+                self.tr("<p>The URL <b>{0}</b> was not found in the Safe"
+                        " Browsing Database and may be considered safe.</p>")
+                .format(urlStr)
+            )
+    
+    @pyqtSlot()
+    def on_saveButton_clicked(self):
+        """
+        Private slot to save the configuration data.
+        """
+        self.__save()
--- a/WebBrowser/SafeBrowsing/SafeBrowsingDialog.ui	Sun Jul 30 19:56:04 2017 +0200
+++ b/WebBrowser/SafeBrowsing/SafeBrowsingDialog.ui	Fri Aug 04 18:38:45 2017 +0200
@@ -7,7 +7,7 @@
     <x>0</x>
     <y>0</y>
     <width>650</width>
-    <height>288</height>
+    <height>461</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -42,21 +42,31 @@
         </property>
        </widget>
       </item>
-      <item row="0" column="1">
+      <item row="0" column="1" colspan="2">
+       <widget class="QCheckBox" name="gsbFilterPlatformCheckBox">
+        <property name="toolTip">
+         <string>Select to check against the current platform only</string>
+        </property>
+        <property name="text">
+         <string>Adjust to current platform</string>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="1">
        <widget class="QLabel" name="label_14">
         <property name="text">
          <string>API Key:</string>
         </property>
        </widget>
       </item>
-      <item row="0" column="2">
+      <item row="1" column="2">
        <widget class="QLineEdit" name="gsbApiKeyEdit">
         <property name="toolTip">
          <string>Enter the Google Safe Browsing API key</string>
         </property>
        </widget>
       </item>
-      <item row="1" column="1" colspan="2">
+      <item row="2" column="1" colspan="2">
        <widget class="QPushButton" name="gsbHelpButton">
         <property name="toolTip">
          <string>Press to get some help about obtaining the API key</string>
@@ -70,6 +80,46 @@
     </widget>
    </item>
    <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_2">
+     <item>
+      <spacer name="horizontalSpacer">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <widget class="QPushButton" name="saveButton">
+       <property name="toolTip">
+        <string>Press to save the current configuration settings</string>
+       </property>
+       <property name="text">
+        <string>Save Configuration</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <spacer name="horizontalSpacer_2">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+    </layout>
+   </item>
+   <item>
     <widget class="QGroupBox" name="groupBox">
      <property name="title">
       <string>Manage Local Cache</string>
@@ -123,6 +173,38 @@
     </widget>
    </item>
    <item>
+    <widget class="QGroupBox" name="groupBox_2">
+     <property name="title">
+      <string>URL Check</string>
+     </property>
+     <layout class="QGridLayout" name="gridLayout_2">
+      <item row="0" column="0">
+       <widget class="QLabel" name="label">
+        <property name="text">
+         <string>URL:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="0" column="1">
+       <widget class="E5ClearableLineEdit" name="urlEdit"/>
+      </item>
+      <item row="1" column="0" colspan="2">
+       <widget class="QPushButton" name="urlCheckButton">
+        <property name="enabled">
+         <bool>false</bool>
+        </property>
+        <property name="toolTip">
+         <string>Press to check the entered URL</string>
+        </property>
+        <property name="text">
+         <string>Check URL</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
     <widget class="QDialogButtonBox" name="buttonBox">
      <property name="orientation">
       <enum>Qt::Horizontal</enum>
@@ -134,10 +216,23 @@
    </item>
   </layout>
  </widget>
+ <customwidgets>
+  <customwidget>
+   <class>E5ClearableLineEdit</class>
+   <extends>QLineEdit</extends>
+   <header>E5Gui/E5LineEdit.h</header>
+  </customwidget>
+ </customwidgets>
  <tabstops>
   <tabstop>gsbGroupBox</tabstop>
+  <tabstop>gsbFilterPlatformCheckBox</tabstop>
   <tabstop>gsbApiKeyEdit</tabstop>
   <tabstop>gsbHelpButton</tabstop>
+  <tabstop>saveButton</tabstop>
+  <tabstop>updateCacheButton</tabstop>
+  <tabstop>clearCacheButton</tabstop>
+  <tabstop>urlEdit</tabstop>
+  <tabstop>urlCheckButton</tabstop>
  </tabstops>
  <resources/>
  <connections/>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebBrowser/SafeBrowsing/SafeBrowsingInfoWidget.py	Fri Aug 04 18:38:45 2017 +0200
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2017 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a widget to show some threat information.
+"""
+
+from __future__ import unicode_literals
+
+from PyQt5.QtCore import Qt, QPoint
+from PyQt5.QtWidgets import QMenu, QLabel, QHBoxLayout, QSizePolicy
+
+import UI.PixmapCache
+
+
+class SafeBrowsingInfoWidget(QMenu):
+    """
+    Class implementing a widget to show some threat information.
+    """
+    def __init__(self, info, parent=None):
+        """
+        Constructor
+        
+        @param info information string to be shown
+        @type str
+        @param parent reference to the parent widget
+        @type QWidget
+        """
+        super(SafeBrowsingInfoWidget, self).__init__(parent)
+        
+        self.setMinimumWidth(500)
+        
+        layout = QHBoxLayout(self)
+        
+        iconLabel = QLabel(self)
+        iconLabel.setPixmap(UI.PixmapCache.getPixmap("safeBrowsing48.png"))
+        layout.addWidget(iconLabel, 0, Qt.AlignTop)
+        
+        infoLabel = QLabel(self)
+        infoLabel.setWordWrap(True)
+        infoLabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
+        infoLabel.setText(info)
+        layout.addWidget(infoLabel, 0, Qt.AlignTop)
+    
+    def showAt(self, pos):
+        """
+        Public method to show the widget.
+        
+        @param pos position to show at
+        @type QPoint
+        """
+        self.adjustSize()
+        xpos = pos.x() - self.width()
+        if xpos < 0:
+            xpos = 10
+        p = QPoint(xpos, pos.y() + 10)
+        self.move(p)
+        self.show()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebBrowser/SafeBrowsing/SafeBrowsingLabel.py	Fri Aug 04 18:38:45 2017 +0200
@@ -0,0 +1,99 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2017 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the label to show some SSL info.
+"""
+
+from __future__ import unicode_literals
+
+from PyQt5.QtCore import Qt, pyqtSlot, pyqtSignal, QPoint
+from PyQt5.QtWidgets import QLabel
+
+
+class SafeBrowsingLabel(QLabel):
+    """
+    Class implementing a label to show some Safe Browsing info.
+    
+    @signal clicked(pos) emitted to indicate a click of the label (QPoint)
+    """
+    clicked = pyqtSignal(QPoint)
+    
+    nokStyle = "QLabel { color : white; background-color : red; }"
+    
+    def __init__(self, parent=None):
+        """
+        Constructor
+        
+        @param parent reference to the parent widget (QWidget)
+        """
+        super(SafeBrowsingLabel, self).__init__(parent)
+        
+        self.setFocusPolicy(Qt.NoFocus)
+        self.setCursor(Qt.PointingHandCursor)
+        
+        self.setStyleSheet(SafeBrowsingLabel.nokStyle)
+        
+        self.__threatType = ""
+        self.__threatMessages = ""
+        
+        self.__deafultText = self.tr("Malicious Site")
+        self.__updateLabel()
+    
+    def mouseReleaseEvent(self, evt):
+        """
+        Protected method to handle mouse release events.
+        
+        @param evt reference to the mouse event (QMouseEvent)
+        """
+        if evt.button() == Qt.LeftButton:
+            self.clicked.emit(evt.globalPos())
+        else:
+            super(SafeBrowsingLabel, self).mouseReleaseEvent(evt)
+    
+    def mouseDoubleClickEvent(self, evt):
+        """
+        Protected method to handle mouse double click events.
+        
+        @param evt reference to the mouse event (QMouseEvent)
+        """
+        if evt.button() == Qt.LeftButton:
+            self.clicked.emit(evt.globalPos())
+        else:
+            super(SafeBrowsingLabel, self).mouseDoubleClickEvent(evt)
+    
+    @pyqtSlot()
+    def __updateLabel(self):
+        """
+        Private slot to update the label text.
+        """
+        if self.__threatType:
+            self.setText(self.__threatType)
+        else:
+            self.setText(self.__deafultText)
+    
+    @pyqtSlot(str, str)
+    def setThreatInfo(self, threatType, threatMessages):
+        """
+        Public slot to set threat information.
+        
+        @param threatType threat type
+        @type str
+        @param threatMessages more verbose info about detected threats
+        @type str
+        """
+        self.__threatType = threatType
+        self.__threatMessages = threatMessages
+        
+        self.__updateLabel()
+    
+    def getThreatInfo(self):
+        """
+        Public method to get the threat info text.
+        
+        @return threat info text
+        @rtype str
+        """
+        return self.__threatMessages
--- a/WebBrowser/SafeBrowsing/SafeBrowsingManager.py	Sun Jul 30 19:56:04 2017 +0200
+++ b/WebBrowser/SafeBrowsing/SafeBrowsingManager.py	Fri Aug 04 18:38:45 2017 +0200
@@ -19,13 +19,15 @@
 import os
 import base64
 
-from PyQt5.QtCore import pyqtSignal, QObject, QCoreApplication
+from PyQt5.QtCore import pyqtSignal, QObject, QCoreApplication, QUrl
 
 import Preferences
 import Utilities
 
 from .SafeBrowsingAPIClient import SafeBrowsingAPIClient
 from .SafeBrowsingCache import SafeBrowsingCache, ThreatList, HashPrefixList
+from .SafeBrowsingUrl import SafeBrowsingUrl
+from .SafeBrowsingUtilities import toHex
 
 
 class SafeBrowsingManager(QObject):
@@ -47,12 +49,12 @@
         
         self.__apiKey = Preferences.getWebBrowser("SafeBrowsingApiKey")
         if self.__apiKey:
-##            self.__apiClient = SafeBrowsingAPIClient(self.__apiKey,
-##                                                     parent=self)
+            self.__apiClient = SafeBrowsingAPIClient(self.__apiKey,
+                                                     parent=self)
             # TODO: switch these after debugging is finished
-            self.__apiClient = SafeBrowsingAPIClient(self.__apiKey,
-                                                     parent=self,
-                                                     fairUse=False)
+##            self.__apiClient = SafeBrowsingAPIClient(self.__apiKey,
+##                                                     parent=self,
+##                                                     fairUse=False)
         else:
             self.__apiClient = None
         
@@ -65,7 +67,7 @@
         self.__cache = SafeBrowsingCache(gsbCachePath, self)
         
         self.__gsbDialog = None
-        self.__platforms = None     # TODO: delete if not needed
+        self.__setPlatforms()
     
     def configurationChanged(self):
         """
@@ -78,16 +80,33 @@
                 if self.__apiClient:
                     self.__apiClient.setApiKey(self.__apiKey)
                 else:
-##                    self.__apiClient = SafeBrowsingAPIClient(self.__apiKey,
-##                                                             parent=self)
+                    self.__apiClient = SafeBrowsingAPIClient(self.__apiKey,
+                                                             parent=self)
                     # TODO: switch these after debugging is finished
-                    self.__apiClient = SafeBrowsingAPIClient(self.__apiKey,
-                                                             parent=self,
-                                                             fairUse=False)
+##                    self.__apiClient = SafeBrowsingAPIClient(self.__apiKey,
+##                                                             parent=self,
+##                                                             fairUse=False)
         
         self.__enabled = (
             Preferences.getWebBrowser("SafeBrowsingEnabled") and
             bool(self.__apiKey))
+        
+        self.__setPlatforms()
+    
+    def __setPlatforms(self):
+        """
+        Private method to set the platforms to be checked against.
+        """
+        self.__platforms = None
+        if Preferences.getWebBrowser("SafeBrowsingFilterPlatform"):
+            if Utilities.isWindowsPlatform():
+                platform = "windows"
+            elif Utilities.isMacPlatform():
+                platform = "macos"
+            else:
+                # treat all other platforms like linux
+                platform = "linux"
+            self.__platforms = SafeBrowsingAPIClient.getPlatformTypes(platform)
     
     def isEnabled(self):
         """
@@ -125,7 +144,10 @@
         
         if not self.__apiClient.fairUseDelayExpired():
             return False, \
-                self.tr("The fair use wait period has not expired yet.")
+                self.tr("The fair use wait period has not expired yet."
+                        "Expiration will be at {0}.").format(
+                    self.__apiClient.getFairUseDelayExpirationDateTime()
+                    .toString("yyyy-MM-dd, HH:mm:ss"))
         
         # step 1: remove expired hashes
         self.__cache.cleanupFullHashes()
@@ -206,6 +228,13 @@
         """
         Private method to verify the local checksum of a threat list with the
         checksum of the safe browsing server.
+        
+        @param threatList threat list to calculate checksum for
+        @type ThreatList
+        @param remoteChecksum SHA256 checksum as reported by the Google server
+        @type bytes
+        @return flag indicating equality
+        @rtype bool
         """
         localChecksum = self.__cache.hashPrefixListChecksum(threatList)
         return remoteChecksum == localChecksum
@@ -227,3 +256,230 @@
                 self, parent=WebBrowserWindow.mainWindow())
         
         self.__gsbDialog.show()
+    
+    def lookupUrl(self, url):
+        """
+        Public method to lookup an URL.
+        
+        @param url URL to be checked
+        @type str or QUrl
+        @return list of threat lists the URL was found in
+        @rtype list of ThreatList
+        @exception ValueError raised for an invalid URL
+        """
+        if self.__enabled:
+            if isinstance(url, QUrl):
+                urlStr = url.toString().strip()
+            else:
+                urlStr = url.strip()
+            
+            if not urlStr:
+                raise ValueError("Empty URL given.")
+            
+            urlHashes = SafeBrowsingUrl(urlStr).hashes()
+            listNames = self.__lookupHashes(urlHashes)
+            if listNames:
+                return listNames
+        
+        return None
+    
+    def __lookupHashes(self, fullHashes):
+        """
+        Private method to lookup the given hashes.
+        
+        @param fullHashes list of hashes to lookup
+        @type list of bytes
+        @return names of threat lists hashes were found in
+        @rtype list of ThreatList
+        """
+        fullHashes = list(fullHashes)
+        cues = [toHex(fh[:4]) for fh in fullHashes]
+        result = []
+        
+        matchingPrefixes = {}
+        matchingFullHashes = set()
+        isPotentialThreat = False
+        # Lookup hash prefixes which match full URL hash
+        for threatList, hashPrefix, negativeCacheExpired in \
+                self.__cache.lookupHashPrefix(cues):
+            for fullHash in fullHashes:
+                if fullHash.startswith(hashPrefix):
+                    isPotentialThreat = True
+                    # consider hash prefix negative cache as expired if it
+                    # is expired in at least one threat list
+                    matchingPrefixes[hashPrefix] = matchingPrefixes.get(
+                        hashPrefix, False) or negativeCacheExpired
+                    matchingFullHashes.add(fullHash)
+            
+        # if none matches, url hash is clear
+        if not isPotentialThreat:
+            return []
+        
+        # if there is non-expired full hash, URL is blacklisted
+        matchingExpiredThreatLists = set()
+        for threatList, hasExpired in self.__cache.lookupFullHashes(
+                matchingFullHashes):
+            if hasExpired:
+                matchingExpiredThreatLists.add(threatList)
+            else:
+                result.append(threatList)
+        if result:
+            return result
+        
+        # If there are no matching expired full hash entries and negative
+        # cache is still current for all prefixes, consider it safe.
+        if len(matchingExpiredThreatLists) == 0 and \
+           sum(map(int, matchingPrefixes.values())) == 0:
+            return []
+        
+        # Now it can be assumed that there are expired matching full hash
+        # entries and/or cache prefix entries with expired negative cache.
+        # Both require full hash synchronization.
+        self.__syncFullHashes(matchingPrefixes.keys())
+        
+        # Now repeat full hash lookup
+        for threatList, hasExpired in self.__cache.lookupFullHashes(
+                matchingFullHashes):
+            if not hasExpired:
+                result.append(threatList)
+        
+        return result
+    
+    def __syncFullHashes(self, hashPrefixes):
+        """
+        Private method to download full hashes matching given prefixes.
+        
+        This also updates the cache expiration timestamps.
+        
+        @param hashPrefixes list of hash prefixes to get full hashes for
+        @type list of bytes
+        """
+        threatLists = self.__cache.getThreatLists()
+        clientStates = {}
+        for threatList, clientState in threatLists:
+            clientStates[threatList.asTuple()] = clientState
+        
+        fullHashResponses = self.__apiClient.getFullHashes(
+            hashPrefixes, clientStates)
+        
+        # update negative cache for each hash prefix
+        # store full hash with positive cache bumped up
+        for match in fullHashResponses["matches"]:
+            threatList = ThreatList.fromApiEntry(match)
+            hashValue = base64.b64decode(match["threat"]["hash"])
+            cacheDuration = int(match["cacheDuration"].rstrip("s"))
+            malwareThreatType = None
+            for metadata in match["threatEntryMetadata"].get("entries", []):
+                key = base64.b64decode(metadata["key"])
+                value = base64.b64decode(metadata["value"])
+                if key == b"malware_threat_type":
+                    malwareThreatType = value
+                    if not isinstance(malwareThreatType, str):
+                        malwareThreatType = malwareThreatType.decode()
+            self.__cache.storeFullHash(threatList, hashValue, cacheDuration,
+                                       malwareThreatType)
+        
+        negativeCacheDuration = int(
+            fullHashResponses["negativeCacheDuration"].rstrip("s"))
+        for prefixValue in hashPrefixes:
+            for threatList, clientState in threatLists:
+                self.__cache.updateHashPrefixExpiration(
+                    threatList, prefixValue, negativeCacheDuration)
+    
+    @classmethod
+    def getIgnoreSchemes(cls):
+        """
+        Class method to get the schemes not to be checked.
+        
+        @return list of schemes to be ignored
+        @rtype list of str
+        """
+        return [
+            "about",
+            "eric",
+            "qrc",
+            "qthelp",
+            "chrome",
+            "abp",
+            "file",
+        ]
+    
+    def getThreatMessage(self, threatType):
+        """
+        Public method to get a warning message for the given threat type.
+        
+        @param threatType threat type to get the message for
+        @type str
+        @return threat message
+        @rtype str
+        """
+        if self.__apiClient:
+            msg = self.__apiClient.getThreatMessage(threatType)
+        else:
+            msg = ""
+        
+        return msg
+    
+    def getThreatMessages(self, threatLists):
+        """
+        Public method to get threat messages for the given threats.
+        
+        @param threatLists list of threat lists to get a message for
+        @type list of ThreatList
+        @return list of threat messages, one per unique threat type
+        @rtype list of str
+        """
+        threatTypes = set()
+        for threatList in threatLists:
+            threatTypes.add(threatList.threatType)
+        
+        messages = []
+        if self.__apiClient:
+            for threatType in sorted(threatTypes):
+                msg = self.__apiClient.getThreatMessage(threatType)
+                messages.append(msg)
+        
+        return messages
+    
+    def getThreatType(self, threatList):
+        """
+        Public method to get a display string for a given threat type.
+        
+        @param threatList threat list to get display string for
+        @type str
+        @return display string
+        @rtype str
+        """
+        displayString = ""
+        if self.__apiClient:
+            displayString = self.__apiClient.getThreatType(
+                threatList.threatType)
+        return displayString
+    
+    def getPlatformString(self, platformType):
+        """
+        Public method to get the platform string for a given platform type.
+        
+        @param platformType platform type as defined in the v4 API
+        @type str
+        @return platform string
+        @rtype str
+        """
+        if self.__apiClient:
+            return self.__apiClient.getPlatformString(platformType)
+        else:
+            return ""
+    
+    def getThreatEntryString(self, threatEntry):
+        """
+        Public method to get the threat entry string.
+        
+        @param threatEntry threat entry type as defined in the v4 API
+        @type str
+        @return threat entry string
+        @rtype str
+        """
+        if self.__apiClient:
+            return self.__apiClient.getThreatEntryString(threatEntry)
+        else:
+            return ""
--- a/WebBrowser/SafeBrowsing/SafeBrowsingUrl.py	Sun Jul 30 19:56:04 2017 +0200
+++ b/WebBrowser/SafeBrowsing/SafeBrowsingUrl.py	Fri Aug 04 18:38:45 2017 +0200
@@ -108,9 +108,8 @@
             query = None
         if not path:
             path = '/'
-        hasTrailingSlash = (path[-1] == '/')
         path = posixpath.normpath(path).replace('//', '/')
-        if hasTrailingSlash and path[-1] != '/':
+        if path[-1] != '/':
             path += '/'
         port = urlParts.port
         host = host.strip('.')
--- a/WebBrowser/UrlBar/UrlBar.py	Sun Jul 30 19:56:04 2017 +0200
+++ b/WebBrowser/UrlBar/UrlBar.py	Fri Aug 04 18:38:45 2017 +0200
@@ -13,7 +13,7 @@
 except NameError:
     pass
 
-from PyQt5.QtCore import pyqtSlot, Qt, QPointF, QUrl, QDateTime, QTimer
+from PyQt5.QtCore import pyqtSlot, Qt, QPointF, QUrl, QDateTime, QTimer, QPoint
 from PyQt5.QtGui import QColor, QPalette, QLinearGradient, QIcon
 from PyQt5.QtWidgets import QDialog, QApplication
 from PyQt5.QtWebEngineWidgets import QWebEnginePage
@@ -23,7 +23,10 @@
 
 from WebBrowser.WebBrowserWindow import WebBrowserWindow
 
+from WebBrowser.SafeBrowsing.SafeBrowsingLabel import SafeBrowsingLabel
+
 from .FavIconLabel import FavIconLabel
+
 import UI.PixmapCache
 import Preferences
 
@@ -51,6 +54,10 @@
         self.__bmInactiveIcon = QIcon(
             self.__bmActiveIcon.pixmap(16, 16, QIcon.Disabled))
         
+        self.__safeBrowsingLabel = SafeBrowsingLabel(self)
+        self.addWidget(self.__safeBrowsingLabel, E5LineEdit.LeftSide)
+        self.__safeBrowsingLabel.setVisible(False)
+        
         self.__favicon = FavIconLabel(self)
         self.addWidget(self.__favicon, E5LineEdit.LeftSide)
         
@@ -68,6 +75,7 @@
         self.addWidget(self.__clearButton, E5LineEdit.RightSide)
         self.__clearButton.setVisible(False)
         
+        self.__safeBrowsingLabel.clicked.connect(self.__showThreatInfo)
         self.__bookmarkButton.clicked.connect(self.__showBookmarkInfo)
         self.__rssButton.clicked.connect(self.__rssClicked)
         self.__clearButton.clicked.connect(self.clear)
@@ -95,6 +103,9 @@
         self.__browser.loadProgress.connect(self.update)
         self.__browser.loadFinished.connect(self.__loadFinished)
         self.__browser.loadStarted.connect(self.__loadStarted)
+        
+        self.__browser.safeBrowsingBad.connect(
+            self.__safeBrowsingLabel.setThreatInfo)
     
     def browser(self):
         """
@@ -156,6 +167,10 @@
             self.__checkBookmark()
             self.__bookmarkButton.setVisible(True)
         
+        self.__browserUrlChanged(self.__browser.url())
+        self.__safeBrowsingLabel.setVisible(
+            not self.__browser.getSafeBrowsingStatus())
+        
         if ok:
             QTimer.singleShot(0, self.__setRssButton)
     
@@ -220,16 +235,19 @@
         if self.__browser is not None:
             p = self.palette()
             progress = self.__browser.progress()
+            
+            if not self.__browser.getSafeBrowsingStatus():
+                # malicious web site
+                backgroundColor = QColor(170, 0, 0)
+                foregroundColor = QColor(Qt.white)
+            elif self.__browser.url().scheme() == "https":
+                backgroundColor = Preferences.getWebBrowser(
+                    "SaveUrlColor")
+            
             if progress == 0 or progress == 100:
-                if self.__browser.url().scheme() == "https":
-                    backgroundColor = Preferences.getWebBrowser(
-                        "SaveUrlColor")
                 p.setBrush(QPalette.Base, backgroundColor)
                 p.setBrush(QPalette.Text, foregroundColor)
             else:
-                if self.__browser.url().scheme() == "https":
-                    backgroundColor = Preferences.getWebBrowser(
-                        "SaveUrlColor")
                 highlight = QApplication.palette().color(QPalette.Highlight)
                 r = (highlight.red() + 2 * backgroundColor.red()) // 3
                 g = (highlight.green() + 2 * backgroundColor.green()) // 3
@@ -379,3 +397,18 @@
         feeds = self.__browser.getRSS()
         dlg = FeedsDialog(feeds, self.__browser)
         dlg.exec_()
+    
+    @pyqtSlot(QPoint)
+    def __showThreatInfo(self, pos):
+        """
+        Private slot to show the threat info widget.
+        
+        @param pos position to show the info at
+        @type QPoint
+        """
+        threatInfo = self.__safeBrowsingLabel.getThreatInfo()
+        if threatInfo:
+            from WebBrowser.SafeBrowsing.SafeBrowsingInfoWidget import \
+                SafeBrowsingInfoWidget
+            widget = SafeBrowsingInfoWidget(threatInfo, self.__browser)
+            widget.showAt(pos)
--- a/WebBrowser/WebBrowserPage.py	Sun Jul 30 19:56:04 2017 +0200
+++ b/WebBrowser/WebBrowserPage.py	Fri Aug 04 18:38:45 2017 +0200
@@ -14,12 +14,15 @@
 except NameError:
     pass
 
-from PyQt5.QtCore import pyqtSlot, QUrl, QTimer, QEventLoop, QPoint, QPointF
+from PyQt5.QtCore import pyqtSlot, pyqtSignal, QUrl, QTimer, QEventLoop, \
+    QPoint, QPointF
 from PyQt5.QtGui import QDesktopServices
 from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineSettings, \
     QWebEngineScript
 from PyQt5.QtWebChannel import QWebChannel
 
+from E5Gui import E5MessageBox
+
 from WebBrowser.WebBrowserWindow import WebBrowserWindow
 
 from .JavaScript.ExternalJsObject import ExternalJsObject
@@ -33,6 +36,11 @@
 class WebBrowserPage(QWebEnginePage):
     """
     Class implementing an enhanced web page.
+    
+    @signal safeBrowsingAbort() emitted to indicate an abort due to a safe
+        browsing event
+    @signal safeBrowsingBad(threatType, threatMessages) emitted to indicate a
+        malicious web site as determined by safe browsing
     """
     if qVersionTuple() >= (5, 7, 0):
         # SafeJsWorld = QWebEngineScript.ApplicationWorld
@@ -40,6 +48,9 @@
     else:
         SafeJsWorld = QWebEngineScript.MainWorld
     
+    safeBrowsingAbort = pyqtSignal()
+    safeBrowsingBad = pyqtSignal(str, str)
+    
     def __init__(self, parent=None):
         """
         Constructor
@@ -65,6 +76,7 @@
         self.urlChanged.connect(self.__urlChanged)
         
         self.__printer = None
+        self.__badSite = False
     
     def acceptNavigationRequest(self, url, type_, isMainFrame):
         """
@@ -96,6 +108,33 @@
             WebBrowserWindow.greaseMonkeyManager().downloadScript(url)
             return False
         
+        # Safe Browsing
+        self.__badSite = False
+        if url.scheme() not in \
+           WebBrowserWindow.safeBrowsingManager().getIgnoreSchemes():
+            threatLists = WebBrowserWindow.safeBrowsingManager().lookupUrl(url)
+            if threatLists:
+                threatMessages = WebBrowserWindow.safeBrowsingManager()\
+                    .getThreatMessages(threatLists)
+                res = E5MessageBox.warning(
+                    WebBrowserWindow.getWindow(),
+                    self.tr("Suspicuous URL detected"),
+                    self.tr("<p>The URL <b>{0}</b> was found in the Safe"
+                            " Browsing database.</p>{1}").format(
+                        url.toString(), "".join(threatMessages)),
+                    E5MessageBox.StandardButtons(
+                        E5MessageBox.Abort |
+                        E5MessageBox.Ignore),
+                    E5MessageBox.Abort)
+                if res == E5MessageBox.Abort:
+                    self.safeBrowsingAbort.emit()
+                    return False
+                
+                self.__badSite = True
+                threatType = WebBrowserWindow.safeBrowsingManager()\
+                    .getThreatType(threatLists[0])
+                self.safeBrowsingBad.emit(threatType, "".join(threatMessages))
+        
         return QWebEnginePage.acceptNavigationRequest(self, url, type_,
                                                       isMainFrame)
     
@@ -374,6 +413,19 @@
         self.view().mainWindow().javascriptConsole().javaScriptConsoleMessage(
             level, message, lineNumber, sourceId)
     
+    ###########################################################################
+    ## Methods below implement safe browsing related functions
+    ###########################################################################
+    
+    def getSafeBrowsingStatus(self):
+        """
+        Public method to get the safe browsing status of the current page.
+        
+        @return flag indicating a safe site
+        @rtype bool
+        """
+        return not self.__badSite
+    
     ##################################################
     ## Methods below implement compatibility functions
     ##################################################
--- a/WebBrowser/WebBrowserView.py	Sun Jul 30 19:56:04 2017 +0200
+++ b/WebBrowser/WebBrowserView.py	Fri Aug 04 18:38:45 2017 +0200
@@ -53,6 +53,10 @@
     @signal search(QUrl) emitted, when a search is requested
     @signal zoomValueChanged(int) emitted to signal a change of the zoom value
     @signal faviconChanged() emitted to signal a changed web site icon
+    @signal safeBrowsingAbort() emitted to indicate an abort due to a safe
+        browsing event
+    @signal safeBrowsingBad(threatType, threatMessages) emitted to indicate a
+        malicious web site as determined by safe browsing
     """
     sourceChanged = pyqtSignal(QUrl)
     forwardAvailable = pyqtSignal(bool)
@@ -61,6 +65,8 @@
     search = pyqtSignal(QUrl)
     zoomValueChanged = pyqtSignal(int)
     faviconChanged = pyqtSignal()
+    safeBrowsingAbort = pyqtSignal()
+    safeBrowsingBad = pyqtSignal(str, str)
     
     ZoomLevels = [
         30, 40, 50, 67, 80, 90,
@@ -85,8 +91,8 @@
         
         self.__speedDial = WebBrowserWindow.speedDial()
         
-        self.__page = WebBrowserPage(self)
-        self.setPage(self.__page)
+        self.__page = None
+        self.__createNewPage()
         
         self.__mw = mainWindow
         self.__isLoading = False
@@ -137,6 +143,16 @@
         
         self.grabGesture(Qt.PinchGesture)
     
+    def __createNewPage(self):
+        """
+        Private method to create a new page object.
+        """
+        self.__page = WebBrowserPage(self)
+        self.setPage(self.__page)
+        
+        self.__page.safeBrowsingAbort.connect(self.safeBrowsingAbort)
+        self.__page.safeBrowsingBad.connect(self.safeBrowsingBad)
+    
     def __setRwhvqt(self):
         """
         Private slot to set widget that receives input events.
@@ -1579,8 +1595,7 @@
         @type QWebEnginePage.RenderProcessTerminationStatus
         """
         self.page().deleteLater()
-        self.__page = WebBrowserPage(self)
-        self.setPage(self.__page)
+        self.__createNewPage()
         
         html = readAllFileContents(":/html/tabCrashPage.html")
         html = html.replace("@IMAGE@", pixmapToDataUrl(
@@ -2049,3 +2064,19 @@
                 QUrl.fromUserInput(urlStr))
         
         return title, urlStr, icon
+    
+    ###########################################################################
+    ## Methods below implement safe browsing related functions
+    ###########################################################################
+    
+    def getSafeBrowsingStatus(self):
+        """
+        Public method to get the safe browsing status of the current page.
+        
+        @return flag indicating a safe site
+        @rtype bool
+        """
+        if self.__page:
+            return self.__page.getSafeBrowsingStatus()
+        else:
+            return True
--- a/eric6.e4p	Sun Jul 30 19:56:04 2017 +0200
+++ b/eric6.e4p	Fri Aug 04 18:38:45 2017 +0200
@@ -1407,6 +1407,8 @@
     <Source>WebBrowser/SafeBrowsing/SafeBrowsingAPIClient.py</Source>
     <Source>WebBrowser/SafeBrowsing/SafeBrowsingCache.py</Source>
     <Source>WebBrowser/SafeBrowsing/SafeBrowsingDialog.py</Source>
+    <Source>WebBrowser/SafeBrowsing/SafeBrowsingInfoWidget.py</Source>
+    <Source>WebBrowser/SafeBrowsing/SafeBrowsingLabel.py</Source>
     <Source>WebBrowser/SafeBrowsing/SafeBrowsingManager.py</Source>
     <Source>WebBrowser/SafeBrowsing/SafeBrowsingUrl.py</Source>
     <Source>WebBrowser/SafeBrowsing/SafeBrowsingUtilities.py</Source>

eric ide

mercurial