use super::Configuration;
use crate::{
message::FeroxMessage,
traits::FeroxSerialize,
utils::{module_colorizer, parse_url_with_raw_path, status_colorizer},
DEFAULT_BACKUP_EXTENSIONS, DEFAULT_IGNORED_EXTENSIONS, DEFAULT_METHOD, DEFAULT_STATUS_CODES,
DEFAULT_WORDLIST, VERSION,
};
use anyhow::{bail, Result};
use log::LevelFilter;
use std::collections::HashMap;
#[cfg(not(test))]
use std::process::exit;
pub(super) fn report_and_exit(err: &str) -> ! {
eprintln!(
"{} {}: {}",
status_colorizer("ERROR"),
module_colorizer("Configuration::new"),
err
);
#[cfg(test)]
panic!();
#[cfg(not(test))]
exit(1);
}
pub(super) fn serialized_type() -> String {
String::from("configuration")
}
pub(super) fn timeout() -> u64 {
7
}
pub(super) fn save_state() -> bool {
true
}
pub(super) fn threads() -> usize {
50
}
pub(super) fn request_protocol() -> String {
String::from("https")
}
pub(super) fn status_codes() -> Vec<u16> {
DEFAULT_STATUS_CODES
.iter()
.map(|code| code.as_u16())
.chain([103, 425])
.collect()
}
pub(super) fn methods() -> Vec<String> {
vec![DEFAULT_METHOD.to_owned()]
}
pub(super) fn ignored_extensions() -> Vec<String> {
DEFAULT_IGNORED_EXTENSIONS
.iter()
.map(|s| s.to_string())
.collect()
}
pub(super) fn backup_extensions() -> Vec<String> {
DEFAULT_BACKUP_EXTENSIONS
.iter()
.map(|s| s.to_string())
.collect()
}
pub(super) fn wordlist() -> String {
String::from(DEFAULT_WORDLIST)
}
pub(super) fn user_agent() -> String {
format!("feroxbuster/{VERSION}")
}
pub(super) fn depth() -> usize {
4
}
pub(super) fn extract_links() -> bool {
true
}
pub(super) fn response_size_limit() -> usize {
4 * 1024 * 1024 }
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum OutputLevel {
Default,
Quiet,
Silent,
SilentJSON,
}
impl Default for OutputLevel {
fn default() -> Self {
Self::Default
}
}
pub fn determine_output_level(quiet: bool, silent: bool, json: bool) -> OutputLevel {
if quiet && silent {
if json {
OutputLevel::SilentJSON
} else {
OutputLevel::Silent
}
} else if quiet {
OutputLevel::Quiet
} else if silent {
if json {
OutputLevel::SilentJSON
} else {
OutputLevel::Silent
}
} else {
OutputLevel::Default
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum RequesterPolicy {
AutoTune,
AutoBail,
Default,
}
impl Default for RequesterPolicy {
fn default() -> Self {
Self::Default
}
}
pub fn determine_requester_policy(auto_tune: bool, auto_bail: bool) -> RequesterPolicy {
if auto_tune && auto_bail {
RequesterPolicy::AutoBail
} else if auto_tune {
RequesterPolicy::AutoTune
} else if auto_bail {
RequesterPolicy::AutoBail
} else {
RequesterPolicy::Default
}
}
pub fn split_query(query: &str) -> Result<(String, String)> {
if query.is_empty() || query == "=" {
bail!("Empty query string provided");
}
let mut split_val = query.split('=');
let name = split_val.next().unwrap().trim();
if name.is_empty() {
bail!("Empty key in query string");
}
let value = split_val.collect::<Vec<&str>>().join("=");
Ok((name.to_string(), value.to_string()))
}
pub fn split_header(header: &str) -> Result<(String, String)> {
if header.is_empty() {
bail!("Empty header provided");
}
let mut split_val = header.split(':');
let name = split_val.next().unwrap().trim().to_string();
if name.is_empty() {
bail!("Empty header name provided");
}
let value = split_val.collect::<Vec<&str>>().join(":");
if value.starts_with(' ') && !value.starts_with(" ") {
let trimmed = value.trim_start();
Ok((name, trimmed.to_string()))
} else {
Ok((name, value))
}
}
pub fn combine_cookies(cookie1: &str, cookie2: &str) -> String {
let mut cookie_map = HashMap::new();
let parse_cookie = |cookie_str: &str, map: &mut HashMap<String, String>| {
for pair in cookie_str.split(';') {
let mut key_value = pair.trim().splitn(2, '=');
if let (Some(key), Some(value)) = (key_value.next(), key_value.next()) {
map.insert(key.to_string(), value.to_string());
}
}
};
parse_cookie(cookie1, &mut cookie_map);
parse_cookie(cookie2, &mut cookie_map);
cookie_map
.into_iter()
.map(|(key, value)| format!("{key}={value}"))
.collect::<Vec<_>>()
.join("; ")
}
pub enum ContentType {
Json,
UrlEncoded,
}
impl ContentType {
pub fn to_header_value(self: ContentType) -> String {
match self {
Self::Json => "application/json".to_string(),
Self::UrlEncoded => "application/x-www-form-urlencoded".to_string(),
}
}
}
pub fn parse_request_file(config: &mut Configuration) -> Result<()> {
let contents = std::fs::read_to_string(&config.request_file)?;
if contents.is_empty() {
bail!("Empty --request-file file provided");
}
let lines = contents.split("\r\n\r\n").collect::<Vec<&str>>();
if lines.len() < 2 {
bail!("Invalid request: Missing head/body CRLF separator");
}
let head = lines[0];
let body = lines[1].as_bytes().to_vec();
if config.data.is_empty() {
config.data = body;
}
let mut head_parts = head.split("\r\n");
let Some(request_line) = head_parts.next() else {
bail!("Invalid request: Missing request line");
};
if request_line.is_empty() {
bail!("Invalid request: Empty request line");
}
let mut request_parts = request_line.split_whitespace();
let Some(method) = request_parts.next() else {
bail!("Invalid request: Missing method");
};
if method.is_empty() {
bail!("Invalid request: Empty method");
}
let method = method.to_string();
if !config.methods.contains(&method) {
config.methods.push(method);
}
let Some(uri) = request_parts.next() else {
bail!("Invalid request: Missing request line URI");
};
if uri.is_empty() {
bail!("Invalid request: Empty request line URI");
}
for mut line in head_parts {
line = line.trim();
if line.is_empty() {
break; }
let Ok((name, value)) = split_header(line) else {
log::warn!("Invalid header: {line}");
continue;
};
if name.is_empty() {
log::warn!("Invalid header name: {line}");
continue;
}
if name.to_lowercase() == "user-agent" {
if config.user_agent == user_agent() {
config.user_agent = value;
}
continue;
}
if name.to_lowercase() == "content-length" {
log::debug!("Skipping content-length header, a new one will be created");
continue;
}
if config.headers.contains_key(&name) {
if name.to_lowercase() == "cookie" {
let existing = config.headers.get_mut(&name).unwrap();
let combined = combine_cookies(&value, existing);
*existing = combined;
continue;
}
log::debug!("Found header from cli, overriding raw request with cli entry: {name}");
continue;
}
config.headers.insert(name, value);
}
let url = parse_url_with_raw_path(uri);
if let Ok(mut url) = url {
if let Some(host) = config.headers.get("Host") {
url.set_host(Some(host)).unwrap();
}
url.query_pairs().for_each(|(key, value)| {
for (k, _) in &config.queries {
if k.to_lowercase() == key.to_lowercase() {
return;
}
}
config.queries.push((key.to_string(), value.to_string()));
});
url.set_query(None);
url.set_fragment(None);
config.target_url = url.to_string();
config.scope.push(url);
} else {
for (key, value) in &config.headers {
if key.to_lowercase() == "host" {
config.target_url = format!("{}://{value}{uri}", config.protocol);
break;
}
}
if config.target_url.is_empty() {
bail!("Invalid request: Missing Host header and request line URI isn't a full URL");
}
if let Ok(url) = parse_url_with_raw_path(&config.target_url) {
config.scope.push(url);
} else {
bail!(
"Invalid request: Could not parse target URL {}",
config.target_url
);
}
let mut uri_parts = uri.splitn(2, '?');
uri_parts.next();
if let Some(queries) = uri_parts.next() {
let query_parts = queries.split("&");
query_parts.into_iter().for_each(|query| {
let Ok((name, value)) = split_query(query) else {
return;
};
for (k, _) in &config.queries {
if k.to_lowercase() == name.to_lowercase() {
return;
}
}
config.queries.push((name, value));
});
}
}
Ok(())
}
pub fn preconfig_log(level: LevelFilter, message: String) {
let log = FeroxMessage {
module: "feroxbuster::config".to_owned(),
level: level.as_str().to_owned(),
message,
..Default::default()
};
eprintln!("{}", log.as_str());
}
#[cfg(test)]
mod tests {
use super::*;
use std::env;
use std::fs::{self, File};
use std::io::{self, Write};
use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};
struct TempSetup {
pub path: PathBuf,
pub config: Configuration,
pub file: File,
}
impl TempSetup {
pub fn new() -> Self {
let mut temp_dir: PathBuf = env::temp_dir();
temp_dir.push(format!(
"temp_request_file_{}.txt",
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos()
));
let config: Configuration = Configuration {
request_file: temp_dir.to_str().unwrap().to_string(),
..Default::default()
};
let file = File::create(&temp_dir).unwrap();
Self {
path: temp_dir,
config,
file,
}
}
pub fn cleanup(self) {
fs::remove_file(self.path).unwrap();
}
}
#[test]
fn determine_output_level_returns_correct_results() {
let mut level = determine_output_level(true, true, false);
assert_eq!(level, OutputLevel::Silent);
level = determine_output_level(false, true, false);
assert_eq!(level, OutputLevel::Silent);
let mut level = determine_output_level(true, true, true);
assert_eq!(level, OutputLevel::SilentJSON);
level = determine_output_level(false, true, true);
assert_eq!(level, OutputLevel::SilentJSON);
level = determine_output_level(false, false, false);
assert_eq!(level, OutputLevel::Default);
level = determine_output_level(true, false, false);
assert_eq!(level, OutputLevel::Quiet);
level = determine_output_level(false, false, true);
assert_eq!(level, OutputLevel::Default);
level = determine_output_level(true, false, true);
assert_eq!(level, OutputLevel::Quiet);
}
#[test]
fn determine_requester_policy_returns_correct_results() {
let mut level = determine_requester_policy(true, true);
assert_eq!(level, RequesterPolicy::AutoBail);
level = determine_requester_policy(false, true);
assert_eq!(level, RequesterPolicy::AutoBail);
level = determine_requester_policy(false, false);
assert_eq!(level, RequesterPolicy::Default);
level = determine_requester_policy(true, false);
assert_eq!(level, RequesterPolicy::AutoTune);
}
#[test]
#[should_panic]
fn report_and_exit_panics_under_test() {
report_and_exit("test");
}
#[test]
fn test_split_query_simple() {
let query = "name=value";
let result = split_query(query).unwrap();
assert_eq!(result, ("name".to_string(), "value".to_string()));
}
#[test]
fn test_split_query_with_spaces() {
let query = " name = value ";
let result = split_query(query).unwrap();
assert_eq!(result, ("name".to_string(), " value ".to_string()));
}
#[test]
fn test_split_query_empty_value() {
let query = "name=";
let result = split_query(query).unwrap();
assert_eq!(result, ("name".to_string(), "".to_string()));
}
#[test]
fn test_split_query_no_value() {
let query = "name";
let result = split_query(query).unwrap();
assert_eq!(result, ("name".to_string(), "".to_string()));
}
#[test]
fn test_split_query_multiple_equals() {
let query = "name=value=another";
let result = split_query(query).unwrap();
assert_eq!(result, ("name".to_string(), "value=another".to_string()));
}
#[test]
fn test_split_query_empty_key_and_value() {
let query = "=";
let result = split_query(query);
assert!(result.is_err());
}
#[test]
fn test_split_query_empty_key() {
let query = "=value";
let result = split_query(query);
assert!(result.is_err());
}
#[test]
fn test_split_query_trailing_equals_in_value() {
let query = "name=value=";
let result = split_query(query).unwrap();
assert_eq!(result, ("name".to_string(), "value=".to_string()));
}
#[test]
fn test_split_query_no_equals() {
let query = "just_a_key";
let result = split_query(query).unwrap();
assert_eq!(result, ("just_a_key".to_string(), "".to_string()));
}
#[test]
fn test_split_query_empty_input() {
let query = "";
assert!(split_query(query).is_err());
}
#[test]
fn test_split_header_simple() -> Result<()> {
let header = "Content-Type: text/html";
let result = split_header(header)?;
assert_eq!(
result,
("Content-Type".to_string(), "text/html".to_string())
);
Ok(())
}
#[test]
fn test_split_header_with_leading_space_in_value() -> Result<()> {
let header = "Content-Type: text/html";
let result = split_header(header)?;
assert_eq!(
result,
("Content-Type".to_string(), " text/html".to_string())
);
Ok(())
}
#[test]
fn test_split_header_with_trimmed_leading_space() -> Result<()> {
let header = "Content-Type: text/html";
let result = split_header(header)?;
assert_eq!(
result,
("Content-Type".to_string(), "text/html".to_string())
);
Ok(())
}
#[test]
fn test_split_header_with_multiple_colons() -> Result<()> {
let header = "Date: Mon, 27 Jul 2009 12:28:53 GMT";
let result = split_header(header)?;
assert_eq!(
result,
(
"Date".to_string(),
"Mon, 27 Jul 2009 12:28:53 GMT".to_string()
)
);
Ok(())
}
#[test]
fn test_split_header_empty_value() -> Result<()> {
let header = "X-Custom-Header: ";
let result = split_header(header)?;
assert_eq!(result, ("X-Custom-Header".to_string(), "".to_string()));
Ok(())
}
#[test]
fn test_split_header_no_value() -> Result<()> {
let header = "X-Custom-Header:";
let result = split_header(header)?;
assert_eq!(result, ("X-Custom-Header".to_string(), "".to_string()));
Ok(())
}
#[test]
fn test_split_header_no_colon() -> Result<()> {
let header = "InvalidHeader";
let result = split_header(header)?;
assert_eq!(result, ("InvalidHeader".to_string(), "".to_string()));
Ok(())
}
#[test]
fn test_split_header_empty_key() {
let header = ": value";
let result = split_header(header);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"Empty header name provided"
);
}
#[test]
fn test_split_header_empty_key_and_value() {
let header = ": ";
let result = split_header(header);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"Empty header name provided"
);
}
#[test]
fn test_split_header_empty_input() {
let header = "";
let result = split_header(header);
assert!(result.is_err());
assert_eq!(result.unwrap_err().to_string(), "Empty header provided");
}
#[test]
fn test_split_header_value_with_leading_single_space() -> Result<()> {
let header = "Authorization: Bearer token";
let result = split_header(header)?;
assert_eq!(
result,
("Authorization".to_string(), "Bearer token".to_string())
);
Ok(())
}
#[test]
fn test_split_header_value_with_leading_multiple_spaces() -> Result<()> {
let header = "Authorization: Bearer token";
let result = split_header(header)?;
assert_eq!(
result,
("Authorization".to_string(), " Bearer token".to_string())
);
Ok(())
}
#[test]
fn test_parse_raw_with_empty_request() {
let mut config = Configuration::new().unwrap();
let result = parse_request_file(&mut config);
assert!(result.is_err());
}
#[test]
fn test_parse_raw_with_empty_file() -> io::Result<()> {
let mut tmp = TempSetup::new();
let result = parse_request_file(&mut tmp.config);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"Empty --request-file file provided"
);
tmp.cleanup();
Ok(())
}
#[test]
fn test_parse_raw_without_head_body_crlf() -> io::Result<()> {
let mut tmp = TempSetup::new();
write!(tmp.file, "GET / HTTP/1.1\r\n")?;
let result = parse_request_file(&mut tmp.config);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"Invalid request: Missing head/body CRLF separator"
);
tmp.cleanup();
Ok(())
}
#[test]
fn test_parse_raw_with_only_head_body_crlf() -> io::Result<()> {
let mut tmp: TempSetup = TempSetup::new();
writeln!(tmp.file, "\r\n\r\n")?;
let result = parse_request_file(&mut tmp.config);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"Invalid request: Empty request line"
);
tmp.cleanup();
Ok(())
}
#[test]
fn test_parse_raw_body_is_overridden_by_cli() -> io::Result<()> {
let mut tmp: TempSetup = TempSetup::new();
write!(
tmp.file,
"GET http://localhost/srv HTTP/1.0\r\n\r\nrequest-body"
)?;
parse_request_file(&mut tmp.config).unwrap();
assert_eq!(tmp.config.data, b"request-body".to_vec());
tmp.config.data = b"cli-data".to_vec();
parse_request_file(&mut tmp.config).unwrap();
assert_eq!(tmp.config.data, b"cli-data".to_vec());
tmp.cleanup();
Ok(())
}
#[test]
fn test_parse_raw_with_empty_request_line() -> io::Result<()> {
let mut tmp: TempSetup = TempSetup::new();
write!(tmp.file, "\r\nHost: example.com\r\n\r\n")?;
let result = parse_request_file(&mut tmp.config);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"Invalid request: Empty request line"
);
tmp.cleanup();
Ok(())
}
#[test]
fn test_parse_raw_with_missing_uri() -> io::Result<()> {
let mut tmp: TempSetup = TempSetup::new();
write!(tmp.file, "GET\r\nHost: example.com\r\n\r\n")?;
let result = parse_request_file(&mut tmp.config);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"Invalid request: Missing request line URI"
);
tmp.cleanup();
Ok(())
}
#[test]
fn test_parse_raw_with_missing_method() -> io::Result<()> {
let mut tmp: TempSetup = TempSetup::new();
write!(tmp.file, " \r\nHost: example.com\r\n\r\n")?;
let result = parse_request_file(&mut tmp.config);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"Invalid request: Missing method"
);
tmp.cleanup();
Ok(())
}
#[test]
fn test_parse_raw_methods_are_appended_if_unique() -> io::Result<()> {
let mut tmp: TempSetup = TempSetup::new();
write!(
tmp.file,
"POST / HTTP/1.1\r\nHost: example.com\r\nUser-Agent: test-agent\r\n\r\n"
)?;
let result = parse_request_file(&mut tmp.config);
assert!(result.is_ok());
assert_eq!(tmp.config.methods, vec!["GET", "POST"]);
tmp.cleanup();
Ok(())
}
#[test]
fn test_parse_raw_methods_are_ignored_if_already_present_from_cli() -> io::Result<()> {
let mut tmp: TempSetup = TempSetup::new();
write!(
tmp.file,
"GET / HTTP/1.1\r\nHost: example.com\r\nUser-Agent: test-agent\r\n\r\n"
)?;
let result = parse_request_file(&mut tmp.config);
assert!(result.is_ok());
assert_eq!(tmp.config.methods, vec!["GET"]);
tmp.cleanup();
Ok(())
}
#[test]
fn test_parse_raw_headers_added_to_config_if_missing_else_overridden_from_cli() -> io::Result<()>
{
let mut tmp: TempSetup = TempSetup::new();
tmp.config
.headers
.insert(String::from("stuff"), String::from("things"));
write!(
tmp.file,
"GET / HTTP/1.1\r\nHost: example.com\r\nstuff: mothings\r\n\r\n"
)?;
let result = parse_request_file(&mut tmp.config);
assert!(result.is_ok());
assert!(tmp.config.headers.contains_key("Host"));
assert_eq!(tmp.config.headers.get("stuff").unwrap(), "things");
tmp.cleanup();
Ok(())
}
#[test]
fn test_parse_raw_with_user_agent_in_request() -> io::Result<()> {
let mut tmp: TempSetup = TempSetup::new();
write!(
tmp.file,
"GET / HTTP/1.1\r\nHost: example.com\r\nUser-Agent: test-agent\r\n\r\n"
)?;
let result = parse_request_file(&mut tmp.config);
assert!(result.is_ok());
assert_eq!(tmp.config.user_agent, "test-agent");
tmp.cleanup();
Ok(())
}
#[test]
fn test_parse_raw_with_user_agent_in_request_and_cli() -> io::Result<()> {
let mut tmp: TempSetup = TempSetup::new();
write!(
tmp.file,
"GET / HTTP/1.1\r\nHost: example.com\r\nUser-Agent: test-agent\r\n\r\n"
)?;
tmp.config.user_agent = "cli-agent".to_string();
let result = parse_request_file(&mut tmp.config);
assert!(result.is_ok());
assert_eq!(tmp.config.user_agent, "cli-agent");
tmp.cleanup();
Ok(())
}
#[test]
fn test_parse_raw_content_length_is_always_skipped() -> io::Result<()> {
let mut tmp: TempSetup = TempSetup::new();
write!(
tmp.file,
"GET / HTTP/1.1\r\nHost: example.com\r\nContent-length: 21\r\n\r\n"
)?;
let result = parse_request_file(&mut tmp.config);
assert!(result.is_ok());
assert!(!tmp.config.headers.contains_key("Content-length"));
tmp.cleanup();
Ok(())
}
#[test]
fn test_parse_raw_cookie_header_appended_or_overridden() -> io::Result<()> {
let mut tmp: TempSetup = TempSetup::new();
write!(
tmp.file,
"GET / HTTP/1.1\r\nHost: example.com\r\nCookie: derp=tronic2; super=duper2\r\n\r\n"
)?;
tmp.config.headers.insert(
"Cookie".to_string(),
"derp=tronic; stuff=things".to_string(),
);
let result = parse_request_file(&mut tmp.config);
assert!(result.is_ok());
let cookies = tmp.config.headers.get("Cookie").unwrap();
assert!(cookies.contains("derp=tronic"));
assert!(cookies.contains("stuff=things"));
assert!(cookies.contains("super=duper2"));
assert!(!cookies.contains("derp=tronic2"));
tmp.cleanup();
Ok(())
}
#[test]
fn test_parse_raw_with_relative_path_and_partial_host_header() -> io::Result<()> {
let mut tmp: TempSetup = TempSetup::new();
write!(tmp.file, "GET /srv HTTP/1.1\r\nHost: example.com\r\n\r\n")?;
let result = parse_request_file(&mut tmp.config);
assert!(result.is_ok());
assert_eq!(tmp.config.target_url, "https://example.com/srv");
tmp.cleanup();
Ok(())
}
#[test]
fn test_parse_raw_with_relative_path_and_no_host_header() -> io::Result<()> {
let mut tmp: TempSetup = TempSetup::new();
write!(tmp.file, "GET /srv HTTP/1.1\r\n\r\n")?;
let result: std::result::Result<(), anyhow::Error> = parse_request_file(&mut tmp.config);
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
"Invalid request: Missing Host header and request line URI isn't a full URL"
);
tmp.cleanup();
Ok(())
}
#[test]
fn test_parse_raw_with_full_url_and_no_host_header() -> io::Result<()> {
let mut tmp: TempSetup = TempSetup::new();
write!(tmp.file, "GET http://localhost/srv HTTP/1.1\r\n\r\n")?;
let result: std::result::Result<(), anyhow::Error> = parse_request_file(&mut tmp.config);
assert!(result.is_ok());
assert_eq!(tmp.config.target_url, "http://localhost/srv");
tmp.cleanup();
Ok(())
}
#[test]
fn test_parse_raw_with_full_url_and_host_header() -> io::Result<()> {
let mut tmp: TempSetup = TempSetup::new();
write!(
tmp.file,
"GET http://localhost/srv HTTP/1.1\r\nHost: example.com\r\n\r\n"
)?;
let result: std::result::Result<(), anyhow::Error> = parse_request_file(&mut tmp.config);
assert!(result.is_ok());
assert_eq!(tmp.config.target_url, "http://example.com/srv");
tmp.cleanup();
Ok(())
}
#[test]
fn test_parse_raw_with_partial_url_and_queries() -> io::Result<()> {
let mut tmp: TempSetup = TempSetup::new();
write!(
tmp.file,
"GET /srv?mostuff=mothings&derp=tronic2 HTTP/1.1\r\nHost: example.com\r\n\r\n"
)?;
tmp.config
.queries
.push(("derp".to_string(), "tronic".to_string()));
tmp.config
.queries
.push(("stuff".to_string(), "things".to_string()));
let result: std::result::Result<(), anyhow::Error> = parse_request_file(&mut tmp.config);
assert!(result.is_ok());
assert_eq!(
tmp.config.queries,
vec![
(String::from("derp"), String::from("tronic")),
(String::from("stuff"), String::from("things")),
(String::from("mostuff"), String::from("mothings"))
]
);
tmp.cleanup();
Ok(())
}
#[test]
fn test_parse_raw_with_full_url_and_queries() -> io::Result<()> {
let mut tmp: TempSetup = TempSetup::new();
write!(
tmp.file,
"GET http://localhost/srv?mostuff=mothings&derp=tronic2 HTTP/1.1\r\nHost: example.com\r\n\r\n"
)?;
tmp.config
.queries
.push(("derp".to_string(), "tronic".to_string()));
tmp.config
.queries
.push(("stuff".to_string(), "things".to_string()));
let result: std::result::Result<(), anyhow::Error> = parse_request_file(&mut tmp.config);
assert!(result.is_ok());
assert_eq!(
tmp.config.queries,
vec![
(String::from("derp"), String::from("tronic")),
(String::from("stuff"), String::from("things")),
(String::from("mostuff"), String::from("mothings"))
]
);
tmp.cleanup();
Ok(())
}
#[test]
fn test_combine_cookies() {
let cookie1 = "super=duper; stuff=things";
let cookie2 = "stuff=mothings; derp=tronic";
let combined_cookie = combine_cookies(cookie1, cookie2);
assert!(combined_cookie.contains("super=duper"));
assert!(combined_cookie.contains("stuff=mothings"));
assert!(combined_cookie.contains("derp=tronic"));
assert!(combined_cookie.contains("; "));
}
#[test]
fn test_split_header() {
let result = split_header("Content-Type: application/json");
assert_eq!(
result.unwrap(),
("Content-Type".to_string(), "application/json".to_string())
);
let result = split_header("Content-Length: 1234");
assert_eq!(
result.unwrap(),
("Content-Length".to_string(), "1234".to_string())
);
let result = split_header("Authorization: Bearer token");
assert_eq!(
result.unwrap(),
("Authorization".to_string(), "Bearer token".to_string())
);
let result = split_header("NoValueHeader");
assert_eq!(
result.unwrap(),
("NoValueHeader".to_string(), "".to_string())
);
let result = split_header("");
assert!(result.is_err());
}
#[test]
fn test_split_query() {
let result = split_query("name=John");
assert_eq!(result.unwrap(), ("name".to_string(), "John".to_string()));
let result = split_query("name=");
assert_eq!(result.unwrap(), ("name".to_string(), "".to_string()));
let result = split_query("name=John=Doe");
assert_eq!(
result.unwrap(),
("name".to_string(), "John=Doe".to_string())
);
let result = split_query("=John");
assert!(result.is_err());
let result = split_query("");
assert!(result.is_err());
}
}