mirror of
https://github.com/nushell/nushell.git
synced 2025-08-09 07:05:47 +02:00
Do not pass non-string env vars to externals (#4748)
* Do not pass non-string env vars to externals Also misc cleanup * Add note to default config * Add a test * Ensure PATH/Path conversion list <-> string
This commit is contained in:
@ -3,15 +3,10 @@ use std::path::{Path, PathBuf};
|
||||
|
||||
use nu_protocol::ast::PathMember;
|
||||
use nu_protocol::engine::{EngineState, Stack};
|
||||
use nu_protocol::{Config, PipelineData, ShellError, Span, Value};
|
||||
use nu_protocol::{PipelineData, ShellError, Span, Value};
|
||||
|
||||
use crate::eval_block;
|
||||
|
||||
#[cfg(windows)]
|
||||
const ENV_SEP: &str = ";";
|
||||
#[cfg(not(windows))]
|
||||
const ENV_SEP: &str = ":";
|
||||
|
||||
#[cfg(windows)]
|
||||
const ENV_PATH_NAME: &str = "Path";
|
||||
#[cfg(windows)]
|
||||
@ -52,6 +47,23 @@ pub fn convert_env_values(engine_state: &mut EngineState, stack: &Stack) -> Opti
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
error = error.or_else(|| ensure_path(&mut new_scope, ENV_PATH_NAME));
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let first_result = ensure_path(&mut new_scope, ENV_PATH_NAME);
|
||||
if first_result.is_some() {
|
||||
let second_result = ensure_path(&mut new_scope, ENV_PATH_NAME_SECONDARY);
|
||||
|
||||
if second_result.is_some() {
|
||||
error = error.or(first_result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (k, v) in new_scope {
|
||||
engine_state.env_vars.insert(k, v);
|
||||
}
|
||||
@ -60,18 +72,51 @@ pub fn convert_env_values(engine_state: &mut EngineState, stack: &Stack) -> Opti
|
||||
}
|
||||
|
||||
/// Translate one environment variable from Value to String
|
||||
///
|
||||
/// Returns Ok(None) if the env var is not
|
||||
pub fn env_to_string(
|
||||
env_name: &str,
|
||||
value: Value,
|
||||
value: &Value,
|
||||
engine_state: &EngineState,
|
||||
stack: &Stack,
|
||||
config: &Config,
|
||||
) -> Result<String, ShellError> {
|
||||
match get_converted_value(engine_state, stack, env_name, &value, "to_string") {
|
||||
match get_converted_value(engine_state, stack, env_name, value, "to_string") {
|
||||
ConversionResult::Ok(v) => Ok(v.as_string()?),
|
||||
ConversionResult::ConversionError(e) => Err(e),
|
||||
ConversionResult::GeneralError(e) => Err(e),
|
||||
ConversionResult::CellPathError => Ok(value.into_string(ENV_SEP, config)),
|
||||
ConversionResult::CellPathError => match value.as_string() {
|
||||
Ok(s) => Ok(s),
|
||||
Err(_) => {
|
||||
if env_name == ENV_PATH_NAME {
|
||||
// Try to convert PATH/Path list to a string
|
||||
match value {
|
||||
Value::List { vals, .. } => {
|
||||
let paths = vals
|
||||
.iter()
|
||||
.map(|v| v.as_string())
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
match std::env::join_paths(paths) {
|
||||
Ok(p) => Ok(p.to_string_lossy().to_string()),
|
||||
Err(_) => Err(ShellError::EnvVarNotAString(
|
||||
env_name.to_string(),
|
||||
value.span()?,
|
||||
)),
|
||||
}
|
||||
}
|
||||
_ => Err(ShellError::EnvVarNotAString(
|
||||
env_name.to_string(),
|
||||
value.span()?,
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::EnvVarNotAString(
|
||||
env_name.to_string(),
|
||||
value.span()?,
|
||||
))
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,13 +124,17 @@ pub fn env_to_string(
|
||||
pub fn env_to_strings(
|
||||
engine_state: &EngineState,
|
||||
stack: &Stack,
|
||||
config: &Config,
|
||||
) -> Result<HashMap<String, String>, ShellError> {
|
||||
let env_vars = stack.get_env_vars(engine_state);
|
||||
let mut env_vars_str = HashMap::new();
|
||||
for (env_name, val) in env_vars {
|
||||
let val_str = env_to_string(&env_name, val, engine_state, stack, config)?;
|
||||
env_vars_str.insert(env_name, val_str);
|
||||
match env_to_string(&env_name, &val, engine_state, stack) {
|
||||
Ok(val_str) => {
|
||||
env_vars_str.insert(env_name, val_str);
|
||||
}
|
||||
Err(ShellError::EnvVarNotAString(..)) => {} // ignore non-string values
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(env_vars_str)
|
||||
@ -93,16 +142,16 @@ pub fn env_to_strings(
|
||||
|
||||
/// Shorthand for env_to_string() for PWD with custom error
|
||||
pub fn current_dir_str(engine_state: &EngineState, stack: &Stack) -> Result<String, ShellError> {
|
||||
let config = stack.get_config()?;
|
||||
if let Some(pwd) = stack.get_env_var(engine_state, "PWD") {
|
||||
match env_to_string("PWD", pwd, engine_state, stack, &config) {
|
||||
match env_to_string("PWD", &pwd, engine_state, stack) {
|
||||
Ok(cwd) => {
|
||||
if Path::new(&cwd).is_absolute() {
|
||||
Ok(cwd)
|
||||
} else {
|
||||
Err(ShellError::LabeledError(
|
||||
Err(ShellError::SpannedLabeledError(
|
||||
"Invalid current directory".to_string(),
|
||||
format!("The 'PWD' environment variable must be set to an absolute path. Found: '{}'", cwd)
|
||||
format!("The 'PWD' environment variable must be set to an absolute path. Found: '{}'", cwd),
|
||||
pwd.span()?
|
||||
))
|
||||
}
|
||||
}
|
||||
@ -149,21 +198,7 @@ pub fn path_str(
|
||||
}
|
||||
}?;
|
||||
|
||||
if let Value::String { val, .. } = pathval {
|
||||
Ok(val)
|
||||
} else {
|
||||
match get_converted_value(engine_state, stack, pathname, &pathval, "to_string") {
|
||||
ConversionResult::Ok(v) => Ok(v.as_string()?),
|
||||
ConversionResult::ConversionError(e) => Err(e),
|
||||
ConversionResult::GeneralError(e) => Err(e),
|
||||
ConversionResult::CellPathError => Err(ShellError::SpannedLabeledErrorHelp(
|
||||
format!("Missing environment conversion of {} to string", pathname),
|
||||
"Could not convert to string".to_string(),
|
||||
pathval.span()?,
|
||||
"The 'to_string' field of ENV_CONVERSIONS environment variable must be set up correctly".to_string()
|
||||
))
|
||||
}
|
||||
}
|
||||
env_to_string(pathname, &pathval, engine_state, stack)
|
||||
}
|
||||
|
||||
fn get_converted_value(
|
||||
@ -226,7 +261,6 @@ fn get_converted_value(
|
||||
Err(e) => ConversionResult::ConversionError(e),
|
||||
}
|
||||
} else {
|
||||
// This one is OK to fail: We want to know if custom conversion is working
|
||||
ConversionResult::ConversionError(ShellError::MissingParameter(
|
||||
"block input".into(),
|
||||
from_span,
|
||||
@ -239,3 +273,56 @@ fn get_converted_value(
|
||||
ConversionResult::CellPathError
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_path(scope: &mut HashMap<String, Value>, env_path_name: &str) -> Option<ShellError> {
|
||||
let mut error = None;
|
||||
|
||||
// If PATH/Path is still a string, force-convert it to a list
|
||||
match scope.get(env_path_name) {
|
||||
Some(Value::String { val, span }) => {
|
||||
// Force-split path into a list
|
||||
let span = *span;
|
||||
let paths = std::env::split_paths(val)
|
||||
.map(|p| Value::String {
|
||||
val: p.to_string_lossy().to_string(),
|
||||
span,
|
||||
})
|
||||
.collect();
|
||||
|
||||
scope.insert(env_path_name.to_string(), Value::List { vals: paths, span });
|
||||
}
|
||||
Some(Value::List { vals, span }) => {
|
||||
// Must be a list of strings
|
||||
if !vals.iter().all(|v| matches!(v, Value::String { .. })) {
|
||||
error = error.or_else(|| {
|
||||
Some(ShellError::SpannedLabeledError(
|
||||
format!("Wrong {} environment variable value", env_path_name),
|
||||
format!("{} must be a list of strings", env_path_name),
|
||||
*span,
|
||||
))
|
||||
});
|
||||
}
|
||||
}
|
||||
Some(val) => {
|
||||
// All other values are errors
|
||||
let span = match val.span() {
|
||||
Ok(sp) => sp,
|
||||
Err(e) => {
|
||||
error = error.or(Some(e));
|
||||
Span::test_data() // FIXME: any better span to use here?
|
||||
}
|
||||
};
|
||||
|
||||
error = error.or_else(|| {
|
||||
Some(ShellError::SpannedLabeledError(
|
||||
format!("Wrong {} environment variable value", env_path_name),
|
||||
format!("{} must be a list of strings", env_path_name),
|
||||
span,
|
||||
))
|
||||
});
|
||||
}
|
||||
None => { /* not preset, do nothing */ }
|
||||
}
|
||||
|
||||
error
|
||||
}
|
||||
|
Reference in New Issue
Block a user