Compare commits
No commits in common. "cb2f106239a95d840c2ec20426ffcf3577f8d9d1" and "1b77c0daa61bddc975ec23386c97d502af0be462" have entirely different histories.
cb2f106239
...
1b77c0daa6
4 changed files with 38 additions and 285 deletions
64
Cargo.lock
generated
64
Cargo.lock
generated
|
|
@ -414,7 +414,9 @@ checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android-tzdata",
|
"android-tzdata",
|
||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
|
"js-sys",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
"wasm-bindgen",
|
||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -440,6 +442,7 @@ version = "4.5.37"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2"
|
checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"clap_lex",
|
"clap_lex",
|
||||||
"strsim",
|
"strsim",
|
||||||
|
|
@ -630,13 +633,11 @@ dependencies = [
|
||||||
"prometheus 0.14.0",
|
"prometheus 0.14.0",
|
||||||
"prometheus_exporter",
|
"prometheus_exporter",
|
||||||
"rand",
|
"rand",
|
||||||
"regex",
|
|
||||||
"rlua",
|
"rlua",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1639,15 +1640,6 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_spanned"
|
|
||||||
version = "0.6.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_urlencoded"
|
name = "serde_urlencoded"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
|
|
@ -1904,47 +1896,6 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "toml"
|
|
||||||
version = "0.8.22"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
"serde_spanned",
|
|
||||||
"toml_datetime",
|
|
||||||
"toml_edit",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "toml_datetime"
|
|
||||||
version = "0.6.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "toml_edit"
|
|
||||||
version = "0.22.26"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e"
|
|
||||||
dependencies = [
|
|
||||||
"indexmap",
|
|
||||||
"serde",
|
|
||||||
"serde_spanned",
|
|
||||||
"toml_datetime",
|
|
||||||
"toml_write",
|
|
||||||
"winnow",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "toml_write"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing"
|
name = "tracing"
|
||||||
version = "0.1.41"
|
version = "0.1.41"
|
||||||
|
|
@ -2256,15 +2207,6 @@ version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winnow"
|
|
||||||
version = "0.7.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9e27d6ad3dac991091e4d35de9ba2d2d00647c5d0fc26c5496dee55984ae111b"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winsafe"
|
name = "winsafe"
|
||||||
version = "0.0.19"
|
version = "0.0.19"
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@ version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = { version = "4.3.1" }
|
actix-web = "4.3.1"
|
||||||
chrono = { version = "0.4.41", default-features = false, features = ["std", "clock"] }
|
clap = { version = "4.3", features = ["derive"] }
|
||||||
clap = { version = "4.5", default-features = false, features = ["std", "derive", "help", "usage", "suggestions"] }
|
chrono = "0.4.24"
|
||||||
futures = "0.3.28"
|
futures = "0.3.28"
|
||||||
ipnetwork = "0.21.1"
|
ipnetwork = "0.21.1"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
|
|
@ -20,5 +20,3 @@ tokio = { version = "1.28.0", features = ["full"] }
|
||||||
log = "0.4.27"
|
log = "0.4.27"
|
||||||
env_logger = "0.11.8"
|
env_logger = "0.11.8"
|
||||||
tempfile = "3.19.1"
|
tempfile = "3.19.1"
|
||||||
regex = "1.11.1"
|
|
||||||
toml = "0.8.22"
|
|
||||||
|
|
|
||||||
|
|
@ -472,7 +472,7 @@ impl ScriptManager {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate a deceptive response, calling all `response_gen` handlers
|
/// Generate a deceptive response, calling all response_gen handlers
|
||||||
pub fn generate_response(
|
pub fn generate_response(
|
||||||
&self,
|
&self,
|
||||||
path: &str,
|
path: &str,
|
||||||
|
|
|
||||||
249
src/main.rs
249
src/main.rs
|
|
@ -1,7 +1,6 @@
|
||||||
use actix_web::{App, HttpResponse, HttpServer, web};
|
use actix_web::{App, HttpResponse, HttpServer, web};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use ipnetwork::IpNetwork;
|
use ipnetwork::IpNetwork;
|
||||||
use regex::Regex;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
@ -97,7 +96,7 @@ struct Args {
|
||||||
|
|
||||||
#[clap(
|
#[clap(
|
||||||
long,
|
long,
|
||||||
help = "Path to configuration file (JSON or TOML, overrides command line options)"
|
help = "Path to JSON configuration file (overrides command line options)"
|
||||||
)]
|
)]
|
||||||
config_file: Option<PathBuf>,
|
config_file: Option<PathBuf>,
|
||||||
|
|
||||||
|
|
@ -109,44 +108,6 @@ struct Args {
|
||||||
log_level: String,
|
log_level: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trap pattern structure that can be either a plain string or regex
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
|
||||||
#[serde(untagged)]
|
|
||||||
enum TrapPattern {
|
|
||||||
Plain(String),
|
|
||||||
Regex { pattern: String, regex: bool },
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TrapPattern {
|
|
||||||
fn as_plain(value: &str) -> Self {
|
|
||||||
Self::Plain(value.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_regex(value: &str) -> Self {
|
|
||||||
Self::Regex {
|
|
||||||
pattern: value.to_string(),
|
|
||||||
regex: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn matches(&self, path: &str) -> bool {
|
|
||||||
match self {
|
|
||||||
Self::Plain(pattern) => path.contains(pattern),
|
|
||||||
Self::Regex {
|
|
||||||
pattern,
|
|
||||||
regex: true,
|
|
||||||
} => {
|
|
||||||
if let Ok(re) = Regex::new(pattern) {
|
|
||||||
re.is_match(path)
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configuration structure
|
// Configuration structure
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
struct Config {
|
struct Config {
|
||||||
|
|
@ -158,7 +119,7 @@ struct Config {
|
||||||
max_delay: u64,
|
max_delay: u64,
|
||||||
max_tarpit_time: u64,
|
max_tarpit_time: u64,
|
||||||
block_threshold: u32,
|
block_threshold: u32,
|
||||||
trap_patterns: Vec<TrapPattern>,
|
trap_patterns: Vec<String>,
|
||||||
whitelist_networks: Vec<String>,
|
whitelist_networks: Vec<String>,
|
||||||
markov_corpora_dir: String,
|
markov_corpora_dir: String,
|
||||||
lua_scripts_dir: String,
|
lua_scripts_dir: String,
|
||||||
|
|
@ -179,37 +140,17 @@ impl Default for Config {
|
||||||
max_tarpit_time: 600,
|
max_tarpit_time: 600,
|
||||||
block_threshold: 3,
|
block_threshold: 3,
|
||||||
trap_patterns: vec![
|
trap_patterns: vec![
|
||||||
// Basic attack patterns as plain strings
|
"/vendor/phpunit".to_string(),
|
||||||
TrapPattern::as_plain("/vendor/phpunit"),
|
"eval-stdin.php".to_string(),
|
||||||
TrapPattern::as_plain("eval-stdin.php"),
|
"/wp-admin".to_string(),
|
||||||
TrapPattern::as_plain("/wp-admin"),
|
"/wp-login.php".to_string(),
|
||||||
TrapPattern::as_plain("/wp-login.php"),
|
"/xmlrpc.php".to_string(),
|
||||||
TrapPattern::as_plain("/xmlrpc.php"),
|
"/phpMyAdmin".to_string(),
|
||||||
TrapPattern::as_plain("/phpMyAdmin"),
|
"/solr/".to_string(),
|
||||||
TrapPattern::as_plain("/solr/"),
|
"/.env".to_string(),
|
||||||
TrapPattern::as_plain("/.env"),
|
"/config".to_string(),
|
||||||
TrapPattern::as_plain("/config"),
|
"/api/".to_string(),
|
||||||
TrapPattern::as_plain("/actuator/"),
|
"/actuator/".to_string(),
|
||||||
// More aggressive patterns for various PHP exploits
|
|
||||||
TrapPattern::as_regex(r"/.*phpunit.*eval-stdin\.php"),
|
|
||||||
TrapPattern::as_regex(r"/index\.php\?s=/index/\\think\\app/invokefunction"),
|
|
||||||
TrapPattern::as_regex(r".*%ADd\+auto_prepend_file%3dphp://input.*"),
|
|
||||||
TrapPattern::as_regex(r".*%ADd\+allow_url_include%3d1.*"),
|
|
||||||
TrapPattern::as_regex(r".*/wp-content/plugins/.*\.php"),
|
|
||||||
TrapPattern::as_regex(r".*/wp-content/themes/.*\.php"),
|
|
||||||
TrapPattern::as_regex(r".*eval\(.*\).*"),
|
|
||||||
TrapPattern::as_regex(r".*/adminer\.php.*"),
|
|
||||||
TrapPattern::as_regex(r".*/admin\.php.*"),
|
|
||||||
TrapPattern::as_regex(r".*/administrator/.*"),
|
|
||||||
TrapPattern::as_regex(r".*/wp-json/.*"),
|
|
||||||
TrapPattern::as_regex(r".*/api/.*\.php.*"),
|
|
||||||
TrapPattern::as_regex(r".*/cgi-bin/.*"),
|
|
||||||
TrapPattern::as_regex(r".*/owa/.*"),
|
|
||||||
TrapPattern::as_regex(r".*/ecp/.*"),
|
|
||||||
TrapPattern::as_regex(r".*/webshell\.php.*"),
|
|
||||||
TrapPattern::as_regex(r".*/shell\.php.*"),
|
|
||||||
TrapPattern::as_regex(r".*/cmd\.php.*"),
|
|
||||||
TrapPattern::as_regex(r".*/struts.*"),
|
|
||||||
],
|
],
|
||||||
whitelist_networks: vec![
|
whitelist_networks: vec![
|
||||||
"192.168.0.0/16".to_string(),
|
"192.168.0.0/16".to_string(),
|
||||||
|
|
@ -293,65 +234,19 @@ impl Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load configuration from a file (JSON or TOML)
|
// Load configuration from a JSON file
|
||||||
fn load_from_file(path: &Path) -> std::io::Result<Self> {
|
fn load_from_file(path: &Path) -> std::io::Result<Self> {
|
||||||
let content = fs::read_to_string(path)?;
|
let content = fs::read_to_string(path)?;
|
||||||
|
let config = serde_json::from_str(&content)?;
|
||||||
let extension = path
|
|
||||||
.extension()
|
|
||||||
.map(|ext| ext.to_string_lossy().to_lowercase())
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let config = match extension.as_str() {
|
|
||||||
"toml" => toml::from_str(&content).map_err(|e| {
|
|
||||||
std::io::Error::new(
|
|
||||||
std::io::ErrorKind::InvalidData,
|
|
||||||
format!("Failed to parse TOML: {e}"),
|
|
||||||
)
|
|
||||||
})?,
|
|
||||||
_ => {
|
|
||||||
// Default to JSON for any other extension
|
|
||||||
serde_json::from_str(&content).map_err(|e| {
|
|
||||||
std::io::Error::new(
|
|
||||||
std::io::ErrorKind::InvalidData,
|
|
||||||
format!("Failed to parse JSON: {e}"),
|
|
||||||
)
|
|
||||||
})?
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save configuration to a file (JSON or TOML)
|
// Save configuration to a JSON file
|
||||||
fn save_to_file(&self, path: &Path) -> std::io::Result<()> {
|
fn save_to_file(&self, path: &Path) -> std::io::Result<()> {
|
||||||
if let Some(parent) = path.parent() {
|
if let Some(parent) = path.parent() {
|
||||||
fs::create_dir_all(parent)?;
|
fs::create_dir_all(parent)?;
|
||||||
}
|
}
|
||||||
|
let content = serde_json::to_string_pretty(self)?;
|
||||||
let extension = path
|
|
||||||
.extension()
|
|
||||||
.map(|ext| ext.to_string_lossy().to_lowercase())
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let content = match extension.as_str() {
|
|
||||||
"toml" => toml::to_string_pretty(self).map_err(|e| {
|
|
||||||
std::io::Error::new(
|
|
||||||
std::io::ErrorKind::InvalidData,
|
|
||||||
format!("Failed to serialize to TOML: {e}"),
|
|
||||||
)
|
|
||||||
})?,
|
|
||||||
_ => {
|
|
||||||
// Default to JSON for any other extension
|
|
||||||
serde_json::to_string_pretty(self).map_err(|e| {
|
|
||||||
std::io::Error::new(
|
|
||||||
std::io::ErrorKind::InvalidData,
|
|
||||||
format!("Failed to serialize to JSON: {e}"),
|
|
||||||
)
|
|
||||||
})?
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fs::write(path, content)?;
|
fs::write(path, content)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -493,20 +388,16 @@ fn find_header_end(data: &[u8]) -> Option<usize> {
|
||||||
|
|
||||||
// Extract path from raw request data
|
// Extract path from raw request data
|
||||||
fn extract_path_from_request(data: &[u8]) -> Option<&str> {
|
fn extract_path_from_request(data: &[u8]) -> Option<&str> {
|
||||||
// Get first line from request
|
let request_line = data
|
||||||
let first_line = data
|
|
||||||
.split(|&b| b == b'\r' || b == b'\n')
|
.split(|&b| b == b'\r' || b == b'\n')
|
||||||
.next()
|
.next()
|
||||||
.filter(|line| !line.is_empty())?;
|
.filter(|line| !line.is_empty())?;
|
||||||
|
|
||||||
// Split by spaces and ensure we have at least 3 parts (METHOD PATH VERSION)
|
let mut parts = request_line.split(|&b| b == b' ');
|
||||||
let parts: Vec<&[u8]> = first_line.split(|&b| b == b' ').collect();
|
let _ = parts.next()?; // Skip HTTP method
|
||||||
if parts.len() < 3 || !parts[2].starts_with(b"HTTP/") {
|
let path = parts.next()?;
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the path (second element)
|
std::str::from_utf8(path).ok()
|
||||||
std::str::from_utf8(parts[1]).ok()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract header value from raw request data
|
// Extract header value from raw request data
|
||||||
|
|
@ -991,9 +882,10 @@ async fn should_tarpit(path: &str, ip: &IpAddr, config: &Config) -> bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use pattern matching based on the trap pattern type (plain string or regex)
|
// Use a more efficient pattern matching approach
|
||||||
|
let path_lower = path.to_lowercase();
|
||||||
for pattern in &config.trap_patterns {
|
for pattern in &config.trap_patterns {
|
||||||
if pattern.matches(path) {
|
if path_lower.contains(pattern) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1196,23 +1088,12 @@ async fn main() -> std::io::Result<()> {
|
||||||
if let Err(e) = fs::create_dir_all(&config.config_dir) {
|
if let Err(e) = fs::create_dir_all(&config.config_dir) {
|
||||||
log::warn!("Failed to create config directory: {e}");
|
log::warn!("Failed to create config directory: {e}");
|
||||||
} else {
|
} else {
|
||||||
// Save both JSON and TOML versions of the config for user reference
|
let config_path = Path::new(&config.config_dir).join("config.json");
|
||||||
let config_path_json = Path::new(&config.config_dir).join("config.json");
|
if !config_path.exists() {
|
||||||
let config_path_toml = Path::new(&config.config_dir).join("config.toml");
|
if let Err(e) = config.save_to_file(&config_path) {
|
||||||
|
log::warn!("Failed to save default configuration: {e}");
|
||||||
if !config_path_json.exists() {
|
|
||||||
if let Err(e) = config.save_to_file(&config_path_json) {
|
|
||||||
log::warn!("Failed to save JSON configuration: {e}");
|
|
||||||
} else {
|
} else {
|
||||||
log::info!("Saved JSON configuration to {config_path_json:?}");
|
log::info!("Saved default configuration to {config_path:?}");
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !config_path_toml.exists() {
|
|
||||||
if let Err(e) = config.save_to_file(&config_path_toml) {
|
|
||||||
log::warn!("Failed to save TOML configuration: {e}");
|
|
||||||
} else {
|
|
||||||
log::info!("Saved TOML configuration to {config_path_toml:?}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1433,27 +1314,6 @@ mod tests {
|
||||||
assert_eq!(config.cache_dir, "/tmp/eris/cache");
|
assert_eq!(config.cache_dir, "/tmp/eris/cache");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_trap_pattern_matching() {
|
|
||||||
// Test plain string pattern
|
|
||||||
let plain = TrapPattern::as_plain("phpunit");
|
|
||||||
assert!(plain.matches("path/to/phpunit/test"));
|
|
||||||
assert!(!plain.matches("path/to/something/else"));
|
|
||||||
|
|
||||||
// Test regex pattern
|
|
||||||
let regex = TrapPattern::as_regex(r".*eval-stdin\.php.*");
|
|
||||||
assert!(regex.matches("/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php"));
|
|
||||||
assert!(regex.matches("/tests/eval-stdin.php?param"));
|
|
||||||
assert!(!regex.matches("/normal/path"));
|
|
||||||
|
|
||||||
// Test invalid regex pattern (should return false)
|
|
||||||
let invalid = TrapPattern::Regex {
|
|
||||||
pattern: "(invalid[regex".to_string(),
|
|
||||||
regex: true,
|
|
||||||
};
|
|
||||||
assert!(!invalid.matches("anything"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_should_tarpit() {
|
async fn test_should_tarpit() {
|
||||||
let config = Config::default();
|
let config = Config::default();
|
||||||
|
|
@ -1512,25 +1372,6 @@ mod tests {
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
);
|
);
|
||||||
|
|
||||||
// Test regex patterns
|
|
||||||
assert!(
|
|
||||||
should_tarpit(
|
|
||||||
"/index.php?s=/index/\\think\\app/invokefunction&function=call_user_func_array&vars[0]=md5&vars[1][]=Hello",
|
|
||||||
&IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)),
|
|
||||||
&config
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
should_tarpit(
|
|
||||||
"/hello.world?%ADd+allow_url_include%3d1+%ADd+auto_prepend_file%3dphp://input",
|
|
||||||
&IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)),
|
|
||||||
&config
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|
@ -1558,7 +1399,6 @@ mod tests {
|
||||||
state.blocked.insert(ip1);
|
state.blocked.insert(ip1);
|
||||||
assert!(state.blocked.contains(&ip1));
|
assert!(state.blocked.contains(&ip1));
|
||||||
assert!(!state.blocked.contains(&ip2));
|
assert!(!state.blocked.contains(&ip2));
|
||||||
drop(state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test active connections
|
// Test active connections
|
||||||
|
|
@ -1572,14 +1412,13 @@ mod tests {
|
||||||
assert_eq!(state.active_connections.len(), 1);
|
assert_eq!(state.active_connections.len(), 1);
|
||||||
assert!(!state.active_connections.contains(&ip1));
|
assert!(!state.active_connections.contains(&ip1));
|
||||||
assert!(state.active_connections.contains(&ip2));
|
assert!(state.active_connections.contains(&ip2));
|
||||||
drop(state);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_find_header_end() {
|
fn test_find_header_end() {
|
||||||
let data = b"GET / HTTP/1.1\r\nHost: example.com\r\nUser-Agent: test\r\n\r\nBody content";
|
let data = b"GET / HTTP/1.1\r\nHost: example.com\r\nUser-Agent: test\r\n\r\nBody content";
|
||||||
assert_eq!(find_header_end(data), Some(55));
|
assert_eq!(find_header_end(data), Some(53));
|
||||||
|
|
||||||
let incomplete = b"GET / HTTP/1.1\r\nHost: example.com\r\n";
|
let incomplete = b"GET / HTTP/1.1\r\nHost: example.com\r\n";
|
||||||
assert_eq!(find_header_end(incomplete), None);
|
assert_eq!(find_header_end(incomplete), None);
|
||||||
|
|
@ -1634,30 +1473,4 @@ mod tests {
|
||||||
assert_eq!(choose_response_type("/api/v1/users"), "api");
|
assert_eq!(choose_response_type("/api/v1/users"), "api");
|
||||||
assert_eq!(choose_response_type("/index.html"), "generic");
|
assert_eq!(choose_response_type("/index.html"), "generic");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_config_file_formats() {
|
|
||||||
// Create temporary JSON config file
|
|
||||||
let temp_dir = std::env::temp_dir();
|
|
||||||
let json_path = temp_dir.join("temp_config.json");
|
|
||||||
let toml_path = temp_dir.join("temp_config.toml");
|
|
||||||
|
|
||||||
let config = Config::default();
|
|
||||||
|
|
||||||
// Test JSON serialization and deserialization
|
|
||||||
config.save_to_file(&json_path).unwrap();
|
|
||||||
let loaded_json = Config::load_from_file(&json_path).unwrap();
|
|
||||||
assert_eq!(loaded_json.listen_addr, config.listen_addr);
|
|
||||||
assert_eq!(loaded_json.min_delay, config.min_delay);
|
|
||||||
|
|
||||||
// Test TOML serialization and deserialization
|
|
||||||
config.save_to_file(&toml_path).unwrap();
|
|
||||||
let loaded_toml = Config::load_from_file(&toml_path).unwrap();
|
|
||||||
assert_eq!(loaded_toml.listen_addr, config.listen_addr);
|
|
||||||
assert_eq!(loaded_toml.min_delay, config.min_delay);
|
|
||||||
|
|
||||||
// Clean up
|
|
||||||
let _ = std::fs::remove_file(json_path);
|
|
||||||
let _ = std::fs::remove_file(toml_path);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue