use std::collections::HashMap;
pub fn parse_form_data(data: &str) -> HashMap<String, String> {
let mut params = HashMap::new();
for pair in data.split('&') {
if let Some((key, value)) = pair.split_once('=') {
let decoded_key = urlencoding::decode(key).unwrap_or_else(|_| key.into());
let decoded_value = urlencoding::decode(value).unwrap_or_else(|_| value.into());
params.insert(decoded_key.to_string(), decoded_value.to_string());
}
}
params
}
pub fn encode_form_data(params: &HashMap<String, String>) -> String {
params
.iter()
.map(|(key, value)| {
format!(
"{}={}",
urlencoding::encode(key),
urlencoding::encode(value)
)
})
.collect::<Vec<_>>()
.join("&")
}
pub fn find_credential_fields(params: &HashMap<String, String>) -> Vec<(String, String)> {
let mut credentials = Vec::new();
let credential_patterns = [
"username",
"user",
"login",
"email",
"username_or_email",
"user_name",
"password",
"pass",
"passwd",
"pwd",
"secret",
"_token",
"session",
"sessionid",
"sid",
"auth",
"authorization",
"api_key",
"apikey",
"key",
"client_secret",
"access_token",
"refresh_token",
];
for (key, value) in params {
let key_lower = key.to_lowercase();
for pattern in &credential_patterns {
if key_lower.contains(pattern) {
credentials.push((key.clone(), value.clone()));
break;
}
}
if value.len() > 10 && value.chars().all(|c| c.is_alphanumeric()) {
credentials.push((key.clone(), value.clone()));
}
}
credentials
}
pub fn filter_form_data(data: &str, replacement_pattern: &str) -> String {
let mut params = parse_form_data(data);
let credentials = find_credential_fields(¶ms);
for (key, _) in credentials {
if let Some(value) = params.get_mut(&key) {
*value = format!("{replacement_pattern}_{}", key.to_uppercase());
}
}
encode_form_data(¶ms)
}
pub fn analyze_form_data(data: &str) -> FormDataAnalysis {
let params = parse_form_data(data);
let credentials = find_credential_fields(¶ms);
FormDataAnalysis {
total_fields: params.len(),
credential_fields: credentials,
all_fields: params,
}
}
#[derive(Debug)]
pub struct FormDataAnalysis {
pub total_fields: usize,
pub credential_fields: Vec<(String, String)>,
pub all_fields: HashMap<String, String>,
}
impl FormDataAnalysis {
pub fn print_summary(&self) {
println!("Form Data Analysis:");
println!(" Total fields: {}", self.total_fields);
println!(
" Credential fields found: {}",
self.credential_fields.len()
);
if !self.credential_fields.is_empty() {
println!(" Sensitive fields:");
for (key, value) in &self.credential_fields {
let preview = if value.len() > 20 {
format!("{}...", &value[..20])
} else {
value.clone()
};
println!(" {key}: {preview}");
}
}
println!(" All fields:");
for (key, value) in &self.all_fields {
let preview = if value.len() > 50 {
format!("{}...", &value[..50])
} else {
value.clone()
};
println!(" {key}: {preview}");
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_form_data() {
let data = "username=testuser&password=secret123&csrf_token=abc123";
let params = parse_form_data(data);
assert_eq!(params.get("username"), Some(&"testuser".to_string()));
assert_eq!(params.get("password"), Some(&"secret123".to_string()));
assert_eq!(params.get("csrf_token"), Some(&"abc123".to_string()));
}
#[test]
fn test_find_credential_fields() {
let mut params = HashMap::new();
params.insert("username".to_string(), "testuser".to_string());
params.insert("password".to_string(), "secret123".to_string());
params.insert("normal_field".to_string(), "value".to_string());
let credentials = find_credential_fields(¶ms);
assert_eq!(credentials.len(), 2);
let keys: Vec<&String> = credentials.iter().map(|(k, _)| k).collect();
assert!(keys.contains(&&"username".to_string()));
assert!(keys.contains(&&"password".to_string()));
}
#[test]
fn test_filter_form_data() {
let data = "username=testuser&password=secret123&normal=value";
let filtered = filter_form_data(data, "[FILTERED]");
assert!(filtered.contains("%5BFILTERED%5D_USERNAME"));
assert!(filtered.contains("%5BFILTERED%5D_PASSWORD"));
assert!(filtered.contains("normal=value"));
}
}