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"]) |