60 self.__lookupApiCache = {} |
60 self.__lookupApiCache = {} |
61 # Temporary cache used by the lookup API (v4) |
61 # Temporary cache used by the lookup API (v4) |
62 # key: URL as string |
62 # key: URL as string |
63 # value: dictionary with these entries: |
63 # value: dictionary with these entries: |
64 # "validUntil": (QDateTime) |
64 # "validUntil": (QDateTime) |
65 # "threatInfo": (ThreatList) |
65 # "threatInfo": (list of ThreatList) |
66 |
66 |
67 def setApiKey(self, apiKey): |
67 def setApiKey(self, apiKey): |
68 """ |
68 """ |
69 Public method to set the API key. |
69 Public method to set the API key. |
70 |
70 |
75 |
75 |
76 def getThreatLists(self): |
76 def getThreatLists(self): |
77 """ |
77 """ |
78 Public method to retrieve all available threat lists. |
78 Public method to retrieve all available threat lists. |
79 |
79 |
80 @return list of threat lists |
80 @return tuple containing list of threat lists and an error message |
81 @rtype list of dict containing 'threatType', 'platformType' and |
81 @rtype tuple of (list of dict containing 'threatType', 'platformType' |
82 'threatEntryType' |
82 and 'threatEntryType', bool) |
83 """ |
83 """ |
84 url = QUrl(self.GsbUrlTemplate.format("threatLists", self.__apiKey)) |
84 url = QUrl(self.GsbUrlTemplate.format("threatLists", self.__apiKey)) |
85 req = QNetworkRequest(url) |
85 req = QNetworkRequest(url) |
86 reply = WebBrowserWindow.networkManager().get(req) |
86 reply = WebBrowserWindow.networkManager().get(req) |
87 |
87 |
88 while reply.isRunning(): |
88 while reply.isRunning(): |
89 QCoreApplication.processEvents(QEventLoop.AllEvents, 200) |
89 QCoreApplication.processEvents(QEventLoop.AllEvents, 200) |
90 # max. 200 ms processing |
90 # max. 200 ms processing |
91 |
91 |
92 res = None |
92 res = None |
|
93 error = "" |
93 if reply.error() != QNetworkReply.NoError: |
94 if reply.error() != QNetworkReply.NoError: |
94 self.networkError.emit(reply.errorString()) |
95 error = reply.errorString() |
|
96 self.networkError.emit(error) |
95 else: |
97 else: |
96 result = self.__extractData(reply) |
98 result = self.__extractData(reply) |
97 res = result["threatLists"] |
99 res = result["threatLists"] |
98 |
100 |
99 reply.deleteLater() |
101 reply.deleteLater() |
100 return res |
102 return res, error |
101 |
103 |
102 ####################################################################### |
104 ####################################################################### |
103 ## Methods below implement the 'Update API (v4)' |
105 ## Methods below implement the 'Update API (v4)' |
104 ####################################################################### |
106 ####################################################################### |
105 |
107 |
108 Public method to fetch hash prefix updates for the given threat list. |
110 Public method to fetch hash prefix updates for the given threat list. |
109 |
111 |
110 @param clientStates dictionary of client states with keys like |
112 @param clientStates dictionary of client states with keys like |
111 (threatType, platformType, threatEntryType) |
113 (threatType, platformType, threatEntryType) |
112 @type dict |
114 @type dict |
113 @return list of threat updates |
115 @return tuple containing the list of threat updates and an error |
114 @rtype list of dict |
116 message |
|
117 @rtype tuple of (list of dict, bool) |
115 """ |
118 """ |
116 requestBody = { |
119 requestBody = { |
117 "client": { |
120 "client": { |
118 "clientId": self.ClientId, |
121 "clientId": self.ClientId, |
119 "clientVersion": self.ClientVersion, |
122 "clientVersion": self.ClientVersion, |
145 while reply.isRunning(): |
148 while reply.isRunning(): |
146 QCoreApplication.processEvents(QEventLoop.AllEvents, 200) |
149 QCoreApplication.processEvents(QEventLoop.AllEvents, 200) |
147 # max. 200 ms processing |
150 # max. 200 ms processing |
148 |
151 |
149 res = None |
152 res = None |
|
153 error = "" |
150 if reply.error() != QNetworkReply.NoError: |
154 if reply.error() != QNetworkReply.NoError: |
151 self.networkError.emit(reply.errorString()) |
155 error = reply.errorString() |
|
156 self.networkError.emit(error) |
152 else: |
157 else: |
153 result = self.__extractData(reply) |
158 result = self.__extractData(reply) |
154 res = result["listUpdateResponses"] |
159 res = result["listUpdateResponses"] |
155 |
160 |
156 reply.deleteLater() |
161 reply.deleteLater() |
157 return res |
162 return res, error |
158 |
163 |
159 def getFullHashes(self, prefixes, clientState): |
164 def getFullHashes(self, prefixes, clientState): |
160 """ |
165 """ |
161 Public method to find full hashes matching hash prefixes. |
166 Public method to find full hashes matching hash prefixes. |
162 |
167 |
210 |
215 |
211 while reply.isRunning(): |
216 while reply.isRunning(): |
212 QCoreApplication.processEvents(QEventLoop.AllEvents, 200) |
217 QCoreApplication.processEvents(QEventLoop.AllEvents, 200) |
213 # max. 200 ms processing |
218 # max. 200 ms processing |
214 |
219 |
215 res = None |
220 res = [] |
216 if reply.error() != QNetworkReply.NoError: |
221 if reply.error() != QNetworkReply.NoError: |
217 self.networkError.emit(reply.errorString()) |
222 self.networkError.emit(reply.errorString()) |
218 else: |
223 else: |
219 res = self.__extractData(reply) |
224 res = self.__extractData(reply) |
220 |
225 |
279 |
284 |
280 @param url URL to be checked |
285 @param url URL to be checked |
281 @type QUrl |
286 @type QUrl |
282 @param platforms list of platform types to check against |
287 @param platforms list of platform types to check against |
283 @type list of str |
288 @type list of str |
284 @return list of threat list info objects |
289 @return tuple containing the list of threat list info objects and |
285 @rtype list of ThreatList |
290 an error message |
286 """ |
291 @rtype tuple of (list of ThreatList, str) |
|
292 """ |
|
293 error = "" |
|
294 |
287 # sanitize the URL by removing user info and query data |
295 # sanitize the URL by removing user info and query data |
288 url = url.adjusted( |
296 url = url.adjusted( |
289 QUrl.RemoveUserInfo | QUrl.RemoveQuery | QUrl.RemoveFragment |
297 QUrl.RemoveUserInfo | QUrl.RemoveQuery | QUrl.RemoveFragment |
290 ) |
298 ) |
291 urlStr = url.toString() |
299 urlStr = url.toString() |
|
300 |
|
301 # check the local cache first |
292 if urlStr in self.__lookupApiCache: |
302 if urlStr in self.__lookupApiCache: |
293 if self.__lookupApiCache[urlStr]["validUntil"] > \ |
303 if self.__lookupApiCache[urlStr]["validUntil"] > \ |
294 QDateTime.currentDateTime(): |
304 QDateTime.currentDateTime(): |
295 # cached entry is still valid |
305 # cached entry is still valid |
296 return self.__lookupApiCache[urlStr]["threatInfo"] |
306 return self.__lookupApiCache[urlStr]["threatInfo"], error |
297 else: |
307 else: |
298 del self.__lookupApiCache[urlStr] |
308 del self.__lookupApiCache[urlStr] |
299 |
309 |
|
310 # no valid entry found, ask the safe browsing server |
300 requestBody = { |
311 requestBody = { |
301 "client": { |
312 "client": { |
302 "clientId": self.ClientId, |
313 "clientId": self.ClientId, |
303 "clientVersion": self.ClientVersion, |
314 "clientVersion": self.ClientVersion, |
304 }, |
315 }, |
305 "threatInfo": { |
316 "threatInfo": { |
306 "threatTypes": [ |
317 "threatTypes": SafeBrowsingAPIClient.definedThreatTypes(), |
307 "MALWARE", "SOCIAL_ENGINEERING", "UNWANTED_SOFTWARE", |
|
308 "POTENTIALLY_HARMFUL_APPLICATION", |
|
309 ], |
|
310 "platformTypes": platforms, |
318 "platformTypes": platforms, |
311 "threatEntryTypes": ["URL", "EXECUTABLE"], |
319 "threatEntryTypes": |
|
320 SafeBrowsingAPIClient.definedThreatEntryTypes(), |
312 "threatEntries": [ |
321 "threatEntries": [ |
313 {"url": urlStr}, |
322 {"url": urlStr}, |
314 ], |
323 ], |
315 }, |
324 }, |
316 } |
325 } |
326 QCoreApplication.processEvents(QEventLoop.AllEvents, 200) |
335 QCoreApplication.processEvents(QEventLoop.AllEvents, 200) |
327 # max. 200 ms processing |
336 # max. 200 ms processing |
328 |
337 |
329 threats = [] |
338 threats = [] |
330 if reply.error() != QNetworkReply.NoError: |
339 if reply.error() != QNetworkReply.NoError: |
331 self.networkError.emit(reply.errorString()) |
340 error = reply.errorString() |
|
341 self.networkError.emit(error) |
332 else: |
342 else: |
333 res = json.loads(str(reply.readAll(), "utf-8")) |
343 res = json.loads(str(reply.readAll(), "utf-8")) |
334 if res and "matches" in res: |
344 if res and "matches" in res: |
335 cacheDuration = 0 |
345 cacheDuration = 0 |
336 for match in res["matches"]: |
346 for match in res["matches"]: |
353 "validUntil": validUntil, |
363 "validUntil": validUntil, |
354 "threatInfo": threats |
364 "threatInfo": threats |
355 } |
365 } |
356 |
366 |
357 reply.deleteLater() |
367 reply.deleteLater() |
358 return threats |
368 return threats, error |
359 |
369 |
360 ####################################################################### |
370 ####################################################################### |
361 ## Methods below implement global (class wide) functionality |
371 ## Methods below implement global (class wide) functionality |
362 ####################################################################### |
372 ####################################################################### |
363 |
373 |
447 "SafeBrowsingAPI", "Unknown Threat") |
457 "SafeBrowsingAPI", "Unknown Threat") |
448 |
458 |
449 return displayString |
459 return displayString |
450 |
460 |
451 @classmethod |
461 @classmethod |
|
462 def definedThreatTypes(cls): |
|
463 """ |
|
464 Class method to get all threat types defined in API v4. |
|
465 |
|
466 @return list of defined threat types |
|
467 @rtype list of str |
|
468 """ |
|
469 return [ |
|
470 "THREAT_TYPE_UNSPECIFIED", "MALWARE", "SOCIAL_ENGINEERING", |
|
471 "UNWANTED_SOFTWARE", "POTENTIALLY_HARMFUL_APPLICATION", |
|
472 ] |
|
473 |
|
474 @classmethod |
|
475 def getThreatEntryString(cls, threatEntry): |
|
476 """ |
|
477 Class method to get the threat entry string. |
|
478 |
|
479 @param threatEntry threat entry type as defined in the v4 API |
|
480 @type str |
|
481 @return threat entry string |
|
482 @rtype str |
|
483 """ |
|
484 if threatEntry == "URL": |
|
485 return "URL" |
|
486 elif threatEntry == "EXECUTABLE": |
|
487 return QCoreApplication.translate( |
|
488 "SafeBrowsingAPI", "executable program") |
|
489 else: |
|
490 return QCoreApplication.translate( |
|
491 "SafeBrowsingAPI", "unknown type") |
|
492 |
|
493 @classmethod |
|
494 def definedThreatEntryTypes(cls): |
|
495 """ |
|
496 Class method to get all threat entry types defined in API v4. |
|
497 |
|
498 @return list of all defined threat entry types |
|
499 @rtype list of str |
|
500 """ |
|
501 return [ |
|
502 "THREAT_ENTRY_TYPE_UNSPECIFIED", "URL", "EXECUTABLE", |
|
503 ] |
|
504 |
|
505 @classmethod |
452 def getPlatformString(cls, platformType): |
506 def getPlatformString(cls, platformType): |
453 """ |
507 """ |
454 Class method to get the platform string for a given platform type. |
508 Class method to get the platform string for a given platform type. |
455 |
509 |
456 @param platformType platform type as defined in the v4 API |
510 @param platformType platform type as defined in the v4 API |
478 else: |
532 else: |
479 return QCoreApplication.translate( |
533 return QCoreApplication.translate( |
480 "SafeBrowsingAPI", "unknown platform") |
534 "SafeBrowsingAPI", "unknown platform") |
481 |
535 |
482 @classmethod |
536 @classmethod |
483 def getThreatEntryString(cls, threatEntry): |
|
484 """ |
|
485 Class method to get the threat entry string. |
|
486 |
|
487 @param threatEntry threat entry type as defined in the v4 API |
|
488 @type str |
|
489 @return threat entry string |
|
490 @rtype str |
|
491 """ |
|
492 if threatEntry == "URL": |
|
493 return "URL" |
|
494 elif threatEntry == "EXECUTABLE": |
|
495 return QCoreApplication.translate( |
|
496 "SafeBrowsingAPI", "executable program") |
|
497 else: |
|
498 return QCoreApplication.translate( |
|
499 "SafeBrowsingAPI", "unknown type") |
|
500 |
|
501 @classmethod |
|
502 def getPlatformTypes(cls, platform): |
537 def getPlatformTypes(cls, platform): |
503 """ |
538 """ |
504 Class method to get the platform types for a given platform. |
539 Class method to get the platform types for a given platform. |
505 |
540 |
506 @param platform platform string |
541 @param platform platform string |
521 platformTypes.append("OSX") |
556 platformTypes.append("OSX") |
522 else: |
557 else: |
523 raise ValueError("Unsupported platform") |
558 raise ValueError("Unsupported platform") |
524 |
559 |
525 return platformTypes |
560 return platformTypes |
|
561 |
|
562 @classmethod |
|
563 def definedPlatformTypes(cls): |
|
564 """ |
|
565 Class method to get all platform types defined in API v4. |
|
566 |
|
567 @return list of all defined platform types |
|
568 @rtype list of str |
|
569 """ |
|
570 return [ |
|
571 "PLATFORM_TYPE_UNSPECIFIED", "WINDOWS", "LINUX", "ANDROID", "OSX", |
|
572 "IOS", "ANY_PLATFORM", "ALL_PLATFORMS", "CHROME", |
|
573 ] |