impl ShellSafetyAnalyzer {
#[must_use]
pub fn new() -> Self {
Self {
safety_violations: Vec::new(),
best_practice_warnings: Vec::new(),
}
}
pub fn analyze_safety(&mut self, source: &str) -> Result<Vec<String>, String> {
let mut violations = Vec::new();
for line in source.lines() {
let trimmed = line.trim();
if trimmed.contains("rm -rf $") {
violations.push("Dangerous rm -rf with variable".to_string());
}
if trimmed.contains("eval \"$") {
violations.push("Dangerous eval with user input".to_string());
}
if trimmed.contains("$@") && !trimmed.contains("\"$@\"") {
violations.push("Unquoted $@ parameter expansion".to_string());
}
}
self.safety_violations = violations.clone();
Ok(violations)
}
pub fn check_security_vulnerabilities(&mut self, source: &str) -> Result<Vec<String>, String> {
let mut vulnerabilities = Vec::new();
for line in source.lines() {
let trimmed = line.trim();
if trimmed.contains("curl") && !trimmed.contains("--fail") {
vulnerabilities.push("curl without --fail may ignore errors".to_string());
}
if trimmed.contains("wget") && !trimmed.contains("-O") {
vulnerabilities.push("wget without explicit output may overwrite".to_string());
}
}
Ok(vulnerabilities)
}
pub fn validate_best_practices(&mut self, source: &str) -> Result<Vec<String>, String> {
let mut warnings = Vec::new();
let has_shebang = source.lines().next().unwrap_or("").starts_with("#!");
if !has_shebang {
warnings.push("Missing shebang line".to_string());
}
let has_set_flags = source.contains("set -e") || source.contains("set -u");
if !has_set_flags {
warnings.push("Consider using 'set -e' or 'set -u' for error handling".to_string());
}
self.best_practice_warnings = warnings.clone();
Ok(warnings)
}
#[must_use]
pub fn get_safety_violations(&self) -> &[String] {
&self.safety_violations
}
#[must_use]
pub fn get_best_practice_warnings(&self) -> &[String] {
&self.best_practice_warnings
}
}
impl ShellCommandParser {
#[must_use]
pub fn new() -> Self {
Self {
commands: Vec::new(),
variables: Vec::new(),
}
}
pub fn parse_command_line(&mut self, line: &str) -> Result<Vec<String>, String> {
let tokens: Vec<String> = line
.split_whitespace()
.map(std::string::ToString::to_string)
.collect();
self.commands.extend(tokens.clone());
Ok(tokens)
}
pub fn extract_variable_assignments(
&mut self,
line: &str,
) -> Result<Vec<(String, String)>, String> {
let mut assignments = Vec::new();
if line.contains('=') && !line.trim().starts_with('#') {
let parts: Vec<&str> = line.split('=').collect();
if parts.len() >= 2 {
let mut var_part = parts[0].trim();
if var_part.starts_with("export ") {
var_part = &var_part[7..]; }
let var_name = var_part.trim().to_string();
let var_value = parts[1].trim().to_string();
assignments.push((var_name.clone(), var_value));
self.variables.push(var_name);
}
}
Ok(assignments)
}
#[must_use]
pub fn get_commands(&self) -> &[String] {
&self.commands
}
#[must_use]
pub fn get_variables(&self) -> &[String] {
&self.variables
}
}