use async_trait::async_trait;
use flume::Sender;
use regex::Regex;
use reqwest::{
header::{HeaderMap, HeaderName, HeaderValue},
Response, Url,
};
use tokio::sync::Mutex;
use crate::{
enums::{
content::Content,
dispatchers::{RequesterDispatcher, SubdomainExtractorDispatcher, SubscanModuleDispatcher},
result::OptionalSubscanModuleResult,
},
extractors::html::HTMLExtractor,
interfaces::{
extractor::SubdomainExtractorInterface, module::SubscanModuleInterface,
requester::RequesterInterface,
},
requesters::client::HTTPClient,
types::{
core::{Result, SubscanModuleCoreComponents},
result::status::SubscanModuleStatus::Finished,
},
};
pub const DNSDUMPSTERCRAWLER_MODULE_NAME: &str = "dnsdumpstercrawler";
pub const DNSDUMPSTERCRAWLER_URL: &str = "https://api.dnsdumpster.com/htmld/";
pub const DNSDUMPSTERCRAWLER_BASE_URL: &str = "https://dnsdumpster.com";
pub const DNSDUMPSTERCRAWLER_SUBDOMAIN_TAG: &str = "table > tbody > tr > td:first-child";
pub const DNSDUMPSTERCRAWLER_AUTH_TOKEN_PATTERN: &str = r#"Authorization": "(?<token>[^"]+)"#;
pub struct DNSDumpsterCrawler {
pub name: String,
pub url: Url,
pub base_url: Url,
pub components: SubscanModuleCoreComponents,
}
impl DNSDumpsterCrawler {
pub fn dispatcher() -> SubscanModuleDispatcher {
let url = Url::parse(DNSDUMPSTERCRAWLER_URL);
let base_url = Url::parse(DNSDUMPSTERCRAWLER_BASE_URL);
let selector: String = DNSDUMPSTERCRAWLER_SUBDOMAIN_TAG.into();
let requester: RequesterDispatcher = HTTPClient::default().into();
let extractor: HTMLExtractor = HTMLExtractor::new(selector, vec![]);
let dnsdumpster = Self {
name: DNSDUMPSTERCRAWLER_MODULE_NAME.into(),
url: url.unwrap(),
base_url: base_url.unwrap(),
components: SubscanModuleCoreComponents {
requester: requester.into(),
extractor: extractor.into(),
},
};
dnsdumpster.into()
}
pub async fn get_auth_token(&self, content: Content) -> Option<String> {
let pattern = Regex::new(DNSDUMPSTERCRAWLER_AUTH_TOKEN_PATTERN).unwrap();
if let Some(caps) = pattern.captures(&content.as_string()) {
return Some(caps["token"].to_string());
}
None
}
}
#[async_trait]
impl SubscanModuleInterface for DNSDumpsterCrawler {
async fn name(&self) -> &str {
&self.name
}
async fn requester(&self) -> Option<&Mutex<RequesterDispatcher>> {
Some(&self.components.requester)
}
async fn extractor(&self) -> Option<&SubdomainExtractorDispatcher> {
Some(&self.components.extractor)
}
async fn run(&mut self, domain: &str, results: Sender<OptionalSubscanModuleResult>) {
let requester = &mut *self.requester().await.unwrap().lock().await;
let extractor = self.extractor().await.unwrap();
let content = requester.get_content(self.base_url.clone()).await;
match content {
Ok(content) => match self.get_auth_token(content).await {
Some(token) => {
if let RequesterDispatcher::HTTPClient(requester) = requester {
let headers = HeaderMap::from_iter([(
HeaderName::from_static("authorization"),
HeaderValue::from_str(&token).unwrap(),
)]);
let params = &[("target", domain)];
requester.config.headers.extend(headers);
let rbuilder = requester
.client
.post(self.url.clone())
.form(params)
.timeout(requester.config.timeout)
.headers(requester.config.headers.clone());
if let Ok(request) = rbuilder.build() {
let response: Result<Response> =
requester.client.execute(request).await.map_err(|err| err.into());
match response {
Ok(response) => {
let content = response.text().await.unwrap_or_default();
let subdomains =
extractor.extract(content.into(), domain).await;
for subdomain in &subdomains.unwrap_or_default() {
results.send(self.item(subdomain).await).unwrap();
}
}
Err(err) => results.send(self.status(err.into()).await).unwrap(),
}
}
results.send(self.status(Finished).await).unwrap();
} else {
results.send(self.error("misconfigured requester").await).unwrap();
}
}
None => results.send(self.error("not get token").await).unwrap(),
},
Err(err) => results.send(self.status(err.into()).await).unwrap(),
}
}
}