hush: disallow "{echo hi; }" (require whitespace)
and "{ echo hi }" (require semicolon or &)
function old new delta
parse_stream 2098 2176 +78
done_command 98 84 -14
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 1/1 up/down: 78/-14) Total: 64 bytes
diff --git a/shell/hush.c b/shell/hush.c
index 61b6a79..8dda988 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -319,6 +319,10 @@
*/
struct redir_struct *redirects; /* I/O redirections */
};
+/* Is there anything in this command at all? */
+#define IS_NULL_CMD(cmd) \
+ (!(cmd)->group && !(cmd)->argv && !(cmd)->redirects)
+
struct pipe {
struct pipe *next;
@@ -341,6 +345,9 @@
PIPE_OR = 3,
PIPE_BG = 4,
} pipe_style;
+/* Is there anything in this pipe at all? */
+#define IS_NULL_PIPE(pi) \
+ ((pi)->num_cmds == 0 IF_HAS_KEYWORDS( && (pi)->res_word == RES_NONE))
/* This holds pointers to the various results of parsing */
struct parse_context {
@@ -3971,8 +3978,10 @@
return pi;
}
-/* Command (member of a pipe) is complete. The only possible error here
- * is out of memory, in which case xmalloc exits. */
+/* Command (member of a pipe) is complete, or we start a new pipe
+ * if ctx->command is NULL.
+ * No errors possible here.
+ */
static int done_command(struct parse_context *ctx)
{
/* The command is really already in the pipe structure, so
@@ -3981,13 +3990,9 @@
struct command *command = ctx->command;
if (command) {
- if (command->group == NULL
- && command->argv == NULL
- && command->redirects == NULL
- ) {
+ if (IS_NULL_CMD(command)) {
debug_printf_parse("done_command: skipping null cmd, num_cmds=%d\n", pi->num_cmds);
- memset(command, 0, sizeof(*command)); /* paranoia */
- return pi->num_cmds;
+ goto clear_and_ret;
}
pi->num_cmds++;
debug_printf_parse("done_command: ++num_cmds=%d\n", pi->num_cmds);
@@ -3999,12 +4004,9 @@
/* Only real trickiness here is that the uncommitted
* command structure is not counted in pi->num_cmds. */
pi->cmds = xrealloc(pi->cmds, sizeof(*pi->cmds) * (pi->num_cmds+1));
- command = &pi->cmds[pi->num_cmds];
+ ctx->command = command = &pi->cmds[pi->num_cmds];
+ clear_and_ret:
memset(command, 0, sizeof(*command));
-
- ctx->command = command;
- /* but ctx->pipe and ctx->list_head remain unchanged */
-
return pi->num_cmds; /* used only for 0/nonzero check */
}
@@ -4024,9 +4026,7 @@
/* Without this check, even just <enter> on command line generates
* tree of three NOPs (!). Which is harmless but annoying.
- * IOW: it is safe to do it unconditionally.
- * RES_NONE case is for "for a in; do ..." (empty IN set)
- * and other cases to work. */
+ * IOW: it is safe to do it unconditionally. */
if (not_null
#if ENABLE_HUSH_IF
|| ctx->ctx_res_w == RES_FI
@@ -4048,7 +4048,7 @@
ctx->pipe->next = new_p;
ctx->pipe = new_p;
/* RES_THEN, RES_DO etc are "sticky" -
- * they remain set for commands inside if/while.
+ * they remain set for pipes inside if/while.
* This is used to control execution.
* RES_FOR and RES_IN are NOT sticky (needed to support
* cases where variable or value happens to match a keyword):
@@ -4304,7 +4304,7 @@
&& ctx->ctx_res_w != RES_IN
# endif
) {
- debug_printf_parse(": checking '%s' for reserved-ness\n", word->data);
+ debug_printf_parse("checking '%s' for reserved-ness\n", word->data);
if (reserved_word(word, ctx)) {
o_reset_to_empty_unquoted(word);
debug_printf_parse("done_word return %d\n",
@@ -4775,6 +4775,14 @@
if (ch == '(') {
endch = ')';
command->grp_type = GRP_SUBSHELL;
+ } else {
+ /* bash does not allow "{echo...", requires whitespace */
+ ch = i_getch(input);
+ if (ch != ' ' && ch != '\t' && ch != '\n') {
+ syntax_error_unexpected_ch(ch);
+ return 1;
+ }
+ nommu_addchr(&ctx->as_string, ch);
}
{
@@ -5352,13 +5360,11 @@
if (end_trigger && end_trigger == ch
&& (heredoc_cnt == 0 || end_trigger != ';')
) {
-//TODO: disallow "{ cmd }" without semicolon
if (heredoc_cnt) {
/* This is technically valid:
* { cat <<HERE; }; echo Ok
* heredoc
* heredoc
- * heredoc
* HERE
* but we don't support this.
* We require heredoc to be in enclosing {}/(),
@@ -5370,6 +5376,15 @@
if (done_word(&dest, &ctx)) {
goto parse_error;
}
+ /* Disallow "{ cmd }" without semicolon or & */
+ //debug_printf_parse("null pi %d\n", IS_NULL_PIPE(ctx.pipe))
+ //debug_printf_parse("null cmd %d\n", IS_NULL_CMD(ctx.command))
+ if (ch == '}'
+ && !(IS_NULL_PIPE(ctx.pipe) && IS_NULL_CMD(ctx.command))
+ ) {
+ syntax_error_unexpected_ch(ch);
+ goto parse_error;
+ }
done_pipe(&ctx, PIPE_SEQ);
dest.o_assignment = MAYBE_ASSIGNMENT;
/* Do we sit outside of any if's, loops or case's? */