|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2011 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the <a href="http://www.virustotal.com">VirusTotal</a> API class. |
|
8 """ |
|
9 |
|
10 import json |
|
11 |
|
12 from PyQt4.QtCore import QObject, QUrl, QByteArray, QCoreApplication, QThread |
|
13 from PyQt4.QtNetwork import QNetworkRequest, QNetworkReply, QNetworkAccessManager |
|
14 |
|
15 import Helpviewer.HelpWindow |
|
16 |
|
17 import Preferences |
|
18 |
|
19 class VirusTotalAPI(QObject): |
|
20 """ |
|
21 Class implementing the <a href="http://www.virustotal.com">VirusTotal</a> API. |
|
22 """ |
|
23 TestServiceKeyScanID = \ |
|
24 "4feed2c2e352f105f6188efd1d5a558f24aee6971bdf96d5fdb19c197d6d3fad" |
|
25 |
|
26 ServiceResult_RequestLimitReached = -2 |
|
27 ServiceResult_InvalidServiceKey = -1 |
|
28 ServiceResult_ItemNotPresent = 0 |
|
29 ServiceResult_ItemPresent = 1 |
|
30 |
|
31 GetFileReportPattern = "{0}://www.virustotal.com/api/get_file_report.json" |
|
32 ScanUrlPattern = "{0}://www.virustotal.com/api/scan_url.json" |
|
33 GetUrlReportPattern = "{0}://www.virustotal.com/api/get_url_report.json" |
|
34 |
|
35 ReportUrlScanPagePattern = "http://www.virustotal.com/url-scan/report.html?id={0}" |
|
36 ReportFileScanPagePattern = "http://www.virustotal.com/file-scan/report.html?id={0}" |
|
37 |
|
38 SearchUrl = "http://www.virustotal.com/search.html" |
|
39 |
|
40 def __init__(self, parent=None): |
|
41 """ |
|
42 Constructor |
|
43 |
|
44 @param parent reference to the parent object (QObject) |
|
45 """ |
|
46 QObject.__init__(self, parent) |
|
47 |
|
48 self.__loadSettings() |
|
49 |
|
50 def __loadSettings(self): |
|
51 """ |
|
52 Private method to load the settings. |
|
53 """ |
|
54 if Preferences.getHelp("VirusTotalSecure"): |
|
55 protocol = "https" |
|
56 else: |
|
57 protocol = "http" |
|
58 self.GetFileReportUrl = self.GetFileReportPattern.format(protocol) |
|
59 self.ScanUrlUrl = self.ScanUrlPattern.format(protocol) |
|
60 self.GetUrlReportUrl = self.GetUrlReportPattern.format(protocol) |
|
61 |
|
62 self.errorMessages = { |
|
63 -2: self.trUtf8("Request limit has been reached."), |
|
64 -1: self.trUtf8("Invalid key given."), |
|
65 0: self.trUtf8("Requested item is not present.") |
|
66 } |
|
67 |
|
68 def checkServiceKeyValidity(self, key, protocol=""): |
|
69 """ |
|
70 Public method to check the validity of the given service key. |
|
71 |
|
72 @param key service key (string) |
|
73 @param protocol protocol used to access VirusTotal (string) |
|
74 @return flag indicating validity (boolean) and an error message in |
|
75 case of a network error (string) |
|
76 """ |
|
77 if protocol == "": |
|
78 urlStr = self.GetFileReportUrl |
|
79 else: |
|
80 urlStr = self.GetFileReportPattern.format(protocol) |
|
81 request = QNetworkRequest(QUrl(urlStr)) |
|
82 request.setHeader(QNetworkRequest.ContentTypeHeader, |
|
83 "application/x-www-form-urlencoded") |
|
84 params = QByteArray("key={0}&resource={1}".format( |
|
85 key, self.TestServiceKeyScanID)) |
|
86 |
|
87 nam = Helpviewer.HelpWindow.HelpWindow.networkAccessManager() |
|
88 reply = nam.post(request, params) |
|
89 while not reply.isFinished(): |
|
90 QCoreApplication.processEvents() |
|
91 QThread.msleep(100) |
|
92 if QCoreApplication.closingDown(): |
|
93 reply.abort() |
|
94 QCoreApplication.processEvents() |
|
95 if reply.error() == QNetworkReply.NoError: |
|
96 result = json.loads(str(reply.readAll(), "utf-8")) |
|
97 if result["result"] != self.ServiceResult_InvalidServiceKey: |
|
98 return True, "" |
|
99 else: |
|
100 return False, "" |
|
101 |
|
102 return False, reply.errorString() |
|
103 |
|
104 def submitUrl(self, url): |
|
105 """ |
|
106 Public method to submit an URL to be scanned. |
|
107 |
|
108 @param url url to be scanned (QUrl) |
|
109 @return flag indicating success (boolean) and the scan ID (string) |
|
110 """ |
|
111 request = QNetworkRequest(QUrl(self.ScanUrlUrl)) |
|
112 request.setHeader(QNetworkRequest.ContentTypeHeader, |
|
113 "application/x-www-form-urlencoded") |
|
114 params = QByteArray( |
|
115 "key={0}&url=".format(Preferences.getHelp("VirusTotalServiceKey")))\ |
|
116 .append(QUrl.toPercentEncoding(url.toString())) |
|
117 |
|
118 nam = Helpviewer.HelpWindow.HelpWindow.networkAccessManager() |
|
119 reply = nam.post(request, params) |
|
120 while not reply.isFinished(): |
|
121 QCoreApplication.processEvents() |
|
122 QThread.msleep(100) |
|
123 if QCoreApplication.closingDown(): |
|
124 reply.abort() |
|
125 QCoreApplication.processEvents() |
|
126 if reply.error() == QNetworkReply.NoError: |
|
127 result = json.loads(str(reply.readAll(), "utf-8")) |
|
128 if result["result"] == self.ServiceResult_ItemPresent: |
|
129 return True, result["scan_id"] |
|
130 else: |
|
131 return False, self.errorMessages[result["result"]] |
|
132 |
|
133 return False, reply.errorString() |
|
134 |
|
135 def getUrlScanReportUrl(self, scanId): |
|
136 """ |
|
137 Public method to get the report URL for a URL scan. |
|
138 |
|
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 |
|
148 @param scanId ID of the scan to get the report URL for (string) |
|
149 @return file scan report URL (string) |
|
150 """ |
|
151 fileScanPageUrl = "" # default value |
|
152 |
|
153 request = QNetworkRequest(QUrl(self.GetUrlReportUrl)) |
|
154 request.setHeader(QNetworkRequest.ContentTypeHeader, |
|
155 "application/x-www-form-urlencoded") |
|
156 params = QByteArray("key={0}&resource={1}".format( |
|
157 Preferences.getHelp("VirusTotalServiceKey"), scanId)) |
|
158 |
|
159 nam = Helpviewer.HelpWindow.HelpWindow.networkAccessManager() |
|
160 reply = nam.post(request, params) |
|
161 while not reply.isFinished(): |
|
162 QCoreApplication.processEvents() |
|
163 QThread.msleep(100) |
|
164 if QCoreApplication.closingDown(): |
|
165 reply.abort() |
|
166 QCoreApplication.processEvents() |
|
167 if reply.error() == QNetworkReply.NoError: |
|
168 result = json.loads(str(reply.readAll(), "utf-8")) |
|
169 if "file-report" in result: |
|
170 fileScanPageUrl = self.ReportFileScanPagePattern.format( |
|
171 result["file-report"]) |
|
172 |
|
173 return fileScanPageUrl |
|
174 |
|
175 @classmethod |
|
176 def getSearchRequestData(cls, term): |
|
177 """ |
|
178 |
|
179 """ |
|
180 request = QNetworkRequest(QUrl(cls.SearchUrl)) |
|
181 request.setHeader(QNetworkRequest.ContentTypeHeader, |
|
182 "application/x-www-form-urlencoded") |
|
183 op = QNetworkAccessManager.PostOperation |
|
184 params = QByteArray("chain=").append(QUrl.toPercentEncoding(term)) |
|
185 |
|
186 return (request, op, params) |