repo_description: A fast, simple, recursive content discovery tool written in Rust.
repo_topics: content-discovery enumeration hacktoberfest pentest pentesting-tool rust url-bruteforcer web
code: use super::FeroxFilter;
  use super::SimilarityFilter;
  use crate::event_handlers::Handles;
  use crate::response::FeroxResponse;
  use crate::utils::{logged_request, parse_url_with_raw_path};
  use crate::DEFAULT_METHOD;
  use crate::NEAR_DUPLICATE_DISTANCE;
  use anyhow::Result;
  use regex::Regex;
  use std::sync::Arc;
  
  /// wrapper around logic necessary to create a SimilarityFilter
  ///
  /// - parses given url
  /// - makes request to the parsed url
  /// - gathers extensions from the url, if configured to do so
  /// - computes hash of response body
  /// - creates filter with hash
  pub(crate) async fn create_similarity_filter(
      similarity_filter: &str,
      handles: Arc<Handles>,
  ) -> Result<SimilarityFilter> {
      // url as-is based on input, ignores user-specified url manipulation options (add-slash etc)
      let url = parse_url_with_raw_path(similarity_filter)?;
  
      // attempt to request the given url
      let resp = logged_request(&url, DEFAULT_METHOD, None, handles.clone()).await?;
  
      // if successful, create a filter based on the response's body
      let mut fr = FeroxResponse::from(
          resp,
          similarity_filter,
          DEFAULT_METHOD,
          handles.config.output_level,
          handles.config.response_size_limit,
      )
      .await;
  
      if handles.config.collect_extensions {
          fr.parse_extension(handles.clone())?;
      }
  
      let filter = SimilarityFilter::from(&fr);
  
      Ok(filter)
  }
  
  /// used in conjunction with the Scan Management Menu
  ///
  /// when a user uses the n[ew-filter] command in the menu, the two params are passed here for
  /// processing.
  ///
  /// an example command may be `new-filter lines 40`. `lines` and `40` are passed here as &str's
  ///
  /// once here, the type and value are used to create an appropriate FeroxFilter. If anything
  /// goes wrong during creation, None is returned.
  pub(crate) fn filter_lookup(filter_type: &str, filter_value: &str) -> Option<Box<dyn FeroxFilter>> {
      match filter_type {
          "status" => {
              if let Ok(parsed) = filter_value.parse() {
                  return Some(Box::new(super::StatusCodeFilter {
                      filter_code: parsed,
                  }));
              }
          }
          "lines" => {
              if let Ok(parsed) = filter_value.parse() {
                  return Some(Box::new(super::LinesFilter { line_count: parsed }));
              }
          }
          "size" => {
              if let Ok(parsed) = filter_value.parse() {
                  return Some(Box::new(super::SizeFilter {
                      content_length: parsed,
                  }));
              }
          }
          "words" => {
              if let Ok(parsed) = filter_value.parse() {
                  return Some(Box::new(super::WordsFilter { word_count: parsed }));
              }
          }
          "regex" => {
              if let Ok(parsed) = Regex::new(filter_value) {
                  return Some(Box::new(super::RegexFilter {
                      compiled: parsed,
                      raw_string: filter_value.to_string(),
                  }));
              }
          }
          "similarity" => {
              return Some(Box::new(SimilarityFilter::new(
                  0,
                  filter_value.to_string(),
                  NEAR_DUPLICATE_DISTANCE,
              )));
          }
          _ => (),
      }
  
      None
  }
  
  #[cfg(test)]
  mod tests {
      use super::*;
      use crate::config::Configuration;
      use crate::filters::{LinesFilter, RegexFilter, SizeFilter, StatusCodeFilter, WordsFilter};
      use crate::scan_manager::FeroxScans;
      use httpmock::Method::GET;
      use httpmock::MockServer;
  
      #[test]
      /// filter_lookup returns correct filters
      fn filter_lookup_returns_correct_filters() {
          let filter = filter_lookup("status", "200").unwrap();
          assert_eq!(
              filter.as_any().downcast_ref::<StatusCodeFilter>().unwrap(),
              &StatusCodeFilter { filter_code: 200 }
          );
  
          let filter = filter_lookup("lines", "10").unwrap();
          assert_eq!(
              filter.as_any().downcast_ref::<LinesFilter>().unwrap(),
              &LinesFilter { line_count: 10 }
          );
  
          let filter = filter_lookup("size", "20").unwrap();
          assert_eq!(
              filter.as_any().downcast_ref::<SizeFilter>().unwrap(),
              &SizeFilter { content_length: 20 }
          );
  
          let filter = filter_lookup("words", "30").unwrap();
          assert_eq!(
              filter.as_any().downcast_ref::<WordsFilter>().unwrap(),
              &WordsFilter { word_count: 30 }
          );
  
          let filter = filter_lookup("regex", "stuff.*").unwrap();
          let compiled = Regex::new("stuff.*").unwrap();
          let raw_string = String::from("stuff.*");
          assert_eq!(
              filter.as_any().downcast_ref::<RegexFilter>().unwrap(),
              &RegexFilter {
                  compiled,
                  raw_string
              }
          );
  
          let filter = filter_lookup("similarity", "http://localhost").unwrap();
          assert_eq!(
              filter.as_any().downcast_ref::<SimilarityFilter>().unwrap(),
              &SimilarityFilter {
                  hash: 0,
                  original_url: "http://localhost".to_string(),
                  cutoff: NEAR_DUPLICATE_DISTANCE,
              }
          );
  
          assert!(filter_lookup("non-existent", "").is_none());
      }
  
      #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
      /// ensure create_similarity_filter correctness of return value and side-effects
      async fn create_similarity_filter_is_correct() {
          let srv = MockServer::start();
  
          let mock = srv.mock(|when, then| {
              when.method(GET).path("/");
              then.status(200).body("this is a test");
          });
  
          let scans = FeroxScans::default();
          let config = Configuration {
              collect_extensions: true,
              ..Default::default()
          };
  
          let (test_handles, _) = Handles::for_testing(Some(Arc::new(scans)), Some(Arc::new(config)));
  
          let handles = Arc::new(test_handles);
  
          let filter = create_similarity_filter(&srv.url("/"), handles.clone())
              .await
              .unwrap();
  
          assert_eq!(mock.hits(), 1);
  
          assert_eq!(
              filter,
              SimilarityFilter {
                  hash: 14897447612059286329,
                  original_url: srv.url("/"),
                  cutoff: NEAR_DUPLICATE_DISTANCE,
              }
          );
      }
  }
  