Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/uu/env/locales/en-US.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ env-error-backslash-c-not-allowed = '\c' must not appear in double-quoted -S str
env-error-invalid-sequence = invalid sequence '\{ $char }' in -S at position { $position }
env-error-missing-closing-brace = Missing closing brace at position { $position }
env-error-missing-variable = Missing variable name at position { $position }
env-error-only-braced-variable = only ${VARNAME} expansion is supported at position { $position }
env-error-missing-closing-brace-after-value = Missing closing brace after default value at position { $position }
env-error-unexpected-number = Unexpected character: '{ $char }', expected variable name must not start with 0..9 at position { $position }
env-error-expected-brace-or-colon = Unexpected character: '{ $char }', expected a closing brace ('{"}"}') or colon (':') at position { $position }
Expand Down
1 change: 1 addition & 0 deletions src/uu/env/locales/fr-FR.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ env-error-backslash-c-not-allowed = '\\c' ne doit pas apparaître dans une chaî
env-error-invalid-sequence = séquence invalide '\\{ $char }' dans -S à la position { $position }
env-error-missing-closing-brace = Accolade fermante manquante à la position { $position }
env-error-missing-variable = Nom de variable manquant à la position { $position }
env-error-only-braced-variable = seule l'expansion ${VARNAME} est prise en charge à la position { $position }
env-error-missing-closing-brace-after-value = Accolade fermante manquante après la valeur par défaut à la position { $position }
env-error-unexpected-number = Caractère inattendu : '{ $char }', le nom de variable attendu ne doit pas commencer par 0..9 à la position { $position }
env-error-expected-brace-or-colon = Caractère inattendu : '{ $char }', accolade fermante ('{"\\}"}') ou deux-points (':') attendu à la position { $position }
Expand Down
96 changes: 79 additions & 17 deletions src/uu/env/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,10 @@ pub enum EnvError {
EnvParsingOfVariableMissingClosingBrace(usize),
#[error("{}", translate!("env-error-missing-variable", "position" => .0))]
EnvParsingOfMissingVariable(usize),
#[error("{}", translate!("env-error-missing-closing-brace-after-value", "position" => .0))]
EnvParsingOfVariableMissingClosingBraceAfterValue(usize),
#[error("{}", translate!("env-error-only-braced-variable", "position" => .0))]
EnvParsingOfVariableOnlyBracedName(usize),
#[error("{}", translate!("env-error-unexpected-number", "position" => .0, "char" => .1.clone()))]
EnvParsingOfVariableUnexpectedNumber(usize, String),
#[error("{}", translate!("env-error-expected-brace-or-colon", "position" => .0, "char" => .1.clone()))]
EnvParsingOfVariableExceptedBraceOrColon(usize, String),
#[error("")]
EnvReachedEnd,
#[error("")]
Expand Down Expand Up @@ -481,18 +479,14 @@ pub fn parse_args_from_str(text: &NativeIntStr) -> UResult<Vec<NativeIntString>>
125,
translate!("env-error-variable-name-issue", "position" => pos, "error" => e),
),
EnvError::EnvParsingOfVariableMissingClosingBraceAfterValue(pos) => USimpleError::new(
EnvError::EnvParsingOfVariableOnlyBracedName(pos) => USimpleError::new(
125,
translate!("env-error-variable-name-issue", "position" => pos, "error" => e),
),
EnvError::EnvParsingOfVariableUnexpectedNumber(pos, _) => USimpleError::new(
125,
translate!("env-error-variable-name-issue", "position" => pos, "error" => e),
),
EnvError::EnvParsingOfVariableExceptedBraceOrColon(pos, _) => USimpleError::new(
125,
translate!("env-error-variable-name-issue", "position" => pos, "error" => e),
),
_ => USimpleError::new(
125,
translate!("env-error-generic", "error" => format!("{e:?}")),
Expand All @@ -513,13 +507,29 @@ fn check_and_handle_string_args(
prefix_to_test: &str,
all_args: &mut Vec<OsString>,
do_debug_print_args: Option<&Vec<OsString>>,
require_non_empty_payload: bool,
strip_optional_leading_equals: bool,
) -> UResult<bool> {
let native_arg = NCvt::convert(arg);
if let Some(remaining_arg) = native_arg.strip_prefix(&*NCvt::convert(prefix_to_test)) {
if require_non_empty_payload && remaining_arg.is_empty() {
return Ok(false);
}

if let Some(input_args) = do_debug_print_args {
debug_print_args(input_args); // do it here, such that its also printed when we get an error/panic during parsing
}

let remaining_arg = if strip_optional_leading_equals {
if let Some(stripped_remaining_arg) = remaining_arg.strip_prefix(&*NCvt::convert("=")) {
stripped_remaining_arg
} else {
remaining_arg
}
} else {
remaining_arg
};

let arg_strings = parse_args_from_str(remaining_arg)?;
all_args.extend(
arg_strings
Expand Down Expand Up @@ -574,7 +584,12 @@ impl EnvAppData {
options::UNSET,
];
let short_flags_with_args = ['a', 'C', 'f', 'u'];
let mut consumed_split_payload_arg: Option<usize> = None;
for (n, arg) in original_args.iter().enumerate() {
if consumed_split_payload_arg == Some(n) {
consumed_split_payload_arg = None;
continue;
}
let arg_str = arg.to_string_lossy();
// Stop processing env flags once we reach the command or -- argument
if 0 < n
Expand All @@ -589,13 +604,21 @@ impl EnvAppData {
}
expecting_arg = false;
match arg {
b if check_and_handle_string_args(b, "--split-string", &mut all_args, None)? => {
b if check_and_handle_string_args(
b,
"--split-string",
&mut all_args,
None,
true,
true,
)? =>
{
self.had_string_argument = true;
}
b if check_and_handle_string_args(b, "-S", &mut all_args, None)? => {
b if check_and_handle_string_args(b, "-S", &mut all_args, None, true, false)? => {
self.had_string_argument = true;
}
b if check_and_handle_string_args(b, "-vS", &mut all_args, None)? => {
b if check_and_handle_string_args(b, "-vS", &mut all_args, None, true, false)? => {
self.do_debug_printing = true;
self.had_string_argument = true;
}
Expand All @@ -604,12 +627,39 @@ impl EnvAppData {
"-vvS",
&mut all_args,
Some(original_args),
true,
false,
)? =>
{
self.do_debug_printing = true;
self.do_input_debug_printing = Some(false); // already done
self.had_string_argument = true;
}
b if b == "--split-string" || b == "-S" || b == "-vS" || b == "-vvS" => {
let Some(next_arg) = original_args.get(n + 1) else {
all_args.push(arg.clone());
continue;
};

if b == "-vS" || b == "-vvS" {
self.do_debug_printing = true;
}
if b == "-vvS" {
debug_print_args(original_args);
self.do_input_debug_printing = Some(false);
}

let native_next_arg = NCvt::convert(next_arg);
let arg_strings = parse_args_from_str(native_next_arg.as_ref())?;
all_args.extend(
arg_strings
.into_iter()
.map(from_native_int_representation_owned),
);
self.had_string_argument = true;
expecting_arg = false;
consumed_split_payload_arg = Some(n + 1);
}
_ => {
if let Some(flag) = arg_str.strip_prefix("--") {
if flags_with_args.contains(&flag) {
Expand Down Expand Up @@ -714,8 +764,6 @@ impl EnvAppData {
&signal_apply_all,
)?;

apply_change_directory(&opts)?;

// NOTE: we manually set and unset the env vars below rather than using Command::env() to more
// easily handle the case where no command is given

Expand Down Expand Up @@ -754,6 +802,7 @@ impl EnvAppData {
}
}

apply_change_directory(&opts)?;
if opts.program.is_empty() {
// no program provided, so just dump all env vars to stdout
print_all_env_vars(opts.line_ending)?;
Expand Down Expand Up @@ -1246,21 +1295,34 @@ mod tests {
.contains("variable name issue (at 10): Missing closing brace")
);

let result = parse_args_from_str(&NCvt::convert(r"echo ${FOO:-value"));
let result = parse_args_from_str(&NCvt::convert(r"echo ${FOO:-value}"));
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("variable name issue (at 17): Missing closing brace after default value")
.contains("only ${VARNAME} expansion is supported")
);

let result = parse_args_from_str(&NCvt::convert(r"echo $FOO"));
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("only ${VARNAME} expansion is supported")
);
let result = parse_args_from_str(&NCvt::convert(r"echo ${1FOO}"));
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("variable name issue (at 7): Unexpected character: '1', expected variable name must not start with 0..9"));

let result = parse_args_from_str(&NCvt::convert(r"echo ${FOO?}"));
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("variable name issue (at 10): Unexpected character: '?', expected a closing brace ('}') or colon (':')"));
assert!(
result
.unwrap_err()
.to_string()
.contains("only ${VARNAME} expansion is supported")
);
}
}
20 changes: 4 additions & 16 deletions src/uu/env/src/split_iterator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,18 +99,11 @@ impl<'a> SplitIterator<'a> {
parser: self.get_parser_mut(),
};

let (name, default) = var_parse.parse_variable()?;
let name = var_parse.parse_variable()?;

let varname_os_str_cow = from_native_int_representation(Cow::Borrowed(name));
let value = std::env::var_os(varname_os_str_cow);
match (&value, default) {
(None, None) => {} // do nothing, just replace it with ""
(Some(value), _) => {
self.expander.put_string(value);
}
(None, Some(default)) => {
self.expander.put_native_string(default);
}
if let Some(value) = std::env::var_os(varname_os_str_cow) {
self.expander.put_string(value);
}

Ok(())
Expand Down Expand Up @@ -286,15 +279,10 @@ impl<'a> SplitIterator<'a> {
self.take_one()?;
Ok(())
}
Some(c) if REPLACEMENTS.iter().any(|&x| x.0 == c) => {
// See GNU test-suite e11: In single quotes, \t remains as it is.
// Comparing with GNU behavior: \a is not accepted and issues an error.
// So apparently only known sequences are allowed, even though they are not expanded.... bug of GNU?
Some(_) => {
self.push_char_to_word(BACKSLASH);
self.take_one()?;
Ok(())
}
Some(c) => Err(self.make_invalid_sequence_backslash_xin_minus_s(c)),
}
}

Expand Down
104 changes: 19 additions & 85 deletions src/uu/env/src/variable_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,126 +34,60 @@ impl<'a> VariableParser<'a, '_> {
Ok(())
}

fn parse_braced_variable_name(
&mut self,
) -> Result<(&'a NativeIntStr, Option<&'a NativeIntStr>), EnvError> {
fn parse_braced_variable_name(&mut self) -> Result<&'a NativeIntStr, EnvError> {
let pos_start = self.parser.get_peek_position();

self.check_variable_name_start()?;

let (varname_end, default_end);
loop {
let varname_end = loop {
match self.get_current_char() {
None => {
return Err(EnvError::EnvParsingOfVariableMissingClosingBrace(
self.parser.get_peek_position(),
));
}
Some(c) if !c.is_ascii() || c.is_ascii_alphanumeric() || c == '_' => {
Some(c) if c.is_ascii_alphanumeric() || c == '_' => {
self.skip_one()?;
}
Some(':') => {
varname_end = self.parser.get_peek_position();
loop {
match self.get_current_char() {
None => {
return Err(
EnvError::EnvParsingOfVariableMissingClosingBraceAfterValue(
self.parser.get_peek_position(),
),
);
}
Some('}') => {
default_end = Some(self.parser.get_peek_position());
self.skip_one()?;
break;
}
Some(_) => {
self.skip_one()?;
}
}
}
break;
}
Some('}') => {
varname_end = self.parser.get_peek_position();
default_end = None;
let varname_end = self.parser.get_peek_position();
self.skip_one()?;
break;
break varname_end;
}
Some(c) => {
return Err(EnvError::EnvParsingOfVariableExceptedBraceOrColon(
Some(_) => {
return Err(EnvError::EnvParsingOfVariableOnlyBracedName(
self.parser.get_peek_position(),
c.to_string(),
));
}
}
}

let default_opt = if let Some(default_end) = default_end {
Some(self.parser.substring(&Range {
start: varname_end + 1,
end: default_end,
}))
} else {
None
};

let varname = self.parser.substring(&Range {
start: pos_start,
end: varname_end,
});

Ok((varname, default_opt))
}

fn parse_unbraced_variable_name(&mut self) -> Result<&'a NativeIntStr, EnvError> {
let pos_start = self.parser.get_peek_position();

self.check_variable_name_start()?;

loop {
match self.get_current_char() {
None => break,
Some(c) if c.is_ascii_alphanumeric() || c == '_' => {
self.skip_one()?;
}
Some(_) => break,
}
}

let pos_end = self.parser.get_peek_position();

if pos_end == pos_start {
if varname_end == pos_start {
return Err(EnvError::EnvParsingOfMissingVariable(pos_start));
}

let varname = self.parser.substring(&Range {
start: pos_start,
end: pos_end,
end: varname_end,
});

Ok(varname)
}

pub fn parse_variable(
&mut self,
) -> Result<(&'a NativeIntStr, Option<&'a NativeIntStr>), EnvError> {
pub fn parse_variable(&mut self) -> Result<&'a NativeIntStr, EnvError> {
self.skip_one()?;

let (name, default) = match self.get_current_char() {
None => {
return Err(EnvError::EnvParsingOfMissingVariable(
self.parser.get_peek_position(),
));
}
match self.get_current_char() {
None => Err(EnvError::EnvParsingOfMissingVariable(
self.parser.get_peek_position(),
)),
Some('{') => {
self.skip_one()?;
self.parse_braced_variable_name()?
self.parse_braced_variable_name()
}
Some(_) => (self.parse_unbraced_variable_name()?, None),
};

Ok((name, default))
Some(_) => Err(EnvError::EnvParsingOfVariableOnlyBracedName(
self.parser.get_peek_position(),
)),
}
}
}
Loading
Loading