58 self.__apiKey = apiKey |
50 self.__apiKey = apiKey |
59 self.__fairUse = fairUse |
51 self.__fairUse = fairUse |
60 |
52 |
61 self.__nextRequestNoSoonerThan = QDateTime() |
53 self.__nextRequestNoSoonerThan = QDateTime() |
62 self.__failCount = 0 |
54 self.__failCount = 0 |
63 |
|
64 # get threat lists |
|
65 self.__threatListsReply = None |
|
66 |
|
67 # threats lists updates |
|
68 self.__threatsUpdatesRequest = None |
|
69 self.__threatsUpdateReply = None |
|
70 |
|
71 # full hashes |
|
72 self.__fullHashesRequest = None |
|
73 self.__fullHashesReply = None |
|
74 |
55 |
75 def getThreatLists(self): |
56 def getThreatLists(self): |
76 """ |
57 """ |
77 Public method to retrieve all available threat lists. |
58 Public method to retrieve all available threat lists. |
|
59 |
|
60 @return list of threat lists |
|
61 @rtype list of dict containing 'threatType', 'platformType' and |
|
62 'threatEntryType' |
78 """ |
63 """ |
79 url = QUrl(self.GsbUrlTemplate.format("threatLists", self.__apiKey)) |
64 url = QUrl(self.GsbUrlTemplate.format("threatLists", self.__apiKey)) |
80 req = QNetworkRequest(url) |
65 req = QNetworkRequest(url) |
81 reply = WebBrowserWindow.networkManager().get(req) |
66 reply = WebBrowserWindow.networkManager().get(req) |
82 reply.finished.connect(self.__threatListsReceived) |
67 |
83 self.__threatListsReply = reply |
68 while reply.isRunning(): |
84 |
69 QCoreApplication.processEvents(QEventLoop.AllEvents, 200) |
85 @pyqtSlot() |
70 # max. 200 ms processing |
86 def __threatListsReceived(self): |
71 |
87 """ |
72 res = None |
88 Private slot handling the threat lists. |
73 if reply.error() != QNetworkReply.NoError: |
89 """ |
74 self.networkError.emit(reply.errorString()) |
90 reply = self.sender() |
75 else: |
91 if reply is self.__threatListsReply: |
76 result = self.__extractData(reply) |
92 self.__threatListsReply = None |
77 res = result["threatLists"] |
93 result, hasError = self.__extractData(reply) |
78 |
94 if hasError: |
79 reply.deleteLater() |
95 # reschedule |
80 return res |
96 self.networkError.emit(reply.errorString()) |
81 |
97 self.__reschedule(reply.error(), self.getThreatLists) |
82 def getThreatsUpdate(self, clientState): |
98 else: |
|
99 self.threatLists.emit(result["threatLists"]) |
|
100 |
|
101 reply.deleteLater() |
|
102 |
|
103 def getThreatsUpdate(self, clientState=None): |
|
104 """ |
83 """ |
105 Public method to fetch hash prefix updates for the given threat list. |
84 Public method to fetch hash prefix updates for the given threat list. |
106 |
85 |
107 @param clientState dictionary of client states with keys like |
86 @param clientState dictionary of client states with keys like |
108 (threatType, platformType, threatEntryType) |
87 (threatType, platformType, threatEntryType) |
109 @type dict |
88 @type dict |
110 """ |
89 @return list of threat updates |
111 if self.__threatsUpdateReply is not None: |
90 @rtype list of dict |
112 # update is in progress |
91 """ |
113 return |
92 requestBody = { |
114 |
93 "client": { |
115 if clientState is None: |
94 "clientId": self.ClientId, |
116 if self.__threatsUpdatesRequest: |
95 "clientVersion": self.ClientVersion, |
117 requestBody = self.__threatsUpdatesRequest |
96 }, |
118 else: |
97 "listUpdateRequests": [], |
119 return |
98 } |
120 else: |
99 |
121 requestBody = { |
100 for (threatType, platformType, threatEntryType), currentState in \ |
122 "client": { |
101 clientState.items(): |
123 "clientId": self.ClientId, |
102 requestBody["listUpdateRequests"].append( |
124 "clientVersion": self.ClientVersion, |
103 { |
125 }, |
104 "threatType": threatType, |
126 "listUpdateRequests": [], |
105 "platformType": platformType, |
127 } |
106 "threatEntryType": threatEntryType, |
128 |
107 "state": currentState, |
129 for (threatType, platformType, threatEntryType), currentState in \ |
108 "constraints": { |
130 clientState.items(): |
109 "supportedCompressions": ["RAW"], |
131 requestBody["listUpdateRequests"].append( |
|
132 { |
|
133 "threatType": threatType, |
|
134 "platformType": platformType, |
|
135 "threatEntryType": threatEntryType, |
|
136 "state": currentState, |
|
137 "constraints": { |
|
138 "supportedCompressions": ["RAW"], |
|
139 } |
|
140 } |
110 } |
141 ) |
111 } |
142 |
112 ) |
143 self.__threatsUpdatesRequest = requestBody |
|
144 |
113 |
145 data = QByteArray(json.dumps(requestBody).encode("utf-8")) |
114 data = QByteArray(json.dumps(requestBody).encode("utf-8")) |
146 url = QUrl(self.GsbUrlTemplate.format("threatListUpdates:fetch", |
115 url = QUrl(self.GsbUrlTemplate.format("threatListUpdates:fetch", |
147 self.__apiKey)) |
116 self.__apiKey)) |
148 req = QNetworkRequest(url) |
117 req = QNetworkRequest(url) |
149 req.setHeader(QNetworkRequest.ContentTypeHeader, "application/json") |
118 req.setHeader(QNetworkRequest.ContentTypeHeader, "application/json") |
150 reply = WebBrowserWindow.networkManager().post(req, data) |
119 reply = WebBrowserWindow.networkManager().post(req, data) |
151 reply.finished.connect(self.__threatsUpdateReceived) |
120 |
152 self.__threatsUpdateReply = reply |
121 while reply.isRunning(): |
153 |
122 QCoreApplication.processEvents(QEventLoop.AllEvents, 200) |
154 @pyqtSlot() |
123 # max. 200 ms processing |
155 def __threatsUpdateReceived(self): |
124 |
156 """ |
125 res = None |
157 Private slot handling the threats update. |
126 if reply.error() != QNetworkReply.NoError: |
158 """ |
127 self.networkError.emit(reply.errorString()) |
159 reply = self.sender() |
128 else: |
160 if reply is self.__threatsUpdateReply: |
129 result = self.__extractData(reply) |
161 self.__threatsUpdateReply = None |
130 res = result["listUpdateResponses"] |
162 result, hasError = self.__extractData(reply) |
131 |
163 if hasError: |
132 reply.deleteLater() |
164 # reschedule |
133 return res |
165 self.networkError.emit(reply.errorString()) |
134 |
166 self.__reschedule(reply.error(), self.getThreatsUpdate) |
135 def getFullHashes(self, prefixes, clientState): |
167 else: |
|
168 self.__threatsUpdatesRequest = None |
|
169 self.threatsUpdate.emit(result["listUpdateResponses"]) |
|
170 |
|
171 reply.deleteLater() |
|
172 |
|
173 def getFullHashes(self, prefixes=None, clientState=None): |
|
174 """ |
136 """ |
175 Public method to find full hashes matching hash prefixes. |
137 Public method to find full hashes matching hash prefixes. |
176 |
138 |
177 @param prefixes list of hash prefixes to find |
139 @param prefixes list of hash prefixes to find |
178 @type list of str (Python 2) or list of bytes (Python 3) |
140 @type list of str (Python 2) or list of bytes (Python 3) |
179 @param clientState dictionary of client states with keys like |
141 @param clientState dictionary of client states with keys like |
180 (threatType, platformType, threatEntryType) |
142 (threatType, platformType, threatEntryType) |
181 @type dict |
143 @type dict |
182 """ |
144 @return dictionary containing the list of found hashes and the |
183 if self.__fullHashesReply is not None: |
145 negative cache duration |
184 # full hash request in progress |
146 @rtype dict |
185 return |
147 """ |
186 |
148 requestBody = { |
187 if prefixes is None or clientState is None: |
149 "client": { |
188 if self.__fullHashesRequest: |
150 "clientId": self.ClientId, |
189 requestBody = self.__fullHashesRequest |
151 "clientVersion": self.ClientVersion, |
190 else: |
152 }, |
191 return |
153 "clientStates": [], |
192 else: |
154 "threatInfo": { |
193 requestBody = { |
155 "threatTypes": [], |
194 "client": { |
156 "platformTypes": [], |
195 "clientId": self.ClientId, |
157 "threatEntryTypes": [], |
196 "clientVersion": self.ClientVersion, |
158 "threatEntries": [], |
197 }, |
159 }, |
198 "clientStates": [], |
160 } |
199 "threatInfo": { |
161 |
200 "threatTypes": [], |
162 for prefix in prefixes: |
201 "platformTypes": [], |
163 requestBody["threatInfo"]["threatEntries"].append( |
202 "threatEntryTypes": [], |
164 {"hash": base64.b64encode(prefix).decode("ascii")}) |
203 "threatEntries": [], |
165 |
204 }, |
166 for (threatType, platformType, threatEntryType), currentState in \ |
205 } |
167 clientState.items(): |
206 |
168 requestBody["clientStates"].append(clientState) |
207 for prefix in prefixes: |
169 if threatType not in requestBody["threatInfo"]["threatTypes"]: |
208 requestBody["threatInfo"]["threatEntries"].append( |
170 requestBody["threatInfo"]["threatTypes"].append(threatType) |
209 {"hash": base64.b64encode(prefix).decode("ascii")}) |
171 if platformType not in \ |
210 |
172 requestBody["threatInfo"]["platformTypes"]: |
211 for (threatType, platformType, threatEntryType), currentState in \ |
173 requestBody["threatInfo"]["platformTypes"].append( |
212 clientState.items(): |
174 platformType) |
213 requestBody["clientStates"].append(clientState) |
175 if threatEntryType not in \ |
214 if threatType not in requestBody["threatInfo"]["threatTypes"]: |
176 requestBody["threatInfo"]["threatEntryTypes"]: |
215 requestBody["threatInfo"]["threatTypes"].append(threatType) |
177 requestBody["threatInfo"]["threatEntryTypes"].append( |
216 if platformType not in \ |
178 threatEntryType) |
217 requestBody["threatInfo"]["platformTypes"]: |
|
218 requestBody["threatInfo"]["platformTypes"].append( |
|
219 platformType) |
|
220 if threatEntryType not in \ |
|
221 requestBody["threatInfo"]["threatEntryTypes"]: |
|
222 requestBody["threatInfo"]["threatEntryTypes"].append( |
|
223 threatEntryType) |
|
224 |
|
225 self.__fullHashesRequest = requestBody |
|
226 |
179 |
227 data = QByteArray(json.dumps(requestBody).encode("utf-8")) |
180 data = QByteArray(json.dumps(requestBody).encode("utf-8")) |
228 url = QUrl(self.GsbUrlTemplate.format("fullHashes:find", |
181 url = QUrl(self.GsbUrlTemplate.format("fullHashes:find", |
229 self.__apiKey)) |
182 self.__apiKey)) |
230 req = QNetworkRequest(url) |
183 req = QNetworkRequest(url) |
231 req.setHeader(QNetworkRequest.ContentTypeHeader, "application/json") |
184 req.setHeader(QNetworkRequest.ContentTypeHeader, "application/json") |
232 reply = WebBrowserWindow.networkManager().post(req, data) |
185 reply = WebBrowserWindow.networkManager().post(req, data) |
233 reply.finished.connect(self.__fullHashesReceived) |
186 |
234 self.__fullHashesReply = reply |
187 while reply.isRunning(): |
235 |
188 QCoreApplication.processEvents(QEventLoop.AllEvents, 200) |
236 @pyqtSlot() |
189 # max. 200 ms processing |
237 def __fullHashesReceived(self): |
190 |
238 """ |
191 res = None |
239 Private slot handling the full hashes reply. |
192 if reply.error() != QNetworkReply.NoError: |
240 """ |
193 self.networkError.emit(reply.errorString()) |
241 reply = self.sender() |
194 else: |
242 if reply is self.__fullHashesReply: |
195 res = self.__extractData(reply) |
243 self.__fullHashesReply = None |
196 |
244 result, hasError = self.__extractData(reply) |
197 reply.deleteLater() |
245 if hasError: |
198 return res |
246 # reschedule |
|
247 self.networkError.emit(reply.errorString()) |
|
248 self.__reschedule(reply.error(), self.getFullHashes) |
|
249 else: |
|
250 self.__fullHashesRequest = None |
|
251 self.fullHashes.emit(result) |
|
252 |
|
253 reply.deleteLater() |
|
254 |
199 |
255 def __extractData(self, reply): |
200 def __extractData(self, reply): |
256 """ |
201 """ |
257 Private method to extract the data of a network reply. |
202 Private method to extract the data of a network reply. |
258 |
203 |
259 @param reply reference to the network reply object |
204 @param reply reference to the network reply object |
260 @type QNetworkReply |
205 @type QNetworkReply |
261 @return tuple containing the extracted data and an error flag |
206 @return extracted data |
262 @type tuple of (list or dict, bool) |
207 @type list or dict |
263 """ |
208 """ |
264 if reply.error() != QNetworkReply.NoError: |
|
265 return None, True |
|
266 |
|
267 self.__failCount = 0 |
|
268 result = json.loads(str(reply.readAll(), "utf-8")) |
209 result = json.loads(str(reply.readAll(), "utf-8")) |
269 self.__setWaitDuration(result.get("minimumWaitDuration")) |
210 self.__setWaitDuration(result.get("minimumWaitDuration")) |
270 return result, False |
211 return result |
271 |
212 |
272 def __setWaitDuration(self, minimumWaitDuration): |
213 def __setWaitDuration(self, minimumWaitDuration): |
273 """ |
214 """ |
274 Private method to set the minimum wait duration. |
215 Private method to set the minimum wait duration. |
275 |
216 |