Sun, 30 Jul 2017 19:56:04 +0200
Added a progress bar to the management part and fine tuned the hash prefix update process.
# -*- coding: utf-8 -*- # Copyright (c) 2017 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing the interface for Google Safe Browsing. """ # # Some part of this code were ported from gglsbl.client and adapted # to Qt. # # https://github.com/afilipovich/gglsbl # from __future__ import unicode_literals import os import base64 from PyQt5.QtCore import pyqtSignal, QObject, QCoreApplication import Preferences import Utilities from .SafeBrowsingAPIClient import SafeBrowsingAPIClient from .SafeBrowsingCache import SafeBrowsingCache, ThreatList, HashPrefixList class SafeBrowsingManager(QObject): """ Class implementing the interface for Google Safe Browsing. @signal progressMessage(message,maximum) emitted to give a message for the action about to be performed and the maximum value @signal progress(current) emitted to signal the current progress """ progressMessage = pyqtSignal(str, int) progress = pyqtSignal(int) def __init__(self): """ Constructor """ super(SafeBrowsingManager, self).__init__() self.__apiKey = Preferences.getWebBrowser("SafeBrowsingApiKey") if self.__apiKey: ## self.__apiClient = SafeBrowsingAPIClient(self.__apiKey, ## parent=self) # TODO: switch these after debugging is finished self.__apiClient = SafeBrowsingAPIClient(self.__apiKey, parent=self, fairUse=False) else: self.__apiClient = None self.__enabled = ( Preferences.getWebBrowser("SafeBrowsingEnabled") and bool(self.__apiKey)) gsbCachePath = os.path.join( Utilities.getConfigDir(), "web_browser", "safe_browsing") self.__cache = SafeBrowsingCache(gsbCachePath, self) self.__gsbDialog = None self.__platforms = None # TODO: delete if not needed def configurationChanged(self): """ Public method to handle changes of the settings. """ apiKey = Preferences.getWebBrowser("SafeBrowsingApiKey") if apiKey != self.__apiKey: self.__apiKey = apiKey if self.__apiKey: if self.__apiClient: self.__apiClient.setApiKey(self.__apiKey) else: ## self.__apiClient = SafeBrowsingAPIClient(self.__apiKey, ## parent=self) # TODO: switch these after debugging is finished self.__apiClient = SafeBrowsingAPIClient(self.__apiKey, parent=self, fairUse=False) self.__enabled = ( Preferences.getWebBrowser("SafeBrowsingEnabled") and bool(self.__apiKey)) def isEnabled(self): """ Public method to check, if safe browsing is enabled. @return flag indicating the enabled state @rtype bool """ return self.__enabled def close(self): """ Public method to close the safe browsing interface. """ self.__cache.close() def fairUseDelayExpired(self): """ Public method to check, if the fair use wait period has expired. @return flag indicating expiration @rtype bool """ return self.__enabled and self.__apiClient.fairUseDelayExpired() def updateHashPrefixCache(self): """ Public method to load or update the locally cached threat lists. @return flag indicating success and an error message @rtype tuple of (bool, str) """ if not self.__enabled: return False, self.tr("Safe Browsing is disabled.") if not self.__apiClient.fairUseDelayExpired(): return False, \ self.tr("The fair use wait period has not expired yet.") # step 1: remove expired hashes self.__cache.cleanupFullHashes() # step 2: update threat lists threatListsForRemove = {} for threatList, clientState in self.__cache.getThreatLists(): threatListsForRemove[repr(threatList)] = threatList threatLists = self.__apiClient.getThreatLists() maximum = len(threatLists) current = 0 self.progressMessage.emit(self.tr("Updating threat lists"), maximum) for entry in threatLists: current += 1 self.progress.emit(current) QCoreApplication.processEvents() threatList = ThreatList.fromApiEntry(entry) if self.__platforms is None or \ threatList.platformType in self.__platforms: self.__cache.addThreatList(threatList) key = repr(threatList) if key in threatListsForRemove: del threatListsForRemove[key] maximum = len(threatListsForRemove.values()) current = 0 self.progressMessage.emit(self.tr("Deleting obsolete threat lists"), maximum) for threatList in threatListsForRemove.values(): current += 1 self.progress.emit(current) QCoreApplication.processEvents() self.__cache.deleteHashPrefixList(threatList) self.__cache.deleteThreatList(threatList) del threatListsForRemove # step 3: update threats threatLists = self.__cache.getThreatLists() clientStates = {} for threatList, clientState in threatLists: clientStates[threatList.asTuple()] = clientState threatsUpdateResponses = \ self.__apiClient.getThreatsUpdate(clientStates) maximum = len(threatsUpdateResponses) current = 0 self.progressMessage.emit(self.tr("Updating hash prefixes"), maximum) for response in threatsUpdateResponses: current += 1 self.progress.emit(current) QCoreApplication.processEvents() responseThreatList = ThreatList.fromApiEntry(response) if response["responseType"] == "FULL_UPDATE": self.__cache.deleteHashPrefixList(responseThreatList) for removal in response.get("removals", []): self.__cache.removeHashPrefixIndices( responseThreatList, removal["rawIndices"]["indices"]) QCoreApplication.processEvents() for addition in response.get("additions", []): hashPrefixList = HashPrefixList( addition["rawHashes"]["prefixSize"], base64.b64decode(addition["rawHashes"]["rawHashes"])) self.__cache.populateHashPrefixList(responseThreatList, hashPrefixList) QCoreApplication.processEvents() expectedChecksum = base64.b64decode(response["checksum"]["sha256"]) if self.__verifyThreatListChecksum(responseThreatList, expectedChecksum): self.__cache.updateThreatListClientState( responseThreatList, response["newClientState"]) else: return False, \ self.tr("Local cache checksum does not match the server." " Consider cleaning the cache. Threat update has" " been aborted.") return True, "" def __verifyThreatListChecksum(self, threatList, remoteChecksum): """ Private method to verify the local checksum of a threat list with the checksum of the safe browsing server. """ localChecksum = self.__cache.hashPrefixListChecksum(threatList) return remoteChecksum == localChecksum def fullCacheCleanup(self): """ Public method to clean up the cache completely. """ self.__cache.prepareCacheDb() def showSafeBrowsingDialog(self): """ Public slot to show the safe browsing management dialog. """ if self.__gsbDialog is None: from WebBrowser.WebBrowserWindow import WebBrowserWindow from .SafeBrowsingDialog import SafeBrowsingDialog self.__gsbDialog = SafeBrowsingDialog( self, parent=WebBrowserWindow.mainWindow()) self.__gsbDialog.show()