From 4f53f4fd315ffe0c8eda229f9ebea5f03ede7fc0 Mon Sep 17 00:00:00 2001 From: epi Date: Sat, 30 Mar 2024 07:09:25 -0400 Subject: [PATCH] added timestamp field to responses --- src/response.rs | 20 +++++++++++++++++++- src/scan_manager/tests.rs | 5 +++-- src/utils.rs | 14 ++++++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/response.rs b/src/response.rs index ba05eacb..39bfe4b5 100644 --- a/src/response.rs +++ b/src/response.rs @@ -21,7 +21,7 @@ use crate::{ event_handlers::{Command, Handles}, traits::FeroxSerialize, url::FeroxUrl, - utils::{self, fmt_err, parse_url_with_raw_path, status_colorizer}, + utils::{self, fmt_err, parse_url_with_raw_path, status_colorizer, timestamp}, CommandSender, }; @@ -63,6 +63,9 @@ pub struct FeroxResponse { /// Url's file extension, if one exists pub(crate) extension: Option, + + /// Timestamp of when this response was received + timestamp: f64, } /// implement Default trait for FeroxResponse @@ -82,6 +85,7 @@ impl Default for FeroxResponse { wildcard: false, output_level: Default::default(), extension: None, + timestamp: timestamp(), } } } @@ -138,6 +142,11 @@ impl FeroxResponse { self.content_length } + /// Get the timestamp of this response + pub fn timestamp(&self) -> f64 { + self.timestamp + } + /// Set `FeroxResponse`'s `url` attribute, has no affect if an error occurs pub fn set_url(&mut self, url: &str) { match parse_url_with_raw_path(url) { @@ -216,6 +225,7 @@ impl FeroxResponse { let status = response.status(); let headers = response.headers().clone(); let content_length = response.content_length().unwrap_or(0); + let timestamp = timestamp(); // .text() consumes the response, must be called last let text = response @@ -248,6 +258,7 @@ impl FeroxResponse { output_level, wildcard: false, extension: None, + timestamp, } } @@ -574,6 +585,7 @@ impl Serialize for FeroxResponse { "extension", self.extension.as_ref().unwrap_or(&String::new()), )?; + state.serialize_field("timestamp", &self.timestamp)?; state.end() } @@ -599,6 +611,7 @@ impl<'de> Deserialize<'de> for FeroxResponse { line_count: 0, word_count: 0, extension: None, + timestamp: timestamp(), }; let map: HashMap = HashMap::deserialize(deserializer)?; @@ -672,6 +685,11 @@ impl<'de> Deserialize<'de> for FeroxResponse { response.extension = Some(result.to_string()); } } + "timestamp" => { + if let Some(result) = value.as_f64() { + response.timestamp = result; + } + } _ => {} } } diff --git a/src/scan_manager/tests.rs b/src/scan_manager/tests.rs index 4b7c347e..a3bb460f 100644 --- a/src/scan_manager/tests.rs +++ b/src/scan_manager/tests.rs @@ -314,7 +314,7 @@ fn ferox_scans_serialize() { #[test] /// given a FeroxResponses, test that it serializes into the proper JSON entry fn ferox_responses_serialize() { - let json_response = r#"{"type":"response","url":"https://nerdcore.com/css","original_url":"https://nerdcore.com","path":"/css","wildcard":true,"status":301,"method":"GET","content_length":173,"line_count":10,"word_count":16,"headers":{"server":"nginx/1.16.1"},"extension":""}"#; + let json_response = r#"{"type":"response","url":"https://nerdcore.com/css","original_url":"https://nerdcore.com","path":"/css","wildcard":true,"status":301,"method":"GET","content_length":173,"line_count":10,"word_count":16,"headers":{"server":"nginx/1.16.1"},"extension":"","timestamp":1711796681.3455093}"#; let response: FeroxResponse = serde_json::from_str(json_response).unwrap(); let responses = FeroxResponses::default(); @@ -332,7 +332,7 @@ fn ferox_responses_serialize() { /// given a FeroxResponse, test that it serializes into the proper JSON entry fn ferox_response_serialize_and_deserialize() { // deserialize - let json_response = r#"{"type":"response","url":"https://nerdcore.com/css","original_url":"https://nerdcore.com","path":"/css","wildcard":true,"status":301,"method":"GET","content_length":173,"line_count":10,"word_count":16,"headers":{"server":"nginx/1.16.1"},"extension":""}"#; + let json_response = r#"{"type":"response","url":"https://nerdcore.com/css","original_url":"https://nerdcore.com","path":"/css","wildcard":true,"status":301,"method":"GET","content_length":173,"line_count":10,"word_count":16,"headers":{"server":"nginx/1.16.1"},"extension":"","timestamp":1711796681.3455093}"#; let response: FeroxResponse = serde_json::from_str(json_response).unwrap(); assert_eq!(response.url().as_str(), "https://nerdcore.com/css"); @@ -343,6 +343,7 @@ fn ferox_response_serialize_and_deserialize() { assert_eq!(response.line_count(), 10); assert_eq!(response.word_count(), 16); assert_eq!(response.headers().get("server").unwrap(), "nginx/1.16.1"); + assert_eq!(response.timestamp(), 1711796681.3455093); // serialize, however, this can fail when headers are out of order let new_json = serde_json::to_string(&response).unwrap(); diff --git a/src/utils.rs b/src/utils.rs index 08e198ac..b9ae6fa9 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -68,6 +68,20 @@ pub fn fmt_err(msg: &str) -> String { format!("{}: {}", status_colorizer("ERROR"), msg) } +/// simple wrapper to get the current system time as +/// time elapsed from unix epoch +pub fn timestamp() -> f64 { + let since_the_epoch = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_else(|_| Duration::from_secs(0)); + + let secs = since_the_epoch.as_secs() as f64; + let nanos = since_the_epoch.subsec_nanos() as f64; + + // Convert nanoseconds to fractional seconds and add to secs + secs + (nanos / 1_000_000_000.0) +} + /// given a FeroxResponse, send a TryRecursion command /// /// moved to utils to allow for calls from extractor and scanner