hush: fix a bug with backslashes improperly handled in unquoted variables.
with previous patch: function old new delta parse_stream 1638 1758 +120 expand_on_ifs 97 174 +77 free_pipe 206 237 +31 setup_redirect 217 220 +3 setup_redirects 143 144 +1 done_word 698 688 -10 free_strings 38 - -38 expand_variables 1451 1403 -48 ------------------------------------------------------------------------------ (add/remove: 0/1 grow/shrink: 5/2 up/down: 232/-96) Total: 136 bytes
This commit is contained in:
parent
ab876cd107
commit
55789c6646
166
shell/hush.c
166
shell/hush.c
@ -227,8 +227,8 @@ typedef enum {
|
|||||||
REDIRECT_IO = 5
|
REDIRECT_IO = 5
|
||||||
} redir_type;
|
} redir_type;
|
||||||
|
|
||||||
/* The descrip member of this structure is only used to make debugging
|
/* The descrip member of this structure is only used to make
|
||||||
* output pretty */
|
* debugging output pretty */
|
||||||
static const struct {
|
static const struct {
|
||||||
int mode;
|
int mode;
|
||||||
signed char default_fd;
|
signed char default_fd;
|
||||||
@ -283,8 +283,8 @@ struct p_context {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct redir_struct {
|
struct redir_struct {
|
||||||
struct redir_struct *next; /* pointer to the next redirect in the list */
|
struct redir_struct *next;
|
||||||
redir_type type; /* type of redirection */
|
smallint /*redir_type*/ rd_type;
|
||||||
int fd; /* file descriptor being redirected */
|
int fd; /* file descriptor being redirected */
|
||||||
int dup; /* -1, or file descriptor being duplicated */
|
int dup; /* -1, or file descriptor being duplicated */
|
||||||
char *rd_filename; /* filename */
|
char *rd_filename; /* filename */
|
||||||
@ -292,10 +292,10 @@ struct redir_struct {
|
|||||||
|
|
||||||
struct child_prog {
|
struct child_prog {
|
||||||
pid_t pid; /* 0 if exited */
|
pid_t pid; /* 0 if exited */
|
||||||
|
smallint is_stopped; /* is the program currently running? */
|
||||||
|
smallint subshell; /* flag, non-zero if group must be forked */
|
||||||
char **argv; /* program name and arguments */
|
char **argv; /* program name and arguments */
|
||||||
struct pipe *group; /* if non-NULL, first in group or subshell */
|
struct pipe *group; /* if non-NULL, first in group or subshell */
|
||||||
smallint subshell; /* flag, non-zero if group must be forked */
|
|
||||||
smallint is_stopped; /* is the program currently running? */
|
|
||||||
struct redir_struct *redirects; /* I/O redirections */
|
struct redir_struct *redirects; /* I/O redirections */
|
||||||
struct pipe *family; /* pointer back to the child's parent pipe */
|
struct pipe *family; /* pointer back to the child's parent pipe */
|
||||||
};
|
};
|
||||||
@ -346,7 +346,13 @@ typedef struct {
|
|||||||
smallint o_glob;
|
smallint o_glob;
|
||||||
smallint nonnull;
|
smallint nonnull;
|
||||||
smallint has_empty_slot;
|
smallint has_empty_slot;
|
||||||
|
smallint o_assignment; /* 0:maybe, 1:yes, 2:no */
|
||||||
} o_string;
|
} o_string;
|
||||||
|
enum {
|
||||||
|
MAYBE_ASSIGNMENT = 0,
|
||||||
|
DEFINITELY_ASSIGNMENT = 1,
|
||||||
|
NOT_ASSIGNMENT = 2,
|
||||||
|
};
|
||||||
/* Used for initialization: o_string foo = NULL_O_STRING; */
|
/* Used for initialization: o_string foo = NULL_O_STRING; */
|
||||||
#define NULL_O_STRING { NULL }
|
#define NULL_O_STRING { NULL }
|
||||||
|
|
||||||
@ -588,6 +594,19 @@ static int is_assignment(const char *s)
|
|||||||
return *s == '=';
|
return *s == '=';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Replace each \x with x in place, return ptr past NUL. */
|
||||||
|
static char *unbackslash(char *src)
|
||||||
|
{
|
||||||
|
char *dst = src;
|
||||||
|
while (1) {
|
||||||
|
if (*src == '\\')
|
||||||
|
src++;
|
||||||
|
if ((*dst++ = *src++) == '\0')
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return dst;
|
||||||
|
}
|
||||||
|
|
||||||
static char **add_malloced_strings_to_strings(char **strings, char **add)
|
static char **add_malloced_strings_to_strings(char **strings, char **add)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
@ -906,6 +925,19 @@ static void o_addstr(o_string *o, const char *str, int len)
|
|||||||
o->data[o->length] = '\0';
|
o->data[o->length] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void o_addstr_duplicate_backslash(o_string *o, const char *str, int len)
|
||||||
|
{
|
||||||
|
while (len) {
|
||||||
|
o_addchr(o, *str);
|
||||||
|
if (*str++ == '\\'
|
||||||
|
&& (*str != '*' && *str != '?' && *str != '[')
|
||||||
|
) {
|
||||||
|
o_addchr(o, '\\');
|
||||||
|
}
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* My analysis of quoting semantics tells me that state information
|
/* My analysis of quoting semantics tells me that state information
|
||||||
* is associated with a destination, not a source.
|
* is associated with a destination, not a source.
|
||||||
*/
|
*/
|
||||||
@ -1045,18 +1077,7 @@ static int o_get_last_ptr(o_string *o, int n)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* o_glob performs globbing on last list[], saving each result
|
/* o_glob performs globbing on last list[], saving each result
|
||||||
* as a new list[]. unbackslash() is just a helper */
|
* as a new list[]. */
|
||||||
static char *unbackslash(char *src)
|
|
||||||
{
|
|
||||||
char *dst = src;
|
|
||||||
while (1) {
|
|
||||||
if (*src == '\\')
|
|
||||||
src++;
|
|
||||||
if ((*dst++ = *src++) == '\0')
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return dst;
|
|
||||||
}
|
|
||||||
static int o_glob(o_string *o, int n)
|
static int o_glob(o_string *o, int n)
|
||||||
{
|
{
|
||||||
glob_t globdata;
|
glob_t globdata;
|
||||||
@ -1310,7 +1331,8 @@ static int setup_redirects(struct child_prog *prog, int squirrel[])
|
|||||||
}
|
}
|
||||||
if (redir->dup == -1) {
|
if (redir->dup == -1) {
|
||||||
char *p;
|
char *p;
|
||||||
mode = redir_table[redir->type].mode;
|
mode = redir_table[redir->rd_type].mode;
|
||||||
|
//TODO: check redir to names like '\\'
|
||||||
p = expand_string_to_string(redir->rd_filename);
|
p = expand_string_to_string(redir->rd_filename);
|
||||||
openfd = open_or_warn(p, mode);
|
openfd = open_or_warn(p, mode);
|
||||||
free(p);
|
free(p);
|
||||||
@ -1765,6 +1787,7 @@ static int run_pipe(struct pipe *pi)
|
|||||||
for (i = 0; is_assignment(argv[i]); i++) {
|
for (i = 0; is_assignment(argv[i]); i++) {
|
||||||
p = expand_string_to_string(argv[i]);
|
p = expand_string_to_string(argv[i]);
|
||||||
putenv(p);
|
putenv(p);
|
||||||
|
//FIXME: do we leak p?!
|
||||||
}
|
}
|
||||||
for (x = bltins; x != &bltins[ARRAY_SIZE(bltins)]; x++) {
|
for (x = bltins; x != &bltins[ARRAY_SIZE(bltins)]; x++) {
|
||||||
if (strcmp(argv[i], x->cmd) == 0) {
|
if (strcmp(argv[i], x->cmd) == 0) {
|
||||||
@ -2300,7 +2323,10 @@ static int expand_on_ifs(o_string *output, int n, const char *str)
|
|||||||
while (1) {
|
while (1) {
|
||||||
int word_len = strcspn(str, ifs);
|
int word_len = strcspn(str, ifs);
|
||||||
if (word_len) {
|
if (word_len) {
|
||||||
o_addstr(output, str, word_len);
|
if (output->o_quote || !output->o_glob)
|
||||||
|
o_addQstr(output, str, word_len);
|
||||||
|
else /* protect backslashes against globbing up :) */
|
||||||
|
o_addstr_duplicate_backslash(output, str, word_len);
|
||||||
str += word_len;
|
str += word_len;
|
||||||
}
|
}
|
||||||
if (!*str) /* EOL - do not finalize word */
|
if (!*str) /* EOL - do not finalize word */
|
||||||
@ -2324,7 +2350,8 @@ static int expand_on_ifs(o_string *output, int n, const char *str)
|
|||||||
static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask)
|
static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask)
|
||||||
{
|
{
|
||||||
/* or_mask is either 0 (normal case) or 0x80
|
/* or_mask is either 0 (normal case) or 0x80
|
||||||
* (expansion of right-hand side of assignment == 1-element expand) */
|
* (expansion of right-hand side of assignment == 1-element expand.
|
||||||
|
* It will also do no globbing, and thus we must not backslash-quote!) */
|
||||||
|
|
||||||
char first_ch, ored_ch;
|
char first_ch, ored_ch;
|
||||||
int i;
|
int i;
|
||||||
@ -2372,6 +2399,7 @@ static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask)
|
|||||||
break;
|
break;
|
||||||
if (!(first_ch & 0x80)) { /* unquoted $* or $@ */
|
if (!(first_ch & 0x80)) { /* unquoted $* or $@ */
|
||||||
while (global_argv[i]) {
|
while (global_argv[i]) {
|
||||||
|
//see expand_on_ifs below - same??
|
||||||
n = expand_on_ifs(output, n, global_argv[i]);
|
n = expand_on_ifs(output, n, global_argv[i]);
|
||||||
debug_printf_expand("expand_vars_to_list: argv %d (last %d)\n", i, global_argc-1);
|
debug_printf_expand("expand_vars_to_list: argv %d (last %d)\n", i, global_argc-1);
|
||||||
if (global_argv[i++][0] && global_argv[i]) {
|
if (global_argv[i++][0] && global_argv[i]) {
|
||||||
@ -2429,9 +2457,9 @@ static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask)
|
|||||||
arg[0] = first_ch & 0x7f;
|
arg[0] = first_ch & 0x7f;
|
||||||
if (isdigit(arg[0])) {
|
if (isdigit(arg[0])) {
|
||||||
i = xatoi_u(arg);
|
i = xatoi_u(arg);
|
||||||
val = NULL;
|
|
||||||
if (i < global_argc)
|
if (i < global_argc)
|
||||||
val = global_argv[i];
|
val = global_argv[i];
|
||||||
|
/* else val remains NULL: $N with too big N */
|
||||||
} else
|
} else
|
||||||
val = lookup_param(arg);
|
val = lookup_param(arg);
|
||||||
arg[0] = first_ch;
|
arg[0] = first_ch;
|
||||||
@ -2442,11 +2470,16 @@ static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask)
|
|||||||
if (!(first_ch & 0x80)) { /* unquoted $VAR */
|
if (!(first_ch & 0x80)) { /* unquoted $VAR */
|
||||||
debug_printf_expand("unquoted '%s', output->o_quote:%d\n", val, output->o_quote);
|
debug_printf_expand("unquoted '%s', output->o_quote:%d\n", val, output->o_quote);
|
||||||
if (val) {
|
if (val) {
|
||||||
|
/* unquoted var's contents should be globbed, so don't quote */
|
||||||
|
smallint sv = output->o_quote;
|
||||||
|
output->o_quote = 0;
|
||||||
n = expand_on_ifs(output, n, val);
|
n = expand_on_ifs(output, n, val);
|
||||||
val = NULL;
|
val = NULL;
|
||||||
|
output->o_quote = sv;
|
||||||
}
|
}
|
||||||
} else /* quoted $VAR, val will be appended below */
|
} else { /* quoted $VAR, val will be appended below */
|
||||||
debug_printf_expand("quoted '%s', output->o_quote:%d\n", val, output->o_quote);
|
debug_printf_expand("quoted '%s', output->o_quote:%d\n", val, output->o_quote);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (val) {
|
if (val) {
|
||||||
o_addQstr(output, val, strlen(val)); ///maybe q?
|
o_addQstr(output, val, strlen(val)); ///maybe q?
|
||||||
@ -2485,6 +2518,7 @@ static char **expand_variables(char **argv, int or_mask)
|
|||||||
|
|
||||||
if (or_mask & 0x100) {
|
if (or_mask & 0x100) {
|
||||||
output.o_quote = 1;
|
output.o_quote = 1;
|
||||||
|
/* why? */
|
||||||
output.o_glob = 1;
|
output.o_glob = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2687,7 +2721,7 @@ static int setup_redirect(struct p_context *ctx, int fd, redir_type style,
|
|||||||
child->redirects = redir;
|
child->redirects = redir;
|
||||||
}
|
}
|
||||||
|
|
||||||
redir->type = style;
|
redir->rd_type = style;
|
||||||
redir->fd = (fd == -1) ? redir_table[style].default_fd : fd;
|
redir->fd = (fd == -1) ? redir_table[style].default_fd : fd;
|
||||||
|
|
||||||
debug_printf("Redirect type %d%s\n", redir->fd, redir_table[style].descrip);
|
debug_printf("Redirect type %d%s\n", redir->fd, redir_table[style].descrip);
|
||||||
@ -2858,6 +2892,14 @@ static int done_word(o_string *word, struct p_context *ctx)
|
|||||||
{
|
{
|
||||||
struct child_prog *child = ctx->child;
|
struct child_prog *child = ctx->child;
|
||||||
|
|
||||||
|
/* If this word wasn't an assignment, next ones definitely
|
||||||
|
* can't be assignments. Even if they look like ones. */
|
||||||
|
if (word->o_assignment != DEFINITELY_ASSIGNMENT) {
|
||||||
|
word->o_assignment = NOT_ASSIGNMENT;
|
||||||
|
} else {
|
||||||
|
word->o_assignment = MAYBE_ASSIGNMENT;
|
||||||
|
}
|
||||||
|
|
||||||
debug_printf_parse("done_word entered: '%s' %p\n", word->data, child);
|
debug_printf_parse("done_word entered: '%s' %p\n", word->data, child);
|
||||||
if (word->length == 0 && word->nonnull == 0) {
|
if (word->length == 0 && word->nonnull == 0) {
|
||||||
debug_printf_parse("done_word return 0: true null, ignored\n");
|
debug_printf_parse("done_word return 0: true null, ignored\n");
|
||||||
@ -2867,6 +2909,7 @@ static int done_word(o_string *word, struct p_context *ctx)
|
|||||||
/* We do not glob in e.g. >*.tmp case. bash seems to glob here
|
/* We do not glob in e.g. >*.tmp case. bash seems to glob here
|
||||||
* only if run as "bash", not "sh" */
|
* only if run as "bash", not "sh" */
|
||||||
ctx->pending_redirect->rd_filename = xstrdup(word->data);
|
ctx->pending_redirect->rd_filename = xstrdup(word->data);
|
||||||
|
word->o_assignment = NOT_ASSIGNMENT;
|
||||||
debug_printf("word stored in rd_filename: '%s'\n", word->data);
|
debug_printf("word stored in rd_filename: '%s'\n", word->data);
|
||||||
} else {
|
} else {
|
||||||
if (child->group) { /* TODO: example how to trigger? */
|
if (child->group) { /* TODO: example how to trigger? */
|
||||||
@ -2878,15 +2921,18 @@ static int done_word(o_string *word, struct p_context *ctx)
|
|||||||
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)) {
|
if (reserved_word(word, ctx)) {
|
||||||
o_reset(word);
|
o_reset(word);
|
||||||
|
word->o_assignment = NOT_ASSIGNMENT;
|
||||||
debug_printf_parse("done_word return %d\n", (ctx->res_w == RES_SNTX));
|
debug_printf_parse("done_word return %d\n", (ctx->res_w == RES_SNTX));
|
||||||
return (ctx->res_w == RES_SNTX);
|
return (ctx->res_w == RES_SNTX);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (word->nonnull
|
if (word->nonnull /* we saw "xx" or 'xx' */
|
||||||
/* && word->data[0] != */
|
/* optimization: and if it's ("" or '') or ($v... or `cmd`...): */
|
||||||
|
&& (word->data[0] == '\0' || word->data[0] == SPECIAL_VAR_SYMBOL)
|
||||||
|
/* (otherwise it's "abc".... and is already safe) */
|
||||||
) {
|
) {
|
||||||
/* Insert "empty variable" reference, this makes e.g. "", '',
|
/* Insert "empty variable" reference, this makes
|
||||||
* $empty"" etc to not disappear */
|
* e.g. "", $empty"" etc to not disappear */
|
||||||
o_addchr(word, SPECIAL_VAR_SYMBOL);
|
o_addchr(word, SPECIAL_VAR_SYMBOL);
|
||||||
o_addchr(word, SPECIAL_VAR_SYMBOL);
|
o_addchr(word, SPECIAL_VAR_SYMBOL);
|
||||||
}
|
}
|
||||||
@ -3107,14 +3153,14 @@ static int process_command_subs(o_string *dest,
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
while (eol_cnt) {
|
while (eol_cnt) {
|
||||||
o_addQchr(dest, '\n');
|
o_addchr(dest, '\n');
|
||||||
eol_cnt--;
|
eol_cnt--;
|
||||||
}
|
}
|
||||||
/* Even unquoted `echo '\'` results in two backslashes
|
// /* Even unquoted `echo '\'` results in two backslashes
|
||||||
* (which are converted into one by globbing later) */
|
// * (which are converted into one by globbing later) */
|
||||||
if (!dest->o_quote && ch == '\\') {
|
// if (!dest->o_quote && ch == '\\') {
|
||||||
o_addchr(dest, ch);
|
// o_addchr(dest, ch);
|
||||||
}
|
// }
|
||||||
o_addQchr(dest, ch);
|
o_addQchr(dest, ch);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3364,13 +3410,19 @@ static int handle_dollar(o_string *dest, struct in_str *input)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Return code is 0 for normal exit, 1 for syntax error */
|
/* Scan input, call done_word() whenever full IFS delemited word was seen.
|
||||||
|
* call done_pipe if '\n' was seen (and end_trigger != NULL)
|
||||||
|
* Return if (non-quoted) char in end_trigger was seen; or on parse error. */
|
||||||
|
/* Return code is 0 if end_trigger char is met,
|
||||||
|
* -1 on EOF (but if end_trigger == NULL then return 0)
|
||||||
|
* 1 for syntax error */
|
||||||
static int parse_stream(o_string *dest, struct p_context *ctx,
|
static int parse_stream(o_string *dest, struct p_context *ctx,
|
||||||
struct in_str *input, const char *end_trigger)
|
struct in_str *input, const char *end_trigger)
|
||||||
{
|
{
|
||||||
int ch, m;
|
int ch, m;
|
||||||
int redir_fd;
|
int redir_fd;
|
||||||
redir_type redir_style;
|
redir_type redir_style;
|
||||||
|
int shadow_quote = dest->o_quote;
|
||||||
int next;
|
int next;
|
||||||
|
|
||||||
/* Only double-quote state is handled in the state variable dest->o_quote.
|
/* Only double-quote state is handled in the state variable dest->o_quote.
|
||||||
@ -3385,13 +3437,14 @@ static int parse_stream(o_string *dest, struct p_context *ctx,
|
|||||||
ch = i_getch(input);
|
ch = i_getch(input);
|
||||||
if (ch != EOF) {
|
if (ch != EOF) {
|
||||||
m = charmap[ch];
|
m = charmap[ch];
|
||||||
if (ch != '\n')
|
if (ch != '\n') {
|
||||||
next = i_peek(input);
|
next = i_peek(input);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
debug_printf_parse(": ch=%c (%d) m=%d quote=%d\n",
|
debug_printf_parse(": ch=%c (%d) m=%d quote=%d\n",
|
||||||
ch, ch, m, dest->o_quote);
|
ch, ch, m, dest->o_quote);
|
||||||
if (m == CHAR_ORDINARY
|
if (m == CHAR_ORDINARY
|
||||||
|| (m != CHAR_SPECIAL && dest->o_quote)
|
|| (m != CHAR_SPECIAL && shadow_quote)
|
||||||
) {
|
) {
|
||||||
if (ch == EOF) {
|
if (ch == EOF) {
|
||||||
syntax("unterminated \"");
|
syntax("unterminated \"");
|
||||||
@ -3399,6 +3452,12 @@ static int parse_stream(o_string *dest, struct p_context *ctx,
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
o_addQchr(dest, ch);
|
o_addQchr(dest, ch);
|
||||||
|
if (dest->o_assignment == MAYBE_ASSIGNMENT
|
||||||
|
&& ch == '='
|
||||||
|
&& is_assignment(dest->data)
|
||||||
|
) {
|
||||||
|
dest->o_assignment = DEFINITELY_ASSIGNMENT;
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (m == CHAR_IFS) {
|
if (m == CHAR_IFS) {
|
||||||
@ -3416,11 +3475,12 @@ static int parse_stream(o_string *dest, struct p_context *ctx,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (end_trigger) {
|
if (end_trigger) {
|
||||||
if (!dest->o_quote && strchr(end_trigger, ch)) {
|
if (!shadow_quote && strchr(end_trigger, ch)) {
|
||||||
/* Special case: (...word) makes last word terminate,
|
/* Special case: (...word) makes last word terminate,
|
||||||
* as if ';' is seen */
|
* as if ';' is seen */
|
||||||
if (ch == ')') {
|
if (ch == ')') {
|
||||||
done_word(dest, ctx);
|
done_word(dest, ctx);
|
||||||
|
//err chk?
|
||||||
done_pipe(ctx, PIPE_SEQ);
|
done_pipe(ctx, PIPE_SEQ);
|
||||||
}
|
}
|
||||||
if (ctx->res_w == RES_NONE) {
|
if (ctx->res_w == RES_NONE) {
|
||||||
@ -3431,9 +3491,16 @@ static int parse_stream(o_string *dest, struct p_context *ctx,
|
|||||||
}
|
}
|
||||||
if (m == CHAR_IFS)
|
if (m == CHAR_IFS)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
if (dest->o_assignment == MAYBE_ASSIGNMENT) {
|
||||||
|
/* ch is a special char and thus this word
|
||||||
|
* cannot be an assignment: */
|
||||||
|
dest->o_assignment = NOT_ASSIGNMENT;
|
||||||
|
}
|
||||||
|
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
case '#':
|
case '#':
|
||||||
if (dest->length == 0 && !dest->o_quote) {
|
if (dest->length == 0 && !shadow_quote) {
|
||||||
while (1) {
|
while (1) {
|
||||||
ch = i_peek(input);
|
ch = i_peek(input);
|
||||||
if (ch == EOF || ch == '\n')
|
if (ch == EOF || ch == '\n')
|
||||||
@ -3459,7 +3526,7 @@ static int parse_stream(o_string *dest, struct p_context *ctx,
|
|||||||
* an ! appearing in double quotes is escaped using
|
* an ! appearing in double quotes is escaped using
|
||||||
* a backslash. The backslash preceding the ! is not removed."
|
* a backslash. The backslash preceding the ! is not removed."
|
||||||
*/
|
*/
|
||||||
if (dest->o_quote) {
|
if (shadow_quote) { //NOT SURE dest->o_quote) {
|
||||||
if (strchr("$`\"\\", next) != NULL) {
|
if (strchr("$`\"\\", next) != NULL) {
|
||||||
o_addqchr(dest, i_getch(input));
|
o_addqchr(dest, i_getch(input));
|
||||||
} else {
|
} else {
|
||||||
@ -3487,18 +3554,23 @@ static int parse_stream(o_string *dest, struct p_context *ctx,
|
|||||||
}
|
}
|
||||||
if (ch == '\'')
|
if (ch == '\'')
|
||||||
break;
|
break;
|
||||||
o_addqchr(dest, ch);
|
if (dest->o_assignment == NOT_ASSIGNMENT)
|
||||||
|
o_addqchr(dest, ch);
|
||||||
|
else
|
||||||
|
o_addchr(dest, ch);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case '"':
|
case '"':
|
||||||
dest->nonnull = 1;
|
dest->nonnull = 1;
|
||||||
dest->o_quote ^= 1; /* invert */
|
shadow_quote ^= 1; /* invert */
|
||||||
|
if (dest->o_assignment == NOT_ASSIGNMENT)
|
||||||
|
dest->o_quote ^= 1;
|
||||||
break;
|
break;
|
||||||
#if ENABLE_HUSH_TICK
|
#if ENABLE_HUSH_TICK
|
||||||
case '`': {
|
case '`': {
|
||||||
//int pos = dest->length;
|
//int pos = dest->length;
|
||||||
o_addchr(dest, SPECIAL_VAR_SYMBOL);
|
o_addchr(dest, SPECIAL_VAR_SYMBOL);
|
||||||
o_addchr(dest, dest->o_quote ? 0x80 | '`' : '`');
|
o_addchr(dest, shadow_quote /*or dest->o_quote??*/ ? 0x80 | '`' : '`');
|
||||||
add_till_backquote(dest, input);
|
add_till_backquote(dest, input);
|
||||||
o_addchr(dest, SPECIAL_VAR_SYMBOL);
|
o_addchr(dest, SPECIAL_VAR_SYMBOL);
|
||||||
//debug_printf_subst("SUBST RES3 '%s'\n", dest->data + pos);
|
//debug_printf_subst("SUBST RES3 '%s'\n", dest->data + pos);
|
||||||
@ -3585,14 +3657,6 @@ static int parse_stream(o_string *dest, struct p_context *ctx,
|
|||||||
bb_error_msg_and_die("BUG: unexpected %c\n", ch);
|
bb_error_msg_and_die("BUG: unexpected %c\n", ch);
|
||||||
}
|
}
|
||||||
} /* while (1) */
|
} /* while (1) */
|
||||||
/* Complain if quote? No, maybe we just finished a command substitution
|
|
||||||
* that was quoted. Example:
|
|
||||||
* $ echo "`cat foo` plus more"
|
|
||||||
* and we just got the EOF generated by the subshell that ran "cat foo"
|
|
||||||
* The only real complaint is if we got an EOF when end_trigger != NULL,
|
|
||||||
* that is, we were really supposed to get end_trigger, and never got
|
|
||||||
* one before the EOF. Can't use the standard "syntax error" return code,
|
|
||||||
* so that parse_stream_outer can distinguish the EOF and exit smoothly. */
|
|
||||||
debug_printf_parse("parse_stream return %d\n", -(end_trigger != NULL));
|
debug_printf_parse("parse_stream return %d\n", -(end_trigger != NULL));
|
||||||
if (end_trigger)
|
if (end_trigger)
|
||||||
return -1;
|
return -1;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user