pub(crate) const MASK_CHAR: char = '\u{1}';
pub(crate) const MASK_BYTE: u8 = 0x01;
pub(crate) fn shell_mask(command: &str) -> String {
let bytes = command.as_bytes();
let mut mask: Vec<u8> = Vec::with_capacity(command.len());
let mut quote: Option<u8> = None; let mut procsub_depth: usize = 0; let mut arith_depth: usize = 0; let mut test_depth: usize = 0; let mut i = 0usize;
while i < bytes.len() {
let c = bytes[i];
if let Some(q) = quote {
if c == q {
quote = None;
mask.push(c); } else {
mask.push(MASK_BYTE);
}
i += 1;
continue;
}
if procsub_depth > 0 {
match c {
b'(' => procsub_depth += 1,
b')' => procsub_depth -= 1,
_ => {}
}
mask.push(MASK_BYTE);
i += 1;
continue;
}
if arith_depth > 0 {
match c {
b'(' => arith_depth += 1,
b')' => arith_depth -= 1,
_ => {}
}
mask.push(MASK_BYTE);
i += 1;
continue;
}
if test_depth > 0 {
match c {
b'[' => test_depth += 1,
b']' => test_depth -= 1,
_ => {}
}
mask.push(MASK_BYTE);
i += 1;
continue;
}
if c == b'#'
&& (i == 0
|| matches!(
bytes[i - 1],
b' ' | b'\t' | b'\n' | b'\r' | b';' | b'|' | b'&' | b'('
))
{
while i < bytes.len() && bytes[i] != b'\n' {
mask.push(MASK_BYTE);
i += 1;
}
continue;
}
if (c == b'>' || c == b'<') && bytes.get(i + 1) == Some(&b'(') {
mask.push(c); mask.push(b'(');
procsub_depth = 1;
i += 2;
continue;
}
if c == b'(' && bytes.get(i + 1) == Some(&b'(') {
mask.push(b'(');
mask.push(b'(');
arith_depth = 1;
i += 2;
continue;
}
if c == b'[' && bytes.get(i + 1) == Some(&b'[') {
mask.push(b'[');
mask.push(b'[');
test_depth = 1;
i += 2;
continue;
}
if c == b'\'' || c == b'"' || c == b'`' {
quote = Some(c);
mask.push(c); i += 1;
continue;
}
mask.push(c); i += 1;
}
String::from_utf8(mask).unwrap_or_else(|e| String::from_utf8_lossy(e.as_bytes()).into_owned())
}
pub(crate) fn split_segments<'a>(command: &'a str, mask: &str) -> Vec<&'a str> {
let mbytes = mask.as_bytes();
let mut segments = Vec::new();
let mut start = 0usize;
let mut i = 0usize;
while i < mbytes.len() {
let two = mbytes.get(i..i + 2);
if matches!(two, Some(b">|")) {
i += 2;
continue;
}
let is_two_op = matches!(two, Some(b"&&") | Some(b"||"));
let is_one_op = matches!(mbytes[i], b';' | b'|' | b'\n');
if is_two_op {
segments.push(&command[start..i]);
i += 2;
start = i;
} else if is_one_op {
segments.push(&command[start..i]);
i += 1;
start = i;
} else {
i += 1;
}
}
segments.push(&command[start..]);
segments
}
#[derive(Clone, Copy)]
pub(crate) struct MaskedTok<'a> {
pub(crate) orig: &'a str,
pub(crate) masked: &'a str,
}
pub(crate) fn masked_tokens<'a>(segment: &'a str, mask: &'a str) -> Vec<MaskedTok<'a>> {
let mbytes = mask.as_bytes();
let mut toks = Vec::new();
let mut i = 0usize;
while i < mbytes.len() {
if mbytes[i].is_ascii_whitespace() {
i += 1;
continue;
}
let start = i;
while i < mbytes.len() && !mbytes[i].is_ascii_whitespace() {
i += 1;
}
toks.push(MaskedTok {
orig: &segment[start..i],
masked: &mask[start..i],
});
}
toks
}