diff --git a/crates/rwalk/src/engine/pool.rs b/crates/rwalk/src/engine/pool.rs index 29d7c91..2e68aea 100644 --- a/crates/rwalk/src/engine/pool.rs +++ b/crates/rwalk/src/engine/pool.rs @@ -429,12 +429,14 @@ impl WorkerPool { } else { self.pb.inc(1); } - if self.worker_config.filterer.filter(&response)? { - self.worker_config.handler.handle(response.clone(), self)?; - if self.config.bell { - bell(); + if !results.contains_key(&response.url.to_string()) { + if self.worker_config.filterer.filter(&response)? { + results.insert(response.url.to_string(), response.clone()); + self.worker_config.handler.handle(response.clone(), self)?; + if self.config.bell { + bell(); + } } - results.insert(response.url.to_string(), response); } Ok::<(), crate::error::RwalkError>(()) diff --git a/crates/rwalk/src/utils/tree.rs b/crates/rwalk/src/utils/tree.rs index 49f228b..32f533a 100644 --- a/crates/rwalk/src/utils/tree.rs +++ b/crates/rwalk/src/utils/tree.rs @@ -30,7 +30,7 @@ impl Node { for (key, child) in &self.children { if child.children.len() == 1 && !child.is_endpoint { let (grandchild_key, grandchild) = child.children.iter().next().unwrap(); - let new_key = format!("{}/{}", key, grandchild_key); + let new_key = format!("{}{}", key, grandchild_key); keys_to_remove.push(key.clone()); nodes_to_add.insert(new_key, grandchild.clone()); @@ -76,7 +76,7 @@ impl TreeItem for Node { if !self.name.is_empty() { write!( f, - "{} /{}", + "{} {}", display_status_code(self.status), style.paint(&self.name) ) @@ -96,7 +96,12 @@ pub fn display_url_tree(base: &Url, urls: &DashMap) { let url = entry.key(); if let Ok(parsed_url) = Url::parse(url) { let path = parsed_url.path(); - let components: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect(); + + let components = path + .split('/') + .filter(|s| !s.is_empty()) + .map(|comp| format!("/{}", comp)) + .collect::>(); insert_path(&mut root, &components, entry.value()); } @@ -111,19 +116,19 @@ pub fn display_url_tree(base: &Url, urls: &DashMap) { ptree::print_tree(&root).unwrap(); } -fn insert_path(node: &mut Node, components: &[&str], response: &RwalkResponse) { +fn insert_path(node: &mut Node, components: &[String], response: &RwalkResponse) { if components.is_empty() { node.is_endpoint = true; node.status = response.status as u16; return; } - let component = components[0]; + let component = components[0].clone(); let child = node .children - .entry(component.to_string()) + .entry(component.clone()) .or_insert_with(|| Node { - name: String::new(), + name: component, status: response.status as u16, ..Default::default() }); diff --git a/crates/rwalk/src/wordlist/mod.rs b/crates/rwalk/src/wordlist/mod.rs index 36ebd1a..35282fe 100644 --- a/crates/rwalk/src/wordlist/mod.rs +++ b/crates/rwalk/src/wordlist/mod.rs @@ -5,7 +5,6 @@ use crossbeam::deque::Injector; use rayon::iter::{IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator}; use transformation::Transformer; use url::Url; -use url::Position; pub mod filters; pub mod processor; @@ -45,14 +44,15 @@ impl Wordlist { } pub fn inject_into(&self, injector: &Injector, url: &Url, depth: usize) -> Result<()> { - let base_prefix = url[..Position::BeforePath].to_string(); - + let base_url = url.to_string(); + self.words.par_iter().try_for_each(|word| { - let full_url = if base_prefix.ends_with('/') || word.starts_with('/') { - format!("{}{}", base_prefix.trim_end_matches('/'), word) - } else { - format!("{}/{}", base_prefix, word) - }; + // Trim slashes to ensure exactly one slash between + let base_trimmed = base_url.trim_end_matches('/'); + let word_trimmed = word.strip_prefix('/').unwrap_or(word); + + let full_url = format!("{}/{}", base_trimmed, word_trimmed); + injector.push(Task::new_recursive(full_url, depth)); Ok(()) })