Completed
Push — master ( 5f7358...8f706c )
by Дроботун
01:08
created

VirusTotalAPIFiles.get_file_id()   B

Complexity

Conditions 7

Size

Total Lines 33
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 17
nop 2
dl 0
loc 33
rs 8
c 0
b 0
f 0
1
"""The module describes the VirusTotalAPIFiles class
2
3
   Author: Evgeny Drobotun (c) 2019
4
   License: MIT (https://github.com/drobotun/virustotalapi3/blob/master/LICENSE)
5
6
   More information: https://virustotalapi3.readthedocs.io/en/latest/file_class.html
7
"""
8
9
import hashlib
10
import errno
11
import requests
12
13
from .vtapi3base import VirusTotalAPI
14
from .vtapi3error import VirusTotalAPIError
15
16
class VirusTotalAPIFiles(VirusTotalAPI):
17
    """The analysis new files and retrieving information about any file from the VirusTotal database
18
       methods are defined in the class.
19
20
       Methods:
21
          get_file_id(): Get SHA256, SHA1 or MD5 file identifier.
22
          upload(): Upload and analyse a file.
23
          get_upload_url(): Get a URL for uploading files larger than 32MB.
24
          get_report(): Retrieve information about a file.
25
          reanalyse(): Reanalyse a file already in VirusTotal.
26
          get_comments(): Retrieve comments for a file.
27
          put_comments(): Add a comment to a file.
28
          get_votes(): Retrieve votes for a file.
29
          put_votes(): Add a votes to a file.
30
          get_relationship(): Retrieve objects related to a file.
31
          get_behaviours(): Get the PCAP for the sandbox.
32
    """
33
34
    @staticmethod
35
    def get_file_id(file_path, hash_alg='sha256'):
36
        """Get SHA256, SHA1 or MD5 file identifier.
37
38
           Args:
39
              file_path: Path to the file to be scanned (str).
40
              hash: Necessary identifier ('sha256', 'sha1' or 'md5'). The default value is
41
                 'sha256'.
42
43
           Return:
44
              The SHA256, SHA1 or MD5 identifier of the file.
45
46
           Exception
47
              VirusTotalAPIError(File not found): In case the file is not found.
48
              VirusTotalAPIError(Permission error): In case do not have access rights to the file.
49
              VirusTotalAPIError(IO Error): If an IO error occurs during file operations.
50
        """
51
        buffer_size = 65536
52
        hasher = hashlib.new(hash_alg)
53
        try:
54
            with open(file_path, 'rb') as file:
55
                buffer = file.read(buffer_size)
56
                while len(buffer) > 0:
57
                    hasher.update(buffer)
58
                    buffer = file.read(buffer_size)
59
        except FileNotFoundError:
60
            raise VirusTotalAPIError('File not found', errno.ENOENT)
61
        except PermissionError:
62
            raise VirusTotalAPIError('Permission error', errno.EPERM)
63
        except OSError:
64
            raise VirusTotalAPIError('IO Error', errno.EIO)
65
        else:
66
            return hasher.hexdigest()
67
68
    def upload(self, file_path):
69
        """Upload and analyse a file.
70
71
        Args:
72
           file_path: Path to the file to be scanned (str).
73
74
        Return:
75
           The response from the server as a byte sequence.
76
77
        Exception
78
           VirusTotalAPIError(Connection error): In case of server connection errors.
79
           VirusTotalAPIError(Timeout error): If the response timeout from the server is exceeded.
80
           VirusTotalAPIError(File not found): In case the file you want to upload to the server is
81
              not found.
82
           VirusTotalAPIError(Permission error): In case do not have access rights to the file.
83
           VirusTotalAPIError(IO Error): If an IO error occurs during file operations.
84
        """
85
        self._last_http_error = None
86
        self._last_result = None
87
        api_url = self.base_url + '/files'
88
        try:
89
            with open(file_path, 'rb') as file:
90
                files = {'file': (file_path, file)}
91
                response = requests.post(api_url, headers=self.headers, files=files,
92
                                         timeout=self.timeout, proxies=self.proxies)
