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