Coverage Report

Created: 2024-05-20 06:31

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