mirror of
https://github.com/edu4rdshl/fhc.git
synced 2026-07-17 23:24:50 +00:00
231 lines
7.2 KiB
Rust
231 lines
7.2 KiB
Rust
use {
|
|
crate::{
|
|
structs::{HTTPFilters, HttpData, LibOptions},
|
|
utils,
|
|
},
|
|
futures::stream::StreamExt,
|
|
rand::{distributions::Alphanumeric, thread_rng as rng, Rng},
|
|
reqwest::{
|
|
header::{CONTENT_LENGTH, CONTENT_TYPE, USER_AGENT},
|
|
redirect::Policy,
|
|
Client, Response,
|
|
},
|
|
scraper::{Html, Selector},
|
|
std::collections::{HashMap, HashSet},
|
|
};
|
|
|
|
#[must_use]
|
|
pub async fn return_http_data(options: &LibOptions, from_cli: bool) -> HashMap<String, HttpData> {
|
|
let threads = options.hosts.len().min(options.threads);
|
|
|
|
let filter_codes = options.filter_codes.as_deref().unwrap_or_default();
|
|
let exclude_codes = options.exclude_codes.as_deref().unwrap_or_default();
|
|
|
|
futures::stream::iter(options.hosts.clone().into_iter().map(|host| {
|
|
let user_agent = utils::return_random_user_agent(&options.user_agents);
|
|
|
|
async move {
|
|
let mut http_data = HttpData {
|
|
checked_host: host.clone(),
|
|
..Default::default()
|
|
};
|
|
let mut response = None;
|
|
|
|
// Attempt both HTTPS and HTTP requests, retry if necessary
|
|
for _ in 0..options.retries {
|
|
let https_req = options
|
|
.client
|
|
.get(format!("https://{}", host))
|
|
.header(USER_AGENT, &user_agent)
|
|
.send();
|
|
|
|
let http_req = options
|
|
.client
|
|
.get(format!("http://{}", host))
|
|
.header(USER_AGENT, &user_agent)
|
|
.send();
|
|
|
|
response = https_req.await.or(http_req.await).ok();
|
|
|
|
if response.is_some() {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if let Some(resp) = response {
|
|
// Those are always set
|
|
http_data.protocol = resp.url().scheme().to_string();
|
|
http_data.status_code = resp.status().as_u16();
|
|
http_data.final_url = resp.url().to_string();
|
|
|
|
if !from_cli {
|
|
assign_response_data(&mut http_data, resp, options.return_filters).await;
|
|
}
|
|
} else {
|
|
http_data.http_status = "INACTIVE".to_string();
|
|
}
|
|
|
|
if !options.quiet_flag
|
|
&& !http_data.final_url.is_empty()
|
|
&& (filter_codes.is_empty()
|
|
|| filter_codes.contains(&http_data.status_code.to_string()))
|
|
&& (exclude_codes.is_empty()
|
|
|| !exclude_codes.contains(&http_data.status_code.to_string()))
|
|
{
|
|
if options.show_full_data {
|
|
println!(
|
|
"{},[{}],[{}]",
|
|
http_data.checked_host, http_data.final_url, http_data.status_code
|
|
);
|
|
} else {
|
|
println!("{}://{}", http_data.protocol, http_data.checked_host);
|
|
}
|
|
}
|
|
(host, http_data)
|
|
}
|
|
}))
|
|
.buffer_unordered(threads)
|
|
.collect::<HashMap<String, HttpData>>()
|
|
.await
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn return_http_client(timeout: u64, max_redirects: usize) -> Client {
|
|
let policy = if max_redirects == 0 {
|
|
Policy::none()
|
|
} else {
|
|
Policy::limited(max_redirects)
|
|
};
|
|
|
|
reqwest::Client::builder()
|
|
.timeout(std::time::Duration::from_secs(timeout))
|
|
.redirect(policy)
|
|
.danger_accept_invalid_certs(true)
|
|
.use_native_tls()
|
|
.build()
|
|
.expect("Failed to create HTTP client")
|
|
}
|
|
|
|
#[allow(clippy::field_reassign_with_default)]
|
|
pub async fn assign_response_data(http_data: &mut HttpData, resp: Response, return_filters: bool) {
|
|
let headers = resp.headers().clone();
|
|
let url = resp.url().clone();
|
|
|
|
http_data.http_status = "ACTIVE".to_string();
|
|
http_data.content_type = headers
|
|
.get(CONTENT_TYPE)
|
|
.and_then(|v| v.to_str().ok())
|
|
.unwrap_or_default()
|
|
.to_string();
|
|
|
|
let full_body = (resp.text().await).unwrap_or_default();
|
|
|
|
http_data.content_length = headers
|
|
.get(CONTENT_LENGTH)
|
|
.and_then(|v| v.to_str().ok()?.parse().ok())
|
|
.unwrap_or_else(|| full_body.chars().count() as u64);
|
|
|
|
http_data.headers = format!("{headers:?}");
|
|
|
|
return_title_and_body(http_data, &full_body);
|
|
|
|
http_data.words_count = full_body.split_whitespace().count();
|
|
http_data.lines = full_body.lines().count() + 1;
|
|
http_data.points_to_another_host = url.host_str() != Some(&http_data.checked_host);
|
|
|
|
if return_filters {
|
|
let host = url.host_str().unwrap_or_default();
|
|
let client = return_http_client(5, 3);
|
|
let user_agents = utils::user_agents();
|
|
http_data.bad_data = return_filters_data(host, client, user_agents).await;
|
|
}
|
|
}
|
|
|
|
pub fn return_title_and_body(http_data: &mut HttpData, body: &str) {
|
|
let document = Html::parse_document(body);
|
|
|
|
// Return title
|
|
match Selector::parse("title") {
|
|
Ok(selector) => {
|
|
if let Some(title_element) = document.select(&selector).next() {
|
|
http_data.title = title_element.inner_html();
|
|
} else {
|
|
http_data.title = "NULL".to_string();
|
|
}
|
|
}
|
|
Err(err) => {
|
|
eprintln!("Failed to parse selector: {err:?}");
|
|
}
|
|
}
|
|
|
|
// Return body
|
|
match Selector::parse("body") {
|
|
Ok(selector) => {
|
|
if let Some(body_element) = document.select(&selector).next() {
|
|
http_data.body = body_element.inner_html();
|
|
} else {
|
|
http_data.body = "NULL".to_string();
|
|
}
|
|
}
|
|
Err(err) => {
|
|
eprintln!("Failed to parse selector: {err:?}");
|
|
}
|
|
}
|
|
|
|
drop(document);
|
|
}
|
|
|
|
pub async fn return_filters_data(
|
|
host: &str,
|
|
client: Client,
|
|
user_agents_list: Vec<String>,
|
|
) -> HTTPFilters {
|
|
let mut urls_to_check = HashSet::new();
|
|
let random_str = rng()
|
|
.sample_iter(Alphanumeric)
|
|
.take(16)
|
|
.map(char::from)
|
|
.collect::<String>();
|
|
let words = vec![
|
|
"admin".to_string() + &random_str + "/",
|
|
".htaccess".to_string() + &random_str,
|
|
random_str.to_string() + "/",
|
|
random_str.to_string(),
|
|
];
|
|
for word in words {
|
|
urls_to_check.insert(format!("{}/{}", &host, word));
|
|
}
|
|
|
|
let threads = urls_to_check.len();
|
|
let mut http_filters = HTTPFilters::default();
|
|
|
|
let lib_options = LibOptions {
|
|
hosts: urls_to_check,
|
|
client,
|
|
user_agents: user_agents_list,
|
|
retries: 1,
|
|
threads,
|
|
quiet_flag: true,
|
|
..Default::default()
|
|
};
|
|
|
|
let data = return_http_data(&lib_options, false).await;
|
|
|
|
data.values()
|
|
.map(|http_data| {
|
|
http_filters
|
|
.bad_http_lengths
|
|
.append(&mut vec![http_data.content_length.to_string()]);
|
|
http_filters.bad_words_numbers.append(&mut vec![http_data
|
|
.body
|
|
.split(' ')
|
|
.count()
|
|
.to_string()]);
|
|
http_filters
|
|
.bad_lines_numbers
|
|
.append(&mut vec![http_data.lines.to_string()]);
|
|
})
|
|
.for_each(drop);
|
|
|
|
http_filters
|
|
}
|