93
        except FileNotFoundError:
94
            raise VirusTotalAPIError('File not found', errno.ENOENT)
95
        except PermissionError:
96
            raise VirusTotalAPIError('Permission error', errno.EPERM)
97
        except requests.exceptions.Timeout:
98
            raise VirusTotalAPIError('Timeout error', errno.ETIMEDOUT)
99
        except requests.exceptions.ConnectionError:
100
            raise VirusTotalAPIError('Connection error', errno.ECONNABORTED)
101
        except OSError:
102
            raise VirusTotalAPIError('IO Error', errno.EIO)
103
        else:
104
            self._last_http_error = response.status_code
105
            self._last_result = response.content
106
            return response.content
107
108
    def get_upload_url(self):
109
        """Get a URL for uploading files larger than 32MB.
110
111
        Return:
112
           The response from the server as a byte sequence.
113
114
        Exception
115
           VirusTotalAPIError(Connection error): In case of server connection errors.
116
           VirusTotalAPIError(Timeout error): If the response timeout from the server is exceeded.
117
        """
118
        self._last_http_error = None
119
        self._last_result = None
120
        api_url = self.base_url + '/files/upload_url'
121
        try:
122
            response = requests.get(api_url, headers=self.headers,
123
                                    timeout=self.timeout, proxies=self.proxies)
124
        except requests.exceptions.Timeout:
125
            raise VirusTotalAPIError('Timeout error', errno.ETIMEDOUT)
126
        except requests.exceptions.ConnectionError:
127
            raise VirusTotalAPIError('Connection error', errno.ECONNABORTED)
128
        else:
129
            self._last_http_error = response.status_code
130
            self._last_result = response.content
131
            return response.content
132
133
    def get_report(self, file_id):
134
        """Retrieve information about a file.
135
136
        Args:
137
           file_id: SHA-256, SHA-1 or MD5 identifying the file (str).
138
139
       Return:
140
           The response from the server as a byte sequence.
141
142
        Exception
143
           VirusTotalAPIError(Connection error): In case of server connection errors.
144
           VirusTotalAPIError(Timeout error): If the response timeout from the server is exceeded.
145
        """
146
        self._last_http_error = None
147
        self._last_result = None
148
        api_url = self.base_url + '/files/' + file_id
149
        try:
150
            response = requests.get(api_url, headers=self.headers,
151
                                    timeout=self.timeout, proxies=self.proxies)
152
        except requests.exceptions.Timeout:
153
            raise VirusTotalAPIError('Timeout error', errno.ETIMEDOUT)
154
        except requests.exceptions.ConnectionError:
155
            raise VirusTotalAPIError('Connection error', errno.ECONNABORTED)
156
        else:
157
            self._last_http_error = response.status_code
158
            self._last_result = response.content
159
            return response.content
160
161
    def analyse(self, file_id):
162
        """Reanalyse a file already in VirusTotal.
163
164
        Args:
165
           file_id: SHA-256, SHA-1 or MD5 identifying the file (str).
166
167
        Return:
168
           The response from the server as a byte sequence.
169
170
        Exception
171
           VirusTotalAPIError(Connection error): In case of server connection errors.
172
           VirusTotalAPIError(Timeout error): If the response timeout from the server is exceeded.
173
        """
174
        self._last_http_error = None
175
        self._last_result = None
176
        api_url = self.base_url + '/files/' + file_id + '/analyse'
177
        try:
178
            response = requests.post(api_url, headers=self.headers,
179
                                     timeout=self.timeout, proxies=self.proxies)
180
        except requests.exceptions.Timeout:
181
            raise VirusTotalAPIError('Timeout error', errno.ETIMEDOUT)
182
        except requests.exceptions.ConnectionError:
183
            raise VirusTotalAPIError('Connection error', errno.ECONNABORTED)
184
        else:
185
            self._last_http_error = response.status_code
186
            self._last_result = response.content
187
            return response.content
188
189
    def get_comments(self, file_id, limit=10, cursor='""'):
