use std::ffi::OsStr;
use crate::{
FieldSanitizer,
NameMatchMode,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ArgvSanitizer {
field_sanitizer: FieldSanitizer,
}
impl ArgvSanitizer {
pub const fn new(field_sanitizer: FieldSanitizer) -> Self {
Self { field_sanitizer }
}
pub const fn field_sanitizer(&self) -> &FieldSanitizer {
&self.field_sanitizer
}
pub fn field_sanitizer_mut(&mut self) -> &mut FieldSanitizer {
&mut self.field_sanitizer
}
pub fn sanitize_argv<I, S>(&self, argv: I, match_mode: NameMatchMode) -> Vec<String>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
let mut sanitized = Vec::new();
let mut pending_sensitive_name: Option<String> = None;
let mut parse_options = true;
for arg in argv {
let arg = arg.as_ref().to_string_lossy().into_owned();
if let Some(name) = pending_sensitive_name.take() {
sanitized.push(self.sanitize_sensitive_value(&name, &arg, match_mode));
continue;
}
if arg == "--" {
parse_options = false;
sanitized.push(arg);
continue;
}
if let Some(value) = self.sanitize_assignment_arg(&arg, match_mode) {
sanitized.push(value);
continue;
}
if parse_options {
if let Some(value) = self.sanitize_inline_option_arg(&arg, match_mode) {
sanitized.push(value);
continue;
}
if let Some(name) = option_name(&arg).filter(|name| {
self.field_sanitizer
.sensitivity_for_name(name, match_mode)
.is_some()
}) {
pending_sensitive_name = Some(name.to_string());
}
}
sanitized.push(arg);
}
sanitized
}
pub fn sanitize_argv_display<I, S>(&self, argv: I, match_mode: NameMatchMode) -> String
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
format!("{:?}", self.sanitize_argv(argv, match_mode))
}
fn sanitize_assignment_arg(&self, arg: &str, match_mode: NameMatchMode) -> Option<String> {
let (key, value) = arg.split_once('=')?;
if key.is_empty() {
return None;
}
let sanitized_value = self.field_sanitizer.sanitize_value(key, value, match_mode);
if matches!(sanitized_value, std::borrow::Cow::Borrowed(_)) {
return None;
}
Some(format!("{key}={sanitized_value}"))
}
fn sanitize_sensitive_value(
&self,
name: &str,
value: &str,
match_mode: NameMatchMode,
) -> String {
self.field_sanitizer
.sanitize_value(name, value, match_mode)
.into_owned()
}
fn sanitize_inline_option_arg(&self, arg: &str, match_mode: NameMatchMode) -> Option<String> {
if !arg.starts_with('-') || arg == "-" {
return None;
}
let (left, value) = arg.split_once('=')?;
let name = option_name(left)?;
self.field_sanitizer
.sensitivity_for_name(name, match_mode)?;
let sanitized_value = self.sanitize_sensitive_value(name, value, match_mode);
Some(format!("{left}={sanitized_value}"))
}
}
impl Default for ArgvSanitizer {
fn default() -> Self {
Self::new(FieldSanitizer::default())
}
}
fn option_name(arg: &str) -> Option<&str> {
if !arg.starts_with('-') || arg == "-" {
return None;
}
let name = arg.trim_start_matches('-');
if name.is_empty() { None } else { Some(name) }
}