Skip to content

Commit 9319b70

Browse files
authored
Merge pull request #175 from mbland/safe-prompt
Add @go.prompt_for_safe_input to lib/prompt
2 parents 38e79b6 + d751a99 commit 9319b70

File tree

5 files changed

+141
-5
lines changed

5 files changed

+141
-5
lines changed

lib/prompt

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
# @go.prompt_for_input
1010
# Prompts the user for a line of input
1111
#
12+
# @go.prompt_for_safe_input
13+
# Prompts the user for a line of input, then validates it isn't dangerous
14+
#
1215
# @go.prompt_for_yes_or_no
1316
# Prompts the user for a yes or no response
1417
#
@@ -61,6 +64,42 @@
6164
fi
6265
}
6366

67+
# Prompts the user for a line of input, then validates it isn't dangerous
68+
#
69+
# With the exception of the `--or-die` option, the arguments are the same as the
70+
# underlying `@go.prompt_for_input`. Useful if the input value may be used to
71+
# construct a command or query.
72+
#
73+
# Options (must be specified before arguments):
74+
# --or-die <desc> Print description and stack and exit with error if invalid
75+
#
76+
# Arguments:
77+
# result_var Name of the caller-declared variable for the result
78+
# prompt Text prompt for user input
79+
# default (Optional) Default value if response is empty
80+
# fail_msg (Optional) Failure message if empty input isn't valid
81+
@go.prompt_for_safe_input() {
82+
local or_die
83+
local description
84+
85+
if [[ "$1" == '--or-die' ]]; then
86+
or_die='true'
87+
description="$2"
88+
shift 2
89+
fi
90+
@go.validate_identifier_or_die 'Input prompt response variable name' "$1"
91+
92+
if ! @go.prompt_for_input "$@"; then
93+
return 1
94+
elif [[ "$or_die" == 'true' ]]; then
95+
@go.validate_input_or_die "$description" "${!1}"
96+
elif ! @go.validate_input "${!1}"; then
97+
@go.printf '"%s" is an invalid response, as it contains %s.\n' \
98+
"${!1}" 'unescaped shell metacharacters or control operators' >&2
99+
return 1
100+
fi
101+
}
102+
64103
# Prompts the user for a yes or no response
65104
#
66105
# Arguments:

lib/validation

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,9 @@ readonly __GO_VALID_IDENTIFIER_PATTERN='^[[:alpha:]_][[:alnum:]_]*$'
5252
local skip_callers="${3:-2}"
5353

5454
if ! @go.validate_input "$value"; then
55-
printf '%s "%s" for %s contains invalid characters at:\n' \
56-
"$description" "$value" "${FUNCNAME[$((skip_callers - 1))]}" >&2
55+
printf '%s "%s" for %s contains %s at:\n' \
56+
"$description" "$value" "${FUNCNAME[$((skip_callers - 1))]}" \
57+
'unescaped shell metacharacters or control operators' >&2
5758
@go.print_stack_trace "$skip_callers" >&2
5859
exit 1
5960
fi

