From 9dd0c47d0be72c5fe5a5134d9bdf8ebb9d6bd43a Mon Sep 17 00:00:00 2001 From: Kurtis Rader Date: Mon, 20 Feb 2017 20:23:55 -0800 Subject: [PATCH] harden `alias` against `foo; bar` If the first word of the alias body ends with a semicolon we need to strip that character, and otherwise escape the extracted command, to ensure the subsequent function definition is valid. Fixes #3860 --- share/functions/alias.fish | 16 ++++++++++------ tests/alias.err | 0 tests/alias.in | 7 +++++++ tests/alias.out | 2 ++ 4 files changed, 19 insertions(+), 6 deletions(-) create mode 100644 tests/alias.err create mode 100644 tests/alias.in create mode 100644 tests/alias.out diff --git a/share/functions/alias.fish b/share/functions/alias.fish index f9637ca00..e7c1d983a 100644 --- a/share/functions/alias.fish +++ b/share/functions/alias.fish @@ -11,6 +11,7 @@ function alias --description 'Creates a function wrapping a command' set -l body set -l prefix set -l first_word + set -l wrapped_cmd switch (count $argv) case 0 @@ -43,11 +44,13 @@ function alias --description 'Creates a function wrapping a command' return 1 end - # Extract the first command from the body - # This is supposed to replace all non-escaped (i.e. preceded by an odd number of `\`) spaces with a newline - # so it splits on them + # Extract the first command from the body. This is supposed to replace all non-escaped (i.e. + # preceded by an odd number of `\`) spaces with a newline so it splits on them. See issue #2220 + # for why the following borderline incomprehensible code exists. set -l tmp (string replace -ra "([^\\\ ])((\\\\\\\)*) " '$1\n' $body) set first_word (string trim $tmp[1]) + # If the user does something like `alias x 'foo; bar'` we need to strip the semicolon. + set wrapped_cmd (string trim -c ';' $first_word) if set -q tmp[2] set body $tmp[2..-1] else @@ -56,8 +59,7 @@ function alias --description 'Creates a function wrapping a command' # Prevent the alias from immediately running into an infinite recursion if # $body starts with the same command as $name. - - if test $first_word = $name + if test $wrapped_cmd = $name if contains $name (builtin --names) set prefix builtin else @@ -65,5 +67,7 @@ function alias --description 'Creates a function wrapping a command' end end set -l cmd_string (string escape "alias $argv") - echo "function $name --wraps $first_word --description $cmd_string; $prefix $first_word $body \$argv; end" | source + set wrapped_cmd (string escape $wrapped_cmd) + echo "function $name --wraps $wrapped_cmd --description $cmd_string; $prefix $first_word $body \$argv; end" | source + #echo "function $name --wraps $wrapped_cmd --description $cmd_string; $prefix $first_word $body \$argv; end" end diff --git a/tests/alias.err b/tests/alias.err new file mode 100644 index 000000000..e69de29bb diff --git a/tests/alias.in b/tests/alias.in new file mode 100644 index 000000000..b3423b56f --- /dev/null +++ b/tests/alias.in @@ -0,0 +1,7 @@ +# Avoid regressions of issue #3860 wherein the first word of the alias ends +# with a semicolon. +function foo + echo ran foo +end +alias my_alias "foo; and echo foo ran" +my_alias diff --git a/tests/alias.out b/tests/alias.out new file mode 100644 index 000000000..b07ca34b6 --- /dev/null +++ b/tests/alias.out @@ -0,0 +1,2 @@ +ran foo +foo ran