Coverage Report

Created: 2023-09-25 07:17

/src/neomutt/commands.c
Line
Count
Source (jump to first uncovered line)
1
/**
2
 * @file
3
 * Functions to parse commands in a config file
4
 *
5
 * @authors
6
 * Copyright (C) 1996-2002,2007,2010,2012-2013,2016 Michael R. Elkins <me@mutt.org>
7
 * Copyright (C) 2004 g10 Code GmbH
8
 * Copyright (C) 2020 R Primus <rprimus@gmail.com>
9
 *
10
 * @copyright
11
 * This program is free software: you can redistribute it and/or modify it under
12
 * the terms of the GNU General Public License as published by the Free Software
13
 * Foundation, either version 2 of the License, or (at your option) any later
14
 * version.
15
 *
16
 * This program is distributed in the hope that it will be useful, but WITHOUT
17
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
19
 * details.
20
 *
21
 * You should have received a copy of the GNU General Public License along with
22
 * this program.  If not, see <http://www.gnu.org/licenses/>.
23
 */
24
25
/**
26
 * @page neo_commands Functions to parse commands in a config file
27
 *
28
 * Functions to parse commands in a config file
29
 */
30
31
#include "config.h"
32
#include <errno.h>
33
#include <inttypes.h> // IWYU pragma: keep
34
#include <limits.h>
35
#include <stdbool.h>
36
#include <stdio.h>
37
#include <string.h>
38
#include <unistd.h>
39
#include "mutt/lib.h"
40
#include "address/lib.h"
41
#include "config/lib.h"
42
#include "email/lib.h"
43
#include "core/lib.h"
44
#include "alias/lib.h"
45
#include "gui/lib.h"
46
#include "mutt.h"
47
#include "commands.h"
48
#include "attach/lib.h"
49
#include "color/lib.h"
50
#include "imap/lib.h"
51
#include "key/lib.h"
52
#include "menu/lib.h"
53
#include "pager/lib.h"
54
#include "parse/lib.h"
55
#include "store/lib.h"
56
#include "alternates.h"
57
#include "globals.h" // IWYU pragma: keep
58
#include "muttlib.h"
59
#include "mx.h"
60
#include "score.h"
61
#include "version.h"
62
#ifdef USE_INOTIFY
63
#include "monitor.h"
64
#endif
65
#ifdef ENABLE_NLS
66
#include <libintl.h>
67
#endif
68
69
/// LIFO designed to contain the list of config files that have been sourced and
70
/// avoid cyclic sourcing.
71
static struct ListHead MuttrcStack = STAILQ_HEAD_INITIALIZER(MuttrcStack);
72
73
0
#define MAX_ERRS 128
74
75
/**
76
 * enum TriBool - Tri-state boolean
77
 */
78
enum TriBool
79
{
80
  TB_UNSET = -1, ///< Value hasn't been set
81
  TB_FALSE,      ///< Value is false
82
  TB_TRUE,       ///< Value is true
83
};
84
85
/**
86
 * enum GroupState - Type of email address group
87
 */
88
enum GroupState
89
{
90
  GS_NONE, ///< Group is missing an argument
91
  GS_RX,   ///< Entry is a regular expression
92
  GS_ADDR, ///< Entry is an address
93
};
94
95
/**
96
 * is_function - Is the argument a neomutt function?
97
 * @param name  Command name to be searched for
98
 * @retval true  Function found
99
 * @retval false Function not found
100
 */
101
static bool is_function(const char *name)
102
0
{
103
0
  for (size_t i = 0; MenuNames[i].name; i++)
104
0
  {
105
0
    const struct MenuFuncOp *fns = km_get_table(MenuNames[i].value);
106
0
    if (!fns)
107
0
      continue;
108
109
0
    for (int j = 0; fns[j].name; j++)
110
0
      if (mutt_str_equal(name, fns[j].name))
111
0
        return true;
112
0
  }
113
0
  return false;
114
0
}
115
116
/**
117
 * parse_grouplist - Parse a group context
118
 * @param gl   GroupList to add to
119
 * @param buf  Temporary Buffer space
120
 * @param s    Buffer containing string to be parsed
121
 * @param err  Buffer for error messages
122
 * @retval  0 Success
123
 * @retval -1 Error
124
 */
125
int parse_grouplist(struct GroupList *gl, struct Buffer *buf, struct Buffer *s,
126
                    struct Buffer *err)
127
0
{
128
0
  while (mutt_istr_equal(buf->data, "-group"))
129
0
  {
130
0
    if (!MoreArgs(s))
131
0
    {
132
0
      buf_strcpy(err, _("-group: no group name"));
133
0
      return -1;
134
0
    }
135
136
0
    parse_extract_token(buf, s, TOKEN_NO_FLAGS);
137
138
0
    mutt_grouplist_add(gl, mutt_pattern_group(buf->data));
139
140
0
    if (!MoreArgs(s))
141
0
    {
142
0
      buf_strcpy(err, _("out of arguments"));
143
0
      return -1;
144
0
    }
145
146
0
    parse_extract_token(buf, s, TOKEN_NO_FLAGS);
147
0
  }
148
149
0
  return 0;
150
0
}
151
152
/**
153
 * parse_rc_line_cwd - Parse and run a muttrc line in a relative directory
154
 * @param line   Line to be parsed
155
 * @param cwd    File relative where to run the line
156
 * @param err    Where to write error messages
157
 * @retval #CommandResult Result e.g. #MUTT_CMD_SUCCESS
158
 */
159
enum CommandResult parse_rc_line_cwd(const char *line, char *cwd, struct Buffer *err)
160
0
{
161
0
  mutt_list_insert_head(&MuttrcStack, mutt_str_dup(NONULL(cwd)));
162
163
0
  enum CommandResult ret = parse_rc_line(line, err);
164
165
0
  struct ListNode *np = STAILQ_FIRST(&MuttrcStack);
166
0
  STAILQ_REMOVE_HEAD(&MuttrcStack, entries);
167
0
  FREE(&np->data);
168
0
  FREE(&np);
169
170
0
  return ret;
171
0
}
172
173
/**
174
 * mutt_get_sourced_cwd - Get the current file path that is being parsed
175
 * @retval ptr File path that is being parsed or cwd at runtime
176
 *
177
 * @note Caller is responsible for freeing returned string
178
 */
179
char *mutt_get_sourced_cwd(void)
180
0
{
181
0
  struct ListNode *np = STAILQ_FIRST(&MuttrcStack);
182
0
  if (np && np->data)
183
0
    return mutt_str_dup(np->data);
184
185
  // stack is empty, return our own dummy file relative to cwd
186
0
  struct Buffer *cwd = buf_pool_get();
187
0
  mutt_path_getcwd(cwd);
188
0
  buf_addstr(cwd, "/dummy.rc");
189
0
  char *ret = buf_strdup(cwd);
190
0
  buf_pool_release(&cwd);
191
0
  return ret;
192
0
}
193
194
/**
195
 * source_rc - Read an initialization file
196
 * @param rcfile_path Path to initialization file
197
 * @param err         Buffer for error messages
198
 * @retval <0 NeoMutt should pause to let the user know
199
 */
