product_os_command_control/
commands.rs1use std::prelude::v1::*;
7
8use std::collections::BTreeMap;
9use product_os_request::{ProductOSClient, ProductOSResponse};
10
11use crate::registry::Node;
12
13
14pub struct Command {
35 pub requester: product_os_request::ProductOSRequestClient,
37 pub node_url: product_os_request::Uri,
39 pub verify_key: Vec<u8>,
41 pub module: String,
43 pub instruction: String,
45 pub data: Option<serde_json::Value>
47}
48
49impl Command {
50 pub async fn command(&self) -> Result<ProductOSResponse<product_os_request::BodyBytes>, product_os_request::ProductOSRequestError> {
58 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
59 }
60}
61
62
63
64pub struct Ask {
87 pub requester: product_os_request::ProductOSRequestClient,
89 pub node_url: product_os_request::Uri,
91 pub verify_key: Vec<u8>,
93 pub path: String,
95 pub data: Option<serde_json::Value>,
97 pub headers: BTreeMap<String, String>,
99 pub params: BTreeMap<String, String>,
101 pub method: product_os_request::Method
103}
104
105impl Ask {
106 pub async fn ask(&self) -> Result<ProductOSResponse<product_os_request::BodyBytes>, product_os_request::ProductOSRequestError> {
114 ask(&self.requester, &self.node_url, self.verify_key.as_slice(), self.path.as_str(), &self.data, &self.headers, &self.params, &self.method).await
115 }
116}
117
118
119
120pub async fn command(requester: &product_os_request::ProductOSRequestClient, uri: product_os_request::Uri, verify_key: Vec<u8>,
137 module: &str, instruction: &str, data: Option<serde_json::Value>) -> Result<ProductOSResponse<product_os_request::BodyBytes>, product_os_request::ProductOSRequestError> {
138 let endpoint = String::from(uri.to_string().trim_end_matches("/")) +
139 "/command/" + module + "/" + instruction;
140
141 let mut request = requester.new_request(product_os_request::Method::POST, endpoint.as_str());
142
143 match &data {
144 None => {}
145 Some(data) => {
146 requester.set_body_json(&mut request, data.to_owned()).await;
147 }
148 }
149
150 request.add_header("x-product-os-verify",
151 product_os_security::create_auth_request(None, false, data,
152 None, &[], Some(verify_key.as_slice())).as_str(),
153 true);
154
155 match requester.request(request).await {
156 Ok(response) => {
157 tracing::trace!("Successfully sent {:?}/{:?} command to server {:?}", module, instruction, uri);
158 Ok(response)
159 },
160 Err(e) => {
161 tracing::error!("Error encountered {:?} from {}", e, uri);
162 Err(product_os_request::ProductOSRequestError::Error(format!("No matching node found: {:?}", e)))
163 }
164 }
165}
166
167pub async fn ask_node(requester: &product_os_request::ProductOSRequestClient, node: &Node, verify_key: &[u8],
186 path: &str, data: &Option<serde_json::Value>, headers: &BTreeMap<String, String>,
187 params: &BTreeMap<String, String>, method: &product_os_request::Method) -> Result<ProductOSResponse<product_os_request::BodyBytes>, product_os_request::ProductOSRequestError> {
188 #[allow(deprecated)]
189 let uri = node.get_address();
190
191 ask(requester, &uri, verify_key, path, data, headers, params, method).await
192}
193
194pub async fn ask(requester: &product_os_request::ProductOSRequestClient, url: &product_os_request::Uri, verify_key: &[u8],
213 path: &str, data: &Option<serde_json::Value>, headers: &BTreeMap<String, String>,
214 params: &BTreeMap<String, String>, method: &product_os_request::Method) -> Result<ProductOSResponse<product_os_request::BodyBytes>, product_os_request::ProductOSRequestError> {
215 let path_clean = clean_path(path, false);
216 let endpoint = String::from(url.to_string().trim_end_matches("/")) +
217 path_clean.as_str();
218
219 let mut request = requester.new_request(method.to_owned(), endpoint.as_str());
220
221 request.add_headers(headers.to_owned(), false);
222 request.add_params(params.to_owned());
223
224 match &data {
225 None => {}
226 Some(data) => {
227 requester.set_body_json(&mut request, data.to_owned()).await;
228 }
229 }
230
231 request.add_header("x-product-os-verify",
232 product_os_security::create_auth_request(None, false, data.to_owned(),
233 None, &[], Some(verify_key)).as_str(),
234 true);
235
236 match requester.request(request).await {
237 Ok(response) => {
238 tracing::trace!("Successfully sent ask {:?} command to server {:?}", path_clean, url);
239 Ok(response)
240 },
241 Err(e) => {
242 tracing::error!("Error encountered {:?} from {}", e, url);
243 Err(product_os_request::ProductOSRequestError::Error(format!("No matching node found: {:?}", e)))
244 }
245 }
246}
247
248pub(crate) fn clean_path(input: &str, add_back_slash: bool) -> String {
253 let mut path = input.to_string();
254
255 if path != "/" {
256 while path.contains("//") {
257 path = path.replace("//", "/");
258 }
259 path = path.trim_end_matches('/').to_string();
260 if path.is_empty() {
261 path = "/".to_string();
262 }
263 }
264
265 if !path.starts_with('/') {
266 path = format!("/{}", path);
267 }
268
269 if add_back_slash && !path.ends_with('/') {
270 path.push('/');
271 }
272
273 path.replace("..", "")
274}
275
276#[cfg(test)]
277mod tests {
278 use super::clean_path;
279
280 #[test]
281 fn test_clean_path_normal() {
282 assert_eq!(clean_path("/api/test", false), "/api/test");
283 }
284
285 #[test]
286 fn test_clean_path_root() {
287 assert_eq!(clean_path("/", false), "/");
288 }
289
290 #[test]
291 fn test_clean_path_double_slashes() {
292 let result = clean_path("//api//test", false);
293 assert!(!result.contains("//"));
294 }
295
296 #[test]
297 fn test_clean_path_trailing_slash() {
298 assert_eq!(clean_path("/api/test/", false), "/api/test");
299 }
300
301 #[test]
302 fn test_clean_path_no_leading_slash() {
303 let result = clean_path("api/test", false);
304 assert!(result.starts_with('/'));
305 }
306
307 #[test]
308 fn test_clean_path_traversal() {
309 let result = clean_path("/../../../etc/passwd", false);
310 assert!(!result.contains(".."));
311 }
312
313 #[test]
314 fn test_clean_path_add_back_slash() {
315 let result = clean_path("/api/test", true);
316 assert!(result.ends_with('/'));
317 }
318
319 #[test]
320 fn test_clean_path_add_back_slash_already_present() {
321 let result = clean_path("/", true);
322 assert_eq!(result, "/");
323 }
324
325 #[test]
326 fn test_clean_path_empty_string() {
327 let result = clean_path("", false);
328 assert!(result.starts_with('/'));
329 }
330}