added remove filter command to SMM

This commit is contained in:
epi 2022-04-06 19:16:30 -05:00
parent 56267726cc
commit 50b29a2b74
13 changed files with 259 additions and 98 deletions

43
Cargo.lock generated
View File

@ -411,14 +411,14 @@ dependencies = [
[[package]]
name = "crossterm"
version = "0.23.1"
version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1fd7173631a4e9e2ca8b32ae2fad58aab9843ea5aaf56642661937d87e28a3e"
checksum = "a2102ea4f781910f8a5b98dd061f4c2023f479ce7bb1236330099ceb5a93cf17"
dependencies = [
"bitflags",
"crossterm_winapi",
"libc",
"mio 0.7.14",
"mio",
"parking_lot",
"signal-hook",
"signal-hook-mio",
@ -634,9 +634,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "encoding_rs"
version = "0.8.30"
version = "0.8.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7896dc8abb250ffdda33912550faa54c88ec8b998dec0b2c55ab224921ce11df"
checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b"
dependencies = [
"cfg-if",
]
@ -1251,9 +1251,9 @@ checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760"
[[package]]
name = "libc"
version = "0.2.121"
version = "0.2.122"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f"
checksum = "ec647867e2bf0772e28c8bcde4f0d19a9216916e890543b5a03ed8ef27b8f259"
[[package]]
name = "libnghttp2-sys"
@ -1344,19 +1344,6 @@ version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
[[package]]
name = "mio"
version = "0.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc"
dependencies = [
"libc",
"log",
"miow",
"ntapi",
"winapi",
]
[[package]]
name = "mio"
version = "0.8.2"
@ -1746,9 +1733,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
[[package]]
name = "proc-macro2"
version = "1.0.36"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1"
dependencies = [
"unicode-xid",
]
@ -1934,9 +1921,9 @@ dependencies = [
[[package]]
name = "rlimit"
version = "0.8.1"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fbcc6f9672090a4060a6de8d0fb2d4acd381363cda5b7049a26e2b32464f8ad"
checksum = "f7278a1ec8bfd4a4e07515c589f5ff7b309a373f987393aef44813d9dcf87aa3"
dependencies = [
"libc",
]
@ -2123,7 +2110,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
dependencies = [
"libc",
"mio 0.7.14",
"mio",
"signal-hook",
]
@ -2221,9 +2208,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.90"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "704df27628939572cd88d33f171cd6f896f4eaca85252c6e0a72d8d8287ee86f"
checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d"
dependencies = [
"proc-macro2",
"quote",
@ -2359,7 +2346,7 @@ dependencies = [
"bytes",
"libc",
"memchr",
"mio 0.8.2",
"mio",
"num_cpus",
"once_cell",
"parking_lot",

View File

@ -44,8 +44,8 @@ console = "0.15.0"
openssl = { version = "0.10.38", features = ["vendored"] }
dirs = "4.0.0"
regex = "1.5.5"
crossterm = "0.23.1"
rlimit = "0.8.1"
crossterm = "0.23.2"
rlimit = "0.8.3"
ctrlc = "3.2.1"
fuzzyhash = "0.2.1"
anyhow = "1.0.56"

View File

@ -43,6 +43,9 @@ pub enum Command {
/// Add a `FeroxFilter` implementor to `FilterHandler`'s instance of `FeroxFilters`
AddFilter(Box<dyn FeroxFilter>),
/// Remove a set of `FeroxFilter` implementors from `FeroxFilters` by index
RemoveFilters(Vec<usize>),
/// Send a `FeroxResponse` to the output handler for reporting
Report(Box<FeroxResponse>),

View File

@ -90,6 +90,7 @@ impl FiltersHandler {
self.data.push(filter)?;
}
}
Command::RemoveFilters(mut indices) => self.data.remove(&mut indices),
Command::Sync(sender) => {
log::debug!("filters: {:?}", self);
sender.send(true).unwrap_or_default();
@ -128,7 +129,7 @@ mod tests {
event_handle.send(Command::Sync(tx)).unwrap();
rx.await.unwrap();
assert!(event_handle.data.filters.lock().unwrap().is_empty());
assert!(event_handle.data.filters.read().unwrap().is_empty());
event_handle
.send(Command::AddFilter(Box::new(WordsFilter { word_count: 1 })))
@ -138,6 +139,6 @@ mod tests {
event_handle.send(Command::Sync(tx)).unwrap();
rx.await.unwrap();
assert_eq!(event_handle.data.filters.lock().unwrap().len(), 1);
assert_eq!(event_handle.data.filters.read().unwrap().len(), 1);
}
}

View File

@ -1,4 +1,4 @@
use std::sync::Mutex;
use std::sync::RwLock;
use anyhow::Result;
use serde::{ser::SerializeSeq, Serialize, Serializer};
@ -17,14 +17,14 @@ use super::{
#[derive(Debug, Default)]
pub struct FeroxFilters {
/// collection of `FeroxFilters`
pub filters: Mutex<Vec<Box<dyn FeroxFilter>>>,
pub filters: RwLock<Vec<Box<dyn FeroxFilter>>>,
}
/// implementation of FeroxFilter collection
impl FeroxFilters {
/// add a single FeroxFilter to the collection
pub fn push(&self, filter: Box<dyn FeroxFilter>) -> Result<()> {
if let Ok(mut guard) = self.filters.lock() {
if let Ok(mut guard) = self.filters.write() {
if guard.contains(&filter) {
return Ok(());
}
@ -34,6 +34,32 @@ impl FeroxFilters {
Ok(())
}
pub fn remove(&self, indices: &mut [usize]) {
// since we're removing by index, indices must be sorted and then reversed.
// this allows us to iterate over the collection from the rear, allowing any shifting
// of the vector to happen on sections that we no longer care about, as we're moving
// in the opposite direction
indices.sort_unstable();
indices.reverse();
if let Ok(mut guard) = self.filters.write() {
for index in indices {
// numbering of the menu starts at 1, so we'll need to reduce the index by 1
// to account for that. if they've provided 0 as an offset, we'll set the
// result to a gigantic number and skip it in the loop with a bounds check
let reduced_idx = index.checked_sub(1).unwrap_or(usize::MAX);
// check if number provided is out of range
if reduced_idx >= guard.len() {
// usize can't be negative, just need to handle exceeding bounds
continue;
}
guard.remove(reduced_idx);
}
}
}
/// Simple helper to stay DRY; determines whether or not a given `FeroxResponse` should be reported
/// to the user or not.
pub fn should_filter_response(
@ -41,7 +67,7 @@ impl FeroxFilters {
response: &FeroxResponse,
tx_stats: CommandSender,
) -> bool {
if let Ok(filters) = self.filters.lock() {
if let Ok(filters) = self.filters.read() {
for filter in filters.iter() {
// wildcard.should_filter goes here
if filter.should_filter_response(response) {
@ -63,7 +89,7 @@ impl Serialize for FeroxFilters {
where
S: Serializer,
{
if let Ok(guard) = self.filters.lock() {
if let Ok(guard) = self.filters.read() {
let mut seq = serializer.serialize_seq(Some(guard.len()))?;
for filter in &*guard {

View File

@ -10,6 +10,9 @@ pub struct SimilarityFilter {
/// Percentage of similarity at which a page is determined to be a near-duplicate of another
pub threshold: u32,
/// Url originally requested for the similarity filter
pub original_url: String,
}
/// implementation of FeroxFilter for SimilarityFilter

View File

@ -188,6 +188,7 @@ fn similarity_filter_is_accurate() {
let mut filter = SimilarityFilter {
hash: FuzzyHash::new("kitten").to_string(),
threshold: 95,
original_url: "".to_string(),
};
// kitten/sitting is 57% similar, so a threshold of 95 should not be filtered
@ -213,11 +214,13 @@ fn similarity_filter_as_any() {
let filter = SimilarityFilter {
hash: String::from("stuff"),
threshold: 95,
original_url: "".to_string(),
};
let filter2 = SimilarityFilter {
hash: String::from("stuff"),
threshold: 95,
original_url: "".to_string(),
};
assert!(filter.box_eq(filter2.as_any()));
@ -228,3 +231,39 @@ fn similarity_filter_as_any() {
filter
);
}
#[test]
/// test correctness of FeroxFilters::remove
fn remove_function_works_as_expected() {
let data = FeroxFilters::default();
assert!(data.filters.read().unwrap().is_empty());
(0..8).for_each(|i| {
data.push(Box::new(WordsFilter { word_count: i })).unwrap();
});
// remove removes index-1 from the vec, zero is skipped, and out-of-bounds indices are skipped
data.remove(&mut [0]);
assert_eq!(data.filters.read().unwrap().len(), 8);
data.remove(&mut [10000]);
assert_eq!(data.filters.read().unwrap().len(), 8);
// removing 0, 2, 4
data.remove(&mut [1, 3, 5]);
assert_eq!(data.filters.read().unwrap().len(), 5);
let expected = vec![
WordsFilter { word_count: 1 },
WordsFilter { word_count: 3 },
WordsFilter { word_count: 5 },
WordsFilter { word_count: 6 },
WordsFilter { word_count: 7 },
];
for filter in data.filters.read().unwrap().iter() {
let downcast = filter.as_any().downcast_ref::<WordsFilter>().unwrap();
assert!(expected.contains(downcast));
}
}

View File

@ -46,6 +46,7 @@ pub(crate) async fn create_similarity_filter(
Ok(SimilarityFilter {
hash,
threshold: SIMILARITY_THRESHOLD,
original_url: similarity_filter.to_string(),
})
}
@ -94,13 +95,9 @@ pub(crate) fn filter_lookup(filter_type: &str, filter_value: &str) -> Option<Box
}
"similarity" => {
return Some(Box::new(SimilarityFilter {
// bastardizing the hash field to pass back a url, this means that a caller
// of this function needs to turn the url into a hash. This is a workaround for
// wanting to call this this function from menu.rs but not having access to
// a Handles instance. So, we pass things back up into ferox_scanner.rs and use the
// Handles there to make the actual request.
hash: filter_value.to_string(),
hash: String::new(),
threshold: SIMILARITY_THRESHOLD,
original_url: filter_value.to_string(),
}));
}
_ => (),
@ -109,7 +106,6 @@ pub(crate) fn filter_lookup(filter_type: &str, filter_value: &str) -> Option<Box
None
}
// todo add tests
#[cfg(test)]
mod tests {
use super::*;
@ -161,8 +157,9 @@ mod tests {
assert_eq!(
filter.as_any().downcast_ref::<SimilarityFilter>().unwrap(),
&SimilarityFilter {
hash: "http://localhost".to_string(),
threshold: SIMILARITY_THRESHOLD
hash: String::new(),
threshold: SIMILARITY_THRESHOLD,
original_url: "http://localhost".to_string()
}
);
@ -199,7 +196,8 @@ mod tests {
filter,
SimilarityFilter {
hash: "3:YKEpn:Yfp".to_string(),
threshold: SIMILARITY_THRESHOLD
threshold: SIMILARITY_THRESHOLD,
original_url: srv.url("/")
}
);
}

View File

@ -9,13 +9,16 @@ use regex::Regex;
#[derive(Debug)]
pub enum MenuCmd {
/// user wants to add a url to be scanned
Add(String),
AddUrl(String),
/// user wants to cancel one or more active scans
Cancel(Vec<usize>, bool),
/// user wants to create a new filter
NewFilter(Box<dyn FeroxFilter>),
AddFilter(Box<dyn FeroxFilter>),
/// user wants to remove one or more active filters
RemoveFilter(Vec<usize>),
}
/// Data container for a command result to be used internally by the ferox_scanner
@ -40,6 +43,9 @@ pub(super) struct Menu {
/// footer: instructions surrounded by separators
footer: String,
/// unicode line border, matched to longest displayed line
border: String,
/// target for output
pub(super) term: Term,
}
@ -74,13 +80,13 @@ impl Menu {
let new_filter_cmd = format!(
" {}[{}] FILTER_TYPE FILTER_VALUE (ex: {} lines 40)\n",
style("n").yellow(),
style("ew-filter").yellow(),
style("n").yellow(),
style("n").green(),
style("ew-filter").green(),
style("n").green(),
);
let valid_filters = format!(
" FILTER_TYPEs: {}, {}, {}, {}, {}, {}",
" FILTER_TYPEs: {}, {}, {}, {}, {}, {}\n",
style("status").yellow(),
style("lines").yellow(),
style("size").yellow(),
@ -89,11 +95,20 @@ impl Menu {
style("similarity").yellow()
);
let mut commands = String::from("Commands:\n");
let rm_filter_cmd = format!(
" {}[{}] FILTER_ID[-FILTER_ID[,...]] (ex: {} 1-4,8,9-13 or {} 3)",
style("r").red(),
style("m-filter").red(),
style("rm-filter").red(),
style("r").red(),
);
let mut commands = format!("{}:\n", style("Commands").bright().blue());
commands.push_str(&add_cmd);
commands.push_str(&canx_cmd);
commands.push_str(&new_filter_cmd);
commands.push_str(&valid_filters);
commands.push_str(&rm_filter_cmd);
let longest = measure_text_width(&canx_cmd).max(measure_text_width(&name));
@ -102,11 +117,12 @@ impl Menu {
let padded_name = pad_str(&name, longest, Alignment::Center, None);
let header = format!("{}\n{}\n{}", border, padded_name, border);
let footer = format!("{}\n{}\n{}", border, commands, border);
let footer = format!("{}\n{}", commands, border);
Self {
header,
footer,
border,
term: Term::stderr(),
}
}
@ -116,6 +132,11 @@ impl Menu {
self.println(&self.header);
}
/// print menu unicode border line
pub(super) fn print_border(&self) {
self.println(&self.border);
}
/// print menu footer
pub(super) fn print_footer(&self) {
self.println(&self.footer);
@ -225,7 +246,7 @@ impl Menu {
let re = Regex::new(r"^[aA][dD]*").unwrap();
let line = re.replace(line, "").to_string().trim().to_string();
Some(MenuCmd::Add(line))
Some(MenuCmd::AddUrl(line))
}
'n' => {
// new filter command
@ -238,12 +259,27 @@ impl Menu {
// have a string in the filter_value position
if let Some(result) = filter_lookup(filter_type, filter_value) {
// lookup was successful, return the new filter
return Some(MenuCmd::NewFilter(result));
return Some(MenuCmd::AddFilter(result));
}
}
}
None
}
'r' => {
// remove filter command
// remove r[m-filter] from the command so it can be passed to the number
// splitter
let re = Regex::new(r"^[rR][mfilterMFILTER-]*").unwrap();
// we don't respect a -f or lack thereof in this command, but in case the user
// doesn't realize / thinks its the same as cancel -f, just remove it
let line = line.replace("-f", "");
let line = re.replace(&line, "").to_string();
let indices = self.split_to_nums(&line);
Some(MenuCmd::RemoveFilter(indices))
}
_ => {
// invalid input
None

View File

@ -6,7 +6,6 @@ use crate::filters::{
WildcardFilter, WordsFilter,
};
use crate::traits::FeroxFilter;
use crate::utils::{create_report_string, ferox_print};
use crate::Command::AddFilter;
use crate::{
config::OutputLevel,
@ -15,7 +14,7 @@ use crate::{
scan_manager::{MenuCmd, MenuCmdResult},
scanner::RESPONSES,
traits::FeroxSerialize,
SLEEP_DURATION,
Command, SLEEP_DURATION,
};
use anyhow::Result;
use console::style;
@ -309,6 +308,8 @@ impl FeroxScans {
.clone()
};
let mut printed = 0;
for (i, scan) in scans.iter().enumerate() {
if matches!(scan.scan_order, ScanOrder::Initial) || scan.task.try_lock().is_err() {
// original target passed in via either -u or --stdin
@ -316,12 +317,21 @@ impl FeroxScans {
}
if matches!(scan.scan_type, ScanType::Directory) {
if printed == 0 {
self.menu
.println(&format!("{}:", style("Scans").bright().blue()));
}
// we're only interested in displaying directory scans, as those are
// the only ones that make sense to be stopped
let scan_msg = format!("{:3}: {}", i, scan);
self.menu.println(&scan_msg);
printed += 1;
}
}
if printed > 0 {
self.menu.print_border();
}
}
/// Given a list of indexes, cancel their associated FeroxScans
@ -372,12 +382,34 @@ impl FeroxScans {
num_cancelled
}
fn display_filters(&self, handles: Arc<Handles>) {
let mut printed = 0;
if let Ok(guard) = handles.filters.data.filters.read() {
for (i, filter) in guard.iter().enumerate() {
if i == 0 {
self.menu
.println(&format!("{}:", style("Filters").bright().blue()));
}
let filter_msg = format!("{:3}: {}", i + 1, filter);
self.menu.println(&filter_msg);
printed += 1;
}
if printed > 0 {
self.menu.print_border();
}
}
}
/// CLI menu that allows for interactive cancellation of recursed-into directories
async fn interactive_menu(&self) -> Option<MenuCmdResult> {
async fn interactive_menu(&self, handles: Arc<Handles>) -> Option<MenuCmdResult> {
self.menu.hide_progress_bars();
self.menu.clear_screen();
self.menu.print_header();
self.display_scans().await;
self.display_filters(handles.clone());
self.menu.print_footer();
let menu_cmd = if let Ok(line) = self.menu.term.read_line() {
@ -392,11 +424,17 @@ impl FeroxScans {
let num_cancelled = self.cancel_scans(indices, should_force).await;
Some(MenuCmdResult::NumCancelled(num_cancelled))
}
Some(MenuCmd::Add(url)) => Some(MenuCmdResult::Url(url)),
Some(MenuCmd::NewFilter(filter)) => Some(MenuCmdResult::Filter(filter)),
Some(MenuCmd::AddUrl(url)) => Some(MenuCmdResult::Url(url)),
Some(MenuCmd::AddFilter(filter)) => Some(MenuCmdResult::Filter(filter)),
Some(MenuCmd::RemoveFilter(indices)) => {
handles
.filters
.send(Command::RemoveFilters(indices))
.unwrap_or_default();
None
}
None => None,
};
log::error!("{}", format!("{:?}", result)); // todo remove
self.menu.clear_screen();
self.menu.show_progress_bars();
@ -449,7 +487,11 @@ impl FeroxScans {
///
/// When the value stored in `PAUSE_SCAN` becomes `false`, the function returns, exiting the busy
/// loop
pub async fn pause(&self, get_user_input: bool) -> Option<MenuCmdResult> {
pub async fn pause(
&self,
get_user_input: bool,
handles: Arc<Handles>,
) -> Option<MenuCmdResult> {
// function uses tokio::time, not std
// local testing showed a pretty slow increase (less than linear) in CPU usage as # of
@ -461,30 +503,9 @@ impl FeroxScans {
INTERACTIVE_BARRIER.fetch_add(1, Ordering::Relaxed);
if get_user_input {
command_result = self.interactive_menu().await;
command_result = self.interactive_menu(handles).await;
PAUSE_SCAN.store(false, Ordering::Relaxed);
self.print_known_responses();
if let Some(MenuCmdResult::Filter(_)) = command_result {
// adding/cancelling a url has immediate visual cues, whereas adding a filter
// does not. in order to print this message below already found urls, we need
// to postpone printing until after the call to `self.print_known_responses`
let colored = format!(
"New {} successfully {}!",
style("filter").bright().blue(),
style("created").green()
);
let msg = create_report_string(
&style("MSG").bright().blue().to_string(),
"-",
"-",
"-",
"-",
&colored,
OutputLevel::Default,
);
ferox_print(&msg, &PROGRESS_PRINTER);
}
}
}

View File

@ -36,6 +36,7 @@ fn default_scantype_is_file() {
async fn scanner_pause_scan_with_finished_spinner() {
let now = time::Instant::now();
let urls = FeroxScans::default();
let handles = Arc::new(Handles::for_testing(None, None).0);
PAUSE_SCAN.store(true, Ordering::Relaxed);
@ -46,7 +47,7 @@ async fn scanner_pause_scan_with_finished_spinner() {
PAUSE_SCAN.store(false, Ordering::Relaxed);
});
urls.pause(false).await;
urls.pause(false, handles).await;
assert!(now.elapsed() > expected);
}
@ -400,6 +401,7 @@ fn feroxstates_feroxserialize_implementation() {
.push(Box::new(SimilarityFilter {
hash: "3:YKEpn:Yfp".to_string(),
threshold: SIMILARITY_THRESHOLD,
original_url: "http://localhost:12345/".to_string(),
}))
.unwrap();
@ -496,7 +498,7 @@ fn feroxstates_feroxserialize_implementation() {
r#""collect_extensions":true"#,
r#""collect_backups":false"#,
r#""collect_words":false"#,
r#""filters":[{"filter_code":100},{"word_count":200},{"content_length":300},{"line_count":400},{"compiled":".*","raw_string":".*"},{"hash":"3:YKEpn:Yfp","threshold":95}]"#,
r#""filters":[{"filter_code":100},{"word_count":200},{"content_length":300},{"line_count":400},{"compiled":".*","raw_string":".*"},{"hash":"3:YKEpn:Yfp","threshold":95,"original_url":"http://localhost:12345/"}]"#,
r#""collected_extensions":["php"]"#,
r#""dont_collect":["tif","tiff","ico","cur","bmp","webp","svg","png","jpg","jpeg","jfif","gif","avif","apng","pjpeg","pjp","mov","wav","mpg","mpeg","mp3","mp4","m4a","m4p","m4v","ogg","webm","ogv","oga","flac","aac","3gp","css","zip","xls","xml","gz","tgz"]"#,
]
@ -627,7 +629,7 @@ async fn ferox_scan_abort() {
/// and their correctness can be verified easily manually; just calling for now
fn menu_print_header_and_footer() {
let menu = Menu::new();
let menu_cmd_1 = MenuCmd::Add(String::from("http://localhost"));
let menu_cmd_1 = MenuCmd::AddUrl(String::from("http://localhost"));
let menu_cmd_2 = MenuCmd::Cancel(vec![0], false);
let menu_cmd_res_1 = MenuCmdResult::Url(String::from("http://localhost"));
let menu_cmd_res_2 = MenuCmdResult::NumCancelled(2);
@ -682,9 +684,9 @@ fn menu_get_command_input_from_user_returns_add() {
if cmd != "None" {
let result = menu.get_command_input_from_user(&full_cmd).unwrap();
assert!(matches!(result, MenuCmd::Add(_)));
assert!(matches!(result, MenuCmd::AddUrl(_)));
if let MenuCmd::Add(url) = result {
if let MenuCmd::AddUrl(url) = result {
assert_eq!(url, test_url);
}
} else {

View File

@ -8,7 +8,9 @@ use indicatif::ProgressBar;
use lazy_static::lazy_static;
use tokio::sync::Semaphore;
use crate::filters::{create_similarity_filter, SimilarityFilter};
use crate::filters::{create_similarity_filter, EmptyFilter, SimilarityFilter};
use crate::progress::PROGRESS_PRINTER;
use crate::utils::ferox_print;
use crate::Command::AddFilter;
use crate::{
event_handlers::{
@ -49,7 +51,7 @@ async fn check_for_user_input(
// todo write a test or two for this function at some point...
if pause_flag.load(Ordering::Acquire) {
match scanned_urls.pause(true).await {
match scanned_urls.pause(true, handles.clone()).await {
Some(MenuCmdResult::Url(url)) => {
// user wants to add a new url to be scanned, need to send
// it over to the event handler for processing
@ -66,10 +68,10 @@ async fn check_for_user_input(
}
}
Some(MenuCmdResult::Filter(mut filter)) => {
let url = if let Some(SimilarityFilter { hash, threshold: _ }) =
let url = if let Some(SimilarityFilter { original_url, .. }) =
filter.as_any().downcast_ref::<SimilarityFilter>()
{
hash.to_owned()
original_url.to_owned()
} else {
String::new()
};
@ -83,8 +85,16 @@ async fn check_for_user_input(
let real_filter = create_similarity_filter(&url, handles.clone())
.await
.unwrap_or_default();
filter = Box::new(real_filter)
ferox_print(
&format!("real filter: {:?}", real_filter),
&PROGRESS_PRINTER,
);
if real_filter.original_url.is_empty() {
// failed to create filter
filter = Box::new(EmptyFilter {});
} else {
filter = Box::new(real_filter)
}
}
handles

View File

@ -1,9 +1,14 @@
//! collection of all traits used
use crate::filters::{
LinesFilter, RegexFilter, SimilarityFilter, SizeFilter, StatusCodeFilter, WildcardFilter,
WordsFilter,
};
use crate::response::FeroxResponse;
use anyhow::Result;
use crossterm::style::{style, Stylize};
use serde::Serialize;
use std::any::Any;
use std::fmt::Debug;
use std::fmt::{self, Debug, Display, Formatter};
// references:
// https://dev.to/magnusstrale/rust-trait-objects-in-a-vector-non-trivial-4co5
@ -22,6 +27,36 @@ pub trait FeroxFilter: Debug + Send + Sync {
fn as_any(&self) -> &dyn Any;
}
impl Display for dyn FeroxFilter {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
if let Some(filter) = self.as_any().downcast_ref::<LinesFilter>() {
write!(f, "Line count: {}", style(filter.line_count).cyan())
} else if let Some(filter) = self.as_any().downcast_ref::<WordsFilter>() {
write!(f, "Word count: {}", style(filter.word_count).cyan())
} else if let Some(filter) = self.as_any().downcast_ref::<SizeFilter>() {
write!(f, "Response size: {}", style(filter.content_length).cyan())
} else if let Some(filter) = self.as_any().downcast_ref::<RegexFilter>() {
write!(f, "Regex: {}", style(&filter.raw_string).cyan())
} else if let Some(filter) = self.as_any().downcast_ref::<WildcardFilter>() {
if filter.dynamic != u64::MAX {
write!(f, "Dynamic wildcard: {}", style(filter.dynamic).cyan())
} else {
write!(f, "Static wildcard: {}", style(filter.size).cyan())
}
} else if let Some(filter) = self.as_any().downcast_ref::<StatusCodeFilter>() {
write!(f, "Status code: {}", style(filter.filter_code).cyan())
} else if let Some(filter) = self.as_any().downcast_ref::<SimilarityFilter>() {
write!(
f,
"Pages similar to: {}",
style(&filter.original_url).cyan()
)
} else {
write!(f, "Filter: {:?}", self)
}
}
}
/// implementation of PartialEq, necessary long-form due to "trait cannot be made into an object"
/// error when attempting to derive PartialEq on the trait itself
impl PartialEq for Box<dyn FeroxFilter> {