200
int source_rc(const char *rcfile_path, struct Buffer *err)
201
0
{
202
0
  int lineno = 0, rc = 0, warnings = 0;
203
0
  enum CommandResult line_rc;
204
0
  struct Buffer *token = NULL, *linebuf = NULL;
205
0
  char *line = NULL;
206
0
  char *currentline = NULL;
207
0
  char rcfile[PATH_MAX] = { 0 };
208
0
  size_t linelen = 0;
209
0
  pid_t pid;
210
211
0
  mutt_str_copy(rcfile, rcfile_path, sizeof(rcfile));
212
213
0
  size_t rcfilelen = mutt_str_len(rcfile);
214
0
  if (rcfilelen == 0)
215
0
    return -1;
216
217
0
  bool ispipe = rcfile[rcfilelen - 1] == '|';
218
219
0
  if (!ispipe)
220
0
  {
221
0
    struct ListNode *np = STAILQ_FIRST(&MuttrcStack);
222
0
    if (!mutt_path_to_absolute(rcfile, np ? NONULL(np->data) : ""))
223
0
    {
224
0
      mutt_error(_("Error: Can't build path of '%s'"), rcfile_path);
225
0
      return -1;
226
0
    }
227
228
0
    STAILQ_FOREACH(np, &MuttrcStack, entries)
229
0
    {
230
0
      if (mutt_str_equal(np->data, rcfile))
231
0
      {
232
0
        break;
233
0
      }
234
0
    }
235
0
    if (np)
236
0
    {
237
0
      mutt_error(_("Error: Cyclic sourcing of configuration file '%s'"), rcfile);
238
0
      return -1;
239
0
    }
240
241
0
    mutt_list_insert_head(&MuttrcStack, mutt_str_dup(rcfile));
242
0
  }
243
244
0
  mutt_debug(LL_DEBUG2, "Reading configuration file '%s'\n", rcfile);
245
246
0
  FILE *fp = mutt_open_read(rcfile, &pid);
247
0
  if (!fp)
248
0
  {
249
0
    buf_printf(err, "%s: %s", rcfile, strerror(errno));
250
0
    return -1;
251
0
  }
252
253
0
  token = buf_pool_get();
254
0
  linebuf = buf_pool_get();
255
256
0
  const char *const c_config_charset = cs_subset_string(NeoMutt->sub, "config_charset");
257
0
  const char *const c_charset = cc_charset();
258
0
  while ((line = mutt_file_read_line(line, &linelen, fp, &lineno, MUTT_RL_CONT)) != NULL)
259
0
  {
260
0
    const bool conv = c_config_charset && c_charset;
261
0
    if (conv)
262
0
    {
263
0
      currentline = mutt_str_dup(line);
264
0
      if (!currentline)
265
0
        continue;
266
0
      mutt_ch_convert_string(&currentline, c_config_charset, c_charset, MUTT_ICONV_NO_FLAGS);
267
0
    }
268
0
    else
269
0
    {
270
0
      currentline = line;
271
0
    }
272
273
0
    buf_strcpy(linebuf, currentline);
274
275
0
    buf_reset(err);
276
0
    line_rc = parse_rc_buffer(linebuf, token, err);
277
0
    if (line_rc == MUTT_CMD_ERROR)
278
0
    {
279
0
      mutt_error(_("Error in %s, line %d: %s"), rcfile, lineno, buf_string(err));
280
0
      if (--rc < -MAX_ERRS)
281
0
      {
282
0
        if (conv)
283
0
          FREE(&currentline);
284
0
        break;
285
0
      }
286
0
    }
287
0
    else if (line_rc == MUTT_CMD_WARNING)
288
0
    {
289
      /* Warning */
290
0
      mutt_warning(_("Warning in %s, line %d: %s"), rcfile, lineno, buf_string(err));
291
0
      warnings++;
292
0
    }
293
0
    else if (line_rc == MUTT_CMD_FINISH)
294
0
    {
295
0
      if (conv)
296
0
        FREE(&currentline);
297
0
      break; /* Found "finish" command */
298
0
    }
299
0
    else
300
0
    {
301
0
      if (rc < 0)
302
0
        rc = -1;
303
0
    }
304
0
    if (conv)
305
0
      FREE(&currentline);
306
0
  }
307
308
0
  FREE(&line);
309
0
  mutt_file_fclose(&fp);
310
0
  if (pid != -1)
311
0
    filter_wait(pid);
312
313
0
  if (rc)
314
0
  {
315
    /* the neomuttrc source keyword */
316
0
    buf_reset(err);
317
0
    buf_printf(err, (rc >= -MAX_ERRS) ? _("source: errors in %s") : _("source: reading aborted due to too many errors in %s"),
318
0
               rcfile);
319
0
    rc = -1;
320
0
  }
321
0
  else
322
0
  {
323
    /* Don't alias errors with warnings */
324
0
    if (warnings > 0)
325
0
    {
326
0
      buf_printf(err, ngettext("source: %d warning in %s", "source: %d warnings in %s", warnings),
327
0
                 warnings, rcfile);
328
0
      rc = -2;
329
0
    }
330
0
  }
331
332
0
  if (!ispipe && !STAILQ_EMPTY(&MuttrcStack))
333
0
  {
334
0
    struct ListNode *np = STAILQ_FIRST(&MuttrcStack);
335
0
    STAILQ_REMOVE_HEAD(&MuttrcStack, entries);
336
0
    FREE(&np->data);
337
0
    FREE(&np);
338
0
  }
339
340
0
  buf_pool_release(&token);
341
0
  buf_pool_release(&linebuf);
342
0
  return rc;
343
0
}
344
345
/**
346
 * parse_cd - Parse the 'cd' command - Implements Command::parse() - @ingroup command_parse
347
 */
348
static enum CommandResult parse_cd(struct Buffer *buf, struct Buffer *s,
349
                                   intptr_t data, struct Buffer *err)
350
0
{
351
0
  parse_extract_token(buf, s, TOKEN_NO_FLAGS);
352
0
  buf_expand_path(buf);
353
0
  if (buf_len(buf) == 0)
354
0
  {
355
0
    if (HomeDir)
356
0
    {
357
0
      buf_strcpy(buf, HomeDir);
358
0
    }
359
0
    else
360
0
    {
361
0
      buf_printf(err, _("%s: too few arguments"), "cd");
362
0
      return MUTT_CMD_ERROR;
363
0
    }
364
0
  }
365
366
0
  if (chdir(buf_string(buf)) != 0)
367
0
  {
368
0
    buf_printf(err, "cd: %s", strerror(errno));
369
0
    return MUTT_CMD_ERROR;
370
0
  }
371
372
0
  return MUTT_CMD_SUCCESS;
373
0
}
374
375
/**
376
 * parse_echo - Parse the 'echo' command - Implements Command::parse() - @ingroup command_parse
377
 */
378
static enum CommandResult parse_echo(struct Buffer *buf, struct Buffer *s,
379
                                     intptr_t data, struct Buffer *err)
380
0
{
381
0
  if (!MoreArgs(s))
382
0
  {
383
0
    buf_printf(err, _("%s: too few arguments"), "echo");
384
0
    return MUTT_CMD_WARNING;
385
0
  }
386
0
  parse_extract_token(buf, s, TOKEN_NO_FLAGS);
387
0
  OptForceRefresh = true;
388
0
  mutt_message("%s", buf->data);
389
0
  OptForceRefresh = false;
390
0
  mutt_sleep(0);
391
392
0
  return MUTT_CMD_SUCCESS;
393
0
}
394
395
/**
396
 * parse_finish - Parse the 'finish' command - Implements Command::parse() - @ingroup command_parse
397
 * @retval  #MUTT_CMD_FINISH Stop processing the current file
398
 * @retval  #MUTT_CMD_WARNING Failed
399
 *
400
 * If the 'finish' command is found, we should stop reading the current file.
401
 */
402
static enum CommandResult parse_finish(struct Buffer *buf, struct Buffer *s,
403
                                       intptr_t data, struct Buffer *err)
404
0
{
405
0
  if (MoreArgs(s))
406
0
  {
407
0
    buf_printf(err, _("%s: too many arguments"), "finish");
408
0
    return MUTT_CMD_WARNING;
409
0
  }
410
411
0
  return MUTT_CMD_FINISH;
412
0
}
413
414
/**
415
 * parse_group - Parse the 'group' and 'ungroup' commands - Implements Command::parse() - @ingroup command_parse
416
 */
417
static enum CommandResult parse_group(struct Buffer *buf, struct Buffer *s,
418
                                      intptr_t data, struct Buffer *err)
419
0
{
420
0
  struct GroupList gl = STAILQ_HEAD_INITIALIZER(gl);
421
0
  enum GroupState gstate = GS_NONE;
422
423
0
  do
424
0
  {
425
0
    parse_extract_token(buf, s, TOKEN_NO_FLAGS);
426
0
    if (parse_grouplist(&gl, buf, s, err) == -1)
427
0
      goto bail;
428
429
0
    if ((data == MUTT_UNGROUP) && mutt_istr_equal(buf->data, "*"))
430
0
    {
431
0
      mutt_grouplist_clear(&gl);
432
0
      goto out;
433
0
    }
434
435
0
    if (mutt_istr_equal(buf->data, "-rx"))
436
0
    {
437
0
      gstate = GS_RX;
438
0
    }
439
0
    else if (mutt_istr_equal(buf->data, "-addr"))
440
0
    {
441
0
      gstate = GS_ADDR;
442
0
    }
443
0
    else
444
0
    {
445
0
      switch (gstate)
446
0
      {
447
0
        case GS_NONE:
448
0
          buf_printf(err, _("%sgroup: missing -rx or -addr"),
449
0
                     (data == MUTT_UNGROUP) ? "un" : "");
450
0
          goto warn;
451
452
0
        case GS_RX:
453
0
          if ((data == MUTT_GROUP) &&
454
0
              (mutt_grouplist_add_regex(&gl, buf->data, REG_ICASE, err) != 0))
455
0
          {
456
0
            goto bail;
457
0
          }
458
0
          else if ((data == MUTT_UNGROUP) &&
459
0
                   (mutt_grouplist_remove_regex(&gl, buf->data) < 0))
460
0
          {
461
0
            goto bail;
462
0
          }
463
0
          break;
464
465
0
        case GS_ADDR:
466
0
        {
467
0
          char *estr = NULL;
468
0
          struct AddressList al = TAILQ_HEAD_INITIALIZER(al);
469
0
          mutt_addrlist_parse2(&al, buf->data);
470
0
          if (TAILQ_EMPTY(&al))
471
0
            goto bail;
472
0
          if (mutt_addrlist_to_intl(&al, &estr))
473
0
          {
474
0
            buf_printf(err, _("%sgroup: warning: bad IDN '%s'"),
475
0
                       (data == 1) ? "un" : "", estr);
476
0
            mutt_addrlist_clear(&al);
477
0
            FREE(&estr);
478
0
            goto bail;
479
0
          }
480
0
          if (data == MUTT_GROUP)
481
0
            mutt_grouplist_add_addrlist(&gl, &al);
482
0
          else if (data == MUTT_UNGROUP)
483
0
            mutt_grouplist_remove_addrlist(&gl, &al);
484
0
          mutt_addrlist_clear(&al);
485
0
          break;
486
0
        }
487
0
      }
488
0
    }
489
0
  } while (MoreArgs(s));
490
491
0
out:
492
0
  mutt_grouplist_destroy(&gl);
493
0
  return MUTT_CMD_SUCCESS;
494
495
0
bail:
496
0
  mutt_grouplist_destroy(&gl);
497
0
  return MUTT_CMD_ERROR;
498
499
0
warn:
500
0
  mutt_grouplist_destroy(&gl);
501
0
  return MUTT_CMD_WARNING;
502
0
}
503
504
/**
505
 * parse_ifdef - Parse the 'ifdef' and 'ifndef' commands - Implements Command::parse() - @ingroup command_parse
506
 *
507
 * The 'ifdef' command allows conditional elements in the config file.
508
 * If a given variable, function, command or compile-time symbol exists, then
509
 * read the rest of the line of config commands.
510
 * e.g.
511
 *      ifdef sidebar source ~/.neomutt/sidebar.rc
512
 *
513
 * If (data == 1) then it means use the 'ifndef' (if-not-defined) command.
514
 * e.g.
515
 *      ifndef imap finish
516
 */
517
static enum CommandResult parse_ifdef(struct Buffer *buf, struct Buffer *s,
518
                                      intptr_t data, struct Buffer *err)
519
0
{
520
0
  parse_extract_token(buf, s, TOKEN_NO_FLAGS);
521
522
0
  if (buf_is_empty(buf))
523
0
  {
524
0
    buf_printf(err, _("%s: too few arguments"), (data ? "ifndef" : "ifdef"));
525
0
    return MUTT_CMD_WARNING;
526
0
  }
527
528
  // is the item defined as:
529
0
  bool res = cs_subset_lookup(NeoMutt->sub, buf->data) // a variable?
530
0
             || feature_enabled(buf->data)             // a compiled-in feature?
531
0
             || is_function(buf->data)                 // a function?
532
0
             || command_get(buf->data)                 // a command?
533
#ifdef USE_HCACHE
534
             || store_is_valid_backend(buf->data) // a store? (database)
535
#endif
536
0
             || mutt_str_getenv(buf->data); // an environment variable?
537
538
0
  if (!MoreArgs(s))
539
0
  {
540
0
    buf_printf(err, _("%s: too few arguments"), (data ? "ifndef" : "ifdef"));
541
0
    return MUTT_CMD_WARNING;
542
0
  }
543
0
  parse_extract_token(buf, s, TOKEN_SPACE);
544
545
  /* ifdef KNOWN_SYMBOL or ifndef UNKNOWN_SYMBOL */
546
0
  if ((res && (data == 0)) || (!res && (data == 1)))
547
0
  {
548
0
    enum CommandResult rc = parse_rc_line(buf->data, err);
549
0
    if (rc == MUTT_CMD_ERROR)
550
0
    {
551
0
      mutt_error(_("Error: %s"), buf_string(err));
552
0
      return MUTT_CMD_ERROR;
553
0
    }
554
0
    return rc;
555
0
  }
556
0
  return MUTT_CMD_SUCCESS;
557
0
}
558
559
/**
560
 * parse_ignore - Parse the 'ignore' command - Implements Command::parse() - @ingroup command_parse
561
 */
562
static enum CommandResult parse_ignore(struct Buffer *buf, struct Buffer *s,
563
                                       intptr_t data, struct Buffer *err)
564
0
{
565
0
  do
566
0
  {
567
0
    parse_extract_token(buf, s, TOKEN_NO_FLAGS);
568
0
    remove_from_stailq(&UnIgnore, buf->data);
569
0
    add_to_stailq(&Ignore, buf->data);
570
0
  } while (MoreArgs(s));
571
572
0
  return MUTT_CMD_SUCCESS;
573
0
}
574
575
/**
576
 * parse_lists - Parse the 'lists' command - Implements Command::parse() - @ingroup command_parse
577
 */
578
static enum CommandResult parse_lists(struct Buffer *buf, struct Buffer *s,
579
                                      intptr_t data, struct Buffer *err)
580
0
{
581
0
  struct GroupList gl = STAILQ_HEAD_INITIALIZER(gl);
582
583
0
  do
584
0
  {
585
0
    parse_extract_token(buf, s, TOKEN_NO_FLAGS);
586
587
0
    if (parse_grouplist(&gl, buf, s, err) == -1)
588
0
      goto bail;
589
590
0
    mutt_regexlist_remove(&UnMailLists, buf->data);
591
592
0
    if (mutt_regexlist_add(&MailLists, buf->data, REG_ICASE, err) != 0)
593
0
      goto bail;
594
595
0
    if (mutt_grouplist_add_regex(&gl, buf->data, REG_ICASE, err) != 0)
596
0
      goto bail;
597
0
  } while (MoreArgs(s));
598
599
0
  mutt_grouplist_destroy(&gl);
600
0
  return MUTT_CMD_SUCCESS;
601
602
0
bail:
603
0
  mutt_grouplist_destroy(&gl);
604
0
  return MUTT_CMD_ERROR;
605
0
}
606
607
/**
608
 * mailbox_add - Add a new Mailbox
609
 */
610
static enum CommandResult mailbox_add(const char *folder, const char *mailbox,
611
                                      const char *label, enum TriBool poll,
612
                                      enum TriBool notify, struct Buffer *err)
613
0
{
614
0
  mutt_debug(LL_DEBUG1, "Adding mailbox: '%s' label '%s', poll %s, notify %s\n",
615
0
             mailbox, label ? label : "[NONE]",
616
0
             (poll == TB_UNSET) ? "[UNSPECIFIED]" :
617
0
             (poll == TB_TRUE)  ? "true" :
618
0
                                  "false",
619
0
             (notify == TB_UNSET) ? "[UNSPECIFIED]" :
620
0
             (notify == TB_TRUE)  ? "true" :
621
0
                                    "false");
622
0
  struct Mailbox *m = mailbox_new();
623
624
0
  buf_strcpy(&m->pathbuf, mailbox);
625
0
  /* int rc = */ mx_path_canon2(m, folder);
626
627
0
  if (m->type <= MUTT_UNKNOWN)
628
0
  {
629
0
    buf_printf(err, "Unknown Mailbox: %s", m->realpath);
630
0
    mailbox_free(&m);
631
0
    return MUTT_CMD_ERROR;
632
0
  }
633
634
0
  bool new_account = false;
635
0
  struct Account *a = mx_ac_find(m);
636
0
  if (!a)
637
0
  {
638
0
    a = account_new(NULL, NeoMutt->sub);
639
0
    a->type = m->type;
640
0
    new_account = true;
641
0
  }
642
643
0
  if (!new_account)
644
0
  {
645
0
    struct Mailbox *m_old = mx_mbox_find(a, m->realpath);
646
0
    if (m_old)
647
0
    {
648
0
      if (!m_old->visible)
649
0
      {
650
0
        m_old->visible = true;
651
0
        m_old->gen = mailbox_gen();
652
0
      }
653
654
0
      if (label)
655
0
        mutt_str_replace(&m_old->name, label);
656
657
0
      if (notify != TB_UNSET)
658
0
        m_old->notify_user = notify;
659
660
0
      if (poll != TB_UNSET)
661
0
        m_old->poll_new_mail = poll;
662
663
0
      struct EventMailbox ev_m = { m_old };
664
0
      notify_send(m_old->notify, NT_MAILBOX, NT_MAILBOX_CHANGE, &ev_m);
665
666
0
      mailbox_free(&m);
667
0
      return MUTT_CMD_SUCCESS;
668
0
    }
669
0
  }
670
671
0
  if (label)
672
0
    m->name = mutt_str_dup(label);
673
674
0
  if (notify != TB_UNSET)
675
0
    m->notify_user = notify;
676
677
0
  if (poll != TB_UNSET)
678
0
    m->poll_new_mail = poll;
679
680
0
  if (!mx_ac_add(a, m))
681
0
  {
682
0
    mailbox_free(&m);
683
0
    if (new_account)
684
0
    {
685
0
      cs_subset_free(&a->sub);
686
0
      FREE(&a->name);
687
0
      notify_free(&a->notify);
688
0
      FREE(&a);
689
0
    }
690
0
    return MUTT_CMD_SUCCESS;
691
0
  }
692
693
0
  if (new_account)
694
0
  {
695
0
    neomutt_account_add(NeoMutt, a);
696
0
  }
697
698
  // this is finally a visible mailbox in the sidebar and mailboxes list
699
0
  m->visible = true;
700
701
0
#ifdef USE_INOTIFY
702
0
  mutt_monitor_add(m);
703
0
#endif
704
705
0
  return MUTT_CMD_SUCCESS;
706
0
}
707
708
/**
709
 * parse_mailboxes - Parse the 'mailboxes' command - Implements Command::parse() - @ingroup command_parse
710
 *
711
 * This is also used by 'virtual-mailboxes'.
712
 */
713
enum CommandResult parse_mailboxes(struct Buffer *buf, struct Buffer *s,
714
                                   intptr_t data, struct Buffer *err)
715
0
{
716
0
  enum CommandResult rc = MUTT_CMD_WARNING;
717
718
0
  struct Buffer *label = buf_pool_get();
719
0
  struct Buffer *mailbox = buf_pool_get();
720
721
0
  const char *const c_folder = cs_subset_string(NeoMutt->sub, "folder");
722
0
  while (MoreArgs(s))
723
0
  {
724
0
    bool label_set = false;
725
0
    enum TriBool notify = TB_UNSET;
726
0
    enum TriBool poll = TB_UNSET;
727
728
0
    do
729
0
    {
730
      // Start by handling the options
731
0
      parse_extract_token(buf, s, TOKEN_NO_FLAGS);
732
733
0
      if (mutt_str_equal(buf_string(buf), "-label"))
734
0
      {
735
0
        if (!MoreArgs(s))
736
0
        {
737
0
          buf_printf(err, _("%s: too few arguments"), "mailboxes -label");
738
0
          goto done;
739
0
        }
740
741
0
        parse_extract_token(label, s, TOKEN_NO_FLAGS);
742
0
        label_set = true;
743
0
      }
744
0
      else if (mutt_str_equal(buf_string(buf), "-nolabel"))
745
0
      {
746
0
        buf_reset(label);
747
0
        label_set = true;
748
0
      }
749
0
      else if (mutt_str_equal(buf_string(buf), "-notify"))
750
0
      {
751
0
        notify = TB_TRUE;
752
0
      }
753
0
      else if (mutt_str_equal(buf_string(buf), "-nonotify"))
754
0
      {
755
0
        notify = TB_FALSE;
756
0
      }
757
0
      else if (mutt_str_equal(buf_string(buf), "-poll"))
758
0
      {
759
0
        poll = TB_TRUE;
760
0
      }
761
0
      else if (mutt_str_equal(buf_string(buf), "-nopoll"))
762
0
      {
763
0
        poll = TB_FALSE;
764
0
      }
765
0
      else if ((data & MUTT_NAMED) && !label_set)
766
0
      {
767
0
        if (!MoreArgs(s))
768
0
        {
769
0
          buf_printf(err, _("%s: too few arguments"), "named-mailboxes");
770
0
          goto done;
771
0
        }
772
773
0
        buf_copy(label, buf);
774
0
        label_set = true;
775
0
      }
776
0
      else
777
0
      {
778
0
        buf_copy(mailbox, buf);
779
0
        break;
780
0
      }
781
0
    } while (MoreArgs(s));
782
783
0
    if (buf_is_empty(mailbox))
784
0
    {
785
0
      buf_printf(err, _("%s: too few arguments"), "mailboxes");
786
0
      goto done;
787
0
    }
788
789
0
    rc = mailbox_add(c_folder, buf_string(mailbox),
790
0
                     label_set ? buf_string(label) : NULL, poll, notify, err);
791
0
    if (rc != MUTT_CMD_SUCCESS)
792
0
      goto done;
793
794
0
    buf_reset(label);
795
0
    buf_reset(mailbox);
796
0
  }
797
798
0
  rc = MUTT_CMD_SUCCESS;
799
800
0
done:
801
0
  buf_pool_release(&label);
802
0
  buf_pool_release(&mailbox);
803
0
  return rc;
804
0
}
805
806
/**
807
 * parse_my_hdr - Parse the 'my_hdr' command - Implements Command::parse() - @ingroup command_parse
808
 */
809
enum CommandResult parse_my_hdr(struct Buffer *buf, struct Buffer *s,
810
                                intptr_t data, struct Buffer *err)
811
0
{
812
0
  parse_extract_token(buf, s, TOKEN_SPACE | TOKEN_QUOTE);
813
0
  char *p = strpbrk(buf->data, ": \t");
814
0
  if (!p || (*p != ':'))
815
0
  {
816
0
    buf_strcpy(err, _("invalid header field"));
817
0
    return MUTT_CMD_WARNING;
818
0
  }
819
820
0
  struct EventHeader ev_h = { buf->data };
821
0
  struct ListNode *n = header_find(&UserHeader, buf->data);
822
823
0
  if (n)
824
0
  {
825
0
    header_update(n, buf->data);
826
0
    mutt_debug(LL_NOTIFY, "NT_HEADER_CHANGE: %s\n", buf->data);
827
0
    notify_send(NeoMutt->notify, NT_HEADER, NT_HEADER_CHANGE, &ev_h);
828
0
  }
829
0
  else
830
0
  {
831
0
    header_add(&UserHeader, buf->data);
832
0
    mutt_debug(LL_NOTIFY, "NT_HEADER_ADD: %s\n", buf->data);
833
0
    notify_send(NeoMutt->notify, NT_HEADER, NT_HEADER_ADD, &ev_h);
834
0
  }
835
836
0
  return MUTT_CMD_SUCCESS;
837
0
}
838
839
/**
840
 * set_dump - Dump list of config variables into a file/pager.
841
 * @param flags what configs to dump: see #ConfigDumpFlags
842
 * @param err buffer for error message
843
 * @return num See #CommandResult
844
 *
845
 * FIXME: Move me into parse/set.c.  Note: this function currently depends on
846
 * pager, which is the reason it is not included in the parse library.
847
 */
848
enum CommandResult set_dump(ConfigDumpFlags flags, struct Buffer *err)
849
0
{
850
0
  struct Buffer *tempfile = buf_pool_get();
851
0
  buf_mktemp(tempfile);
852
853
0
  FILE *fp_out = mutt_file_fopen(buf_string(tempfile), "w");
854
0
  if (!fp_out)
855
0
  {
856
    // L10N: '%s' is the file name of the temporary file
857
0
    buf_printf(err, _("Could not create temporary file %s"), buf_string(tempfile));
858
0
    buf_pool_release(&tempfile);
859
0
    return MUTT_CMD_ERROR;
860
0
  }
861
862
0
  dump_config(NeoMutt->sub->cs, flags, fp_out);
863
864
0
  mutt_file_fclose(&fp_out);
865
866
0
  struct PagerData pdata = { 0 };
867
0
  struct PagerView pview = { &pdata };
868
869
0
  pdata.fname = buf_string(tempfile);
870
871
0
  pview.banner = "set";
872
0
  pview.flags = MUTT_PAGER_NO_FLAGS;
873
0
  pview.mode = PAGER_MODE_OTHER;
874
875
0
  mutt_do_pager(&pview, NULL);
876
0
  buf_pool_release(&tempfile);
877
878
0
  return MUTT_CMD_SUCCESS;
879
0
}
880
881
/**
882
 * envlist_sort - Compare two environment strings - Implements ::sort_t - @ingroup sort_api
883
 */
884
static int envlist_sort(const void *a, const void *b, void *sdata)
885
0
{
886
0
  return strcmp(*(const char **) a, *(const char **) b);
887
0
}
888
889
/**
890
 * parse_setenv - Parse the 'setenv' and 'unsetenv' commands - Implements Command::parse() - @ingroup command_parse
891
 */
892
static enum CommandResult parse_setenv(struct Buffer *buf, struct Buffer *s,
893
                                       intptr_t data, struct Buffer *err)
894
0
{
895
0
  char **envp = EnvList;
896
897
0
  bool query = false;
898
0
  bool prefix = false;
899
0
  bool unset = (data == MUTT_SET_UNSET);
900
901
0
  if (!MoreArgs(s))
902
0
  {
903
0
    if (!StartupComplete)
904
0
    {
905
0
      buf_printf(err, _("%s: too few arguments"), "setenv");
906
0
      return MUTT_CMD_WARNING;
907
0
    }
908
909
0
    struct Buffer *tempfile = buf_pool_get();
910
0
    buf_mktemp(tempfile);
911
912
0
    FILE *fp_out = mutt_file_fopen(buf_string(tempfile), "w");
913
0
    if (!fp_out)
914
0
    {
915
      // L10N: '%s' is the file name of the temporary file
916
0
      buf_printf(err, _("Could not create temporary file %s"), buf_string(tempfile));
917
0
      buf_pool_release(&tempfile);
918
0
      return MUTT_CMD_ERROR;
919
0
    }
920
921
0
    int count = 0;
922
0
    for (char **env = EnvList; *env; env++)
923
0
      count++;
924
925
0
    mutt_qsort_r(EnvList, count, sizeof(char *), envlist_sort, NULL);
926
927
0
    for (char **env = EnvList; *env; env++)
928
0
      fprintf(fp_out, "%s\n", *env);
929
930
0
    mutt_file_fclose(&fp_out);
931
932
0
    struct PagerData pdata = { 0 };
933
0
    struct PagerView pview = { &pdata };
934
935
0
    pdata.fname = buf_string(tempfile);
936
937
0
    pview.banner = "setenv";
938
0
    pview.flags = MUTT_PAGER_NO_FLAGS;
939
0
    pview.mode = PAGER_MODE_OTHER;
940
941
0
    mutt_do_pager(&pview, NULL);
942
0
    buf_pool_release(&tempfile);
943
944
0
    return MUTT_CMD_SUCCESS;
945
0
  }
946
947
0
  if (*s->dptr == '?')
948
0
  {
949
0
    query = true;
950
0
    prefix = true;
951
952
0
    if (unset)
953
0
    {
954
0
      buf_printf(err, _("Can't query a variable with the '%s' command"), "unsetenv");
955
0
      return MUTT_CMD_WARNING;
956
0
    }
957
958
0
    s->dptr++;
959
0
  }
960
961
  /* get variable name */
962
0
  parse_extract_token(buf, s, TOKEN_EQUAL | TOKEN_QUESTION);
963
964
0
  if (*s->dptr == '?')
965
0
  {
966
0
    if (unset)
967
0
    {
968
0
      buf_printf(err, _("Can't query a variable with the '%s' command"), "unsetenv");
969
0
      return MUTT_CMD_WARNING;
970
0
    }
971
972
0
    if (prefix)
973
0
    {
974
0
      buf_printf(err, _("Can't use a prefix when querying a variable"));
975
0
      return MUTT_CMD_WARNING;
976
0
    }
977
978
0
    query = true;
979
0
    s->dptr++;
980
0
  }
981
982
0
  if (query)
983
0
  {
984
0
    bool found = false;
985
0
    while (envp && *envp)
986
0
    {
987
      /* This will display all matches for "^QUERY" */
988
0
      if (mutt_str_startswith(*envp, buf->data))
989
0
      {
990
0
        if (!found)
991
0
        {
992
0
          mutt_endwin();
993
0
          found = true;
994
0
        }
995
0
        puts(*envp);
996
0
      }
997
0
      envp++;
998
0
    }
999
1000
0
    if (found)
1001
0
    {
1002
0
      mutt_any_key_to_continue(NULL);
1003
0
      return MUTT_CMD_SUCCESS;
1004
0
    }
1005
1006
0
    buf_printf(err, _("%s is unset"), buf->data);
1007
0
    return MUTT_CMD_WARNING;
1008
0
  }
1009
1010
0
  if (unset)
1011
0
  {
1012
0
    if (!envlist_unset(&EnvList, buf->data))
1013
0
    {
1014
0
      buf_printf(err, _("%s is unset"), buf->data);
1015
0
      return MUTT_CMD_WARNING;
1016
0
    }
1017
0
    return MUTT_CMD_SUCCESS;
1018
0
  }
1019
1020
  /* set variable */
1021
1022
0
  if (*s->dptr == '=')
1023
0
  {
1024
0
    s->dptr++;
1025
0
    SKIPWS(s->dptr);
1026
0
  }
1027
1028
0
  if (!MoreArgs(s))
1029
0
  {
1030
0
    buf_printf(err, _("%s: too few arguments"), "setenv");
1031
0
    return MUTT_CMD_WARNING;
1032
0
  }
1033
1034
0
  char *name = mutt_str_dup(buf->data);
1035
0
  parse_extract_token(buf, s, TOKEN_NO_FLAGS);
1036
0
  envlist_set(&EnvList, name, buf->data, true);
1037
0
  FREE(&name);
1038
1039
0
  return MUTT_CMD_SUCCESS;
1040
0
}
1041
1042
/**
1043
 * parse_source - Parse the 'source' command - Implements Command::parse() - @ingroup command_parse
1044
 */
1045
static enum CommandResult parse_source(struct Buffer *buf, struct Buffer *s,
1046
                                       intptr_t data, struct Buffer *err)
1047
0
{
1048
0
  char path[PATH_MAX] = { 0 };
1049
1050
0
  do
1051
0
  {
1052
0
    if (parse_extract_token(buf, s, TOKEN_NO_FLAGS) != 0)
1053
0
    {
1054
0
      buf_printf(err, _("source: error at %s"), s->dptr);
1055
0
      return MUTT_CMD_ERROR;
1056
0
    }
1057
0
    mutt_str_copy(path, buf->data, sizeof(path));
1058
0
    mutt_expand_path(path, sizeof(path));
1059
1060
0
    if (source_rc(path, err) < 0)
1061
0
    {
1062
0
      buf_printf(err, _("source: file %s could not be sourced"), path);
1063
0
      return MUTT_CMD_ERROR;
1064
0
    }
1065
1066
0
  } while (MoreArgs(s));
1067
1068
0
  return MUTT_CMD_SUCCESS;
1069
0
}
1070
1071
/**
1072
 * parse_nospam - Parse the 'nospam' command - Implements Command::parse() - @ingroup command_parse
1073
 */
1074
static enum CommandResult parse_nospam(struct Buffer *buf, struct Buffer *s,
1075
                                       intptr_t data, struct Buffer *err)
1076
0
{
1077
0
  if (!MoreArgs(s))
1078
0
  {
1079
0
    buf_printf(err, _("%s: too few arguments"), "nospam");
1080
0
    return MUTT_CMD_ERROR;
1081
0
  }
1082
1083
  // Extract the first token, a regex or "*"
1084
0
  parse_extract_token(buf, s, TOKEN_NO_FLAGS);
1085
1086
0
  if (MoreArgs(s))
1087
0
  {
1088
0
    buf_printf(err, _("%s: too many arguments"), "finish");
1089
0
    return MUTT_CMD_ERROR;
1090
0
  }
1091
1092
  // "*" is special - clear both spam and nospam lists
1093
0
  if (mutt_str_equal(buf_string(buf), "*"))
1094
0
  {
1095
0
    mutt_replacelist_free(&SpamList);
1096
0
    mutt_regexlist_free(&NoSpamList);
1097
0
    return MUTT_CMD_SUCCESS;
1098
0
  }
1099
1100
  // If it's on the spam list, just remove it
1101
0
  if (mutt_replacelist_remove(&SpamList, buf_string(buf)) != 0)
1102
0
    return MUTT_CMD_SUCCESS;
1103
1104
  // Otherwise, add it to the nospam list
1105
0
  if (mutt_regexlist_add(&NoSpamList, buf_string(buf), REG_ICASE, err) != 0)
1106
0
    return MUTT_CMD_ERROR;
1107
1108
0
  return MUTT_CMD_SUCCESS;
1109
0
}
1110
1111
/**
1112
 * parse_spam - Parse the 'spam' command - Implements Command::parse() - @ingroup command_parse
1113
 */
1114
static enum CommandResult parse_spam(struct Buffer *buf, struct Buffer *s,
1115
                                     intptr_t data, struct Buffer *err)
1116
0
{
1117
0
  if (!MoreArgs(s))
1118
0
  {
1119
0
    buf_printf(err, _("%s: too few arguments"), "spam");
1120
0
    return MUTT_CMD_ERROR;
1121
0
  }
1122
1123
  // Extract the first token, a regex
1124
0
  parse_extract_token(buf, s, TOKEN_NO_FLAGS);
1125
1126
  // If there's a second parameter, it's a template for the spam tag
1127
0
  if (MoreArgs(s))
1128
0
  {
1129
0
    struct Buffer *templ = buf_pool_get();
1130
0
    parse_extract_token(templ, s, TOKEN_NO_FLAGS);
1131
1132
    // Add to the spam list
1133
0
    int rc = mutt_replacelist_add(&SpamList, buf_string(buf), buf_string(templ), err);
1134
0
    buf_pool_release(&templ);
1135
0
    if (rc != 0)
1136
0
      return MUTT_CMD_ERROR;
1137
0
  }
1138
0
  else
1139
0
  {
1140
    // If not, try to remove from the nospam list
1141
0
    mutt_regexlist_remove(&NoSpamList, buf_string(buf));
1142
0
  }
1143
1144
0
  return MUTT_CMD_SUCCESS;
1145
0
}
1146
1147
/**
1148
 * parse_stailq - Parse a list command - Implements Command::parse() - @ingroup command_parse
1149
 *
1150
 * This is used by 'alternative_order', 'auto_view' and several others.
1151
 */
1152
static enum CommandResult parse_stailq(struct Buffer *buf, struct Buffer *s,
1153
                                       intptr_t data, struct Buffer *err)
1154
0
{
1155
0
  do
1156
0
  {
1157
0
    parse_extract_token(buf, s, TOKEN_NO_FLAGS);
1158
0
    add_to_stailq((struct ListHead *) data, buf->data);
1159
0
  } while (MoreArgs(s));
1160
1161
0
  return MUTT_CMD_SUCCESS;
1162
0
}
1163
1164
/**
1165
 * parse_subscribe - Parse the 'subscribe' command - Implements Command::parse() - @ingroup command_parse
1166
 */
1167
static enum CommandResult parse_subscribe(struct Buffer *buf, struct Buffer *s,
1168
                                          intptr_t data, struct Buffer *err)
1169
0
{
1170
0
  struct GroupList gl = STAILQ_HEAD_INITIALIZER(gl);
1171
1172
0
  do
1173
0
  {
1174
0
    parse_extract_token(buf, s, TOKEN_NO_FLAGS);
1175
1176
0
    if (parse_grouplist(&gl, buf, s, err) == -1)
1177
0
      goto bail;
1178
1179
0
    mutt_regexlist_remove(&UnMailLists, buf->data);
1180
0
    mutt_regexlist_remove(&UnSubscribedLists, buf->data);
1181
1182
0
    if (mutt_regexlist_add(&MailLists, buf->data, REG_ICASE, err) != 0)
1183
0
      goto bail;
1184
0
    if (mutt_regexlist_add(&SubscribedLists, buf->data, REG_ICASE, err) != 0)
1185
0
      goto bail;
1186
0
    if (mutt_grouplist_add_regex(&gl, buf->data, REG_ICASE, err) != 0)
1187
0
      goto bail;
1188
0
  } while (MoreArgs(s));
1189
1190
0
  mutt_grouplist_destroy(&gl);
1191
0
  return MUTT_CMD_SUCCESS;
1192
1193
0
bail:
1194
0
  mutt_grouplist_destroy(&gl);
1195
0
  return MUTT_CMD_ERROR;
1196
0
}
1197
1198
#ifdef USE_IMAP
1199
/**
1200
 * parse_subscribe_to - Parse the 'subscribe-to' command - Implements Command::parse() - @ingroup command_parse
1201
 *
1202
 * The 'subscribe-to' command allows to subscribe to an IMAP-Mailbox.
1203
 * Patterns are not supported.
1204
 * Use it as follows: subscribe-to =folder
1205
 */
1206
enum CommandResult parse_subscribe_to(struct Buffer *buf, struct Buffer *s,
1207
                                      intptr_t data, struct Buffer *err)
1208
0
{
1209
0
  if (!buf || !s || !err)
1210
0
    return MUTT_CMD_ERROR;
1211
1212
0
  buf_reset(err);
1213
1214
0
  if (MoreArgs(s))
1215
0
  {
1216
0
    parse_extract_token(buf, s, TOKEN_NO_FLAGS);
1217
1218
0
    if (MoreArgs(s))
1219
0
    {
1220
0
      buf_printf(err, _("%s: too many arguments"), "subscribe-to");
1221
0
      return MUTT_CMD_WARNING;
1222
0
    }
1223
1224
0
    if (!buf_is_empty(buf))
1225
0
    {
1226
      /* Expand and subscribe */
1227
0
      if (imap_subscribe(mutt_expand_path(buf->data, buf->dsize), true) == 0)
1228
0
      {
1229
0
        mutt_message(_("Subscribed to %s"), buf->data);
1230
0
        return MUTT_CMD_SUCCESS;
1231
0
      }
1232
1233
0
      buf_printf(err, _("Could not subscribe to %s"), buf->data);
1234
0
      return MUTT_CMD_ERROR;
1235
0
    }
1236
1237
0
    mutt_debug(LL_DEBUG1, "Corrupted buffer");
1238
0
    return MUTT_CMD_ERROR;
1239
0
  }
1240
1241
0
  buf_addstr(err, _("No folder specified"));
1242
0
  return MUTT_CMD_WARNING;
1243
0
}
1244
#endif
1245
1246
/**
1247
 * parse_tag_formats - Parse the 'tag-formats' command - Implements Command::parse() - @ingroup command_parse
1248
 *
1249
 * Parse config like: `tag-formats pgp GP`
1250
 *
1251
 * @note This maps format -> tag
1252
 */
1253
static enum CommandResult parse_tag_formats(struct Buffer *buf, struct Buffer *s,
1254
                                            intptr_t data, struct Buffer *err)
1255
0
{
1256
0
  if (!s)
1257
0
    return MUTT_CMD_ERROR;
1258
1259
0
  struct Buffer *tagbuf = buf_pool_get();
1260
0
  struct Buffer *fmtbuf = buf_pool_get();
1261
1262
0
  while (MoreArgs(s))
1263
0
  {
1264
0
    parse_extract_token(tagbuf, s, TOKEN_NO_FLAGS);
1265
0
    const char *tag = buf_string(tagbuf);
1266
0
    if (*tag == '\0')
1267
0
      continue;
1268
1269
0
    parse_extract_token(fmtbuf, s, TOKEN_NO_FLAGS);
1270
0
    const char *fmt = buf_string(fmtbuf);
1271
1272
    /* avoid duplicates */
1273
0
    const char *tmp = mutt_hash_find(TagFormats, fmt);
1274
0
    if (tmp)
1275
0
    {
1276
0
      mutt_warning(_("tag format '%s' already registered as '%s'"), fmt, tmp);
1277
0
      continue;
1278
0
    }
1279
1280
0
    mutt_hash_insert(TagFormats, fmt, mutt_str_dup(tag));
1281
0
  }
1282
1283
0
  buf_pool_release(&tagbuf);
1284
0
  buf_pool_release(&fmtbuf);
1285
0
  return MUTT_CMD_SUCCESS;
1286
0
}
1287
1288
/**
1289
 * parse_tag_transforms - Parse the 'tag-transforms' command - Implements Command::parse() - @ingroup command_parse
1290
 *
1291
 * Parse config like: `tag-transforms pgp P`
1292
 *
1293
 * @note This maps tag -> transform
1294
 */
1295
static enum CommandResult parse_tag_transforms(struct Buffer *buf, struct Buffer *s,
1296
                                               intptr_t data, struct Buffer *err)
1297
0
{
1298
0
  if (!s)
1299
0
    return MUTT_CMD_ERROR;
1300
1301
0
  struct Buffer *tagbuf = buf_pool_get();
1302
0
  struct Buffer *trnbuf = buf_pool_get();
1303
1304
0
  while (MoreArgs(s))
1305
0
  {
1306
0
    parse_extract_token(tagbuf, s, TOKEN_NO_FLAGS);
1307
0
    const char *tag = buf_string(tagbuf);
1308
0
    if (*tag == '\0')
1309
0
      continue;
1310
1311
0
    parse_extract_token(trnbuf, s, TOKEN_NO_FLAGS);
1312
0
    const char *trn = buf_string(trnbuf);
1313
1314
    /* avoid duplicates */
1315
0
    const char *tmp = mutt_hash_find(TagTransforms, tag);
1316
0
    if (tmp)
1317
0
    {
1318
0
      mutt_warning(_("tag transform '%s' already registered as '%s'"), tag, tmp);
1319
0
      continue;
1320
0
    }
1321
1322
0
    mutt_hash_insert(TagTransforms, tag, mutt_str_dup(trn));
1323
0
  }
1324
1325
0
  buf_pool_release(&tagbuf);
1326
0
  buf_pool_release(&trnbuf);
1327
0
  return MUTT_CMD_SUCCESS;
1328
0
}
1329
1330
/**
1331
 * parse_unignore - Parse the 'unignore' command - Implements Command::parse() - @ingroup command_parse
1332
 */
1333
static enum CommandResult parse_unignore(struct Buffer *buf, struct Buffer *s,
1334
                                         intptr_t data, struct Buffer *err)
