~kevin8t8/mutt

e53ae9edaf0b863eba5461d50f1aabc8c7321c82 — Kevin McCarthy 11 days ago e5a32a6
Escape config vars when expanding hook "command" argument.

The "command" parameter of folder, send, send2, account, reply, and
message hooks is run through mutt_extract_token() twice.  Once when
parsing the hook command initially, and once via mutt_parse_rc_line()
when the actual hook fires.

In theory, Mutt users should be aware of this, and should place all
their command strings with configuration variables in single-quotes,
to delay expansion until execution.

But in reality Mutt has been escaping configuration variables as part
of mutt_extract_token() for many years.

The previous commit turned the escaping off, because it damages
"backup" assignments to a $my_ variable, and I believe is confusing
behavior.

But for hooks, users now expect the escaping behavior (for
double-quoted or unquoted command strings).

Note that mutt_parse_hook() also passed the MUTT_TOKEN_SPACE flag, but
neglected to do so for message-hook.  Since all other "command"
argument hooks were included, and were added to the list over the
years, I believe this was an accidental oversight.
3 files changed, 52 insertions(+), 3 deletions(-)

M hook.c
M init.c
M mutt.h
M hook.c => hook.c +8 -2
@@ 52,7 52,7 @@ int mutt_parse_hook (BUFFER *buf, BUFFER *s, union pointer_long_t udata, BUFFER 
{
  HOOK *ptr;
  BUFFER *command, *pattern;
  int rc = -1, not = 0;
  int rc = -1, not = 0, token_flags = 0;
  regex_t *rx = NULL;
  pattern_t *pat = NULL;
  long data = udata.l;


@@ 75,7 75,13 @@ int mutt_parse_hook (BUFFER *buf, BUFFER *s, union pointer_long_t udata, BUFFER 
    goto cleanup;
  }

  mutt_extract_token (command, s, (data & (MUTT_FOLDERHOOK | MUTT_SENDHOOK | MUTT_SEND2HOOK | MUTT_ACCOUNTHOOK | MUTT_REPLYHOOK)) ?  MUTT_TOKEN_SPACE : 0);
  /* These hook types have a command parameter which is run through
   * mutt_parse_rc_line() a second time upon hook execution. */
  if (data & (MUTT_FOLDERHOOK | MUTT_SENDHOOK | MUTT_SEND2HOOK |
              MUTT_ACCOUNTHOOK | MUTT_REPLYHOOK | MUTT_MESSAGEHOOK))
    token_flags = MUTT_TOKEN_SPACE | MUTT_TOKEN_ESC_VARS;

  mutt_extract_token (command, s, token_flags);

  if (!mutt_buffer_len (command))
  {

M init.c => init.c +43 -1
@@ 72,6 72,7 @@ typedef struct myvar
static myvar_t* MyVars;

static int var_to_string (int idx, BUFFER *val);
static void escape_string_to_buffer (BUFFER *dst, const char *src);

static void myvar_set (const char* var, const char* val);
static const char* myvar_get (const char* var);


@@ 311,7 312,18 @@ int mutt_extract_token (BUFFER *dest, BUFFER *tok, int flags)
          BUFFER *val = mutt_buffer_pool_get ();

          if (var_to_string (idx, val))
            mutt_buffer_addstr (dest, mutt_b2s (val));
          {
            if (flags & MUTT_TOKEN_ESC_VARS)
            {
              BUFFER *escval = mutt_buffer_pool_get ();

              escape_string_to_buffer (escval, mutt_b2s (val));
              mutt_buffer_addstr (dest, mutt_b2s (escval));
              mutt_buffer_pool_release (&escval);
            }
            else
              mutt_buffer_addstr (dest, mutt_b2s (val));
          }
          mutt_buffer_pool_release (&val);
        }
        FREE (&var);


@@ 1888,6 1900,36 @@ static void mutt_restore_default (struct option_t *p)
    mutt_set_current_menu_redraw_full ();
}

static void escape_string_to_buffer (BUFFER *dst, const char *src)
{
  mutt_buffer_clear (dst);

  if (!src || !*src)
    return;

  for (; *src; src++)
  {
    switch (*src)
    {
      case '\n':
        mutt_buffer_addstr (dst, "\\n");
        break;
      case '\r':
        mutt_buffer_addstr (dst, "\\r");
        break;
      case '\t':
        mutt_buffer_addstr (dst, "\\t");
        break;
      case '\\':
      case '"':
        mutt_buffer_addch (dst, '\\');
        /* fall through */
      default:
        mutt_buffer_addch (dst, *src);
    }
  }
}

static size_t escape_string (char *dst, size_t len, const char* src)
{
  char* p = dst;

M mutt.h => mutt.h +1 -0
@@ 126,6 126,7 @@ struct timespec
#define MUTT_TOKEN_PATTERN    (1<<4)  /* !)|~ are terms (for patterns) */
#define MUTT_TOKEN_COMMENT    (1<<5)  /* don't reap comments */
#define MUTT_TOKEN_SEMICOLON  (1<<6)  /* don't treat ; as special */
#define MUTT_TOKEN_ESC_VARS   (1<<7)  /* escape configuration variables */

typedef struct
{