3 * Config/command parsing
6 * Copyright (C) 1996-2002,2010,2013,2016 Michael R. Elkins <me@mutt.org>
7 * Copyright (C) 2019 Pietro Cerutti <gahr@gahr.ch>
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
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
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/>.
25 * @page init Config/command parsing
27 * Config/command parsing
42 #include <sys/utsname.h>
44 #include "mutt/mutt.h"
45 #include "address/lib.h"
46 #include "email/lib.h"
53 #include "hcache/hcache.h"
56 #include "mutt_menu.h"
57 #include "mutt_parse.h"
58 #include "mutt_window.h"
66 #include "notmuch/mutt_notmuch.h"
69 #include "imap/imap.h"
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);
81 #define NUM_VARS mutt_array_size(MuttVars)
82 #define NUM_COMMANDS mutt_array_size(Commands)
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 };
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;
95 /* List of tags found in last call to mutt_nm_query_complete(). */
96 static char **nm_tags;
100 * enum GroupState - Type of email address group
104 GS_NONE, ///< Group is missing an argument
105 GS_RX, ///< Entry is a regular expression
106 GS_ADDR, ///< Entry is an address
110 * add_to_stailq - Add a string to a list
111 * @param head String list
112 * @param str String to add
114 * @note Duplicate or empty strings will not be added
116 static void add_to_stailq(struct ListHead *head, const char *str)
118 /* don't add a NULL or empty string to the list */
119 if (!str || (*str == '\0'))
122 /* check to make sure the item is not already on this list */
123 struct ListNode *np = NULL;
124 STAILQ_FOREACH(np, head, entries)
126 if (mutt_str_strcasecmp(str, np->data) == 0)
131 mutt_list_insert_tail(head, mutt_str_strdup(str));
135 * alternates_clean - Clear the recipient valid flag of all emails
137 static void alternates_clean(void)
142 for (int i = 0; i < Context->mailbox->msg_count; i++)
143 Context->mailbox->emails[i]->recip_valid = false;
147 * attachments_clean - always wise to do what someone else did before
149 static void attachments_clean(void)
154 for (int i = 0; i < Context->mailbox->msg_count; i++)
155 Context->mailbox->emails[i]->attach_valid = false;
159 * matches_ensure_morespace - Allocate more space for auto-completion
160 * @param current Current allocation
162 static void matches_ensure_morespace(int current)
164 if (current <= (MatchesListsize - 2))
167 int base_space = MAX(NUM_VARS, NUM_COMMANDS) + 1;
168 int extra_space = MatchesListsize - base_space;
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;
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
183 * Changes the dest buffer if necessary/possible to aid completion.
185 static void candidate(char *user, const char *src, char *dest, size_t dlen)
187 if (!dest || !user || !src)
190 if (strstr(src, user) != src)
193 matches_ensure_morespace(NumMatched);
194 Matches[NumMatched++] = src;
196 mutt_str_strfcpy(dest, src, dlen);
200 for (l = 0; src[l] && src[l] == dest[l]; l++)
207 * clear_subject_mods - Clear out all modified email subjects
209 static void clear_subject_mods(void)
214 for (int i = 0; i < Context->mailbox->msg_count; i++)
215 FREE(&Context->mailbox->emails[i]->env->disp_subj);
220 * complete_all_nm_tags - Pass a list of Notmuch tags to the completion code
221 * @param pt List of all Notmuch tags
225 static int complete_all_nm_tags(const char *pt)
231 mutt_str_strfcpy(UserTyped, pt, sizeof(UserTyped));
232 memset(Matches, 0, MatchesListsize);
233 memset(Completed, 0, sizeof(Completed));
235 nm_db_longrun_init(Context->mailbox, false);
237 /* Work out how many tags there are. */
238 if (nm_get_all_tags(Context->mailbox, NULL, &tag_count_1) || (tag_count_1 == 0))
241 /* Free the old list, if any. */
244 for (int i = 0; nm_tags[i]; i++)
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;
252 /* Get all the tags. */
253 if (nm_get_all_tags(Context->mailbox, nm_tags, &tag_count_2) || (tag_count_1 != tag_count_2))
257 nm_db_longrun_done(Context->mailbox);
261 /* Put them into the completion machinery. */
262 for (int num = 0; num < tag_count_1; num++)
264 candidate(UserTyped, nm_tags[num], Completed, sizeof(Completed));
267 matches_ensure_morespace(NumMatched);
268 Matches[NumMatched++] = UserTyped;
271 nm_db_longrun_done(Context->mailbox);
277 * execute_commands - Execute a set of NeoMutt commands
278 * @param p List of command strings
279 * @retval 0 Success, all the commands succeeded
282 static int execute_commands(struct ListHead *p)
285 struct Buffer *err = mutt_buffer_pool_get();
286 struct Buffer *token = mutt_buffer_pool_get();
288 struct ListNode *np = NULL;
289 STAILQ_FOREACH(np, p, entries)
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));
295 if ((rc2 == MUTT_CMD_ERROR) || (rc2 == MUTT_CMD_WARNING))
296 mutt_warning(_("Warning in command line: %s"), mutt_b2s(err));
298 if ((rc2 == MUTT_CMD_ERROR) || (rc2 == MUTT_CMD_WARNING))
300 mutt_buffer_pool_release(&token);
301 mutt_buffer_pool_release(&err);
305 mutt_buffer_pool_release(&token);
306 mutt_buffer_pool_release(&err);
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
318 static char *find_cfg(const char *home, const char *xdg_cfg_home)
320 const char *names[] = {
326 const char *locations[][2] = {
327 { xdg_cfg_home, "neomutt/" },
328 { xdg_cfg_home, "mutt/" },
329 { home, ".neomutt/" },
335 for (int i = 0; locations[i][0] || locations[i][1]; i++)
337 if (!locations[i][0])
340 for (int j = 0; names[j]; j++)
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);
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
359 static char *getmailname(void)
361 char *mailname = NULL;
362 static const char *mn_files[] = { "/etc/mailname", "/etc/mail/mailname" };
364 for (size_t i = 0; i < mutt_array_size(mn_files); i++)
366 FILE *fp = mutt_file_fopen(mn_files[i], "r");
371 mailname = mutt_file_read_line(NULL, &len, fp, NULL, 0);
372 mutt_file_fclose(&fp);
373 if (mailname && *mailname)
384 * get_hostname - Find the Fully-Qualified Domain Name
385 * @retval true Success
386 * @retval false Error, failed to find any name
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.
391 static bool get_hostname(void)
394 struct utsname utsname;
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)
407 mutt_perror(_("unable to determine nodename via uname()"));
408 return false; // TEST09: can't test
411 str = utsname.nodename;
414 /* some systems report the FQDN instead of just the hostname */
415 char *dot = strchr(str, '.');
417 ShortHostname = mutt_str_substr_dup(str, dot);
419 ShortHostname = mutt_str_strdup(str);
423 /* now get FQDN. Use configured domain first, DNS next, then uname */
425 /* we have a compile-time domain name, use that for 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);
430 C_Hostname = getmailname();
434 if (getdnsdomainname(buffer, sizeof(buffer)) == 0)
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);
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);
455 cs_str_initial_set(Config, "hostname", C_Hostname, NULL);
461 * mutt_attachmatch_new - Create a new AttachMatch
462 * @retval ptr New AttachMatch
464 static struct AttachMatch *mutt_attachmatch_new(void)
466 return mutt_mem_calloc(1, sizeof(struct AttachMatch));
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
477 static enum CommandResult parse_attach_list(struct Buffer *buf, struct Buffer *s,
478 struct ListHead *head, struct Buffer *err)
480 struct AttachMatch *a = NULL;
482 char *tmpminor = NULL;
488 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
490 if (!buf->data || (*buf->data == '\0'))
493 a = mutt_attachmatch_new();
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");
501 a->major = mutt_str_strdup(buf->data);
503 p = strchr(a->major, '/');
512 a->minor = "unknown";
515 len = strlen(a->minor);
516 tmpminor = mutt_mem_malloc(len + 3);
517 strcpy(&tmpminor[1], a->minor);
519 tmpminor[len + 1] = '$';
520 tmpminor[len + 2] = '\0';
522 a->major_int = mutt_check_mime_type(a->major);
523 ret = REG_COMP(&a->minor_regex, tmpminor, REG_ICASE);
529 regerror(ret, &a->minor_regex, err->data, err->dsize);
532 return MUTT_CMD_ERROR;
535 mutt_debug(LL_DEBUG3, "added %s/%s [%d]\n", a->major, a->minor, a->major_int);
537 mutt_list_insert_tail(head, (char *) a);
538 } while (MoreArgs(s));
541 return MUTT_CMD_SUCCESS;
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
554 static int parse_grouplist(struct GroupList *gl, struct Buffer *buf,
555 struct Buffer *s, unsigned long data, struct Buffer *err)
557 while (mutt_str_strcasecmp(buf->data, "-group") == 0)
561 mutt_buffer_strcpy(err, _("-group: no group name"));
565 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
567 mutt_grouplist_add(gl, mutt_pattern_group(buf->data));
571 mutt_buffer_strcpy(err, _("out of arguments"));
575 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
582 * parse_replace_list - Parse a string replacement rule - Implements ::command_t
584 static enum CommandResult parse_replace_list(struct Buffer *buf, struct Buffer *s,
585 unsigned long data, struct Buffer *err)
587 struct ReplaceList *list = (struct ReplaceList *) data;
588 struct Buffer templ = mutt_buffer_make(0);
590 /* First token is a regex. */
593 mutt_buffer_printf(err, _("%s: too few arguments"), "subjectrx");
594 return MUTT_CMD_WARNING;
596 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
598 /* Second token is a replacement template */
601 mutt_buffer_printf(err, _("%s: too few arguments"), "subjectrx");
602 return MUTT_CMD_WARNING;
604 mutt_extract_token(&templ, s, MUTT_TOKEN_NO_FLAGS);
606 if (mutt_replacelist_add(list, buf->data, templ.data, err) != 0)
609 return MUTT_CMD_ERROR;
613 return MUTT_CMD_SUCCESS;
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
624 static enum CommandResult parse_unattach_list(struct Buffer *buf, struct Buffer *s,
625 struct ListHead *head, struct Buffer *err)
627 struct AttachMatch *a = NULL;
633 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
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");
641 tmp = mutt_str_strdup(buf->data);
643 minor = strchr(tmp, '/');
653 const enum ContentType major = mutt_check_mime_type(tmp);
655 struct ListNode *np = NULL, *tmp2 = NULL;
656 STAILQ_FOREACH_SAFE(np, head, entries, tmp2)
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))
663 mutt_debug(LL_DEBUG3, "removed %s/%s [%d]\n", a->major, a->minor, a->major_int);
664 regfree(&a->minor_regex);
666 STAILQ_REMOVE(head, np, ListNode, entries);
672 } while (MoreArgs(s));
676 return MUTT_CMD_SUCCESS;
680 * parse_unreplace_list - Remove a string replacement rule - Implements ::command_t
682 static enum CommandResult parse_unreplace_list(struct Buffer *buf, struct Buffer *s,
683 unsigned long data, struct Buffer *err)
685 struct ReplaceList *list = (struct ReplaceList *) data;
687 /* First token is a regex. */
690 mutt_buffer_printf(err, _("%s: too few arguments"), "unsubjectrx");
691 return MUTT_CMD_WARNING;
694 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
696 /* "*" is a special case. */
697 if (mutt_str_strcmp(buf->data, "*") == 0)
699 mutt_replacelist_free(list);
700 return MUTT_CMD_SUCCESS;
703 mutt_replacelist_remove(list, buf->data);
704 return MUTT_CMD_SUCCESS;
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'
714 static int print_attach_list(struct ListHead *h, const char op, const char *name)
716 struct ListNode *np = NULL;
717 STAILQ_FOREACH(np, h, entries)
719 printf("attachments %c%s %s/%s\n", op, name,
720 ((struct AttachMatch *) np->data)->major,
721 ((struct AttachMatch *) np->data)->minor);
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
732 * @note The string comparison is case-insensitive
734 static void remove_from_stailq(struct ListHead *head, const char *str)
736 if (mutt_str_strcmp("*", str) == 0)
737 mutt_list_free(head); /* "unCMD *" means delete all current entries */
740 struct ListNode *np = NULL, *tmp = NULL;
741 STAILQ_FOREACH_SAFE(np, head, entries, tmp)
743 if (mutt_str_strcasecmp(str, np->data) == 0)
745 STAILQ_REMOVE(head, np, ListNode, entries);
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
760 static int source_rc(const char *rcfile_path, struct Buffer *err)
762 int line = 0, rc = 0, warnings = 0;
763 enum CommandResult line_rc;
765 char *linebuf = NULL;
766 char *currentline = NULL;
767 char rcfile[PATH_MAX];
772 mutt_str_strfcpy(rcfile, rcfile_path, sizeof(rcfile));
774 size_t rcfilelen = mutt_str_strlen(rcfile);
778 bool ispipe = rcfile[rcfilelen - 1] == '|';
782 struct ListNode *np = STAILQ_FIRST(&MuttrcStack);
783 if (!mutt_path_to_absolute(rcfile, np ? NONULL(np->data) : ""))
785 mutt_error(_("Error: Can't build path of '%s'"), rcfile_path);
789 STAILQ_FOREACH(np, &MuttrcStack, entries)
791 if (mutt_str_strcmp(np->data, rcfile) == 0)
798 mutt_list_insert_head(&MuttrcStack, mutt_str_strdup(rcfile));
802 mutt_error(_("Error: Cyclic sourcing of configuration file '%s'"), rcfile);
807 mutt_debug(LL_DEBUG2, "Reading configuration file '%s'\n", rcfile);
809 FILE *fp = mutt_open_read(rcfile, &pid);
812 mutt_buffer_printf(err, "%s: %s", rcfile, strerror(errno));
816 mutt_buffer_init(&token);
817 while ((linebuf = mutt_file_read_line(linebuf, &buflen, fp, &line, MUTT_CONT)))
819 const bool conv = C_ConfigCharset && C_Charset;
822 currentline = mutt_str_strdup(linebuf);
825 mutt_ch_convert_string(¤tline, C_ConfigCharset, C_Charset, 0);
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)
833 mutt_error(_("Error in %s, line %d: %s"), rcfile, line, err->data);
834 if (--rc < -MAX_ERRS)
841 else if (line_rc == MUTT_CMD_WARNING)
844 mutt_warning(_("Warning in %s, line %d: %s"), rcfile, line, err->data);
847 else if (line_rc == MUTT_CMD_FINISH)
849 break; /* Found "finish" command */
861 mutt_file_fclose(&fp);
863 mutt_wait_filter(pid);
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"),
874 /* Don't alias errors with warnings */
877 mutt_buffer_printf(err, ngettext("source: %d warning in %s", "source: %d warnings in %s", warnings),
883 if (!ispipe && !STAILQ_EMPTY(&MuttrcStack))
885 struct ListNode *np = STAILQ_FIRST(&MuttrcStack);
886 STAILQ_REMOVE_HEAD(&MuttrcStack, entries);
895 * parse_alias - Parse the 'alias' command - Implements ::command_t
897 static enum CommandResult parse_alias(struct Buffer *buf, struct Buffer *s,
898 unsigned long data, struct Buffer *err)
900 struct Alias *tmp = NULL;
902 struct GroupList gl = STAILQ_HEAD_INITIALIZER(gl);
906 mutt_buffer_strcpy(err, _("alias: no address"));
907 return MUTT_CMD_WARNING;
910 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
912 if (parse_grouplist(&gl, buf, s, data, err) == -1)
913 return MUTT_CMD_ERROR;
915 /* check to see if an alias with this name already exists */
916 TAILQ_FOREACH(tmp, &Aliases, entries)
918 if (mutt_str_strcasecmp(tmp->name, buf->data) == 0)
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;
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();
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);
944 mutt_addrlist_parse2(&tmp->addr, buf->data);
946 if (mutt_addrlist_to_intl(&tmp->addr, &estr))
948 mutt_buffer_printf(err, _("Warning: Bad IDN '%s' in alias '%s'"), estr, tmp->name);
953 mutt_grouplist_add_addrlist(&gl, &tmp->addr);
954 mutt_alias_add_reverse(tmp);
956 if (C_DebugLevel > LL_DEBUG4)
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)
966 mutt_debug(LL_DEBUG5, " Group %s\n", a->mailbox);
968 mutt_debug(LL_DEBUG5, " %s\n", a->mailbox);
971 mutt_grouplist_destroy(&gl);
972 return MUTT_CMD_SUCCESS;
975 mutt_grouplist_destroy(&gl);
976 return MUTT_CMD_ERROR;
980 * parse_alternates - Parse the 'alternates' command - Implements ::command_t
982 static enum CommandResult parse_alternates(struct Buffer *buf, struct Buffer *s,
983 unsigned long data, struct Buffer *err)
985 struct GroupList gl = STAILQ_HEAD_INITIALIZER(gl);
991 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
993 if (parse_grouplist(&gl, buf, s, data, err) == -1)
996 mutt_regexlist_remove(&UnAlternates, buf->data);
998 if (mutt_regexlist_add(&Alternates, buf->data, REG_ICASE, err) != 0)
1001 if (mutt_grouplist_add_regex(&gl, buf->data, REG_ICASE, err) != 0)
1003 } while (MoreArgs(s));
1005 mutt_grouplist_destroy(&gl);
1006 return MUTT_CMD_SUCCESS;
1009 mutt_grouplist_destroy(&gl);
1010 return MUTT_CMD_ERROR;
1014 * parse_attachments - Parse the 'attachments' command - Implements ::command_t
1016 static enum CommandResult parse_attachments(struct Buffer *buf, struct Buffer *s,
1017 unsigned long data, struct Buffer *err)
1020 char *category = NULL;
1021 struct ListHead *head = NULL;
1023 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1024 if (!buf->data || (*buf->data == '\0'))
1026 mutt_buffer_strcpy(err, _("attachments: no disposition"));
1027 return MUTT_CMD_WARNING;
1030 category = buf->data;
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;
1046 if ((op != '+') && (op != '-'))
1051 if (mutt_str_startswith("attachment", category, CASE_IGNORE))
1054 head = &AttachAllow;
1056 head = &AttachExclude;
1058 else if (mutt_str_startswith("inline", category, CASE_IGNORE))
1061 head = &InlineAllow;
1063 head = &InlineExclude;
1067 mutt_buffer_strcpy(err, _("attachments: invalid disposition"));
1068 return MUTT_CMD_ERROR;
1071 return parse_attach_list(buf, s, head, err);
1075 * parse_echo - Parse the 'echo' command - Implements ::command_t
1077 static enum CommandResult parse_echo(struct Buffer *buf, struct Buffer *s,
1078 unsigned long data, struct Buffer *err)
1082 mutt_buffer_printf(err, _("%s: too few arguments"), "echo");
1083 return MUTT_CMD_WARNING;
1085 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1086 OptForceRefresh = true;
1087 mutt_message("%s", buf->data);
1088 OptForceRefresh = false;
1091 return MUTT_CMD_SUCCESS;
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
1099 * If the 'finish' command is found, we should stop reading the current file.
1101 static enum CommandResult parse_finish(struct Buffer *buf, struct Buffer *s,
1102 unsigned long data, struct Buffer *err)
1106 mutt_buffer_printf(err, _("%s: too many arguments"), "finish");
1107 return MUTT_CMD_WARNING;
1110 return MUTT_CMD_FINISH;
1114 * parse_group - Parse the 'group' and 'ungroup' commands - Implements ::command_t
1116 static enum CommandResult parse_group(struct Buffer *buf, struct Buffer *s,
1117 unsigned long data, struct Buffer *err)
1119 struct GroupList gl = STAILQ_HEAD_INITIALIZER(gl);
1120 enum GroupState state = GS_NONE;
1124 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1125 if (parse_grouplist(&gl, buf, s, data, err) == -1)
1128 if ((data == MUTT_UNGROUP) && (mutt_str_strcasecmp(buf->data, "*") == 0))
1130 mutt_grouplist_clear(&gl);
1134 if (mutt_str_strcasecmp(buf->data, "-rx") == 0)
1136 else if (mutt_str_strcasecmp(buf->data, "-addr") == 0)
1143 mutt_buffer_printf(err, _("%sgroup: missing -rx or -addr"),
1144 (data == MUTT_UNGROUP) ? "un" : "");
1148 if ((data == MUTT_GROUP) &&
1149 (mutt_grouplist_add_regex(&gl, buf->data, REG_ICASE, err) != 0))
1153 else if ((data == MUTT_UNGROUP) &&
1154 (mutt_grouplist_remove_regex(&gl, buf->data) < 0))
1163 struct AddressList al = TAILQ_HEAD_INITIALIZER(al);
1164 mutt_addrlist_parse2(&al, buf->data);
1165 if (TAILQ_EMPTY(&al))
1167 if (mutt_addrlist_to_intl(&al, &estr))
1169 mutt_buffer_printf(err, _("%sgroup: warning: bad IDN '%s'"),
1170 (data == 1) ? "un" : "", estr);
1171 mutt_addrlist_clear(&al);
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);
1184 } while (MoreArgs(s));
1187 mutt_grouplist_destroy(&gl);
1188 return MUTT_CMD_SUCCESS;
1191 mutt_grouplist_destroy(&gl);
1192 return MUTT_CMD_ERROR;
1195 mutt_grouplist_destroy(&gl);
1196 return MUTT_CMD_WARNING;
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
1205 static bool is_function(const char *name)
1207 for (enum MenuType i = 0; i < MENU_MAX; i++)
1209 const struct Binding *b = km_get_table(Menus[i].value);
1213 for (int j = 0; b[j].name; j++)
1214 if (mutt_str_strcmp(name, b[j].name) == 0)
1221 * parse_ifdef - Parse the 'ifdef' and 'ifndef' commands - Implements ::command_t
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.
1227 * ifdef sidebar source ~/.neomutt/sidebar.rc
1229 * If (data == 1) then it means use the 'ifndef' (if-not-defined) command.
1231 * ifndef imap finish
1233 static enum CommandResult parse_ifdef(struct Buffer *buf, struct Buffer *s,
1234 unsigned long data, struct Buffer *err)
1236 struct Buffer token = mutt_buffer_make(0);
1238 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
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?
1250 mutt_buffer_printf(err, _("%s: too few arguments"), (data ? "ifndef" : "ifdef"));
1251 return MUTT_CMD_WARNING;
1253 mutt_extract_token(buf, s, MUTT_TOKEN_SPACE);
1255 /* ifdef KNOWN_SYMBOL or ifndef UNKNOWN_SYMBOL */
1256 if ((res && (data == 0)) || (!res && (data == 1)))
1258 enum CommandResult rc = mutt_parse_rc_line(buf->data, &token, err);
1259 if (rc == MUTT_CMD_ERROR)
1261 mutt_error(_("Error: %s"), err->data);
1263 return MUTT_CMD_ERROR;
1268 return MUTT_CMD_SUCCESS;
1272 * parse_ignore - Parse the 'ignore' command - Implements ::command_t
1274 static enum CommandResult parse_ignore(struct Buffer *buf, struct Buffer *s,
1275 unsigned long data, struct Buffer *err)
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));
1284 return MUTT_CMD_SUCCESS;
1288 * parse_lists - Parse the 'lists' command - Implements ::command_t
1290 static enum CommandResult parse_lists(struct Buffer *buf, struct Buffer *s,
1291 unsigned long data, struct Buffer *err)
1293 struct GroupList gl = STAILQ_HEAD_INITIALIZER(gl);
1297 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1299 if (parse_grouplist(&gl, buf, s, data, err) == -1)
1302 mutt_regexlist_remove(&UnMailLists, buf->data);
1304 if (mutt_regexlist_add(&MailLists, buf->data, REG_ICASE, err) != 0)
1307 if (mutt_grouplist_add_regex(&gl, buf->data, REG_ICASE, err) != 0)
1309 } while (MoreArgs(s));
1311 mutt_grouplist_destroy(&gl);
1312 return MUTT_CMD_SUCCESS;
1315 mutt_grouplist_destroy(&gl);
1316 return MUTT_CMD_ERROR;
1320 * parse_mailboxes - Parse the 'mailboxes' command - Implements ::command_t
1322 * This is also used by 'virtual-mailboxes'.
1324 static enum CommandResult parse_mailboxes(struct Buffer *buf, struct Buffer *s,
1325 unsigned long data, struct Buffer *err)
1329 struct Mailbox *m = mailbox_new();
1331 if (data & MUTT_NAMED)
1333 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1334 if (buf->data && (*buf->data != '\0'))
1336 m->name = mutt_str_strdup(buf->data);
1345 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1346 if (mutt_buffer_is_empty(buf))
1348 /* Skip empty tokens. */
1353 mutt_buffer_strcpy(&m->pathbuf, buf->data);
1354 /* int rc = */ mx_path_canon2(m, C_Folder);
1356 bool new_account = false;
1357 struct Account *a = mx_ac_find(m);
1360 a = account_new(NULL, NeoMutt->sub);
1361 a->magic = m->magic;
1367 struct Mailbox *m_old = mx_mbox_find(a, m->realpath);
1370 if (m_old->flags == MB_HIDDEN)
1372 m_old->flags = MB_NORMAL;
1373 mutt_sb_notify_mailbox(m_old, true);
1380 if (mx_ac_add(a, m) < 0)
1392 neomutt_account_add(NeoMutt, a);
1396 mutt_sb_notify_mailbox(m, true);
1399 mutt_monitor_add(m);
1402 return MUTT_CMD_SUCCESS;
1406 * parse_my_hdr - Parse the 'my_hdr' command - Implements ::command_t
1408 static enum CommandResult parse_my_hdr(struct Buffer *buf, struct Buffer *s,
1409 unsigned long data, struct Buffer *err)
1411 struct ListNode *n = NULL;
1414 mutt_extract_token(buf, s, MUTT_TOKEN_SPACE | MUTT_TOKEN_QUOTE);
1415 char *p = strpbrk(buf->data, ": \t");
1416 if (!p || (*p != ':'))
1418 mutt_buffer_strcpy(err, _("invalid header field"));
1419 return MUTT_CMD_WARNING;
1421 keylen = p - buf->data + 1;
1423 STAILQ_FOREACH(n, &UserHeader, entries)
1425 /* see if there is already a field by this name */
1426 if (mutt_str_strncasecmp(buf->data, n->data, keylen) == 0)
1434 /* not found, allocate memory for a new node and add it to the list */
1435 n = mutt_list_insert_tail(&UserHeader, NULL);
1439 /* found, free the existing data */
1443 n->data = buf->data;
1444 mutt_buffer_init(buf);
1446 return MUTT_CMD_SUCCESS;
1451 * parse_path_list - Parse the 'sidebar_whitelist' command - Implements ::command_t
1453 static enum CommandResult parse_path_list(struct Buffer *buf, struct Buffer *s,
1454 unsigned long data, struct Buffer *err)
1456 struct Buffer *path = mutt_buffer_pool_get();
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);
1466 return MUTT_CMD_SUCCESS;
1472 * parse_path_unlist - Parse the 'unsidebar_whitelist' command - Implements ::command_t
1474 static enum CommandResult parse_path_unlist(struct Buffer *buf, struct Buffer *s,
1475 unsigned long data, struct Buffer *err)
1477 struct Buffer *path = mutt_buffer_pool_get();
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)
1485 mutt_list_free((struct ListHead *) data);
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);
1493 return MUTT_CMD_SUCCESS;
1498 * parse_set - Parse the 'set' family of commands - Implements ::command_t
1500 * This is used by 'reset', 'set', 'toggle' and 'unset'.
1502 static enum CommandResult parse_set(struct Buffer *buf, struct Buffer *s,
1503 unsigned long data, struct Buffer *err)
1505 /* The order must match `enum MuttSetCommand` */
1506 static const char *set_commands[] = { "set", "toggle", "unset", "reset" };
1512 bool prefix = false;
1514 bool inv = (data == MUTT_SET_INV);
1515 bool reset = (data == MUTT_SET_RESET);
1516 bool unset = (data == MUTT_SET_UNSET);
1518 if (*s->dptr == '?')
1524 else if (mutt_str_startswith(s->dptr, "no", CASE_MATCH))
1530 else if (mutt_str_startswith(s->dptr, "inv", CASE_MATCH))
1536 else if (*s->dptr == '&')
1543 if (prefix && (data != MUTT_SET_SET))
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;
1550 /* get the variable name */
1551 mutt_extract_token(buf, s, MUTT_TOKEN_EQUAL | MUTT_TOKEN_QUESTION);
1554 bool equals = false;
1556 struct HashElem *he = NULL;
1557 bool my = mutt_str_startswith(buf->data, "my_", CASE_MATCH);
1560 he = cs_get_elem(Config, buf->data);
1563 if (reset && (mutt_str_strcmp(buf->data, "all") == 0))
1565 struct HashElem **list = get_elem_list(Config);
1567 return MUTT_CMD_ERROR;
1569 for (size_t i = 0; list[i]; i++)
1570 cs_he_reset(Config, list[i], NULL);
1577 mutt_buffer_printf(err, "ERR01 unknown variable: %s", buf->data);
1578 return MUTT_CMD_ERROR;
1582 bq = ((DTYPE(he->type) == DT_BOOL) || (DTYPE(he->type) == DT_QUAD));
1585 if (*s->dptr == '?')
1589 mutt_buffer_printf(err,
1590 "ERR02 can't use a prefix when querying a variable");
1591 return MUTT_CMD_WARNING;
1594 if (reset || unset || inv)
1596 mutt_buffer_printf(err, "ERR03 can't query a variable with the '%s' command",
1597 set_commands[data]);
1598 return MUTT_CMD_WARNING;
1604 else if (*s->dptr == '=')
1608 mutt_buffer_printf(err,
1609 "ERR04 can't use prefix when setting a variable");
1610 return MUTT_CMD_WARNING;
1613 if (reset || unset || inv)
1615 mutt_buffer_printf(err, "ERR05 can't set a variable with the '%s' command",
1616 set_commands[data]);
1617 return MUTT_CMD_WARNING;
1624 if (!bq && (inv || (unset && prefix)))
1626 if (data == MUTT_SET_SET)
1628 mutt_buffer_printf(err, "ERR06 prefixes 'no' and 'inv' may only be "
1629 "used with bool/quad variables");
1633 mutt_buffer_printf(err, "ERR07 command '%s' can only be used with bool/quad variables",
1634 set_commands[data]);
1636 return MUTT_CMD_WARNING;
1641 // mutt_buffer_printf(err, "ACT24 reset variable %s", buf->data);
1644 rc = cs_he_reset(Config, he, err);
1645 if (CSR_RESULT(rc) != CSR_SUCCESS)
1646 return MUTT_CMD_ERROR;
1650 myvar_del(buf->data);
1655 if ((data == MUTT_SET_SET) && !inv && !unset)
1659 // mutt_buffer_printf(err, "ACT08 query variable %s", buf->data);
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)
1668 mutt_buffer_addstr(err, buf->data);
1669 return MUTT_CMD_ERROR;
1671 pretty_var(buf->data, err);
1675 const char *val = myvar_get(buf->data);
1678 mutt_buffer_addstr(err, buf->data);
1679 mutt_buffer_addch(err, '=');
1680 pretty_var(val, err);
1684 mutt_buffer_printf(err, _("%s: unknown variable"), buf->data);
1685 return MUTT_CMD_ERROR;
1692 // mutt_buffer_printf(err, "ACT11 set variable %s to ", buf->data);
1693 const char *name = NULL;
1696 name = mutt_str_strdup(buf->data);
1698 mutt_extract_token(buf, s, MUTT_TOKEN_BACKTICK_VARS);
1701 myvar_set(name, buf->data);
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))
1714 if ((mutt_b2s(&scratch)[scratchlen - 1] != '|') && /* not a command */
1715 (url_check_scheme(mutt_b2s(&scratch)) == U_UNKNOWN)) /* probably a local file */
1717 struct ListNode *np = STAILQ_FIRST(&MuttrcStack);
1718 if (mutt_path_to_absolute(scratch.data, np ? NONULL(np->data) : "./"))
1720 mutt_buffer_reset(buf);
1721 mutt_buffer_addstr(buf, mutt_b2s(&scratch));
1725 mutt_error(_("Error: Can't build path of '%s'"), mutt_b2s(&scratch));
1729 mutt_buffer_dealloc(&scratch);
1731 else if (IS_COMMAND(he))
1733 struct Buffer scratch = mutt_buffer_make(1024);
1734 mutt_buffer_strcpy(&scratch, mutt_b2s(buf));
1736 if (mutt_str_strcmp(buf->data, "builtin") != 0)
1738 mutt_buffer_expand_path(&scratch);
1740 mutt_buffer_reset(buf);
1741 mutt_buffer_addstr(buf, mutt_b2s(&scratch));
1742 mutt_buffer_dealloc(&scratch);
1745 rc = cs_he_string_set(Config, he, buf->data, err);
1746 if (CSR_RESULT(rc) != CSR_SUCCESS)
1747 return MUTT_CMD_ERROR;
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;
1763 // mutt_buffer_printf(err, "ACT10 query variable %s", buf->data);
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)
1772 mutt_buffer_addstr(err, buf->data);
1773 return MUTT_CMD_ERROR;
1775 pretty_var(buf->data, err);
1779 const char *val = myvar_get(buf->data);
1782 mutt_buffer_addstr(err, buf->data);
1783 mutt_buffer_addch(err, '=');
1784 pretty_var(val, err);
1788 mutt_buffer_printf(err, _("%s: unknown variable"), buf->data);
1789 return MUTT_CMD_ERROR;
1799 myvar_del(buf->data);
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);
1809 quad_he_toggle(Config, he, err);
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;
1822 rc = cs_he_string_set(Config, he, NULL, err);
1823 if (CSR_RESULT(rc) != CSR_SUCCESS)
1824 return MUTT_CMD_ERROR;
1828 return MUTT_CMD_SUCCESS;
1832 * parse_setenv - Parse the 'setenv' and 'unsetenv' commands - Implements ::command_t
1834 static enum CommandResult parse_setenv(struct Buffer *buf, struct Buffer *s,
1835 unsigned long data, struct Buffer *err)
1837 char **envp = mutt_envlist_getlist();
1840 bool unset = (data == MUTT_SET_UNSET);
1844 mutt_buffer_printf(err, _("%s: too few arguments"), "setenv");
1845 return MUTT_CMD_WARNING;
1848 if (*s->dptr == '?')
1854 /* get variable name */
1855 mutt_extract_token(buf, s, MUTT_TOKEN_EQUAL);
1860 while (envp && *envp)
1862 /* This will display all matches for "^QUERY" */
1863 if (mutt_str_startswith(*envp, buf->data, CASE_MATCH))
1877 mutt_any_key_to_continue(NULL);
1878 return MUTT_CMD_SUCCESS;
1881 mutt_buffer_printf(err, _("%s is unset"), buf->data);
1882 return MUTT_CMD_WARNING;
1887 if (mutt_envlist_unset(buf->data))
1888 return MUTT_CMD_SUCCESS;
1889 return MUTT_CMD_ERROR;
1894 if (*s->dptr == '=')
1902 mutt_buffer_printf(err, _("%s: too few arguments"), "setenv");
1903 return MUTT_CMD_WARNING;
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);
1911 return MUTT_CMD_SUCCESS;
1915 * parse_source - Parse the 'source' command - Implements ::command_t
1917 static enum CommandResult parse_source(struct Buffer *buf, struct Buffer *s,
1918 unsigned long data, struct Buffer *err)
1920 char path[PATH_MAX];
1924 if (mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS) != 0)
1926 mutt_buffer_printf(err, _("source: error at %s"), s->dptr);
1927 return MUTT_CMD_ERROR;
1929 mutt_str_strfcpy(path, buf->data, sizeof(path));
1930 mutt_expand_path(path, sizeof(path));
1932 if (source_rc(path, err) < 0)
1934 mutt_buffer_printf(err, _("source: file %s could not be sourced"), path);
1935 return MUTT_CMD_ERROR;
1938 } while (MoreArgs(s));
1940 return MUTT_CMD_SUCCESS;
1944 * parse_spam_list - Parse the 'spam' and 'nospam' commands - Implements ::command_t
1946 static enum CommandResult parse_spam_list(struct Buffer *buf, struct Buffer *s,
1947 unsigned long data, struct Buffer *err)
1949 struct Buffer templ;
1951 mutt_buffer_init(&templ);
1953 /* Insist on at least one parameter */
1956 if (data == MUTT_SPAM)
1957 mutt_buffer_strcpy(err, _("spam: no matching pattern"));
1959 mutt_buffer_strcpy(err, _("nospam: no matching pattern"));
1960 return MUTT_CMD_ERROR;
1963 /* Extract the first token, a regex */
1964 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
1966 /* data should be either MUTT_SPAM or MUTT_NOSPAM. MUTT_SPAM is for spam commands. */
1967 if (data == MUTT_SPAM)
1969 /* If there's a second parameter, it's a template for the spam tag. */
1972 mutt_extract_token(&templ, s, MUTT_TOKEN_NO_FLAGS);
1974 /* Add to the spam list. */
1975 if (mutt_replacelist_add(&SpamList, buf->data, templ.data, err) != 0)
1978 return MUTT_CMD_ERROR;
1982 /* If not, try to remove from the nospam list. */
1985 mutt_regexlist_remove(&NoSpamList, buf->data);
1988 return MUTT_CMD_SUCCESS;
1990 /* MUTT_NOSPAM is for nospam commands. */
1991 else if (data == MUTT_NOSPAM)
1993 /* nospam only ever has one parameter. */
1995 /* "*" is a special case. */
1996 if (mutt_str_strcmp(buf->data, "*") == 0)
1998 mutt_replacelist_free(&SpamList);
1999 mutt_regexlist_free(&NoSpamList);
2000 return MUTT_CMD_SUCCESS;
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;
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;
2011 return MUTT_CMD_SUCCESS;
2014 /* This should not happen. */
2015 mutt_buffer_strcpy(err, "This is no good at all.");
2016 return MUTT_CMD_ERROR;
2020 * parse_stailq - Parse a list command - Implements ::command_t
2022 * This is used by 'alternative_order', 'auto_view' and several others.
2024 static enum CommandResult parse_stailq(struct Buffer *buf, struct Buffer *s,
2025 unsigned long data, struct Buffer *err)
2029 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
2030 add_to_stailq((struct ListHead *) data, buf->data);
2031 } while (MoreArgs(s));
2033 return MUTT_CMD_SUCCESS;
2037 * parse_subjectrx_list - Parse the 'subjectrx' command - Implements ::command_t
2039 static enum CommandResult parse_subjectrx_list(struct Buffer *buf, struct Buffer *s,
2040 unsigned long data, struct Buffer *err)
2042 enum CommandResult rc;
2044 rc = parse_replace_list(buf, s, data, err);
2045 if (rc == MUTT_CMD_SUCCESS)
2046 clear_subject_mods();
2051 * parse_subscribe - Parse the 'subscribe' command - Implements ::command_t
2053 static enum CommandResult parse_subscribe(struct Buffer *buf, struct Buffer *s,
2054 unsigned long data, struct Buffer *err)
2056 struct GroupList gl = STAILQ_HEAD_INITIALIZER(gl);
2060 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
2062 if (parse_grouplist(&gl, buf, s, data, err) == -1)
2065 mutt_regexlist_remove(&UnMailLists, buf->data);
2066 mutt_regexlist_remove(&UnSubscribedLists, buf->data);
2068 if (mutt_regexlist_add(&MailLists, buf->data, REG_ICASE, err) != 0)
2070 if (mutt_regexlist_add(&SubscribedLists, buf->data, REG_ICASE, err) != 0)
2072 if (mutt_grouplist_add_regex(&gl, buf->data, REG_ICASE, err) != 0)
2074 } while (MoreArgs(s));
2076 mutt_grouplist_destroy(&gl);
2077 return MUTT_CMD_SUCCESS;
2080 mutt_grouplist_destroy(&gl);
2081 return MUTT_CMD_ERROR;
2086 * parse_subscribe_to - Parse the 'subscribe-to' command - Implements ::command_t
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
2092 static enum CommandResult parse_subscribe_to(struct Buffer *buf, struct Buffer *s,
2093 unsigned long data, struct Buffer *err)
2095 if (!buf || !s || !err)
2096 return MUTT_CMD_ERROR;
2098 mutt_buffer_reset(err);
2102 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
2106 mutt_buffer_printf(err, _("%s: too many arguments"), "subscribe-to");
2107 return MUTT_CMD_WARNING;
2110 if (buf->data && (*buf->data != '\0'))
2112 /* Expand and subscribe */
2113 if (imap_subscribe(mutt_expand_path(buf->data, buf->dsize), true) != 0)
2115 mutt_buffer_printf(err, _("Could not subscribe to %s"), buf->data);
2116 return MUTT_CMD_ERROR;
2120 mutt_message(_("Subscribed to %s"), buf->data);
2121 return MUTT_CMD_SUCCESS;
2126 mutt_debug(LL_DEBUG1, "Corrupted buffer");
2127 return MUTT_CMD_ERROR;
2131 mutt_buffer_addstr(err, _("No folder specified"));
2132 return MUTT_CMD_WARNING;
2137 * parse_tag_formats - Parse the 'tag-formats' command - Implements ::command_t
2139 static enum CommandResult parse_tag_formats(struct Buffer *buf, struct Buffer *s,
2140 unsigned long data, struct Buffer *err)
2143 return MUTT_CMD_ERROR;
2149 char *tag = NULL, *format = NULL;
2151 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
2152 if (buf->data && (*buf->data != '\0'))
2153 tag = mutt_str_strdup(buf->data);
2157 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
2158 format = mutt_str_strdup(buf->data);
2160 /* avoid duplicates */
2161 tmp = mutt_hash_find(TagFormats, format);
2164 mutt_debug(LL_DEBUG3, "tag format '%s' already registered as '%s'\n", format, tmp);
2170 mutt_hash_insert(TagFormats, format, tag);
2172 return MUTT_CMD_SUCCESS;
2176 * parse_tag_transforms - Parse the 'tag-transforms' command - Implements ::command_t
2178 static enum CommandResult parse_tag_transforms(struct Buffer *buf, struct Buffer *s,
2179 unsigned long data, struct Buffer *err)
2182 return MUTT_CMD_ERROR;
2188 char *tag = NULL, *transform = NULL;
2190 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
2191 if (buf->data && (*buf->data != '\0'))
2192 tag = mutt_str_strdup(buf->data);
2196 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
2197 transform = mutt_str_strdup(buf->data);
2199 /* avoid duplicates */
2200 tmp = mutt_hash_find(TagTransforms, tag);
2203 mutt_debug(LL_DEBUG3, "tag transform '%s' already registered as '%s'\n", tag, tmp);
2209 mutt_hash_insert(TagTransforms, tag, transform);
2211 return MUTT_CMD_SUCCESS;
2215 * parse_unalias - Parse the 'unalias' command - Implements ::command_t
2217 static enum CommandResult parse_unalias(struct Buffer *buf, struct Buffer *s,
2218 unsigned long data, struct Buffer *err)
2220 struct Alias *a = NULL;
2224 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
2226 if (mutt_str_strcmp("*", buf->data) == 0)
2228 if (CurrentMenu == MENU_ALIAS)
2230 TAILQ_FOREACH(a, &Aliases, entries)
2234 mutt_menu_set_current_redraw_full();
2237 mutt_aliaslist_free(&Aliases);
2242 TAILQ_FOREACH(a, &Aliases, entries)
2244 if (mutt_str_strcasecmp(buf->data, a->name) == 0)
2246 if (CurrentMenu == MENU_ALIAS)
2249 mutt_menu_set_current_redraw_full();
2253 TAILQ_REMOVE(&Aliases, a, entries);
2254 mutt_alias_free(&a);
2260 } while (MoreArgs(s));
2261 return MUTT_CMD_SUCCESS;
2265 * parse_unalternates - Parse the 'unalternates' command - Implements ::command_t
2267 static enum CommandResult parse_unalternates(struct Buffer *buf, struct Buffer *s,
2268 unsigned long data, struct Buffer *err)
2273 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
2274 mutt_regexlist_remove(&Alternates, buf->data);
2276 if ((mutt_str_strcmp(buf->data, "*") != 0) &&
2277 (mutt_regexlist_add(&UnAlternates, buf->data, REG_ICASE, err) != 0))
2279 return MUTT_CMD_ERROR;
2282 } while (MoreArgs(s));
2284 return MUTT_CMD_SUCCESS;
2288 * mutt_attachmatch_free - Free an AttachMatch - Implements ::list_free_t
2289 * @param ptr AttachMatch to free
2291 * @note We don't free minor because it is either a pointer into major,
2292 * or a static string.
2294 void mutt_attachmatch_free(struct AttachMatch **ptr)
2299 struct AttachMatch *am = *ptr;
2300 regfree(&am->minor_regex);
2306 * parse_unattachments - Parse the 'unattachments' command - Implements ::command_t
2308 static enum CommandResult parse_unattachments(struct Buffer *buf, struct Buffer *s,
2309 unsigned long data, struct Buffer *err)
2313 struct ListHead *head = NULL;
2315 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
2316 if (!buf->data || (*buf->data == '\0'))
2318 mutt_buffer_strcpy(err, _("unattachments: no disposition"));
2319 return MUTT_CMD_WARNING;
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();
2335 if ((op != '+') && (op != '-'))
2340 if (mutt_str_startswith("attachment", p, CASE_IGNORE))
2343 head = &AttachAllow;
2345 head = &AttachExclude;
2347 else if (mutt_str_startswith("inline", p, CASE_IGNORE))
2350 head = &InlineAllow;
2352 head = &InlineExclude;
2356 mutt_buffer_strcpy(err, _("unattachments: invalid disposition"));
2357 return MUTT_CMD_ERROR;
2360 return parse_unattach_list(buf, s, head, err);
2364 * parse_unignore - Parse the 'unignore' command - Implements ::command_t
2366 static enum CommandResult parse_unignore(struct Buffer *buf, struct Buffer *s,
2367 unsigned long data, struct Buffer *err)
2371 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
2373 /* don't add "*" to the unignore list */
2374 if (strcmp(buf->data, "*") != 0)
2375 add_to_stailq(&UnIgnore, buf->data);
2377 remove_from_stailq(&Ignore, buf->data);
2378 } while (MoreArgs(s));
2380 return MUTT_CMD_SUCCESS;
2384 * parse_unlists - Parse the 'unlists' command - Implements ::command_t
2386 static enum CommandResult parse_unlists(struct Buffer *buf, struct Buffer *s,
2387 unsigned long data, struct Buffer *err)
2389 mutt_hash_free(&AutoSubscribeCache);
2392 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
2393 mutt_regexlist_remove(&SubscribedLists, buf->data);
2394 mutt_regexlist_remove(&MailLists, buf->data);
2396 if ((mutt_str_strcmp(buf->data, "*") != 0) &&
2397 (mutt_regexlist_add(&UnMailLists, buf->data, REG_ICASE, err) != 0))
2399 return MUTT_CMD_ERROR;
2401 } while (MoreArgs(s));
2403 return MUTT_CMD_SUCCESS;
2407 * parse_unmailboxes - Parse the 'unmailboxes' command - Implements ::command_t
2409 * This is also used by 'unvirtual-mailboxes'
2411 static enum CommandResult parse_unmailboxes(struct Buffer *buf, struct Buffer *s,
2412 unsigned long data, struct Buffer *err)
2414 bool tmp_valid = false;
2415 bool clear_all = false;
2417 while (!clear_all && MoreArgs(s))
2419 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
2421 if (mutt_str_strcmp(buf->data, "*") == 0)
2428 mutt_buffer_expand_path(buf);
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)
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);
2442 /* Compare against path or desc? Ensure 'buf' is valid */
2443 if (!clear_this && tmp_valid)
2446 (mutt_str_strcasecmp(mutt_b2s(buf), mailbox_path(np->mailbox)) == 0) ||
2447 (mutt_str_strcasecmp(mutt_b2s(buf), np->mailbox->name) == 0);
2453 mutt_sb_notify_mailbox(np->mailbox, false);
2456 mutt_monitor_remove(np->mailbox);
2458 if (Context && (Context->mailbox == np->mailbox))
2460 np->mailbox->flags |= MB_HIDDEN;
2464 account_mailbox_remove(np->mailbox->account, np->mailbox);
2468 neomutt_mailboxlist_clear(&ml);
2470 return MUTT_CMD_SUCCESS;
2474 * parse_unmy_hdr - Parse the 'unmy_hdr' command - Implements ::command_t
2476 static enum CommandResult parse_unmy_hdr(struct Buffer *buf, struct Buffer *s,
2477 unsigned long data, struct Buffer *err)
2479 struct ListNode *np = NULL, *tmp = NULL;
2484 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
2485 if (mutt_str_strcmp("*", buf->data) == 0)
2487 mutt_list_free(&UserHeader);
2491 l = mutt_str_strlen(buf->data);
2492 if (buf->data[l - 1] == ':')
2495 STAILQ_FOREACH_SAFE(np, &UserHeader, entries, tmp)
2497 if ((mutt_str_strncasecmp(buf->data, np->data, l) == 0) && (np->data[l] == ':'))
2499 STAILQ_REMOVE(&UserHeader, np, ListNode, entries);
2504 } while (MoreArgs(s));
2505 return MUTT_CMD_SUCCESS;
2509 * parse_unstailq - Parse an unlist command - Implements ::command_t
2511 * This is used by 'unalternative_order', 'unauto_view' and several others.
2513 static enum CommandResult parse_unstailq(struct Buffer *buf, struct Buffer *s,
2514 unsigned long data, struct Buffer *err)
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)
2522 mutt_list_free((struct ListHead *) data);
2525 remove_from_stailq((struct ListHead *) data, buf->data);
2526 } while (MoreArgs(s));
2528 return MUTT_CMD_SUCCESS;
2532 * parse_unsubjectrx_list - Parse the 'unsubjectrx' command - Implements ::command_t
2534 static enum CommandResult parse_unsubjectrx_list(struct Buffer *buf, struct Buffer *s,
2535 unsigned long data, struct Buffer *err)
2537 enum CommandResult rc;
2539 rc = parse_unreplace_list(buf, s, data, err);
2540 if (rc == MUTT_CMD_SUCCESS)
2541 clear_subject_mods();
2546 * parse_unsubscribe - Parse the 'unsubscribe' command - Implements ::command_t
2548 static enum CommandResult parse_unsubscribe(struct Buffer *buf, struct Buffer *s,
2549 unsigned long data, struct Buffer *err)
2551 mutt_hash_free(&AutoSubscribeCache);
2554 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
2555 mutt_regexlist_remove(&SubscribedLists, buf->data);
2557 if ((mutt_str_strcmp(buf->data, "*") != 0) &&
2558 (mutt_regexlist_add(&UnSubscribedLists, buf->data, REG_ICASE, err) != 0))
2560 return MUTT_CMD_ERROR;
2562 } while (MoreArgs(s));
2564 return MUTT_CMD_SUCCESS;
2569 * parse_unsubscribe_from - Parse the 'unsubscribe-from' command - Implements ::command_t
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
2575 static enum CommandResult parse_unsubscribe_from(struct Buffer *buf, struct Buffer *s,
2576 unsigned long data, struct Buffer *err)
2578 if (!buf || !s || !err)
2579 return MUTT_CMD_ERROR;
2583 mutt_extract_token(buf, s, MUTT_TOKEN_NO_FLAGS);
2587 mutt_buffer_printf(err, _("%s: too many arguments"), "unsubscribe-from");
2588 return MUTT_CMD_WARNING;
2591 if (buf->data && (*buf->data != '\0'))
2593 /* Expand and subscribe */
2594 if (imap_subscribe(mutt_expand_path(buf->data, buf->dsize), false) != 0)
2596 mutt_buffer_printf(err, _("Could not unsubscribe from %s"), buf->data);
2597 return MUTT_CMD_ERROR;
2601 mutt_message(_("Unsubscribed from %s"), buf->data);
2602 return MUTT_CMD_SUCCESS;
2607 mutt_debug(LL_DEBUG1, "Corrupted buffer");
2608 return MUTT_CMD_ERROR;
2612 mutt_buffer_addstr(err, _("No folder specified"));
2613 return MUTT_CMD_WARNING;
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
2623 const struct Command *mutt_command_get(const char *s)
2625 for (int i = 0; Commands[i].name; i++)
2626 if (mutt_str_strcmp(s, Commands[i].name) == 0)
2627 return &Commands[i];
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
2637 * This is used by Lua to expose all of NeoMutt's Commands.
2639 void mutt_commands_apply(void *data, void (*application)(void *, const struct Command *))
2641 for (int i = 0; Commands[i].name; i++)
2642 application(data, &Commands[i]);
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
2654 int mutt_extract_token(struct Buffer *dest, struct Buffer *tok, TokenFlags flags)
2660 char qc = '\0'; /* quote char */
2663 mutt_buffer_reset(dest);
2666 while ((ch = *tok->dptr))
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)))
2684 qc = 0; /* end of quote */
2685 else if (!qc && ((ch == '\'') || (ch == '"')) && !(flags & MUTT_TOKEN_QUOTE))
2687 else if ((ch == '\\') && (qc != '\''))
2689 if (tok->dptr[0] == '\0')
2690 return -1; /* premature end of token */
2691 switch (ch = *tok->dptr++)
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);
2701 mutt_buffer_addch(dest, '\033'); // Escape
2704 mutt_buffer_addch(dest, '\f');
2707 mutt_buffer_addch(dest, '\n');
2710 mutt_buffer_addch(dest, '\r');
2713 mutt_buffer_addch(dest, '\t');
2716 if (isdigit((unsigned char) ch) && isdigit((unsigned char) tok->dptr[0]) &&
2717 isdigit((unsigned char) tok->dptr[1]))
2719 mutt_buffer_addch(dest, (ch << 6) + (tok->dptr[0] << 3) + tok->dptr[1] - 3504);
2723 mutt_buffer_addch(dest, ch);
2726 else if ((ch == '^') && (flags & MUTT_TOKEN_CONDENSE))
2728 if (tok->dptr[0] == '\0')
2729 return -1; /* premature end of token */
2732 mutt_buffer_addch(dest, ch);
2734 mutt_buffer_addch(dest, '\033'); // Escape
2735 else if (isalpha((unsigned char) ch))
2736 mutt_buffer_addch(dest, toupper((unsigned char) ch) - '@');
2739 mutt_buffer_addch(dest, '^');
2740 mutt_buffer_addch(dest, ch);
2743 else if ((ch == '`') && (!qc || (qc == '"')))
2755 pc = strpbrk(pc, "\\`");
2758 /* skip any quoted chars */
2762 } while (pc && (pc[0] != '`'));
2765 mutt_debug(LL_DEBUG1, "mismatched backticks\n");
2769 mutt_buffer_init(&cmd);
2771 if (flags & MUTT_TOKEN_BACKTICK_VARS)
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);
2780 cmd.data = mutt_str_strdup(tok->dptr);
2783 pid = mutt_create_filter(cmd.data, NULL, &fp, NULL);
2786 mutt_debug(LL_DEBUG1, "unable to fork command: %s\n", cmd.data);
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);
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
2804 if (expn.data && qc)
2806 mutt_buffer_addstr(dest, expn.data);
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;
2822 else if ((ch == '$') && (!qc || (qc == '"')) &&
2823 ((tok->dptr[0] == '{') || isalpha((unsigned char) tok->dptr[0])))
2825 const char *env = NULL;
2828 if (tok->dptr[0] == '{')
2830 pc = strchr(tok->dptr, '}');
2833 var = mutt_str_substr_dup(tok->dptr + 1, pc);
2836 if ((flags & MUTT_TOKEN_NOSHELL))
2838 mutt_buffer_addch(dest, ch);
2839 mutt_buffer_addch(dest, '{');
2840 mutt_buffer_addstr(dest, var);
2841 mutt_buffer_addch(dest, '}');
2848 for (pc = tok->dptr; isalnum((unsigned char) *pc) || (pc[0] == '_'); pc++)
2850 var = mutt_str_substr_dup(tok->dptr, pc);
2855 struct Buffer result;
2856 mutt_buffer_init(&result);
2857 int rc = cs_str_string_get(Config, var, &result);
2859 if (CSR_RESULT(rc) == CSR_SUCCESS)
2861 mutt_buffer_addstr(dest, result.data);
2864 else if ((env = myvar_get(var)))
2866 mutt_buffer_addstr(dest, env);
2868 else if (!(flags & MUTT_TOKEN_NOSHELL) && (env = mutt_str_getenv(var)))
2870 mutt_buffer_addstr(dest, env);
2874 mutt_buffer_addch(dest, ch);
2875 mutt_buffer_addstr(dest, var);
2881 mutt_buffer_addch(dest, ch);
2883 mutt_buffer_addch(dest, 0); /* terminate the string */
2889 * mutt_opts_free - clean up before quitting
2891 void mutt_opts_free(void)
2893 mutt_list_free(&MuttrcStack);
2897 mutt_aliaslist_free(&Aliases);
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);
2907 mutt_grouplist_free();
2908 mutt_hash_free(&ReverseAliases);
2909 mutt_hash_free(&TagFormats);
2910 mutt_hash_free(&TagTransforms);
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);
2922 mutt_list_free(&SidebarWhitelist);
2924 mutt_list_free(&UnIgnore);
2925 mutt_list_free(&UserHeader);
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);
2933 mutt_colors_free(&Colors);
2935 FREE(&CurrentFolder);
2938 FREE(&ShortHostname);
2941 mutt_replacelist_free(&SpamList);
2942 mutt_replacelist_free(&SubjectRegexList);
2944 mutt_delete_hooks(MUTT_HOOK_NO_FLAGS);
2949 mutt_regexlist_free(&NoSpamList);
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
2958 HookFlags mutt_get_hook_type(const char *name)
2960 for (const struct Command *c = Commands; c->name; c++)
2962 if (((c->func == mutt_parse_hook) || (c->func == mutt_parse_idxfmt_hook)) &&
2963 (mutt_str_strcasecmp(c->name, name) == 0))
2968 return MUTT_HOOK_NO_FLAGS;
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
2978 int mutt_init(bool skip_sys_rc, struct ListHead *commands)
2982 struct Buffer err = mutt_buffer_make(256);
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);
2993 snprintf(AttachmentMarker, sizeof(AttachmentMarker), "\033]9;%" PRIu64 "\a", // Escape
2996 snprintf(ProtectedHeaderMarker, sizeof(ProtectedHeaderMarker), "\033]8;%lld\a", // Escape
2997 (long long) mutt_date_epoch());
2999 /* "$spoolfile" precedence: config file, environment */
3000 const char *p = mutt_str_getenv("MAIL");
3002 p = mutt_str_getenv("MAILDIR");
3006 mutt_path_concat(buf, NONULL(HomeDir), MAILPATH, sizeof(buf));
3008 mutt_path_concat(buf, MAILPATH, NONULL(Username), sizeof(buf));
3012 cs_str_initial_set(Config, "spoolfile", p, NULL);
3013 cs_str_reset(Config, "spoolfile", NULL);
3015 p = mutt_str_getenv("REPLYTO");
3018 struct Buffer tmp, token;
3020 snprintf(buf, sizeof(buf), "Reply-To: %s", p);
3022 mutt_buffer_init(&tmp);
3025 tmp.dsize = mutt_str_strlen(buf);
3027 mutt_buffer_init(&token);
3028 parse_my_hdr(&token, &tmp, 0, &err); /* adds to UserHeader */
3032 p = mutt_str_getenv("EMAIL");
3035 cs_str_initial_set(Config, "from", p, NULL);
3036 cs_str_reset(Config, "from", NULL);
3039 /* "$mailcap_path" precedence: config file, environment, code */
3040 const char *env_mc = mutt_str_getenv("MAILCAPS");
3042 cs_str_string_set(Config, "mailcap_path", env_mc, NULL);
3044 /* "$tmpdir" precedence: config file, environment, code */
3045 const char *env_tmp = mutt_str_getenv("TMPDIR");
3047 cs_str_string_set(Config, "tmpdir", env_tmp, NULL);
3049 /* "$visual", "$editor" precedence: config file, environment, code */
3050 const char *env_ed = mutt_str_getenv("VISUAL");
3052 env_ed = mutt_str_getenv("EDITOR");
3055 cs_str_string_set(Config, "editor", env_ed, NULL);
3056 cs_str_string_set(Config, "visual", env_ed, NULL);
3059 C_Charset = mutt_ch_get_langinfo_charset();
3060 mutt_ch_set_charset(C_Charset);
3062 Matches = mutt_mem_calloc(MatchesListsize, sizeof(char *));
3064 CurrentMenu = MENU_MAIN;
3067 /* Unset suspend by default if we're the session leader */
3068 if (getsid(0) == getpid())
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"
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");
3086 if (STAILQ_EMPTY(&Muttrc))
3088 const char *xdg_cfg_home = mutt_str_getenv("XDG_CONFIG_HOME");
3090 if (!xdg_cfg_home && HomeDir)
3092 snprintf(buf, sizeof(buf), "%s/.config", HomeDir);
3096 char *config = find_cfg(HomeDir, xdg_cfg_home);
3099 mutt_list_insert_tail(&Muttrc, config);
3104 struct ListNode *np = NULL;
3105 STAILQ_FOREACH(np, &Muttrc, entries)
3107 mutt_str_strfcpy(buf, np->data, sizeof(buf));
3109 mutt_expand_path(buf, sizeof(buf));
3110 np->data = mutt_str_strdup(buf);
3111 if (access(np->data, F_OK))
3113 mutt_perror(np->data);
3114 return 1; // TEST10: neomutt -F missing
3119 if (!STAILQ_EMPTY(&Muttrc))
3121 cs_str_string_set(Config, "alias_file", STAILQ_FIRST(&Muttrc)->data, NULL);
3124 /* Process the global rc file if it exists and the user hasn't explicitly
3125 * requested not to via "-n". */
3130 if (mutt_set_xdg_path(XDG_CONFIG_DIRS, buf, sizeof(buf)))
3133 snprintf(buf, sizeof(buf), "%s/neomuttrc", SYSCONFDIR);
3134 if (access(buf, F_OK) == 0)
3137 snprintf(buf, sizeof(buf), "%s/Muttrc", SYSCONFDIR);
3138 if (access(buf, F_OK) == 0)
3141 snprintf(buf, sizeof(buf), "%s/neomuttrc", PKGDATADIR);
3142 if (access(buf, F_OK) == 0)
3145 snprintf(buf, sizeof(buf), "%s/Muttrc", PKGDATADIR);
3148 if (access(buf, F_OK) == 0)
3150 if (source_rc(buf, &err) != 0)
3152 mutt_error("%s", err.data);
3153 need_pause = 1; // TEST11: neomutt (error in /etc/neomuttrc)
3158 /* Read the user's initialization file. */
3159 struct ListNode *np = NULL;
3160 STAILQ_FOREACH(np, &Muttrc, entries)
3164 if (source_rc(np->data, &err) != 0)
3166 mutt_error("%s", err.data);
3167 need_pause = 1; // TEST12: neomutt (error in ~/.neomuttrc)
3172 if (execute_commands(commands) != 0)
3173 need_pause = 1; // TEST13: neomutt -e broken
3175 if (!get_hostname())
3180 struct passwd *pw = getpwuid(getuid());
3184 C_Realname = mutt_str_strdup(mutt_gecos_name(name, sizeof(name), pw));
3187 cs_str_initial_set(Config, "realname", C_Realname, NULL);
3189 if (need_pause && !OptNoCurses)
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')
3196 mutt_file_mkdir(C_Tmpdir, S_IRWXU);
3199 mutt_hist_read_file();
3202 if (C_VirtualSpoolfile)
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);
3208 cs_str_string_set(Config, "spoolfile", mailbox_path(mp->mailbox), NULL);
3209 neomutt_mailboxlist_clear(&ml);
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
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.
3229 enum CommandResult mutt_parse_rc_line(/* const */ char *line,
3230 struct Buffer *token, struct Buffer *err)
3232 if (!line || !*line)
3236 enum CommandResult rc = MUTT_CMD_SUCCESS;
3238 struct Buffer expn = mutt_buffer_make(0);
3239 mutt_buffer_addstr(&expn, line);
3240 expn.dptr = expn.data;
3245 while (*expn.dptr != '\0')
3247 if (*expn.dptr == '#')
3248 break; /* rest of line is a comment */
3249 if (*expn.dptr == ';')
3254 mutt_extract_token(token, &expn, MUTT_TOKEN_NO_FLAGS);
3255 for (i = 0; Commands[i].name; i++)
3257 if (mutt_str_strcmp(token->data, Commands[i].name) == 0)
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 */
3264 break; /* Continue with next command */
3267 if (!Commands[i].name)
3269 mutt_buffer_printf(err, _("%s: unknown command"), NONULL(token->data));
3270 rc = MUTT_CMD_ERROR;
3271 break; /* Ignore the rest of the line */
3275 mutt_buffer_dealloc(&expn);
3280 * mutt_query_variables - Implement the -Q command line flag
3281 * @param queries List of query strings
3282 * @retval 0 Success, all queries exist
3285 int mutt_query_variables(struct ListHead *queries)
3287 struct Buffer value = mutt_buffer_make(256);
3288 struct Buffer tmp = mutt_buffer_make(256);
3291 struct ListNode *np = NULL;
3292 STAILQ_FOREACH(np, queries, entries)
3294 mutt_buffer_reset(&value);
3296 struct HashElem *he = cs_get_elem(Config, np->data);
3303 int rv = cs_he_string_get(Config, he, &value);
3304 if (CSR_RESULT(rv) != CSR_SUCCESS)
3310 int type = DTYPE(he->type);
3311 if (IS_PATH(he) && !(he->type & DT_MAILBOX))
3312 mutt_pretty_mailbox(value.data, value.dsize);
3314 if ((type != DT_BOOL) && (type != DT_NUMBER) && (type != DT_LONG) && (type != DT_QUAD))
3316 mutt_buffer_reset(&tmp);
3317 pretty_var(value.data, &tmp);
3318 mutt_buffer_strcpy(&value, tmp.data);
3321 dump_config_neo(Config, he, &value, NULL, CS_DUMP_NO_FLAGS, stdout);
3324 mutt_buffer_dealloc(&value);
3325 mutt_buffer_dealloc(&tmp);
3327 return rc; // TEST16: neomutt -Q charset
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
3336 enum QuadOption query_quadoption(enum QuadOption opt, const char *prompt)
3345 opt = mutt_yesorno(prompt, (opt == MUTT_ASKYES) ? MUTT_YES : MUTT_NO);
3346 mutt_window_clearline(MuttMessageWindow, 0);
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
3362 int mutt_command_complete(char *buf, size_t buflen, int pos, int numtabs)
3366 int spaces; /* keep track of the number of leading spaces on the line */
3367 struct MyVar *myv = NULL;
3372 pt = buf + pos - spaces;
3373 while ((pt > buf) && !isspace((unsigned char) *pt))
3376 if (pt == buf) /* complete cmd */
3378 /* first TAB. Collect all the matches */
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;
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')
3396 if ((Completed[0] == '\0') && (UserTyped[0] != '\0'))
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))
3405 /* cycle through all the matches */
3406 snprintf(Completed, sizeof(Completed), "%s", Matches[(numtabs - 2) % NumMatched]);
3409 /* return the completed command */
3410 strncpy(buf, Completed, buflen - spaces);
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 };
3420 /* loop through all the possible prefixes (no, inv, ...) */
3421 if (mutt_str_startswith(buf, "set", CASE_MATCH))
3423 for (num = 0; prefixes[num]; num++)
3425 if (mutt_str_startswith(pt, prefixes[num], CASE_MATCH))
3427 pt += mutt_str_strlen(prefixes[num]);
3433 /* first TAB. Collect all the matches */
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)
3444 candidate(UserTyped, myv->name, Completed, sizeof(Completed));
3446 matches_ensure_morespace(NumMatched);
3447 Matches[NumMatched++] = UserTyped;
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')
3455 if ((Completed[0] == 0) && UserTyped[0])
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))
3464 /* cycle through all the matches */
3465 snprintf(Completed, sizeof(Completed), "%s", Matches[(numtabs - 2) % NumMatched]);
3468 strncpy(pt, Completed, buf + buflen - pt - spaces);
3470 else if (mutt_str_startswith(buf, "exec", CASE_MATCH))
3472 const struct Binding *menu = km_get_table(CurrentMenu);
3474 if (!menu && (CurrentMenu != MENU_PAGER))
3478 /* first TAB. Collect all the matches */
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))
3491 for (num = 0; menu[num].name; num++)
3492 candidate(UserTyped, menu[num].name, Completed, sizeof(Completed));
3494 matches_ensure_morespace(NumMatched);
3495 Matches[NumMatched++] = UserTyped;
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')
3503 if ((Completed[0] == '\0') && (UserTyped[0] != '\0'))
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))
3512 /* cycle through all the matches */
3513 snprintf(Completed, sizeof(Completed), "%s", Matches[(numtabs - 2) % NumMatched]);
3516 strncpy(pt, Completed, buf + buflen - pt - spaces);
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
3532 int mutt_label_complete(char *buf, size_t buflen, int numtabs)
3535 int spaces; /* keep track of the number of leading spaces on the line */
3537 if (!Context || !Context->mailbox->label_hash)
3543 /* first TAB. Collect all the matches */
3546 struct HashElem *entry = NULL;
3547 struct HashWalkState state = { 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;
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')
3565 if ((Completed[0] == '\0') && (UserTyped[0] != '\0'))
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))
3574 /* cycle through all the matches */
3575 snprintf(Completed, sizeof(Completed), "%s", Matches[(numtabs - 2) % NumMatched]);
3578 /* return the completed label */
3579 strncpy(buf, Completed, buflen - spaces);
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
3594 * Complete the nearest "tag:"-prefixed string previous to pos.
3596 bool mutt_nm_query_complete(char *buf, size_t buflen, int pos, int numtabs)
3604 pt = (char *) mutt_str_rstrnstr((char *) buf, pos, "tag:");
3610 /* First TAB. Collect all the matches */
3611 complete_all_nm_tags(pt);
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')
3619 if ((Completed[0] == '\0') && (UserTyped[0] != '\0'))
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))
3628 /* cycle through all the matches */
3629 snprintf(Completed, sizeof(Completed), "%s", Matches[(numtabs - 2) % NumMatched]);
3632 /* return the completed query */
3633 strncpy(pt, Completed, buf + buflen - pt - spaces);
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
3651 * Complete the nearest "+" or "-" -prefixed string previous to pos.
3653 bool mutt_nm_tag_complete(char *buf, size_t buflen, int numtabs)
3660 /* Only examine the last token */
3661 char *last_space = strrchr(buf, ' ');
3663 pt = (last_space + 1);
3666 if ((pt[0] == '+') || (pt[0] == '-'))
3671 /* First TAB. Collect all the matches */
3672 complete_all_nm_tags(pt);
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')
3680 if ((Completed[0] == '\0') && (UserTyped[0] != '\0'))
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))
3689 /* cycle through all the matches */
3690 snprintf(Completed, sizeof(Completed), "%s", Matches[(numtabs - 2) % NumMatched]);
3693 /* return the completed query */
3694 strncpy(pt, Completed, buf + buflen - pt);
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
3706 int mutt_var_value_complete(char *buf, size_t buflen, int pos)
3714 const int spaces = buf - pt;
3716 pt = buf + pos - spaces;
3717 while ((pt > buf) && !isspace((unsigned char) *pt))
3719 pt++; /* move past the space */
3720 if (*pt == '=') /* abort if no var before the '=' */
3723 if (mutt_str_startswith(buf, "set", CASE_MATCH))
3725 const char *myvarval = NULL;
3727 mutt_str_strfcpy(var, pt, sizeof(var));
3728 /* ignore the trailing '=' when comparing */
3729 int vlen = mutt_str_strlen(var);
3733 var[vlen - 1] = '\0';
3735 struct HashElem *he = cs_get_elem(Config, var);
3738 myvarval = myvar_get(var);
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);
3747 return 0; /* no such variable. */
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)
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);
3762 mutt_buffer_dealloc(&value);
3763 mutt_buffer_dealloc(&pretty);
3771 * init_config - Initialise the config system
3772 * @retval ptr New Config Set
3774 struct ConfigSet *init_config(size_t size)
3776 struct ConfigSet *cs = cs_new(size);
3790 if (!cs_register_variables(cs, MuttVars, 0))
3792 mutt_error("cs_register_variables() failed");
3801 * charset_validator - Validate the "charset" config variable - Implements ::cs_validator()
3803 int charset_validator(const struct ConfigSet *cs, const struct ConfigDef *cdef,
3804 intptr_t value, struct Buffer *err)
3809 const char *str = (const char *) value;
3811 if ((strcmp(cdef->name, "charset") == 0) && strchr(str, ':'))
3814 err, _("'charset' must contain exactly one character set name"));
3815 return CSR_ERR_INVALID;
3818 int rc = CSR_SUCCESS;
3819 bool strict = (strcmp(cdef->name, "send_charset") == 0);
3821 char *s = mutt_str_strdup(str);
3823 for (char *p = strtok_r(s, ":", &q); p; p = strtok_r(NULL, ":", &q))
3827 if (!mutt_ch_check_charset(p, strict))
3829 rc = CSR_ERR_INVALID;
3830 mutt_buffer_printf(err, _("Invalid value for option %s: %s"), cdef->name, p);
3841 * hcache_validator - Validate the "header_cache_backend" config variable - Implements ::cs_validator()
3843 int hcache_validator(const struct ConfigSet *cs, const struct ConfigDef *cdef,
3844 intptr_t value, struct Buffer *err)
3849 const char *str = (const char *) value;
3851 if (mutt_hcache_is_valid_backend(str))
3854 mutt_buffer_printf(err, _("Invalid value for option %s: %s"), cdef->name, str);
3855 return CSR_ERR_INVALID;
3860 * pager_validator - Check for config variables that can't be set from the pager - Implements ::cs_validator()
3862 int pager_validator(const struct ConfigSet *cs, const struct ConfigDef *cdef,
3863 intptr_t value, struct Buffer *err)
3865 if (CurrentMenu == MENU_PAGER)
3867 mutt_buffer_printf(err, _("Option %s may not be set or reset from the pager"),
3869 return CSR_ERR_INVALID;
3876 * multipart_validator - Validate the "show_multipart_alternative" config variable - Implements ::cs_validator()
3878 int multipart_validator(const struct ConfigSet *cs, const struct ConfigDef *cdef,
3879 intptr_t value, struct Buffer *err)
3884 const char *str = (const char *) value;
3886 if ((mutt_str_strcmp(str, "inline") == 0) || (mutt_str_strcmp(str, "info") == 0))
3889 mutt_buffer_printf(err, _("Invalid value for option %s: %s"), cdef->name, str);
3890 return CSR_ERR_INVALID;
3894 * reply_validator - Validate the "reply_regex" config variable - Implements ::cs_validator()
3896 int reply_validator(const struct ConfigSet *cs, const struct ConfigDef *cdef,
3897 intptr_t value, struct Buffer *err)
3899 if (pager_validator(cs, cdef, value, err) != CSR_SUCCESS)
3900 return CSR_ERR_INVALID;
3905 mutt_buffer_printf(err, _("Option %s may not be set when in attach-message mode"),
3907 return CSR_ERR_INVALID;