pub(crate) fn scan_backward_for_file_write(bytes: &[u8], here_pos: usize) -> bool {
let mut pos = here_pos;
skip_ws_backward(bytes, &mut pos);
if pos == 0 {
return false;
}
let file_token = paren_aware_token(bytes, &mut pos);
if file_token.is_empty() {
return false;
}
skip_ws_backward(bytes, &mut pos);
if pos == 0 {
return false;
}
if pos >= 2 && bytes[pos - 1] == b'>' && bytes[pos - 2] == b'>' {
pos -= 2;
skip_ws_backward(bytes, &mut pos);
if pos == 0 {
return true;
}
if scan_args_for_write_command(bytes, &mut pos) {
return true;
}
}
if pos > 0 && bytes[pos - 1] == b'>' {
pos -= 1;
skip_ws_backward(bytes, &mut pos);
if pos == 0 {
return true;
}
if scan_args_for_write_command(bytes, &mut pos) {
return true;
}
}
let cmd = token_before_pos(bytes, &mut pos);
if is_file_write_command(cmd) {
return true;
}
if cmd.len() > 1 && cmd[0] == b'-' {
skip_ws_backward(bytes, &mut pos);
if pos == 0 {
return false;
}
let prev_cmd = token_before_pos(bytes, &mut pos);
return is_file_write_command(prev_cmd);
}
false
}
fn token_before_pos<'a>(bytes: &'a [u8], pos: &mut usize) -> &'a [u8] {
while *pos > 0 && (bytes[*pos - 1] as char).is_ascii_whitespace() {
*pos -= 1;
}
if *pos == 0 {
return b"";
}
paren_aware_token(bytes, pos)
}
fn paren_aware_token<'a>(bytes: &'a [u8], pos: &mut usize) -> &'a [u8] {
let end = *pos;
if bytes[*pos - 1] == b')' {
let mut depth = 1;
*pos -= 1;
while *pos > 0 && depth > 0 {
*pos -= 1;
if bytes[*pos] == b'(' {
depth -= 1;
} else if bytes[*pos] == b')' {
depth += 1;
}
}
return &bytes[*pos..end];
}
if bytes[*pos - 1] == b'>' {
*pos -= 1;
if *pos > 0 && bytes[*pos - 1] == b'>' {
*pos -= 1; }
return &bytes[*pos..end];
}
while *pos > 0 && !(bytes[*pos - 1] as char).is_ascii_whitespace() {
*pos -= 1;
}
&bytes[*pos..end]
}
fn skip_ws_backward(bytes: &[u8], pos: &mut usize) {
while *pos > 0 && (bytes[*pos - 1] as char).is_ascii_whitespace() {
*pos -= 1;
}
}
fn is_file_write_command(cmd: &[u8]) -> bool {
cmd == b"cat" || cmd == b"tee" || cmd == b"printf" || cmd == b"dd" || cmd.first() == Some(&b'$')
}
fn scan_args_for_write_command(bytes: &[u8], pos: &mut usize) -> bool {
loop {
let tok = token_before_pos(bytes, pos);
if tok.is_empty() {
return false;
}
if is_file_write_command(tok) {
return true;
}
skip_ws_backward(bytes, pos);
if *pos == 0 {
return false;
}
let next = bytes[*pos - 1];
if next == b'|' || next == b';' || next == b'&' || next == b'(' {
return false;
}
}
}
fn prev_token(bytes: &[u8], end: usize) -> &[u8] {
let mut pos = end;
while pos > 0 && (bytes[pos - 1] as char).is_ascii_whitespace() {
pos -= 1;
}
if pos == 0 {
return b"";
}
let end_pos = pos;
while pos > 0 && !(bytes[pos - 1] as char).is_ascii_whitespace() {
pos -= 1;
}
&bytes[pos..end_pos]
}
fn is_stdin_consuming_flag(tok: &[u8]) -> bool {
tok == b"--data"
|| tok == b"--data-raw"
|| tok == b"--data-binary"
|| tok == b"--data-urlencode"
|| tok == b"--body-file"
|| tok == b"-d"
|| tok == b"-F"
|| tok == b"--stdin"
}
pub(crate) fn scan_backward_for_stdin_flag(bytes: &[u8], here_pos: usize) -> bool {
let mut pos = here_pos;
while pos > 0 && (bytes[pos - 1] as char).is_ascii_whitespace() {
pos -= 1;
}
if pos == 0 {
return false;
}
let tok = prev_token(bytes, pos);
pos -= tok.len();
if tok == b"-" {
while pos > 0 && (bytes[pos - 1] as char).is_ascii_whitespace() {
pos -= 1;
}
if pos == 0 {
return false;
}
let flag = prev_token(bytes, pos);
if is_stdin_consuming_flag(flag) {
return true;
}
if flag == b"cat" {
return true;
}
return false;
}
if is_stdin_consuming_flag(tok) {
return true;
}
false
}
#[cfg(test)]
mod tests {
use super::scan_backward_for_file_write;
#[test]
fn scan_backward_empty_input_not_file_write() {
assert!(!scan_backward_for_file_write(b"", 0));
}
#[test]
fn scan_backward_only_whitespace_before_heredoc_not_file_write() {
let cmd = b" <<";
assert!(!scan_backward_for_file_write(cmd, 3));
}
#[test]
fn scan_backward_leading_whitespace_file_token_then_redirect() {
let cmd = b" cat > file <<";
assert!(scan_backward_for_file_write(cmd, 13));
}
#[test]
fn scan_backward_file_token_only_no_redirect_no_tee_not_file_write() {
let cmd = b"somecmd file <<";
assert!(!scan_backward_for_file_write(cmd, 13));
}
}