diff options
| author | Mason Reed <mason@vector35.com> | 2025-10-18 20:16:39 -0400 |
|---|---|---|
| committer | Mason Reed <mason@vector35.com> | 2025-10-22 00:36:25 -0400 |
| commit | ea914efbe24c80b6e1523471a7369d79ac07deb0 (patch) | |
| tree | 61c0fcd05a49dc72bb83e81dd7340e59a41f2ed5 /plugins/warp/src | |
| parent | 92656ce40229cd9962febe57678d7ae5378b31a0 (diff) | |
[WARP] Use download provider API instead of reqwest
Unfortunately we cannot use reqwest because enterprise servers provide invalid certificates, so we must use the enterprise download provider to bypass certain certificate validations
Diffstat (limited to 'plugins/warp/src')
| -rw-r--r-- | plugins/warp/src/container.rs | 4 | ||||
| -rw-r--r-- | plugins/warp/src/container/network.rs | 110 | ||||
| -rw-r--r-- | plugins/warp/src/container/network/client.rs | 386 | ||||
| -rw-r--r-- | plugins/warp/src/plugin.rs | 92 |
4 files changed, 277 insertions, 315 deletions
diff --git a/plugins/warp/src/container.rs b/plugins/warp/src/container.rs index 8c76bc8f..b5e92bc7 100644 --- a/plugins/warp/src/container.rs +++ b/plugins/warp/src/container.rs @@ -35,6 +35,10 @@ pub enum ContainerError { FailedIO(io::ErrorKind), #[error("source {0} does not have an available path")] SourcePathUnavailable(SourceId), + #[error("search failed: {0}")] + SearchFailed(String), + #[error("failed to commit source '{0}': {1}")] + CommitFailed(SourceId, String), } /// Represents the ID for a single container source. diff --git a/plugins/warp/src/container/network.rs b/plugins/warp/src/container/network.rs index 21ccf05f..d0ccd3ea 100644 --- a/plugins/warp/src/container/network.rs +++ b/plugins/warp/src/container/network.rs @@ -115,26 +115,33 @@ impl NetworkContainer { let mut result: HashMap<SourceId, Vec<FunctionGUID>> = HashMap::new(); // Only query server for unknown guids if we have any. if !unknown.is_empty() { - if let Some(queried_results) = - self.client + let queried_results = + match self + .client .query_functions_source(Some(target_id), tags, &unknown) - { - // Cache the new results, this means we will not try and contact the server for that guids source. - // NOTE: Here we do not just simply list the queried results because we also - // want to cache function guids which have no source, this is important so that we never - // attempt to contact the server for that guid. - for guid in &unknown { - let sources = queried_results - .keys() - .filter(|source_id| queried_results[source_id].contains(guid)) - .copied() - .collect(); - self.known_function_sources.insert(*guid, sources); - } + { + Ok(queried_results) => queried_results, + Err(e) => { + log::error!("Failed to query functions source: {}", e); + return result; + } + }; - for (source_id, guids) in queried_results { - result.entry(source_id).or_default().extend(guids); - } + // Cache the new results, this means we will not try and contact the server for that guids source. + // NOTE: Here we do not just simply list the queried results because we also + // want to cache function guids which have no source, this is important so that we never + // attempt to contact the server for that guid. + for guid in &unknown { + let sources = queried_results + .keys() + .filter(|source_id| queried_results[source_id].contains(guid)) + .copied() + .collect(); + self.known_function_sources.insert(*guid, sources); + } + + for (source_id, guids) in queried_results { + result.entry(source_id).or_default().extend(guids); } } @@ -157,34 +164,40 @@ impl NetworkContainer { functions: &[FunctionGUID], ) { let target_id = self.get_target_id(target); - if let Some(file) = self + let file = match self .client .query_functions(target_id, Some(*source), functions) { - log::debug!("Got {} chunks from server", file.chunks.len()); - for chunk in &file.chunks { - match &chunk.kind { - ChunkKind::Signature(sc) => { - let functions: Vec<_> = sc.functions().collect(); - // Probe the source before attempting to access it, as it might not exist locally. - self.probe_source(*source); - match self.cache.add_functions(target, source, &functions) { - Ok(_) => log::debug!( - "Added {} functions into cached source '{}'", - functions.len(), - source - ), - Err(err) => log::error!( - "Failed to add {} function into cached source '{}': {}", - functions.len(), - source, - err - ), - } + Ok(file) => file, + Err(e) => { + log::error!("Failed to query functions: {}", e); + return; + } + }; + + log::debug!("Got {} chunks from server", file.chunks.len()); + for chunk in &file.chunks { + match &chunk.kind { + ChunkKind::Signature(sc) => { + let functions: Vec<_> = sc.functions().collect(); + // Probe the source before attempting to access it, as it might not exist locally. + self.probe_source(*source); + match self.cache.add_functions(target, source, &functions) { + Ok(_) => log::debug!( + "Added {} functions into cached source '{}'", + functions.len(), + source + ), + Err(err) => log::error!( + "Failed to add {} function into cached source '{}': {}", + functions.len(), + source, + err + ), } - // TODO; Probably want to pull type in with this. - ChunkKind::Type(_) => {} } + // TODO; Probably want to pull type in with this. + ChunkKind::Type(_) => {} } } } @@ -192,10 +205,10 @@ impl NetworkContainer { /// Push a file to the network source. /// /// **This is blocking** - pub fn push_file(&mut self, source_id: SourceId, file: &WarpFile) { + pub fn push_file(&mut self, source_id: SourceId, file: &WarpFile) -> Result<i32, String> { // TODO: We need a better name for the commit. I would like to derive it automatically from // TODO: something instead of having the user give it TBH. - self.client.push_file(source_id, file, "commit"); + self.client.push_file(source_id, file, "commit") } /// Probe the source to make sure it exists in the cache. Retrieving the name from the server. @@ -261,10 +274,14 @@ impl Container for NetworkContainer { .added_chunks .remove(source) .ok_or(ContainerError::SourceNotFound(source.clone()))?; + if chunks.is_empty() { + return Ok(false); + } // Because each add operation is its own chunk, we should merge them into larger chunks before sending. let merged_chunks = Chunk::merge(&chunks, CompressionType::Zstd); let file = WarpFile::new(WarpFileHeader::new(), merged_chunks); - self.push_file(*source, &file); + self.push_file(*source, &file) + .map_err(|e| ContainerError::CommitFailed(*source, e))?; Ok(true) } @@ -407,12 +424,9 @@ impl Container for NetworkContainer { } fn search(&self, query: &ContainerSearchQuery) -> ContainerResult<ContainerSearchResponse> { - // TODO: Give this an actual network error. self.client .search(query) - .ok_or(ContainerError::CorruptedData( - "search query failed to validate", - )) + .map_err(|e| ContainerError::SearchFailed(e.to_string())) } } diff --git a/plugins/warp/src/container/network/client.rs b/plugins/warp/src/container/network/client.rs index 39e7640a..d2217bce 100644 --- a/plugins/warp/src/container/network/client.rs +++ b/plugins/warp/src/container/network/client.rs @@ -3,9 +3,8 @@ use crate::container::{ ContainerSearchItem, ContainerSearchItemKind, ContainerSearchQuery, ContainerSearchResponse, SourceId, SourcePath, SourceTag, }; -use reqwest::blocking::Client; -use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION}; -use reqwest::StatusCode; +use base64::Engine; +use binaryninja::download::DownloadProvider; use serde::Deserialize; use serde_json::json; use std::collections::HashMap; @@ -23,42 +22,23 @@ use warp::WarpFile; /// NOTE: **All requests are blocking**. #[derive(Clone, Debug)] pub struct NetworkClient { - client: Client, + provider: DownloadProvider, + headers: Vec<(String, String)>, pub server_url: String, } impl NetworkClient { - pub fn new( - server_url: String, - server_token: Option<String>, - https_proxy: Option<String>, - ) -> reqwest::Result<Self> { - let version_info = binaryninja::version_info(); - // TODO: IIRC we had a user agent format already for some other thing. - let client_agent = format!( - "Binary Ninja/{}.{}.{}", - version_info.major, version_info.minor, version_info.build - ); + pub fn new(server_url: String, server_token: Option<String>) -> Self { // TODO: This might want to be kept for the request header? - let mut headers = HeaderMap::new(); + let mut headers: Vec<(String, String)> = vec![]; if let Some(token) = &server_token { - headers.insert( - AUTHORIZATION, - HeaderValue::from_str(&format!("Bearer {}", token)).unwrap(), - ); + headers.push(("authorization".to_string(), format!("Bearer {}", token))); } - // TODO: Configurable timeout? - let mut client_builder = Client::builder() - .connect_timeout(std::time::Duration::from_secs(10)) - .default_headers(headers) - .user_agent(client_agent); - if let Some(https_proxy) = https_proxy { - client_builder = client_builder.proxy(reqwest::Proxy::all(&https_proxy)?); - } - Ok(Self { - client: client_builder.build()?, + Self { + provider: DownloadProvider::try_default().unwrap(), + headers, server_url, - }) + } } /// Check to see the status of the server. @@ -67,10 +47,14 @@ impl NetworkClient { /// server that is unresponsive. /// /// Route: `api/v1/status` - pub fn status(&self) -> reqwest::Result<StatusCode> { + pub fn status(&self) -> Result<(), String> { let status_url = format!("{}/api/v1/status", self.server_url); - let resp = self.client.get(&status_url).send()?; - Ok(resp.status()) + let mut inst = self.provider.create_instance().unwrap(); + let resp = inst.get(&status_url, self.headers.clone())?; + match resp.is_success() { + true => Ok(()), + false => Err(format!("Server returned an error: {}", resp.status_code)), + } } /// Query the logged in user. @@ -78,8 +62,9 @@ impl NetworkClient { /// NOTE: **THIS IS BLOCKING** /// /// Route: `api/v1/users/me` (TODO: Comment about the query) - pub fn current_user(&self) -> reqwest::Result<(i32, String)> { + pub fn current_user(&self) -> Result<(i32, String), String> { let current_user_url = format!("{}/api/v1/users/me", self.server_url); + let mut inst = self.provider.create_instance().unwrap(); #[derive(Deserialize)] struct CurrentUser { @@ -87,12 +72,14 @@ impl NetworkClient { id: i32, } - let resp = self - .client - .get(¤t_user_url) - .send()? - .error_for_status()?; - let user: CurrentUser = resp.json()?; + let resp = inst.get(¤t_user_url, self.headers.clone())?; + if !resp.is_success() { + return Err(format!( + "'{}' returned {}", + current_user_url, resp.status_code + )); + } + let user: CurrentUser = resp.json().map_err(|e| e.to_string())?; Ok((user.id, user.username)) } @@ -101,16 +88,20 @@ impl NetworkClient { /// NOTE: **THIS IS BLOCKING** /// /// Route: `api/v1/users/me` (TODO: Comment about the query) - pub fn source_name(&self, id: SourceId) -> reqwest::Result<String> { + pub fn source_name(&self, id: SourceId) -> Result<String, String> { let source_url = format!("{}/api/v1/sources/{}", self.server_url, id); + let mut inst = self.provider.create_instance().unwrap(); #[derive(Deserialize)] struct Source { name: String, } - let resp = self.client.get(&source_url).send()?.error_for_status()?; - let src: Source = resp.json()?; + let resp = inst.get(&source_url, self.headers.clone())?; + if !resp.is_success() { + return Err(format!("'{}' returned {}", source_url, resp.status_code)); + } + let src: Source = resp.json().map_err(|e| e.to_string())?; Ok(src.name) } @@ -123,8 +114,9 @@ impl NetworkClient { /// NOTE: **THIS IS BLOCKING** /// /// Route: `api/v1/sources/` - pub fn create_source(&self, name: &str) -> reqwest::Result<SourceId> { + pub fn create_source(&self, name: &str) -> Result<SourceId, String> { let source_url = format!("{}/api/v1/sources", self.server_url); + let mut inst = self.provider.create_instance().unwrap(); let body = json!({ "name": name, @@ -137,14 +129,11 @@ impl NetworkClient { id: Uuid, } - let resp = self - .client - .post(&source_url) - .json(&body) - .send()? - .error_for_status()?; - - let parsed: CreateSourceResponse = resp.json()?; + let resp = inst.post_json(&source_url, self.headers.clone(), &body)?; + if !resp.is_success() { + return Err(format!("'{}' returned {}", source_url, resp.status_code)); + } + let parsed: CreateSourceResponse = resp.json().map_err(|e| e.to_string())?; Ok(SourceId(parsed.id)) } @@ -153,8 +142,9 @@ impl NetworkClient { /// NOTE: **THIS IS BLOCKING** /// /// Route: `api/v1/sources/query` (TODO: Comment about the query) - pub fn query_sources(&self, user_id: Option<i32>) -> reqwest::Result<Vec<SourceId>> { + pub fn query_sources(&self, user_id: Option<i32>) -> Result<Vec<SourceId>, String> { let sources_url = format!("{}/api/v1/sources/query", self.server_url); + let mut inst = self.provider.create_instance().unwrap(); #[derive(Deserialize)] struct SourceItem { @@ -170,16 +160,12 @@ impl NetworkClient { if let Some(user_id) = user_id { query.insert("user_id", user_id); } - let query_str = json!(query).to_string(); - let resp = self - .client - .post(&sources_url) - .body(query_str) - .header("Content-Type", "application/json") - .send()? - .error_for_status()?; - let parsed: SourcesQueryResponse = resp.json()?; + let resp = inst.post_json(&sources_url, self.headers.clone(), &json!(query))?; + if !resp.is_success() { + return Err(format!("'{}' returned {}", sources_url, resp.status_code)); + } + let parsed: SourcesQueryResponse = resp.json().map_err(|e| e.to_string())?; Ok(parsed.items.into_iter().map(|it| SourceId(it.id)).collect()) } @@ -190,6 +176,12 @@ impl NetworkClient { /// Route: `api/v1/targets/query` (TODO: Comment about the query) pub fn query_target_id(&self, target: &Target) -> Option<NetworkTargetId> { let query_target_url = format!("{}/api/v1/targets/query", self.server_url); + let mut inst = self.provider.create_instance().unwrap(); + + #[derive(Deserialize)] + struct TargetQueryResponse { + id: NetworkTargetId, + } let mut query = HashMap::new(); if let Some(platform) = &target.platform { @@ -198,25 +190,13 @@ impl NetworkClient { if let Some(architecture) = &target.architecture { query.insert("arch", architecture); } - let query_str = json!(query).to_string(); - - #[derive(Deserialize)] - struct TargetQueryResponse { - id: NetworkTargetId, - } - // NOTE: This is blocking. - let response = self - .client - .post(query_target_url) - .body(query_str) - .header("Content-Type", "application/json") - .send() + let resp = inst + .post_json(&query_target_url, self.headers.clone(), &json!(query)) .ok()?; - // Assuming the first response is the one we want. // TODO: Handle multiple responses, or error out. - let json_response: Vec<TargetQueryResponse> = response.json().ok()?; + let json_response: Vec<TargetQueryResponse> = resp.json().ok()?; let first_response = json_response.first()?; Some(first_response.id) @@ -257,27 +237,22 @@ impl NetworkClient { target: Option<NetworkTargetId>, source: Option<SourceId>, guids: &[FunctionGUID], - ) -> Option<WarpFile<'static>> { + ) -> Result<WarpFile<'static>, String> { let query_functions_url = format!("{}/api/v1/functions/query", self.server_url); // TODO: Allow for source tags? We really only need this in query_functions_source as that // TODO: is what prevents a undesired source from being "known" to the container. let payload = Self::query_functions_body(target, source, &[], guids); + let mut inst = self.provider.create_instance().unwrap(); - // Make the POST request - let response = self - .client - .post(&query_functions_url) - .json(&payload) - .send() - .ok()?; - if !response.status().is_success() { - log::error!("Failed to query functions: {}", response.status()); - return None; + let resp = inst.post_json(&query_functions_url, self.headers.clone(), &payload)?; + if !resp.is_success() { + return Err(format!( + "'{}' returned {}", + query_functions_url, resp.status_code + )); } - // Get response bytes and convert to WarpFile - let bytes = response.bytes().ok()?; - WarpFile::from_owned_bytes(bytes.to_vec()) + WarpFile::from_owned_bytes(resp.data).ok_or("Failed to parse WARP data".to_string()) } /// Query the functions, returning the sources and the corresponding function guids. @@ -290,25 +265,21 @@ impl NetworkClient { target: Option<NetworkTargetId>, tags: &[SourceTag], guids: &[FunctionGUID], - ) -> Option<HashMap<SourceId, Vec<FunctionGUID>>> { + ) -> Result<HashMap<SourceId, Vec<FunctionGUID>>, String> { let query_functions_source_url = format!("{}/api/v1/functions/query/source", self.server_url); let payload = Self::query_functions_body(target, None, tags, guids); + let mut inst = self.provider.create_instance().unwrap(); - // Make the POST request - let response = self - .client - .post(&query_functions_source_url) - .json(&payload) - .send() - .ok()?; - if !response.status().is_success() { - log::error!("Failed to query functions source: {}", response.status()); - return None; + let resp = inst.post_json(&query_functions_source_url, self.headers.clone(), &payload)?; + if !resp.is_success() { + return Err(format!( + "'{}' returned {}", + query_functions_source_url, resp.status_code + )); } - // Mapping of source id to function guids - let json_response: HashMap<String, Vec<String>> = response.json().ok()?; + let json_response: HashMap<String, Vec<String>> = resp.json().map_err(|e| e.to_string())?; let mapped_function_guids = json_response .into_iter() .filter_map(|(source_str, guid_strs)| { @@ -321,85 +292,79 @@ impl NetworkClient { }) .collect(); - Some(mapped_function_guids) + Ok(mapped_function_guids) } - /// Pushes the file to the remote source. + /// Pushes the file to the remote source, returning the commit id. /// /// NOTE: **THIS IS BLOCKING** /// - /// Route: `api/v1/files/{source}` - pub fn push_file(&self, source_id: SourceId, file: &WarpFile, name: &str) -> bool { - let push_file_url = format!("{}/api/v1/files", self.server_url); - - // Convert WarpFile to bytes - let file_bytes = file.to_bytes(); + /// Route: `api/v1/files/json` + pub fn push_file( + &self, + source_id: SourceId, + file: &WarpFile, + name: &str, + ) -> Result<i32, String> { + let push_file_url = format!("{}/api/v1/files/json", self.server_url); + // Convert WarpFile to base64 encoded bytes + let file_bytes_base64 = base64::engine::general_purpose::STANDARD.encode(&file.to_bytes()); + let mut inst = self.provider.create_instance().unwrap(); - let Ok(file_part) = reqwest::blocking::multipart::Part::bytes(file_bytes) - .file_name("data.warp") - .mime_str("application/octet-stream") - else { - log::error!("Failed to create file part"); - return false; - }; + #[derive(Deserialize)] + struct UploadResponse { + commit_id: i32, + } - let form = reqwest::blocking::multipart::Form::new() - .part("file", file_part) - .text("name", name.to_string()) - .text("source", source_id.to_string()); + let body = json!({ + "file": file_bytes_base64, + "name": name, + "source": source_id.to_string(), + "description": serde_json::Value::Null, + }); - // Send the request - match self.client.post(&push_file_url).multipart(form).send() { - Ok(response) => { - if response.status().is_success() { - true - } else { - log::error!("Failed to push file: {}", response.status()); - false - } - } - Err(e) => { - log::error!("Failed to send push request: {}", e); - false - } + let resp = inst.post_json(&push_file_url, self.headers.clone(), &body)?; + if !resp.is_success() { + return Err(format!("'{}' returned {}", push_file_url, resp.status_code)); } + let out: UploadResponse = resp.json().map_err(|e| e.to_string())?; + Ok(out.commit_id) } - pub fn function_data(&self, id: i32) -> Option<Function> { + pub fn function_data(&self, id: i32) -> Result<Function, String> { let function_data_url = format!("{}/api/v1/functions/{}/data", self.server_url, id); - let response = self.client.get(&function_data_url).send().ok()?; - if !response.status().is_success() { - log::error!( - "Failed to fetch function data for {}: {}", - id, - response.status() - ); - return None; + let mut inst = self.provider.create_instance().unwrap(); + + let resp = inst.get(&function_data_url, self.headers.clone())?; + if !resp.is_success() { + return Err(format!( + "'{}' returned {}", + function_data_url, resp.status_code + )); } - let bytes = response.bytes().ok()?; - Function::from_bytes(bytes.as_ref()) + Function::from_bytes(&resp.data) + .ok_or_else(|| format!("Failed to parse function data for function {}", id,)) } - pub fn function_datas(&self, ids: &[i32]) -> Option<Vec<Function>> { + pub fn function_datas(&self, ids: &[i32]) -> Result<Vec<Function>, String> { if ids.is_empty() { - return Some(Vec::new()); + return Ok(Vec::new()); } let function_data_url = format!("{}/api/v1/functions/data", self.server_url); + let mut inst = self.provider.create_instance().unwrap(); + let body = json!({ "ids": ids, }); - let response = self - .client - .post(&function_data_url) - .json(&body) - .send() - .ok()?; - if !response.status().is_success() { - log::error!("Failed to fetch function data: {}", response.status()); - return None; + let resp = inst.post_json(&function_data_url, self.headers.clone(), &body)?; + if !resp.is_success() { + return Err(format!( + "'{}' returned {}", + function_data_url, resp.status_code + )); } - let bytes = response.bytes().ok()?; - let file = WarpFile::from_bytes(bytes.as_ref())?; + let file = WarpFile::from_bytes(&resp.data) + .ok_or_else(|| format!("Failed to parse function data for functions {:?}", ids))?; let mut functions = Vec::with_capacity(ids.len()); for chunk in file.chunks { let ChunkKind::Signature(sc) = chunk.kind else { @@ -407,39 +372,37 @@ impl NetworkClient { }; functions.extend(sc.functions()); } - Some(functions) + Ok(functions) } - pub fn type_data(&self, guid: TypeGUID) -> Option<Type> { + pub fn type_data(&self, guid: TypeGUID) -> Result<Type, String> { let type_data_url = format!("{}/api/v1/types/{}/data", self.server_url, guid.to_string()); - let response = self.client.get(&type_data_url).send().ok()?; - if !response.status().is_success() { - log::error!( - "Failed to fetch type data for {}: {}", - guid.to_string(), - response.status() - ); - return None; + let mut inst = self.provider.create_instance().unwrap(); + + let resp = inst.get(&type_data_url, self.headers.clone())?; + if !resp.is_success() { + return Err(format!("'{}' returned {}", type_data_url, resp.status_code)); } - let bytes = response.bytes().ok()?; - Type::from_bytes(bytes.as_ref()) + Type::from_bytes(&resp.data) + .ok_or_else(|| format!("Failed to parse type data for type {}", guid)) } - pub fn type_datas(&self, guids: &[TypeGUID]) -> Option<Vec<ComputedType>> { + pub fn type_datas(&self, guids: &[TypeGUID]) -> Result<Vec<ComputedType>, String> { if guids.is_empty() { - return Some(Vec::new()); + return Ok(Vec::new()); } let type_data_url = format!("{}/api/v1/types/data", self.server_url); + let mut inst = self.provider.create_instance().unwrap(); + let body = json!({ "ids": guids.iter().map(|g| g.to_string()).collect::<Vec<_>>(), }); - let response = self.client.post(&type_data_url).json(&body).send().ok()?; - if !response.status().is_success() { - log::error!("Failed to fetch type data: {}", response.status()); - return None; + let resp = inst.post_json(&type_data_url, self.headers.clone(), &body)?; + if !resp.is_success() { + return Err(format!("'{}' returned {}", type_data_url, resp.status_code)); } - let bytes = response.bytes().ok()?; - let file = WarpFile::from_bytes(bytes.as_ref())?; + let file = WarpFile::from_bytes(&resp.data) + .ok_or_else(|| format!("Failed to parse type data for types {:?}", guids))?; let mut types = Vec::with_capacity(guids.len()); for chunk in file.chunks { let ChunkKind::Type(tc) = chunk.kind else { @@ -447,12 +410,10 @@ impl NetworkClient { }; types.extend(tc.types()); } - Some(types) + Ok(types) } - pub fn search(&self, query: &ContainerSearchQuery) -> Option<ContainerSearchResponse> { - let search_url = format!("{}/api/v1/search", self.server_url); - + pub fn search(&self, query: &ContainerSearchQuery) -> Result<ContainerSearchResponse, String> { #[derive(serde::Serialize)] struct SearchRequest<'a> { #[serde(rename = "q")] @@ -468,6 +429,26 @@ impl NetworkClient { #[serde(skip_serializing_if = "Option::is_none")] retrieve_data: Option<bool>, } + let source_id_str = query.source.map(|s| s.to_string()); + let request = SearchRequest { + q: &query.query, + limit: query.limit, + offset: query.offset, + source_id: source_id_str, + source_tags: match query.tags.is_empty() { + true => None, + false => Some(query.tags.clone()), + }, + // This must be passed to retrieve the function and type data. + retrieve_data: Some(true), + }; + let request_qs = serde_qs::to_string(&request).map_err(|e| e.to_string())?; + let search_url = match request_qs.is_empty() { + true => format!("{}/api/v1/search", self.server_url), + false => format!("{}/api/v1/search?{}", self.server_url, request_qs), + }; + + let mut inst = self.provider.create_instance().unwrap(); #[derive(serde::Deserialize)] struct SearchResponse { @@ -488,32 +469,11 @@ impl NetworkClient { data: Option<Vec<u8>>, } - let source_id_str = query.source.map(|s| s.to_string()); - let request = SearchRequest { - q: &query.query, - limit: query.limit, - offset: query.offset, - source_id: source_id_str, - source_tags: match query.tags.is_empty() { - true => None, - false => Some(query.tags.clone()), - }, - // This must be passed to retrieve the function and type data. - retrieve_data: Some(true), - }; - - let resp = match self.client.get(search_url).query(&request).send() { - Ok(r) => r, - Err(err) => { - log::error!("Failed to send search request: {}", err); - return None; - } - }; - - let Ok(parsed) = resp.json::<SearchResponse>() else { - log::error!("Failed to parse search response"); - return None; - }; + let resp = inst.get(&search_url, self.headers.clone())?; + if !resp.is_success() { + return Err(format!("'{}' returned {}", search_url, resp.status_code)); + } + let parsed: SearchResponse = resp.json().map_err(|e| e.to_string())?; // TODO: This is quite scuffed, but it works for now. (Mostly just that it looks bad and queries a lot) // TODO: Here I think would be a good place to sort it so sources always come first. @@ -580,7 +540,7 @@ impl NetworkClient { }); } - Some(ContainerSearchResponse { + Ok(ContainerSearchResponse { items, total: parsed.total, offset: parsed.offset, diff --git a/plugins/warp/src/plugin.rs b/plugins/warp/src/plugin.rs index c396ebca..16462f00 100644 --- a/plugins/warp/src/plugin.rs +++ b/plugins/warp/src/plugin.rs @@ -16,7 +16,6 @@ use binaryninja::is_ui_enabled; use binaryninja::logger::Logger; use binaryninja::settings::{QueryOptions, Settings}; use log::LevelFilter; -use reqwest::StatusCode; mod commit; mod create; @@ -58,64 +57,49 @@ fn load_network_container() { let global_bn_settings = Settings::new(); let add_network_container = |url: String, api_key: Option<String>| { - let https_proxy_str = global_bn_settings.get_string("network.httpsProxy"); - let https_proxy = if https_proxy_str.is_empty() { - None - } else { - Some(https_proxy_str) - }; - match NetworkClient::new(url.clone(), api_key.clone(), https_proxy) { - Ok(network_client) => { - // Before constructing the container, let's make sure that the server is OK. - if let Ok(StatusCode::OK) = network_client.status() { - // Check if the user is logged in. If so, we should collect the writable sources. - let mut writable_sources = Vec::new(); - match network_client.current_user() { - Ok((id, username)) => { - log::info!( - "Server '{}' connected, logged in as user '{}'", - url, - username - ); - match network_client.query_sources(Some(id)) { - Ok(sources) => { - writable_sources = sources; - } - Err(e) => { - log::error!( - "Server '{}' failed to get sources for user: {}", - url, - e - ); - } - } - } - Err(e) if api_key.is_some() => { - log::error!( - "Server '{}' failed to authenticate with provided API key: {}", - url, - e - ); - } - Err(_) => { - log::info!("Server '{}' connected, logged in as guest", url); - } - } + let network_client = NetworkClient::new(url.clone(), api_key.clone()); + // Before constructing the container, let's make sure that the server is OK. + if let Err(e) = network_client.status() { + log::error!("Server '{}' failed to connect: {}", url, e); + return; + } - // TODO: Make the cache path include the domain or url, so that we can have multiple servers. - let main_cache_path = NetworkContainer::root_cache_location().join("main"); - let network_container = - NetworkContainer::new(network_client, main_cache_path, &writable_sources); - log::debug!("{:#?}", network_container); - add_cached_container(network_container); - } else { - log::error!("Server '{}' is not reachable, disabling container...", url); + // Check if the user is logged in. If so, we should collect the writable sources. + let mut writable_sources = Vec::new(); + match network_client.current_user() { + Ok((id, username)) => { + log::info!( + "Server '{}' connected, logged in as user '{}'", + url, + username + ); + match network_client.query_sources(Some(id)) { + Ok(sources) => { + writable_sources = sources; + } + Err(e) => { + log::error!("Server '{}' failed to get sources for user: {}", url, e); + } } } - Err(e) => { - log::error!("Failed to add networked container: {}", e); + Err(e) if api_key.is_some() => { + log::error!( + "Server '{}' failed to authenticate with provided API key: {}", + url, + e + ); + } + Err(_) => { + log::info!("Server '{}' connected, logged in as guest", url); } } + + // TODO: Make the cache path include the domain or url, so that we can have multiple servers. + let main_cache_path = NetworkContainer::root_cache_location().join("main"); + let network_container = + NetworkContainer::new(network_client, main_cache_path, &writable_sources); + log::debug!("{:#?}", network_container); + add_cached_container(network_container); }; let plugin_settings = |
