Coverage Report

Created: 2023-11-27 06:46

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