forked from extern/nushell
Always escape non-literal arguments when running external command (#2697)
This commit is contained in:
parent
1b0ed30516
commit
c283db373b
@ -3,6 +3,7 @@ use crate::evaluate::evaluate_baseline_expr;
|
|||||||
use crate::futures::ThreadedReceiver;
|
use crate::futures::ThreadedReceiver;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
@ -13,6 +14,7 @@ use futures_codec::FramedRead;
|
|||||||
use log::trace;
|
use log::trace;
|
||||||
|
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::hir::Expression;
|
||||||
use nu_protocol::hir::{ExternalCommand, ExternalRedirection};
|
use nu_protocol::hir::{ExternalCommand, ExternalRedirection};
|
||||||
use nu_protocol::{Primitive, Scope, ShellTypeName, UntaggedValue, Value};
|
use nu_protocol::{Primitive, Scope, ShellTypeName, UntaggedValue, Value};
|
||||||
use nu_source::Tag;
|
use nu_source::Tag;
|
||||||
@ -50,6 +52,7 @@ async fn run_with_stdin(
|
|||||||
|
|
||||||
let mut command_args = vec![];
|
let mut command_args = vec![];
|
||||||
for arg in command.args.iter() {
|
for arg in command.args.iter() {
|
||||||
|
let is_literal = matches!(arg.expr, Expression::Literal(_));
|
||||||
let value = evaluate_baseline_expr(arg, &context.registry, scope.clone()).await?;
|
let value = evaluate_baseline_expr(arg, &context.registry, scope.clone()).await?;
|
||||||
|
|
||||||
// Skip any arguments that don't really exist, treating them as optional
|
// Skip any arguments that don't really exist, treating them as optional
|
||||||
@ -65,8 +68,10 @@ async fn run_with_stdin(
|
|||||||
for t in table {
|
for t in table {
|
||||||
match &t.value {
|
match &t.value {
|
||||||
UntaggedValue::Primitive(_) => {
|
UntaggedValue::Primitive(_) => {
|
||||||
command_args
|
command_args.push((
|
||||||
.push(t.convert_to_string().trim_end_matches('\n').to_string());
|
t.convert_to_string().trim_end_matches('\n').to_string(),
|
||||||
|
is_literal,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ShellError::labeled_error(
|
return Err(ShellError::labeled_error(
|
||||||
@ -80,14 +85,14 @@ async fn run_with_stdin(
|
|||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let trimmed_value_string = value.as_string()?.trim_end_matches('\n').to_string();
|
let trimmed_value_string = value.as_string()?.trim_end_matches('\n').to_string();
|
||||||
command_args.push(trimmed_value_string);
|
command_args.push((trimmed_value_string, is_literal));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let process_args = command_args
|
let process_args = command_args
|
||||||
.iter()
|
.iter()
|
||||||
.map(|arg| {
|
.map(|(arg, _is_literal)| {
|
||||||
let home_dir;
|
let home_dir;
|
||||||
|
|
||||||
#[cfg(feature = "dirs")]
|
#[cfg(feature = "dirs")]
|
||||||
@ -103,8 +108,9 @@ async fn run_with_stdin(
|
|||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
{
|
{
|
||||||
if argument_contains_whitespace(&arg) && !argument_is_quoted(&arg) {
|
if !_is_literal {
|
||||||
add_quotes(&arg)
|
let escaped = escape_double_quotes(&arg);
|
||||||
|
add_double_quotes(&escaped)
|
||||||
} else {
|
} else {
|
||||||
arg.as_ref().to_string()
|
arg.as_ref().to_string()
|
||||||
}
|
}
|
||||||
@ -476,11 +482,6 @@ where
|
|||||||
shellexpand::tilde_with_context(input, home_dir)
|
shellexpand::tilde_with_context(input, home_dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
pub fn argument_contains_whitespace(argument: &str) -> bool {
|
|
||||||
argument.chars().any(|c| c.is_whitespace())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn argument_is_quoted(argument: &str) -> bool {
|
fn argument_is_quoted(argument: &str) -> bool {
|
||||||
if argument.len() < 2 {
|
if argument.len() < 2 {
|
||||||
return false;
|
return false;
|
||||||
@ -491,10 +492,20 @@ fn argument_is_quoted(argument: &str) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
fn add_quotes(argument: &str) -> String {
|
fn add_double_quotes(argument: &str) -> String {
|
||||||
format!("\"{}\"", argument)
|
format!("\"{}\"", argument)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
fn escape_double_quotes(argument: &str) -> Cow<'_, str> {
|
||||||
|
// allocate new string only if required
|
||||||
|
if argument.contains('"') {
|
||||||
|
Cow::Owned(argument.replace('"', r#"\""#))
|
||||||
|
} else {
|
||||||
|
Cow::Borrowed(argument)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
fn remove_quotes(argument: &str) -> Option<&str> {
|
fn remove_quotes(argument: &str) -> Option<&str> {
|
||||||
if !argument_is_quoted(argument) {
|
if !argument_is_quoted(argument) {
|
||||||
@ -520,7 +531,7 @@ fn shell_os_paths() -> Vec<std::path::PathBuf> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{
|
use super::{
|
||||||
add_quotes, argument_contains_whitespace, argument_is_quoted, expand_tilde, remove_quotes,
|
add_double_quotes, argument_is_quoted, escape_double_quotes, expand_tilde, remove_quotes,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "which")]
|
#[cfg(feature = "which")]
|
||||||
use super::{run_external_command, EvaluationContext, InputStream};
|
use super::{run_external_command, EvaluationContext, InputStream};
|
||||||
@ -600,10 +611,10 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn checks_contains_whitespace_from_argument_to_be_passed_in() {
|
fn checks_escape_double_quotes() {
|
||||||
assert_eq!(argument_contains_whitespace("andrés"), false);
|
assert_eq!(escape_double_quotes("andrés"), "andrés");
|
||||||
assert_eq!(argument_contains_whitespace("and rés"), true);
|
assert_eq!(escape_double_quotes(r#"an"drés"#), r#"an\"drés"#);
|
||||||
assert_eq!(argument_contains_whitespace(r#"and\ rés"#), true);
|
assert_eq!(escape_double_quotes(r#""an"drés""#), r#"\"an\"drés\""#);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -631,9 +642,8 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn adds_quotes_to_argument_to_be_passed_in() {
|
fn adds_double_quotes_to_argument_to_be_passed_in() {
|
||||||
assert_eq!(add_quotes("andrés"), "\"andrés\"");
|
assert_eq!(add_double_quotes("andrés"), "\"andrés\"");
|
||||||
//assert_eq!(add_quotes("\"andrés\""), "\"andrés\"");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
Loading…
Reference in New Issue
Block a user