summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--binaryninjaapi.h2
-rw-r--r--binaryninjacore.h4
-rw-r--r--downloadprovider.cpp2
-rw-r--r--python/downloadprovider.py99
4 files changed, 56 insertions, 51 deletions
diff --git a/binaryninjaapi.h b/binaryninjaapi.h
index d1467d0f..5c3e0cde 100644
--- a/binaryninjaapi.h
+++ b/binaryninjaapi.h
@@ -4939,7 +4939,7 @@ __attribute__ ((format (printf, 1, 2)))
int PerformRequest(const std::string& url, BNDownloadInstanceOutputCallbacks* callbacks);
int PerformCustomRequest(const std::string& method, const std::string& url, const std::unordered_map<std::string, std::string>& headers, Response& response, BNDownloadInstanceInputOutputCallbacks* callbacks);
- uint64_t ReadDataCallback(uint8_t* data, uint64_t len);
+ int64_t ReadDataCallback(uint8_t* data, uint64_t len);
uint64_t WriteDataCallback(uint8_t* data, uint64_t len);
bool NotifyProgressCallback(uint64_t progress, uint64_t total);
void SetError(const std::string& error);
diff --git a/binaryninjacore.h b/binaryninjacore.h
index bb678720..80f38350 100644
--- a/binaryninjacore.h
+++ b/binaryninjacore.h
@@ -2168,7 +2168,7 @@ extern "C"
struct BNDownloadInstanceInputOutputCallbacks
{
- uint64_t (*readCallback)(uint8_t* data, uint64_t len, void* ctxt);
+ int64_t (*readCallback)(uint8_t* data, uint64_t len, void* ctxt);
void* readContext;
uint64_t (*writeCallback)(uint8_t* data, uint64_t len, void* ctxt);
void* writeContext;
@@ -4783,7 +4783,7 @@ __attribute__ ((format (printf, 1, 2)))
BINARYNINJACOREAPI void BNFreeDownloadInstanceResponse(BNDownloadInstanceResponse* response);
BINARYNINJACOREAPI int BNPerformDownloadRequest(BNDownloadInstance* instance, const char* url, BNDownloadInstanceOutputCallbacks* callbacks);
BINARYNINJACOREAPI int BNPerformCustomRequest(BNDownloadInstance* instance, const char* method, const char* url, uint64_t headerCount, const char* const* headerKeys, const char* const* headerValues, BNDownloadInstanceResponse** response, BNDownloadInstanceInputOutputCallbacks* callbacks);
- BINARYNINJACOREAPI uint64_t BNReadDataForDownloadInstance(BNDownloadInstance* instance, uint8_t* data, uint64_t len);
+ BINARYNINJACOREAPI int64_t BNReadDataForDownloadInstance(BNDownloadInstance* instance, uint8_t* data, uint64_t len);
BINARYNINJACOREAPI uint64_t BNWriteDataForDownloadInstance(BNDownloadInstance* instance, uint8_t* data, uint64_t len);
BINARYNINJACOREAPI bool BNNotifyProgressForDownloadInstance(BNDownloadInstance* instance, uint64_t progress, uint64_t total);
BINARYNINJACOREAPI char* BNGetErrorForDownloadInstance(BNDownloadInstance* instance);
diff --git a/downloadprovider.cpp b/downloadprovider.cpp
index ce703b81..d54a85e3 100644
--- a/downloadprovider.cpp
+++ b/downloadprovider.cpp
@@ -85,7 +85,7 @@ void DownloadInstance::PerformFreeResponse(void* ctxt, BNDownloadInstanceRespons
}
-uint64_t DownloadInstance::ReadDataCallback(uint8_t* data, uint64_t len)
+int64_t DownloadInstance::ReadDataCallback(uint8_t* data, uint64_t len)
{
return BNReadDataForDownloadInstance(m_object, data, len);
}
diff --git a/python/downloadprovider.py b/python/downloadprovider.py
index 2e43128f..b45bda19 100644
--- a/python/downloadprovider.py
+++ b/python/downloadprovider.py
@@ -110,16 +110,25 @@ class DownloadInstance(object):
for i in range(header_count):
headers[header_key_array[i]] = header_value_array[i]
- # Read all data
- data = b''
- while True:
- read_buffer = ctypes.create_string_buffer(0x1000)
- read_len = core.BNReadDataForDownloadInstance(self.handle, ctypes.cast(read_buffer, ctypes.POINTER(ctypes.c_uint8)), 0x1000)
- if read_len == 0:
- break
- data += read_buffer[:read_len]
+ # Generator function that returns a chunk of data on every iteration
+ def data_generator():
+ while True:
+ read_buffer = ctypes.create_string_buffer(0x1000)
+ read_len = core.BNReadDataForDownloadInstance(self.handle, ctypes.cast(read_buffer, ctypes.POINTER(ctypes.c_uint8)), 0x1000)
+ if read_len == 0:
+ break
+ if read_len < 0:
+ raise IOError()
+ yield read_buffer[:read_len]
+
+ try:
+ py_response = self.perform_custom_request(method, url, headers, data_generator())
+ except Exception as e:
+ out_response[0] = None
+ core.BNSetErrorForDownloadInstance(self.handle, e.__class__.__name__)
+ log.log_error(traceback.format_exc())
+ return -1
- py_response = self.perform_custom_request(method, url, headers, data)
if py_response is not None:
# Assign to an instance variable so the memory stays live until the request is done
self.bn_response = core.BNDownloadInstanceResponse()
@@ -152,7 +161,7 @@ class DownloadInstance(object):
raise NotImplementedError
@abc.abstractmethod
- def perform_custom_request(self, method, url, headers, data):
+ def perform_custom_request(self, method, url, headers, data_generator):
raise NotImplementedError
def _read_callback(self, data, len_, ctxt):
@@ -404,37 +413,39 @@ try:
return 0
- def perform_custom_request(self, method, url, headers, data):
- try:
- proxy_setting = settings.Settings().get_string('network.httpsProxy')
- if proxy_setting:
- proxies = {"https": proxy_setting}
- else:
- proxies = None
+ def perform_custom_request(self, method, url, headers, data_generator):
+ proxy_setting = settings.Settings().get_string('network.httpsProxy')
+ if proxy_setting:
+ proxies = {"https": proxy_setting}
+ else:
+ proxies = None
- r = requests.request(pyNativeStr(method), pyNativeStr(url), headers=headers, data=data, proxies=proxies)
- response = r.content
- if len(response) == 0:
- core.BNSetErrorForDownloadInstance(self.handle, "No data received from server!")
- return None
- raw_bytes = (ctypes.c_ubyte * len(response)).from_buffer_copy(response)
+ # Cannot have Content-Length if the body is chunked
+ if b"Content-Length" in headers:
+ del headers[b"Content-Length"]
+
+ r = requests.request(pyNativeStr(method), pyNativeStr(url), headers=headers, data=data_generator, proxies=proxies, stream=True)
+
+ total_size = 0
+ for (key, value) in r.headers.items():
+ if key.lower() == b'content-length':
+ total_size = int(value)
+ break
+
+ bytes_sent = 0
+ for chunk in r.iter_content(None):
+ raw_bytes = (ctypes.c_ubyte * len(chunk)).from_buffer_copy(chunk)
bytes_wrote = core.BNWriteDataForDownloadInstance(self.handle, raw_bytes, len(raw_bytes))
if bytes_wrote != len(raw_bytes):
core.BNSetErrorForDownloadInstance(self.handle, "Bytes written mismatch!")
return None
- continue_download = core.BNNotifyProgressForDownloadInstance(self.handle, bytes_wrote, bytes_wrote)
+ bytes_sent += bytes_wrote
+ continue_download = core.BNNotifyProgressForDownloadInstance(self.handle, bytes_sent, total_size)
if continue_download is False:
core.BNSetErrorForDownloadInstance(self.handle, "Download aborted!")
return None
- return DownloadInstance.Response(r.status_code, r.headers, None)
- except requests.RequestException as e:
- core.BNSetErrorForDownloadInstance(self.handle, e.__class__.__name__)
- return None
- except:
- core.BNSetErrorForDownloadInstance(self.handle, "Unknown Exception!")
- log.log_error(traceback.format_exc())
- return None
+ return DownloadInstance.Response(r.status_code, r.headers, None)
class PythonDownloadProvider(DownloadProvider):
name = "PythonDownloadProvider"
@@ -521,7 +532,7 @@ if not _loaded and (sys.platform != "win32") and (sys.version_info >= (2, 7, 9))
return self._method
return Request.get_method(self, *args, **kwargs)
- def perform_custom_request(self, method, url, headers, data):
+ def perform_custom_request(self, method, url, headers, data_generator):
result = None
try:
proxy_setting = settings.Settings().get_string('network.httpsProxy')
@@ -529,23 +540,21 @@ if not _loaded and (sys.platform != "win32") and (sys.version_info >= (2, 7, 9))
opener = build_opener(ProxyHandler({'https': proxy_setting}))
install_opener(opener)
+ # Cannot have Content-Length if the body is chunked
if b"Content-Length" in headers:
del headers[b"Content-Length"]
- req = PythonDownloadInstance.CustomRequest(pyNativeStr(url), data=data, headers=headers, method=pyNativeStr(method))
+ req = PythonDownloadInstance.CustomRequest(pyNativeStr(url), data=data_generator, headers=headers, method=pyNativeStr(method))
result = urlopen(req)
except HTTPError as he:
result = he
- except URLError as e:
- core.BNSetErrorForDownloadInstance(self.handle, e.__class__.__name__)
- log.log_error(str(e))
- return None
- except:
- core.BNSetErrorForDownloadInstance(self.handle, "Unknown Exception!")
- log.log_error(traceback.format_exc())
- return None
- total_size = int(result.headers.get('content-length', 0))
+ total_size = 0
+ for (key, value) in result.headers.items():
+ if key.lower() == b'content-length':
+ total_size = int(value)
+ break
+
bytes_sent = 0
while True:
data = result.read(4096)
@@ -562,10 +571,6 @@ if not _loaded and (sys.platform != "win32") and (sys.version_info >= (2, 7, 9))
core.BNSetErrorForDownloadInstance(self.handle, "Download aborted!")
return None
- if not bytes_sent:
- core.BNSetErrorForDownloadInstance(self.handle, "Received no data!")
- return None
-
return DownloadInstance.Response(result.getcode(), result.headers, None)
class PythonDownloadProvider(DownloadProvider):