subscan 1.3.0

A subdomain enumeration tool leveraging diverse techniques, designed for advanced pentesting operations
Documentation
use std::{
    collections::BTreeSet,
    fs,
    net::TcpListener,
    path::{Path, PathBuf},
    thread,
};

use serde_json::Value;
use subscan::{
    enums::{
        content::Content,
        dispatchers::SubscanModuleDispatcher,
        result::{OptionalSubscanModuleResult, SubscanModuleResult},
    },
    interfaces::module::SubscanModuleInterface,
    types::{
        core::{Subdomain, UnboundedFlumeChannel},
        result::status::SubscanModuleStatus,
    },
};

use crate::common::constants::{LOCAL_HOST, READ_ERROR};

pub async fn run_module(
    mut module: SubscanModuleDispatcher,
    domain: &str,
) -> (BTreeSet<Subdomain>, SubscanModuleStatus) {
    let mut results = BTreeSet::new();
    let mut statuses = Vec::new();

    let channel: UnboundedFlumeChannel<OptionalSubscanModuleResult> = flume::unbounded().into();

    module.run(domain, channel.tx).await;

    while let Ok(msg) = channel.rx.recv() {
        if let Some(result) = msg.as_ref() {
            match result {
                SubscanModuleResult::SubscanModuleResultItem(item) => {
                    assert_eq!(
                        item.module,
                        module.name().await,
                        "SubscanModuleResultItem has incorrect module name"
                    );
                    results.insert(item.subdomain.clone());
                }
                SubscanModuleResult::SubscanModuleStatusItem(item) => {
                    assert_eq!(
                        item.module,
                        module.name().await,
                        "SubscanModuleResultItem has incorrect module name"
                    );
                    statuses.push(item.status.clone());
                }
            }
        } else {
            break;
        }
    }

    assert_eq!(
        statuses.len(),
        1,
        "The module only has to send one status update"
    );

    (results, statuses.first().unwrap().clone())
}

pub fn stubs_path() -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/stubs")
}

pub fn read_stub(path: &str) -> Value {
    let file_path = stubs_path().join(path);
    let content = fs::read_to_string(file_path).unwrap();

    serde_json::from_str(&content).unwrap()
}

pub fn testdata_path() -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR")).join("testing/testdata")
}

pub fn read_testdata(path: &str) -> Content {
    Content::String(fs::read_to_string(testdata_path().join(path)).expect(READ_ERROR))
}

pub fn get_random_port() -> u16 {
    TcpListener::bind(format!("{LOCAL_HOST}:0"))
        .unwrap()
        .local_addr()
        .unwrap()
        .port()
}

pub fn md5_hex(target: String) -> String {
    format!("{:x}", md5::compute(target))
}

pub fn current_thread_hex() -> String {
    md5_hex(thread::current().name().unwrap().to_uppercase())
}

pub fn fix_new_lines(input: &str) -> String {
    input.replace("\r\n", "\n")
}