use std::prelude::v1::*;
use std::collections::BTreeMap;
use product_os_request::{ProductOSClient, ProductOSResponse};
use crate::registry::Node;
pub struct Command {
pub requester: product_os_request::ProductOSRequestClient,
pub node_url: product_os_request::Uri,
pub verify_key: Vec<u8>,
pub module: String,
pub instruction: String,
pub data: Option<serde_json::Value>
}
impl Command {
pub async fn command(&self) -> Result<ProductOSResponse<product_os_request::BodyBytes>, product_os_request::ProductOSRequestError> {
command(&self.requester, self.node_url.to_owned(), self.verify_key.to_owned(), self.module.as_str(), self.instruction.as_str(), self.data.to_owned()).await
}
}
pub struct Ask {
pub requester: product_os_request::ProductOSRequestClient,
pub node_url: product_os_request::Uri,
pub verify_key: Vec<u8>,
pub path: String,
pub data: Option<serde_json::Value>,
pub headers: BTreeMap<String, String>,
pub params: BTreeMap<String, String>,
pub method: product_os_request::Method
}
impl Ask {
pub async fn ask(&self) -> Result<ProductOSResponse<product_os_request::BodyBytes>, product_os_request::ProductOSRequestError> {
ask(&self.requester, &self.node_url, self.verify_key.as_slice(), self.path.as_str(), &self.data, &self.headers, &self.params, &self.method).await
}
}
pub async fn command(requester: &product_os_request::ProductOSRequestClient, uri: product_os_request::Uri, verify_key: Vec<u8>,
module: &str, instruction: &str, data: Option<serde_json::Value>) -> Result<ProductOSResponse<product_os_request::BodyBytes>, product_os_request::ProductOSRequestError> {
let endpoint = String::from(uri.to_string().trim_end_matches("/")) +
"/command/" + module + "/" + instruction;
let mut request = requester.new_request(product_os_request::Method::POST, endpoint.as_str());
match &data {
None => {}
Some(data) => {
requester.set_body_json(&mut request, data.to_owned()).await;
}
}
request.add_header("x-product-os-verify",
product_os_security::create_auth_request(None, false, data,
None, &[], Some(verify_key.as_slice())).as_str(),
true);
match requester.request(request).await {
Ok(response) => {
tracing::trace!("Successfully sent {:?}/{:?} command to server {:?}", module, instruction, uri);
Ok(response)
},
Err(e) => {
tracing::error!("Error encountered {:?} from {}", e, uri);
Err(product_os_request::ProductOSRequestError::Error(format!("No matching node found: {:?}", e)))
}
}
}
pub async fn ask_node(requester: &product_os_request::ProductOSRequestClient, node: &Node, verify_key: &[u8],
path: &str, data: &Option<serde_json::Value>, headers: &BTreeMap<String, String>,
params: &BTreeMap<String, String>, method: &product_os_request::Method) -> Result<ProductOSResponse<product_os_request::BodyBytes>, product_os_request::ProductOSRequestError> {
#[allow(deprecated)]
let uri = node.get_address();
ask(requester, &uri, verify_key, path, data, headers, params, method).await
}
pub async fn ask(requester: &product_os_request::ProductOSRequestClient, url: &product_os_request::Uri, verify_key: &[u8],
path: &str, data: &Option<serde_json::Value>, headers: &BTreeMap<String, String>,
params: &BTreeMap<String, String>, method: &product_os_request::Method) -> Result<ProductOSResponse<product_os_request::BodyBytes>, product_os_request::ProductOSRequestError> {
let path_clean = clean_path(path, false);
let endpoint = String::from(url.to_string().trim_end_matches("/")) +
path_clean.as_str();
let mut request = requester.new_request(method.to_owned(), endpoint.as_str());
request.add_headers(headers.to_owned(), false);
request.add_params(params.to_owned());
match &data {
None => {}
Some(data) => {
requester.set_body_json(&mut request, data.to_owned()).await;
}
}
request.add_header("x-product-os-verify",
product_os_security::create_auth_request(None, false, data.to_owned(),
None, &[], Some(verify_key)).as_str(),
true);
match requester.request(request).await {
Ok(response) => {
tracing::trace!("Successfully sent ask {:?} command to server {:?}", path_clean, url);
Ok(response)
},
Err(e) => {
tracing::error!("Error encountered {:?} from {}", e, url);
Err(product_os_request::ProductOSRequestError::Error(format!("No matching node found: {:?}", e)))
}
}
}
pub(crate) fn clean_path(input: &str, add_back_slash: bool) -> String {
let mut path = input.to_string();
if path != "/" {
while path.contains("//") {
path = path.replace("//", "/");
}
path = path.trim_end_matches('/').to_string();
if path.is_empty() {
path = "/".to_string();
}
}
if !path.starts_with('/') {
path = format!("/{}", path);
}
if add_back_slash && !path.ends_with('/') {
path.push('/');
}
path.replace("..", "")
}
#[cfg(test)]
mod tests {
use super::clean_path;
#[test]
fn test_clean_path_normal() {
assert_eq!(clean_path("/api/test", false), "/api/test");
}
#[test]
fn test_clean_path_root() {
assert_eq!(clean_path("/", false), "/");
}
#[test]
fn test_clean_path_double_slashes() {
let result = clean_path("//api//test", false);
assert!(!result.contains("//"));
}
#[test]
fn test_clean_path_trailing_slash() {
assert_eq!(clean_path("/api/test/", false), "/api/test");
}
#[test]
fn test_clean_path_no_leading_slash() {
let result = clean_path("api/test", false);
assert!(result.starts_with('/'));
}
#[test]
fn test_clean_path_traversal() {
let result = clean_path("/../../../etc/passwd", false);
assert!(!result.contains(".."));
}
#[test]
fn test_clean_path_add_back_slash() {
let result = clean_path("/api/test", true);
assert!(result.ends_with('/'));
}
#[test]
fn test_clean_path_add_back_slash_already_present() {
let result = clean_path("/", true);
assert_eq!(result, "/");
}
#[test]
fn test_clean_path_empty_string() {
let result = clean_path("", false);
assert!(result.starts_with('/'));
}
}