7 Module implementing the <a href="http://www.virustotal.com">VirusTotal</a> API class. |
7 Module implementing the <a href="http://www.virustotal.com">VirusTotal</a> API class. |
8 """ |
8 """ |
9 |
9 |
10 import json |
10 import json |
11 |
11 |
12 from PyQt4.QtCore import QObject, QUrl, QByteArray, QCoreApplication, QThread |
12 from PyQt4.QtCore import QObject, QUrl, QByteArray, pyqtSignal |
13 from PyQt4.QtNetwork import QNetworkRequest, QNetworkReply, QNetworkAccessManager |
13 from PyQt4.QtNetwork import QNetworkRequest, QNetworkReply, QNetworkAccessManager |
14 |
14 |
15 import Helpviewer.HelpWindow |
15 import Helpviewer.HelpWindow |
16 |
16 |
17 import Preferences |
17 import Preferences |
18 |
18 |
19 class VirusTotalAPI(QObject): |
19 class VirusTotalAPI(QObject): |
20 """ |
20 """ |
21 Class implementing the <a href="http://www.virustotal.com">VirusTotal</a> API. |
21 Class implementing the <a href="http://www.virustotal.com">VirusTotal</a> API. |
|
22 |
|
23 @signal checkServiceKeyFinished(bool, str) emitted after the service key check |
|
24 has been performed. It gives a flag indicating validity (boolean) and |
|
25 an error message in case of a network error (string). |
|
26 @signal submitUrlError(str) emitted with the error string, if the URL scan |
|
27 submission returned an error. |
|
28 @signal urlScanReport(str) emitted with the URL of the URL scan report page |
|
29 @signal fileScanReport(str) emitted with the URL of the file scan report page |
22 """ |
30 """ |
|
31 checkServiceKeyFinished = pyqtSignal(bool, str) |
|
32 submitUrlError = pyqtSignal(str) |
|
33 urlScanReport = pyqtSignal(str) |
|
34 fileScanReport = pyqtSignal(str) |
|
35 |
23 TestServiceKeyScanID = \ |
36 TestServiceKeyScanID = \ |
24 "4feed2c2e352f105f6188efd1d5a558f24aee6971bdf96d5fdb19c197d6d3fad" |
37 "4feed2c2e352f105f6188efd1d5a558f24aee6971bdf96d5fdb19c197d6d3fad" |
25 |
38 |
26 ServiceResult_RequestLimitReached = -2 |
39 ServiceResult_RequestLimitReached = -2 |
27 ServiceResult_InvalidServiceKey = -1 |
40 ServiceResult_InvalidServiceKey = -1 |
84 params = QByteArray("key={0}&resource={1}".format( |
105 params = QByteArray("key={0}&resource={1}".format( |
85 key, self.TestServiceKeyScanID)) |
106 key, self.TestServiceKeyScanID)) |
86 |
107 |
87 nam = Helpviewer.HelpWindow.HelpWindow.networkAccessManager() |
108 nam = Helpviewer.HelpWindow.HelpWindow.networkAccessManager() |
88 reply = nam.post(request, params) |
109 reply = nam.post(request, params) |
89 while not reply.isFinished(): |
110 reply.finished.connect(self.__checkServiceKeyValidityFinished) |
90 QCoreApplication.processEvents() |
111 self.__replies.append(reply) |
91 QThread.msleep(100) |
112 |
92 if QCoreApplication.closingDown(): |
113 def __checkServiceKeyValidityFinished(self): |
93 reply.abort() |
114 """ |
94 QCoreApplication.processEvents() |
115 Private slot to determine the result of the service key validity check. |
|
116 """ |
|
117 res = False |
|
118 msg = "" |
|
119 |
|
120 reply = self.sender() |
95 if reply.error() == QNetworkReply.NoError: |
121 if reply.error() == QNetworkReply.NoError: |
96 result = json.loads(str(reply.readAll(), "utf-8")) |
122 result = json.loads(str(reply.readAll(), "utf-8")) |
97 if result["result"] != self.ServiceResult_InvalidServiceKey: |
123 if result["result"] != self.ServiceResult_InvalidServiceKey: |
98 return True, "" |
124 res = True |
99 else: |
125 else: |
100 return False, "" |
126 msg = reply.errorString() |
101 |
127 self.__replies.remove(reply) |
102 return False, reply.errorString() |
128 |
|
129 self.checkServiceKeyFinished.emit(res, msg) |
103 |
130 |
104 def submitUrl(self, url): |
131 def submitUrl(self, url): |
105 """ |
132 """ |
106 Public method to submit an URL to be scanned. |
133 Public method to submit an URL to be scanned. |
107 |
134 |
115 "key={0}&url=".format(Preferences.getHelp("VirusTotalServiceKey")))\ |
142 "key={0}&url=".format(Preferences.getHelp("VirusTotalServiceKey")))\ |
116 .append(QUrl.toPercentEncoding(url.toString())) |
143 .append(QUrl.toPercentEncoding(url.toString())) |
117 |
144 |
118 nam = Helpviewer.HelpWindow.HelpWindow.networkAccessManager() |
145 nam = Helpviewer.HelpWindow.HelpWindow.networkAccessManager() |
119 reply = nam.post(request, params) |
146 reply = nam.post(request, params) |
120 while not reply.isFinished(): |
147 reply.finished.connect(self.__submitUrlFinished) |
121 QCoreApplication.processEvents() |
148 self.__replies.append(reply) |
122 QThread.msleep(100) |
149 |
123 if QCoreApplication.closingDown(): |
150 def __submitUrlFinished(self): |
124 reply.abort() |
151 """ |
125 QCoreApplication.processEvents() |
152 Private slot to determine the result of the URL scan submission. |
|
153 """ |
|
154 reply = self.sender() |
126 if reply.error() == QNetworkReply.NoError: |
155 if reply.error() == QNetworkReply.NoError: |
127 result = json.loads(str(reply.readAll(), "utf-8")) |
156 result = json.loads(str(reply.readAll(), "utf-8")) |
128 if result["result"] == self.ServiceResult_ItemPresent: |
157 if result["result"] == self.ServiceResult_ItemPresent: |
129 return True, result["scan_id"] |
158 self.urlScanReport.emit( |
|
159 self.ReportUrlScanPagePattern.format(result["scan_id"])) |
|
160 self.__getFileScanReportUrl(result["scan_id"]) |
130 else: |
161 else: |
131 return False, self.errorMessages[result["result"]] |
162 self.submitUrlError.emit(self.errorMessages[result["result"]]) |
132 |
163 else: |
133 return False, reply.errorString() |
164 self.submitUrlError.emit(reply.errorString()) |
134 |
165 self.__replies.remove(reply) |
135 def getUrlScanReportUrl(self, scanId): |
166 |
136 """ |
167 def __getFileScanReportUrl(self, scanId): |
137 Public method to get the report URL for a URL scan. |
168 """ |
138 |
169 Private method to get the report URL for a file scan. |
139 @param scanId ID of the scan to get the report URL for (string) |
|
140 @return URL scan report URL (string) |
|
141 """ |
|
142 return self.ReportUrlScanPagePattern.format(scanId) |
|
143 |
|
144 def getFileScanReportUrl(self, scanId): |
|
145 """ |
|
146 Public method to get the report URL for a file scan. |
|
147 |
170 |
148 @param scanId ID of the scan to get the report URL for (string) |
171 @param scanId ID of the scan to get the report URL for (string) |
149 @return file scan report URL (string) |
172 @return file scan report URL (string) |
150 """ |
173 """ |
151 fileScanPageUrl = "" # default value |
|
152 |
|
153 request = QNetworkRequest(QUrl(self.GetUrlReportUrl)) |
174 request = QNetworkRequest(QUrl(self.GetUrlReportUrl)) |
154 request.setHeader(QNetworkRequest.ContentTypeHeader, |
175 request.setHeader(QNetworkRequest.ContentTypeHeader, |
155 "application/x-www-form-urlencoded") |
176 "application/x-www-form-urlencoded") |
156 params = QByteArray("key={0}&resource={1}".format( |
177 params = QByteArray("key={0}&resource={1}".format( |
157 Preferences.getHelp("VirusTotalServiceKey"), scanId)) |
178 Preferences.getHelp("VirusTotalServiceKey"), scanId)) |
158 |
179 |
159 nam = Helpviewer.HelpWindow.HelpWindow.networkAccessManager() |
180 nam = Helpviewer.HelpWindow.HelpWindow.networkAccessManager() |
160 reply = nam.post(request, params) |
181 reply = nam.post(request, params) |
161 while not reply.isFinished(): |
182 reply.finished.connect(self.__getFileScanReportUrlFinished) |
162 QCoreApplication.processEvents() |
183 self.__replies.append(reply) |
163 QThread.msleep(100) |
184 |
164 if QCoreApplication.closingDown(): |
185 def __getFileScanReportUrlFinished(self): |
165 reply.abort() |
186 """ |
166 QCoreApplication.processEvents() |
187 Private slot to determine the result of the file scan report URL request. |
|
188 """ |
|
189 reply = self.sender() |
167 if reply.error() == QNetworkReply.NoError: |
190 if reply.error() == QNetworkReply.NoError: |
168 result = json.loads(str(reply.readAll(), "utf-8")) |
191 result = json.loads(str(reply.readAll(), "utf-8")) |
169 if "file-report" in result: |
192 if "file-report" in result: |
170 fileScanPageUrl = self.ReportFileScanPagePattern.format( |
193 self.fileScanReport.emit( |
171 result["file-report"]) |
194 self.ReportFileScanPagePattern.format(result["file-report"])) |
172 |
195 self.__replies.remove(reply) |
173 return fileScanPageUrl |
|
174 |
196 |
175 @classmethod |
197 @classmethod |
176 def getSearchRequestData(cls, term): |
198 def getSearchRequestData(cls, term): |
177 """ |
199 """ |
178 |
200 Class method to assemble the search request data structure. |
|
201 |
|
202 @param term search term (string) |
|
203 @return tuple of network request object, operation and parameters |
|
204 (QNetworkRequest, QNetworkAccessManager.Operation, QByteArray) |
179 """ |
205 """ |
180 request = QNetworkRequest(QUrl(cls.SearchUrl)) |
206 request = QNetworkRequest(QUrl(cls.SearchUrl)) |
181 request.setHeader(QNetworkRequest.ContentTypeHeader, |
207 request.setHeader(QNetworkRequest.ContentTypeHeader, |
182 "application/x-www-form-urlencoded") |
208 "application/x-www-form-urlencoded") |
183 op = QNetworkAccessManager.PostOperation |
209 op = QNetworkAccessManager.PostOperation |