aboutsummaryrefslogtreecommitdiff
path: root/shell/hush.c
diff options
context:
space:
mode:
Diffstat (limited to 'shell/hush.c')
-rw-r--r--shell/hush.c200
1 files changed, 147 insertions, 53 deletions
diff --git a/shell/hush.c b/shell/hush.c
index 9d3f06db0..415993e71 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -4888,34 +4888,15 @@ static int parse_dollar(o_string *as_string,
}
#if BB_MMU
-# if BASH_PATTERN_SUBST
-#define encode_string(as_string, dest, input, dquote_end, process_bkslash) \
- encode_string(dest, input, dquote_end, process_bkslash)
-# else
-/* only ${var/pattern/repl} (its pattern part) needs additional mode */
-#define encode_string(as_string, dest, input, dquote_end, process_bkslash) \
+#define encode_string(as_string, dest, input, dquote_end) \
encode_string(dest, input, dquote_end)
-# endif
#define as_string NULL
-
-#else /* !MMU */
-
-# if BASH_PATTERN_SUBST
-/* all parameters are needed, no macro tricks */
-# else
-#define encode_string(as_string, dest, input, dquote_end, process_bkslash) \
- encode_string(as_string, dest, input, dquote_end)
-# endif
#endif
static int encode_string(o_string *as_string,
o_string *dest,
struct in_str *input,
- int dquote_end,
- int process_bkslash)
+ int dquote_end)
{
-#if !BASH_PATTERN_SUBST
- const int process_bkslash = 1;
-#endif
int ch;
int next;
@@ -4938,7 +4919,7 @@ static int encode_string(o_string *as_string,
}
debug_printf_parse("\" ch=%c (%d) escape=%d\n",
ch, ch, !!(dest->o_expflags & EXP_FLAG_ESC_GLOB_CHARS));
- if (process_bkslash && ch == '\\') {
+ if (ch == '\\') {
if (next == EOF) {
/* Testcase: in interactive shell a file with
* echo "unterminated string\<eof>
@@ -5447,7 +5428,7 @@ static struct pipe *parse_stream(char **pstring,
}
if (ctx.is_assignment == NOT_ASSIGNMENT)
ctx.word.o_expflags |= EXP_FLAG_ESC_GLOB_CHARS;
- if (!encode_string(&ctx.as_string, &ctx.word, input, '"', /*process_bkslash:*/ 1))
+ if (!encode_string(&ctx.as_string, &ctx.word, input, '"'))
goto parse_error;
ctx.word.o_expflags &= ~EXP_FLAG_ESC_GLOB_CHARS;
continue; /* get next char */
@@ -5744,16 +5725,8 @@ static int expand_on_ifs(int *ended_with_ifs, o_string *output, int n, const cha
* Returns malloced string.
* As an optimization, we return NULL if expansion is not needed.
*/
-#if !BASH_PATTERN_SUBST
-/* only ${var/pattern/repl} (its pattern part) needs additional mode */
-#define encode_then_expand_string(str, process_bkslash, do_unbackslash) \
- encode_then_expand_string(str)
-#endif
-static char *encode_then_expand_string(const char *str, int process_bkslash, int do_unbackslash)
+static char *encode_then_expand_string(const char *str)
{
-#if !BASH_PATTERN_SUBST
- enum { do_unbackslash = 1 };
-#endif
char *exp_str;
struct in_str input;
o_string dest = NULL_O_STRING;
@@ -5771,14 +5744,135 @@ static char *encode_then_expand_string(const char *str, int process_bkslash, int
* echo $(($a + `echo 1`)) $((1 + $((2)) ))
*/
setup_string_in_str(&input, str);
- encode_string(NULL, &dest, &input, EOF, process_bkslash);
+ encode_string(NULL, &dest, &input, EOF);
//TODO: error check (encode_string returns 0 on error)?
//bb_error_msg("'%s' -> '%s'", str, dest.data);
exp_str = expand_string_to_string(dest.data,
+ EXP_FLAG_ESC_GLOB_CHARS,
+ /*unbackslash:*/ 1
+ );
+ //bb_error_msg("'%s' -> '%s'", dest.data, exp_str);
+ o_free_unsafe(&dest);
+ return exp_str;
+}
+
+#if !BASH_PATTERN_SUBST
+#define encode_then_expand_vararg(str, handle_squotes, do_unbackslash) \
+ encode_then_expand_vararg(str, handle_squotes)
+#endif
+static char *encode_then_expand_vararg(const char *str, int handle_squotes, int do_unbackslash)
+{
+#if !BASH_PATTERN_SUBST
+ const int do_unbackslash = 0;
+#endif
+ char *exp_str;
+ struct in_str input;
+ o_string dest = NULL_O_STRING;
+
+ if (!strchr(str, '$')
+ && !strchr(str, '\\')
+ && !strchr(str, '\'')
+//todo:better code
+ && !strchr(str, '"')
+#if ENABLE_HUSH_TICK
+ && !strchr(str, '`')
+#endif
+ ) {
+ return NULL;
+ }
+
+ /* Expanding ARG in ${var#ARG}, ${var%ARG}, or ${var/ARG/ARG}.
+ * These can contain single- and double-quoted strings,
+ * and treated as if the ARG string is initially unquoted. IOW:
+ * ${var#ARG} and "${var#ARG}" treat ARG the same (ARG can even be
+ * a dquoted string: "${var#"zz"}"), the difference only comes later
+ * (word splitting and globbing of the ${var...} result).
+ */
+
+ setup_string_in_str(&input, str);
+ o_addchr(&dest, '\0');
+ dest.length = 0;
+ exp_str = NULL;
+
+ for (;;) {
+ int ch;
+ int next;
+
+ ch = i_getch(&input);
+ if (ch == EOF) {
+ if (dest.o_expflags) { /* EXP_FLAG_ESC_GLOB_CHARS set? */
+ syntax_error_unterm_ch('"');
+ goto ret; /* error */
+ }
+ break;
+ }
+ debug_printf_parse("%s: ch=%c (%d) escape=%d\n",
+ __func__, ch, ch, !!dest.o_expflags);
+ if (ch == '\'' && handle_squotes && !dest.o_expflags) {
+//quoting version of add_till_single_quote() (try to merge?):
+ for (;;) {
+ ch = i_getch(&input);
+ if (ch == EOF) {
+ syntax_error_unterm_ch('\'');
+ goto ret; /* error */
+ }
+ if (ch == '\'')
+ break;
+ o_addqchr(&dest, ch);
+ }
+ continue;
+ }
+ if (ch == '"') {
+ dest.o_expflags ^= EXP_FLAG_ESC_GLOB_CHARS;
+ continue;
+ }
+ if (ch == '\\') {
+ ch = i_getch(&input);
+ if (ch == EOF) {
+//example? error message? syntax_error_unterm_ch('"');
+ debug_printf_parse("%s: error: \\<eof>\n", __func__);
+ goto ret;
+ }
+ o_addqchr(&dest, ch);
+ continue;
+ }
+ next = '\0';
+ if (ch != '\n') {
+ next = i_peek(&input);
+ }
+ if (ch == '$') {
+ if (!parse_dollar(NULL, &dest, &input, /*quote_mask:*/ 0x80)) {
+ debug_printf_parse("%s: error: parse_dollar returned 0 (error)\n", __func__);
+ goto ret;
+ }
+ continue;
+ }
+#if ENABLE_HUSH_TICK
+ if (ch == '`') {
+ //unsigned pos = dest->length;
+ o_addchr(&dest, SPECIAL_VAR_SYMBOL);
+ o_addchr(&dest, 0x80 | '`');
+ if (!add_till_backquote(&dest, &input,
+ /*in_dquote:*/ dest.o_expflags /* nonzero if EXP_FLAG_ESC_GLOB_CHARS set */
+ )
+ ) {
+ goto ret; /* error */
+ }
+ o_addchr(&dest, SPECIAL_VAR_SYMBOL);
+ //debug_printf_subst("SUBST RES3 '%s'\n", dest->data + pos);
+ continue;
+ }
+#endif
+ o_addQchr(&dest, ch);
+ } /* for (;;) */
+
+ debug_printf_parse("encode: '%s' -> '%s'\n", str, dest.data);
+ exp_str = expand_string_to_string(dest.data,
do_unbackslash ? EXP_FLAG_ESC_GLOB_CHARS : 0,
do_unbackslash
);
- //bb_error_msg("'%s' -> '%s'", dest.data, exp_str);
+ ret:
+ debug_printf_parse("expand: '%s' -> '%s'\n", dest.data, exp_str);
o_free_unsafe(&dest);
return exp_str;
}
@@ -5793,7 +5887,7 @@ static arith_t expand_and_evaluate_arith(const char *arg, const char **errmsg_p)
math_state.lookupvar = get_local_var_value;
math_state.setvar = set_local_var_from_halves;
//math_state.endofname = endofname;
- exp_str = encode_then_expand_string(arg, /*process_bkslash:*/ 1, /*unbackslash:*/ 1);
+ exp_str = encode_then_expand_string(arg);
res = arith(&math_state, exp_str ? exp_str : arg);
free(exp_str);
if (errmsg_p)
@@ -5869,7 +5963,6 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
char *to_be_freed;
char *p;
char *var;
- char first_char;
char exp_op;
char exp_save = exp_save; /* for compiler */
char *exp_saveptr; /* points to expansion operator */
@@ -5883,10 +5976,10 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
var = arg;
exp_saveptr = arg[1] ? strchr(VAR_ENCODED_SUBST_OPS, arg[1]) : NULL;
arg0 = arg[0];
- first_char = arg[0] = arg0 & 0x7f;
+ arg[0] = (arg0 & 0x7f);
exp_op = 0;
- if (first_char == '#' && arg[1] /* ${#...} but not ${#} */
+ if (arg[0] == '#' && arg[1] /* ${#...} but not ${#} */
&& (!exp_saveptr /* and ( not(${#<op_char>...}) */
|| (arg[2] == '\0' && strchr(SPECIAL_VARS_STR, arg[1])) /* or ${#C} "len of $C" ) */
) /* NB: skipping ^^^specvar check mishandles ${#::2} */
@@ -5897,7 +5990,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
} else {
/* Maybe handle parameter expansion */
if (exp_saveptr /* if 2nd char is one of expansion operators */
- && strchr(NUMERIC_SPECVARS_STR, first_char) /* 1st char is special variable */
+ && strchr(NUMERIC_SPECVARS_STR, arg[0]) /* 1st char is special variable */
) {
/* ${?:0}, ${#[:]%0} etc */
exp_saveptr = var + 1;
@@ -5966,6 +6059,9 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
* Word is expanded to produce a glob pattern.
* Then var's value is matched to it and matching part removed.
*/
+//FIXME: ${x#...${...}...}
+//should evaluate inner ${...} even if x is "" and no shrinking of it is possible -
+//inner ${...} may have side effects!
if (val && val[0]) {
char *t;
char *exp_exp_word;
@@ -5974,20 +6070,16 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
if (exp_op == *exp_word) /* ## or %% */
exp_word++;
debug_printf_expand("expand: exp_word:'%s'\n", exp_word);
- /*
- * process_bkslash:1 unbackslash:1 breaks this:
- * a='a\\'; echo ${a%\\\\} # correct output is: a
- * process_bkslash:1 unbackslash:0 breaks this:
- * a='a}'; echo ${a%\}} # correct output is: a
- */
- exp_exp_word = encode_then_expand_string(exp_word, /*process_bkslash:*/ 0, /*unbackslash:*/ 0);
+ exp_exp_word = encode_then_expand_vararg(exp_word, /*handle_squotes:*/ 1, /*unbackslash:*/ 0);
if (exp_exp_word)
exp_word = exp_exp_word;
- debug_printf_expand("expand: exp_exp_word:'%s'\n", exp_word);
- /* HACK ALERT. We depend here on the fact that
+ debug_printf_expand("expand: exp_word:'%s'\n", exp_word);
+ /*
+ * HACK ALERT. We depend here on the fact that
* G.global_argv and results of utoa and get_local_var_value
* are actually in writable memory:
- * scan_and_match momentarily stores NULs there. */
+ * scan_and_match momentarily stores NULs there.
+ */
t = (char*)val;
loc = scan_and_match(t, exp_word, scan_flags);
debug_printf_expand("op:%c str:'%s' pat:'%s' res:'%s'\n", exp_op, t, exp_word, loc);
@@ -6020,7 +6112,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
* (note that a*z _pattern_ is never globbed!)
*/
char *pattern, *repl, *t;
- pattern = encode_then_expand_string(exp_word, /*process_bkslash:*/ 0, /*unbackslash:*/ 0);
+ pattern = encode_then_expand_vararg(exp_word, /*handle_squotes:*/ 1, /*unbackslash:*/ 0);
if (!pattern)
pattern = xstrdup(exp_word);
debug_printf_varexp("pattern:'%s'->'%s'\n", exp_word, pattern);
@@ -6028,7 +6120,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
exp_word = p;
p = strchr(p, SPECIAL_VAR_SYMBOL);
*p = '\0';
- repl = encode_then_expand_string(exp_word, /*process_bkslash:*/ 0, /*unbackslash:*/ 1);
+ repl = encode_then_expand_vararg(exp_word, /*handle_squotes:*/ 1, /*unbackslash:*/ 1);
debug_printf_varexp("repl:'%s'->'%s'\n", exp_word, repl);
/* HACK ALERT. We depend here on the fact that
* G.global_argv and results of utoa and get_local_var_value
@@ -6131,7 +6223,9 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
debug_printf_expand("expand: op:%c (null:%s) test:%i\n", exp_op,
(exp_save == ':') ? "true" : "false", use_word);
if (use_word) {
- to_be_freed = encode_then_expand_string(exp_word, /*process_bkslash:*/ 1, /*unbackslash:*/ 1);
+//FIXME: unquoted ${x:+"b c" d} and ${x:+'b c' d} should expand to two words
+//currently it expands to three.
+ to_be_freed = encode_then_expand_vararg(exp_word, /*handle_squotes:*/ !(arg0 & 0x80), /*unbackslash:*/ 0);
if (to_be_freed)
exp_word = to_be_freed;
if (exp_op == '?') {
@@ -6934,7 +7028,7 @@ static void setup_heredoc(struct redir_struct *redir)
expanded = NULL;
if (!(redir->rd_dup & HEREDOC_QUOTED)) {
- expanded = encode_then_expand_string(heredoc, /*process_bkslash:*/ 1, /*unbackslash:*/ 1);
+ expanded = encode_then_expand_string(heredoc);
if (expanded)
heredoc = expanded;
}