summaryrefslogtreecommitdiff
path: root/rust/src
diff options
context:
space:
mode:
authorMason Reed <mason@vector35.com>2025-09-27 17:39:19 -0400
committerMason Reed <mason@vector35.com>2025-10-22 00:36:24 -0400
commit31ec051fd683d3747f10a0981b497d90f08d25a3 (patch)
tree039f28498656a427297406d90955766981bc0b06 /rust/src
parent058800a116bf25f1f215b758663366d4695f0e31 (diff)
[Rust] Refactor download provider module to allow for custom implementations
Diffstat (limited to 'rust/src')
-rw-r--r--rust/src/download.rs10
-rw-r--r--rust/src/download/instance.rs484
-rw-r--r--rust/src/download/provider.rs124
-rw-r--r--rust/src/download_provider.rs304
-rw-r--r--rust/src/lib.rs2
5 files changed, 619 insertions, 305 deletions
diff --git a/rust/src/download.rs b/rust/src/download.rs
new file mode 100644
index 00000000..4e541a90
--- /dev/null
+++ b/rust/src/download.rs
@@ -0,0 +1,10 @@
+//! Interface for registering new download providers
+//!
+//! WARNING: Do _not_ use this for anything other than provider registration. If you need to perform
+//! http requests, use a real requests library.
+
+mod instance;
+mod provider;
+
+pub use instance::*;
+pub use provider::*;
diff --git a/rust/src/download/instance.rs b/rust/src/download/instance.rs
new file mode 100644
index 00000000..7064024b
--- /dev/null
+++ b/rust/src/download/instance.rs
@@ -0,0 +1,484 @@
+use crate::download::DownloadProvider;
+use crate::headless::is_shutdown_requested;
+use crate::rc::{Ref, RefCountable};
+use crate::string::{strings_to_string_list, BnString, IntoCStr};
+use binaryninjacore_sys::*;
+use std::collections::HashMap;
+use std::ffi::{c_void, CStr};
+use std::mem::{ManuallyDrop, MaybeUninit};
+use std::os::raw::c_char;
+use std::ptr::null_mut;
+use std::slice;
+
+pub trait CustomDownloadInstance: Sized {
+ fn new_with_provider(provider: DownloadProvider) -> Result<Ref<DownloadInstance>, ()> {
+ let instance_uninit = MaybeUninit::uninit();
+ // SAFETY: Download instance is freed by cb_destroy_instance
+ let leaked_instance = Box::leak(Box::new(instance_uninit));
+ let mut callbacks = BNDownloadInstanceCallbacks {
+ context: leaked_instance as *mut _ as *mut c_void,
+ destroyInstance: Some(cb_destroy_instance::<Self>),
+ performRequest: Some(cb_perform_request::<Self>),
+ performCustomRequest: Some(cb_perform_custom_request::<Self>),
+ freeResponse: Some(cb_free_response::<Self>),
+ };
+ let instance_ptr = unsafe { BNInitDownloadInstance(provider.handle, &mut callbacks) };
+ // TODO: If possible pass a sensible error back...
+ let instance_ref = unsafe { DownloadInstance::ref_from_raw(instance_ptr) };
+ // We now have the core instance, so we can actually construct the object.
+ leaked_instance.write(Self::from_core(instance_ref.clone()));
+ Ok(instance_ref)
+ }
+
+ /// Construct the object now that the core object has been created.
+ fn from_core(core: Ref<DownloadInstance>) -> Self;
+
+ /// Get the core object, typically the handle is stored directly on the object.
+ fn handle(&self) -> Ref<DownloadInstance>;
+
+ /// Send an HTTP request on behalf of the caller.
+ ///
+ /// The caller will expect you to inform them of progress via the following:
+ ///
+ /// - [DownloadInstance::read_callback]
+ /// - [DownloadInstance::write_callback]
+ /// - [DownloadInstance::progress_callback]
+ fn perform_request(&self, url: &str) -> Result<(), String> {
+ self.perform_custom_request("GET", url, vec![])?;
+ Ok(())
+ }
+
+ /// Send an HTTP request on behalf of the caller.
+ ///
+ /// The caller will expect you to inform them of progress via the following:
+ ///
+ /// - [DownloadInstance::read_callback]
+ /// - [DownloadInstance::write_callback]
+ /// - [DownloadInstance::progress_callback]
+ fn perform_custom_request<I>(
+ &self,
+ method: &str,
+ url: &str,
+ headers: I,
+ ) -> Result<DownloadResponse, String>
+ where
+ I: IntoIterator<Item = (String, String)>;
+}
+
+// TODO: Change this to a trait?
+pub struct DownloadInstanceOutputCallbacks {
+ pub write: Option<Box<dyn FnMut(&[u8]) -> usize>>,
+ pub progress: Option<Box<dyn FnMut(usize, usize) -> bool>>,
+}
+
+// TODO: Change this to a trait?
+pub struct DownloadInstanceInputOutputCallbacks {
+ pub read: Option<Box<dyn FnMut(&mut [u8]) -> Option<usize>>>,
+ pub write: Option<Box<dyn FnMut(&[u8]) -> usize>>,
+ pub progress: Option<Box<dyn FnMut(usize, usize) -> bool>>,
+}
+
+pub struct DownloadResponse {
+ pub status_code: u16,
+ pub headers: HashMap<String, String>,
+}
+
+/// A reader for a [`DownloadInstance`].
+pub struct DownloadInstanceReader {
+ pub instance: Ref<DownloadInstance>,
+}
+
+impl DownloadInstanceReader {
+ pub fn new(instance: Ref<DownloadInstance>) -> Self {
+ Self { instance }
+ }
+}
+
+impl std::io::Read for DownloadInstanceReader {
+ fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
+ let length = self.instance.read_callback(buf);
+ if length < 0 {
+ Err(std::io::Error::new(
+ std::io::ErrorKind::Interrupted,
+ "Connection interrupted",
+ ))
+ } else {
+ Ok(length as usize)
+ }
+ }
+}
+
+/// A writer for a [`DownloadInstance`].
+pub struct DownloadInstanceWriter {
+ pub instance: Ref<DownloadInstance>,
+ /// The expected length of the download.
+ pub total_length: Option<u64>,
+ /// The current progress of the download.
+ pub progress: u64,
+}
+
+impl DownloadInstanceWriter {
+ pub fn new(instance: Ref<DownloadInstance>, total_length: Option<u64>) -> Self {
+ Self {
+ instance,
+ total_length,
+ progress: 0,
+ }
+ }
+}
+
+impl std::io::Write for DownloadInstanceWriter {
+ fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
+ let length = self.instance.write_callback(buf);
+ if is_shutdown_requested() || length == 0 {
+ Err(std::io::Error::from(std::io::ErrorKind::ConnectionAborted))
+ } else {
+ self.progress += buf.len() as u64;
+ if self
+ .instance
+ .progress_callback(self.progress, self.total_length.unwrap_or(u64::max_value()))
+ {
+ Ok(length as usize)
+ } else {
+ Err(std::io::Error::from(std::io::ErrorKind::ConnectionAborted))
+ }
+ }
+ }
+
+ fn flush(&mut self) -> std::io::Result<()> {
+ Ok(())
+ }
+}
+
+pub struct DownloadInstance {
+ pub(crate) handle: *mut BNDownloadInstance,
+}
+
+impl DownloadInstance {
+ pub(crate) unsafe fn from_raw(handle: *mut BNDownloadInstance) -> Self {
+ debug_assert!(!handle.is_null());
+ Self { handle }
+ }
+
+ pub(crate) unsafe fn ref_from_raw(handle: *mut BNDownloadInstance) -> Ref<Self> {
+ Ref::new(Self::from_raw(handle))
+ }
+
+ fn get_error(&self) -> String {
+ let err: *mut c_char = unsafe { BNGetErrorForDownloadInstance(self.handle) };
+ unsafe { BnString::into_string(err) }
+ }
+
+ /// Sets the error for the instance, any later call to [`DownloadInstance::get_error`] will
+ /// return the string passed here.
+ fn set_error(&self, err: &str) {
+ let err = err.to_cstr();
+ unsafe { BNSetErrorForDownloadInstance(self.handle, err.as_ptr()) };
+ }
+
+ /// Use inside [`CustomDownloadInstance::perform_custom_request`] to pass data back to the caller.
+ pub fn write_callback(&self, data: &[u8]) -> u64 {
+ unsafe {
+ BNWriteDataForDownloadInstance(self.handle, data.as_ptr() as *mut _, data.len() as u64)
+ }
+ }
+
+ /// Use inside [`CustomDownloadInstance::perform_custom_request`] to read data from the caller.
+ pub fn read_callback(&self, data: &mut [u8]) -> i64 {
+ unsafe {
+ BNReadDataForDownloadInstance(
+ self.handle,
+ data.as_mut_ptr() as *mut _,
+ data.len() as u64,
+ )
+ }
+ }
+
+ /// Use inside [`CustomDownloadInstance::perform_custom_request`] to inform the caller of the request progress.
+ pub fn progress_callback(&self, progress: u64, total: u64) -> bool {
+ unsafe { BNNotifyProgressForDownloadInstance(self.handle, progress, total) }
+ }
+
+ pub fn perform_request(
+ &mut self,
+ url: &str,
+ callbacks: &DownloadInstanceOutputCallbacks,
+ ) -> Result<(), String> {
+ let mut cbs = BNDownloadInstanceOutputCallbacks {
+ writeCallback: Some(cb_write_output),
+ writeContext: callbacks as *const _ as *mut c_void,
+ progressCallback: Some(cb_progress_output),
+ progressContext: callbacks as *const _ as *mut c_void,
+ };
+
+ let url_raw = url.to_cstr();
+ let result = unsafe {
+ BNPerformDownloadRequest(
+ self.handle,
+ url_raw.as_ptr(),
+ &mut cbs as *mut BNDownloadInstanceOutputCallbacks,
+ )
+ };
+
+ if result < 0 {
+ Err(self.get_error())
+ } else {
+ Ok(())
+ }
+ }
+
+ pub fn perform_custom_request<I>(
+ &mut self,
+ method: &str,
+ url: &str,
+ headers: I,
+ callbacks: &DownloadInstanceInputOutputCallbacks,
+ ) -> Result<DownloadResponse, String>
+ where
+ I: IntoIterator<Item = (String, String)>,
+ {
+ let mut header_keys = vec![];
+ let mut header_values = vec![];
+ for (key, value) in headers {
+ header_keys.push(key.to_cstr());
+ header_values.push(value.to_cstr());
+ }
+
+ let mut header_key_ptrs = vec![];
+ let mut header_value_ptrs = vec![];
+
+ for (key, value) in header_keys.iter().zip(header_values.iter()) {
+ header_key_ptrs.push(key.as_ptr());
+ header_value_ptrs.push(value.as_ptr());
+ }
+
+ let mut cbs = BNDownloadInstanceInputOutputCallbacks {
+ readCallback: Some(cb_read_input),
+ readContext: callbacks as *const _ as *mut c_void,
+ writeCallback: Some(cb_write_input),
+ writeContext: callbacks as *const _ as *mut c_void,
+ progressCallback: Some(cb_progress_input),
+ progressContext: callbacks as *const _ as *mut c_void,
+ };
+
+ let mut response: *mut BNDownloadInstanceResponse = null_mut();
+
+ let method_raw = method.to_cstr();
+ let url_raw = url.to_cstr();
+ let result = unsafe {
+ BNPerformCustomRequest(
+ self.handle,
+ method_raw.as_ptr(),
+ url_raw.as_ptr(),
+ header_key_ptrs.len() as u64,
+ header_key_ptrs.as_ptr(),
+ header_value_ptrs.as_ptr(),
+ &mut response as *mut *mut BNDownloadInstanceResponse,
+ &mut cbs as *mut BNDownloadInstanceInputOutputCallbacks,
+ )
+ };
+
+ if result < 0 {
+ unsafe { BNFreeDownloadInstanceResponse(response) };
+ return Err(self.get_error());
+ }
+
+ let mut response_headers = HashMap::new();
+ unsafe {
+ let response_header_keys: &[*mut c_char] =
+ slice::from_raw_parts((*response).headerKeys, (*response).headerCount as usize);
+ let response_header_values: &[*mut c_char] =
+ slice::from_raw_parts((*response).headerValues, (*response).headerCount as usize);
+
+ for (key, value) in response_header_keys
+ .iter()
+ .zip(response_header_values.iter())
+ {
+ response_headers.insert(
+ CStr::from_ptr(*key).to_str().unwrap().to_owned(),
+ CStr::from_ptr(*value).to_str().unwrap().to_owned(),
+ );
+ }
+ }
+
+ let r = DownloadResponse {
+ status_code: unsafe { (*response).statusCode },
+ headers: response_headers,
+ };
+
+ unsafe { BNFreeDownloadInstanceResponse(response) };
+
+ Ok(r)
+ }
+}
+
+// TODO: Verify the object is thread safe in the core (hint its not).
+unsafe impl Send for DownloadInstance {}
+unsafe impl Sync for DownloadInstance {}
+
+impl ToOwned for DownloadInstance {
+ type Owned = Ref<Self>;
+
+ fn to_owned(&self) -> Self::Owned {
+ unsafe { RefCountable::inc_ref(self) }
+ }
+}
+
+unsafe impl RefCountable for DownloadInstance {
+ unsafe fn inc_ref(handle: &Self) -> Ref<Self> {
+ Ref::new(Self {
+ handle: BNNewDownloadInstanceReference(handle.handle),
+ })
+ }
+
+ unsafe fn dec_ref(handle: &Self) {
+ BNFreeDownloadInstance(handle.handle);
+ }
+}
+
+unsafe extern "C" fn cb_read_input(data: *mut u8, len: u64, ctxt: *mut c_void) -> i64 {
+ let callbacks = ctxt as *mut DownloadInstanceInputOutputCallbacks;
+ if let Some(func) = &mut (*callbacks).read {
+ let slice = slice::from_raw_parts_mut(data, len as usize);
+ let result = (func)(slice);
+ if let Some(count) = result {
+ count as i64
+ } else {
+ -1
+ }
+ } else {
+ 0
+ }
+}
+
+unsafe extern "C" fn cb_write_input(data: *mut u8, len: u64, ctxt: *mut c_void) -> u64 {
+ let callbacks = ctxt as *mut DownloadInstanceInputOutputCallbacks;
+ if let Some(func) = &mut (*callbacks).write {
+ let slice = slice::from_raw_parts(data, len as usize);
+ let result = (func)(slice);
+ result as u64
+ } else {
+ 0
+ }
+}
+
+unsafe extern "C" fn cb_progress_input(ctxt: *mut c_void, progress: usize, total: usize) -> bool {
+ let callbacks = ctxt as *mut DownloadInstanceInputOutputCallbacks;
+ if let Some(func) = &mut (*callbacks).progress {
+ (func)(progress, total)
+ } else {
+ true
+ }
+}
+
+unsafe extern "C" fn cb_write_output(data: *mut u8, len: u64, ctxt: *mut c_void) -> u64 {
+ let callbacks = ctxt as *mut DownloadInstanceOutputCallbacks;
+ if let Some(func) = &mut (*callbacks).write {
+ let slice = slice::from_raw_parts(data, len as usize);
+ let result = (func)(slice);
+ result as u64
+ } else {
+ 0u64
+ }
+}
+
+unsafe extern "C" fn cb_progress_output(ctxt: *mut c_void, progress: usize, total: usize) -> bool {
+ let callbacks = ctxt as *mut DownloadInstanceOutputCallbacks;
+ if let Some(func) = &mut (*callbacks).progress {
+ (func)(progress, total)
+ } else {
+ true
+ }
+}
+
+pub unsafe extern "C" fn cb_destroy_instance<C: CustomDownloadInstance>(ctxt: *mut c_void) {
+ let _ = Box::from_raw(ctxt as *mut C);
+}
+
+pub unsafe extern "C" fn cb_perform_request<C: CustomDownloadInstance>(
+ ctxt: *mut c_void,
+ url: *const c_char,
+) -> i32 {
+ let c = ManuallyDrop::new(Box::from_raw(ctxt as *mut C));
+
+ let url = match CStr::from_ptr(url).to_str() {
+ Ok(url) => url,
+ Err(e) => {
+ c.handle().set_error(&format!("Invalid URL: {}", e));
+ return -1;
+ }
+ };
+
+ match c.perform_request(url) {
+ Ok(()) => 0,
+ Err(e) => {
+ c.handle().set_error(&e);
+ -1
+ }
+ }
+}
+
+pub unsafe extern "C" fn cb_perform_custom_request<C: CustomDownloadInstance>(
+ ctxt: *mut c_void,
+ method: *const c_char,
+ url: *const c_char,
+ header_count: u64,
+ header_keys: *const *const c_char,
+ header_values: *const *const c_char,
+ response: *mut *mut BNDownloadInstanceResponse,
+) -> i32 {
+ let c = ManuallyDrop::new(Box::from_raw(ctxt as *mut C));
+
+ let method = match CStr::from_ptr(method).to_str() {
+ Ok(method) => method,
+ Err(e) => {
+ c.handle().set_error(&format!("Invalid Method: {}", e));
+ return -1;
+ }
+ };
+
+ let url = match CStr::from_ptr(url).to_str() {
+ Ok(url) => url,
+ Err(e) => {
+ c.handle().set_error(&format!("Invalid URL: {}", e));
+ return -1;
+ }
+ };
+
+ // SAFETY BnString and *mut c_char are transparent
+ let header_count = usize::try_from(header_count).unwrap();
+ let header_keys = slice::from_raw_parts(header_keys as *const BnString, header_count);
+ let header_values = slice::from_raw_parts(header_values as *const BnString, header_count);
+ let header_keys_str = header_keys.iter().map(|s| s.to_string_lossy().to_string());
+ let header_values_str = header_values
+ .iter()
+ .map(|s| s.to_string_lossy().to_string());
+ let headers = header_keys_str.zip(header_values_str);
+
+ match c.perform_custom_request(method, url, headers) {
+ Ok(res) => {
+ let res_header_keys_ptr = strings_to_string_list(res.headers.keys());
+ let res_header_values_ptr = strings_to_string_list(res.headers.values());
+ let raw_response = BNDownloadInstanceResponse {
+ statusCode: res.status_code,
+ headerCount: res.headers.len() as u64,
+ headerKeys: res_header_keys_ptr,
+ headerValues: res_header_values_ptr,
+ };
+ // Leak the response and free it with cb_free_response
+ unsafe { *response = Box::leak(Box::new(raw_response)) };
+ 0
+ }
+ Err(e) => {
+ c.handle().set_error(&e);
+ -1
+ }
+ }
+}
+
+unsafe extern "C" fn cb_free_response<C: CustomDownloadInstance>(
+ _ctxt: *mut c_void,
+ response: *mut BNDownloadInstanceResponse,
+) {
+ let _ = Box::from_raw(response);
+}
diff --git a/rust/src/download/provider.rs b/rust/src/download/provider.rs
new file mode 100644
index 00000000..795c4a7c
--- /dev/null
+++ b/rust/src/download/provider.rs
@@ -0,0 +1,124 @@
+use crate::download::{CustomDownloadInstance, DownloadInstance};
+use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Guard, Ref};
+use crate::settings::Settings;
+use crate::string::IntoCStr;
+use binaryninjacore_sys::*;
+use std::ffi::c_void;
+use std::mem::MaybeUninit;
+
+/// Register a new download provider type, which is used by the core (and other plugins) to make HTTP requests.
+pub fn register_download_provider<C>(name: &str) -> &'static mut C
+where
+ C: CustomDownloadProvider,
+{
+ let name = name.to_cstr();
+ let provider_uninit = MaybeUninit::uninit();
+ // SAFETY: Download provider is never freed
+ let leaked_provider = Box::leak(Box::new(provider_uninit));
+ let result = unsafe {
+ BNRegisterDownloadProvider(
+ name.as_ptr(),
+ &mut BNDownloadProviderCallbacks {
+ context: leaked_provider as *mut _ as *mut c_void,
+ createInstance: Some(cb_create_instance::<C>),
+ },
+ )
+ };
+
+ let provider_core = DownloadProvider::from_raw(result);
+ // We now have the core provider so we can actually construct the object.
+ leaked_provider.write(C::from_core(provider_core));
+ unsafe { leaked_provider.assume_init_mut() }
+}
+
+pub trait CustomDownloadProvider: 'static + Sync {
+ type Instance: CustomDownloadInstance;
+
+ fn handle(&self) -> DownloadProvider;
+
+ /// Called to construct this provider object with the given core object.
+ fn from_core(core: DownloadProvider) -> Self;
+
+ fn create_instance(&self) -> Result<Ref<DownloadInstance>, ()> {
+ Self::Instance::new_with_provider(self.handle())
+ }
+}
+
+#[derive(Copy, Clone)]
+pub struct DownloadProvider {
+ pub(crate) handle: *mut BNDownloadProvider,
+}
+
+impl DownloadProvider {
+ pub(crate) fn from_raw(handle: *mut BNDownloadProvider) -> DownloadProvider {
+ Self { handle }
+ }
+
+ pub fn get(name: &str) -> Option<DownloadProvider> {
+ let name = name.to_cstr();
+ let result = unsafe { BNGetDownloadProviderByName(name.as_ptr()) };
+ if result.is_null() {
+ return None;
+ }
+ Some(DownloadProvider { handle: result })
+ }
+
+ pub fn list() -> Result<Array<DownloadProvider>, ()> {
+ let mut count = 0;
+ let list: *mut *mut BNDownloadProvider = unsafe { BNGetDownloadProviderList(&mut count) };
+
+ if list.is_null() {
+ return Err(());
+ }
+
+ Ok(unsafe { Array::new(list, count, ()) })
+ }
+
+ /// TODO: We may want to `impl Default`, error checking might be preventing us from doing so
+ pub fn try_default() -> Result<DownloadProvider, ()> {
+ let s = Settings::new();
+ let dp_name = s.get_string("network.downloadProviderName");
+ Self::get(&dp_name).ok_or(())
+ }
+
+ pub fn create_instance(&self) -> Result<Ref<DownloadInstance>, ()> {
+ let result: *mut BNDownloadInstance =
+ unsafe { BNCreateDownloadProviderInstance(self.handle) };
+ if result.is_null() {
+ return Err(());
+ }
+
+ Ok(unsafe { DownloadInstance::ref_from_raw(result) })
+ }
+}
+
+impl CoreArrayProvider for DownloadProvider {
+ type Raw = *mut BNDownloadProvider;
+ type Context = ();
+ type Wrapped<'a> = Guard<'a, DownloadProvider>;
+}
+
+unsafe impl CoreArrayProviderInner for DownloadProvider {
+ unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) {
+ BNFreeDownloadProviderList(raw);
+ }
+
+ unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> {
+ Guard::new(DownloadProvider::from_raw(*raw), &())
+ }
+}
+
+unsafe impl Send for DownloadProvider {}
+unsafe impl Sync for DownloadProvider {}
+
+unsafe extern "C" fn cb_create_instance<C: CustomDownloadProvider>(
+ ctxt: *mut c_void,
+) -> *mut BNDownloadInstance {
+ ffi_wrap!("CustomDownloadProvider::cb_create_instance", unsafe {
+ let provider = &*(ctxt as *const C);
+ match provider.create_instance() {
+ Ok(instance) => Ref::into_raw(instance).handle,
+ Err(_) => std::ptr::null_mut(),
+ }
+ })
+}
diff --git a/rust/src/download_provider.rs b/rust/src/download_provider.rs
deleted file mode 100644
index b0c25b42..00000000
--- a/rust/src/download_provider.rs
+++ /dev/null
@@ -1,304 +0,0 @@
-use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable};
-use crate::settings::Settings;
-use crate::string::{BnString, IntoCStr};
-use binaryninjacore_sys::*;
-use std::collections::HashMap;
-use std::ffi::{c_void, CStr};
-use std::os::raw::c_char;
-use std::ptr::null_mut;
-use std::slice;
-
-pub struct DownloadProvider {
- handle: *mut BNDownloadProvider,
-}
-
-impl DownloadProvider {
- pub fn get(name: &str) -> Option<DownloadProvider> {
- let name = name.to_cstr();
- let result = unsafe { BNGetDownloadProviderByName(name.as_ptr()) };
- if result.is_null() {
- return None;
- }
- Some(DownloadProvider { handle: result })
- }
-
- pub fn list() -> Result<Array<DownloadProvider>, ()> {
- let mut count = 0;
- let list: *mut *mut BNDownloadProvider = unsafe { BNGetDownloadProviderList(&mut count) };
-
- if list.is_null() {
- return Err(());
- }
-
- Ok(unsafe { Array::new(list, count, ()) })
- }
-
- /// TODO : We may want to `impl Default`....excessive error checking might be preventing us from doing so
- pub fn try_default() -> Result<DownloadProvider, ()> {
- let s = Settings::new();
- let dp_name = s.get_string("network.downloadProviderName");
- Self::get(&dp_name).ok_or(())
- }
-
- pub(crate) fn from_raw(handle: *mut BNDownloadProvider) -> DownloadProvider {
- Self { handle }
- }
-
- pub fn create_instance(&self) -> Result<Ref<DownloadInstance>, ()> {
- let result: *mut BNDownloadInstance =
- unsafe { BNCreateDownloadProviderInstance(self.handle) };
- if result.is_null() {
- return Err(());
- }
-
- Ok(unsafe { DownloadInstance::ref_from_raw(result) })
- }
-}
-
-impl CoreArrayProvider for DownloadProvider {
- type Raw = *mut BNDownloadProvider;
- type Context = ();
- type Wrapped<'a> = Guard<'a, DownloadProvider>;
-}
-
-unsafe impl CoreArrayProviderInner for DownloadProvider {
- unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) {
- BNFreeDownloadProviderList(raw);
- }
-
- unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> {
- Guard::new(DownloadProvider::from_raw(*raw), &())
- }
-}
-
-pub struct DownloadInstanceOutputCallbacks {
- pub write: Option<Box<dyn FnMut(&[u8]) -> usize>>,
- pub progress: Option<Box<dyn FnMut(usize, usize) -> bool>>,
-}
-
-pub struct DownloadInstanceInputOutputCallbacks {
- pub read: Option<Box<dyn FnMut(&mut [u8]) -> Option<isize>>>,
- pub write: Option<Box<dyn FnMut(&[u8]) -> usize>>,
- pub progress: Option<Box<dyn FnMut(usize, usize) -> bool>>,
-}
-
-pub struct DownloadResponse {
- pub status_code: u16,
- pub headers: HashMap<String, String>,
-}
-
-pub struct DownloadInstance {
- handle: *mut BNDownloadInstance,
-}
-
-impl DownloadInstance {
- pub(crate) unsafe fn from_raw(handle: *mut BNDownloadInstance) -> Self {
- debug_assert!(!handle.is_null());
-
- Self { handle }
- }
-
- pub(crate) unsafe fn ref_from_raw(handle: *mut BNDownloadInstance) -> Ref<Self> {
- Ref::new(Self::from_raw(handle))
- }
-
- fn get_error(&self) -> String {
- let err: *mut c_char = unsafe { BNGetErrorForDownloadInstance(self.handle) };
- unsafe { BnString::into_string(err) }
- }
-
- unsafe extern "C" fn o_write_callback(data: *mut u8, len: u64, ctxt: *mut c_void) -> u64 {
- let callbacks = ctxt as *mut DownloadInstanceOutputCallbacks;
- if let Some(func) = &mut (*callbacks).write {
- let slice = slice::from_raw_parts(data, len as usize);
- let result = (func)(slice);
- result as u64
- } else {
- 0u64
- }
- }
-
- unsafe extern "C" fn o_progress_callback(
- ctxt: *mut c_void,
- progress: usize,
- total: usize,
- ) -> bool {
- let callbacks = ctxt as *mut DownloadInstanceOutputCallbacks;
- if let Some(func) = &mut (*callbacks).progress {
- (func)(progress, total)
- } else {
- true
- }
- }
-
- pub fn perform_request(
- &mut self,
- url: &str,
- callbacks: &DownloadInstanceOutputCallbacks,
- ) -> Result<(), String> {
- let mut cbs = BNDownloadInstanceOutputCallbacks {
- writeCallback: Some(Self::o_write_callback),
- writeContext: callbacks as *const _ as *mut c_void,
- progressCallback: Some(Self::o_progress_callback),
- progressContext: callbacks as *const _ as *mut c_void,
- };
-
- let url_raw = url.to_cstr();
- let result = unsafe {
- BNPerformDownloadRequest(
- self.handle,
- url_raw.as_ptr(),
- &mut cbs as *mut BNDownloadInstanceOutputCallbacks,
- )
- };
-
- if result < 0 {
- Err(self.get_error())
- } else {
- Ok(())
- }
- }
-
- unsafe extern "C" fn i_read_callback(data: *mut u8, len: u64, ctxt: *mut c_void) -> i64 {
- let callbacks = ctxt as *mut DownloadInstanceInputOutputCallbacks;
- if let Some(func) = &mut (*callbacks).read {
- let slice = slice::from_raw_parts_mut(data, len as usize);
- let result = (func)(slice);
- if let Some(count) = result {
- count as i64
- } else {
- -1
- }
- } else {
- 0
- }
- }
-
- unsafe extern "C" fn i_write_callback(data: *mut u8, len: u64, ctxt: *mut c_void) -> u64 {
- let callbacks = ctxt as *mut DownloadInstanceInputOutputCallbacks;
- if let Some(func) = &mut (*callbacks).write {
- let slice = slice::from_raw_parts(data, len as usize);
- let result = (func)(slice);
- result as u64
- } else {
- 0
- }
- }
-
- unsafe extern "C" fn i_progress_callback(
- ctxt: *mut c_void,
- progress: usize,
- total: usize,
- ) -> bool {
- let callbacks = ctxt as *mut DownloadInstanceInputOutputCallbacks;
- if let Some(func) = &mut (*callbacks).progress {
- (func)(progress, total)
- } else {
- true
- }
- }
-
- pub fn perform_custom_request<I>(
- &mut self,
- method: &str,
- url: &str,
- headers: I,
- callbacks: &DownloadInstanceInputOutputCallbacks,
- ) -> Result<DownloadResponse, String>
- where
- I: IntoIterator<Item = (String, String)>,
- {
- let mut header_keys = vec![];
- let mut header_values = vec![];
- for (key, value) in headers {
- header_keys.push(key.to_cstr());
- header_values.push(value.to_cstr());
- }
-
- let mut header_key_ptrs = vec![];
- let mut header_value_ptrs = vec![];
-
- for (key, value) in header_keys.iter().zip(header_values.iter()) {
- header_key_ptrs.push(key.as_ptr());
- header_value_ptrs.push(value.as_ptr());
- }
-
- let mut cbs = BNDownloadInstanceInputOutputCallbacks {
- readCallback: Some(Self::i_read_callback),
- readContext: callbacks as *const _ as *mut c_void,
- writeCallback: Some(Self::i_write_callback),
- writeContext: callbacks as *const _ as *mut c_void,
- progressCallback: Some(Self::i_progress_callback),
- progressContext: callbacks as *const _ as *mut c_void,
- };
-
- let mut response: *mut BNDownloadInstanceResponse = null_mut();
-
- let method_raw = method.to_cstr();
- let url_raw = url.to_cstr();
- let result = unsafe {
- BNPerformCustomRequest(
- self.handle,
- method_raw.as_ptr(),
- url_raw.as_ptr(),
- header_key_ptrs.len() as u64,
- header_key_ptrs.as_ptr(),
- header_value_ptrs.as_ptr(),
- &mut response as *mut *mut BNDownloadInstanceResponse,
- &mut cbs as *mut BNDownloadInstanceInputOutputCallbacks,
- )
- };
-
- if result < 0 {
- unsafe { BNFreeDownloadInstanceResponse(response) };
- return Err(self.get_error());
- }
-
- let mut response_headers = HashMap::new();
- unsafe {
- let response_header_keys: &[*mut c_char] =
- slice::from_raw_parts((*response).headerKeys, (*response).headerCount as usize);
- let response_header_values: &[*mut c_char] =
- slice::from_raw_parts((*response).headerValues, (*response).headerCount as usize);
-
- for (key, value) in response_header_keys
- .iter()
- .zip(response_header_values.iter())
- {
- response_headers.insert(
- CStr::from_ptr(*key).to_str().unwrap().to_owned(),
- CStr::from_ptr(*value).to_str().unwrap().to_owned(),
- );
- }
- }
-
- let r = DownloadResponse {
- status_code: unsafe { (*response).statusCode },
- headers: response_headers,
- };
-
- unsafe { BNFreeDownloadInstanceResponse(response) };
-
- Ok(r)
- }
-}
-
-impl ToOwned for DownloadInstance {
- type Owned = Ref<Self>;
-
- fn to_owned(&self) -> Self::Owned {
- unsafe { RefCountable::inc_ref(self) }
- }
-}
-
-unsafe impl RefCountable for DownloadInstance {
- unsafe fn inc_ref(handle: &Self) -> Ref<Self> {
- Ref::new(Self {
- handle: BNNewDownloadInstanceReference(handle.handle),
- })
- }
-
- unsafe fn dec_ref(handle: &Self) {
- BNFreeDownloadInstance(handle.handle);
- }
-}
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
index c9b0bb0a..476cd04c 100644
--- a/rust/src/lib.rs
+++ b/rust/src/lib.rs
@@ -44,7 +44,7 @@ pub mod database;
pub mod debuginfo;
pub mod demangle;
pub mod disassembly;
-pub mod download_provider;
+pub mod download;
pub mod enterprise;
pub mod external_library;
pub mod file_accessor;