From 80ec1ad2adc962274f9dd0a2fd8d543dda344318 Mon Sep 17 00:00:00 2001 From: "Tom \"Ravi\" Hale" Date: Wed, 12 Sep 2018 17:57:46 +0700 Subject: [PATCH] Replace built-in shell array quoting with printf %q --- lib/scm_breeze.sh | 43 +++++++++++++++++---- test/lib/git/status_shortcuts_test.sh | 54 +++++++++++++++++++++------ test/lib/scm_breeze_test.sh | 6 +-- test/support/test_helper.sh | 14 ------- 4 files changed, 81 insertions(+), 36 deletions(-) diff --git a/lib/scm_breeze.sh b/lib/scm_breeze.sh index b177db9..c9cb87e 100644 --- a/lib/scm_breeze.sh +++ b/lib/scm_breeze.sh @@ -18,18 +18,45 @@ _alias() { fi } +# Quote the contents of "$@" +function token_quote { + # Older versions of {ba,z}sh don't support the built-in quoting, so fall back to printf %q + local quoted=() + for token; do + quoted+=( "$(printf '%q' "$token")" ) + done + printf '%s\n' "${quoted[*]}" + + # Keep this code for use when minimum versions of {ba,z}sh can be increased. + # See https://github.com/scmbreeze/scm_breeze/issues/260 + # + # if [[ $shell = bash ]]; then + # # ${parameter@operator} where parameter is ${@} and operator is 'Q' + # # https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html + # eval "${@@Q}" + # else # zsh + # # http://zsh.sourceforge.net/Doc/Release/Expansion.html#Parameter-Expansion-Flags + # eval "${(q-)@}" + # fi +} + # Quote "$@" before `eval` to prevent arbitrary code execution. # Eg, the following will run `date`: # evil() { eval "$@"; }; evil "echo" "foo;date" function _safe_eval() { - if [[ $shell = bash ]]; then - # ${parameter@operator} where parameter is ${@} and operator is 'Q' - # https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html - eval "${@@Q}" - else # zsh - # http://zsh.sourceforge.net/Doc/Release/Expansion.html#Parameter-Expansion-Flags - eval "${(q-)@}" - fi + eval $(token_quote "$@") + + # Keep this code for use when minimum versions of {ba,z}sh can be increased. + # See https://github.com/scmbreeze/scm_breeze/issues/260 + # + # if [[ $shell = bash ]]; then + # # ${parameter@operator} where parameter is ${@} and operator is 'Q' + # # https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html + # eval "${@@Q}" + # else # zsh + # # http://zsh.sourceforge.net/Doc/Release/Expansion.html#Parameter-Expansion-Flags + # eval "${(q-)@}" + # fi } find_binary(){ diff --git a/test/lib/git/status_shortcuts_test.sh b/test/lib/git/status_shortcuts_test.sh index 3f54c5d..0cbf23b 100755 --- a/test/lib/git/status_shortcuts_test.sh +++ b/test/lib/git/status_shortcuts_test.sh @@ -51,38 +51,70 @@ setupTestRepo() { test_scmb_expand_args() { local e1="one" e2="two" e3="three" e4="four" e5="five" e6="six" e7='$dollar' e8='two words' local error="Args not expanded correctly" - assertEquals "$error" "'one' 'three' 'six'" \ + assertEquals "$error" 'one three six' \ "$(eval a=$(scmb_expand_args 1 3 6); token_quote "${a[@]}")" - assertEquals "$error" "'one' 'two' 'three' 'five'" \ + assertEquals "$error" 'one two three five' \ "$(eval a=$(scmb_expand_args 1-3 5); token_quote "${a[@]}")" - assertEquals "$error" "'\$dollar' 'two' 'three' 'four' 'one'" \ + assertEquals "$error" '\$dollar two three four one' \ "$(eval a=$(scmb_expand_args 7 2-4 1); token_quote "${a[@]}")" # Test that any args with spaces remain quoted - assertEquals "$error" "'-m' 'Test Commit Message' 'one'" \ + assertEquals "$error" '-m Test\ Commit\ Message one' \ "$(eval a=$(scmb_expand_args -m "Test Commit Message" 1); token_quote "${a[@]}")" - assertEquals "$error" "'-ma' 'Test Commit Message' 'Unquoted'"\ + assertEquals "$error" '-ma Test\ Commit\ Message Unquoted'\ "$(eval a=$(scmb_expand_args -ma "Test Commit Message" "Unquoted"); token_quote "${a[@]}")" - assertEquals "$error" "'\$dollar' 'one' 'two words'" \ + assertEquals "$error" '\$dollar one two\ words' \ "$(eval a=$(scmb_expand_args 7 1-1 8); token_quote "${a[@]}")" + + # Keep this code for use when minimum versions of {ba,z}sh can be increased. + # See token_quote() source and https://github.com/scmbreeze/scm_breeze/issues/260 + # local e1="one" e2="two" e3="three" e4="four" e5="five" e6="six" e7='$dollar' e8='two words' + # local error="Args not expanded correctly" + # assertEquals "$error" "'one' 'three' 'six'" \ + # "$(eval a=$(scmb_expand_args 1 3 6); token_quote "${a[@]}")" + # assertEquals "$error" "'one' 'two' 'three' 'five'" \ + # "$(eval a=$(scmb_expand_args 1-3 5); token_quote "${a[@]}")" + # assertEquals "$error" "'\$dollar' 'two' 'three' 'four' 'one'" \ + # "$(eval a=$(scmb_expand_args 7 2-4 1); token_quote "${a[@]}")" + # + # # Test that any args with spaces remain quoted + # assertEquals "$error" "'-m' 'Test Commit Message' 'one'" \ + # "$(eval a=$(scmb_expand_args -m "Test Commit Message" 1); token_quote "${a[@]}")" + # assertEquals "$error" "'-ma' 'Test Commit Message' 'Unquoted'"\ + # "$(eval a=$(scmb_expand_args -ma "Test Commit Message" "Unquoted"); token_quote "${a[@]}")" + # assertEquals "$error" "'\$dollar' 'one' 'two words'" \ + # "$(eval a=$(scmb_expand_args 7 1-1 8); token_quote "${a[@]}")" } test_exec_scmb_expand_args() { local e1="one" e2="a b c" e3='$dollar' e4="single'quote" e5='double"quote' e6='a(){:;};a&' - assertEquals "literals with spaces not preserved" "'foo' 'bar baz'" \ + assertEquals "literals with spaces not preserved" 'foo bar\ baz' \ "$(eval a="$(scmb_expand_args foo 'bar baz')"; token_quote "${a[@]}")" - assertEquals "variables with spaces not preserved" "'one' 'a b c'" \ + assertEquals "variables with spaces not preserved" 'one a\ b\ c' \ "$(eval a="$(scmb_expand_args 1-2)"; token_quote "${a[@]}")" # Expecting text: '$dollar' "single'quote" 'double"quote' # Generate quoted expected string with: token_quote "$(cat)" then copy/paste, ^D assertEquals "special characters are preserved" \ - "'\$dollar' 'single'\\''quote' 'double\"quote' 'a(){:;};a&'" \ + '\$dollar single\'\''quote double\"quote a\(\)\{:\;\}\;a\&' \ "$(eval a="$(scmb_expand_args 3-6)"; token_quote "${a[@]}")" + + # Keep this code for use when minimum versions of {ba,z}sh can be increased. + # See token_quote() source and https://github.com/scmbreeze/scm_breeze/issues/260 + # local e1="one" e2="a b c" e3='$dollar' e4="single'quote" e5='double"quote' e6='a(){:;};a&' + # assertEquals "literals with spaces not preserved" "'foo' 'bar baz'" \ + # "$(eval a="$(scmb_expand_args foo 'bar baz')"; token_quote "${a[@]}")" + # assertEquals "variables with spaces not preserved" "'one' 'a b c'" \ + # "$(eval a="$(scmb_expand_args 1-2)"; token_quote "${a[@]}")" + # # Expecting text: '$dollar' "single'quote" 'double"quote' + # # Generate quoted expected string with: token_quote "$(cat)" then copy/paste, ^D + # assertEquals "special characters are preserved" \ + # "'\$dollar' 'single'\\''quote' 'double\"quote' 'a(){:;};a&'" \ + # "$(eval a="$(scmb_expand_args 3-6)"; token_quote "${a[@]}")" } test_command_wrapping_escapes_special_characters() { - assertEquals 'should escape | the pipe' "$(exec_scmb_expand_args echo "should escape | the pipe")" - assertEquals 'should escape ; the semicolon' "$(exec_scmb_expand_args echo "should escape ; the semicolon")" + assertEquals 'should escape | the pipe' "$(exec_scmb_expand_args echo should escape '|' the pipe)" + assertEquals 'should escape ; the semicolon' "$(exec_scmb_expand_args echo should escape ';' the semicolon)" } test_git_status_shortcuts() { diff --git a/test/lib/scm_breeze_test.sh b/test/lib/scm_breeze_test.sh index f74beef..5a5a8da 100755 --- a/test/lib/scm_breeze_test.sh +++ b/test/lib/scm_breeze_test.sh @@ -16,9 +16,9 @@ source "$scmbDir/lib/scm_breeze.sh" #----------------------------------------------------------------------------- test__safe_eval() { - assertEquals "runs eval with simple words" "'one' 'two' 'three'" "$(_safe_eval token_quote one two three)" - assertEquals "quotes spaces" "'a' 'b c' 'd'" "$(_safe_eval token_quote a b\ c d)" - assertEquals "quotes special chars" "'a b' '\$dollar' '\\slash' 'c d'" "$(_safe_eval token_quote a\ b '$dollar' '\slash' c\ d)" + assertEquals "runs eval with simple words" 'one two three' "$(_safe_eval token_quote one two three)" + assertEquals "quotes spaces" 'a b\ c d' "$(_safe_eval token_quote a b\ c d)" + assertEquals "quotes special chars" 'a\ b \$dollar \\slash c\ d' "$(_safe_eval token_quote a\ b '$dollar' '\slash' c\ d)" } diff --git a/test/support/test_helper.sh b/test/support/test_helper.sh index de8cd1b..67a6155 100644 --- a/test/support/test_helper.sh +++ b/test/support/test_helper.sh @@ -32,20 +32,6 @@ verboseGitCommands() { unset -f git } -# Quote the contents of "$@" in single quotes -# Avoid printf '%q' as 'a b' becomes a\ b in both {ba,z}sh -# See also quote_args and double_quote -function token_quote { - if [[ $shell = bash ]]; then - # Single quotes are always added - echo "${@@Q}" - else # zsh - # Single quotes only added when needed - #shellcheck disable=2154 # zsh - echo "${(qq)@}" - fi -} - # Asserts #-----------------------------------------------------------------------------