190
        """Retrieve comments for a file.
191
192
        Args:
193
           file_id: SHA-256, SHA-1 or MD5 identifying the file (str).
194
           limit: Maximum number of comments to retrieve (int). The default value is 10.
195
           cursor: Continuation cursor (str). The default value is ''.
196
197
        Return:
198
           The response from the server as a byte sequence.
199
200
        Exception
201
           VirusTotalAPIError(Connection error): In case of server connection errors.
202
           VirusTotalAPIError(Timeout error): If the response timeout from the server is exceeded.
203
        """
204
        self._last_http_error = None
205
        self._last_result = None
206
        query_string = {'limit': str(limit), 'cursor': cursor}
207
        api_url = self.base_url + '/files/' + file_id + '/comments'
208
        try:
209
            response = requests.get(api_url, headers=self.headers, params=query_string,
210
                                    timeout=self.timeout, proxies=self.proxies)
211
        except requests.exceptions.Timeout:
212
            raise VirusTotalAPIError('Timeout error', errno.ETIMEDOUT)
213
        except requests.exceptions.ConnectionError:
214
            raise VirusTotalAPIError('Connection error', errno.ECONNABORTED)
215
        else:
216
            self._last_http_error = response.status_code
217
            self._last_result = response.content
218
            return response.content
219
220
    def put_comments(self, file_id, text):
221
        """Add a comment to a file.
222
223
        Args:
224
           file_id: SHA-256, SHA-1 or MD5 identifying the file (str).
225
           text: Text of the comment (str).
226
227
        Return:
228
           The response from the server as a byte sequence.
229
230
        Exception
231
           VirusTotalAPIError(Connection error): In case of server connection errors.
232
           VirusTotalAPIError(Timeout error): If the response timeout from the server is exceeded.
233
        """
234
        self._last_http_error = None
235
        self._last_result = None
236
        comments = {"data": {'type': 'comment', 'attributes': {'text': text}}}
237
        api_url = self.base_url + '/files/' + file_id + '/comments'
238
        try:
239
            response = requests.post(api_url, headers=self.headers, json=comments,
240
                                     timeout=self.timeout, proxies=self.proxies)
241
        except requests.exceptions.Timeout:
242
            raise VirusTotalAPIError('Timeout error', errno.ETIMEDOUT)
243
        except requests.exceptions.ConnectionError:
244
            raise VirusTotalAPIError('Connection error', errno.ECONNABORTED)
245
        else:
246
            self._last_http_error = response.status_code
247
            self._last_result = response.content
248
            return response.content
249
250
    def get_votes(self, file_id, limit=10, cursor='""'):
251
        """Retrieve votes for a file.
252
253
        Args:
254
           file_id: SHA-256, SHA-1 or MD5 identifying the file (str).
255
           limit: Maximum number of comments to retrieve (int). The default value is 10.
256
           cursor: Continuation cursor (str). The default value is ''.
257
258
        Return:
259
           The response from the server as a byte sequence.
260
261
        Exception
262
           VirusTotalAPIError(Connection error): In case of server connection errors.
263
           VirusTotalAPIError(Timeout error): If the response timeout from the server is exceeded.
264
        """
265
        self._last_http_error = None
266
        self._last_result = None
267
        query_string = {'limit': str(limit), 'cursor': cursor}
268
        api_url = self.base_url + '/files/' + file_id + '/votes'
269
        try:
270
            response = requests.get(api_url, headers=self.headers, params=query_string,
271
                                    timeout=self.timeout, proxies=self.proxies)
272
        except requests.exceptions.Timeout:
273
            raise VirusTotalAPIError('Timeout error', errno.ETIMEDOUT)
274
        except requests.exceptions.ConnectionError:
275
            raise VirusTotalAPIError('Connection error', errno.ECONNABORTED)
276
        else:
277
            self._last_http_error = response.status_code
278
            self._last_result = response.content
279
            return response.content
280
281
    def put_votes(self, file_id, malicious=False):