1335
0
{
1336
0
  do
1337
0
  {
1338
0
    parse_extract_token(buf, s, TOKEN_NO_FLAGS);
1339
1340
    /* don't add "*" to the unignore list */
1341
0
    if (!mutt_str_equal(buf->data, "*"))
1342
0
      add_to_stailq(&UnIgnore, buf->data);
1343
1344
0
    remove_from_stailq(&Ignore, buf->data);
1345
0
  } while (MoreArgs(s));
1346
1347
0
  return MUTT_CMD_SUCCESS;
1348
0
}
1349
1350
/**
1351
 * parse_unlists - Parse the 'unlists' command - Implements Command::parse() - @ingroup command_parse
1352
 */
1353
static enum CommandResult parse_unlists(struct Buffer *buf, struct Buffer *s,
1354
                                        intptr_t data, struct Buffer *err)
1355
0
{
1356
0
  mutt_hash_free(&AutoSubscribeCache);
1357
0
  do
1358
0
  {
1359
0
    parse_extract_token(buf, s, TOKEN_NO_FLAGS);
1360
0
    mutt_regexlist_remove(&SubscribedLists, buf->data);
1361
0
    mutt_regexlist_remove(&MailLists, buf->data);
1362
1363
0
    if (!mutt_str_equal(buf->data, "*") &&
1364
0
        (mutt_regexlist_add(&UnMailLists, buf->data, REG_ICASE, err) != 0))
1365
0
    {
1366
0
      return MUTT_CMD_ERROR;
1367
0
    }
1368
0
  } while (MoreArgs(s));
1369
1370
0
  return MUTT_CMD_SUCCESS;
1371
0
}
1372
1373
/**
1374
 * do_unmailboxes - Remove a Mailbox from the Sidebar/notifications
1375
 * @param m Mailbox to `unmailboxes`
1376
 */
1377
static void do_unmailboxes(struct Mailbox *m)
1378
0
{
1379
0
#ifdef USE_INOTIFY
1380
0
  if (m->poll_new_mail)
1381
0
    mutt_monitor_remove(m);
1382
0
#endif
1383
0
  m->visible = false;
1384
0
  m->gen = -1;
1385
0
  if (m->opened)
1386
0
  {
1387
0
    struct EventMailbox ev_m = { NULL };
1388
0
    mutt_debug(LL_NOTIFY, "NT_MAILBOX_CHANGE: NULL\n");
1389
0
    notify_send(NeoMutt->notify, NT_MAILBOX, NT_MAILBOX_CHANGE, &ev_m);
1390
0
  }
1391
0
  else
1392
0
  {
1393
0
    account_mailbox_remove(m->account, m);
1394
0
    mailbox_free(&m);
1395
0
  }
1396
0
}
1397
1398
/**
1399
 * do_unmailboxes_star - Remove all Mailboxes from the Sidebar/notifications
1400
 */
1401
static void do_unmailboxes_star(void)
1402
0
{
1403
0
  struct MailboxList ml = STAILQ_HEAD_INITIALIZER(ml);
1404
0
  neomutt_mailboxlist_get_all(&ml, NeoMutt, MUTT_MAILBOX_ANY);
1405
0
  struct MailboxNode *np = NULL;
1406
0
  struct MailboxNode *nptmp = NULL;
1407
0
  STAILQ_FOREACH_SAFE(np, &ml, entries, nptmp)
1408
0
  {
1409
0
    do_unmailboxes(np->mailbox);
1410
0
  }
1411
0
  neomutt_mailboxlist_clear(&ml);
1412
0
}
1413
1414
/**
1415
 * parse_unmailboxes - Parse the 'unmailboxes' command - Implements Command::parse() - @ingroup command_parse
1416
 *
1417
 * This is also used by 'unvirtual-mailboxes'
1418
 */
1419
enum CommandResult parse_unmailboxes(struct Buffer *buf, struct Buffer *s,
1420
                                     intptr_t data, struct Buffer *err)
1421
0
{
1422
0
  while (MoreArgs(s))
1423
0
  {
1424
0
    parse_extract_token(buf, s, TOKEN_NO_FLAGS);
1425
1426
0
    if (mutt_str_equal(buf->data, "*"))
1427
0
    {
1428
0
      do_unmailboxes_star();
1429
0
      return MUTT_CMD_SUCCESS;
1430
0
    }
1431
1432
0
    buf_expand_path(buf);
1433
1434
0
    struct Account *a = NULL;
1435
0
    TAILQ_FOREACH(a, &NeoMutt->accounts, entries)
1436
0
    {
1437
0
      struct Mailbox *m = mx_mbox_find(a, buf_string(buf));
1438
0
      if (m)
1439
0
      {
1440
0
        do_unmailboxes(m);
1441
0
        break;
1442
0
      }
1443
0
    }
1444
0
  }
1445
0
  return MUTT_CMD_SUCCESS;
1446
0
}
1447
1448
/**
1449
 * parse_unmy_hdr - Parse the 'unmy_hdr' command - Implements Command::parse() - @ingroup command_parse
1450
 */
1451
static enum CommandResult parse_unmy_hdr(struct Buffer *buf, struct Buffer *s,
1452
                                         intptr_t data, struct Buffer *err)
1453
0
{
1454
0
  struct ListNode *np = NULL, *tmp = NULL;
1455
0
  size_t l;
1456
1457
0
  do
1458
0
  {
1459
0
    parse_extract_token(buf, s, TOKEN_NO_FLAGS);
1460
0
    if (mutt_str_equal("*", buf->data))
1461
0
    {
1462
      /* Clear all headers, send a notification for each header */
1463
0
      STAILQ_FOREACH(np, &UserHeader, entries)
1464
0
      {
1465
0
        mutt_debug(LL_NOTIFY, "NT_HEADER_DELETE: %s\n", np->data);
1466
0
        struct EventHeader ev_h = { np->data };
1467
0
        notify_send(NeoMutt->notify, NT_HEADER, NT_HEADER_DELETE, &ev_h);
1468
0
      }
1469
0
      mutt_list_free(&UserHeader);
1470
0
      continue;
1471
0
    }
1472
1473
0
    l = mutt_str_len(buf->data);
1474
0
    if (buf->data[l - 1] == ':')
1475
0
      l--;
1476
1477
0
    STAILQ_FOREACH_SAFE(np, &UserHeader, entries, tmp)
1478
0
    {
1479
0
      if (mutt_istrn_equal(buf->data, np->data, l) && (np->data[l] == ':'))
1480
0
      {
1481
0
        mutt_debug(LL_NOTIFY, "NT_HEADER_DELETE: %s\n", np->data);
1482
0
        struct EventHeader ev_h = { np->data };
1483
0
        notify_send(NeoMutt->notify, NT_HEADER, NT_HEADER_DELETE, &ev_h);
1484
1485
0
        header_free(&UserHeader, np);
1486
0
      }
1487
0
    }
1488
0
  } while (MoreArgs(s));
1489
0
  return MUTT_CMD_SUCCESS;
1490
0
}
1491
1492
/**
1493
 * parse_unstailq - Parse an unlist command - Implements Command::parse() - @ingroup command_parse
1494
 *
1495
 * This is used by 'unalternative_order', 'unauto_view' and several others.
1496
 */
1497
static enum CommandResult parse_unstailq(struct Buffer *buf, struct Buffer *s,
1498
                                         intptr_t data, struct Buffer *err)
1499
0
{
1500
0
  do
1501
0
  {
1502
0
    parse_extract_token(buf, s, TOKEN_NO_FLAGS);
1503
    /* Check for deletion of entire list */
1504
0
    if (mutt_str_equal(buf->data, "*"))
1505
0
    {
1506
0
      mutt_list_free((struct ListHead *) data);
1507
0
      break;
1508
0
    }
1509
0
    remove_from_stailq((struct ListHead *) data, buf->data);
1510
0
  } while (MoreArgs(s));
1511
1512
0
  return MUTT_CMD_SUCCESS;
1513
0
}
1514
1515
/**
1516
 * parse_unsubscribe - Parse the 'unsubscribe' command - Implements Command::parse() - @ingroup command_parse
1517
 */
1518
static enum CommandResult parse_unsubscribe(struct Buffer *buf, struct Buffer *s,
1519
                                            intptr_t data, struct Buffer *err)
