]> granicus.if.org Git - neomutt/blob - init.c
Convert mutt_attach_reply() to use buffer pool
[neomutt] / init.c
1 /**
2  * @file
3  * Config/command parsing
4  *
5  * @authors
6  * Copyright (C) 1996-2002,2010,2013,2016 Michael R. Elkins <me@mutt.org>
7  * Copyright (C) 2019 Pietro Cerutti <gahr@gahr.ch>
8  *
9  * @copyright
10  * This program is free software: you can redistribute it and/or modify it under
11  * the terms of the GNU General Public License as published by the Free Software
12  * Foundation, either version 2 of the License, or (at your option) any later
13  * version.
14  *
15  * This program is distributed in the hope that it will be useful, but WITHOUT
16  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License along with
21  * this program.  If not, see <http://www.gnu.org/licenses/>.
22  */
23
24 /**
25  * @page init Config/command parsing
26  *
27  * Config/command parsing
28  */
29
30 #include "config.h"
31 #include <ctype.h>
32 #include <errno.h>
33 #include <inttypes.h>
34 #include <limits.h>
35 #include <pwd.h>
36 #include <regex.h>
37 #include <stdbool.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <sys/stat.h>
42 #include <sys/utsname.h>
43 #include <unistd.h>
44 #include "mutt/mutt.h"
45 #include "address/lib.h"
46 #include "email/lib.h"
47 #include "core/lib.h"
48 #include "mutt.h"
49 #include "init.h"
50 #include "alias.h"
51 #include "context.h"
52 #include "filter.h"
53 #include "hcache/hcache.h"
54 #include "keymap.h"
55 #include "monitor.h"
56 #include "mutt_menu.h"
57 #include "mutt_parse.h"
58 #include "mutt_window.h"
59 #include "mx.h"
60 #include "myvar.h"
61 #include "options.h"
62 #include "protos.h"
63 #include "sidebar.h"
64 #include "version.h"
65 #ifdef USE_NOTMUCH
66 #include "notmuch/mutt_notmuch.h"
67 #endif
68 #ifdef USE_IMAP
69 #include "imap/imap.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 #define MAX_ERRS 128
80
81 #define NUM_VARS mutt_array_size(MuttVars)
82 #define NUM_COMMANDS mutt_array_size(Commands)
83
84 /* Initial string that starts completion. No telling how much the user has
85  * typed so far. Allocate 1024 just to be sure! */
86 static char UserTyped[1024] = { 0 };
87
88 static int NumMatched = 0;          /* Number of matches for completion */
89 static char Completed[256] = { 0 }; /* completed string (command or variable) */
90 static const char **Matches;
91 /* this is a lie until mutt_init runs: */
92 static int MatchesListsize = MAX(NUM_VARS, NUM_COMMANDS) + 10;
93
94 #ifdef USE_NOTMUCH
95 /* List of tags found in last call to mutt_nm_query_complete(). */
96 static char **nm_tags;
97 #endif
98
99 /**
100  * enum GroupState - Type of email address group
101  */
102 enum GroupState
103 {
104   GS_NONE, ///< Group is missing an argument
105   GS_RX,   ///< Entry is a regular expression
106   GS_ADDR, ///< Entry is an address
107 };
108
109 /**
110  * add_to_stailq - Add a string to a list
111  * @param head String list
112  * @param str  String to add
113  *
114  * @note Duplicate or empty strings will not be added
115  */
116 static void add_to_stailq(struct ListHead *head, const char *str)
117 {
118   /* don't add a NULL or empty string to the list */
119   if (!str || (*str == '\0'))
120     return;
121
122   /* check to make sure the item is not already on this list */
123   struct ListNode *np = NULL;
124   STAILQ_FOREACH(np, head, entries)
125   {
126     if (mutt_str_strcasecmp(str, np->data) == 0)
127     {
128       return;
129     }
130   }
131   mutt_list_insert_tail(head, mutt_str_strdup(str));
132 }
133
134 /**
135  * alternates_clean - Clear the recipient valid flag of all emails
136  */
137 static void alternates_clean(void)
138 {
139   if (!Context)
140     return;
141
142   for (int i = 0; i < Context->mailbox->msg_count; i++)
143     Context->mailbox->emails[i]->recip_valid = false;
144 }
145
146 /**
147  * attachments_clean - always wise to do what someone else did before
148  */
149 static void attachments_clean(void)
150 {
151   if (!Context)
152     return;
153
154   for (int i = 0; i < Context->mailbox->msg_count; i++)
155     Context->mailbox->emails[i]->attach_valid = false;
156 }
157
158 /**
159  * matches_ensure_morespace - Allocate more space for auto-completion
160  * @param current Current allocation
161  */
162 static void matches_ensure_morespace(int current)
163 {
164   if (current <= (MatchesListsize - 2))
165     return;
166
167   int base_space = MAX(NUM_VARS, NUM_COMMANDS) + 1;
168   int extra_space = MatchesListsize - base_space;
169   extra_space *= 2;
170   const int space = base_space + extra_space;
171   mutt_mem_realloc(&Matches, space * sizeof(char *));
172   memset(&Matches[current + 1], 0, space - current);
173   MatchesListsize = space;
174 }
175
176 /**
177  * candidate - helper function for completion
178  * @param user User entered data for completion
179  * @param src  Candidate for completion
180  * @param dest Completion result gets here
181  * @param dlen Length of dest buffer
182  *
183  * Changes the dest buffer if necessary/possible to aid completion.
184  */
185 static void candidate(char *user, const char *src, char *dest, size_t dlen)
186 {
187   if (!dest || !user || !src)
188     return;
189
190   if (strstr(src, user) != src)
191     return;
192
193   matches_ensure_morespace(NumMatched);
194   Matches[NumMatched++] = src;
195   if (dest[0] == '\0')
196     mutt_str_strfcpy(dest, src, dlen);
197   else
198   {
199     int l;
200     for (l = 0; src[l] && src[l] == dest[l]; l++)
201       ;
202     dest[l] = '\0';
203   }
204 }
205
206 /**
207  * clear_subject_mods - Clear out all modified email subjects
208  */
209 static void clear_subject_mods(void)
210 {
211   if (!Context)
212     return;
213
214   for (int i = 0; i < Context->mailbox->msg_count; i++)
215     FREE(&Context->mailbox->emails[i]->env->disp_subj);
216 }
217
218 #ifdef USE_NOTMUCH
219 /**
220  * complete_all_nm_tags - Pass a list of Notmuch tags to the completion code
221  * @param pt List of all Notmuch tags
222  * @retval  0 Success
223  * @retval -1 Error
224  */
225 static int complete_all_nm_tags(const char *pt)
226 {
227   int tag_count_1 = 0;
228   int tag_count_2 = 0;
229
230   NumMatched = 0;
231   mutt_str_strfcpy(UserTyped, pt, sizeof(UserTyped));
232   memset(Matches, 0, MatchesListsize);
233   memset(Completed, 0, sizeof(Completed));
234
235   nm_db_longrun_init(Context->mailbox, false);
236
237   /* Work out how many tags there are. */
238   if (nm_get_all_tags(Context->mailbox, NULL, &tag_count_1) || (tag_count_1 == 0))
239     goto done;
240
241   /* Free the old list, if any. */
242   if (nm_tags)
243   {
244     for (int i = 0; nm_tags[i]; i++)
245       FREE(&nm_tags[i]);
246     FREE(&nm_tags);
247   }
248   /* Allocate a new list, with sentinel. */
249   nm_tags = mutt_mem_malloc((tag_count_1 + 1) * sizeof(char *));
250   nm_tags[tag_count_1] = NULL;
251
252   /* Get all the tags. */
253   if (nm_get_all_tags(Context->mailbox, nm_tags, &tag_count_2) || (tag_count_1 != tag_count_2))
254   {
255     FREE(&nm_tags);
256     nm_tags = NULL;
257     nm_db_longrun_done(Context->mailbox);
258     return -1;
259   }
260
261   /* Put them into the completion machinery. */
262   for (int num = 0; num < tag_count_1; num++)
263   {
264     candidate(UserTyped, nm_tags[num], Completed, sizeof(Completed));
265   }
266
267   matches_ensure_morespace(NumMatched);
268   Matches[NumMatched++] = UserTyped;
269
270 done:
271   nm_db_longrun_done(Context->mailbox);
272   return 0;
273 }
274 #endif
275
276 /**
277  * execute_commands - Execute a set of NeoMutt commands
278  * @param p List of command strings
279  * @retval  0 Success, all the commands succeeded
280  * @retval -1 Error
281  */
282 static int execute_commands(struct ListHead *p)
283 {
284   int rc = 0;
285   struct Buffer *err = mutt_buffer_pool_get();
286   struct Buffer *token = mutt_buffer_pool_get();
287
288   struct ListNode *np = NULL;
289   STAILQ_FOREACH(np, p, entries)
290   {
291     enum CommandResult rc2 = mutt_parse_rc_line(np->data, token, err);
292     if (rc2 == MUTT_CMD_ERROR)
293       mutt_error(_("Error in command line: %s"), mutt_b2s(err));
294
295     if ((rc2 == MUTT_CMD_ERROR) || (rc2 == MUTT_CMD_WARNING))
296       mutt_warning(_("Warning in command line: %s"), mutt_b2s(err));
297
298     if ((rc2 == MUTT_CMD_ERROR) || (rc2 == MUTT_CMD_WARNING))
299     {
300       mutt_buffer_pool_release(&token);
301       mutt_buffer_pool_release(&err);
302       return -1;
303     }
304   }
305   mutt_buffer_pool_release(&token);
306   mutt_buffer_pool_release(&err);
307
308   return rc;
309 }
310
311 /**
312  * find_cfg - Find a config file
313  * @param home         User's home directory
314  * @param xdg_cfg_home XDG home directory
315  * @retval ptr  Success, first matching directory
316  * @retval NULL Error, no matching directories
317  */
318 static char *find_cfg(const char *home, const char *xdg_cfg_home)
319 {
320   const char *names[] = {
321     "neomuttrc",
322     "muttrc",
323     NULL,
324   };
325
326   const char *locations[][2] = {
327     { xdg_cfg_home, "neomutt/" },
328     { xdg_cfg_home, "mutt/" },
329     { home, ".neomutt/" },
330     { home, ".mutt/" },
331     { home, "." },
332     { NULL, NULL },
333   };
334
335   for (int i = 0; locations[i][0] || locations[i][1]; i++)
336   {
337     if (!locations[i][0])
338       continue;
339
340     for (int j = 0; names[j]; j++)
341     {
342       char buf[256];
343
344       snprintf(buf, sizeof(buf), "%s/%s%s", locations[i][0], locations[i][1], names[j]);
345       if (access(buf, F_OK) == 0)
346         return mutt_str_strdup(buf);
347     }
348   }
349
350   return NULL;
351 }
352
353 #ifndef DOMAIN
354 /**
355  * getmailname - Try to retrieve the FQDN from mailname files
356  * @retval ptr Heap allocated string with the FQDN
357  * @retval NULL if no valid mailname file could be read
358  */
359 static char *getmailname(void)
360 {
361   char *mailname = NULL;
362   static const char *mn_files[] = { "/etc/mailname", "/etc/mail/mailname" };
363
364   for (size_t i = 0; i < mutt_array_size(mn_files); i++)
365   {
366     FILE *fp = mutt_file_fopen(mn_files[i], "r");
367     if (!fp)
368       continue;
369
370     size_t len = 0;
371     mailname = mutt_file_read_line(NULL, &len, fp, NULL, 0);
372     mutt_file_fclose(&fp);
373     if (mailname && *mailname)
374       break;
375
376     FREE(&mailname);
377   }
378
379   return mailname;
380 }
381 #endif
382
383 /**
384  * get_hostname - Find the Fully-Qualified Domain Name
385  * @retval true  Success
386  * @retval false Error, failed to find any name
387  *
388  * Use several methods to try to find the Fully-Qualified domain name of this host.
389  * If the user has already configured a hostname, this function will use it.
390  */
391 static bool get_hostname(void)
392 {
393   char *str = NULL;
394   struct utsname utsname;
395
396   if (C_Hostname)
397   {
398     str = C_Hostname;
399   }
400   else
401   {
402     /* The call to uname() shouldn't fail, but if it does, the system is horribly
403      * broken, and the system's networking configuration is in an unreliable
404      * state.  We should bail.  */
405     if ((uname(&utsname)) == -1)
406     {
407       mutt_perror(_("unable to determine nodename via uname()"));
408       return false; // TEST09: can't test
409     }
410
411     str = utsname.nodename;
412   }
413
414   /* some systems report the FQDN instead of just the hostname */
415   char *dot = strchr(str, '.');
416   if (dot)
417     ShortHostname = mutt_str_substr_dup(str, dot);
418   else
419     ShortHostname = mutt_str_strdup(str);
420
421   if (!C_Hostname)
422   {
423     /* now get FQDN.  Use configured domain first, DNS next, then uname */
424 #ifdef DOMAIN
425     /* we have a compile-time domain name, use that for C_Hostname */
426     C_Hostname =
427         mutt_mem_malloc(mutt_str_strlen(DOMAIN) + mutt_str_strlen(ShortHostname) + 2);
428     sprintf((char *) C_Hostname, "%s.%s", NONULL(ShortHostname), DOMAIN);
429 #else
430     C_Hostname = getmailname();
431     if (!C_Hostname)
432     {
433       char buffer[1024];
434       if (getdnsdomainname(buffer, sizeof(buffer)) == 0)
435       {
436         C_Hostname = mutt_mem_malloc(mutt_str_strlen(buffer) +
437                                      mutt_str_strlen(ShortHostname) + 2);
438         sprintf((char *) C_Hostname, "%s.%s", NONULL(ShortHostname), buffer);
439       }
440       else
441       {
442         /* DNS failed, use the nodename.  Whether or not the nodename had a '.'
443          * in it, we can use the nodename as the FQDN.  On hosts where DNS is
444          * not being used, e.g. small network that relies on hosts files, a
445          * short host name is all that is required for SMTP to work correctly.
446          * It could be wrong, but we've done the best we can, at this point the
447          * onus is on the user to provide the correct hostname if the nodename
448          * won't work in their network.  */
449         C_Hostname = mutt_str_strdup(utsname.nodename);
450       }
451     }
452 #endif
453   }
454   if (C_Hostname)
455     cs_str_initial_set(Config, "hostname", C_Hostname, NULL);
456
457   return true;
458 }
459
460 /**
461  * mutt_attachmatch_new - Create a new AttachMatch
462  * @retval ptr New AttachMatch
463  */
464 static struct AttachMatch *mutt_attachmatch_new(void)
465 {
466   return mutt_mem_calloc(1, sizeof(struct AttachMatch));
467 }
468
469 /**
470  * parse_attach_list - Parse the "attachments" command
471  * @param buf  Buffer for temporary storage
472  * @param s    Buffer containing the attachments command
473  * @param head List of AttachMatch to add to
474  * @param err  Buffer for error messages
475  * @retval #CommandResult Result e.g. #MUTT_CMD_SUCCESS
476  */
477 static enum CommandResult parse_attach_list(struct Buffer *buf, struct Buffer *s,
478                                             struct ListHead *head, struct Buffer *err)
479 {
480   struct AttachMatch *a = NULL;
481   char *p = NULL;
482   char *tmpminor = NULL;
483   size_t len;
484   int ret;
485
486   do
487   {
488     mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
489
490     if (!buf->data || (*buf->data == '\0'))
491       continue;
492
493     a = mutt_attachmatch_new();
494
495     /* some cheap hacks that I expect to remove */
496     if (mutt_str_strcasecmp(buf->data, "any") == 0)
497       a->major = mutt_str_strdup("*/.*");
498     else if (mutt_str_strcasecmp(buf->data, "none") == 0)
499       a->major = mutt_str_strdup("cheap_hack/this_should_never_match");
500     else
501       a->major = mutt_str_strdup(buf->data);
502
503     p = strchr(a->major, '/');
504     if (p)
505     {
506       *p = '\0';
507       p++;
508       a->minor = p;
509     }
510     else
511     {
512       a->minor = "unknown";
513     }
514
515     len = strlen(a->minor);
516     tmpminor = mutt_mem_malloc(len + 3);
517     strcpy(&tmpminor[1], a->minor);
518     tmpminor[0] = '^';
519     tmpminor[len + 1] = '$';
520     tmpminor[len + 2] = '\0';
521
522     a->major_int = mutt_check_mime_type(a->major);
523     ret = REG_COMP(&a->minor_regex, tmpminor, REG_ICASE);
524
525     FREE(&tmpminor);
526
527     if (ret != 0)
528     {
529       regerror(ret, &a->minor_regex, err->data, err->dsize);
530       FREE(&a->major);
531       FREE(&a);
532       return MUTT_CMD_ERROR;
533     }
534
535     mutt_debug(LL_DEBUG3, "added %s/%s [%d]\n", a->major, a->minor, a->major_int);
536
537     mutt_list_insert_tail(head, (char *) a);
538   } while (MoreArgs(s));
539
540   attachments_clean();
541   return MUTT_CMD_SUCCESS;
542 }
543
544 /**
545  * parse_grouplist - Parse a group context
546  * @param gl   GroupList to add to
547  * @param buf  Temporary Buffer space
548  * @param s    Buffer containing string to be parsed
549  * @param data Flags associated with the command
550  * @param err  Buffer for error messages
551  * @retval  0 Success
552  * @retval -1 Error
553  */
554 static int parse_grouplist(struct GroupList *gl, struct Buffer *buf,
555                            struct Buffer *s, unsigned long data, struct Buffer *err)
556 {
557   while (mutt_str_strcasecmp(buf->data, "-group") == 0)
558   {
559     if (!MoreArgs(s))
560     {
561       mutt_buffer_strcpy(err, _("-group: no group name"));
562       return -1;
563     }
564
565     mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
566
567     mutt_grouplist_add(gl, mutt_pattern_group(buf->data));
568
569     if (!MoreArgs(s))
570     {
571       mutt_buffer_strcpy(err, _("out of arguments"));
572       return -1;
573     }
574
575     mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
576   }
577
578   return 0;
579 }
580
581 /**
582  * parse_replace_list - Parse a string replacement rule - Implements ::command_t
583  */
584 static enum CommandResult parse_replace_list(struct Buffer *buf, struct Buffer *s,
585                                              unsigned long data, struct Buffer *err)
586 {
587   struct ReplaceList *list = (struct ReplaceList *) data;
588   struct Buffer templ = mutt_buffer_make(0);
589
590   /* First token is a regex. */
591   if (!MoreArgs(s))
592   {
593     mutt_buffer_printf(err, _("%s: too few arguments"), "subjectrx");
594     return MUTT_CMD_WARNING;
595   }
596   mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
597
598   /* Second token is a replacement template */
599   if (!MoreArgs(s))
600   {
601     mutt_buffer_printf(err, _("%s: too few arguments"), "subjectrx");
602     return MUTT_CMD_WARNING;
603   }
604   mutt_extract_token(&templ, s, MUTT_TOKEN_NO_FLAGS);
605
606   if (mutt_replacelist_add(list, buf->data, templ.data, err) != 0)
607   {
608     FREE(&templ.data);
609     return MUTT_CMD_ERROR;
610   }
611   FREE(&templ.data);
612
613   return MUTT_CMD_SUCCESS;
614 }
615
616 /**
617  * parse_unattach_list - Parse the "unattachments" command
618  * @param buf  Buffer for temporary storage
619  * @param s    Buffer containing the unattachments command
620  * @param head List of AttachMatch to remove from
621  * @param err  Buffer for error messages
622  * @retval #MUTT_CMD_SUCCESS Always
623  */
624 static enum CommandResult parse_unattach_list(struct Buffer *buf, struct Buffer *s,
625                                               struct ListHead *head, struct Buffer *err)
626 {
627   struct AttachMatch *a = NULL;
628   char *tmp = NULL;
629   char *minor = NULL;
630
631   do
632   {
633     mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
634     FREE(&tmp);
635
636     if (mutt_str_strcasecmp(buf->data, "any") == 0)
637       tmp = mutt_str_strdup("*/.*");
638     else if (mutt_str_strcasecmp(buf->data, "none") == 0)
639       tmp = mutt_str_strdup("cheap_hack/this_should_never_match");
640     else
641       tmp = mutt_str_strdup(buf->data);
642
643     minor = strchr(tmp, '/');
644     if (minor)
645     {
646       *minor = '\0';
647       minor++;
648     }
649     else
650     {
651       minor = "unknown";
652     }
653     const enum ContentType major = mutt_check_mime_type(tmp);
654
655     struct ListNode *np = NULL, *tmp2 = NULL;
656     STAILQ_FOREACH_SAFE(np, head, entries, tmp2)
657     {
658       a = (struct AttachMatch *) np->data;
659       mutt_debug(LL_DEBUG3, "check %s/%s [%d] : %s/%s [%d]\n", a->major,
660                  a->minor, a->major_int, tmp, minor, major);
661       if ((a->major_int == major) && (mutt_str_strcasecmp(minor, a->minor) == 0))
662       {
663         mutt_debug(LL_DEBUG3, "removed %s/%s [%d]\n", a->major, a->minor, a->major_int);
664         regfree(&a->minor_regex);
665         FREE(&a->major);
666         STAILQ_REMOVE(head, np, ListNode, entries);
667         FREE(&np->data);
668         FREE(&np);
669       }
670     }
671
672   } while (MoreArgs(s));
673
674   FREE(&tmp);
675   attachments_clean();
676   return MUTT_CMD_SUCCESS;
677 }
678
679 /**
680  * parse_unreplace_list - Remove a string replacement rule - Implements ::command_t
681  */
682 static enum CommandResult parse_unreplace_list(struct Buffer *buf, struct Buffer *s,
683                                                unsigned long data, struct Buffer *err)
684 {
685   struct ReplaceList *list = (struct ReplaceList *) data;
686
687   /* First token is a regex. */
688   if (!MoreArgs(s))
689   {
690     mutt_buffer_printf(err, _("%s: too few arguments"), "unsubjectrx");
691     return MUTT_CMD_WARNING;
692   }
693
694   mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
695
696   /* "*" is a special case. */
697   if (mutt_str_strcmp(buf->data, "*") == 0)
698   {
699     mutt_replacelist_free(list);
700     return MUTT_CMD_SUCCESS;
701   }
702
703   mutt_replacelist_remove(list, buf->data);
704   return MUTT_CMD_SUCCESS;
705 }
706
707 /**
708  * print_attach_list - Print a list of attachments
709  * @param h    List of attachments
710  * @param op   Operation, e.g. '+', '-'
711  * @param name Attached/Inline, 'A', 'I'
712  * @retval 0 Always
713  */
714 static int print_attach_list(struct ListHead *h, const char op, const char *name)
715 {
716   struct ListNode *np = NULL;
717   STAILQ_FOREACH(np, h, entries)
718   {
719     printf("attachments %c%s %s/%s\n", op, name,
720            ((struct AttachMatch *) np->data)->major,
721            ((struct AttachMatch *) np->data)->minor);
722   }
723
724   return 0;
725 }
726
727 /**
728  * remove_from_stailq - Remove an item, matching a string, from a List
729  * @param head Head of the List
730  * @param str  String to match
731  *
732  * @note The string comparison is case-insensitive
733  */
734 static void remove_from_stailq(struct ListHead *head, const char *str)
735 {
736   if (mutt_str_strcmp("*", str) == 0)
737     mutt_list_free(head); /* "unCMD *" means delete all current entries */
738   else
739   {
740     struct ListNode *np = NULL, *tmp = NULL;
741     STAILQ_FOREACH_SAFE(np, head, entries, tmp)
742     {
743       if (mutt_str_strcasecmp(str, np->data) == 0)
744       {
745         STAILQ_REMOVE(head, np, ListNode, entries);
746         FREE(&np->data);
747         FREE(&np);
748         break;
749       }
750     }
751   }
752 }
753
754 /**
755  * source_rc - Read an initialization file
756  * @param rcfile_path Path to initialization file
757  * @param err         Buffer for error messages
758  * @retval <0 if neomutt should pause to let the user know
759  */
760 static int source_rc(const char *rcfile_path, struct Buffer *err)
761 {
762   int line = 0, rc = 0, warnings = 0;
763   enum CommandResult line_rc;
764   struct Buffer token;
765   char *linebuf = NULL;
766   char *currentline = NULL;
767   char rcfile[PATH_MAX];
768   size_t buflen;
769
770   pid_t pid;
771
772   mutt_str_strfcpy(rcfile, rcfile_path, sizeof(rcfile));
773
774   size_t rcfilelen = mutt_str_strlen(rcfile);
775   if (rcfilelen == 0)
776     return -1;
777
778   bool ispipe = rcfile[rcfilelen - 1] == '|';
779
780   if (!ispipe)
781   {
782     struct ListNode *np = STAILQ_FIRST(&MuttrcStack);
783     if (!mutt_path_to_absolute(rcfile, np ? NONULL(np->data) : ""))
784     {
785       mutt_error(_("Error: Can't build path of '%s'"), rcfile_path);
786       return -1;
787     }
788
789     STAILQ_FOREACH(np, &MuttrcStack, entries)
790     {
791       if (mutt_str_strcmp(np->data, rcfile) == 0)
792       {
793         break;
794       }
795     }
796     if (!np)
797     {
798       mutt_list_insert_head(&MuttrcStack, mutt_str_strdup(rcfile));
799     }
800     else
801     {
802       mutt_error(_("Error: Cyclic sourcing of configuration file '%s'"), rcfile);
803       return -1;
804     }
805   }
806
807   mutt_debug(LL_DEBUG2, "Reading configuration file '%s'\n", rcfile);
808
809   FILE *fp = mutt_open_read(rcfile, &pid);
810   if (!fp)
811   {
812     mutt_buffer_printf(err, "%s: %s", rcfile, strerror(errno));
813     return -1;
814   }
815
816   mutt_buffer_init(&token);
817   while ((linebuf = mutt_file_read_line(linebuf, &buflen, fp, &line, MUTT_CONT)))
818   {
819     const bool conv = C_ConfigCharset && C_Charset;
820     if (conv)
821     {
822       currentline = mutt_str_strdup(linebuf);
823       if (!currentline)
824         continue;
825       mutt_ch_convert_string(&currentline, C_ConfigCharset, C_Charset, 0);
826     }
827     else
828       currentline = linebuf;
829     mutt_buffer_reset(err);
830     line_rc = mutt_parse_rc_line(currentline, &token, err);
831     if (line_rc == MUTT_CMD_ERROR)
832     {
833       mutt_error(_("Error in %s, line %d: %s"), rcfile, line, err->data);
834       if (--rc < -MAX_ERRS)
835       {
836         if (conv)
837           FREE(&currentline);
838         break;
839       }
840     }
841     else if (line_rc == MUTT_CMD_WARNING)
842     {
843       /* Warning */
844       mutt_warning(_("Warning in %s, line %d: %s"), rcfile, line, err->data);
845       warnings++;
846     }
847     else if (line_rc == MUTT_CMD_FINISH)
848     {
849       break; /* Found "finish" command */
850     }
851     else
852     {
853       if (rc < 0)
854         rc = -1;
855     }
856     if (conv)
857       FREE(&currentline);
858   }
859   FREE(&token.data);
860   FREE(&linebuf);
861   mutt_file_fclose(&fp);
862   if (pid != -1)
863     mutt_wait_filter(pid);
864   if (rc)
865   {
866     /* the neomuttrc source keyword */
867     mutt_buffer_reset(err);
868     mutt_buffer_printf(err, (rc >= -MAX_ERRS) ? _("source: errors in %s") : _("source: reading aborted due to too many errors in %s"),
869                        rcfile);
870     rc = -1;
871   }
872   else
873   {
874     /* Don't alias errors with warnings */
875     if (warnings > 0)
876     {
877       mutt_buffer_printf(err, ngettext("source: %d warning in %s", "source: %d warnings in %s", warnings),
878                          warnings, rcfile);
879       rc = -2;
880     }
881   }
882
883   if (!ispipe && !STAILQ_EMPTY(&MuttrcStack))
884   {
885     struct ListNode *np = STAILQ_FIRST(&MuttrcStack);
886     STAILQ_REMOVE_HEAD(&MuttrcStack, entries);
887     FREE(&np->data);
888     FREE(&np);
889   }
890
891   return rc;
892 }
893
894 /**
895  * parse_alias - Parse the 'alias' command - Implements ::command_t
896  */
897 static enum CommandResult parse_alias(struct Buffer *buf, struct Buffer *s,
898                                       unsigned long data, struct Buffer *err)
899 {
900   struct Alias *tmp = NULL;
901   char *estr = NULL;
902   struct GroupList gl = STAILQ_HEAD_INITIALIZER(gl);
903
904   if (!MoreArgs(s))
905   {
906     mutt_buffer_strcpy(err, _("alias: no address"));
907     return MUTT_CMD_WARNING;
908   }
909
910   mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
911
912   if (parse_grouplist(&gl, buf, s, data, err) == -1)
913     return MUTT_CMD_ERROR;
914
915   /* check to see if an alias with this name already exists */
916   TAILQ_FOREACH(tmp, &Aliases, entries)
917   {
918     if (mutt_str_strcasecmp(tmp->name, buf->data) == 0)
919       break;
920   }
921
922   if (!tmp)
923   {
924     /* create a new alias */
925     tmp = mutt_alias_new();
926     tmp->name = mutt_str_strdup(buf->data);
927     TAILQ_INSERT_TAIL(&Aliases, tmp, entries);
928     /* give the main addressbook code a chance */
929     if (CurrentMenu == MENU_ALIAS)
930       OptMenuCaller = true;
931   }
932   else
933   {
934     mutt_alias_delete_reverse(tmp);
935     /* override the previous value */
936     mutt_addrlist_clear(&tmp->addr);
937     if (CurrentMenu == MENU_ALIAS)
938       mutt_menu_set_current_redraw_full();
939   }
940
941   mutt_extract_token(buf, s, MUTT_TOKEN_QUOTE | MUTT_TOKEN_SPACE | MUTT_TOKEN_SEMICOLON);
942   mutt_debug(LL_DEBUG5, "Second token is '%s'\n", buf->data);
943
944   mutt_addrlist_parse2(&tmp->addr, buf->data);
945
946   if (mutt_addrlist_to_intl(&tmp->addr, &estr))
947   {
948     mutt_buffer_printf(err, _("Warning: Bad IDN '%s' in alias '%s'"), estr, tmp->name);
949     FREE(&estr);
950     goto bail;
951   }
952
953   mutt_grouplist_add_addrlist(&gl, &tmp->addr);
954   mutt_alias_add_reverse(tmp);
955
956   if (C_DebugLevel > LL_DEBUG4)
957   {
958     /* A group is terminated with an empty address, so check a->mailbox */
959     struct Address *a = NULL;
960     TAILQ_FOREACH(a, &tmp->addr, entries)
961     {
962       if (!a->mailbox)
963         break;
964
965       if (a->group)
966         mutt_debug(LL_DEBUG5, "  Group %s\n", a->mailbox);
967       else
968         mutt_debug(LL_DEBUG5, "  %s\n", a->mailbox);
969     }
970   }
971   mutt_grouplist_destroy(&gl);
972   return MUTT_CMD_SUCCESS;
973
974 bail:
975   mutt_grouplist_destroy(&gl);
976   return MUTT_CMD_ERROR;
977 }
978
979 /**
980  * parse_alternates - Parse the 'alternates' command - Implements ::command_t
981  */
982 static enum CommandResult parse_alternates(struct Buffer *buf, struct Buffer *s,
983                                            unsigned long data, struct Buffer *err)
984 {
985   struct GroupList gl = STAILQ_HEAD_INITIALIZER(gl);
986
987   alternates_clean();
988
989   do
990   {
991     mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
992
993     if (parse_grouplist(&gl, buf, s, data, err) == -1)
994       goto bail;
995
996     mutt_regexlist_remove(&UnAlternates, buf->data);
997
998     if (mutt_regexlist_add(&Alternates, buf->data, REG_ICASE, err) != 0)
999       goto bail;
1000
1001     if (mutt_grouplist_add_regex(&gl, buf->data, REG_ICASE, err) != 0)
1002       goto bail;
1003   } while (MoreArgs(s));
1004
1005   mutt_grouplist_destroy(&gl);
1006   return MUTT_CMD_SUCCESS;
1007
1008 bail:
1009   mutt_grouplist_destroy(&gl);
1010   return MUTT_CMD_ERROR;
1011 }
1012
1013 /**
1014  * parse_attachments - Parse the 'attachments' command - Implements ::command_t
1015  */
1016 static enum CommandResult parse_attachments(struct Buffer *buf, struct Buffer *s,
1017                                             unsigned long data, struct Buffer *err)
1018 {
1019   char op;
1020   char *category = NULL;
1021   struct ListHead *head = NULL;
1022
1023   mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1024   if (!buf->data || (*buf->data == '\0'))
1025   {
1026     mutt_buffer_strcpy(err, _("attachments: no disposition"));
1027     return MUTT_CMD_WARNING;
1028   }
1029
1030   category = buf->data;
1031   op = *category++;
1032
1033   if (op == '?')
1034   {
1035     mutt_endwin();
1036     fflush(stdout);
1037     printf("\n%s\n\n", _("Current attachments settings:"));
1038     print_attach_list(&AttachAllow, '+', "A");
1039     print_attach_list(&AttachExclude, '-', "A");
1040     print_attach_list(&InlineAllow, '+', "I");
1041     print_attach_list(&InlineExclude, '-', "I");
1042     mutt_any_key_to_continue(NULL);
1043     return MUTT_CMD_SUCCESS;
1044   }
1045
1046   if ((op != '+') && (op != '-'))
1047   {
1048     op = '+';
1049     category--;
1050   }
1051   if (mutt_str_startswith("attachment", category, CASE_IGNORE))
1052   {
1053     if (op == '+')
1054       head = &AttachAllow;
1055     else
1056       head = &AttachExclude;
1057   }
1058   else if (mutt_str_startswith("inline", category, CASE_IGNORE))
1059   {
1060     if (op == '+')
1061       head = &InlineAllow;
1062     else
1063       head = &InlineExclude;
1064   }
1065   else
1066   {
1067     mutt_buffer_strcpy(err, _("attachments: invalid disposition"));
1068     return MUTT_CMD_ERROR;
1069   }
1070
1071   return parse_attach_list(buf, s, head, err);
1072 }
1073
1074 /**
1075  * parse_echo - Parse the 'echo' command - Implements ::command_t
1076  */
1077 static enum CommandResult parse_echo(struct Buffer *buf, struct Buffer *s,
1078                                      unsigned long data, struct Buffer *err)
1079 {
1080   if (!MoreArgs(s))
1081   {
1082     mutt_buffer_printf(err, _("%s: too few arguments"), "echo");
1083     return MUTT_CMD_WARNING;
1084   }
1085   mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1086   OptForceRefresh = true;
1087   mutt_message("%s", buf->data);
1088   OptForceRefresh = false;
1089   mutt_sleep(0);
1090
1091   return MUTT_CMD_SUCCESS;
1092 }
1093
1094 /**
1095  * parse_finish - Parse the 'finish' command - Implements ::command_t
1096  * @retval  #MUTT_CMD_FINISH Stop processing the current file
1097  * @retval  #MUTT_CMD_WARNING Failed
1098  *
1099  * If the 'finish' command is found, we should stop reading the current file.
1100  */
1101 static enum CommandResult parse_finish(struct Buffer *buf, struct Buffer *s,
1102                                        unsigned long data, struct Buffer *err)
1103 {
1104   if (MoreArgs(s))
1105   {
1106     mutt_buffer_printf(err, _("%s: too many arguments"), "finish");
1107     return MUTT_CMD_WARNING;
1108   }
1109
1110   return MUTT_CMD_FINISH;
1111 }
1112
1113 /**
1114  * parse_group - Parse the 'group' and 'ungroup' commands - Implements ::command_t
1115  */
1116 static enum CommandResult parse_group(struct Buffer *buf, struct Buffer *s,
1117                                       unsigned long data, struct Buffer *err)
1118 {
1119   struct GroupList gl = STAILQ_HEAD_INITIALIZER(gl);
1120   enum GroupState state = GS_NONE;
1121
1122   do
1123   {
1124     mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1125     if (parse_grouplist(&gl, buf, s, data, err) == -1)
1126       goto bail;
1127
1128     if ((data == MUTT_UNGROUP) && (mutt_str_strcasecmp(buf->data, "*") == 0))
1129     {
1130       mutt_grouplist_clear(&gl);
1131       goto out;
1132     }
1133
1134     if (mutt_str_strcasecmp(buf->data, "-rx") == 0)
1135       state = GS_RX;
1136     else if (mutt_str_strcasecmp(buf->data, "-addr") == 0)
1137       state = GS_ADDR;
1138     else
1139     {
1140       switch (state)
1141       {
1142         case GS_NONE:
1143           mutt_buffer_printf(err, _("%sgroup: missing -rx or -addr"),
1144                              (data == MUTT_UNGROUP) ? "un" : "");
1145           goto warn;
1146
1147         case GS_RX:
1148           if ((data == MUTT_GROUP) &&
1149               (mutt_grouplist_add_regex(&gl, buf->data, REG_ICASE, err) != 0))
1150           {
1151             goto bail;
1152           }
1153           else if ((data == MUTT_UNGROUP) &&
1154                    (mutt_grouplist_remove_regex(&gl, buf->data) < 0))
1155           {
1156             goto bail;
1157           }
1158           break;
1159
1160         case GS_ADDR:
1161         {
1162           char *estr = NULL;
1163           struct AddressList al = TAILQ_HEAD_INITIALIZER(al);
1164           mutt_addrlist_parse2(&al, buf->data);
1165           if (TAILQ_EMPTY(&al))
1166             goto bail;
1167           if (mutt_addrlist_to_intl(&al, &estr))
1168           {
1169             mutt_buffer_printf(err, _("%sgroup: warning: bad IDN '%s'"),
1170                                (data == 1) ? "un" : "", estr);
1171             mutt_addrlist_clear(&al);
1172             FREE(&estr);
1173             goto bail;
1174           }
1175           if (data == MUTT_GROUP)
1176             mutt_grouplist_add_addrlist(&gl, &al);
1177           else if (data == MUTT_UNGROUP)
1178             mutt_grouplist_remove_addrlist(&gl, &al);
1179           mutt_addrlist_clear(&al);
1180           break;
1181         }
1182       }
1183     }
1184   } while (MoreArgs(s));
1185
1186 out:
1187   mutt_grouplist_destroy(&gl);
1188   return MUTT_CMD_SUCCESS;
1189
1190 bail:
1191   mutt_grouplist_destroy(&gl);
1192   return MUTT_CMD_ERROR;
1193
1194 warn:
1195   mutt_grouplist_destroy(&gl);
1196   return MUTT_CMD_WARNING;
1197 }
1198
1199 /**
1200  * is_function - Is the argument a neomutt function?
1201  * @param name  Command name to be searched for
1202  * @retval true  Function found
1203  * @retval false Function not found
1204  */
1205 static bool is_function(const char *name)
1206 {
1207   for (enum MenuType i = 0; i < MENU_MAX; i++)
1208   {
1209     const struct Binding *b = km_get_table(Menus[i].value);
1210     if (!b)
1211       continue;
1212
1213     for (int j = 0; b[j].name; j++)
1214       if (mutt_str_strcmp(name, b[j].name) == 0)
1215         return true;
1216   }
1217   return false;
1218 }
1219
1220 /**
1221  * parse_ifdef - Parse the 'ifdef' and 'ifndef' commands - Implements ::command_t
1222  *
1223  * The 'ifdef' command allows conditional elements in the config file.
1224  * If a given variable, function, command or compile-time symbol exists, then
1225  * read the rest of the line of config commands.
1226  * e.g.
1227  *      ifdef sidebar source ~/.neomutt/sidebar.rc
1228  *
1229  * If (data == 1) then it means use the 'ifndef' (if-not-defined) command.
1230  * e.g.
1231  *      ifndef imap finish
1232  */
1233 static enum CommandResult parse_ifdef(struct Buffer *buf, struct Buffer *s,
1234                                       unsigned long data, struct Buffer *err)
1235 {
1236   struct Buffer token = mutt_buffer_make(0);
1237
1238   mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1239
1240   // is the item defined as:
1241   bool res = cs_get_elem(Config, buf->data) // a variable?
1242              || feature_enabled(buf->data)  // a compiled-in feature?
1243              || is_function(buf->data)      // a function?
1244              || mutt_command_get(buf->data) // a command?
1245              || myvar_get(buf->data)        // a my_ variable?
1246              || mutt_str_getenv(buf->data); // an environment variable?
1247
1248   if (!MoreArgs(s))
1249   {
1250     mutt_buffer_printf(err, _("%s: too few arguments"), (data ? "ifndef" : "ifdef"));
1251     return MUTT_CMD_WARNING;
1252   }
1253   mutt_extract_token(buf, s, MUTT_TOKEN_SPACE);
1254
1255   /* ifdef KNOWN_SYMBOL or ifndef UNKNOWN_SYMBOL */
1256   if ((res && (data == 0)) || (!res && (data == 1)))
1257   {
1258     enum CommandResult rc = mutt_parse_rc_line(buf->data, &token, err);
1259     if (rc == MUTT_CMD_ERROR)
1260     {
1261       mutt_error(_("Error: %s"), err->data);
1262       FREE(&token.data);
1263       return MUTT_CMD_ERROR;
1264     }
1265     FREE(&token.data);
1266     return rc;
1267   }
1268   return MUTT_CMD_SUCCESS;
1269 }
1270
1271 /**
1272  * parse_ignore - Parse the 'ignore' command - Implements ::command_t
1273  */
1274 static enum CommandResult parse_ignore(struct Buffer *buf, struct Buffer *s,
1275                                        unsigned long data, struct Buffer *err)
1276 {
1277   do
1278   {
1279     mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1280     remove_from_stailq(&UnIgnore, buf->data);
1281     add_to_stailq(&Ignore, buf->data);
1282   } while (MoreArgs(s));
1283
1284   return MUTT_CMD_SUCCESS;
1285 }
1286
1287 /**
1288  * parse_lists - Parse the 'lists' command - Implements ::command_t
1289  */
1290 static enum CommandResult parse_lists(struct Buffer *buf, struct Buffer *s,
1291                                       unsigned long data, struct Buffer *err)
1292 {
1293   struct GroupList gl = STAILQ_HEAD_INITIALIZER(gl);
1294
1295   do
1296   {
1297     mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1298
1299     if (parse_grouplist(&gl, buf, s, data, err) == -1)
1300       goto bail;
1301
1302     mutt_regexlist_remove(&UnMailLists, buf->data);
1303
1304     if (mutt_regexlist_add(&MailLists, buf->data, REG_ICASE, err) != 0)
1305       goto bail;
1306
1307     if (mutt_grouplist_add_regex(&gl, buf->data, REG_ICASE, err) != 0)
1308       goto bail;
1309   } while (MoreArgs(s));
1310
1311   mutt_grouplist_destroy(&gl);
1312   return MUTT_CMD_SUCCESS;
1313
1314 bail:
1315   mutt_grouplist_destroy(&gl);
1316   return MUTT_CMD_ERROR;
1317 }
1318
1319 /**
1320  * parse_mailboxes - Parse the 'mailboxes' command - Implements ::command_t
1321  *
1322  * This is also used by 'virtual-mailboxes'.
1323  */
1324 static enum CommandResult parse_mailboxes(struct Buffer *buf, struct Buffer *s,
1325                                           unsigned long data, struct Buffer *err)
1326 {
1327   while (MoreArgs(s))
1328   {
1329     struct Mailbox *m = mailbox_new();
1330
1331     if (data & MUTT_NAMED)
1332     {
1333       mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1334       if (buf->data && (*buf->data != '\0'))
1335       {
1336         m->name = mutt_str_strdup(buf->data);
1337       }
1338       else
1339       {
1340         mailbox_free(&m);
1341         continue;
1342       }
1343     }
1344
1345     mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1346     if (mutt_buffer_is_empty(buf))
1347     {
1348       /* Skip empty tokens. */
1349       mailbox_free(&m);
1350       continue;
1351     }
1352
1353     mutt_buffer_strcpy(&m->pathbuf, buf->data);
1354     /* int rc = */ mx_path_canon2(m, C_Folder);
1355
1356     bool new_account = false;
1357     struct Account *a = mx_ac_find(m);
1358     if (!a)
1359     {
1360       a = account_new(NULL, NeoMutt->sub);
1361       a->magic = m->magic;
1362       new_account = true;
1363     }
1364
1365     if (!new_account)
1366     {
1367       struct Mailbox *m_old = mx_mbox_find(a, m->realpath);
1368       if (m_old)
1369       {
1370         if (m_old->flags == MB_HIDDEN)
1371         {
1372           m_old->flags = MB_NORMAL;
1373           mutt_sb_notify_mailbox(m_old, true);
1374         }
1375         mailbox_free(&m);
1376         continue;
1377       }
1378     }
1379
1380     if (mx_ac_add(a, m) < 0)
1381     {
1382       //error
1383       mailbox_free(&m);
1384       if (new_account)
1385       {
1386         FREE(&a);
1387       }
1388       continue;
1389     }
1390     if (new_account)
1391     {
1392       neomutt_account_add(NeoMutt, a);
1393     }
1394
1395 #ifdef USE_SIDEBAR
1396     mutt_sb_notify_mailbox(m, true);
1397 #endif
1398 #ifdef USE_INOTIFY
1399     mutt_monitor_add(m);
1400 #endif
1401   }
1402   return MUTT_CMD_SUCCESS;
1403 }
1404
1405 /**
1406  * parse_my_hdr - Parse the 'my_hdr' command - Implements ::command_t
1407  */
1408 static enum CommandResult parse_my_hdr(struct Buffer *buf, struct Buffer *s,
1409                                        unsigned long data, struct Buffer *err)
1410 {
1411   struct ListNode *n = NULL;
1412   size_t keylen;
1413
1414   mutt_extract_token(buf, s, MUTT_TOKEN_SPACE | MUTT_TOKEN_QUOTE);
1415   char *p = strpbrk(buf->data, ": \t");
1416   if (!p || (*p != ':'))
1417   {
1418     mutt_buffer_strcpy(err, _("invalid header field"));
1419     return MUTT_CMD_WARNING;
1420   }
1421   keylen = p - buf->data + 1;
1422
1423   STAILQ_FOREACH(n, &UserHeader, entries)
1424   {
1425     /* see if there is already a field by this name */
1426     if (mutt_str_strncasecmp(buf->data, n->data, keylen) == 0)
1427     {
1428       break;
1429     }
1430   }
1431
1432   if (!n)
1433   {
1434     /* not found, allocate memory for a new node and add it to the list */
1435     n = mutt_list_insert_tail(&UserHeader, NULL);
1436   }
1437   else
1438   {
1439     /* found, free the existing data */
1440     FREE(&n->data);
1441   }
1442
1443   n->data = buf->data;
1444   mutt_buffer_init(buf);
1445
1446   return MUTT_CMD_SUCCESS;
1447 }
1448
1449 #ifdef USE_SIDEBAR
1450 /**
1451  * parse_path_list - Parse the 'sidebar_whitelist' command - Implements ::command_t
1452  */
1453 static enum CommandResult parse_path_list(struct Buffer *buf, struct Buffer *s,
1454                                           unsigned long data, struct Buffer *err)
1455 {
1456   struct Buffer *path = mutt_buffer_pool_get();
1457
1458   do
1459   {
1460     mutt_extract_token(path, s, MUTT_TOKEN_NO_FLAGS);
1461     mutt_buffer_expand_path(path);
1462     add_to_stailq((struct ListHead *) data, mutt_b2s(path));
1463   } while (MoreArgs(s));
1464   mutt_buffer_pool_release(&path);
1465
1466   return MUTT_CMD_SUCCESS;
1467 }
1468 #endif
1469
1470 #ifdef USE_SIDEBAR
1471 /**
1472  * parse_path_unlist - Parse the 'unsidebar_whitelist' command - Implements ::command_t
1473  */
1474 static enum CommandResult parse_path_unlist(struct Buffer *buf, struct Buffer *s,
1475                                             unsigned long data, struct Buffer *err)
1476 {
1477   struct Buffer *path = mutt_buffer_pool_get();
1478
1479   do
1480   {
1481     mutt_extract_token(path, s, MUTT_TOKEN_NO_FLAGS);
1482     /* Check for deletion of entire list */
1483     if (mutt_str_strcmp(mutt_b2s(path), "*") == 0)
1484     {
1485       mutt_list_free((struct ListHead *) data);
1486       break;
1487     }
1488     mutt_buffer_expand_path(path);
1489     remove_from_stailq((struct ListHead *) data, mutt_b2s(path));
1490   } while (MoreArgs(s));
1491   mutt_buffer_pool_release(&path);
1492
1493   return MUTT_CMD_SUCCESS;
1494 }
1495 #endif
1496
1497 /**
1498  * parse_set - Parse the 'set' family of commands - Implements ::command_t
1499  *
1500  * This is used by 'reset', 'set', 'toggle' and 'unset'.
1501  */
1502 static enum CommandResult parse_set(struct Buffer *buf, struct Buffer *s,
1503                                     unsigned long data, struct Buffer *err)
1504 {
1505   /* The order must match `enum MuttSetCommand` */
1506   static const char *set_commands[] = { "set", "toggle", "unset", "reset" };
1507
1508   int rc = 0;
1509
1510   while (MoreArgs(s))
1511   {
1512     bool prefix = false;
1513     bool query = false;
1514     bool inv = (data == MUTT_SET_INV);
1515     bool reset = (data == MUTT_SET_RESET);
1516     bool unset = (data == MUTT_SET_UNSET);
1517
1518     if (*s->dptr == '?')
1519     {
1520       prefix = true;
1521       query = true;
1522       s->dptr++;
1523     }
1524     else if (mutt_str_startswith(s->dptr, "no", CASE_MATCH))
1525     {
1526       prefix = true;
1527       unset = !unset;
1528       s->dptr += 2;
1529     }
1530     else if (mutt_str_startswith(s->dptr, "inv", CASE_MATCH))
1531     {
1532       prefix = true;
1533       inv = !inv;
1534       s->dptr += 3;
1535     }
1536     else if (*s->dptr == '&')
1537     {
1538       prefix = true;
1539       reset = true;
1540       s->dptr++;
1541     }
1542
1543     if (prefix && (data != MUTT_SET_SET))
1544     {
1545       mutt_buffer_printf(err, "ERR22 can't use 'inv', 'no', '&' or '?' with the '%s' command",
1546                          set_commands[data]);
1547       return MUTT_CMD_WARNING;
1548     }
1549
1550     /* get the variable name */
1551     mutt_extract_token(buf, s, MUTT_TOKEN_EQUAL | MUTT_TOKEN_QUESTION);
1552
1553     bool bq = false;
1554     bool equals = false;
1555
1556     struct HashElem *he = NULL;
1557     bool my = mutt_str_startswith(buf->data, "my_", CASE_MATCH);
1558     if (!my)
1559     {
1560       he = cs_get_elem(Config, buf->data);
1561       if (!he)
1562       {
1563         if (reset && (mutt_str_strcmp(buf->data, "all") == 0))
1564         {
1565           struct HashElem **list = get_elem_list(Config);
1566           if (!list)
1567             return MUTT_CMD_ERROR;
1568
1569           for (size_t i = 0; list[i]; i++)
1570             cs_he_reset(Config, list[i], NULL);
1571
1572           FREE(&list);
1573           break;
1574         }
1575         else
1576         {
1577           mutt_buffer_printf(err, "ERR01 unknown variable: %s", buf->data);
1578           return MUTT_CMD_ERROR;
1579         }
1580       }
1581
1582       bq = ((DTYPE(he->type) == DT_BOOL) || (DTYPE(he->type) == DT_QUAD));
1583     }
1584
1585     if (*s->dptr == '?')
1586     {
1587       if (prefix)
1588       {
1589         mutt_buffer_printf(err,
1590                            "ERR02 can't use a prefix when querying a variable");
1591         return MUTT_CMD_WARNING;
1592       }
1593
1594       if (reset || unset || inv)
1595       {
1596         mutt_buffer_printf(err, "ERR03 can't query a variable with the '%s' command",
1597                            set_commands[data]);
1598         return MUTT_CMD_WARNING;
1599       }
1600
1601       query = true;
1602       s->dptr++;
1603     }
1604     else if (*s->dptr == '=')
1605     {
1606       if (prefix)
1607       {
1608         mutt_buffer_printf(err,
1609                            "ERR04 can't use prefix when setting a variable");
1610         return MUTT_CMD_WARNING;
1611       }
1612
1613       if (reset || unset || inv)
1614       {
1615         mutt_buffer_printf(err, "ERR05 can't set a variable with the '%s' command",
1616                            set_commands[data]);
1617         return MUTT_CMD_WARNING;
1618       }
1619
1620       equals = true;
1621       s->dptr++;
1622     }
1623
1624     if (!bq && (inv || (unset && prefix)))
1625     {
1626       if (data == MUTT_SET_SET)
1627       {
1628         mutt_buffer_printf(err, "ERR06 prefixes 'no' and 'inv' may only be "
1629                                 "used with bool/quad variables");
1630       }
1631       else
1632       {
1633         mutt_buffer_printf(err, "ERR07 command '%s' can only be used with bool/quad variables",
1634                            set_commands[data]);
1635       }
1636       return MUTT_CMD_WARNING;
1637     }
1638
1639     if (reset)
1640     {
1641       // mutt_buffer_printf(err, "ACT24 reset variable %s", buf->data);
1642       if (he)
1643       {
1644         rc = cs_he_reset(Config, he, err);
1645         if (CSR_RESULT(rc) != CSR_SUCCESS)
1646           return MUTT_CMD_ERROR;
1647       }
1648       else
1649       {
1650         myvar_del(buf->data);
1651       }
1652       continue;
1653     }
1654
1655     if ((data == MUTT_SET_SET) && !inv && !unset)
1656     {
1657       if (query)
1658       {
1659         // mutt_buffer_printf(err, "ACT08 query variable %s", buf->data);
1660         if (he)
1661         {
1662           mutt_buffer_addstr(err, buf->data);
1663           mutt_buffer_addch(err, '=');
1664           mutt_buffer_reset(buf);
1665           rc = cs_he_string_get(Config, he, buf);
1666           if (CSR_RESULT(rc) != CSR_SUCCESS)
1667           {
1668             mutt_buffer_addstr(err, buf->data);
1669             return MUTT_CMD_ERROR;
1670           }
1671           pretty_var(buf->data, err);
1672         }
1673         else
1674         {
1675           const char *val = myvar_get(buf->data);
1676           if (val)
1677           {
1678             mutt_buffer_addstr(err, buf->data);
1679             mutt_buffer_addch(err, '=');
1680             pretty_var(val, err);
1681           }
1682           else
1683           {
1684             mutt_buffer_printf(err, _("%s: unknown variable"), buf->data);
1685             return MUTT_CMD_ERROR;
1686           }
1687         }
1688         break;
1689       }
1690       else if (equals)
1691       {
1692         // mutt_buffer_printf(err, "ACT11 set variable %s to ", buf->data);
1693         const char *name = NULL;
1694         if (my)
1695         {
1696           name = mutt_str_strdup(buf->data);
1697         }
1698         mutt_extract_token(buf, s, MUTT_TOKEN_BACKTICK_VARS);
1699         if (my)
1700         {
1701           myvar_set(name, buf->data);
1702           FREE(&name);
1703         }
1704         else
1705         {
1706           if (IS_PATH(he))
1707           {
1708             mutt_expand_path(buf->data, buf->dsize);
1709             struct Buffer scratch = mutt_buffer_make(1024);
1710             mutt_buffer_strcpy(&scratch, mutt_b2s(buf));
1711             size_t scratchlen = mutt_buffer_len(&scratch);
1712             if (!(he->type & DT_MAILBOX) && (scratchlen != 0))
1713             {
1714               if ((mutt_b2s(&scratch)[scratchlen - 1] != '|') && /* not a command */
1715                   (url_check_scheme(mutt_b2s(&scratch)) == U_UNKNOWN)) /* probably a local file */
1716               {
1717                 struct ListNode *np = STAILQ_FIRST(&MuttrcStack);
1718                 if (mutt_path_to_absolute(scratch.data, np ? NONULL(np->data) : "./"))
1719                 {
1720                   mutt_buffer_reset(buf);
1721                   mutt_buffer_addstr(buf, mutt_b2s(&scratch));
1722                 }
1723                 else
1724                 {
1725                   mutt_error(_("Error: Can't build path of '%s'"), mutt_b2s(&scratch));
1726                 }
1727               }
1728             }
1729             mutt_buffer_dealloc(&scratch);
1730           }
1731           else if (IS_COMMAND(he))
1732           {
1733             struct Buffer scratch = mutt_buffer_make(1024);
1734             mutt_buffer_strcpy(&scratch, mutt_b2s(buf));
1735
1736             if (mutt_str_strcmp(buf->data, "builtin") != 0)
1737             {
1738               mutt_buffer_expand_path(&scratch);
1739             }
1740             mutt_buffer_reset(buf);
1741             mutt_buffer_addstr(buf, mutt_b2s(&scratch));
1742             mutt_buffer_dealloc(&scratch);
1743           }
1744
1745           rc = cs_he_string_set(Config, he, buf->data, err);
1746           if (CSR_RESULT(rc) != CSR_SUCCESS)
1747             return MUTT_CMD_ERROR;
1748         }
1749         continue;
1750       }
1751       else
1752       {
1753         if (bq)
1754         {
1755           // mutt_buffer_printf(err, "ACT23 set variable %s to 'yes'", buf->data);
1756           rc = cs_he_native_set(Config, he, true, err);
1757           if (CSR_RESULT(rc) != CSR_SUCCESS)
1758             return MUTT_CMD_ERROR;
1759           continue;
1760         }
1761         else
1762         {
1763           // mutt_buffer_printf(err, "ACT10 query variable %s", buf->data);
1764           if (he)
1765           {
1766             mutt_buffer_addstr(err, buf->data);
1767             mutt_buffer_addch(err, '=');
1768             mutt_buffer_reset(buf);
1769             rc = cs_he_string_get(Config, he, buf);
1770             if (CSR_RESULT(rc) != CSR_SUCCESS)
1771             {
1772               mutt_buffer_addstr(err, buf->data);
1773               return MUTT_CMD_ERROR;
1774             }
1775             pretty_var(buf->data, err);
1776           }
1777           else
1778           {
1779             const char *val = myvar_get(buf->data);
1780             if (val)
1781             {
1782               mutt_buffer_addstr(err, buf->data);
1783               mutt_buffer_addch(err, '=');
1784               pretty_var(val, err);
1785             }
1786             else
1787             {
1788               mutt_buffer_printf(err, _("%s: unknown variable"), buf->data);
1789               return MUTT_CMD_ERROR;
1790             }
1791           }
1792           break;
1793         }
1794       }
1795     }
1796
1797     if (my)
1798     {
1799       myvar_del(buf->data);
1800     }
1801     else if (bq)
1802     {
1803       if (inv)
1804       {
1805         // mutt_buffer_printf(err, "ACT25 TOGGLE bool/quad variable %s", buf->data);
1806         if (DTYPE(he->type) == DT_BOOL)
1807           bool_he_toggle(Config, he, err);
1808         else
1809           quad_he_toggle(Config, he, err);
1810       }
1811       else
1812       {
1813         // mutt_buffer_printf(err, "ACT26 UNSET bool/quad variable %s", buf->data);
1814         rc = cs_he_native_set(Config, he, false, err);
1815         if (CSR_RESULT(rc) != CSR_SUCCESS)
1816           return MUTT_CMD_ERROR;
1817       }
1818       continue;
1819     }
1820     else
1821     {
1822       rc = cs_he_string_set(Config, he, NULL, err);
1823       if (CSR_RESULT(rc) != CSR_SUCCESS)
1824         return MUTT_CMD_ERROR;
1825     }
1826   }
1827
1828   return MUTT_CMD_SUCCESS;
1829 }
1830
1831 /**
1832  * parse_setenv - Parse the 'setenv' and 'unsetenv' commands - Implements ::command_t
1833  */
1834 static enum CommandResult parse_setenv(struct Buffer *buf, struct Buffer *s,
1835                                        unsigned long data, struct Buffer *err)
1836 {
1837   char **envp = mutt_envlist_getlist();
1838
1839   bool query = false;
1840   bool unset = (data == MUTT_SET_UNSET);
1841
1842   if (!MoreArgs(s))
1843   {
1844     mutt_buffer_printf(err, _("%s: too few arguments"), "setenv");
1845     return MUTT_CMD_WARNING;
1846   }
1847
1848   if (*s->dptr == '?')
1849   {
1850     query = true;
1851     s->dptr++;
1852   }
1853
1854   /* get variable name */
1855   mutt_extract_token(buf, s, MUTT_TOKEN_EQUAL);
1856
1857   if (query)
1858   {
1859     bool found = false;
1860     while (envp && *envp)
1861     {
1862       /* This will display all matches for "^QUERY" */
1863       if (mutt_str_startswith(*envp, buf->data, CASE_MATCH))
1864       {
1865         if (!found)
1866         {
1867           mutt_endwin();
1868           found = true;
1869         }
1870         puts(*envp);
1871       }
1872       envp++;
1873     }
1874
1875     if (found)
1876     {
1877       mutt_any_key_to_continue(NULL);
1878       return MUTT_CMD_SUCCESS;
1879     }
1880
1881     mutt_buffer_printf(err, _("%s is unset"), buf->data);
1882     return MUTT_CMD_WARNING;
1883   }
1884
1885   if (unset)
1886   {
1887     if (mutt_envlist_unset(buf->data))
1888       return MUTT_CMD_SUCCESS;
1889     return MUTT_CMD_ERROR;
1890   }
1891
1892   /* set variable */
1893
1894   if (*s->dptr == '=')
1895   {
1896     s->dptr++;
1897     SKIPWS(s->dptr);
1898   }
1899
1900   if (!MoreArgs(s))
1901   {
1902     mutt_buffer_printf(err, _("%s: too few arguments"), "setenv");
1903     return MUTT_CMD_WARNING;
1904   }
1905
1906   char *name = mutt_str_strdup(buf->data);
1907   mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1908   mutt_envlist_set(name, buf->data, true);
1909   FREE(&name);
1910
1911   return MUTT_CMD_SUCCESS;
1912 }
1913
1914 /**
1915  * parse_source - Parse the 'source' command - Implements ::command_t
1916  */
1917 static enum CommandResult parse_source(struct Buffer *buf, struct Buffer *s,
1918                                        unsigned long data, struct Buffer *err)
1919 {
1920   char path[PATH_MAX];
1921
1922   do
1923   {
1924     if (mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS) != 0)
1925     {
1926       mutt_buffer_printf(err, _("source: error at %s"), s->dptr);
1927       return MUTT_CMD_ERROR;
1928     }
1929     mutt_str_strfcpy(path, buf->data, sizeof(path));
1930     mutt_expand_path(path, sizeof(path));
1931
1932     if (source_rc(path, err) < 0)
1933     {
1934       mutt_buffer_printf(err, _("source: file %s could not be sourced"), path);
1935       return MUTT_CMD_ERROR;
1936     }
1937
1938   } while (MoreArgs(s));
1939
1940   return MUTT_CMD_SUCCESS;
1941 }
1942
1943 /**
1944  * parse_spam_list - Parse the 'spam' and 'nospam' commands - Implements ::command_t
1945  */
1946 static enum CommandResult parse_spam_list(struct Buffer *buf, struct Buffer *s,
1947                                           unsigned long data, struct Buffer *err)
1948 {
1949   struct Buffer templ;
1950
1951   mutt_buffer_init(&templ);
1952
1953   /* Insist on at least one parameter */
1954   if (!MoreArgs(s))
1955   {
1956     if (data == MUTT_SPAM)
1957       mutt_buffer_strcpy(err, _("spam: no matching pattern"));
1958     else
1959       mutt_buffer_strcpy(err, _("nospam: no matching pattern"));
1960     return MUTT_CMD_ERROR;
1961   }
1962
1963   /* Extract the first token, a regex */
1964   mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1965
1966   /* data should be either MUTT_SPAM or MUTT_NOSPAM. MUTT_SPAM is for spam commands. */
1967   if (data == MUTT_SPAM)
1968   {
1969     /* If there's a second parameter, it's a template for the spam tag. */
1970     if (MoreArgs(s))
1971     {
1972       mutt_extract_token(&templ, s, MUTT_TOKEN_NO_FLAGS);
1973
1974       /* Add to the spam list. */
1975       if (mutt_replacelist_add(&SpamList, buf->data, templ.data, err) != 0)
1976       {
1977         FREE(&templ.data);
1978         return MUTT_CMD_ERROR;
1979       }
1980       FREE(&templ.data);
1981     }
1982     /* If not, try to remove from the nospam list. */
1983     else
1984     {
1985       mutt_regexlist_remove(&NoSpamList, buf->data);
1986     }
1987
1988     return MUTT_CMD_SUCCESS;
1989   }
1990   /* MUTT_NOSPAM is for nospam commands. */
1991   else if (data == MUTT_NOSPAM)
1992   {
1993     /* nospam only ever has one parameter. */
1994
1995     /* "*" is a special case. */
1996     if (mutt_str_strcmp(buf->data, "*") == 0)
1997     {
1998       mutt_replacelist_free(&SpamList);
1999       mutt_regexlist_free(&NoSpamList);
2000       return MUTT_CMD_SUCCESS;
2001     }
2002
2003     /* If it's on the spam list, just remove it. */
2004     if (mutt_replacelist_remove(&SpamList, buf->data) != 0)
2005       return MUTT_CMD_SUCCESS;
2006
2007     /* Otherwise, add it to the nospam list. */
2008     if (mutt_regexlist_add(&NoSpamList, buf->data, REG_ICASE, err) != 0)
2009       return MUTT_CMD_ERROR;
2010
2011     return MUTT_CMD_SUCCESS;
2012   }
2013
2014   /* This should not happen. */
2015   mutt_buffer_strcpy(err, "This is no good at all.");
2016   return MUTT_CMD_ERROR;
2017 }
2018
2019 /**
2020  * parse_stailq - Parse a list command - Implements ::command_t
2021  *
2022  * This is used by 'alternative_order', 'auto_view' and several others.
2023  */
2024 static enum CommandResult parse_stailq(struct Buffer *buf, struct Buffer *s,
2025                                        unsigned long data, struct Buffer *err)
2026 {
2027   do
2028   {
2029     mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
2030     add_to_stailq((struct ListHead *) data, buf->data);
2031   } while (MoreArgs(s));
2032
2033   return MUTT_CMD_SUCCESS;
2034 }
2035
2036 /**
2037  * parse_subjectrx_list - Parse the 'subjectrx' command - Implements ::command_t
2038  */
2039 static enum CommandResult parse_subjectrx_list(struct Buffer *buf, struct Buffer *s,
2040                                                unsigned long data, struct Buffer *err)
2041 {
2042   enum CommandResult rc;
2043
2044   rc = parse_replace_list(buf, s, data, err);
2045   if (rc == MUTT_CMD_SUCCESS)
2046     clear_subject_mods();
2047   return rc;
2048 }
2049
2050 /**
2051  * parse_subscribe - Parse the 'subscribe' command - Implements ::command_t
2052  */
2053 static enum CommandResult parse_subscribe(struct Buffer *buf, struct Buffer *s,
2054                                           unsigned long data, struct Buffer *err)
2055 {
2056   struct GroupList gl = STAILQ_HEAD_INITIALIZER(gl);
2057
2058   do
2059   {
2060     mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
2061
2062     if (parse_grouplist(&gl, buf, s, data, err) == -1)
2063       goto bail;
2064
2065     mutt_regexlist_remove(&UnMailLists, buf->data);
2066     mutt_regexlist_remove(&UnSubscribedLists, buf->data);
2067
2068     if (mutt_regexlist_add(&MailLists, buf->data, REG_ICASE, err) != 0)
2069       goto bail;
2070     if (mutt_regexlist_add(&SubscribedLists, buf->data, REG_ICASE, err) != 0)
2071       goto bail;
2072     if (mutt_grouplist_add_regex(&gl, buf->data, REG_ICASE, err) != 0)
2073       goto bail;
2074   } while (MoreArgs(s));
2075
2076   mutt_grouplist_destroy(&gl);
2077   return MUTT_CMD_SUCCESS;
2078
2079 bail:
2080   mutt_grouplist_destroy(&gl);
2081   return MUTT_CMD_ERROR;
2082 }
2083
2084 #ifdef USE_IMAP
2085 /**
2086  * parse_subscribe_to - Parse the 'subscribe-to' command - Implements ::command_t
2087  *
2088  * The 'subscribe-to' command allows to subscribe to an IMAP-Mailbox.
2089  * Patterns are not supported.
2090  * Use it as follows: subscribe-to =folder
2091  */
2092 static enum CommandResult parse_subscribe_to(struct Buffer *buf, struct Buffer *s,
2093                                              unsigned long data, struct Buffer *err)
2094 {
2095   if (!buf || !s || !err)
2096     return MUTT_CMD_ERROR;
2097
2098   mutt_buffer_reset(err);
2099
2100   if (MoreArgs(s))
2101   {
2102     mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
2103
2104     if (MoreArgs(s))
2105     {
2106       mutt_buffer_printf(err, _("%s: too many arguments"), "subscribe-to");
2107       return MUTT_CMD_WARNING;
2108     }
2109
2110     if (buf->data && (*buf->data != '\0'))
2111     {
2112       /* Expand and subscribe */
2113       if (imap_subscribe(mutt_expand_path(buf->data, buf->dsize), true) != 0)
2114       {
2115         mutt_buffer_printf(err, _("Could not subscribe to %s"), buf->data);
2116         return MUTT_CMD_ERROR;
2117       }
2118       else
2119       {
2120         mutt_message(_("Subscribed to %s"), buf->data);
2121         return MUTT_CMD_SUCCESS;
2122       }
2123     }
2124     else
2125     {
2126       mutt_debug(LL_DEBUG1, "Corrupted buffer");
2127       return MUTT_CMD_ERROR;
2128     }
2129   }
2130
2131   mutt_buffer_addstr(err, _("No folder specified"));
2132   return MUTT_CMD_WARNING;
2133 }
2134 #endif
2135
2136 /**
2137  * parse_tag_formats - Parse the 'tag-formats' command - Implements ::command_t
2138  */
2139 static enum CommandResult parse_tag_formats(struct Buffer *buf, struct Buffer *s,
2140                                             unsigned long data, struct Buffer *err)
2141 {
2142   if (!buf || !s)
2143     return MUTT_CMD_ERROR;
2144
2145   char *tmp = NULL;
2146
2147   while (MoreArgs(s))
2148   {
2149     char *tag = NULL, *format = NULL;
2150
2151     mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
2152     if (buf->data && (*buf->data != '\0'))
2153       tag = mutt_str_strdup(buf->data);
2154     else
2155       continue;
2156
2157     mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
2158     format = mutt_str_strdup(buf->data);
2159
2160     /* avoid duplicates */
2161     tmp = mutt_hash_find(TagFormats, format);
2162     if (tmp)
2163     {
2164       mutt_debug(LL_DEBUG3, "tag format '%s' already registered as '%s'\n", format, tmp);
2165       FREE(&tag);
2166       FREE(&format);
2167       continue;
2168     }
2169
2170     mutt_hash_insert(TagFormats, format, tag);
2171   }
2172   return MUTT_CMD_SUCCESS;
2173 }
2174
2175 /**
2176  * parse_tag_transforms - Parse the 'tag-transforms' command - Implements ::command_t
2177  */
2178 static enum CommandResult parse_tag_transforms(struct Buffer *buf, struct Buffer *s,
2179                                                unsigned long data, struct Buffer *err)
2180 {
2181   if (!buf || !s)
2182     return MUTT_CMD_ERROR;
2183
2184   char *tmp = NULL;
2185
2186   while (MoreArgs(s))
2187   {
2188     char *tag = NULL, *transform = NULL;
2189
2190     mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
2191     if (buf->data && (*buf->data != '\0'))
2192       tag = mutt_str_strdup(buf->data);
2193     else
2194       continue;
2195
2196     mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
2197     transform = mutt_str_strdup(buf->data);
2198
2199     /* avoid duplicates */
2200     tmp = mutt_hash_find(TagTransforms, tag);
2201     if (tmp)
2202     {
2203       mutt_debug(LL_DEBUG3, "tag transform '%s' already registered as '%s'\n", tag, tmp);
2204       FREE(&tag);
2205       FREE(&transform);
2206       continue;
2207     }
2208
2209     mutt_hash_insert(TagTransforms, tag, transform);
2210   }
2211   return MUTT_CMD_SUCCESS;
2212 }
2213
2214 /**
2215  * parse_unalias - Parse the 'unalias' command - Implements ::command_t
2216  */
2217 static enum CommandResult parse_unalias(struct Buffer *buf, struct Buffer *s,
2218                                         unsigned long data, struct Buffer *err)
2219 {
2220   struct Alias *a = NULL;
2221
2222   do
2223   {
2224     mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
2225
2226     if (mutt_str_strcmp("*", buf->data) == 0)
2227     {
2228       if (CurrentMenu == MENU_ALIAS)
2229       {
2230         TAILQ_FOREACH(a, &Aliases, entries)
2231         {
2232           a->del = true;
2233         }
2234         mutt_menu_set_current_redraw_full();
2235       }
2236       else
2237         mutt_aliaslist_free(&Aliases);
2238       break;
2239     }
2240     else
2241     {
2242       TAILQ_FOREACH(a, &Aliases, entries)
2243       {
2244         if (mutt_str_strcasecmp(buf->data, a->name) == 0)
2245         {
2246           if (CurrentMenu == MENU_ALIAS)
2247           {
2248             a->del = true;
2249             mutt_menu_set_current_redraw_full();
2250           }
2251           else
2252           {
2253             TAILQ_REMOVE(&Aliases, a, entries);
2254             mutt_alias_free(&a);
2255           }
2256           break;
2257         }
2258       }
2259     }
2260   } while (MoreArgs(s));
2261   return MUTT_CMD_SUCCESS;
2262 }
2263
2264 /**
2265  * parse_unalternates - Parse the 'unalternates' command - Implements ::command_t
2266  */
2267 static enum CommandResult parse_unalternates(struct Buffer *buf, struct Buffer *s,
2268                                              unsigned long data, struct Buffer *err)
2269 {
2270   alternates_clean();
2271   do
2272   {
2273     mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
2274     mutt_regexlist_remove(&Alternates, buf->data);
2275
2276     if ((mutt_str_strcmp(buf->data, "*") != 0) &&
2277         (mutt_regexlist_add(&UnAlternates, buf->data, REG_ICASE, err) != 0))
2278     {
2279       return MUTT_CMD_ERROR;
2280     }
2281
2282   } while (MoreArgs(s));
2283
2284   return MUTT_CMD_SUCCESS;
2285 }
2286
2287 /**
2288  * mutt_attachmatch_free - Free an AttachMatch - Implements ::list_free_t
2289  * @param ptr AttachMatch to free
2290  *
2291  * @note We don't free minor because it is either a pointer into major,
2292  *       or a static string.
2293  */
2294 void mutt_attachmatch_free(struct AttachMatch **ptr)
2295 {
2296   if (!ptr || !*ptr)
2297     return;
2298
2299   struct AttachMatch *am = *ptr;
2300   regfree(&am->minor_regex);
2301   FREE(&am->major);
2302   FREE(ptr);
2303 }
2304
2305 /**
2306  * parse_unattachments - Parse the 'unattachments' command - Implements ::command_t
2307  */
2308 static enum CommandResult parse_unattachments(struct Buffer *buf, struct Buffer *s,
2309                                               unsigned long data, struct Buffer *err)
2310 {
2311   char op;
2312   char *p = NULL;
2313   struct ListHead *head = NULL;
2314
2315   mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
2316   if (!buf->data || (*buf->data == '\0'))
2317   {
2318     mutt_buffer_strcpy(err, _("unattachments: no disposition"));
2319     return MUTT_CMD_WARNING;
2320   }
2321
2322   p = buf->data;
2323   op = *p++;
2324
2325   if (op == '*')
2326   {
2327     mutt_list_free_type(&AttachAllow, (list_free_t) mutt_attachmatch_free);
2328     mutt_list_free_type(&AttachExclude, (list_free_t) mutt_attachmatch_free);
2329     mutt_list_free_type(&InlineAllow, (list_free_t) mutt_attachmatch_free);
2330     mutt_list_free_type(&InlineExclude, (list_free_t) mutt_attachmatch_free);
2331     attachments_clean();
2332     return 0;
2333   }
2334
2335   if ((op != '+') && (op != '-'))
2336   {
2337     op = '+';
2338     p--;
2339   }
2340   if (mutt_str_startswith("attachment", p, CASE_IGNORE))
2341   {
2342     if (op == '+')
2343       head = &AttachAllow;
2344     else
2345       head = &AttachExclude;
2346   }
2347   else if (mutt_str_startswith("inline", p, CASE_IGNORE))
2348   {
2349     if (op == '+')
2350       head = &InlineAllow;
2351     else
2352       head = &InlineExclude;
2353   }
2354   else
2355   {
2356     mutt_buffer_strcpy(err, _("unattachments: invalid disposition"));
2357     return MUTT_CMD_ERROR;
2358   }
2359
2360   return parse_unattach_list(buf, s, head, err);
2361 }
2362
2363 /**
2364  * parse_unignore - Parse the 'unignore' command - Implements ::command_t
2365  */
2366 static enum CommandResult parse_unignore(struct Buffer *buf, struct Buffer *s,
2367                                          unsigned long data, struct Buffer *err)
2368 {
2369   do
2370   {
2371     mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
2372
2373     /* don't add "*" to the unignore list */
2374     if (strcmp(buf->data, "*") != 0)
2375       add_to_stailq(&UnIgnore, buf->data);
2376
2377     remove_from_stailq(&Ignore, buf->data);
2378   } while (MoreArgs(s));
2379
2380   return MUTT_CMD_SUCCESS;
2381 }
2382
2383 /**
2384  * parse_unlists - Parse the 'unlists' command - Implements ::command_t
2385  */
2386 static enum CommandResult parse_unlists(struct Buffer *buf, struct Buffer *s,
2387                                         unsigned long data, struct Buffer *err)
2388 {
2389   mutt_hash_free(&AutoSubscribeCache);
2390   do
2391   {
2392     mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
2393     mutt_regexlist_remove(&SubscribedLists, buf->data);
2394     mutt_regexlist_remove(&MailLists, buf->data);
2395
2396     if ((mutt_str_strcmp(buf->data, "*") != 0) &&
2397         (mutt_regexlist_add(&UnMailLists, buf->data, REG_ICASE, err) != 0))
2398     {
2399       return MUTT_CMD_ERROR;
2400     }
2401   } while (MoreArgs(s));
2402
2403   return MUTT_CMD_SUCCESS;
2404 }
2405
2406 /**
2407  * parse_unmailboxes - Parse the 'unmailboxes' command - Implements ::command_t
2408  *
2409  * This is also used by 'unvirtual-mailboxes'
2410  */
2411 static enum CommandResult parse_unmailboxes(struct Buffer *buf, struct Buffer *s,
2412                                             unsigned long data, struct Buffer *err)
2413 {
2414   bool tmp_valid = false;
2415   bool clear_all = false;
2416
2417   while (!clear_all && MoreArgs(s))
2418   {
2419     mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
2420
2421     if (mutt_str_strcmp(buf->data, "*") == 0)
2422     {
2423       clear_all = true;
2424       tmp_valid = false;
2425     }
2426     else
2427     {
2428       mutt_buffer_expand_path(buf);
2429       tmp_valid = true;
2430     }
2431
2432     struct MailboxList ml = neomutt_mailboxlist_get_all(NeoMutt, MUTT_MAILBOX_ANY);
2433     struct MailboxNode *np = NULL;
2434     struct MailboxNode *nptmp = NULL;
2435     STAILQ_FOREACH_SAFE(np, &ml, entries, nptmp)
2436     {
2437       /* Decide whether to delete all normal mailboxes or all virtual */
2438       bool virt = ((np->mailbox->magic == MUTT_NOTMUCH) && (data & MUTT_VIRTUAL));
2439       bool norm = ((np->mailbox->magic != MUTT_NOTMUCH) && !(data & MUTT_VIRTUAL));
2440       bool clear_this = clear_all && (virt || norm);
2441
2442       /* Compare against path or desc? Ensure 'buf' is valid */
2443       if (!clear_this && tmp_valid)
2444       {
2445         clear_this =
2446             (mutt_str_strcasecmp(mutt_b2s(buf), mailbox_path(np->mailbox)) == 0) ||
2447             (mutt_str_strcasecmp(mutt_b2s(buf), np->mailbox->name) == 0);
2448       }
2449
2450       if (clear_this)
2451       {
2452 #ifdef USE_SIDEBAR
2453         mutt_sb_notify_mailbox(np->mailbox, false);
2454 #endif
2455 #ifdef USE_INOTIFY
2456         mutt_monitor_remove(np->mailbox);
2457 #endif
2458         if (Context && (Context->mailbox == np->mailbox))
2459         {
2460           np->mailbox->flags |= MB_HIDDEN;
2461         }
2462         else
2463         {
2464           account_mailbox_remove(np->mailbox->account, np->mailbox);
2465         }
2466       }
2467     }
2468     neomutt_mailboxlist_clear(&ml);
2469   }
2470   return MUTT_CMD_SUCCESS;
2471 }
2472
2473 /**
2474  * parse_unmy_hdr - Parse the 'unmy_hdr' command - Implements ::command_t
2475  */
2476 static enum CommandResult parse_unmy_hdr(struct Buffer *buf, struct Buffer *s,
2477                                          unsigned long data, struct Buffer *err)
2478 {
2479   struct ListNode *np = NULL, *tmp = NULL;
2480   size_t l;
2481
2482   do
2483   {
2484     mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
2485     if (mutt_str_strcmp("*", buf->data) == 0)
2486     {
2487       mutt_list_free(&UserHeader);
2488       continue;
2489     }
2490
2491     l = mutt_str_strlen(buf->data);
2492     if (buf->data[l - 1] == ':')
2493       l--;
2494
2495     STAILQ_FOREACH_SAFE(np, &UserHeader, entries, tmp)
2496     {
2497       if ((mutt_str_strncasecmp(buf->data, np->data, l) == 0) && (np->data[l] == ':'))
2498       {
2499         STAILQ_REMOVE(&UserHeader, np, ListNode, entries);
2500         FREE(&np->data);
2501         FREE(&np);
2502       }
2503     }
2504   } while (MoreArgs(s));
2505   return MUTT_CMD_SUCCESS;
2506 }
2507
2508 /**
2509  * parse_unstailq - Parse an unlist command - Implements ::command_t
2510  *
2511  * This is used by 'unalternative_order', 'unauto_view' and several others.
2512  */
2513 static enum CommandResult parse_unstailq(struct Buffer *buf, struct Buffer *s,
2514                                          unsigned long data, struct Buffer *err)
2515 {
2516   do
2517   {
2518     mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
2519     /* Check for deletion of entire list */
2520     if (mutt_str_strcmp(buf->data, "*") == 0)
2521     {
2522       mutt_list_free((struct ListHead *) data);
2523       break;
2524     }
2525     remove_from_stailq((struct ListHead *) data, buf->data);
2526   } while (MoreArgs(s));
2527
2528   return MUTT_CMD_SUCCESS;
2529 }
2530
2531 /**
2532  * parse_unsubjectrx_list - Parse the 'unsubjectrx' command - Implements ::command_t
2533  */
2534 static enum CommandResult parse_unsubjectrx_list(struct Buffer *buf, struct Buffer *s,
2535                                                  unsigned long data, struct Buffer *err)
2536 {
2537   enum CommandResult rc;
2538
2539   rc = parse_unreplace_list(buf, s, data, err);
2540   if (rc == MUTT_CMD_SUCCESS)
2541     clear_subject_mods();
2542   return rc;
2543 }
2544
2545 /**
2546  * parse_unsubscribe - Parse the 'unsubscribe' command - Implements ::command_t
2547  */
2548 static enum CommandResult parse_unsubscribe(struct Buffer *buf, struct Buffer *s,
2549                                             unsigned long data, struct Buffer *err)
2550 {
2551   mutt_hash_free(&AutoSubscribeCache);
2552   do
2553   {
2554     mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
2555     mutt_regexlist_remove(&SubscribedLists, buf->data);
2556
2557     if ((mutt_str_strcmp(buf->data, "*") != 0) &&
2558         (mutt_regexlist_add(&UnSubscribedLists, buf->data, REG_ICASE, err) != 0))
2559     {
2560       return MUTT_CMD_ERROR;
2561     }
2562   } while (MoreArgs(s));
2563
2564   return MUTT_CMD_SUCCESS;
2565 }
2566
2567 #ifdef USE_IMAP
2568 /**
2569  * parse_unsubscribe_from - Parse the 'unsubscribe-from' command - Implements ::command_t
2570  *
2571  * The 'unsubscribe-from' command allows to unsubscribe from an IMAP-Mailbox.
2572  * Patterns are not supported.
2573  * Use it as follows: unsubscribe-from =folder
2574  */
2575 static enum CommandResult parse_unsubscribe_from(struct Buffer *buf, struct Buffer *s,
2576                                                  unsigned long data, struct Buffer *err)
2577 {
2578   if (!buf || !s || !err)
2579     return MUTT_CMD_ERROR;
2580
2581   if (MoreArgs(s))
2582   {
2583     mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
2584
2585     if (MoreArgs(s))
2586     {
2587       mutt_buffer_printf(err, _("%s: too many arguments"), "unsubscribe-from");
2588       return MUTT_CMD_WARNING;
2589     }
2590
2591     if (buf->data && (*buf->data != '\0'))
2592     {
2593       /* Expand and subscribe */
2594       if (imap_subscribe(mutt_expand_path(buf->data, buf->dsize), false) != 0)
2595       {
2596         mutt_buffer_printf(err, _("Could not unsubscribe from %s"), buf->data);
2597         return MUTT_CMD_ERROR;
2598       }
2599       else
2600       {
2601         mutt_message(_("Unsubscribed from %s"), buf->data);
2602         return MUTT_CMD_SUCCESS;
2603       }
2604     }
2605     else
2606     {
2607       mutt_debug(LL_DEBUG1, "Corrupted buffer");
2608       return MUTT_CMD_ERROR;
2609     }
2610   }
2611
2612   mutt_buffer_addstr(err, _("No folder specified"));
2613   return MUTT_CMD_WARNING;
2614 }
2615 #endif
2616
2617 /**
2618  * mutt_command_get - Get a Command by its name
2619  * @param s Command string to lookup
2620  * @retval ptr  Success, Command
2621  * @retval NULL Error, no such command
2622  */
2623 const struct Command *mutt_command_get(const char *s)
2624 {
2625   for (int i = 0; Commands[i].name; i++)
2626     if (mutt_str_strcmp(s, Commands[i].name) == 0)
2627       return &Commands[i];
2628   return NULL;
2629 }
2630
2631 #ifdef USE_LUA
2632 /**
2633  * mutt_commands_apply - Run a callback function on every Command
2634  * @param data        Data to pass to the callback function
2635  * @param application Callback function
2636  *
2637  * This is used by Lua to expose all of NeoMutt's Commands.
2638  */
2639 void mutt_commands_apply(void *data, void (*application)(void *, const struct Command *))
2640 {
2641   for (int i = 0; Commands[i].name; i++)
2642     application(data, &Commands[i]);
2643 }
2644 #endif
2645
2646 /**
2647  * mutt_extract_token - Extract one token from a string
2648  * @param dest  Buffer for the result
2649  * @param tok   Buffer containing tokens
2650  * @param flags Flags, see #TokenFlags
2651  * @retval  0 Success
2652  * @retval -1 Error
2653  */
2654 int mutt_extract_token(struct Buffer *dest, struct Buffer *tok, TokenFlags flags)
2655 {
2656   if (!dest || !tok)
2657     return -1;
2658
2659   char ch;
2660   char qc = '\0'; /* quote char */
2661   char *pc = NULL;
2662
2663   mutt_buffer_reset(dest);
2664
2665   SKIPWS(tok->dptr);
2666   while ((ch = *tok->dptr))
2667   {
2668     if (qc == '\0')
2669     {
2670       if ((IS_SPACE(ch) && !(flags & MUTT_TOKEN_SPACE)) ||
2671           ((ch == '#') && !(flags & MUTT_TOKEN_COMMENT)) ||
2672           ((ch == '=') && (flags & MUTT_TOKEN_EQUAL)) ||
2673           ((ch == '?') && (flags & MUTT_TOKEN_QUESTION)) ||
2674           ((ch == ';') && !(flags & MUTT_TOKEN_SEMICOLON)) ||
2675           ((flags & MUTT_TOKEN_PATTERN) && strchr("~%=!|", ch)))
2676       {
2677         break;
2678       }
2679     }
2680
2681     tok->dptr++;
2682
2683     if (ch == qc)
2684       qc = 0; /* end of quote */
2685     else if (!qc && ((ch == '\'') || (ch == '"')) && !(flags & MUTT_TOKEN_QUOTE))
2686       qc = ch;
2687     else if ((ch == '\\') && (qc != '\''))
2688     {
2689       if (tok->dptr[0] == '\0')
2690         return -1; /* premature end of token */
2691       switch (ch = *tok->dptr++)
2692       {
2693         case 'c':
2694         case 'C':
2695           if (tok->dptr[0] == '\0')
2696             return -1; /* premature end of token */
2697           mutt_buffer_addch(dest, (toupper((unsigned char) tok->dptr[0]) - '@') & 0x7f);
2698           tok->dptr++;
2699           break;
2700         case 'e':
2701           mutt_buffer_addch(dest, '\033'); // Escape
2702           break;
2703         case 'f':
2704           mutt_buffer_addch(dest, '\f');
2705           break;
2706         case 'n':
2707           mutt_buffer_addch(dest, '\n');
2708           break;
2709         case 'r':
2710           mutt_buffer_addch(dest, '\r');
2711           break;
2712         case 't':
2713           mutt_buffer_addch(dest, '\t');
2714           break;
2715         default:
2716           if (isdigit((unsigned char) ch) && isdigit((unsigned char) tok->dptr[0]) &&
2717               isdigit((unsigned char) tok->dptr[1]))
2718           {
2719             mutt_buffer_addch(dest, (ch << 6) + (tok->dptr[0] << 3) + tok->dptr[1] - 3504);
2720             tok->dptr += 2;
2721           }
2722           else
2723             mutt_buffer_addch(dest, ch);
2724       }
2725     }
2726     else if ((ch == '^') && (flags & MUTT_TOKEN_CONDENSE))
2727     {
2728       if (tok->dptr[0] == '\0')
2729         return -1; /* premature end of token */
2730       ch = *tok->dptr++;
2731       if (ch == '^')
2732         mutt_buffer_addch(dest, ch);
2733       else if (ch == '[')
2734         mutt_buffer_addch(dest, '\033'); // Escape
2735       else if (isalpha((unsigned char) ch))
2736         mutt_buffer_addch(dest, toupper((unsigned char) ch) - '@');
2737       else
2738       {
2739         mutt_buffer_addch(dest, '^');
2740         mutt_buffer_addch(dest, ch);
2741       }
2742     }
2743     else if ((ch == '`') && (!qc || (qc == '"')))
2744     {
2745       FILE *fp = NULL;
2746       pid_t pid;
2747       char *ptr = NULL;
2748       size_t expnlen;
2749       struct Buffer expn;
2750       int line = 0;
2751
2752       pc = tok->dptr;
2753       do
2754       {
2755         pc = strpbrk(pc, "\\`");
2756         if (pc)
2757         {
2758           /* skip any quoted chars */
2759           if (*pc == '\\')
2760             pc += 2;
2761         }
2762       } while (pc && (pc[0] != '`'));
2763       if (!pc)
2764       {
2765         mutt_debug(LL_DEBUG1, "mismatched backticks\n");
2766         return -1;
2767       }
2768       struct Buffer cmd;
2769       mutt_buffer_init(&cmd);
2770       *pc = '\0';
2771       if (flags & MUTT_TOKEN_BACKTICK_VARS)
2772       {
2773         /* recursively extract tokens to interpolate variables */
2774         mutt_extract_token(&cmd, tok,
2775                            MUTT_TOKEN_QUOTE | MUTT_TOKEN_SPACE | MUTT_TOKEN_COMMENT |
2776                                MUTT_TOKEN_SEMICOLON | MUTT_TOKEN_NOSHELL);
2777       }
2778       else
2779       {
2780         cmd.data = mutt_str_strdup(tok->dptr);
2781       }
2782       *pc = '`';
2783       pid = mutt_create_filter(cmd.data, NULL, &fp, NULL);
2784       if (pid < 0)
2785       {
2786         mutt_debug(LL_DEBUG1, "unable to fork command: %s\n", cmd.data);
2787         FREE(&cmd.data);
2788         return -1;
2789       }
2790       FREE(&cmd.data);
2791
2792       tok->dptr = pc + 1;
2793
2794       /* read line */
2795       mutt_buffer_init(&expn);
2796       expn.data = mutt_file_read_line(NULL, &expn.dsize, fp, &line, 0);
2797       mutt_file_fclose(&fp);
2798       mutt_wait_filter(pid);
2799
2800       /* if we got output, make a new string consisting of the shell output
2801        * plus whatever else was left on the original line */
2802       /* BUT: If this is inside a quoted string, directly add output to
2803        * the token */
2804       if (expn.data && qc)
2805       {
2806         mutt_buffer_addstr(dest, expn.data);
2807         FREE(&expn.data);
2808       }
2809       else if (expn.data)
2810       {
2811         expnlen = mutt_str_strlen(expn.data);
2812         tok->dsize = expnlen + mutt_str_strlen(tok->dptr) + 1;
2813         ptr = mutt_mem_malloc(tok->dsize);
2814         memcpy(ptr, expn.data, expnlen);
2815         strcpy(ptr + expnlen, tok->dptr);
2816         tok->data = mutt_str_strdup(ptr);
2817         tok->dptr = tok->data;
2818         ptr = NULL;
2819         FREE(&expn.data);
2820       }
2821     }
2822     else if ((ch == '$') && (!qc || (qc == '"')) &&
2823              ((tok->dptr[0] == '{') || isalpha((unsigned char) tok->dptr[0])))
2824     {
2825       const char *env = NULL;
2826       char *var = NULL;
2827
2828       if (tok->dptr[0] == '{')
2829       {
2830         pc = strchr(tok->dptr, '}');
2831         if (pc)
2832         {
2833           var = mutt_str_substr_dup(tok->dptr + 1, pc);
2834           tok->dptr = pc + 1;
2835
2836           if ((flags & MUTT_TOKEN_NOSHELL))
2837           {
2838             mutt_buffer_addch(dest, ch);
2839             mutt_buffer_addch(dest, '{');
2840             mutt_buffer_addstr(dest, var);
2841             mutt_buffer_addch(dest, '}');
2842             FREE(&var);
2843           }
2844         }
2845       }
2846       else
2847       {
2848         for (pc = tok->dptr; isalnum((unsigned char) *pc) || (pc[0] == '_'); pc++)
2849           ;
2850         var = mutt_str_substr_dup(tok->dptr, pc);
2851         tok->dptr = pc;
2852       }
2853       if (var)
2854       {
2855         struct Buffer result;
2856         mutt_buffer_init(&result);
2857         int rc = cs_str_string_get(Config, var, &result);
2858
2859         if (CSR_RESULT(rc) == CSR_SUCCESS)
2860         {
2861           mutt_buffer_addstr(dest, result.data);
2862           FREE(&result.data);
2863         }
2864         else if ((env = myvar_get(var)))
2865         {
2866           mutt_buffer_addstr(dest, env);
2867         }
2868         else if (!(flags & MUTT_TOKEN_NOSHELL) && (env = mutt_str_getenv(var)))
2869         {
2870           mutt_buffer_addstr(dest, env);
2871         }
2872         else
2873         {
2874           mutt_buffer_addch(dest, ch);
2875           mutt_buffer_addstr(dest, var);
2876         }
2877         FREE(&var);
2878       }
2879     }
2880     else
2881       mutt_buffer_addch(dest, ch);
2882   }
2883   mutt_buffer_addch(dest, 0); /* terminate the string */
2884   SKIPWS(tok->dptr);
2885   return 0;
2886 }
2887
2888 /**
2889  * mutt_opts_free - clean up before quitting
2890  */
2891 void mutt_opts_free(void)
2892 {
2893   mutt_list_free(&MuttrcStack);
2894
2895   FREE(&Matches);
2896
2897   mutt_aliaslist_free(&Aliases);
2898
2899   mutt_regexlist_free(&Alternates);
2900   mutt_regexlist_free(&MailLists);
2901   mutt_regexlist_free(&NoSpamList);
2902   mutt_regexlist_free(&SubscribedLists);
2903   mutt_regexlist_free(&UnAlternates);
2904   mutt_regexlist_free(&UnMailLists);
2905   mutt_regexlist_free(&UnSubscribedLists);
2906
2907   mutt_grouplist_free();
2908   mutt_hash_free(&ReverseAliases);
2909   mutt_hash_free(&TagFormats);
2910   mutt_hash_free(&TagTransforms);
2911
2912   /* Lists of strings */
2913   mutt_list_free(&AlternativeOrderList);
2914   mutt_list_free(&AutoViewList);
2915   mutt_list_free(&HeaderOrderList);
2916   mutt_list_free(&Ignore);
2917   mutt_list_free(&MailToAllow);
2918   mutt_list_free(&MimeLookupList);
2919   mutt_list_free(&Muttrc);
2920   mutt_list_free(&MuttrcStack);
2921 #ifdef USE_SIDEBAR
2922   mutt_list_free(&SidebarWhitelist);
2923 #endif
2924   mutt_list_free(&UnIgnore);
2925   mutt_list_free(&UserHeader);
2926
2927   /* Lists of AttachMatch */
2928   mutt_list_free_type(&AttachAllow, (list_free_t) mutt_attachmatch_free);
2929   mutt_list_free_type(&AttachExclude, (list_free_t) mutt_attachmatch_free);
2930   mutt_list_free_type(&InlineAllow, (list_free_t) mutt_attachmatch_free);
2931   mutt_list_free_type(&InlineExclude, (list_free_t) mutt_attachmatch_free);
2932
2933   mutt_colors_free(&Colors);
2934
2935   FREE(&CurrentFolder);
2936   FREE(&HomeDir);
2937   FREE(&LastFolder);
2938   FREE(&ShortHostname);
2939   FREE(&Username);
2940
2941   mutt_replacelist_free(&SpamList);
2942   mutt_replacelist_free(&SubjectRegexList);
2943
2944   mutt_delete_hooks(MUTT_HOOK_NO_FLAGS);
2945
2946   mutt_hist_free();
2947   mutt_keys_free();
2948
2949   mutt_regexlist_free(&NoSpamList);
2950 }
2951
2952 /**
2953  * mutt_get_hook_type - Find a hook by name
2954  * @param name Name to find
2955  * @retval num                 Hook ID, e.g. #MUTT_FOLDER_HOOK
2956  * @retval #MUTT_HOOK_NO_FLAGS Error, no matching hook
2957  */
2958 HookFlags mutt_get_hook_type(const char *name)
2959 {
2960   for (const struct Command *c = Commands; c->name; c++)
2961   {
2962     if (((c->func == mutt_parse_hook) || (c->func == mutt_parse_idxfmt_hook)) &&
2963         (mutt_str_strcasecmp(c->name, name) == 0))
2964     {
2965       return c->data;
2966     }
2967   }
2968   return MUTT_HOOK_NO_FLAGS;
2969 }
2970
2971 /**
2972  * mutt_init - Initialise NeoMutt
2973  * @param skip_sys_rc If true, don't read the system config file
2974  * @param commands    List of config commands to execute
2975  * @retval 0 Success
2976  * @retval 1 Error
2977  */
2978 int mutt_init(bool skip_sys_rc, struct ListHead *commands)
2979 {
2980   char buf[1024];
2981   int need_pause = 0;
2982   struct Buffer err = mutt_buffer_make(256);
2983
2984   mutt_grouplist_init();
2985   /* reverse alias keys need to be strdup'ed because of idna conversions */
2986   ReverseAliases = mutt_hash_new(1031, MUTT_HASH_STRCASECMP | MUTT_HASH_STRDUP_KEYS |
2987                                            MUTT_HASH_ALLOW_DUPS);
2988   TagTransforms = mutt_hash_new(64, MUTT_HASH_STRCASECMP);
2989   TagFormats = mutt_hash_new(64, MUTT_HASH_NO_FLAGS);
2990
2991   mutt_menu_init();
2992
2993   snprintf(AttachmentMarker, sizeof(AttachmentMarker), "\033]9;%" PRIu64 "\a", // Escape
2994            mutt_rand64());
2995
2996   snprintf(ProtectedHeaderMarker, sizeof(ProtectedHeaderMarker), "\033]8;%lld\a", // Escape
2997            (long long) mutt_date_epoch());
2998
2999   /* "$spoolfile" precedence: config file, environment */
3000   const char *p = mutt_str_getenv("MAIL");
3001   if (!p)
3002     p = mutt_str_getenv("MAILDIR");
3003   if (!p)
3004   {
3005 #ifdef HOMESPOOL
3006     mutt_path_concat(buf, NONULL(HomeDir), MAILPATH, sizeof(buf));
3007 #else
3008     mutt_path_concat(buf, MAILPATH, NONULL(Username), sizeof(buf));
3009 #endif
3010     p = buf;
3011   }
3012   cs_str_initial_set(Config, "spoolfile", p, NULL);
3013   cs_str_reset(Config, "spoolfile", NULL);
3014
3015   p = mutt_str_getenv("REPLYTO");
3016   if (p)
3017   {
3018     struct Buffer tmp, token;
3019
3020     snprintf(buf, sizeof(buf), "Reply-To: %s", p);
3021
3022     mutt_buffer_init(&tmp);
3023     tmp.data = buf;
3024     tmp.dptr = buf;
3025     tmp.dsize = mutt_str_strlen(buf);
3026
3027     mutt_buffer_init(&token);
3028     parse_my_hdr(&token, &tmp, 0, &err); /* adds to UserHeader */
3029     FREE(&token.data);
3030   }
3031
3032   p = mutt_str_getenv("EMAIL");
3033   if (p)
3034   {
3035     cs_str_initial_set(Config, "from", p, NULL);
3036     cs_str_reset(Config, "from", NULL);
3037   }
3038
3039   /* "$mailcap_path" precedence: config file, environment, code */
3040   const char *env_mc = mutt_str_getenv("MAILCAPS");
3041   if (env_mc)
3042     cs_str_string_set(Config, "mailcap_path", env_mc, NULL);
3043
3044   /* "$tmpdir" precedence: config file, environment, code */
3045   const char *env_tmp = mutt_str_getenv("TMPDIR");
3046   if (env_tmp)
3047     cs_str_string_set(Config, "tmpdir", env_tmp, NULL);
3048
3049   /* "$visual", "$editor" precedence: config file, environment, code */
3050   const char *env_ed = mutt_str_getenv("VISUAL");
3051   if (!env_ed)
3052     env_ed = mutt_str_getenv("EDITOR");
3053   if (env_ed)
3054   {
3055     cs_str_string_set(Config, "editor", env_ed, NULL);
3056     cs_str_string_set(Config, "visual", env_ed, NULL);
3057   }
3058
3059   C_Charset = mutt_ch_get_langinfo_charset();
3060   mutt_ch_set_charset(C_Charset);
3061
3062   Matches = mutt_mem_calloc(MatchesListsize, sizeof(char *));
3063
3064   CurrentMenu = MENU_MAIN;
3065
3066 #ifdef HAVE_GETSID
3067   /* Unset suspend by default if we're the session leader */
3068   if (getsid(0) == getpid())
3069     C_Suspend = false;
3070 #endif
3071
3072   /* RFC2368, "4. Unsafe headers"
3073    * The creator of a mailto URL can't expect the resolver of a URL to
3074    * understand more than the "subject" and "body" headers. Clients that
3075    * resolve mailto URLs into mail messages should be able to correctly
3076    * create RFC822-compliant mail messages using the "subject" and "body"
3077    * headers.  */
3078   add_to_stailq(&MailToAllow, "body");
3079   add_to_stailq(&MailToAllow, "subject");
3080   /* Cc, In-Reply-To, and References help with not breaking threading on
3081    * mailing lists, see https://github.com/neomutt/neomutt/issues/115 */
3082   add_to_stailq(&MailToAllow, "cc");
3083   add_to_stailq(&MailToAllow, "in-reply-to");
3084   add_to_stailq(&MailToAllow, "references");
3085
3086   if (STAILQ_EMPTY(&Muttrc))
3087   {
3088     const char *xdg_cfg_home = mutt_str_getenv("XDG_CONFIG_HOME");
3089
3090     if (!xdg_cfg_home && HomeDir)
3091     {
3092       snprintf(buf, sizeof(buf), "%s/.config", HomeDir);
3093       xdg_cfg_home = buf;
3094     }
3095
3096     char *config = find_cfg(HomeDir, xdg_cfg_home);
3097     if (config)
3098     {
3099       mutt_list_insert_tail(&Muttrc, config);
3100     }
3101   }
3102   else
3103   {
3104     struct ListNode *np = NULL;
3105     STAILQ_FOREACH(np, &Muttrc, entries)
3106     {
3107       mutt_str_strfcpy(buf, np->data, sizeof(buf));
3108       FREE(&np->data);
3109       mutt_expand_path(buf, sizeof(buf));
3110       np->data = mutt_str_strdup(buf);
3111       if (access(np->data, F_OK))
3112       {
3113         mutt_perror(np->data);
3114         return 1; // TEST10: neomutt -F missing
3115       }
3116     }
3117   }
3118
3119   if (!STAILQ_EMPTY(&Muttrc))
3120   {
3121     cs_str_string_set(Config, "alias_file", STAILQ_FIRST(&Muttrc)->data, NULL);
3122   }
3123
3124   /* Process the global rc file if it exists and the user hasn't explicitly
3125    * requested not to via "-n".  */
3126   if (!skip_sys_rc)
3127   {
3128     do
3129     {
3130       if (mutt_set_xdg_path(XDG_CONFIG_DIRS, buf, sizeof(buf)))
3131         break;
3132
3133       snprintf(buf, sizeof(buf), "%s/neomuttrc", SYSCONFDIR);
3134       if (access(buf, F_OK) == 0)
3135         break;
3136
3137       snprintf(buf, sizeof(buf), "%s/Muttrc", SYSCONFDIR);
3138       if (access(buf, F_OK) == 0)
3139         break;
3140
3141       snprintf(buf, sizeof(buf), "%s/neomuttrc", PKGDATADIR);
3142       if (access(buf, F_OK) == 0)
3143         break;
3144
3145       snprintf(buf, sizeof(buf), "%s/Muttrc", PKGDATADIR);
3146     } while (false);
3147
3148     if (access(buf, F_OK) == 0)
3149     {
3150       if (source_rc(buf, &err) != 0)
3151       {
3152         mutt_error("%s", err.data);
3153         need_pause = 1; // TEST11: neomutt (error in /etc/neomuttrc)
3154       }
3155     }
3156   }
3157
3158   /* Read the user's initialization file.  */
3159   struct ListNode *np = NULL;
3160   STAILQ_FOREACH(np, &Muttrc, entries)
3161   {
3162     if (np->data)
3163     {
3164       if (source_rc(np->data, &err) != 0)
3165       {
3166         mutt_error("%s", err.data);
3167         need_pause = 1; // TEST12: neomutt (error in ~/.neomuttrc)
3168       }
3169     }
3170   }
3171
3172   if (execute_commands(commands) != 0)
3173     need_pause = 1; // TEST13: neomutt -e broken
3174
3175   if (!get_hostname())
3176     return 1;
3177
3178   if (!C_Realname)
3179   {
3180     struct passwd *pw = getpwuid(getuid());
3181     if (pw)
3182     {
3183       char name[256];
3184       C_Realname = mutt_str_strdup(mutt_gecos_name(name, sizeof(name), pw));
3185     }
3186   }
3187   cs_str_initial_set(Config, "realname", C_Realname, NULL);
3188
3189   if (need_pause && !OptNoCurses)
3190   {
3191     log_queue_flush(log_disp_terminal);
3192     if (mutt_any_key_to_continue(NULL) == 'q')
3193       return 1; // TEST14: neomutt -e broken (press 'q')
3194   }
3195
3196   mutt_file_mkdir(C_Tmpdir, S_IRWXU);
3197
3198   mutt_hist_init();
3199   mutt_hist_read_file();
3200
3201 #ifdef USE_NOTMUCH
3202   if (C_VirtualSpoolfile)
3203   {
3204     /* Find the first virtual folder and open it */
3205     struct MailboxList ml = neomutt_mailboxlist_get_all(NeoMutt, MUTT_NOTMUCH);
3206     struct MailboxNode *mp = STAILQ_FIRST(&ml);
3207     if (mp)
3208       cs_str_string_set(Config, "spoolfile", mailbox_path(mp->mailbox), NULL);
3209     neomutt_mailboxlist_clear(&ml);
3210   }
3211 #endif
3212
3213   FREE(&err.data);
3214   return 0;
3215 }
3216
3217 /**
3218  * mutt_parse_rc_line - Parse a line of user config
3219  * @param line  config line to read
3220  * @param token scratch buffer to be used by parser
3221  * @param err   where to write error messages
3222  * @retval #CommandResult Result e.g. #MUTT_CMD_SUCCESS
3223  *
3224  * Caller should free token->data when finished.  the reason for this variable
3225  * is to avoid having to allocate and deallocate a lot of memory if we are
3226  * parsing many lines.  the caller can pass in the memory to use, which avoids
3227  * having to create new space for every call to this function.
3228  */
3229 enum CommandResult mutt_parse_rc_line(/* const */ char *line,
3230                                       struct Buffer *token, struct Buffer *err)
3231 {
3232   if (!line || !*line)
3233     return 0;
3234
3235   int i;
3236   enum CommandResult rc = MUTT_CMD_SUCCESS;
3237
3238   struct Buffer expn = mutt_buffer_make(0);
3239   mutt_buffer_addstr(&expn, line);
3240   expn.dptr = expn.data;
3241
3242   *err->data = 0;
3243
3244   SKIPWS(expn.dptr);
3245   while (*expn.dptr != '\0')
3246   {
3247     if (*expn.dptr == '#')
3248       break; /* rest of line is a comment */
3249     if (*expn.dptr == ';')
3250     {
3251       expn.dptr++;
3252       continue;
3253     }
3254     mutt_extract_token(token, &expn, MUTT_TOKEN_NO_FLAGS);
3255     for (i = 0; Commands[i].name; i++)
3256     {
3257       if (mutt_str_strcmp(token->data, Commands[i].name) == 0)
3258       {
3259         rc = Commands[i].func(token, &expn, Commands[i].data, err);
3260         if (rc != MUTT_CMD_SUCCESS)
3261         {              /* -1 Error, +1 Finish */
3262           goto finish; /* Propagate return code */
3263         }
3264         break; /* Continue with next command */
3265       }
3266     }
3267     if (!Commands[i].name)
3268     {
3269       mutt_buffer_printf(err, _("%s: unknown command"), NONULL(token->data));
3270       rc = MUTT_CMD_ERROR;
3271       break; /* Ignore the rest of the line */
3272     }
3273   }
3274 finish:
3275   mutt_buffer_dealloc(&expn);
3276   return rc;
3277 }
3278
3279 /**
3280  * mutt_query_variables - Implement the -Q command line flag
3281  * @param queries List of query strings
3282  * @retval 0 Success, all queries exist
3283  * @retval 1 Error
3284  */
3285 int mutt_query_variables(struct ListHead *queries)
3286 {
3287   struct Buffer value = mutt_buffer_make(256);
3288   struct Buffer tmp = mutt_buffer_make(256);
3289   int rc = 0;
3290
3291   struct ListNode *np = NULL;
3292   STAILQ_FOREACH(np, queries, entries)
3293   {
3294     mutt_buffer_reset(&value);
3295
3296     struct HashElem *he = cs_get_elem(Config, np->data);
3297     if (!he)
3298     {
3299       rc = 1;
3300       continue;
3301     }
3302
3303     int rv = cs_he_string_get(Config, he, &value);
3304     if (CSR_RESULT(rv) != CSR_SUCCESS)
3305     {
3306       rc = 1;
3307       continue;
3308     }
3309
3310     int type = DTYPE(he->type);
3311     if (IS_PATH(he) && !(he->type & DT_MAILBOX))
3312       mutt_pretty_mailbox(value.data, value.dsize);
3313
3314     if ((type != DT_BOOL) && (type != DT_NUMBER) && (type != DT_LONG) && (type != DT_QUAD))
3315     {
3316       mutt_buffer_reset(&tmp);
3317       pretty_var(value.data, &tmp);
3318       mutt_buffer_strcpy(&value, tmp.data);
3319     }
3320
3321     dump_config_neo(Config, he, &value, NULL, CS_DUMP_NO_FLAGS, stdout);
3322   }
3323
3324   mutt_buffer_dealloc(&value);
3325   mutt_buffer_dealloc(&tmp);
3326
3327   return rc; // TEST16: neomutt -Q charset
3328 }
3329
3330 /**
3331  * query_quadoption - Ask the user a quad-question
3332  * @param opt    Option to use
3333  * @param prompt Message to show to the user
3334  * @retval #QuadOption Result, e.g. #MUTT_NO
3335  */
3336 enum QuadOption query_quadoption(enum QuadOption opt, const char *prompt)
3337 {
3338   switch (opt)
3339   {
3340     case MUTT_YES:
3341     case MUTT_NO:
3342       return opt;
3343
3344     default:
3345       opt = mutt_yesorno(prompt, (opt == MUTT_ASKYES) ? MUTT_YES : MUTT_NO);
3346       mutt_window_clearline(MuttMessageWindow, 0);
3347       return opt;
3348   }
3349
3350   /* not reached */
3351 }
3352
3353 /**
3354  * mutt_command_complete - Complete a command name
3355  * @param buf     Buffer for the result
3356  * @param buflen  Length of the buffer
3357  * @param pos     Cursor position in the buffer
3358  * @param numtabs Number of times the user has hit 'tab'
3359  * @retval 1 Success, a match
3360  * @retval 0 Error, no match
3361  */
3362 int mutt_command_complete(char *buf, size_t buflen, int pos, int numtabs)
3363 {
3364   char *pt = buf;
3365   int num;
3366   int spaces; /* keep track of the number of leading spaces on the line */
3367   struct MyVar *myv = NULL;
3368
3369   SKIPWS(buf);
3370   spaces = buf - pt;
3371
3372   pt = buf + pos - spaces;
3373   while ((pt > buf) && !isspace((unsigned char) *pt))
3374     pt--;
3375
3376   if (pt == buf) /* complete cmd */
3377   {
3378     /* first TAB. Collect all the matches */
3379     if (numtabs == 1)
3380     {
3381       NumMatched = 0;
3382       mutt_str_strfcpy(UserTyped, pt, sizeof(UserTyped));
3383       memset(Matches, 0, MatchesListsize);
3384       memset(Completed, 0, sizeof(Completed));
3385       for (num = 0; Commands[num].name; num++)
3386         candidate(UserTyped, Commands[num].name, Completed, sizeof(Completed));
3387       matches_ensure_morespace(NumMatched);
3388       Matches[NumMatched++] = UserTyped;
3389
3390       /* All matches are stored. Longest non-ambiguous string is ""
3391        * i.e. don't change 'buf'. Fake successful return this time */
3392       if (UserTyped[0] == '\0')
3393         return 1;
3394     }
3395
3396     if ((Completed[0] == '\0') && (UserTyped[0] != '\0'))
3397       return 0;
3398
3399     /* NumMatched will _always_ be at least 1 since the initial
3400      * user-typed string is always stored */
3401     if ((numtabs == 1) && (NumMatched == 2))
3402       snprintf(Completed, sizeof(Completed), "%s", Matches[0]);
3403     else if ((numtabs > 1) && (NumMatched > 2))
3404     {
3405       /* cycle through all the matches */
3406       snprintf(Completed, sizeof(Completed), "%s", Matches[(numtabs - 2) % NumMatched]);
3407     }
3408
3409     /* return the completed command */
3410     strncpy(buf, Completed, buflen - spaces);
3411   }
3412   else if (mutt_str_startswith(buf, "set", CASE_MATCH) ||
3413            mutt_str_startswith(buf, "unset", CASE_MATCH) ||
3414            mutt_str_startswith(buf, "reset", CASE_MATCH) ||
3415            mutt_str_startswith(buf, "toggle", CASE_MATCH))
3416   { /* complete variables */
3417     static const char *const prefixes[] = { "no", "inv", "?", "&", 0 };
3418
3419     pt++;
3420     /* loop through all the possible prefixes (no, inv, ...) */
3421     if (mutt_str_startswith(buf, "set", CASE_MATCH))
3422     {
3423       for (num = 0; prefixes[num]; num++)
3424       {
3425         if (mutt_str_startswith(pt, prefixes[num], CASE_MATCH))
3426         {
3427           pt += mutt_str_strlen(prefixes[num]);
3428           break;
3429         }
3430       }
3431     }
3432
3433     /* first TAB. Collect all the matches */
3434     if (numtabs == 1)
3435     {
3436       NumMatched = 0;
3437       mutt_str_strfcpy(UserTyped, pt, sizeof(UserTyped));
3438       memset(Matches, 0, MatchesListsize);
3439       memset(Completed, 0, sizeof(Completed));
3440       for (num = 0; MuttVars[num].name; num++)
3441         candidate(UserTyped, MuttVars[num].name, Completed, sizeof(Completed));
3442       TAILQ_FOREACH(myv, &MyVars, entries)
3443       {
3444         candidate(UserTyped, myv->name, Completed, sizeof(Completed));
3445       }
3446       matches_ensure_morespace(NumMatched);
3447       Matches[NumMatched++] = UserTyped;
3448
3449       /* All matches are stored. Longest non-ambiguous string is ""
3450        * i.e. don't change 'buf'. Fake successful return this time */
3451       if (UserTyped[0] == '\0')
3452         return 1;
3453     }
3454
3455     if ((Completed[0] == 0) && UserTyped[0])
3456       return 0;
3457
3458     /* NumMatched will _always_ be at least 1 since the initial
3459      * user-typed string is always stored */
3460     if ((numtabs == 1) && (NumMatched == 2))
3461       snprintf(Completed, sizeof(Completed), "%s", Matches[0]);
3462     else if ((numtabs > 1) && (NumMatched > 2))
3463     {
3464       /* cycle through all the matches */
3465       snprintf(Completed, sizeof(Completed), "%s", Matches[(numtabs - 2) % NumMatched]);
3466     }
3467
3468     strncpy(pt, Completed, buf + buflen - pt - spaces);
3469   }
3470   else if (mutt_str_startswith(buf, "exec", CASE_MATCH))
3471   {
3472     const struct Binding *menu = km_get_table(CurrentMenu);
3473
3474     if (!menu && (CurrentMenu != MENU_PAGER))
3475       menu = OpGeneric;
3476
3477     pt++;
3478     /* first TAB. Collect all the matches */
3479     if (numtabs == 1)
3480     {
3481       NumMatched = 0;
3482       mutt_str_strfcpy(UserTyped, pt, sizeof(UserTyped));
3483       memset(Matches, 0, MatchesListsize);
3484       memset(Completed, 0, sizeof(Completed));
3485       for (num = 0; menu[num].name; num++)
3486         candidate(UserTyped, menu[num].name, Completed, sizeof(Completed));
3487       /* try the generic menu */
3488       if ((Completed[0] == '\0') && (CurrentMenu != MENU_PAGER))
3489       {
3490         menu = OpGeneric;
3491         for (num = 0; menu[num].name; num++)
3492           candidate(UserTyped, menu[num].name, Completed, sizeof(Completed));
3493       }
3494       matches_ensure_morespace(NumMatched);
3495       Matches[NumMatched++] = UserTyped;
3496
3497       /* All matches are stored. Longest non-ambiguous string is ""
3498        * i.e. don't change 'buf'. Fake successful return this time */
3499       if (UserTyped[0] == '\0')
3500         return 1;
3501     }
3502
3503     if ((Completed[0] == '\0') && (UserTyped[0] != '\0'))
3504       return 0;
3505
3506     /* NumMatched will _always_ be at least 1 since the initial
3507      * user-typed string is always stored */
3508     if ((numtabs == 1) && (NumMatched == 2))
3509       snprintf(Completed, sizeof(Completed), "%s", Matches[0]);
3510     else if ((numtabs > 1) && (NumMatched > 2))
3511     {
3512       /* cycle through all the matches */
3513       snprintf(Completed, sizeof(Completed), "%s", Matches[(numtabs - 2) % NumMatched]);
3514     }
3515
3516     strncpy(pt, Completed, buf + buflen - pt - spaces);
3517   }
3518   else
3519     return 0;
3520
3521   return 1;
3522 }
3523
3524 /**
3525  * mutt_label_complete - Complete a label name
3526  * @param buf     Buffer for the result
3527  * @param buflen  Length of the buffer
3528  * @param numtabs Number of times the user has hit 'tab'
3529  * @retval 1 Success, a match
3530  * @retval 0 Error, no match
3531  */
3532 int mutt_label_complete(char *buf, size_t buflen, int numtabs)
3533 {
3534   char *pt = buf;
3535   int spaces; /* keep track of the number of leading spaces on the line */
3536
3537   if (!Context || !Context->mailbox->label_hash)
3538     return 0;
3539
3540   SKIPWS(buf);
3541   spaces = buf - pt;
3542
3543   /* first TAB. Collect all the matches */
3544   if (numtabs == 1)
3545   {
3546     struct HashElem *entry = NULL;
3547     struct HashWalkState state = { 0 };
3548
3549     NumMatched = 0;
3550     mutt_str_strfcpy(UserTyped, buf, sizeof(UserTyped));
3551     memset(Matches, 0, MatchesListsize);
3552     memset(Completed, 0, sizeof(Completed));
3553     while ((entry = mutt_hash_walk(Context->mailbox->label_hash, &state)))
3554       candidate(UserTyped, entry->key.strkey, Completed, sizeof(Completed));
3555     matches_ensure_morespace(NumMatched);
3556     qsort(Matches, NumMatched, sizeof(char *), (sort_t *) mutt_str_strcasecmp);
3557     Matches[NumMatched++] = UserTyped;
3558
3559     /* All matches are stored. Longest non-ambiguous string is ""
3560      * i.e. don't change 'buf'. Fake successful return this time */
3561     if (UserTyped[0] == '\0')
3562       return 1;
3563   }
3564
3565   if ((Completed[0] == '\0') && (UserTyped[0] != '\0'))
3566     return 0;
3567
3568   /* NumMatched will _always_ be at least 1 since the initial
3569    * user-typed string is always stored */
3570   if ((numtabs == 1) && (NumMatched == 2))
3571     snprintf(Completed, sizeof(Completed), "%s", Matches[0]);
3572   else if ((numtabs > 1) && (NumMatched > 2))
3573   {
3574     /* cycle through all the matches */
3575     snprintf(Completed, sizeof(Completed), "%s", Matches[(numtabs - 2) % NumMatched]);
3576   }
3577
3578   /* return the completed label */
3579   strncpy(buf, Completed, buflen - spaces);
3580
3581   return 1;
3582 }
3583
3584 #ifdef USE_NOTMUCH
3585 /**
3586  * mutt_nm_query_complete - Complete to the nearest notmuch tag
3587  * @param buf     Buffer for the result
3588  * @param buflen  Length of the buffer
3589  * @param pos     Cursor position in the buffer
3590  * @param numtabs Number of times the user has hit 'tab'
3591  * @retval true  Success, a match
3592  * @retval false Error, no match
3593  *
3594  * Complete the nearest "tag:"-prefixed string previous to pos.
3595  */
3596 bool mutt_nm_query_complete(char *buf, size_t buflen, int pos, int numtabs)
3597 {
3598   char *pt = buf;
3599   int spaces;
3600
3601   SKIPWS(buf);
3602   spaces = buf - pt;
3603
3604   pt = (char *) mutt_str_rstrnstr((char *) buf, pos, "tag:");
3605   if (pt)
3606   {
3607     pt += 4;
3608     if (numtabs == 1)
3609     {
3610       /* First TAB. Collect all the matches */
3611       complete_all_nm_tags(pt);
3612
3613       /* All matches are stored. Longest non-ambiguous string is ""
3614        * i.e. don't change 'buf'. Fake successful return this time.  */
3615       if (UserTyped[0] == '\0')
3616         return true;
3617     }
3618
3619     if ((Completed[0] == '\0') && (UserTyped[0] != '\0'))
3620       return false;
3621
3622     /* NumMatched will _always_ be at least 1 since the initial
3623      * user-typed string is always stored */
3624     if ((numtabs == 1) && (NumMatched == 2))
3625       snprintf(Completed, sizeof(Completed), "%s", Matches[0]);
3626     else if ((numtabs > 1) && (NumMatched > 2))
3627     {
3628       /* cycle through all the matches */
3629       snprintf(Completed, sizeof(Completed), "%s", Matches[(numtabs - 2) % NumMatched]);
3630     }
3631
3632     /* return the completed query */
3633     strncpy(pt, Completed, buf + buflen - pt - spaces);
3634   }
3635   else
3636     return false;
3637
3638   return true;
3639 }
3640 #endif
3641
3642 #ifdef USE_NOTMUCH
3643 /**
3644  * mutt_nm_tag_complete - Complete to the nearest notmuch tag
3645  * @param buf     Buffer for the result
3646  * @param buflen  Length of the buffer
3647  * @param numtabs Number of times the user has hit 'tab'
3648  * @retval true  Success, a match
3649  * @retval false Error, no match
3650  *
3651  * Complete the nearest "+" or "-" -prefixed string previous to pos.
3652  */
3653 bool mutt_nm_tag_complete(char *buf, size_t buflen, int numtabs)
3654 {
3655   if (!buf)
3656     return false;
3657
3658   char *pt = buf;
3659
3660   /* Only examine the last token */
3661   char *last_space = strrchr(buf, ' ');
3662   if (last_space)
3663     pt = (last_space + 1);
3664
3665   /* Skip the +/- */
3666   if ((pt[0] == '+') || (pt[0] == '-'))
3667     pt++;
3668
3669   if (numtabs == 1)
3670   {
3671     /* First TAB. Collect all the matches */
3672     complete_all_nm_tags(pt);
3673
3674     /* All matches are stored. Longest non-ambiguous string is ""
3675      * i.e. don't change 'buf'. Fake successful return this time.  */
3676     if (UserTyped[0] == '\0')
3677       return true;
3678   }
3679
3680   if ((Completed[0] == '\0') && (UserTyped[0] != '\0'))
3681     return false;
3682
3683   /* NumMatched will _always_ be at least 1 since the initial
3684    * user-typed string is always stored */
3685   if ((numtabs == 1) && (NumMatched == 2))
3686     snprintf(Completed, sizeof(Completed), "%s", Matches[0]);
3687   else if ((numtabs > 1) && (NumMatched > 2))
3688   {
3689     /* cycle through all the matches */
3690     snprintf(Completed, sizeof(Completed), "%s", Matches[(numtabs - 2) % NumMatched]);
3691   }
3692
3693   /* return the completed query */
3694   strncpy(pt, Completed, buf + buflen - pt);
3695
3696   return true;
3697 }
3698 #endif
3699
3700 /**
3701  * mutt_var_value_complete - Complete a variable/value
3702  * @param buf    Buffer for the result
3703  * @param buflen Length of the buffer
3704  * @param pos    Cursor position in the buffer
3705  */
3706 int mutt_var_value_complete(char *buf, size_t buflen, int pos)
3707 {
3708   char *pt = buf;
3709
3710   if (buf[0] == '\0')
3711     return 0;
3712
3713   SKIPWS(buf);
3714   const int spaces = buf - pt;
3715
3716   pt = buf + pos - spaces;
3717   while ((pt > buf) && !isspace((unsigned char) *pt))
3718     pt--;
3719   pt++;           /* move past the space */
3720   if (*pt == '=') /* abort if no var before the '=' */
3721     return 0;
3722
3723   if (mutt_str_startswith(buf, "set", CASE_MATCH))
3724   {
3725     const char *myvarval = NULL;
3726     char var[256];
3727     mutt_str_strfcpy(var, pt, sizeof(var));
3728     /* ignore the trailing '=' when comparing */
3729     int vlen = mutt_str_strlen(var);
3730     if (vlen == 0)
3731       return 0;
3732
3733     var[vlen - 1] = '\0';
3734
3735     struct HashElem *he = cs_get_elem(Config, var);
3736     if (!he)
3737     {
3738       myvarval = myvar_get(var);
3739       if (myvarval)
3740       {
3741         struct Buffer pretty = mutt_buffer_make(256);
3742         pretty_var(myvarval, &pretty);
3743         snprintf(pt, buflen - (pt - buf), "%s=%s", var, pretty.data);
3744         mutt_buffer_dealloc(&pretty);
3745         return 1;
3746       }
3747       return 0; /* no such variable. */
3748     }
3749     else
3750     {
3751       struct Buffer value = mutt_buffer_make(256);
3752       struct Buffer pretty = mutt_buffer_make(256);
3753       int rc = cs_he_string_get(Config, he, &value);
3754       if (CSR_RESULT(rc) == CSR_SUCCESS)
3755       {
3756         pretty_var(value.data, &pretty);
3757         snprintf(pt, buflen - (pt - buf), "%s=%s", var, pretty.data);
3758         mutt_buffer_dealloc(&value);
3759         mutt_buffer_dealloc(&pretty);
3760         return 0;
3761       }
3762       mutt_buffer_dealloc(&value);
3763       mutt_buffer_dealloc(&pretty);
3764       return 1;
3765     }
3766   }
3767   return 0;
3768 }
3769
3770 /**
3771  * init_config - Initialise the config system
3772  * @retval ptr New Config Set
3773  */
3774 struct ConfigSet *init_config(size_t size)
3775 {
3776   struct ConfigSet *cs = cs_new(size);
3777
3778   address_init(cs);
3779   bool_init(cs);
3780   enum_init(cs);
3781   long_init(cs);
3782   mbtable_init(cs);
3783   number_init(cs);
3784   quad_init(cs);
3785   regex_init(cs);
3786   slist_init(cs);
3787   sort_init(cs);
3788   string_init(cs);
3789
3790   if (!cs_register_variables(cs, MuttVars, 0))
3791   {
3792     mutt_error("cs_register_variables() failed");
3793     cs_free(&cs);
3794     return NULL;
3795   }
3796
3797   return cs;
3798 }
3799
3800 /**
3801  * charset_validator - Validate the "charset" config variable - Implements ::cs_validator()
3802  */
3803 int charset_validator(const struct ConfigSet *cs, const struct ConfigDef *cdef,
3804                       intptr_t value, struct Buffer *err)
3805 {
3806   if (value == 0)
3807     return CSR_SUCCESS;
3808
3809   const char *str = (const char *) value;
3810
3811   if ((strcmp(cdef->name, "charset") == 0) && strchr(str, ':'))
3812   {
3813     mutt_buffer_printf(
3814         err, _("'charset' must contain exactly one character set name"));
3815     return CSR_ERR_INVALID;
3816   }
3817
3818   int rc = CSR_SUCCESS;
3819   bool strict = (strcmp(cdef->name, "send_charset") == 0);
3820   char *q = NULL;
3821   char *s = mutt_str_strdup(str);
3822
3823   for (char *p = strtok_r(s, ":", &q); p; p = strtok_r(NULL, ":", &q))
3824   {
3825     if (!*p)
3826       continue;
3827     if (!mutt_ch_check_charset(p, strict))
3828     {
3829       rc = CSR_ERR_INVALID;
3830       mutt_buffer_printf(err, _("Invalid value for option %s: %s"), cdef->name, p);
3831       break;
3832     }
3833   }
3834
3835   FREE(&s);
3836   return rc;
3837 }
3838
3839 #ifdef USE_HCACHE
3840 /**
3841  * hcache_validator - Validate the "header_cache_backend" config variable - Implements ::cs_validator()
3842  */
3843 int hcache_validator(const struct ConfigSet *cs, const struct ConfigDef *cdef,
3844                      intptr_t value, struct Buffer *err)
3845 {
3846   if (value == 0)
3847     return CSR_SUCCESS;
3848
3849   const char *str = (const char *) value;
3850
3851   if (mutt_hcache_is_valid_backend(str))
3852     return CSR_SUCCESS;
3853
3854   mutt_buffer_printf(err, _("Invalid value for option %s: %s"), cdef->name, str);
3855   return CSR_ERR_INVALID;
3856 }
3857 #endif
3858
3859 /**
3860  * pager_validator - Check for config variables that can't be set from the pager - Implements ::cs_validator()
3861  */
3862 int pager_validator(const struct ConfigSet *cs, const struct ConfigDef *cdef,
3863                     intptr_t value, struct Buffer *err)
3864 {
3865   if (CurrentMenu == MENU_PAGER)
3866   {
3867     mutt_buffer_printf(err, _("Option %s may not be set or reset from the pager"),
3868                        cdef->name);
3869     return CSR_ERR_INVALID;
3870   }
3871
3872   return CSR_SUCCESS;
3873 }
3874
3875 /**
3876  * multipart_validator - Validate the "show_multipart_alternative" config variable - Implements ::cs_validator()
3877  */
3878 int multipart_validator(const struct ConfigSet *cs, const struct ConfigDef *cdef,
3879                         intptr_t value, struct Buffer *err)
3880 {
3881   if (value == 0)
3882     return CSR_SUCCESS;
3883
3884   const char *str = (const char *) value;
3885
3886   if ((mutt_str_strcmp(str, "inline") == 0) || (mutt_str_strcmp(str, "info") == 0))
3887     return CSR_SUCCESS;
3888
3889   mutt_buffer_printf(err, _("Invalid value for option %s: %s"), cdef->name, str);
3890   return CSR_ERR_INVALID;
3891 }
3892
3893 /**
3894  * reply_validator - Validate the "reply_regex" config variable - Implements ::cs_validator()
3895  */
3896 int reply_validator(const struct ConfigSet *cs, const struct ConfigDef *cdef,
3897                     intptr_t value, struct Buffer *err)
3898 {
3899   if (pager_validator(cs, cdef, value, err) != CSR_SUCCESS)
3900     return CSR_ERR_INVALID;
3901
3902   if (!OptAttachMsg)
3903     return CSR_SUCCESS;
3904
3905   mutt_buffer_printf(err, _("Option %s may not be set when in attach-message mode"),
3906                      cdef->name);
3907   return CSR_ERR_INVALID;
3908 }