282
        """Add a votes to a file.
283
284
        Args:
285
           file_id: SHA-256, SHA-1 or MD5 identifying the file (str).
286
           malicious: Determines a malicious (True) or harmless (False) file (bool).
287
288
        Return:
289
           The response from the server as a byte sequence.
290
291
        Exception
292
           VirusTotalAPIError(Connection error): In case of server connection errors.
293
           VirusTotalAPIError(Timeout error): If the response timeout from the server is exceeded.
294
        """
295
        self._last_http_error = None
296
        self._last_result = None
297
        if malicious:
298
            verdict = 'malicious'
299
        else:
300
            verdict = 'harmless'
301
        votes = {'data': {'type': 'vote', 'attributes': {'verdict': verdict}}}
302
        api_url = self.base_url + '/files/' + file_id + '/votes'
303
        try:
304
            response = requests.post(api_url, headers=self.headers, json=votes,
305
                                     timeout=self.timeout, proxies=self.proxies)
306
        except requests.exceptions.Timeout:
307
            raise VirusTotalAPIError('Timeout error', errno.ETIMEDOUT)
308
        except requests.exceptions.ConnectionError:
309
            raise VirusTotalAPIError('Connection error', errno.ECONNABORTED)
310
        else:
311
            self._last_http_error = response.status_code
312
            self._last_result = response.content
313
            return response.content
314
315
    def get_relationship(self, file_id, relationship='/behaviours', limit=10, cursor='""'):
316
        """Retrieve objects related to a file.
317
318
        Args:
319
           file_id: SHA-256, SHA-1 or MD5 identifying the file (str).
320
           relationship: Relationship name (str). The default value is "/behaviours".
321
           limit: Maximum number of comments to retrieve (int). The default value is 10.
322
           cursor: Continuation cursor (str). The default value is ''.
323
324
        Return:
325
           The response from the server as a byte sequence.
326
327
        Exception
328
           VirusTotalAPIError(Connection error): In case of server connection errors.
329
           VirusTotalAPIError(Timeout error): If the response timeout from the server is exceeded.
330
        """
331
        self._last_http_error = None
332
        self._last_result = None
333
        query_string = {'limit': str(limit), 'cursor': cursor}
334
        api_url = self.base_url + '/files/' + file_id + relationship
335
        try:
336
            response = requests.get(api_url, headers=self.headers, params=query_string,
337
                                    timeout=self.timeout, proxies=self.proxies)
338
        except requests.exceptions.Timeout:
339
            raise VirusTotalAPIError('Timeout error', errno.ETIMEDOUT)
340
        except requests.exceptions.ConnectionError:
341
            raise VirusTotalAPIError('Connection error', errno.ECONNABORTED)
342
        else:
343
            self._last_http_error = response.status_code
344
            self._last_result = response.content
345
            return response.content
346
347
    def get_behaviours(self, sandbox_id):
348
        """Get the PCAP for the sandbox.
349
350
        Args:
351
           sandbox_id: Identifier obtained using the 'get_relationship' method with
352
              the value of the 'relationship' argument equal to 'behaviours' (str).
353
354
        Return:
355
           The response from the server as a byte sequence.
356
357
        Exception
358
           VirusTotalAPIError(Connection error): In case of server connection errors.
359
           VirusTotalAPIError(Timeout error): If the response timeout from the server is exceeded.
360
        """
361
        self._last_http_error = None
362
        self._last_result = None
363
        api_url = self.base_url + '/file_behaviours/' + sandbox_id + '/pcap'
364
        try:
365
            response = requests.get(api_url, headers=self.headers,
366
                                    timeout=self.timeout, proxies=self.proxies)
367
        except requests.exceptions.Timeout:
368
            raise VirusTotalAPIError('Timeout error', errno.ETIMEDOUT)
369
        except requests.exceptions.ConnectionError:
370
            raise VirusTotalAPIError('Connection error', errno.ECONNABORTED)
371
        else:
372
            self._last_http_error = response.status_code
373
            self._last_result = response.content
374
            return response.content
375