use crate::{
config::{CONFIGURATION, PROGRESS_PRINTER},
FeroxError, FeroxResult,
};
use console::{strip_ansi_codes, style, user_attended};
use indicatif::ProgressBar;
use reqwest::{Client, Response, Url};
#[cfg(not(target_os = "windows"))]
use rlimit::{getrlimit, setrlimit, Resource, Rlim};
use std::convert::TryInto;
use std::sync::{Arc, RwLock};
use std::{fs, io};
pub fn open_file(filename: &str) -> Option<Arc<RwLock<io::BufWriter<fs::File>>>> {
log::trace!("enter: open_file({})", filename);
match fs::OpenOptions::new()
.create(true)
.append(true)
.open(filename)
{
Ok(file) => {
let writer = io::BufWriter::new(file);
let locked_file = Some(Arc::new(RwLock::new(writer)));
log::trace!("exit: open_file -> {:?}", locked_file);
locked_file
}
Err(e) => {
log::error!("{}", e);
log::trace!("exit: open_file -> None");
None
}
}
}
pub fn get_current_depth(target: &str) -> usize {
log::trace!("enter: get_current_depth({})", target);
let target = normalize_url(target);
match Url::parse(&target) {
Ok(url) => {
if let Some(parts) = url.path_segments() {
let mut depth = 0;
for _ in parts {
depth += 1;
}
let return_val = depth;
log::trace!("exit: get_current_depth -> {}", return_val);
return return_val;
};
log::debug!(
"get_current_depth called on a Url that cannot be a base: {}",
url
);
log::trace!("exit: get_current_depth -> 0");
0
}
Err(e) => {
log::error!("could not parse to url: {}", e);
log::trace!("exit: get_current_depth -> 0");
0
}
}
}
pub fn status_colorizer(status: &str) -> String {
match status.chars().next() {
Some('1') => style(status).blue().to_string(),
Some('2') => style(status).green().to_string(),
Some('3') => style(status).yellow().to_string(),
Some('4') => style(status).red().to_string(),
Some('5') => style(status).red().to_string(),
Some('W') => style(status).cyan().to_string(),
Some('E') => style(status).red().to_string(),
_ => status.to_string(),
}
}
pub fn module_colorizer(modname: &str) -> String {
style(modname).cyan().to_string()
}
pub fn get_url_path_length(url: &Url) -> u64 {
log::trace!("enter: get_url_path_length({})", url);
let path = url.path();
let segments = if let Some(split) = path.strip_prefix('/') {
split.split_terminator('/')
} else {
log::trace!("exit: get_url_path_length -> 0");
return 0;
};
if let Some(last) = segments.last() {
let url_len: u64 = last
.len()
.try_into()
.expect("Failed usize -> u64 conversion");
log::trace!("exit: get_url_path_length -> {}", url_len);
return url_len;
}
log::trace!("exit: get_url_path_length -> 0");
0
}
pub fn ferox_print(msg: &str, bar: &ProgressBar) {
if user_attended() {
bar.println(msg);
} else {
let stripped = strip_ansi_codes(msg);
println!("{}", stripped);
}
}
pub fn format_url(
url: &str,
word: &str,
add_slash: bool,
queries: &[(String, String)],
extension: Option<&str>,
) -> FeroxResult<Url> {
log::trace!(
"enter: format_url({}, {}, {}, {:?} {:?})",
url,
word,
add_slash,
queries,
extension
);
if Url::parse(&word).is_ok() {
let message = format!(
"word ({}) from the wordlist is actually a URL, skipping...",
word
);
log::warn!("{}", message);
let mut err = FeroxError::default();
err.message = message;
log::trace!("exit: format_url -> {}", err);
return Err(Box::new(err));
}
let url = if word.is_empty() {
url.to_string()
} else if !url.ends_with('/') {
format!("{}/", url)
} else {
url.to_string()
};
let base_url = reqwest::Url::parse(&url)?;
let word = if extension.is_some() {
format!("{}.{}", word, extension.unwrap())
} else if add_slash && !word.ends_with('/') {
format!("{}/", word)
} else {
String::from(word)
};
match base_url.join(&word) {
Ok(request) => {
if queries.is_empty() {
log::trace!("exit: format_url -> {}", request);
Ok(request)
} else {
match reqwest::Url::parse_with_params(request.as_str(), queries) {
Ok(req_w_params) => {
log::trace!("exit: format_url -> {}", req_w_params);
Ok(req_w_params)
}
Err(e) => {
log::error!(
"Could not add query params {:?} to {}: {}",
queries,
request,
e
);
log::trace!("exit: format_url -> {}", request);
Ok(request)
}
}
}
}
Err(e) => {
log::trace!("exit: format_url -> {}", e);
log::error!("Could not join {} with {}", word, base_url);
Err(Box::new(e))
}
}
}
pub async fn make_request(client: &Client, url: &Url) -> FeroxResult<Response> {
log::trace!("enter: make_request(CONFIGURATION.Client, {})", url);
match client.get(url.to_owned()).send().await {
Ok(resp) => {
log::trace!("exit: make_request -> {:?}", resp);
Ok(resp)
}
Err(e) => {
log::trace!("exit: make_request -> {}", e);
if e.to_string().contains("operation timed out") {
log::warn!("Error while making request: {}", e);
} else if e.is_redirect() {
if let Some(last_redirect) = e.url() {
let fancy_message = format!("{} !=> {}", url, last_redirect);
let report = if let Some(msg_status) = e.status() {
create_report_string(msg_status.as_str(), "-1", "-1", "-1", &fancy_message)
} else {
create_report_string("UNK", "-1", "-1", "-1", &fancy_message)
};
ferox_print(&report, &PROGRESS_PRINTER)
};
} else {
log::error!("Error while making request: {}", e);
}
Err(Box::new(e))
}
}
}
pub fn create_report_string(
status: &str,
line_count: &str,
word_count: &str,
content_length: &str,
url: &str,
) -> String {
if CONFIGURATION.quiet {
format!("{}\n", url)
} else {
let color_status = status_colorizer(status);
format!(
"{} {:>8}l {:>8}w {:>8}c {}\n",
color_status, line_count, word_count, content_length, url
)
}
}
#[cfg(not(target_os = "windows"))]
pub fn set_open_file_limit(limit: usize) -> bool {
log::trace!("enter: set_open_file_limit");
if let Ok((soft, hard)) = getrlimit(Resource::NOFILE) {
if hard.as_usize() > limit {
let new_soft_limit = Rlim::from_usize(limit);
if setrlimit(Resource::NOFILE, new_soft_limit, hard).is_ok() {
log::debug!("set open file descriptor limit to {}", limit);
log::trace!("exit: set_open_file_limit -> {}", true);
return true;
}
} else if soft != hard {
if setrlimit(Resource::NOFILE, hard, hard).is_ok() {
log::debug!("set open file descriptor limit to {}", limit);
log::trace!("exit: set_open_file_limit -> {}", true);
return true;
}
}
}
log::warn!("could not set open file descriptor limit to {}", limit);
log::trace!("exit: set_open_file_limit -> {}", false);
false
}
pub fn normalize_url(url: &str) -> String {
log::trace!("enter: normalize_url({})", url);
let normalized = if url.ends_with('/') {
url.to_string()
} else {
format!("{}/", url)
};
log::trace!("exit: normalize_url -> {}", normalized);
normalized
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn utils_set_open_file_limit_with_low_requested_limit() {
let (_, hard) = getrlimit(Resource::NOFILE).unwrap();
let lower_limit = hard.as_usize() - 1;
assert!(set_open_file_limit(lower_limit));
}
#[test]
fn utils_set_open_file_limit_with_high_requested_limit() {
let (_, hard) = getrlimit(Resource::NOFILE).unwrap();
let higher_limit = hard.as_usize() + 1;
let new_soft = Rlim::from_usize(hard.as_usize() - 1);
setrlimit(Resource::NOFILE, new_soft, hard).unwrap();
assert!(set_open_file_limit(higher_limit));
}
#[test]
fn utils_set_open_file_limit_with_fails_when_both_limits_are_equal() {
let (_, hard) = getrlimit(Resource::NOFILE).unwrap();
setrlimit(Resource::NOFILE, hard, hard).unwrap();
assert!(!set_open_file_limit(hard.as_usize()));
}
#[test]
fn get_current_depth_base_url_returns_1() {
let depth = get_current_depth("http://localhost");
assert_eq!(depth, 1);
}
#[test]
fn get_current_depth_base_url_with_slash_returns_1() {
let depth = get_current_depth("http://localhost/");
assert_eq!(depth, 1);
}
#[test]
fn get_current_depth_one_dir_returns_2() {
let depth = get_current_depth("http://localhost/src");
assert_eq!(depth, 2);
}
#[test]
fn get_current_depth_one_dir_with_slash_returns_2() {
let depth = get_current_depth("http://localhost/src/");
assert_eq!(depth, 2);
}
#[test]
fn get_current_depth_single_forward_slash_is_zero() {
let depth = get_current_depth("");
assert_eq!(depth, 0);
}
#[test]
fn format_url_normal() {
assert_eq!(
format_url("http://localhost", "stuff", false, &Vec::new(), None).unwrap(),
reqwest::Url::parse("http://localhost/stuff").unwrap()
);
}
#[test]
fn format_url_no_word() {
assert_eq!(
format_url("http://localhost", "", false, &Vec::new(), None).unwrap(),
reqwest::Url::parse("http://localhost").unwrap()
);
}
#[test]
fn format_url_joins_queries() {
assert_eq!(
format_url(
"http://localhost",
"lazer",
false,
&[(String::from("stuff"), String::from("things"))],
None
)
.unwrap(),
reqwest::Url::parse("http://localhost/lazer?stuff=things").unwrap()
);
}
#[test]
fn format_url_without_word_joins_queries() {
assert_eq!(
format_url(
"http://localhost",
"",
false,
&[(String::from("stuff"), String::from("things"))],
None
)
.unwrap(),
reqwest::Url::parse("http://localhost/?stuff=things").unwrap()
);
}
#[test]
#[should_panic]
fn format_url_no_url() {
format_url("", "stuff", false, &Vec::new(), None).unwrap();
}
#[test]
fn format_url_word_with_preslash() {
assert_eq!(
format_url("http://localhost", "/stuff", false, &Vec::new(), None).unwrap(),
reqwest::Url::parse("http://localhost/stuff").unwrap()
);
}
#[test]
fn format_url_word_with_postslash() {
assert_eq!(
format_url("http://localhost", "stuff/", false, &Vec::new(), None).unwrap(),
reqwest::Url::parse("http://localhost/stuff/").unwrap()
);
}
#[test]
fn format_url_word_that_is_a_url() {
let url = format_url(
"http://localhost",
"http://schmocalhost",
false,
&Vec::new(),
None,
);
assert!(url.is_err());
}
#[test]
fn status_colorizer_uses_red_for_500s() {
assert_eq!(status_colorizer("500"), style("500").red().to_string());
}
#[test]
fn status_colorizer_uses_red_for_400s() {
assert_eq!(status_colorizer("400"), style("400").red().to_string());
}
#[test]
fn status_colorizer_uses_red_for_errors() {
assert_eq!(status_colorizer("ERROR"), style("ERROR").red().to_string());
}
#[test]
fn status_colorizer_uses_cyan_for_wildcards() {
assert_eq!(status_colorizer("WLD"), style("WLD").cyan().to_string());
}
#[test]
fn status_colorizer_uses_blue_for_100s() {
assert_eq!(status_colorizer("100"), style("100").blue().to_string());
}
#[test]
fn status_colorizer_uses_green_for_200s() {
assert_eq!(status_colorizer("200"), style("200").green().to_string());
}
#[test]
fn status_colorizer_uses_yellow_for_300s() {
assert_eq!(status_colorizer("300"), style("300").yellow().to_string());
}
#[test]
fn status_colorizer_returns_as_is() {
assert_eq!(status_colorizer("farfignewton"), "farfignewton".to_string());
}
}