src/eric7/WebBrowser/SafeBrowsing/SafeBrowsingManager.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9413
80c06d472826
equal deleted inserted replaced
9220:e9e7eca7efee 9221:bf71ee032bb4
16 16
17 import os 17 import os
18 import base64 18 import base64
19 19
20 from PyQt6.QtCore import ( 20 from PyQt6.QtCore import (
21 pyqtSignal, pyqtSlot, QObject, QCoreApplication, QUrl, QDateTime, QTimer 21 pyqtSignal,
22 pyqtSlot,
23 QObject,
24 QCoreApplication,
25 QUrl,
26 QDateTime,
27 QTimer,
22 ) 28 )
23 29
24 import Preferences 30 import Preferences
25 import Utilities 31 import Utilities
26 32
34 40
35 41
36 class SafeBrowsingManager(QObject): 42 class SafeBrowsingManager(QObject):
37 """ 43 """
38 Class implementing the interface for Google Safe Browsing. 44 Class implementing the interface for Google Safe Browsing.
39 45
40 @signal progressMessage(message,maximum) emitted to give a message for the 46 @signal progressMessage(message,maximum) emitted to give a message for the
41 action about to be performed and the maximum value 47 action about to be performed and the maximum value
42 @signal progress(current) emitted to signal the current progress 48 @signal progress(current) emitted to signal the current progress
43 """ 49 """
50
44 progressMessage = pyqtSignal(str, int) 51 progressMessage = pyqtSignal(str, int)
45 progress = pyqtSignal(int) 52 progress = pyqtSignal(int)
46 53
47 enabled = ( 54 enabled = Preferences.getWebBrowser("SafeBrowsingEnabled") and bool(
48 Preferences.getWebBrowser("SafeBrowsingEnabled") and 55 Preferences.getWebBrowser("SafeBrowsingApiKey")
49 bool(Preferences.getWebBrowser("SafeBrowsingApiKey"))
50 ) 56 )
51 57
52 def __init__(self): 58 def __init__(self):
53 """ 59 """
54 Constructor 60 Constructor
55 """ 61 """
56 super().__init__() 62 super().__init__()
57 63
58 self.__apiKey = Preferences.getWebBrowser("SafeBrowsingApiKey") 64 self.__apiKey = Preferences.getWebBrowser("SafeBrowsingApiKey")
59 if self.__apiKey: 65 if self.__apiKey:
60 self.__apiClient = SafeBrowsingAPIClient(self.__apiKey, 66 self.__apiClient = SafeBrowsingAPIClient(self.__apiKey, parent=self)
61 parent=self)
62 else: 67 else:
63 self.__apiClient = None 68 self.__apiClient = None
64 69
65 gsbCachePath = os.path.join( 70 gsbCachePath = os.path.join(
66 Utilities.getConfigDir(), "web_browser", "safe_browsing") 71 Utilities.getConfigDir(), "web_browser", "safe_browsing"
72 )
67 self.__cache = SafeBrowsingCache(gsbCachePath, self) 73 self.__cache = SafeBrowsingCache(gsbCachePath, self)
68 74
69 self.__gsbDialog = None 75 self.__gsbDialog = None
70 self.__setPlatforms() 76 self.__setPlatforms()
71 self.__setLookupMethod() 77 self.__setLookupMethod()
72 78
73 self.__updatingThreatLists = False 79 self.__updatingThreatLists = False
74 self.__threatListsUpdateTimer = QTimer(self) 80 self.__threatListsUpdateTimer = QTimer(self)
75 self.__threatListsUpdateTimer.setSingleShot(True) 81 self.__threatListsUpdateTimer.setSingleShot(True)
76 self.__threatListsUpdateTimer.timeout.connect( 82 self.__threatListsUpdateTimer.timeout.connect(
77 self.__threatListsUpdateTimerTimeout) 83 self.__threatListsUpdateTimerTimeout
84 )
78 self.__setAutoUpdateThreatLists() 85 self.__setAutoUpdateThreatLists()
79 86
80 def configurationChanged(self): 87 def configurationChanged(self):
81 """ 88 """
82 Public method to handle changes of the settings. 89 Public method to handle changes of the settings.
83 """ 90 """
84 apiKey = Preferences.getWebBrowser("SafeBrowsingApiKey") 91 apiKey = Preferences.getWebBrowser("SafeBrowsingApiKey")
86 self.__apiKey = apiKey 93 self.__apiKey = apiKey
87 if self.__apiKey: 94 if self.__apiKey:
88 if self.__apiClient: 95 if self.__apiClient:
89 self.__apiClient.setApiKey(self.__apiKey) 96 self.__apiClient.setApiKey(self.__apiKey)
90 else: 97 else:
91 self.__apiClient = SafeBrowsingAPIClient(self.__apiKey, 98 self.__apiClient = SafeBrowsingAPIClient(self.__apiKey, parent=self)
92 parent=self) 99
93 100 SafeBrowsingManager.enabled = Preferences.getWebBrowser(
94 SafeBrowsingManager.enabled = ( 101 "SafeBrowsingEnabled"
95 Preferences.getWebBrowser("SafeBrowsingEnabled") and 102 ) and bool(self.__apiKey)
96 bool(self.__apiKey)) 103
97
98 self.__setPlatforms() 104 self.__setPlatforms()
99 self.__setLookupMethod() 105 self.__setLookupMethod()
100 self.__setAutoUpdateThreatLists() 106 self.__setAutoUpdateThreatLists()
101 107
102 def __setPlatforms(self): 108 def __setPlatforms(self):
103 """ 109 """
104 Private method to set the platforms to be checked against. 110 Private method to set the platforms to be checked against.
105 """ 111 """
106 self.__platforms = None 112 self.__platforms = None
111 platform = "macos" 117 platform = "macos"
112 else: 118 else:
113 # treat all other platforms like linux 119 # treat all other platforms like linux
114 platform = "linux" 120 platform = "linux"
115 self.__platforms = SafeBrowsingAPIClient.getPlatformTypes(platform) 121 self.__platforms = SafeBrowsingAPIClient.getPlatformTypes(platform)
116 122
117 def __setLookupMethod(self): 123 def __setLookupMethod(self):
118 """ 124 """
119 Private method to set the lookup method (Update API or Lookup API). 125 Private method to set the lookup method (Update API or Lookup API).
120 """ 126 """
121 self.__useLookupApi = Preferences.getWebBrowser( 127 self.__useLookupApi = Preferences.getWebBrowser("SafeBrowsingUseLookupApi")
122 "SafeBrowsingUseLookupApi") 128
123
124 @classmethod 129 @classmethod
125 def isEnabled(cls): 130 def isEnabled(cls):
126 """ 131 """
127 Class method to check, if safe browsing is enabled. 132 Class method to check, if safe browsing is enabled.
128 133
129 @return flag indicating the enabled state 134 @return flag indicating the enabled state
130 @rtype bool 135 @rtype bool
131 """ 136 """
132 return cls.enabled 137 return cls.enabled
133 138
134 def close(self): 139 def close(self):
135 """ 140 """
136 Public method to close the safe browsing interface. 141 Public method to close the safe browsing interface.
137 """ 142 """
138 self.__cache.close() 143 self.__cache.close()
139 144
140 def fairUseDelayExpired(self): 145 def fairUseDelayExpired(self):
141 """ 146 """
142 Public method to check, if the fair use wait period has expired. 147 Public method to check, if the fair use wait period has expired.
143 148
144 @return flag indicating expiration 149 @return flag indicating expiration
145 @rtype bool 150 @rtype bool
146 """ 151 """
147 return self.isEnabled() and self.__apiClient.fairUseDelayExpired() 152 return self.isEnabled() and self.__apiClient.fairUseDelayExpired()
148 153
149 def __showNotificationMessage(self, message, timeout=5): 154 def __showNotificationMessage(self, message, timeout=5):
150 """ 155 """
151 Private method to show some message in a notification widget. 156 Private method to show some message in a notification widget.
152 157
153 @param message message to be shown 158 @param message message to be shown
154 @type str 159 @type str
155 @param timeout amount of time in seconds the message should be shown 160 @param timeout amount of time in seconds the message should be shown
156 (0 = indefinitely) 161 (0 = indefinitely)
157 @type int 162 @type int
158 """ 163 """
159 from WebBrowser.WebBrowserWindow import WebBrowserWindow 164 from WebBrowser.WebBrowserWindow import WebBrowserWindow
160 165
161 kind = ( 166 kind = (
162 NotificationTypes.CRITICAL 167 NotificationTypes.CRITICAL
163 if timeout == 0 else 168 if timeout == 0
164 NotificationTypes.INFORMATION 169 else NotificationTypes.INFORMATION
165 ) 170 )
166 171
167 WebBrowserWindow.showNotification( 172 WebBrowserWindow.showNotification(
168 UI.PixmapCache.getPixmap("safeBrowsing48"), 173 UI.PixmapCache.getPixmap("safeBrowsing48"),
169 self.tr("Google Safe Browsing"), 174 self.tr("Google Safe Browsing"),
170 message, 175 message,
171 kind=kind, 176 kind=kind,
172 timeout=timeout, 177 timeout=timeout,
173 ) 178 )
174 179
175 def __setAutoUpdateThreatLists(self): 180 def __setAutoUpdateThreatLists(self):
176 """ 181 """
177 Private method to set auto update for the threat lists. 182 Private method to set auto update for the threat lists.
178 """ 183 """
179 autoUpdateEnabled = ( 184 autoUpdateEnabled = Preferences.getWebBrowser(
180 Preferences.getWebBrowser("SafeBrowsingAutoUpdate") and 185 "SafeBrowsingAutoUpdate"
181 not Preferences.getWebBrowser("SafeBrowsingUseLookupApi") 186 ) and not Preferences.getWebBrowser("SafeBrowsingUseLookupApi")
182 )
183 if autoUpdateEnabled and self.isEnabled(): 187 if autoUpdateEnabled and self.isEnabled():
184 nextUpdateDateTime = Preferences.getWebBrowser( 188 nextUpdateDateTime = Preferences.getWebBrowser("SafeBrowsingUpdateDateTime")
185 "SafeBrowsingUpdateDateTime")
186 if nextUpdateDateTime.isValid(): 189 if nextUpdateDateTime.isValid():
187 interval = ( 190 interval = (
188 QDateTime.currentDateTime().secsTo(nextUpdateDateTime) + 2 191 QDateTime.currentDateTime().secsTo(nextUpdateDateTime)
192 + 2
189 # 2 seconds extra wait time; interval in milliseconds 193 # 2 seconds extra wait time; interval in milliseconds
190 ) 194 )
191 195
192 if interval < 5: 196 if interval < 5:
193 interval = 5 197 interval = 5
194 # minimum 5 seconds interval 198 # minimum 5 seconds interval
195 else: 199 else:
196 interval = 5 200 interval = 5
197 # just wait 5 seconds 201 # just wait 5 seconds
198 self.__threatListsUpdateTimer.start(interval * 1000) 202 self.__threatListsUpdateTimer.start(interval * 1000)
199 else: 203 else:
200 if self.__threatListsUpdateTimer.isActive(): 204 if self.__threatListsUpdateTimer.isActive():
201 self.__threatListsUpdateTimer.stop() 205 self.__threatListsUpdateTimer.stop()
202 206
203 @pyqtSlot() 207 @pyqtSlot()
204 def __threatListsUpdateTimerTimeout(self): 208 def __threatListsUpdateTimerTimeout(self):
205 """ 209 """
206 Private slot to perform the auto update of the threat lists. 210 Private slot to perform the auto update of the threat lists.
207 """ 211 """
208 ok = False 212 ok = False
209 if self.isEnabled(): 213 if self.isEnabled():
210 self.__showNotificationMessage( 214 self.__showNotificationMessage(self.tr("Updating threat lists..."), 0)
211 self.tr("Updating threat lists..."), 0)
212 ok = self.updateHashPrefixCache()[0] 215 ok = self.updateHashPrefixCache()[0]
213 if ok: 216 if ok:
214 self.__showNotificationMessage( 217 self.__showNotificationMessage(self.tr("Updating threat lists done."))
215 self.tr("Updating threat lists done."))
216 else: 218 else:
217 self.__showNotificationMessage( 219 self.__showNotificationMessage(
218 self.tr("Updating threat lists failed."), 220 self.tr("Updating threat lists failed."), timeout=0
219 timeout=0) 221 )
220 222
221 if ok: 223 if ok:
222 nextUpdateDateTime = ( 224 nextUpdateDateTime = self.__apiClient.getFairUseDelayExpirationDateTime()
223 self.__apiClient.getFairUseDelayExpirationDateTime() 225 Preferences.setWebBrowser("SafeBrowsingUpdateDateTime", nextUpdateDateTime)
226 self.__threatListsUpdateTimer.start(
227 (QDateTime.currentDateTime().secsTo(nextUpdateDateTime) + 2) * 1000
224 ) 228 )
225 Preferences.setWebBrowser("SafeBrowsingUpdateDateTime",
226 nextUpdateDateTime)
227 self.__threatListsUpdateTimer.start(
228 (QDateTime.currentDateTime().secsTo(nextUpdateDateTime) + 2) *
229 1000)
230 # 2 seconds extra wait time; interval in milliseconds 229 # 2 seconds extra wait time; interval in milliseconds
231 else: 230 else:
232 Preferences.setWebBrowser("SafeBrowsingUpdateDateTime", 231 Preferences.setWebBrowser("SafeBrowsingUpdateDateTime", QDateTime())
233 QDateTime()) 232
234
235 def updateHashPrefixCache(self): 233 def updateHashPrefixCache(self):
236 """ 234 """
237 Public method to load or update the locally cached threat lists. 235 Public method to load or update the locally cached threat lists.
238 236
239 @return flag indicating success and an error message 237 @return flag indicating success and an error message
240 @rtype tuple of (bool, str) 238 @rtype tuple of (bool, str)
241 """ 239 """
242 if not self.isEnabled(): 240 if not self.isEnabled():
243 return False, self.tr("Safe Browsing is disabled.") 241 return False, self.tr("Safe Browsing is disabled.")
244 242
245 if not self.__apiClient.fairUseDelayExpired(): 243 if not self.__apiClient.fairUseDelayExpired():
246 return ( 244 return (
247 False, 245 False,
248 self.tr("The fair use wait period has not expired yet." 246 self.tr(
249 "Expiration will be at {0}.").format( 247 "The fair use wait period has not expired yet."
250 self.__apiClient.getFairUseDelayExpirationDateTime() 248 "Expiration will be at {0}."
251 .toString("yyyy-MM-dd, HH:mm:ss")) 249 ).format(
250 self.__apiClient.getFairUseDelayExpirationDateTime().toString(
251 "yyyy-MM-dd, HH:mm:ss"
252 )
253 ),
252 ) 254 )
253 255
254 self.__updatingThreatLists = True 256 self.__updatingThreatLists = True
255 ok = True 257 ok = True
256 errorMessage = "" 258 errorMessage = ""
257 259
258 # step 1: remove expired hashes 260 # step 1: remove expired hashes
259 self.__cache.cleanupFullHashes() 261 self.__cache.cleanupFullHashes()
260 QCoreApplication.processEvents() 262 QCoreApplication.processEvents()
261 263
262 # step 2: update threat lists 264 # step 2: update threat lists
263 threatListsForRemove = {} 265 threatListsForRemove = {}
264 for threatList, _clientState in self.__cache.getThreatLists(): 266 for threatList, _clientState in self.__cache.getThreatLists():
265 threatListsForRemove[repr(threatList)] = threatList 267 threatListsForRemove[repr(threatList)] = threatList
266 threatLists, error = self.__apiClient.getThreatLists() 268 threatLists, error = self.__apiClient.getThreatLists()
267 if error: 269 if error:
268 return False, error 270 return False, error
269 271
270 maximum = len(threatLists) 272 maximum = len(threatLists)
271 self.progressMessage.emit(self.tr("Updating threat lists"), maximum) 273 self.progressMessage.emit(self.tr("Updating threat lists"), maximum)
272 for current, entry in enumerate(threatLists, start=1): 274 for current, entry in enumerate(threatLists, start=1):
273 self.progress.emit(current) 275 self.progress.emit(current)
274 QCoreApplication.processEvents() 276 QCoreApplication.processEvents()
275 threatList = ThreatList.fromApiEntry(entry) 277 threatList = ThreatList.fromApiEntry(entry)
276 if ( 278 if self.__platforms is None or threatList.platformType in self.__platforms:
277 self.__platforms is None or
278 threatList.platformType in self.__platforms
279 ):
280 self.__cache.addThreatList(threatList) 279 self.__cache.addThreatList(threatList)
281 key = repr(threatList) 280 key = repr(threatList)
282 if key in threatListsForRemove: 281 if key in threatListsForRemove:
283 del threatListsForRemove[key] 282 del threatListsForRemove[key]
284 283
285 maximum = len(threatListsForRemove.values()) 284 maximum = len(threatListsForRemove.values())
286 self.progressMessage.emit(self.tr("Deleting obsolete threat lists"), 285 self.progressMessage.emit(self.tr("Deleting obsolete threat lists"), maximum)
287 maximum) 286 for current, threatList in enumerate(threatListsForRemove.values(), start=1):
288 for current, threatList in enumerate(
289 threatListsForRemove.values(), start=1
290 ):
291 self.progress.emit(current) 287 self.progress.emit(current)
292 QCoreApplication.processEvents() 288 QCoreApplication.processEvents()
293 self.__cache.deleteHashPrefixList(threatList) 289 self.__cache.deleteHashPrefixList(threatList)
294 self.__cache.deleteThreatList(threatList) 290 self.__cache.deleteThreatList(threatList)
295 del threatListsForRemove 291 del threatListsForRemove
296 292
297 # step 3: update threats 293 # step 3: update threats
298 threatLists = self.__cache.getThreatLists() 294 threatLists = self.__cache.getThreatLists()
299 clientStates = {} 295 clientStates = {}
300 for threatList, clientState in threatLists: 296 for threatList, clientState in threatLists:
301 clientStates[threatList.asTuple()] = clientState 297 clientStates[threatList.asTuple()] = clientState
302 threatsUpdateResponses, error = self.__apiClient.getThreatsUpdate( 298 threatsUpdateResponses, error = self.__apiClient.getThreatsUpdate(clientStates)
303 clientStates)
304 if error: 299 if error:
305 return False, error 300 return False, error
306 301
307 maximum = len(threatsUpdateResponses) 302 maximum = len(threatsUpdateResponses)
308 self.progressMessage.emit(self.tr("Updating hash prefixes"), maximum) 303 self.progressMessage.emit(self.tr("Updating hash prefixes"), maximum)
309 for current, response in enumerate(threatsUpdateResponses, start=1): 304 for current, response in enumerate(threatsUpdateResponses, start=1):
310 self.progress.emit(current) 305 self.progress.emit(current)
311 QCoreApplication.processEvents() 306 QCoreApplication.processEvents()
312 responseThreatList = ThreatList.fromApiEntry(response) 307 responseThreatList = ThreatList.fromApiEntry(response)
313 if response["responseType"] == "FULL_UPDATE": 308 if response["responseType"] == "FULL_UPDATE":
314 self.__cache.deleteHashPrefixList(responseThreatList) 309 self.__cache.deleteHashPrefixList(responseThreatList)
315 for removal in response.get("removals", []): 310 for removal in response.get("removals", []):
316 self.__cache.removeHashPrefixIndices( 311 self.__cache.removeHashPrefixIndices(
317 responseThreatList, removal["rawIndices"]["indices"]) 312 responseThreatList, removal["rawIndices"]["indices"]
313 )
318 QCoreApplication.processEvents() 314 QCoreApplication.processEvents()
319 for addition in response.get("additions", []): 315 for addition in response.get("additions", []):
320 hashPrefixList = HashPrefixList( 316 hashPrefixList = HashPrefixList(
321 addition["rawHashes"]["prefixSize"], 317 addition["rawHashes"]["prefixSize"],
322 base64.b64decode(addition["rawHashes"]["rawHashes"])) 318 base64.b64decode(addition["rawHashes"]["rawHashes"]),
323 self.__cache.populateHashPrefixList(responseThreatList, 319 )
324 hashPrefixList) 320 self.__cache.populateHashPrefixList(responseThreatList, hashPrefixList)
325 QCoreApplication.processEvents() 321 QCoreApplication.processEvents()
326 expectedChecksum = base64.b64decode(response["checksum"]["sha256"]) 322 expectedChecksum = base64.b64decode(response["checksum"]["sha256"])
327 if self.__verifyThreatListChecksum(responseThreatList, 323 if self.__verifyThreatListChecksum(responseThreatList, expectedChecksum):
328 expectedChecksum):
329 self.__cache.updateThreatListClientState( 324 self.__cache.updateThreatListClientState(
330 responseThreatList, response["newClientState"]) 325 responseThreatList, response["newClientState"]
326 )
331 else: 327 else:
332 ok = False 328 ok = False
333 errorMessage = self.tr( 329 errorMessage = self.tr(
334 "Local cache checksum does not match the server. Consider" 330 "Local cache checksum does not match the server. Consider"
335 " cleaning the cache. Threat update has been aborted.") 331 " cleaning the cache. Threat update has been aborted."
336 332 )
333
337 self.__updatingThreatLists = False 334 self.__updatingThreatLists = False
338 335
339 return ok, errorMessage 336 return ok, errorMessage
340 337
341 def isUpdatingThreatLists(self): 338 def isUpdatingThreatLists(self):
342 """ 339 """
343 Public method to check, if we are in the process of updating the 340 Public method to check, if we are in the process of updating the
344 threat lists. 341 threat lists.
345 342
346 @return flag indicating an update process is active 343 @return flag indicating an update process is active
347 @rtype bool 344 @rtype bool
348 """ 345 """
349 return self.__updatingThreatLists 346 return self.__updatingThreatLists
350 347
351 def __verifyThreatListChecksum(self, threatList, remoteChecksum): 348 def __verifyThreatListChecksum(self, threatList, remoteChecksum):
352 """ 349 """
353 Private method to verify the local checksum of a threat list with the 350 Private method to verify the local checksum of a threat list with the
354 checksum of the safe browsing server. 351 checksum of the safe browsing server.
355 352
356 @param threatList threat list to calculate checksum for 353 @param threatList threat list to calculate checksum for
357 @type ThreatList 354 @type ThreatList
358 @param remoteChecksum SHA256 checksum as reported by the Google server 355 @param remoteChecksum SHA256 checksum as reported by the Google server
359 @type bytes 356 @type bytes
360 @return flag indicating equality 357 @return flag indicating equality
361 @rtype bool 358 @rtype bool
362 """ 359 """
363 localChecksum = self.__cache.hashPrefixListChecksum(threatList) 360 localChecksum = self.__cache.hashPrefixListChecksum(threatList)
364 return remoteChecksum == localChecksum 361 return remoteChecksum == localChecksum
365 362
366 def fullCacheCleanup(self): 363 def fullCacheCleanup(self):
367 """ 364 """
368 Public method to clean up the cache completely. 365 Public method to clean up the cache completely.
369 """ 366 """
370 self.__cache.prepareCacheDb() 367 self.__cache.prepareCacheDb()
371 368
372 def showSafeBrowsingDialog(self): 369 def showSafeBrowsingDialog(self):
373 """ 370 """
374 Public slot to show the safe browsing management dialog. 371 Public slot to show the safe browsing management dialog.
375 """ 372 """
376 if self.__gsbDialog is None: 373 if self.__gsbDialog is None:
377 from WebBrowser.WebBrowserWindow import WebBrowserWindow 374 from WebBrowser.WebBrowserWindow import WebBrowserWindow
378 from .SafeBrowsingDialog import SafeBrowsingDialog 375 from .SafeBrowsingDialog import SafeBrowsingDialog
376
379 self.__gsbDialog = SafeBrowsingDialog( 377 self.__gsbDialog = SafeBrowsingDialog(
380 self, parent=WebBrowserWindow.mainWindow()) 378 self, parent=WebBrowserWindow.mainWindow()
381 379 )
380
382 self.__gsbDialog.show() 381 self.__gsbDialog.show()
383 382
384 def lookupUrl(self, url): 383 def lookupUrl(self, url):
385 """ 384 """
386 Public method to lookup an URL. 385 Public method to lookup an URL.
387 386
388 @param url URL to be checked 387 @param url URL to be checked
389 @type str or QUrl 388 @type str or QUrl
390 @return tuple containing the list of threat lists the URL was found in 389 @return tuple containing the list of threat lists the URL was found in
391 and an error message 390 and an error message
392 @rtype tuple of (list of ThreatList, str) 391 @rtype tuple of (list of ThreatList, str)
394 """ 393 """
395 if self.isEnabled(): 394 if self.isEnabled():
396 if self.__useLookupApi: 395 if self.__useLookupApi:
397 if isinstance(url, str): 396 if isinstance(url, str):
398 url = QUrl(url.strip()) 397 url = QUrl(url.strip())
399 398
400 if url.isEmpty(): 399 if url.isEmpty():
401 raise ValueError("Empty URL given.") 400 raise ValueError("Empty URL given.")
402 401
403 listNames, error = self.__apiClient.lookupUrl( 402 listNames, error = self.__apiClient.lookupUrl(url, self.__platforms)
404 url, self.__platforms)
405 return listNames, error 403 return listNames, error
406 else: 404 else:
407 if isinstance(url, QUrl): 405 if isinstance(url, QUrl):
408 urlStr = url.toString().strip() 406 urlStr = url.toString().strip()
409 else: 407 else:
410 urlStr = url.strip() 408 urlStr = url.strip()
411 409
412 if not urlStr: 410 if not urlStr:
413 raise ValueError("Empty URL given.") 411 raise ValueError("Empty URL given.")
414 412
415 urlHashes = SafeBrowsingUrl(urlStr).hashes() 413 urlHashes = SafeBrowsingUrl(urlStr).hashes()
416 listNames = self.__lookupHashes(urlHashes) 414 listNames = self.__lookupHashes(urlHashes)
417 415
418 return listNames, "" 416 return listNames, ""
419 417
420 return None, "" 418 return None, ""
421 419
422 def __lookupHashes(self, fullHashes): 420 def __lookupHashes(self, fullHashes):
423 """ 421 """
424 Private method to lookup the given hashes. 422 Private method to lookup the given hashes.
425 423
426 @param fullHashes list of hashes to lookup 424 @param fullHashes list of hashes to lookup
427 @type list of bytes 425 @type list of bytes
428 @return names of threat lists hashes were found in 426 @return names of threat lists hashes were found in
429 @rtype list of ThreatList 427 @rtype list of ThreatList
430 """ 428 """
431 fullHashes = list(fullHashes) 429 fullHashes = list(fullHashes)
432 cues = [fh[:4].hex() for fh in fullHashes] 430 cues = [fh[:4].hex() for fh in fullHashes]
433 result = [] 431 result = []
434 432
435 matchingPrefixes = {} 433 matchingPrefixes = {}
436 matchingFullHashes = set() 434 matchingFullHashes = set()
437 isPotentialThreat = False 435 isPotentialThreat = False
438 # Lookup hash prefixes which match full URL hash 436 # Lookup hash prefixes which match full URL hash
439 for _threatList, hashPrefix, negativeCacheExpired in ( 437 for (
440 self.__cache.lookupHashPrefix(cues) 438 _threatList,
441 ): 439 hashPrefix,
440 negativeCacheExpired,
441 ) in self.__cache.lookupHashPrefix(cues):
442 for fullHash in fullHashes: 442 for fullHash in fullHashes:
443 if fullHash.startswith(hashPrefix): 443 if fullHash.startswith(hashPrefix):
444 isPotentialThreat = True 444 isPotentialThreat = True
445 # consider hash prefix negative cache as expired if it 445 # consider hash prefix negative cache as expired if it
446 # is expired in at least one threat list 446 # is expired in at least one threat list
447 matchingPrefixes[hashPrefix] = matchingPrefixes.get( 447 matchingPrefixes[hashPrefix] = (
448 hashPrefix, False) or negativeCacheExpired 448 matchingPrefixes.get(hashPrefix, False) or negativeCacheExpired
449 )
449 matchingFullHashes.add(fullHash) 450 matchingFullHashes.add(fullHash)
450 451
451 # if none matches, url hash is clear 452 # if none matches, url hash is clear
452 if not isPotentialThreat: 453 if not isPotentialThreat:
453 return [] 454 return []
454 455
455 # if there is non-expired full hash, URL is blacklisted 456 # if there is non-expired full hash, URL is blacklisted
456 matchingExpiredThreatLists = set() 457 matchingExpiredThreatLists = set()
457 for threatList, hasExpired in self.__cache.lookupFullHashes( 458 for threatList, hasExpired in self.__cache.lookupFullHashes(matchingFullHashes):
458 matchingFullHashes):
459 if hasExpired: 459 if hasExpired:
460 matchingExpiredThreatLists.add(threatList) 460 matchingExpiredThreatLists.add(threatList)
461 else: 461 else:
462 result.append(threatList) 462 result.append(threatList)
463 if result: 463 if result:
464 return result 464 return result
465 465
466 # If there are no matching expired full hash entries and negative 466 # If there are no matching expired full hash entries and negative
467 # cache is still current for all prefixes, consider it safe. 467 # cache is still current for all prefixes, consider it safe.
468 if ( 468 if (
469 len(matchingExpiredThreatLists) == 0 and 469 len(matchingExpiredThreatLists) == 0
470 sum(map(int, matchingPrefixes.values())) == 0 470 and sum(map(int, matchingPrefixes.values())) == 0
471 ): 471 ):
472 return [] 472 return []
473 473
474 # Now it can be assumed that there are expired matching full hash 474 # Now it can be assumed that there are expired matching full hash
475 # entries and/or cache prefix entries with expired negative cache. 475 # entries and/or cache prefix entries with expired negative cache.
476 # Both require full hash synchronization. 476 # Both require full hash synchronization.
477 self.__syncFullHashes(matchingPrefixes.keys()) 477 self.__syncFullHashes(matchingPrefixes.keys())
478 478
479 # Now repeat full hash lookup 479 # Now repeat full hash lookup
480 for threatList, hasExpired in self.__cache.lookupFullHashes( 480 for threatList, hasExpired in self.__cache.lookupFullHashes(matchingFullHashes):
481 matchingFullHashes):
482 if not hasExpired: 481 if not hasExpired:
483 result.append(threatList) 482 result.append(threatList)
484 483
485 return result 484 return result
486 485
487 def __syncFullHashes(self, hashPrefixes): 486 def __syncFullHashes(self, hashPrefixes):
488 """ 487 """
489 Private method to download full hashes matching given prefixes. 488 Private method to download full hashes matching given prefixes.
490 489
491 This also updates the cache expiration timestamps. 490 This also updates the cache expiration timestamps.
492 491
493 @param hashPrefixes list of hash prefixes to get full hashes for 492 @param hashPrefixes list of hash prefixes to get full hashes for
494 @type list of bytes 493 @type list of bytes
495 """ 494 """
496 threatLists = self.__cache.getThreatLists() 495 threatLists = self.__cache.getThreatLists()
497 clientStates = {} 496 clientStates = {}
498 for threatList, clientState in threatLists: 497 for threatList, clientState in threatLists:
499 clientStates[threatList.asTuple()] = clientState 498 clientStates[threatList.asTuple()] = clientState
500 499
501 fullHashResponses = self.__apiClient.getFullHashes( 500 fullHashResponses = self.__apiClient.getFullHashes(hashPrefixes, clientStates)
502 hashPrefixes, clientStates) 501
503
504 # update negative cache for each hash prefix 502 # update negative cache for each hash prefix
505 # store full hash with positive cache bumped up 503 # store full hash with positive cache bumped up
506 for match in fullHashResponses["matches"]: 504 for match in fullHashResponses["matches"]:
507 threatList = ThreatList.fromApiEntry(match) 505 threatList = ThreatList.fromApiEntry(match)
508 hashValue = base64.b64decode(match["threat"]["hash"]) 506 hashValue = base64.b64decode(match["threat"]["hash"])
513 value = base64.b64decode(metadata["value"]) 511 value = base64.b64decode(metadata["value"])
514 if key == b"malware_threat_type": 512 if key == b"malware_threat_type":
515 malwareThreatType = value 513 malwareThreatType = value
516 if not isinstance(malwareThreatType, str): 514 if not isinstance(malwareThreatType, str):
517 malwareThreatType = malwareThreatType.decode() 515 malwareThreatType = malwareThreatType.decode()
518 self.__cache.storeFullHash(threatList, hashValue, cacheDuration, 516 self.__cache.storeFullHash(
519 malwareThreatType) 517 threatList, hashValue, cacheDuration, malwareThreatType
520 518 )
519
521 negativeCacheDuration = int( 520 negativeCacheDuration = int(
522 fullHashResponses["negativeCacheDuration"].rstrip("s")) 521 fullHashResponses["negativeCacheDuration"].rstrip("s")
522 )
523 for prefixValue in hashPrefixes: 523 for prefixValue in hashPrefixes:
524 for threatList, _clientState in threatLists: 524 for threatList, _clientState in threatLists:
525 self.__cache.updateHashPrefixExpiration( 525 self.__cache.updateHashPrefixExpiration(
526 threatList, prefixValue, negativeCacheDuration) 526 threatList, prefixValue, negativeCacheDuration
527 527 )
528
528 @classmethod 529 @classmethod
529 def getIgnoreSchemes(cls): 530 def getIgnoreSchemes(cls):
530 """ 531 """
531 Class method to get the schemes not to be checked. 532 Class method to get the schemes not to be checked.
532 533
533 @return list of schemes to be ignored 534 @return list of schemes to be ignored
534 @rtype list of str 535 @rtype list of str
535 """ 536 """
536 return [ 537 return [
537 "about", 538 "about",
540 "qthelp", 541 "qthelp",
541 "chrome", 542 "chrome",
542 "abp", 543 "abp",
543 "file", 544 "file",
544 ] 545 ]
545 546
546 def getThreatMessage(self, threatType): 547 def getThreatMessage(self, threatType):
547 """ 548 """
548 Public method to get a warning message for the given threat type. 549 Public method to get a warning message for the given threat type.
549 550
550 @param threatType threat type to get the message for 551 @param threatType threat type to get the message for
551 @type str 552 @type str
552 @return threat message 553 @return threat message
553 @rtype str 554 @rtype str
554 """ 555 """
555 msg = ( 556 msg = self.__apiClient.getThreatMessage(threatType) if self.__apiClient else ""
556 self.__apiClient.getThreatMessage(threatType) 557
557 if self.__apiClient else
558 ""
559 )
560
561 return msg 558 return msg
562 559
563 def getThreatMessages(self, threatLists): 560 def getThreatMessages(self, threatLists):
564 """ 561 """
565 Public method to get threat messages for the given threats. 562 Public method to get threat messages for the given threats.
566 563
567 @param threatLists list of threat lists to get a message for 564 @param threatLists list of threat lists to get a message for
568 @type list of ThreatList 565 @type list of ThreatList
569 @return list of threat messages, one per unique threat type 566 @return list of threat messages, one per unique threat type
570 @rtype list of str 567 @rtype list of str
571 """ 568 """
572 threatTypes = set() 569 threatTypes = set()
573 for threatList in threatLists: 570 for threatList in threatLists:
574 threatTypes.add(threatList.threatType) 571 threatTypes.add(threatList.threatType)
575 572
576 messages = [] 573 messages = []
577 if self.__apiClient: 574 if self.__apiClient:
578 for threatType in sorted(threatTypes): 575 for threatType in sorted(threatTypes):
579 msg = self.__apiClient.getThreatMessage(threatType) 576 msg = self.__apiClient.getThreatMessage(threatType)
580 messages.append(msg) 577 messages.append(msg)
581 578
582 return messages 579 return messages
583 580
584 def getThreatType(self, threatList): 581 def getThreatType(self, threatList):
585 """ 582 """
586 Public method to get a display string for a given threat type. 583 Public method to get a display string for a given threat type.
587 584
588 @param threatList threat list to get display string for 585 @param threatList threat list to get display string for
589 @type str 586 @type str
590 @return display string 587 @return display string
591 @rtype str 588 @rtype str
592 """ 589 """
593 displayString = "" 590 displayString = ""
594 if self.__apiClient: 591 if self.__apiClient:
595 displayString = self.__apiClient.getThreatType( 592 displayString = self.__apiClient.getThreatType(threatList.threatType)
596 threatList.threatType)
597 return displayString 593 return displayString
598 594
599 def getPlatformString(self, platformType): 595 def getPlatformString(self, platformType):
600 """ 596 """
601 Public method to get the platform string for a given platform type. 597 Public method to get the platform string for a given platform type.
602 598
603 @param platformType platform type as defined in the v4 API 599 @param platformType platform type as defined in the v4 API
604 @type str 600 @type str
605 @return platform string 601 @return platform string
606 @rtype str 602 @rtype str
607 """ 603 """
608 if self.__apiClient: 604 if self.__apiClient:
609 return self.__apiClient.getPlatformString(platformType) 605 return self.__apiClient.getPlatformString(platformType)
610 else: 606 else:
611 return "" 607 return ""
612 608
613 def getThreatEntryString(self, threatEntry): 609 def getThreatEntryString(self, threatEntry):
614 """ 610 """
615 Public method to get the threat entry string. 611 Public method to get the threat entry string.
616 612
617 @param threatEntry threat entry type as defined in the v4 API 613 @param threatEntry threat entry type as defined in the v4 API
618 @type str 614 @type str
619 @return threat entry string 615 @return threat entry string
620 @rtype str 616 @rtype str
621 """ 617 """

eric ide

mercurial