1520
0
{
1521
0
  mutt_hash_free(&AutoSubscribeCache);
1522
0
  do
1523
0
  {
1524
0
    parse_extract_token(buf, s, TOKEN_NO_FLAGS);
1525
0
    mutt_regexlist_remove(&SubscribedLists, buf->data);
1526
1527
0
    if (!mutt_str_equal(buf->data, "*") &&
1528
0
        (mutt_regexlist_add(&UnSubscribedLists, buf->data, REG_ICASE, err) != 0))
1529
0
    {
1530
0
      return MUTT_CMD_ERROR;
1531
0
    }
1532
0
  } while (MoreArgs(s));
1533
1534
0
  return MUTT_CMD_SUCCESS;
1535
0
}
1536
1537
#ifdef USE_IMAP
1538
/**
1539
 * parse_unsubscribe_from - Parse the 'unsubscribe-from' command - Implements Command::parse() - @ingroup command_parse
1540
 *
1541
 * The 'unsubscribe-from' command allows to unsubscribe from an IMAP-Mailbox.
1542
 * Patterns are not supported.
1543
 * Use it as follows: unsubscribe-from =folder
1544
 */
1545
enum CommandResult parse_unsubscribe_from(struct Buffer *buf, struct Buffer *s,
1546
                                          intptr_t data, struct Buffer *err)
1547
0
{
1548
0
  if (!buf || !s || !err)
1549
0
    return MUTT_CMD_ERROR;
1550
1551
0
  if (MoreArgs(s))
1552
0
  {
1553
0
    parse_extract_token(buf, s, TOKEN_NO_FLAGS);
1554
1555
0
    if (MoreArgs(s))
1556
0
    {
1557
0
      buf_printf(err, _("%s: too many arguments"), "unsubscribe-from");
1558
0
      return MUTT_CMD_WARNING;
1559
0
    }
1560
1561
0
    if (buf->data && (*buf->data != '\0'))
1562
0
    {
1563
      /* Expand and subscribe */
1564
0
      if (imap_subscribe(mutt_expand_path(buf->data, buf->dsize), false) == 0)
1565
0
      {
1566
0
        mutt_message(_("Unsubscribed from %s"), buf->data);
1567
0
        return MUTT_CMD_SUCCESS;
1568
0
      }
1569
1570
0
      buf_printf(err, _("Could not unsubscribe from %s"), buf->data);
1571
0
      return MUTT_CMD_ERROR;
1572
0
    }
1573
1574
0
    mutt_debug(LL_DEBUG1, "Corrupted buffer");
1575
0
    return MUTT_CMD_ERROR;
1576
0
  }
1577
1578
0
  buf_addstr(err, _("No folder specified"));
1579
0
  return MUTT_CMD_WARNING;
1580
0
}
1581
#endif
1582
1583
/**
1584
 * parse_version - Parse the 'version' command - Implements Command::parse() - @ingroup command_parse
1585
 */
1586
static enum CommandResult parse_version(struct Buffer *buf, struct Buffer *s,
1587
                                        intptr_t data, struct Buffer *err)
1588
0
{
1589
  // silently ignore 'version' if it's in a config file
1590
0
  if (!StartupComplete)
1591
0
    return MUTT_CMD_SUCCESS;
1592
1593
0
  struct Buffer *tempfile = buf_pool_get();
1594
0
  buf_mktemp(tempfile);
1595
1596
0
  FILE *fp_out = mutt_file_fopen(buf_string(tempfile), "w");
1597
0
  if (!fp_out)
1598
0
  {
1599
    // L10N: '%s' is the file name of the temporary file
1600
0
    buf_printf(err, _("Could not create temporary file %s"), buf_string(tempfile));
1601
0
    buf_pool_release(&tempfile);
1602
0
    return MUTT_CMD_ERROR;
1603
0
  }
1604
1605
0
  print_version(fp_out);
1606
0
  mutt_file_fclose(&fp_out);
1607
1608
0
  struct PagerData pdata = { 0 };
1609
0
  struct PagerView pview = { &pdata };
1610
1611
0
  pdata.fname = buf_string(tempfile);
1612
1613
0
  pview.banner = "version";
1614
0
  pview.flags = MUTT_PAGER_NO_FLAGS;
1615
0
  pview.mode = PAGER_MODE_OTHER;
1616
1617
0
  mutt_do_pager(&pview, NULL);
1618
0
  buf_pool_release(&tempfile);
1619
1620
0
  return MUTT_CMD_SUCCESS;
1621
0
}
1622
1623
/**
1624
 * source_stack_cleanup - Free memory from the stack used for the source command
1625
 */
1626
void source_stack_cleanup(void)
1627
0
{
1628
0
  mutt_list_free(&MuttrcStack);
1629
0
}
1630
1631
/**
1632
 * MuttCommands - General NeoMutt Commands
1633
 */
1634
static const struct Command MuttCommands[] = {
1635
  // clang-format off
1636
  { "alias",               parse_alias,            0 },
1637
  { "alternates",          parse_alternates,       0 },
1638
  { "alternative_order",   parse_stailq,           IP &AlternativeOrderList },
1639
  { "attachments",         parse_attachments,      0 },
1640
  { "auto_view",           parse_stailq,           IP &AutoViewList },
1641
  { "bind",                mutt_parse_bind,        0 },
1642
  { "cd",                  parse_cd,               0 },
1643
  { "color",               mutt_parse_color,       0 },
1644
  { "echo",                parse_echo,             0 },
1645
  { "exec",                mutt_parse_exec,        0 },
1646
  { "finish",              parse_finish,           0 },
1647
  { "group",               parse_group,            MUTT_GROUP },
1648
  { "hdr_order",           parse_stailq,           IP &HeaderOrderList },
1649
  { "ifdef",               parse_ifdef,            0 },
1650
  { "ifndef",              parse_ifdef,            1 },
1651
  { "ignore",              parse_ignore,           0 },
1652
  { "lists",               parse_lists,            0 },
1653
  { "macro",               mutt_parse_macro,       1 },
1654
  { "mailboxes",           parse_mailboxes,        0 },
1655
  { "mailto_allow",        parse_stailq,           IP &MailToAllow },
1656
  { "mime_lookup",         parse_stailq,           IP &MimeLookupList },
1657
  { "mono",                mutt_parse_mono,        0 },
1658
  { "my_hdr",              parse_my_hdr,           0 },
1659
  { "named-mailboxes",     parse_mailboxes,        MUTT_NAMED },
1660
  { "nospam",              parse_nospam,           0 },
1661
  { "push",                mutt_parse_push,        0 },
1662
  { "reset",               parse_set,              MUTT_SET_RESET },
1663
  { "score",               mutt_parse_score,       0 },
1664
  { "set",                 parse_set,              MUTT_SET_SET },
1665
  { "setenv",              parse_setenv,           MUTT_SET_SET },
1666
  { "source",              parse_source,           0 },
1667
  { "spam",                parse_spam,             0 },
1668
  { "subjectrx",           parse_subjectrx_list,   0 },
1669
  { "subscribe",           parse_subscribe,        0 },
1670
  { "tag-formats",         parse_tag_formats,      0 },
1671
  { "tag-transforms",      parse_tag_transforms,   0 },
1672
  { "toggle",              parse_set,              MUTT_SET_INV },
1673
  { "unalias",             parse_unalias,          0 },
1674
  { "unalternates",        parse_unalternates,     0 },
1675
  { "unalternative_order", parse_unstailq,         IP &AlternativeOrderList },
1676
  { "unattachments",       parse_unattachments,    0 },
1677
  { "unauto_view",         parse_unstailq,         IP &AutoViewList },
1678
  { "unbind",              mutt_parse_unbind,      MUTT_UNBIND },
1679
  { "uncolor",             mutt_parse_uncolor,     0 },
1680
  { "ungroup",             parse_group,            MUTT_UNGROUP },
1681
  { "unhdr_order",         parse_unstailq,         IP &HeaderOrderList },
1682
  { "unignore",            parse_unignore,         0 },
1683
  { "unlists",             parse_unlists,          0 },
1684
  { "unmacro",             mutt_parse_unbind,      MUTT_UNMACRO },
1685
  { "unmailboxes",         parse_unmailboxes,      0 },
1686
  { "unmailto_allow",      parse_unstailq,         IP &MailToAllow },
1687
  { "unmime_lookup",       parse_unstailq,         IP &MimeLookupList },
1688
  { "unmono",              mutt_parse_unmono,      0 },
1689
  { "unmy_hdr",            parse_unmy_hdr,         0 },
1690
  { "unscore",             mutt_parse_unscore,     0 },
1691
  { "unset",               parse_set,              MUTT_SET_UNSET },
1692
  { "unsetenv",            parse_setenv,           MUTT_SET_UNSET },
1693
  { "unsubjectrx",         parse_unsubjectrx_list, 0 },
1694
  { "unsubscribe",         parse_unsubscribe,      0 },
1695
  { "version",             parse_version,          0 },
1696
  // clang-format on
1697
};
1698
1699
/**
1700
 * commands_init - Initialize commands array and register default commands
1701
 */
1702
void commands_init(void)
1703
0
{
1704
0
  commands_register(MuttCommands, mutt_array_size(MuttCommands));
1705
0
}