2 * Copyright notice from original mutt:
3 * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
5 * Parts were written/modified by:
6 * Rocco Rutte <pdmef@cs.tu-berlin.de>
8 * This file is part of mutt-ng, see http://www.muttng.org/.
9 * It's licensed under the GNU General Public License,
10 * please see the file GPL in the top level source directory.
13 #include <lib-lib/lib-lib.h>
15 #include <lib-lua/lib-lua.h>
16 #include <lib-sys/unix.h>
17 #include <lib-ui/lib-ui.h>
18 #include <lib-ui/history.h>
19 #include <lib-mx/mx.h>
26 #include "mutt_idna.h"
33 static const struct mapping_t* get_sortmap (struct option_t* option);
34 static int parse_sort (struct option_t* dst, const char *s,
35 const struct mapping_t *map,
36 char* errbuf, ssize_t errlen);
38 static hash_t *ConfigOptions = NULL;
40 /* for synonym warning reports: synonym found during parsing */
41 typedef struct syn_t {
45 struct option_t* n; /* new */
46 struct option_t* o; /* old */
50 static void syn_wipe(syn_t *syn) {
54 DO_DELETE(syn_t, syn);
55 DO_SLIST(syn_t, syn, syn_delete);
57 /* for synonym warning reports: list of synonyms found */
58 static syn_t *Synonyms = NULL;
59 /* for synonym warning reports: current rc file */
60 static const char* CurRCFile = NULL;
61 /* for synonym warning reports: current rc line */
62 static int CurRCLine = 0;
64 /* prototypes for checking for special vars */
65 static int check_history (const char* option, unsigned long val,
66 char* errbuf, ssize_t errlen);
67 /* this checks that numbers are >= 0 */
68 static int check_num (const char* option, unsigned long val,
69 char* errbuf, ssize_t errlen);
71 /* use this to check only */
72 static int check_special (const char* option, unsigned long val,
73 char* errbuf, ssize_t errlen);
75 /* variable <-> sanity check function mappings
76 * when changing these, make sure the proper _from_string handler
81 int (*check) (const char* option, unsigned long val,
82 char* errbuf, ssize_t errlen);
84 { "history", check_history },
85 { "pager_index_lines", check_num },
90 static void bool_to_string (char* dst, ssize_t dstlen,
91 struct option_t* option) {
92 snprintf (dst, dstlen, "%s=%s", option->option,
93 option (option->data) ? "yes" : "no");
96 static int bool_from_string (struct option_t* dst, const char* val,
97 char* errbuf __attribute__ ((unused)),
98 ssize_t errlen __attribute__ ((unused))) {
103 if (ascii_strncasecmp (val, "yes", 3) == 0)
105 else if (ascii_strncasecmp (val, "no", 2) == 0)
111 set_option (dst->data);
113 unset_option (dst->data);
117 static void num_to_string (char* dst, ssize_t dstlen,
118 struct option_t* option) {
120 const char* fmt = (m_strcmp(option->option, "umask") == 0) ?
122 snprintf (dst, dstlen, fmt, option->option,
123 *((short*) option->data));
126 static int num_from_string (struct option_t* dst, const char* val,
127 char* errbuf, ssize_t errlen) {
128 int num = 0, old = 0;
134 num = strtol (val, &t, 0);
136 if (m_strisempty(val) || *t || (short) num != num) {
138 snprintf (errbuf, errlen, _("'%s' is invalid for $%s"),
144 /* just temporarily accept new val so that check_special for
145 * $history already has it when doing history's init() */
146 old = *((short*) dst->data);
147 *((short*) dst->data) = (short) num;
149 if (!check_special (dst->option, (unsigned long) num, errbuf, errlen)) {
150 *((short*) dst->data) = old;
157 static void str_to_string (char* dst, ssize_t dstlen,
158 struct option_t* option) {
159 snprintf (dst, dstlen, "%s=\"%s\"", option->option,
160 NONULL (*((char**) option->data)));
163 static int path_from_string (struct option_t* dst, const char* val,
164 char* errbuf __attribute__ ((unused)), ssize_t errlen __attribute__ ((unused))) {
165 char path[_POSIX_PATH_MAX];
170 if (m_strisempty(val)) {
171 p_delete((char**) dst->data);
176 m_strcpy(path, sizeof(path), val);
177 mutt_expand_path (path, sizeof(path));
178 m_strreplace((char **) dst->data, path);
182 static int str_from_string (struct option_t* dst, const char* val,
183 char* errbuf, ssize_t errlen) {
187 if (!check_special (dst->option, (unsigned long) val, errbuf, errlen))
190 m_strreplace((char**) dst->data, val);
194 static void quad_to_string (char* dst, ssize_t dstlen,
195 struct option_t* option) {
196 const char *vals[] = { "no", "yes", "ask-no", "ask-yes" };
197 snprintf (dst, dstlen, "%s=%s", option->option,
198 vals[quadoption (option->data)]);
201 static int quad_from_string (struct option_t* dst, const char* val,
202 char* errbuf __attribute__ ((unused)), ssize_t errlen __attribute__ ((unused))) {
207 if (ascii_strncasecmp (val, "yes", 3) == 0)
209 else if (ascii_strncasecmp (val, "no", 2) == 0)
211 else if (ascii_strncasecmp (val, "ask-yes", 7) == 0)
213 else if (ascii_strncasecmp (val, "ask-no", 6) == 0)
219 set_quadoption (dst->data, flag);
223 static void sort_to_string (char* dst, ssize_t dstlen,
224 struct option_t* option) {
225 const struct mapping_t *map = get_sortmap (option);
226 const char *p = NULL;
229 snprintf (dst, sizeof(dst), "%s=unknown", option->option);
233 p = mutt_getnamebyvalue(*((short *)option->data) & SORT_MASK, map);
235 snprintf (dst, dstlen, "%s=%s%s%s", option->option,
236 (*((short *) option->data) & SORT_REVERSE) ?
238 (*((short *) option->data) & SORT_LAST) ? "last-" :
242 static int sort_from_string (struct option_t* dst, const char* val,
243 char* errbuf, ssize_t errlen) {
244 const struct mapping_t *map = NULL;
245 if (!(map = get_sortmap (dst))) {
247 snprintf (errbuf, errlen, _("%s: Unknown type."),
251 if (parse_sort (dst, val, map, errbuf, errlen) == -1)
256 static void rx_to_string (char* dst, ssize_t dstlen,
257 struct option_t* option) {
258 rx_t* p = (rx_t*) option->data;
259 snprintf (dst, dstlen, "%s=\"%s\"", option->option,
260 NONULL (p->pattern));
263 static int rx_from_string (struct option_t* dst, const char* val,
264 char* errbuf, ssize_t errlen) {
267 int flags = 0, e = 0, neg = 0;
273 if (option (OPTATTACHMSG) && !m_strcmp(dst->option, "reply_regexp")) {
275 snprintf (errbuf, errlen,
276 "Operation not permitted when in attach-message mode.");
280 if (!((rx_t*) dst->data))
281 *((rx_t**) dst->data) = p_new(rx_t, 1);
283 p = (rx_t*) dst->data;
285 /* something to do? */
286 if (m_strisempty(val) || (p->pattern && m_strcmp(p->pattern, val) == 0))
289 if (m_strcmp(dst->option, "mask") != 0)
290 flags |= mutt_which_case (val);
293 if (m_strcmp(dst->option, "mask") == 0 && *s == '!') {
298 rx = p_new(regex_t, 1);
300 if ((e = REGCOMP (rx, s, flags)) != 0) {
301 regerror (e, rx, errbuf, errlen);
312 m_strreplace(&p->pattern, val);
316 if (m_strcmp(dst->option, "reply_regexp") == 0)
317 mutt_adjust_all_subjects ();
322 static void magic_to_string (char* dst, ssize_t dstlen,
323 struct option_t* option) {
324 const char* s = NULL;
325 switch (option->data) {
326 case M_MBOX: s = "mbox"; break;
327 case M_MH: s = "MH"; break;
328 case M_MAILDIR: s = "Maildir"; break;
329 default: s = "unknown"; break;
331 snprintf (dst, dstlen, "%s=%s", option->option, s);
334 static int magic_from_string (struct option_t* dst, const char* val,
335 char *errbuf, ssize_t errlen)
339 if (!dst || m_strisempty(val))
341 if (ascii_strncasecmp (val, "mbox", 4) == 0)
343 else if (ascii_strncasecmp (val, "mh", 2) == 0)
345 else if (ascii_strncasecmp (val, "maildir", 7) == 0)
351 *((short*) dst->data) = flag;
358 void (*opt_tostr) (char* dst, ssize_t dstlen, struct option_t* option);
359 int (*opt_fromstr) (struct option_t* dst, const char* val,
360 char* errbuf, ssize_t errlen);
362 { 0, NULL, NULL }, /* there's no DT_ type with 0 */
363 { DT_BOOL, bool_to_string, bool_from_string },
364 { DT_NUM, num_to_string, num_from_string },
365 { DT_STR, str_to_string, str_from_string },
366 { DT_PATH, str_to_string, path_from_string },
367 { DT_QUAD, quad_to_string, quad_from_string },
368 { DT_SORT, sort_to_string, sort_from_string },
369 { DT_RX, rx_to_string, rx_from_string },
370 { DT_MAGIC, magic_to_string, magic_from_string },
374 int mutt_option_value (const char* val, char* dst, ssize_t dstlen) {
375 struct option_t* option = NULL;
376 char* tmp = NULL, *t = NULL;
379 if (!(option = hash_find (ConfigOptions, val))) {
383 tmp = p_new(char, dstlen+1);
384 FuncTable[DTYPE(option->type)].opt_tostr (tmp, dstlen, option);
386 /* as we get things of type $var=value and don't want to bloat the
387 * above "just" for expansion, we do the stripping here */
388 t = strchr (tmp, '=');
392 if (t[l-1] == '"' && *t == '"') {
397 memcpy (dst, t, l+1);
403 static void toggle_quadoption (int opt)
406 int b = (opt % 4) * 2;
408 QuadOptions[n] ^= (1 << b);
411 void set_quadoption (int opt, int flag)
414 int b = (opt % 4) * 2;
416 QuadOptions[n] &= ~(0x3 << b);
417 QuadOptions[n] |= (flag & 0x3) << b;
420 int quadoption (int opt)
423 int b = (opt % 4) * 2;
425 return (QuadOptions[n] >> b) & 0x3;
428 int query_quadoption2(int v, const char *prompt)
436 v = mutt_yesorno(prompt, (v == M_ASKYES));
437 CLEARLINE(stdscr, LINES - 1);
442 int query_quadoption (int opt, const char *prompt)
444 int v = quadoption (opt);
452 v = mutt_yesorno (prompt, (v == M_ASKYES));
453 CLEARLINE(stdscr, LINES - 1);
460 /* always wise to do what someone else did before */
461 static void _attachments_clean (void) {
463 if (Context && Context->msgcount) {
464 for (i = 0; i < Context->msgcount; i++)
465 Context->hdrs[i]->attach_valid = 0;
469 static int parse_attach_list (BUFFER *buf, BUFFER *s, string_list_t **ldata,
470 BUFFER *err __attribute__ ((unused))) {
472 string_list_t *listp, *lastp;
477 /* Find the last item in the list that data points to. */
479 for (listp = *ldata; listp; listp = listp->next) {
480 a = (ATTACH_MATCH *)listp->data;
485 mutt_extract_token (buf, s, 0);
487 if (!buf->data || *buf->data == '\0')
490 a = p_new(ATTACH_MATCH, 1);
492 /* some cheap hacks that I expect to remove */
493 if (!m_strcasecmp(buf->data, "any"))
494 a->major = m_strdup("*/.*");
495 else if (!m_strcasecmp(buf->data, "none"))
496 a->major = m_strdup("cheap_hack/this_should_never_match");
498 a->major = m_strdup(buf->data);
500 if ((p = strchr(a->major, '/'))) {
505 a->minor = "unknown";
508 len = m_strlen(a->minor);
509 tmpminor = p_new(char, len + 3);
510 m_strcpy(&tmpminor[1], len + 3, a->minor);
512 tmpminor[len+1] = '$';
513 tmpminor[len+2] = '\0';
515 a->major_int = mutt_check_mime_type(a->major);
516 regcomp(&a->minor_rx, tmpminor, REG_ICASE|REG_EXTENDED);
520 listp = p_new(string_list_t, 1);
521 listp->data = (char *)a;
530 while (MoreArgs (s));
532 _attachments_clean();
536 static int parse_unattach_list (BUFFER *buf, BUFFER *s, string_list_t **ldata,
537 BUFFER *err __attribute__ ((unused))) {
539 string_list_t *lp, *lastp, *newlp;
545 mutt_extract_token (buf, s, 0);
547 if (!m_strcasecmp(buf->data, "any"))
548 tmp = m_strdup("*/.*");
549 else if (!m_strcasecmp(buf->data, "none"))
550 tmp = m_strdup("cheap_hack/this_should_never_match");
552 tmp = m_strdup(buf->data);
554 if ((minor = strchr(tmp, '/'))) {
558 minor = m_strdup("unknown");
560 major = mutt_check_mime_type(tmp);
562 /* We must do our own walk here because string_list_remove() will only
563 * remove the string_list_t->data, not anything pointed to by the string_list_t->data. */
565 for(lp = *ldata; lp; ) {
566 a = (ATTACH_MATCH *)lp->data;
567 if (a->major_int == major && !m_strcasecmp(minor, a->minor)) {
568 regfree(&a->minor_rx);
571 /* Relink backward */
573 lastp->next = lp->next;
578 p_delete(&lp->data); /* same as a */
588 while (MoreArgs (s));
591 _attachments_clean();
595 static int print_attach_list (string_list_t *lp, char op, const char *name) {
597 printf("attachments %c%s %s/%s\n", op, name,
598 ((ATTACH_MATCH *)lp->data)->major,
599 ((ATTACH_MATCH *)lp->data)->minor);
606 static int parse_attachments (BUFFER *buf, BUFFER *s,
607 unsigned long data __attribute__ ((unused)),
610 string_list_t **listp;
612 mutt_extract_token(buf, s, 0);
613 if (!buf->data || *buf->data == '\0') {
614 m_strcpy(err->data, err->dsize, _("attachments: no disposition"));
618 category = buf->data;
624 printf("\nCurrent attachments settings:\n\n");
625 print_attach_list(AttachAllow, '+', "A");
626 print_attach_list(AttachExclude, '-', "A");
627 print_attach_list(InlineAllow, '+', "I");
628 print_attach_list(InlineExclude, '-', "I");
629 set_option (OPTFORCEREDRAWINDEX);
630 set_option (OPTFORCEREDRAWPAGER);
631 mutt_any_key_to_continue (NULL);
635 if (op != '+' && op != '-') {
639 if (!m_strncasecmp(category, "attachment", strlen(category))) {
641 listp = &AttachAllow;
643 listp = &AttachExclude;
645 else if (!m_strncasecmp(category, "inline", strlen(category))) {
647 listp = &InlineAllow;
649 listp = &InlineExclude;
651 m_strcpy(err->data, err->dsize, _("attachments: invalid disposition"));
655 return parse_attach_list(buf, s, listp, err);
658 static int parse_unattachments (BUFFER *buf, BUFFER *s, unsigned long data __attribute__ ((unused)), BUFFER *err) {
660 string_list_t **listp;
662 mutt_extract_token(buf, s, 0);
663 if (!buf->data || *buf->data == '\0') {
664 m_strcpy(err->data, err->dsize, _("unattachments: no disposition"));
670 if (op != '+' && op != '-') {
674 if (!m_strncasecmp(p, "attachment", strlen(p))) {
676 listp = &AttachAllow;
678 listp = &AttachExclude;
680 else if (!m_strncasecmp(p, "inline", strlen(p))) {
682 listp = &InlineAllow;
684 listp = &InlineExclude;
687 m_strcpy(err->data, err->dsize, _("unattachments: invalid disposition"));
691 return parse_unattach_list(buf, s, listp, err);
694 static int parse_unalias (BUFFER * buf, BUFFER * s,
695 unsigned long data __attribute__ ((unused)),
696 BUFFER * err __attribute__ ((unused)))
698 alias_t *tmp, **last;
701 mutt_extract_token (buf, s, 0);
703 if (!m_strcmp("*", buf->data) == 0) {
704 if (CurrentMenu == MENU_ALIAS) {
705 for (tmp = Aliases; tmp; tmp = tmp->next)
707 set_option(OPTFORCEREDRAWINDEX);
709 alias_list_wipe(&Aliases);
715 for (last = &Aliases; *last; last = &(*last)->next) {
716 if (!m_strcasecmp(buf->data, (*last)->name)) {
717 if (CurrentMenu == MENU_ALIAS) {
719 set_option (OPTFORCEREDRAWINDEX);
721 tmp = alias_list_pop(last);
727 } while (MoreArgs(s));
732 static int parse_alias (BUFFER * buf, BUFFER * s,
733 unsigned long data __attribute__ ((unused)),
740 m_strcpy(err->data, err->dsize, _("alias: no address"));
744 mutt_extract_token (buf, s, 0);
746 /* check to see if an alias with this name already exists */
747 for (last = &Aliases; *last; last = &(*last)->next) {
748 if (!m_strcasecmp((*last)->name, buf->data))
753 /* create a new alias */
755 (*last)->name = m_strdup(buf->data);
756 /* give the main addressbook code a chance */
757 if (CurrentMenu == MENU_ALIAS)
758 set_option (OPTMENUCALLER);
760 /* override the previous value */
761 address_list_wipe(&(*last)->addr);
762 if (CurrentMenu == MENU_ALIAS)
763 set_option (OPTFORCEREDRAWINDEX);
766 mutt_extract_token(buf, s, M_TOKEN_QUOTE | M_TOKEN_SPACE);
767 (*last)->addr = mutt_parse_adrlist((*last)->addr, buf->data);
768 if (mutt_addrlist_to_idna((*last)->addr, &estr)) {
769 snprintf (err->data, err->dsize,
770 _("Warning: Bad IDN '%s' in alias '%s'.\n"), estr, (*last)->name);
779 parse_unmy_hdr(BUFFER * buf, BUFFER * s,
780 unsigned long data __attribute__ ((unused)),
781 BUFFER * err __attribute__ ((unused)))
784 mutt_extract_token (buf, s, 0);
786 if (!m_strcmp("*", buf->data)) {
787 string_list_wipe(&UserHeader);
789 string_list_t **last = &UserHeader;
790 ssize_t l = m_strlen(buf->data);
792 if (buf->data[l - 1] == ':')
796 if (!ascii_strncasecmp(buf->data, (*last)->data, l)
797 && (*last)->data[l] == ':')
799 string_list_t *tmp = string_list_pop(last);
800 string_item_delete(&tmp);
802 last = &(*last)->next;
806 } while (MoreArgs(s));
811 static int parse_my_hdr (BUFFER * buf, BUFFER * s, unsigned long data __attribute__ ((unused)),
818 mutt_extract_token (buf, s, M_TOKEN_SPACE | M_TOKEN_QUOTE);
819 if ((p = strpbrk (buf->data, ": \t")) == NULL || *p != ':') {
820 m_strcpy(err->data, err->dsize, _("invalid header field"));
823 keylen = p - buf->data + 1;
826 for (tmp = UserHeader;; tmp = tmp->next) {
827 /* see if there is already a field by this name */
828 if (ascii_strncasecmp (buf->data, tmp->data, keylen) == 0) {
829 /* replace the old value */
830 p_delete(&tmp->data);
831 tmp->data = buf->data;
838 tmp->next = string_item_new();
842 tmp = string_item_new();
845 tmp->data = buf->data;
851 parse_sort (struct option_t* dst, const char *s, const struct mapping_t *map,
852 char* errbuf, ssize_t errlen) {
855 if (m_strncmp("reverse-", s, 8) == 0) {
857 flags = SORT_REVERSE;
860 if (m_strncmp("last-", s, 5) == 0) {
865 if ((i = mutt_getvaluebyname (s, map)) == -1) {
867 snprintf (errbuf, errlen, _("'%s' is invalid for $%s"), s, dst->option);
871 *((short*) dst->data) = i | flags;
875 /* if additional data more == 1, we want to resolve synonyms */
876 static void mutt_set_default(const char *name __attribute__ ((unused)), void* p, unsigned long more)
878 char buf[LONG_STRING];
879 struct option_t *ptr = p;
881 if (!ptr || *ptr->init || !FuncTable[DTYPE (ptr->type)].opt_fromstr)
884 mutt_option_value(ptr->option, buf, sizeof(buf));
885 if (m_strlen(ptr->init) == 0 && *buf)
886 ptr->init = m_strdup(buf);
889 static int init_expand (char** dst, struct option_t* src) {
895 if (DTYPE(src->type) == DT_STR || DTYPE(src->type) == DT_PATH) {
896 /* only expand for string as it's the only place where
897 * we want to expand vars right now */
898 if (src->init && *src->init) {
901 len = m_strlen(src->init) + 2;
902 in.data = p_new(char, len + 1);
903 snprintf (in.data, len, "\"%s\"", src->init);
906 mutt_extract_token (&token, &in, 0);
907 if (token.data && *token.data)
908 *dst = m_strdup(token.data);
912 p_delete(&token.data);
916 /* for non-string: take value as is */
917 *dst = m_strdup(src->init);
921 /* if additional data more == 1, we want to resolve synonyms */
922 static void mutt_restore_default (const char* name __attribute__ ((unused)),
923 void* p, unsigned long more) {
925 struct option_t* ptr = (struct option_t*) p;
930 if (FuncTable[DTYPE (ptr->type)].opt_fromstr) {
931 init_expand (&init, ptr);
932 if (!FuncTable[DTYPE (ptr->type)].opt_fromstr (ptr, init, errbuf,
934 if (!option (OPTNOCURSES))
936 fprintf (stderr, _("Invalid default setting for $%s found: \"%s\".\n"
937 "Please report this error: \"%s\"\n"),
938 ptr->option, NONULL (init), errbuf);
944 set_option (OPTFORCEREDRAWINDEX);
945 set_option (OPTFORCEREDRAWPAGER);
946 set_option (OPTSORTSUBTHREADS);
947 set_option (OPTNEEDRESORT);
948 set_option (OPTRESORTINIT);
949 set_option (OPTREDRAWTREE);
952 static int check_num (const char* option, unsigned long p,
953 char* errbuf, ssize_t errlen) {
956 snprintf (errbuf, errlen, _("'%d' is invalid for $%s"), (int) p, option);
962 static int check_history (const char* option __attribute__ ((unused)), unsigned long p,
963 char* errbuf, ssize_t errlen) {
964 if (!check_num ("history", p, errbuf, errlen))
966 mutt_init_history ();
970 static int check_special (const char* name, unsigned long val,
971 char* errbuf, ssize_t errlen) {
974 for (i = 0; SpecialVars[i].name; i++) {
975 if (m_strcmp(SpecialVars[i].name, name) == 0) {
976 return (SpecialVars[i].check (SpecialVars[i].name,
977 val, errbuf, errlen));
983 static const struct mapping_t* get_sortmap (struct option_t* option) {
984 const struct mapping_t* map = NULL;
986 switch (option->type & DT_SUBTYPE_MASK) {
988 map = SortAliasMethods;
990 case DT_SORT_BROWSER:
991 map = SortBrowserMethods;
994 map = SortKeyMethods;
997 map = SortAuxMethods;
1006 static int parse_set (BUFFER * tmp, BUFFER * s, unsigned long data,
1009 int query, unset, inv, reset, r = 0;
1010 struct option_t* option = NULL;
1012 while (MoreArgs (s)) {
1013 /* reset state variables */
1015 unset = data & M_SET_UNSET;
1016 inv = data & M_SET_INV;
1017 reset = data & M_SET_RESET;
1019 if (*s->dptr == '?') {
1023 else if (m_strncmp("no", s->dptr, 2) == 0) {
1027 else if (m_strncmp("inv", s->dptr, 3) == 0) {
1031 else if (*s->dptr == '&') {
1036 /* get the variable name */
1037 mutt_extract_token (tmp, s, M_TOKEN_EQUAL);
1038 option = hash_find(ConfigOptions, tmp->data);
1039 if (!option && !(reset && m_strcmp("all", tmp->data) == 0)) {
1040 snprintf (err->data, err->dsize, _("%s: unknown variable"), tmp->data);
1043 s->dptr = vskipspaces(s->dptr);
1046 if (query || unset || inv) {
1047 snprintf (err->data, err->dsize, _("prefix is illegal with reset"));
1051 if (s && *s->dptr == '=') {
1052 snprintf (err->data, err->dsize, _("value is illegal with reset"));
1056 if (!m_strcmp("all", tmp->data)) {
1057 if (CurrentMenu == MENU_PAGER) {
1058 snprintf (err->data, err->dsize, _("Not available in this menu."));
1061 hash_map (ConfigOptions, mutt_restore_default, 1);
1062 set_option (OPTFORCEREDRAWINDEX);
1063 set_option (OPTFORCEREDRAWPAGER);
1064 set_option (OPTSORTSUBTHREADS);
1065 set_option (OPTNEEDRESORT);
1066 set_option (OPTRESORTINIT);
1067 set_option (OPTREDRAWTREE);
1070 mutt_restore_default (NULL, option, 1);
1073 else if (DTYPE (option->type) == DT_BOOL) {
1074 /* XXX this currently ignores the function table
1075 * as we don't get invert and stuff into it */
1076 if (s && *s->dptr == '=') {
1077 if (unset || inv || query) {
1078 snprintf (err->data, err->dsize, "Usage: set variable=yes|no");
1083 mutt_extract_token (tmp, s, 0);
1084 if (ascii_strcasecmp ("yes", tmp->data) == 0)
1086 else if (ascii_strcasecmp ("no", tmp->data) == 0)
1089 snprintf (err->data, err->dsize, "Usage: set variable=yes|no");
1095 bool_to_string (err->data, err->dsize, option);
1100 unset_option (option->data);
1102 toggle_option (option->data);
1104 set_option (option->data);
1106 else if (DTYPE (option->type) == DT_STR ||
1107 DTYPE (option->type) == DT_PATH ||
1108 DTYPE (option->type) == DT_MAGIC ||
1109 DTYPE (option->type) == DT_NUM ||
1110 DTYPE (option->type) == DT_SORT ||
1111 DTYPE (option->type) == DT_RX)
1113 /* XXX maybe we need to get unset into handlers? */
1114 if (DTYPE (option->type) == DT_STR || DTYPE (option->type) == DT_PATH) {
1116 p_delete((void **)(void *)&option->data);
1121 if (query || *s->dptr != '=') {
1122 FuncTable[DTYPE (option->type)].opt_tostr
1123 (err->data, err->dsize, option);
1128 mutt_extract_token (tmp, s, 0);
1129 if (!FuncTable[DTYPE (option->type)].opt_fromstr
1130 (option, tmp->data, err->data, err->dsize))
1133 else if (DTYPE (option->type) == DT_QUAD) {
1136 quad_to_string (err->data, err->dsize, option);
1140 if (*s->dptr == '=') {
1142 mutt_extract_token (tmp, s, 0);
1143 if (ascii_strcasecmp ("yes", tmp->data) == 0)
1144 set_quadoption (option->data, M_YES);
1145 else if (ascii_strcasecmp ("no", tmp->data) == 0)
1146 set_quadoption (option->data, M_NO);
1147 else if (ascii_strcasecmp ("ask-yes", tmp->data) == 0)
1148 set_quadoption (option->data, M_ASKYES);
1149 else if (ascii_strcasecmp ("ask-no", tmp->data) == 0)
1150 set_quadoption (option->data, M_ASKNO);
1152 snprintf (err->data, err->dsize, _("'%s' is invalid for $%s\n"),
1153 tmp->data, option->option);
1160 toggle_quadoption (option->data);
1162 set_quadoption (option->data, M_NO);
1164 set_quadoption (option->data, M_YES);
1168 snprintf (err->data, err->dsize, _("%s: unknown type"),
1174 set_option (OPTFORCEREDRAWINDEX);
1175 set_option (OPTFORCEREDRAWPAGER);
1176 set_option (OPTSORTSUBTHREADS);
1177 set_option (OPTNEEDRESORT);
1178 set_option (OPTRESORTINIT);
1179 set_option (OPTREDRAWTREE);
1186 /* reads the specified initialization file. returns -1 if errors were found
1187 so that we can pause to let the user know... */
1188 static int source_rc (const char *rcfile, BUFFER * err)
1191 int line = 0, rc = 0, conv = 0;
1193 char *linebuf = NULL;
1194 char *currentline = NULL;
1198 if ((f = mutt_open_read (rcfile, &pid)) == NULL) {
1199 snprintf (err->data, err->dsize, "%s: %s", rcfile, strerror (errno));
1204 while ((linebuf = mutt_read_line(linebuf, &buflen, f, &line)) != NULL) {
1205 conv = ConfigCharset && (*ConfigCharset) && mod_cset.charset;
1207 currentline = m_strdup(linebuf);
1210 mutt_convert_string (¤tline, ConfigCharset, mod_cset.charset, 0);
1213 currentline = linebuf;
1218 if (mutt_parse_rc_line (currentline, &token, err) == -1) {
1219 mutt_error (_("Error in %s, line %d: %s"), rcfile, line, err->data);
1220 if (--rc < -MAXERRS) {
1222 p_delete(¤tline);
1231 p_delete(¤tline);
1233 p_delete(&token.data);
1237 mutt_wait_filter (pid);
1239 /* the muttrc source keyword */
1240 snprintf (err->data, err->dsize,
1241 rc >= -MAXERRS ? _("source: errors in %s")
1242 : _("source: reading aborted due too many errors in %s"),
1251 static int parse_source (BUFFER * tmp, BUFFER * s,
1252 unsigned long data __attribute__ ((unused)),
1255 char path[_POSIX_PATH_MAX];
1259 if (mutt_extract_token (tmp, s, 0) != 0) {
1260 snprintf (err->data, err->dsize, _("source: error at %s"), s->dptr);
1264 m_strcpy(path, sizeof(path), tmp->data);
1265 mutt_expand_path (path, sizeof(path));
1267 rc += source_rc (path, err);
1269 while (MoreArgs (s));
1271 return ((rc < 0) ? -1 : 0);
1274 /* line command to execute
1276 token scratch buffer to be used by parser. caller should free
1277 token->data when finished. the reason for this variable is
1278 to avoid having to allocate and deallocate a lot of memory
1279 if we are parsing many lines. the caller can pass in the
1280 memory to use, which avoids having to create new space for
1281 every call to this function.
1283 err where to write error messages */
1284 int mutt_parse_rc_line (const char *line, BUFFER * token, BUFFER * err)
1290 expn.data = expn.dptr = line;
1291 expn.dsize = m_strlen(line);
1295 expn.dptr = vskipspaces(expn.dptr);
1296 while (*expn.dptr) {
1297 if (*expn.dptr == '#')
1298 break; /* rest of line is a comment */
1299 if (*expn.dptr == ';') {
1303 mutt_extract_token (token, &expn, 0);
1304 for (i = 0; Commands[i].name; i++) {
1305 if (!m_strcmp(token->data, Commands[i].name)) {
1306 if (Commands[i].func (token, &expn, Commands[i].data, err) != 0)
1311 if (!Commands[i].name) {
1312 snprintf (err->data, err->dsize, _("%s: unknown command"),
1313 NONULL (token->data));
1320 p_delete(&expn.data);
1325 #define NUMVARS (sizeof(MuttVars)/sizeof(MuttVars[0]))
1326 #define NUMCOMMANDS (sizeof(Commands)/sizeof(Commands[0]))
1327 /* initial string that starts completion. No telling how much crap
1328 * the user has typed so far. Allocate LONG_STRING just to be sure! */
1329 char User_typed[LONG_STRING] = { 0 };
1331 int Num_matched = 0; /* Number of matches for completion */
1332 char Completed[STRING] = { 0 }; /* completed string (command or variable) */
1333 const char *Matches[MAX (NUMVARS, NUMCOMMANDS) + 1]; /* all the matches + User_typed */
1335 /* helper function for completion. Changes the dest buffer if
1336 necessary/possible to aid completion.
1337 dest == completion result gets here.
1338 src == candidate for completion.
1339 try == user entered data for completion.
1340 len == length of dest buffer.
1342 static void candidate (char *dest, char *try, const char *src, int len)
1346 if (strstr (src, try) == src) {
1347 Matches[Num_matched++] = src;
1349 m_strcpy(dest, len, src);
1351 for (l = 0; src[l] && src[l] == dest[l]; l++);
1357 int mutt_command_complete (char *buffer, ssize_t len, int pos, int numtabs)
1361 int spaces; /* keep track of the number of leading spaces on the line */
1363 buffer = vskipspaces(buffer);
1364 spaces = buffer - pt;
1366 pt = buffer + pos - spaces;
1367 while ((pt > buffer) && !isspace ((unsigned char) *pt))
1370 if (pt == buffer) { /* complete cmd */
1371 /* first TAB. Collect all the matches */
1374 m_strcpy(User_typed, sizeof(User_typed), pt);
1375 p_clear(Matches, countof(Matches));
1376 p_clear(Completed, countof(Completed));
1377 for (num = 0; Commands[num].name; num++)
1378 candidate (Completed, User_typed, Commands[num].name,
1380 Matches[Num_matched++] = User_typed;
1382 /* All matches are stored. Longest non-ambiguous string is ""
1383 * i.e. dont change 'buffer'. Fake successful return this time */
1384 if (User_typed[0] == 0)
1388 if (Completed[0] == 0 && User_typed[0])
1391 /* Num_matched will _always_ be atleast 1 since the initial
1392 * user-typed string is always stored */
1393 if (numtabs == 1 && Num_matched == 2)
1394 snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
1395 else if (numtabs > 1 && Num_matched > 2)
1396 /* cycle thru all the matches */
1397 snprintf (Completed, sizeof(Completed), "%s",
1398 Matches[(numtabs - 2) % Num_matched]);
1400 /* return the completed command */
1401 m_strcpy(buffer, len - spaces, Completed);
1403 else if (!m_strncmp(buffer, "set", 3)
1404 || !m_strncmp(buffer, "unset", 5)
1405 || !m_strncmp(buffer, "reset", 5)
1406 || !m_strncmp(buffer, "toggle", 6)) { /* complete variables */
1407 const char *prefixes[] = { "no", "inv", "?", "&", NULL };
1410 /* loop through all the possible prefixes (no, inv, ...) */
1411 if (!m_strncmp(buffer, "set", 3)) {
1412 for (num = 0; prefixes[num]; num++) {
1413 if (!m_strncmp(pt, prefixes[num], m_strlen(prefixes[num]))) {
1414 pt += m_strlen(prefixes[num]);
1420 /* first TAB. Collect all the matches */
1423 m_strcpy(User_typed, sizeof(User_typed), pt);
1424 p_clear(Matches, countof(Matches));
1425 p_clear(Completed, countof(Completed));
1426 for (num = 0; MuttVars[num].option; num++)
1427 candidate(Completed, User_typed, MuttVars[num].option,
1429 Matches[Num_matched++] = User_typed;
1431 /* All matches are stored. Longest non-ambiguous string is ""
1432 * i.e. dont change 'buffer'. Fake successful return this time */
1433 if (User_typed[0] == 0)
1437 if (Completed[0] == 0 && User_typed[0])
1440 /* Num_matched will _always_ be atleast 1 since the initial
1441 * user-typed string is always stored */
1442 if (numtabs == 1 && Num_matched == 2)
1443 snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
1444 else if (numtabs > 1 && Num_matched > 2)
1445 /* cycle thru all the matches */
1446 snprintf (Completed, sizeof(Completed), "%s",
1447 Matches[(numtabs - 2) % Num_matched]);
1449 m_strcpy(pt, buffer + len - pt - spaces, Completed);
1451 else if (!m_strncmp(buffer, "exec", 4)) {
1452 struct binding_t *menu = km_get_table (CurrentMenu);
1454 if (!menu && CurrentMenu != MENU_PAGER)
1458 /* first TAB. Collect all the matches */
1461 m_strcpy(User_typed, sizeof(User_typed), pt);
1462 p_clear(Matches, countof(Matches));
1463 p_clear(Completed, countof(Completed));
1464 for (num = 0; menu[num].name; num++)
1465 candidate (Completed, User_typed, menu[num].name, sizeof(Completed));
1466 /* try the generic menu */
1467 if (Completed[0] == 0 && CurrentMenu != MENU_PAGER) {
1469 for (num = 0; menu[num].name; num++)
1470 candidate (Completed, User_typed, menu[num].name,
1473 Matches[Num_matched++] = User_typed;
1475 /* All matches are stored. Longest non-ambiguous string is ""
1476 * i.e. dont change 'buffer'. Fake successful return this time */
1477 if (User_typed[0] == 0)
1481 if (Completed[0] == 0 && User_typed[0])
1484 /* Num_matched will _always_ be atleast 1 since the initial
1485 * user-typed string is always stored */
1486 if (numtabs == 1 && Num_matched == 2)
1487 snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
1488 else if (numtabs > 1 && Num_matched > 2)
1489 /* cycle thru all the matches */
1490 snprintf (Completed, sizeof(Completed), "%s",
1491 Matches[(numtabs - 2) % Num_matched]);
1493 m_strcpy(pt, buffer + len - pt - spaces, Completed);
1501 int mutt_var_value_complete (char *buffer, ssize_t len, int pos)
1503 char var[STRING], *pt = buffer;
1505 struct option_t* option = NULL;
1510 buffer = vskipspaces(buffer);
1511 spaces = buffer - pt;
1513 pt = buffer + pos - spaces;
1514 while ((pt > buffer) && !isspace ((unsigned char) *pt))
1516 pt++; /* move past the space */
1517 if (*pt == '=') /* abort if no var before the '=' */
1520 if (m_strncmp(buffer, "set", 3) == 0) {
1521 m_strcpy(var, sizeof(var), pt);
1522 /* ignore the trailing '=' when comparing */
1523 var[m_strlen(var) - 1] = 0;
1524 if (!(option = hash_find (ConfigOptions, var)))
1525 return 0; /* no such variable. */
1527 char tmp[LONG_STRING], tmp2[LONG_STRING];
1529 ssize_t dlen = buffer + len - pt - spaces;
1530 const char *vals[] = { "no", "yes", "ask-no", "ask-yes" };
1534 if ((DTYPE (option->type) == DT_STR) ||
1535 (DTYPE (option->type) == DT_PATH) ||
1536 (DTYPE (option->type) == DT_RX)) {
1537 m_strcpy(tmp, sizeof(tmp), NONULL(*((char **)option->data)));
1538 if (DTYPE (option->type) == DT_PATH)
1539 mutt_pretty_mailbox (tmp);
1541 else if (DTYPE (option->type) == DT_QUAD)
1542 m_strcpy(tmp, sizeof(tmp), vals[quadoption(option->data)]);
1543 else if (DTYPE (option->type) == DT_NUM)
1544 snprintf (tmp, sizeof(tmp), "%d", (*((short *) option->data)));
1545 else if (DTYPE (option->type) == DT_SORT) {
1546 const struct mapping_t *map;
1549 switch (option->type & DT_SUBTYPE_MASK) {
1551 map = SortAliasMethods;
1553 case DT_SORT_BROWSER:
1554 map = SortBrowserMethods;
1557 map = SortKeyMethods;
1563 p = mutt_getnamebyvalue(*((short *) option->data) & SORT_MASK, map);
1564 snprintf(tmp, sizeof(tmp), "%s%s%s",
1565 (*((short *)option->data) & SORT_REVERSE) ? "reverse-" : "",
1566 (*((short *)option->data) & SORT_LAST) ? "last-" : "", p);
1568 else if (DTYPE (option->type) == DT_MAGIC) {
1570 switch (DefaultMagic) {
1583 m_strcpy(tmp, sizeof(tmp), p);
1585 else if (DTYPE (option->type) == DT_BOOL)
1586 m_strcpy(tmp, sizeof(tmp), option(option->data) ? "yes" : "no");
1590 for (s = tmp, d = tmp2; *s && (d - tmp2) < ssizeof(tmp2) - 2;) {
1591 if (*s == '\\' || *s == '"')
1597 m_strcpy(tmp, sizeof(tmp), pt);
1598 snprintf (pt, dlen, "%s\"%s\"", tmp, tmp2);
1606 static int mutt_execute_commands (string_list_t * p)
1609 char errstr[STRING];
1613 err.dsize = sizeof(errstr);
1615 for (; p; p = p->next) {
1616 if (mutt_parse_rc_line (p->data, &token, &err) != 0) {
1617 fprintf (stderr, _("Error in command line: %s\n"), err.data);
1618 p_delete(&token.data);
1622 p_delete(&token.data);
1626 void mutt_init (int skip_sys_rc, string_list_t * commands)
1630 char buffer[STRING], error[STRING];
1631 int default_rc = 0, need_pause = 0;
1637 err.dsize = sizeof(error);
1639 ConfigOptions = hash_new (sizeof(MuttVars) * 2, 0);
1640 for (i = 0; MuttVars[i].option; i++) {
1641 hash_insert (ConfigOptions, MuttVars[i].option, &MuttVars[i]);
1645 * XXX - use something even more difficult to predict?
1647 snprintf (AttachmentMarker, sizeof(AttachmentMarker),
1648 "\033]9;%ld\a", (long) time (NULL));
1651 /* Get some information about the user */
1652 if ((pw = getpwuid (getuid ()))) {
1654 mutt_gecos_name(rnbuf, sizeof(rnbuf), pw, mod_core.gecos_mask);
1655 Realname = m_strdup(rnbuf);
1658 if ((p = getenv("MAIL") ?: getenv("MAILDIR"))) {
1659 Spoolfile = m_strdup(p);
1661 mutt_concat_path(buffer, sizeof(buffer), NONULL(mod_core.homedir), MAILPATH);
1662 Spoolfile = m_strdup(buffer);
1665 if ((p = getenv ("REPLYTO")) != NULL) {
1668 snprintf (buffer, sizeof(buffer), "Reply-To: %s", p);
1671 buf.data = buf.dptr = buffer;
1672 buf.dsize = m_strlen(buffer);
1675 parse_my_hdr (&token, &buf, 0, &err);
1676 p_delete(&token.data);
1679 /* Set standard defaults */
1680 hash_map (ConfigOptions, mutt_set_default, 0);
1681 hash_map (ConfigOptions, mutt_restore_default, 0);
1683 CurrentMenu = MENU_MAIN;
1684 mutt_init_history ();
1687 snprintf (buffer, sizeof(buffer), "%s/.madmuttrc", NONULL(mod_core.homedir));
1688 if (access (buffer, F_OK) == -1)
1689 snprintf (buffer, sizeof(buffer), "%s/.madmutt/madmuttrc",
1690 NONULL(mod_core.homedir));
1693 Muttrc = m_strdup(buffer);
1696 m_strcpy(buffer, sizeof(buffer), Muttrc);
1698 mutt_expand_path (buffer, sizeof(buffer));
1699 Muttrc = m_strdup(buffer);
1702 /* Process the global rc file if it exists and the user hasn't explicity
1703 requested not to via "-n". */
1705 snprintf (buffer, sizeof(buffer), "%s/Madmuttrc-%s", SYSCONFDIR,
1707 if (access (buffer, F_OK) == -1)
1708 snprintf (buffer, sizeof(buffer), "%s/Madmuttrc", SYSCONFDIR);
1709 if (access (buffer, F_OK) == -1)
1710 snprintf (buffer, sizeof(buffer), "%s/Madmuttrc-%s", PKGDATADIR,
1712 if (access (buffer, F_OK) == -1)
1713 snprintf (buffer, sizeof(buffer), "%s/Madmuttrc", PKGDATADIR);
1714 if (access (buffer, F_OK) != -1) {
1715 if (source_rc (buffer, &err) != 0) {
1716 fputs (err.data, stderr);
1717 fputc ('\n', stderr);
1723 /* Read the user's initialization file. */
1724 if (access (Muttrc, F_OK) != -1) {
1725 if (!option (OPTNOCURSES))
1727 if (source_rc (Muttrc, &err) != 0) {
1728 fputs (err.data, stderr);
1729 fputc ('\n', stderr);
1733 else if (!default_rc) {
1734 /* file specified by -F does not exist */
1735 snprintf (buffer, sizeof(buffer), "%s: %s", Muttrc, strerror (errno));
1736 mutt_endwin (buffer);
1741 snprintf(buffer, sizeof(buffer), "%s/.madmutt.lua", NONULL(mod_core.homedir));
1742 if (access(buffer, F_OK) < 0)
1743 snprintf(buffer, sizeof(buffer), "%s/.madmutt/cfg.lua", NONULL(mod_core.homedir));
1744 if (!access(buffer, F_OK)) {
1745 need_pause = luaM_wrap(mutt_error, luaM_dofile(buffer));
1749 if (mutt_execute_commands (commands) != 0)
1752 /* warn about synonym variables */
1756 fprintf (stderr, _("Warning: the following synonym variables were found:\n"));
1758 for (syn = Synonyms; syn; syn = syn->next) {
1759 fprintf(stderr, "$%s ($%s should be used) (%s:%d)\n",
1760 syn->o ? NONULL(syn->o->option) : "",
1761 syn->n ? NONULL(syn->n->option) : "",
1762 NONULL(syn->f), syn->l);
1764 fprintf (stderr, _("Warning: synonym variables are scheduled"
1765 " for removal.\n"));
1766 syn_list_wipe(&Synonyms);
1770 if (need_pause && !option (OPTNOCURSES)) {
1771 if (mutt_any_key_to_continue (NULL) == -1)
1776 int mutt_get_hook_type (const char *name)
1778 struct command_t *c;
1780 for (c = Commands; c->name; c++)
1781 if (c->func == mutt_parse_hook && ascii_strcasecmp (c->name, name) == 0)