tests/file/open-or-dup.bats

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,8 @@ create_file_open_test_go_script() {
145145
FILE_PATH_OR_FD="$file_path_or_fd" run "$TEST_GO_SCRIPT"
146146

147147
local err_msg="file_path_or_fd \"$file_path_or_fd\" "
148-
err_msg+='for @go.open_file_or_duplicate_fd contains invalid characters at:'
148+
err_msg+='for @go.open_file_or_duplicate_fd contains '
149+
err_msg+='unescaped shell metacharacters or control operators at:'
149150

150151
local expected=("$err_msg"
151152
" $TEST_GO_SCRIPT:5 main")
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#! /usr/bin/env bats
2+
3+
load ../environment
4+
5+
setup() {
6+
test_filter
7+
@go.create_test_go_script '. "$_GO_USE_MODULES" "prompt"' \
8+
'declare prompt="$1"' \
9+
'declare default="$2"' \
10+
'declare fail_msg="$3"' \
11+
'declare response="initial value"' \
12+
'declare result' \
13+
'@go.prompt_for_safe_input 'response' "$prompt" "$default" "$fail_msg"' \
14+
'result="$?"' \
15+
'if [[ "$result" -eq "0" ]]; then' \
16+
' printf -- "%s\n" "$response"' \
17+
'fi' \
18+
'exit "$result"'
19+
}
20+
21+
teardown() {
22+
@go.remove_test_go_rootdir
23+
}
24+
25+
@test "$SUITE: error if variable not a valid identifier" {
26+
@go.create_test_go_script '. "$_GO_USE_MODULES" "prompt"' \
27+
'@go.prompt_for_safe_input "invalid;"'
28+
29+
run "$TEST_GO_SCRIPT"
30+
assert_failure
31+
32+
local err_msg='Input prompt response variable name "invalid;" for '
33+
err_msg+='@go.prompt_for_safe_input '
34+
err_msg+='contains invalid identifier characters at:'
35+
36+
assert_lines_match "^${err_msg}\$" \
37+
"^ $TEST_GO_SCRIPT:[0-9] main$"
38+
39+
}
40+
41+
@test "$SUITE: reads value with no metacharacters or control operators" {
42+
run "$TEST_GO_SCRIPT" $'What is your quest?\n' <<<'To seek the grail!'
43+
assert_success 'What is your quest?' \
44+
'To seek the grail!'
45+
}
46+
47+
@test "$SUITE: reads a value with an escaped metacharacter" {
48+
run "$TEST_GO_SCRIPT" $'What is your quest?\n' <<<"To seek the grail\;"
49+
assert_success 'What is your quest?' \
50+
"To seek the grail\;"
51+
}
52+
53+
@test "$SUITE: fails when reading a value with an unescaped metacharacter" {
54+
run "$TEST_GO_SCRIPT" $'What is your quest?\n' <<<'To seek the grail;'
55+
56+
local err_msg='"To seek the grail;" is an invalid response, as it contains '
57+
err_msg+='unescaped shell metacharacters or control operators.'
58+
59+
assert_failure 'What is your quest?' \
60+
"$err_msg"
61+
}
62+
63+
@test "$SUITE: fails when the default value has an unescaped metacharacter" {
64+
run "$TEST_GO_SCRIPT" $'What is your quest?\n' 'To seek the grail;' <<<''
65+
66+
local err_msg='"To seek the grail;" is an invalid response, as it contains '
67+
err_msg+='unescaped shell metacharacters or control operators.'
68+
69+
assert_failure 'What is your quest? [default: To seek the grail;]' \
70+
"$err_msg"
71+
}
72+
73+
@test "$SUITE: fails when no default or input provided" {
74+
run "$TEST_GO_SCRIPT" $'What is your quest?\n' '' 'Auuuuuuuugh!' <<<''
75+
assert_failure 'What is your quest?' \
76+
'Auuuuuuuugh!'
77+
}
78+
79+
@test "$SUITE: --or-die exits when the value has an unescaped metacharacter" {
80+
@go.create_test_go_script '. "$_GO_USE_MODULES" "prompt"' \
81+
'declare response="initial value"' \
82+
"@go.prompt_for_safe_input --or-die 'Quest response' 'response' \\" \
83+
$' "What is your quest?\n"'
84+
85+
run "$TEST_GO_SCRIPT" <<<'To seek the grail;'
86+
assert_failure
87+
88+
local err_msg='^Quest response "To seek the grail;" for '
89+
err_msg+='@go.prompt_for_safe_input contains '
90+
err_msg+='unescaped shell metacharacters or control operators at:$'
91+
92+
assert_lines_match 'What is your quest?' \
93+
"$err_msg" \
94+
" $TEST_GO_SCRIPT:[0-9] main"
95+
}

tests/validation.bats

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ assert_success_on_valid_input() {
9292
assert_failure
9393

9494
local err_msg='^input argument "foo;bar" for @go\.validate_input_or_die '
95-
err_msg+='contains invalid characters at:$'
95+
err_msg+='contains unescaped shell metacharacters or control operators at:$'
9696

9797
assert_lines_match "$err_msg" \
9898
" $TEST_GO_SCRIPT:[0-9] main"
@@ -105,7 +105,7 @@ assert_success_on_valid_input() {
105105
run "$TEST_GO_SCRIPT"
106106
assert_failure
107107
assert_lines_match \
108-
'^input argument "foo;bar" for test_func contains invalid characters at:$' \
108+
'^input argument "foo;bar" for test_func contains unescaped .* at:$' \
109109
" $TEST_GO_SCRIPT:[0-9] main"
110110
}
111111

0 commit comments

Comments
 (0)