1313from ReversingLabs .SDK .helper import DEFAULT_USER_AGENT , RESPONSE_CODE_ERROR_MAP , \
1414 RequestTimeoutError , WrongInputError
1515
16+ try :
17+ from requests_toolbelt import MultipartEncoder
18+ _TOOLBELT_INSTALLED = True
19+ except ImportError :
20+ _TOOLBELT_INSTALLED = False
21+
1622
1723class TitaniumScale (object ):
1824
@@ -81,7 +87,7 @@ def test_connection(self):
8187
8288 return
8389
84- def upload_sample_from_path (self , file_path , custom_token = None , user_data = None , custom_data = None ):
90+ def upload_sample_from_path (self , file_path , custom_token = None , user_data = None , custom_data = None , large_file = False ):
8591 """Accepts a file path string for file upload and returns a response.
8692 :param file_path: path to file
8793 :type file_path: str
@@ -93,6 +99,8 @@ def upload_sample_from_path(self, file_path, custom_token=None, user_data=None,
9399 :param custom_data: user-defined data in the form of a JSON string; this data is
94100 included in file analysis reports
95101 :type custom_data: str
102+ :param large_file: Set to True if file size exceeds 1 GB
103+ :type large_file: bool
96104 :return: response
97105 :rtype: requests.Response
98106 """
@@ -104,18 +112,28 @@ def upload_sample_from_path(self, file_path, custom_token=None, user_data=None,
104112 except IOError as error :
105113 raise WrongInputError ("Error while opening file in 'rb' mode - {error}" .format (error = str (error )))
106114
107- response = self .__upload_files (
108- file_handle = file_handle ,
109- custom_token = custom_token ,
110- user_data = user_data ,
111- custom_data = custom_data
112- )
115+ if large_file :
116+ response = self .__upload_large_file (
117+ file_handle = file_handle ,
118+ custom_token = custom_token ,
119+ user_data = user_data ,
120+ custom_data = custom_data
121+ )
122+
123+ else :
124+ response = self .__upload_files (
125+ file_handle = file_handle ,
126+ custom_token = custom_token ,
127+ user_data = user_data ,
128+ custom_data = custom_data
129+ )
113130
114131 self .__raise_on_error (response )
115132
116133 return response
117134
118- def upload_sample_from_file (self , file_source , custom_token = None , user_data = None , custom_data = None ):
135+ def upload_sample_from_file (self , file_source , custom_token = None , user_data = None , custom_data = None ,
136+ large_file = False ):
119137 """Accepts an open file in 'rb' mode for file upload and returns a response.
120138 :param file_source: open file
121139 :type file_source: file or BinaryIO
@@ -127,18 +145,29 @@ def upload_sample_from_file(self, file_source, custom_token=None, user_data=None
127145 :param custom_data: user-defined data in the form of a JSON string; this data is
128146 included in file analysis reports
129147 :type custom_data: str
148+ :param large_file: Set to True if file size exceeds 1 GB
149+ :type large_file: bool
130150 :return: response
131151 :rtype: requests.Response
132152 """
133153 if not hasattr (file_source , "read" ):
134154 raise WrongInputError ("file_source parameter must be a file open in 'rb' mode." )
135155
136- response = self .__upload_files (
137- file_handle = file_source ,
138- custom_token = custom_token ,
139- user_data = user_data ,
140- custom_data = custom_data
141- )
156+ if large_file :
157+ response = self .__upload_large_file (
158+ file_handle = file_source ,
159+ custom_token = custom_token ,
160+ user_data = user_data ,
161+ custom_data = custom_data
162+ )
163+
164+ else :
165+ response = self .__upload_files (
166+ file_handle = file_source ,
167+ custom_token = custom_token ,
168+ user_data = user_data ,
169+ custom_data = custom_data
170+ )
142171
143172 self .__raise_on_error (response )
144173
@@ -174,7 +203,7 @@ def get_results(self, task_url, full_report=False):
174203 return None
175204
176205 def upload_sample_and_get_results (self , file_path = None , file_source = None , full_report = False , custom_token = None ,
177- user_data = None , custom_data = None ):
206+ user_data = None , custom_data = None , large_file = False ):
178207 """Accepts either a file path string or an open file in 'rb' mode for file upload
179208 and returns an analysis report response.
180209 This method combines uploading a sample and obtaining the analysis results.
@@ -194,6 +223,8 @@ def upload_sample_and_get_results(self, file_path=None, file_source=None, full_r
194223 :param custom_data: user-defined data in the form of a JSON string; this data is
195224 included in file analysis reports
196225 :type custom_data: str
226+ :param large_file: Set to True if file size exceeds 1 GB
227+ :type large_file: bool
197228 :return: response
198229 :rtype: requests.Response
199230 """
@@ -206,14 +237,16 @@ def upload_sample_and_get_results(self, file_path=None, file_source=None, full_r
206237 file_path = file_path ,
207238 custom_token = custom_token ,
208239 user_data = user_data ,
209- custom_data = custom_data
240+ custom_data = custom_data ,
241+ large_file = large_file
210242 )
211243 else :
212244 upload_response = self .upload_sample_from_file (
213245 file_source = file_source ,
214246 custom_token = custom_token ,
215247 user_data = user_data ,
216- custom_data = custom_data
248+ custom_data = custom_data ,
249+ large_file = large_file
217250 )
218251
219252 task_url = upload_response .json ().get ("task_url" )
@@ -252,6 +285,75 @@ def __get_results(self, task_url, full_report=False):
252285
253286 return response
254287
288+ def __upload_large_file (self , file_handle , custom_token , user_data , custom_data ):
289+ """A generic POST request method for all TitaniumScale methods,
290+ optimized for streaming large files using requests-toolbelt.
291+ :param file_handle: files to send
292+ :type file_handle: file or BinaryIO
293+ :param custom_token: set custom token string for filtering processing tasks (X-TiScale-Token)
294+ :type custom_token: str
295+ :param user_data: user-defined data in the form of a JSON string; this data is
296+ NOT included in file analysis reports
297+ :type user_data: str
298+ :param custom_data: user-defined data in the form of a JSON string; this data is
299+ included in file analysis reports
300+ :type custom_data: str
301+ :return: response
302+ :rtype: requests.Response
303+ """
304+ if not _TOOLBELT_INSTALLED :
305+ raise ImportError ("To use large file upload, you need to run "
306+ "'pip install reversinglabs-sdk-py3[large_files]' because additional dependencies "
307+ "are required." )
308+
309+ request_headers = self ._headers .copy ()
310+
311+ if custom_token is not None :
312+ if not isinstance (custom_token , str ):
313+ raise WrongInputError ("custom_token parameter must be string." )
314+
315+ request_headers ["X-TiScale-Token" ] = "Token {custom_token}" .format (custom_token = custom_token )
316+
317+ fields = {}
318+
319+ if user_data is not None :
320+ try :
321+ json .loads (user_data )
322+ fields ["user_data" ] = user_data
323+
324+ except (TypeError , json .decoder .JSONDecodeError ):
325+ raise WrongInputError ("user_data parameter must be a valid JSON string." )
326+
327+ if custom_data is not None :
328+ try :
329+ json .loads (custom_data )
330+ fields ["custom_data" ] = custom_data
331+
332+ except (TypeError , json .decoder .JSONDecodeError ):
333+ raise WrongInputError ("custom_data parameter must be a valid JSON string." )
334+
335+ filename = "file"
336+
337+ fields ["file" ] = (filename , file_handle , "application/octet-stream" )
338+
339+ multipart_encoder = MultipartEncoder (fields = fields )
340+
341+ url = self ._url .format (endpoint = self .__UPLOAD_ENDPOINT )
342+
343+ request_headers ["User-Agent" ] = (f"{ self ._user_agent } ; { self .__class__ .__name__ } "
344+ f"{ inspect .currentframe ().f_back .f_code .co_name } " )
345+ request_headers ["Content-Type" ] = multipart_encoder .content_type
346+
347+ response = requests .post (
348+ url = url ,
349+ data = multipart_encoder ,
350+ verify = self ._verify ,
351+ proxies = self ._proxies ,
352+ headers = request_headers
353+ )
354+
355+ return response
356+
255357 def __upload_files (self , file_handle , custom_token , user_data , custom_data ):
256358 """A generic POST request method for all TitaniumScale methods.
257359 :param file_handle: files to send
0 commit comments