225 from .SafeBrowsingDialog import SafeBrowsingDialog |
254 from .SafeBrowsingDialog import SafeBrowsingDialog |
226 self.__gsbDialog = SafeBrowsingDialog( |
255 self.__gsbDialog = SafeBrowsingDialog( |
227 self, parent=WebBrowserWindow.mainWindow()) |
256 self, parent=WebBrowserWindow.mainWindow()) |
228 |
257 |
229 self.__gsbDialog.show() |
258 self.__gsbDialog.show() |
|
259 |
|
260 def lookupUrl(self, url): |
|
261 """ |
|
262 Public method to lookup an URL. |
|
263 |
|
264 @param url URL to be checked |
|
265 @type str or QUrl |
|
266 @return list of threat lists the URL was found in |
|
267 @rtype list of ThreatList |
|
268 @exception ValueError raised for an invalid URL |
|
269 """ |
|
270 if self.__enabled: |
|
271 if isinstance(url, QUrl): |
|
272 urlStr = url.toString().strip() |
|
273 else: |
|
274 urlStr = url.strip() |
|
275 |
|
276 if not urlStr: |
|
277 raise ValueError("Empty URL given.") |
|
278 |
|
279 urlHashes = SafeBrowsingUrl(urlStr).hashes() |
|
280 listNames = self.__lookupHashes(urlHashes) |
|
281 if listNames: |
|
282 return listNames |
|
283 |
|
284 return None |
|
285 |
|
286 def __lookupHashes(self, fullHashes): |
|
287 """ |
|
288 Private method to lookup the given hashes. |
|
289 |
|
290 @param fullHashes list of hashes to lookup |
|
291 @type list of bytes |
|
292 @return names of threat lists hashes were found in |
|
293 @rtype list of ThreatList |
|
294 """ |
|
295 fullHashes = list(fullHashes) |
|
296 cues = [toHex(fh[:4]) for fh in fullHashes] |
|
297 result = [] |
|
298 |
|
299 matchingPrefixes = {} |
|
300 matchingFullHashes = set() |
|
301 isPotentialThreat = False |
|
302 # Lookup hash prefixes which match full URL hash |
|
303 for threatList, hashPrefix, negativeCacheExpired in \ |
|
304 self.__cache.lookupHashPrefix(cues): |
|
305 for fullHash in fullHashes: |
|
306 if fullHash.startswith(hashPrefix): |
|
307 isPotentialThreat = True |
|
308 # consider hash prefix negative cache as expired if it |
|
309 # is expired in at least one threat list |
|
310 matchingPrefixes[hashPrefix] = matchingPrefixes.get( |
|
311 hashPrefix, False) or negativeCacheExpired |
|
312 matchingFullHashes.add(fullHash) |
|
313 |
|
314 # if none matches, url hash is clear |
|
315 if not isPotentialThreat: |
|
316 return [] |
|
317 |
|
318 # if there is non-expired full hash, URL is blacklisted |
|
319 matchingExpiredThreatLists = set() |
|
320 for threatList, hasExpired in self.__cache.lookupFullHashes( |
|
321 matchingFullHashes): |
|
322 if hasExpired: |
|
323 matchingExpiredThreatLists.add(threatList) |
|
324 else: |
|
325 result.append(threatList) |
|
326 if result: |
|
327 return result |
|
328 |
|
329 # If there are no matching expired full hash entries and negative |
|
330 # cache is still current for all prefixes, consider it safe. |
|
331 if len(matchingExpiredThreatLists) == 0 and \ |
|
332 sum(map(int, matchingPrefixes.values())) == 0: |
|
333 return [] |
|
334 |
|
335 # Now it can be assumed that there are expired matching full hash |
|
336 # entries and/or cache prefix entries with expired negative cache. |
|
337 # Both require full hash synchronization. |
|
338 self.__syncFullHashes(matchingPrefixes.keys()) |
|
339 |
|
340 # Now repeat full hash lookup |
|
341 for threatList, hasExpired in self.__cache.lookupFullHashes( |
|
342 matchingFullHashes): |
|
343 if not hasExpired: |
|
344 result.append(threatList) |
|
345 |
|
346 return result |
|
347 |
|
348 def __syncFullHashes(self, hashPrefixes): |
|
349 """ |
|
350 Private method to download full hashes matching given prefixes. |
|
351 |
|
352 This also updates the cache expiration timestamps. |
|
353 |
|
354 @param hashPrefixes list of hash prefixes to get full hashes for |
|
355 @type list of bytes |
|
356 """ |
|
357 threatLists = self.__cache.getThreatLists() |
|
358 clientStates = {} |
|
359 for threatList, clientState in threatLists: |
|
360 clientStates[threatList.asTuple()] = clientState |
|
361 |
|
362 fullHashResponses = self.__apiClient.getFullHashes( |
|
363 hashPrefixes, clientStates) |
|
364 |
|
365 # update negative cache for each hash prefix |
|
366 # store full hash with positive cache bumped up |
|
367 for match in fullHashResponses["matches"]: |
|
368 threatList = ThreatList.fromApiEntry(match) |
|
369 hashValue = base64.b64decode(match["threat"]["hash"]) |
|
370 cacheDuration = int(match["cacheDuration"].rstrip("s")) |
|
371 malwareThreatType = None |
|
372 for metadata in match["threatEntryMetadata"].get("entries", []): |
|
373 key = base64.b64decode(metadata["key"]) |
|
374 value = base64.b64decode(metadata["value"]) |
|
375 if key == b"malware_threat_type": |
|
376 malwareThreatType = value |
|
377 if not isinstance(malwareThreatType, str): |
|
378 malwareThreatType = malwareThreatType.decode() |
|
379 self.__cache.storeFullHash(threatList, hashValue, cacheDuration, |
|
380 malwareThreatType) |
|
381 |
|
382 negativeCacheDuration = int( |
|
383 fullHashResponses["negativeCacheDuration"].rstrip("s")) |
|
384 for prefixValue in hashPrefixes: |
|
385 for threatList, clientState in threatLists: |
|
386 self.__cache.updateHashPrefixExpiration( |
|
387 threatList, prefixValue, negativeCacheDuration) |
|
388 |
|
389 @classmethod |
|
390 def getIgnoreSchemes(cls): |
|
391 """ |
|
392 Class method to get the schemes not to be checked. |
|
393 |
|
394 @return list of schemes to be ignored |
|
395 @rtype list of str |
|
396 """ |
|
397 return [ |
|
398 "about", |
|
399 "eric", |
|
400 "qrc", |
|
401 "qthelp", |
|
402 "chrome", |
|
403 "abp", |
|
404 "file", |
|
405 ] |
|
406 |
|
407 def getThreatMessage(self, threatType): |
|
408 """ |
|
409 Public method to get a warning message for the given threat type. |
|
410 |
|
411 @param threatType threat type to get the message for |
|
412 @type str |
|
413 @return threat message |
|
414 @rtype str |
|
415 """ |
|
416 if self.__apiClient: |
|
417 msg = self.__apiClient.getThreatMessage(threatType) |
|
418 else: |
|
419 msg = "" |
|
420 |
|
421 return msg |
|
422 |
|
423 def getThreatMessages(self, threatLists): |
|
424 """ |
|
425 Public method to get threat messages for the given threats. |
|
426 |
|
427 @param threatLists list of threat lists to get a message for |
|
428 @type list of ThreatList |
|
429 @return list of threat messages, one per unique threat type |
|
430 @rtype list of str |
|
431 """ |
|
432 threatTypes = set() |
|
433 for threatList in threatLists: |
|
434 threatTypes.add(threatList.threatType) |
|
435 |
|
436 messages = [] |
|
437 if self.__apiClient: |
|
438 for threatType in sorted(threatTypes): |
|
439 msg = self.__apiClient.getThreatMessage(threatType) |
|
440 messages.append(msg) |
|
441 |
|
442 return messages |
|
443 |
|
444 def getThreatType(self, threatList): |
|
445 """ |
|
446 Public method to get a display string for a given threat type. |
|
447 |
|
448 @param threatList threat list to get display string for |
|
449 @type str |
|
450 @return display string |
|
451 @rtype str |
|
452 """ |
|
453 displayString = "" |
|
454 if self.__apiClient: |
|
455 displayString = self.__apiClient.getThreatType( |
|
456 threatList.threatType) |
|
457 return displayString |
|
458 |
|
459 def getPlatformString(self, platformType): |
|
460 """ |
|
461 Public method to get the platform string for a given platform type. |
|
462 |
|
463 @param platformType platform type as defined in the v4 API |
|
464 @type str |
|
465 @return platform string |
|
466 @rtype str |
|
467 """ |
|
468 if self.__apiClient: |
|
469 return self.__apiClient.getPlatformString(platformType) |
|
470 else: |
|
471 return "" |
|
472 |
|
473 def getThreatEntryString(self, threatEntry): |
|
474 """ |
|
475 Public method to get the threat entry string. |
|
476 |
|
477 @param threatEntry threat entry type as defined in the v4 API |
|
478 @type str |
|
479 @return threat entry string |
|
480 @rtype str |
|
481 """ |
|
482 if self.__apiClient: |
|
483 return self.__apiClient.getThreatEntryString(threatEntry) |
|
484 else: |
|
485 return "" |