mirror of
https://github.com/epi052/feroxbuster
synced 2024-07-09 03:55:47 +00:00
added remove filter command to SMM
This commit is contained in:
parent
56267726cc
commit
50b29a2b74
43
Cargo.lock
generated
43
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>),
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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("/")
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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> {
|
||||
|
|
Loading…
Reference in New Issue
Block a user