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/curses.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);
85 { "smtp_use_tls", send_smtp_check_usetls },
87 { "history", check_history },
88 { "pager_index_lines", check_num },
93 static void bool_to_string (char* dst, ssize_t dstlen,
94 struct option_t* option) {
95 snprintf (dst, dstlen, "%s=%s", option->option,
96 option (option->data) ? "yes" : "no");
99 static int bool_from_string (struct option_t* dst, const char* val,
100 char* errbuf __attribute__ ((unused)),
101 ssize_t errlen __attribute__ ((unused))) {
106 if (ascii_strncasecmp (val, "yes", 3) == 0)
108 else if (ascii_strncasecmp (val, "no", 2) == 0)
114 set_option (dst->data);
116 unset_option (dst->data);
120 static void num_to_string (char* dst, ssize_t dstlen,
121 struct option_t* option) {
123 const char* fmt = (m_strcmp(option->option, "umask") == 0) ?
125 snprintf (dst, dstlen, fmt, option->option,
126 *((short*) option->data));
129 static int num_from_string (struct option_t* dst, const char* val,
130 char* errbuf, ssize_t errlen) {
131 int num = 0, old = 0;
137 num = strtol (val, &t, 0);
139 if (m_strisempty(val) || *t || (short) num != num) {
141 snprintf (errbuf, errlen, _("'%s' is invalid for $%s"),
147 /* just temporarily accept new val so that check_special for
148 * $history already has it when doing history's init() */
149 old = *((short*) dst->data);
150 *((short*) dst->data) = (short) num;
152 if (!check_special (dst->option, (unsigned long) num, errbuf, errlen)) {
153 *((short*) dst->data) = old;
160 static void str_to_string (char* dst, ssize_t dstlen,
161 struct option_t* option) {
162 snprintf (dst, dstlen, "%s=\"%s\"", option->option,
163 NONULL (*((char**) option->data)));
166 static int path_from_string (struct option_t* dst, const char* val,
167 char* errbuf __attribute__ ((unused)), ssize_t errlen __attribute__ ((unused))) {
168 char path[_POSIX_PATH_MAX];
173 if (m_strisempty(val)) {
174 p_delete((char**) dst->data);
179 m_strcpy(path, sizeof(path), val);
180 mutt_expand_path (path, sizeof(path));
181 m_strreplace((char **) dst->data, path);
185 static int str_from_string (struct option_t* dst, const char* val,
186 char* errbuf, ssize_t errlen) {
190 if (!check_special (dst->option, (unsigned long) val, errbuf, errlen))
193 m_strreplace((char**) dst->data, val);
197 static void quad_to_string (char* dst, ssize_t dstlen,
198 struct option_t* option) {
199 const char *vals[] = { "no", "yes", "ask-no", "ask-yes" };
200 snprintf (dst, dstlen, "%s=%s", option->option,
201 vals[quadoption (option->data)]);
204 static int quad_from_string (struct option_t* dst, const char* val,
205 char* errbuf __attribute__ ((unused)), ssize_t errlen __attribute__ ((unused))) {
210 if (ascii_strncasecmp (val, "yes", 3) == 0)
212 else if (ascii_strncasecmp (val, "no", 2) == 0)
214 else if (ascii_strncasecmp (val, "ask-yes", 7) == 0)
216 else if (ascii_strncasecmp (val, "ask-no", 6) == 0)
222 set_quadoption (dst->data, flag);
226 static void sort_to_string (char* dst, ssize_t dstlen,
227 struct option_t* option) {
228 const struct mapping_t *map = get_sortmap (option);
229 const char *p = NULL;
232 snprintf (dst, sizeof(dst), "%s=unknown", option->option);
236 p = mutt_getnamebyvalue(*((short *)option->data) & SORT_MASK, map);
238 snprintf (dst, dstlen, "%s=%s%s%s", option->option,
239 (*((short *) option->data) & SORT_REVERSE) ?
241 (*((short *) option->data) & SORT_LAST) ? "last-" :
245 static int sort_from_string (struct option_t* dst, const char* val,
246 char* errbuf, ssize_t errlen) {
247 const struct mapping_t *map = NULL;
248 if (!(map = get_sortmap (dst))) {
250 snprintf (errbuf, errlen, _("%s: Unknown type."),
254 if (parse_sort (dst, val, map, errbuf, errlen) == -1)
259 static void rx_to_string (char* dst, ssize_t dstlen,
260 struct option_t* option) {
261 rx_t* p = (rx_t*) option->data;
262 snprintf (dst, dstlen, "%s=\"%s\"", option->option,
263 NONULL (p->pattern));
266 static int rx_from_string (struct option_t* dst, const char* val,
267 char* errbuf, ssize_t errlen) {
270 int flags = 0, e = 0, neg = 0;
276 if (option (OPTATTACHMSG) && !m_strcmp(dst->option, "reply_regexp")) {
278 snprintf (errbuf, errlen,
279 "Operation not permitted when in attach-message mode.");
283 if (!((rx_t*) dst->data))
284 *((rx_t**) dst->data) = p_new(rx_t, 1);
286 p = (rx_t*) dst->data;
288 /* something to do? */
289 if (m_strisempty(val) || (p->pattern && m_strcmp(p->pattern, val) == 0))
292 if (m_strcmp(dst->option, "mask") != 0)
293 flags |= mutt_which_case (val);
296 if (m_strcmp(dst->option, "mask") == 0 && *s == '!') {
301 rx = p_new(regex_t, 1);
303 if ((e = REGCOMP (rx, s, flags)) != 0) {
304 regerror (e, rx, errbuf, errlen);
315 m_strreplace(&p->pattern, val);
319 if (m_strcmp(dst->option, "reply_regexp") == 0)
320 mutt_adjust_all_subjects ();
325 static void magic_to_string (char* dst, ssize_t dstlen,
326 struct option_t* option) {
327 const char* s = NULL;
328 switch (option->data) {
329 case M_MBOX: s = "mbox"; break;
330 case M_MH: s = "MH"; break;
331 case M_MAILDIR: s = "Maildir"; break;
332 default: s = "unknown"; break;
334 snprintf (dst, dstlen, "%s=%s", option->option, s);
337 static int magic_from_string (struct option_t* dst, const char* val,
338 char *errbuf, ssize_t errlen)
342 if (!dst || m_strisempty(val))
344 if (ascii_strncasecmp (val, "mbox", 4) == 0)
346 else if (ascii_strncasecmp (val, "mh", 2) == 0)
348 else if (ascii_strncasecmp (val, "maildir", 7) == 0)
354 *((short*) dst->data) = flag;
361 void (*opt_tostr) (char* dst, ssize_t dstlen, struct option_t* option);
362 int (*opt_fromstr) (struct option_t* dst, const char* val,
363 char* errbuf, ssize_t errlen);
365 { 0, NULL, NULL }, /* there's no DT_ type with 0 */
366 { DT_BOOL, bool_to_string, bool_from_string },
367 { DT_NUM, num_to_string, num_from_string },
368 { DT_STR, str_to_string, str_from_string },
369 { DT_PATH, str_to_string, path_from_string },
370 { DT_QUAD, quad_to_string, quad_from_string },
371 { DT_SORT, sort_to_string, sort_from_string },
372 { DT_RX, rx_to_string, rx_from_string },
373 { DT_MAGIC, magic_to_string, magic_from_string },
377 int mutt_option_value (const char* val, char* dst, ssize_t dstlen) {
378 struct option_t* option = NULL;
379 char* tmp = NULL, *t = NULL;
382 if (!(option = hash_find (ConfigOptions, val))) {
386 tmp = p_new(char, dstlen+1);
387 FuncTable[DTYPE(option->type)].opt_tostr (tmp, dstlen, option);
389 /* as we get things of type $var=value and don't want to bloat the
390 * above "just" for expansion, we do the stripping here */
391 t = strchr (tmp, '=');
395 if (t[l-1] == '"' && *t == '"') {
400 memcpy (dst, t, l+1);
406 static void toggle_quadoption (int opt)
409 int b = (opt % 4) * 2;
411 QuadOptions[n] ^= (1 << b);
414 void set_quadoption (int opt, int flag)
417 int b = (opt % 4) * 2;
419 QuadOptions[n] &= ~(0x3 << b);
420 QuadOptions[n] |= (flag & 0x3) << b;
423 int quadoption (int opt)
426 int b = (opt % 4) * 2;
428 return (QuadOptions[n] >> b) & 0x3;
431 int query_quadoption2(int v, const char *prompt)
439 v = mutt_yesorno(prompt, (v == M_ASKYES));
440 CLEARLINE (LINES - 1);
445 int query_quadoption (int opt, const char *prompt)
447 int v = quadoption (opt);
455 v = mutt_yesorno (prompt, (v == M_ASKYES));
456 CLEARLINE (LINES - 1);
463 /* always wise to do what someone else did before */
464 static void _attachments_clean (void) {
466 if (Context && Context->msgcount) {
467 for (i = 0; i < Context->msgcount; i++)
468 Context->hdrs[i]->attach_valid = 0;
472 static int parse_attach_list (BUFFER *buf, BUFFER *s, string_list_t **ldata,
473 BUFFER *err __attribute__ ((unused))) {
475 string_list_t *listp, *lastp;
480 /* Find the last item in the list that data points to. */
482 for (listp = *ldata; listp; listp = listp->next) {
483 a = (ATTACH_MATCH *)listp->data;
488 mutt_extract_token (buf, s, 0);
490 if (!buf->data || *buf->data == '\0')
493 a = p_new(ATTACH_MATCH, 1);
495 /* some cheap hacks that I expect to remove */
496 if (!m_strcasecmp(buf->data, "any"))
497 a->major = m_strdup("*/.*");
498 else if (!m_strcasecmp(buf->data, "none"))
499 a->major = m_strdup("cheap_hack/this_should_never_match");
501 a->major = m_strdup(buf->data);
503 if ((p = strchr(a->major, '/'))) {
508 a->minor = "unknown";
511 len = m_strlen(a->minor);
512 tmpminor = p_new(char, len + 3);
513 m_strcpy(&tmpminor[1], len + 3, a->minor);
515 tmpminor[len+1] = '$';
516 tmpminor[len+2] = '\0';
518 a->major_int = mutt_check_mime_type(a->major);
519 regcomp(&a->minor_rx, tmpminor, REG_ICASE|REG_EXTENDED);
523 listp = p_new(string_list_t, 1);
524 listp->data = (char *)a;
533 while (MoreArgs (s));
535 _attachments_clean();
539 static int parse_unattach_list (BUFFER *buf, BUFFER *s, string_list_t **ldata,
540 BUFFER *err __attribute__ ((unused))) {
542 string_list_t *lp, *lastp, *newlp;
548 mutt_extract_token (buf, s, 0);
550 if (!m_strcasecmp(buf->data, "any"))
551 tmp = m_strdup("*/.*");
552 else if (!m_strcasecmp(buf->data, "none"))
553 tmp = m_strdup("cheap_hack/this_should_never_match");
555 tmp = m_strdup(buf->data);
557 if ((minor = strchr(tmp, '/'))) {
561 minor = m_strdup("unknown");
563 major = mutt_check_mime_type(tmp);
565 /* We must do our own walk here because string_list_remove() will only
566 * remove the string_list_t->data, not anything pointed to by the string_list_t->data. */
568 for(lp = *ldata; lp; ) {
569 a = (ATTACH_MATCH *)lp->data;
570 if (a->major_int == major && !m_strcasecmp(minor, a->minor)) {
571 regfree(&a->minor_rx);
574 /* Relink backward */
576 lastp->next = lp->next;
581 p_delete(&lp->data); /* same as a */
591 while (MoreArgs (s));
594 _attachments_clean();
598 static int print_attach_list (string_list_t *lp, char op, const char *name) {
600 printf("attachments %c%s %s/%s\n", op, name,
601 ((ATTACH_MATCH *)lp->data)->major,
602 ((ATTACH_MATCH *)lp->data)->minor);
609 static int parse_attachments (BUFFER *buf, BUFFER *s,
610 unsigned long data __attribute__ ((unused)),
613 string_list_t **listp;
615 mutt_extract_token(buf, s, 0);
616 if (!buf->data || *buf->data == '\0') {
617 m_strcpy(err->data, err->dsize, _("attachments: no disposition"));
621 category = buf->data;
627 printf("\nCurrent attachments settings:\n\n");
628 print_attach_list(AttachAllow, '+', "A");
629 print_attach_list(AttachExclude, '-', "A");
630 print_attach_list(InlineAllow, '+', "I");
631 print_attach_list(InlineExclude, '-', "I");
632 set_option (OPTFORCEREDRAWINDEX);
633 set_option (OPTFORCEREDRAWPAGER);
634 mutt_any_key_to_continue (NULL);
638 if (op != '+' && op != '-') {
642 if (!m_strncasecmp(category, "attachment", strlen(category))) {
644 listp = &AttachAllow;
646 listp = &AttachExclude;
648 else if (!m_strncasecmp(category, "inline", strlen(category))) {
650 listp = &InlineAllow;
652 listp = &InlineExclude;
654 m_strcpy(err->data, err->dsize, _("attachments: invalid disposition"));
658 return parse_attach_list(buf, s, listp, err);
661 static int parse_unattachments (BUFFER *buf, BUFFER *s, unsigned long data __attribute__ ((unused)), BUFFER *err) {
663 string_list_t **listp;
665 mutt_extract_token(buf, s, 0);
666 if (!buf->data || *buf->data == '\0') {
667 m_strcpy(err->data, err->dsize, _("unattachments: no disposition"));
673 if (op != '+' && op != '-') {
677 if (!m_strncasecmp(p, "attachment", strlen(p))) {
679 listp = &AttachAllow;
681 listp = &AttachExclude;
683 else if (!m_strncasecmp(p, "inline", strlen(p))) {
685 listp = &InlineAllow;
687 listp = &InlineExclude;
690 m_strcpy(err->data, err->dsize, _("unattachments: invalid disposition"));
694 return parse_unattach_list(buf, s, listp, err);
697 static int parse_unalias (BUFFER * buf, BUFFER * s,
698 unsigned long data __attribute__ ((unused)),
699 BUFFER * err __attribute__ ((unused)))
701 alias_t *tmp, **last;
704 mutt_extract_token (buf, s, 0);
706 if (!m_strcmp("*", buf->data) == 0) {
707 if (CurrentMenu == MENU_ALIAS) {
708 for (tmp = Aliases; tmp; tmp = tmp->next)
710 set_option(OPTFORCEREDRAWINDEX);
712 alias_list_wipe(&Aliases);
718 for (last = &Aliases; *last; last = &(*last)->next) {
719 if (!m_strcasecmp(buf->data, (*last)->name)) {
720 if (CurrentMenu == MENU_ALIAS) {
722 set_option (OPTFORCEREDRAWINDEX);
724 tmp = alias_list_pop(last);
730 } while (MoreArgs(s));
735 static int parse_alias (BUFFER * buf, BUFFER * s,
736 unsigned long data __attribute__ ((unused)),
743 m_strcpy(err->data, err->dsize, _("alias: no address"));
747 mutt_extract_token (buf, s, 0);
749 /* check to see if an alias with this name already exists */
750 for (last = &Aliases; *last; last = &(*last)->next) {
751 if (!m_strcasecmp((*last)->name, buf->data))
756 /* create a new alias */
758 (*last)->name = m_strdup(buf->data);
759 /* give the main addressbook code a chance */
760 if (CurrentMenu == MENU_ALIAS)
761 set_option (OPTMENUCALLER);
763 /* override the previous value */
764 address_list_wipe(&(*last)->addr);
765 if (CurrentMenu == MENU_ALIAS)
766 set_option (OPTFORCEREDRAWINDEX);
769 mutt_extract_token(buf, s, M_TOKEN_QUOTE | M_TOKEN_SPACE);
770 (*last)->addr = mutt_parse_adrlist((*last)->addr, buf->data);
771 if (mutt_addrlist_to_idna((*last)->addr, &estr)) {
772 snprintf (err->data, err->dsize,
773 _("Warning: Bad IDN '%s' in alias '%s'.\n"), estr, (*last)->name);
782 parse_unmy_hdr(BUFFER * buf, BUFFER * s,
783 unsigned long data __attribute__ ((unused)),
784 BUFFER * err __attribute__ ((unused)))
787 mutt_extract_token (buf, s, 0);
789 if (!m_strcmp("*", buf->data)) {
790 string_list_wipe(&UserHeader);
792 string_list_t **last = &UserHeader;
793 ssize_t l = m_strlen(buf->data);
795 if (buf->data[l - 1] == ':')
799 if (!ascii_strncasecmp(buf->data, (*last)->data, l)
800 && (*last)->data[l] == ':')
802 string_list_t *tmp = string_list_pop(last);
803 string_item_delete(&tmp);
805 last = &(*last)->next;
809 } while (MoreArgs(s));
814 static int parse_my_hdr (BUFFER * buf, BUFFER * s, unsigned long data __attribute__ ((unused)),
821 mutt_extract_token (buf, s, M_TOKEN_SPACE | M_TOKEN_QUOTE);
822 if ((p = strpbrk (buf->data, ": \t")) == NULL || *p != ':') {
823 m_strcpy(err->data, err->dsize, _("invalid header field"));
826 keylen = p - buf->data + 1;
829 for (tmp = UserHeader;; tmp = tmp->next) {
830 /* see if there is already a field by this name */
831 if (ascii_strncasecmp (buf->data, tmp->data, keylen) == 0) {
832 /* replace the old value */
833 p_delete(&tmp->data);
834 tmp->data = buf->data;
841 tmp->next = string_item_new();
845 tmp = string_item_new();
848 tmp->data = buf->data;
854 parse_sort (struct option_t* dst, const char *s, const struct mapping_t *map,
855 char* errbuf, ssize_t errlen) {
858 if (m_strncmp("reverse-", s, 8) == 0) {
860 flags = SORT_REVERSE;
863 if (m_strncmp("last-", s, 5) == 0) {
868 if ((i = mutt_getvaluebyname (s, map)) == -1) {
870 snprintf (errbuf, errlen, _("'%s' is invalid for $%s"), s, dst->option);
874 *((short*) dst->data) = i | flags;
878 /* if additional data more == 1, we want to resolve synonyms */
879 static void mutt_set_default(const char *name __attribute__ ((unused)), void* p, unsigned long more)
881 char buf[LONG_STRING];
882 struct option_t *ptr = p;
884 if (!ptr || *ptr->init || !FuncTable[DTYPE (ptr->type)].opt_fromstr)
887 mutt_option_value(ptr->option, buf, sizeof(buf));
888 if (m_strlen(ptr->init) == 0 && buf && *buf)
889 ptr->init = m_strdup(buf);
892 static int init_expand (char** dst, struct option_t* src) {
898 if (DTYPE(src->type) == DT_STR || DTYPE(src->type) == DT_PATH) {
899 /* only expand for string as it's the only place where
900 * we want to expand vars right now */
901 if (src->init && *src->init) {
904 len = m_strlen(src->init) + 2;
905 in.data = p_new(char, len + 1);
906 snprintf (in.data, len, "\"%s\"", src->init);
909 mutt_extract_token (&token, &in, 0);
910 if (token.data && *token.data)
911 *dst = m_strdup(token.data);
915 p_delete(&token.data);
919 /* for non-string: take value as is */
920 *dst = m_strdup(src->init);
924 /* if additional data more == 1, we want to resolve synonyms */
925 static void mutt_restore_default (const char* name __attribute__ ((unused)),
926 void* p, unsigned long more) {
928 struct option_t* ptr = (struct option_t*) p;
933 if (FuncTable[DTYPE (ptr->type)].opt_fromstr) {
934 init_expand (&init, ptr);
935 if (!FuncTable[DTYPE (ptr->type)].opt_fromstr (ptr, init, errbuf,
937 if (!option (OPTNOCURSES))
939 fprintf (stderr, _("Invalid default setting for $%s found: \"%s\".\n"
940 "Please report this error: \"%s\"\n"),
941 ptr->option, NONULL (init), errbuf);
947 if (ptr->flags & R_INDEX)
948 set_option (OPTFORCEREDRAWINDEX);
949 if (ptr->flags & R_PAGER)
950 set_option (OPTFORCEREDRAWPAGER);
951 if (ptr->flags & R_RESORT_SUB)
952 set_option (OPTSORTSUBTHREADS);
953 if (ptr->flags & R_RESORT)
954 set_option (OPTNEEDRESORT);
955 if (ptr->flags & R_RESORT_INIT)
956 set_option (OPTRESORTINIT);
957 if (ptr->flags & R_TREE)
958 set_option (OPTREDRAWTREE);
961 static int check_num (const char* option, unsigned long p,
962 char* errbuf, ssize_t errlen) {
965 snprintf (errbuf, errlen, _("'%d' is invalid for $%s"), (int) p, option);
971 static int check_history (const char* option __attribute__ ((unused)), unsigned long p,
972 char* errbuf, ssize_t errlen) {
973 if (!check_num ("history", p, errbuf, errlen))
975 mutt_init_history ();
979 static int check_special (const char* name, unsigned long val,
980 char* errbuf, ssize_t errlen) {
983 for (i = 0; SpecialVars[i].name; i++) {
984 if (m_strcmp(SpecialVars[i].name, name) == 0) {
985 return (SpecialVars[i].check (SpecialVars[i].name,
986 val, errbuf, errlen));
992 static const struct mapping_t* get_sortmap (struct option_t* option) {
993 const struct mapping_t* map = NULL;
995 switch (option->type & DT_SUBTYPE_MASK) {
997 map = SortAliasMethods;
999 case DT_SORT_BROWSER:
1000 map = SortBrowserMethods;
1003 map = SortKeyMethods;
1006 map = SortAuxMethods;
1015 #define CHECK_PAGER \
1016 if ((CurrentMenu == MENU_PAGER) && \
1017 (!option || (option->flags & R_RESORT))) \
1019 snprintf (err->data, err->dsize, \
1020 _("Not available in this menu.")); \
1024 static int parse_set (BUFFER * tmp, BUFFER * s, unsigned long data,
1027 int query, unset, inv, reset, r = 0;
1028 struct option_t* option = NULL;
1030 while (MoreArgs (s)) {
1031 /* reset state variables */
1033 unset = data & M_SET_UNSET;
1034 inv = data & M_SET_INV;
1035 reset = data & M_SET_RESET;
1037 if (*s->dptr == '?') {
1041 else if (m_strncmp("no", s->dptr, 2) == 0) {
1045 else if (m_strncmp("inv", s->dptr, 3) == 0) {
1049 else if (*s->dptr == '&') {
1054 /* get the variable name */
1055 mutt_extract_token (tmp, s, M_TOKEN_EQUAL);
1056 option = hash_find(ConfigOptions, tmp->data);
1057 if (!option && !(reset && m_strcmp("all", tmp->data) == 0)) {
1058 snprintf (err->data, err->dsize, _("%s: unknown variable"), tmp->data);
1061 s->dptr = vskipspaces(s->dptr);
1064 if (query || unset || inv) {
1065 snprintf (err->data, err->dsize, _("prefix is illegal with reset"));
1069 if (s && *s->dptr == '=') {
1070 snprintf (err->data, err->dsize, _("value is illegal with reset"));
1074 if (!m_strcmp("all", tmp->data)) {
1075 if (CurrentMenu == MENU_PAGER) {
1076 snprintf (err->data, err->dsize, _("Not available in this menu."));
1079 hash_map (ConfigOptions, mutt_restore_default, 1);
1080 set_option (OPTFORCEREDRAWINDEX);
1081 set_option (OPTFORCEREDRAWPAGER);
1082 set_option (OPTSORTSUBTHREADS);
1083 set_option (OPTNEEDRESORT);
1084 set_option (OPTRESORTINIT);
1085 set_option (OPTREDRAWTREE);
1089 mutt_restore_default (NULL, option, 1);
1092 else if (DTYPE (option->type) == DT_BOOL) {
1093 /* XXX this currently ignores the function table
1094 * as we don't get invert and stuff into it */
1095 if (s && *s->dptr == '=') {
1096 if (unset || inv || query) {
1097 snprintf (err->data, err->dsize, "Usage: set variable=yes|no");
1102 mutt_extract_token (tmp, s, 0);
1103 if (ascii_strcasecmp ("yes", tmp->data) == 0)
1105 else if (ascii_strcasecmp ("no", tmp->data) == 0)
1108 snprintf (err->data, err->dsize, "Usage: set variable=yes|no");
1114 bool_to_string (err->data, err->dsize, option);
1120 unset_option (option->data);
1122 toggle_option (option->data);
1124 set_option (option->data);
1126 else if (DTYPE (option->type) == DT_STR ||
1127 DTYPE (option->type) == DT_PATH ||
1128 DTYPE (option->type) == DT_MAGIC ||
1129 DTYPE (option->type) == DT_NUM ||
1130 DTYPE (option->type) == DT_SORT ||
1131 DTYPE (option->type) == DT_RX)
1133 /* XXX maybe we need to get unset into handlers? */
1134 if (DTYPE (option->type) == DT_STR || DTYPE (option->type) == DT_PATH) {
1137 p_delete((void **)(void *)&option->data);
1142 if (query || *s->dptr != '=') {
1143 FuncTable[DTYPE (option->type)].opt_tostr
1144 (err->data, err->dsize, option);
1150 mutt_extract_token (tmp, s, 0);
1151 if (!FuncTable[DTYPE (option->type)].opt_fromstr
1152 (option, tmp->data, err->data, err->dsize))
1155 else if (DTYPE (option->type) == DT_QUAD) {
1158 quad_to_string (err->data, err->dsize, option);
1162 if (*s->dptr == '=') {
1165 mutt_extract_token (tmp, s, 0);
1166 if (ascii_strcasecmp ("yes", tmp->data) == 0)
1167 set_quadoption (option->data, M_YES);
1168 else if (ascii_strcasecmp ("no", tmp->data) == 0)
1169 set_quadoption (option->data, M_NO);
1170 else if (ascii_strcasecmp ("ask-yes", tmp->data) == 0)
1171 set_quadoption (option->data, M_ASKYES);
1172 else if (ascii_strcasecmp ("ask-no", tmp->data) == 0)
1173 set_quadoption (option->data, M_ASKNO);
1175 snprintf (err->data, err->dsize, _("'%s' is invalid for $%s\n"),
1176 tmp->data, option->option);
1183 toggle_quadoption (option->data);
1185 set_quadoption (option->data, M_NO);
1187 set_quadoption (option->data, M_YES);
1191 snprintf (err->data, err->dsize, _("%s: unknown type"),
1197 if (option->flags & R_INDEX)
1198 set_option (OPTFORCEREDRAWINDEX);
1199 if (option->flags & R_PAGER)
1200 set_option (OPTFORCEREDRAWPAGER);
1201 if (option->flags & R_RESORT_SUB)
1202 set_option (OPTSORTSUBTHREADS);
1203 if (option->flags & R_RESORT)
1204 set_option (OPTNEEDRESORT);
1205 if (option->flags & R_RESORT_INIT)
1206 set_option (OPTRESORTINIT);
1207 if (option->flags & R_TREE)
1208 set_option (OPTREDRAWTREE);
1215 /* reads the specified initialization file. returns -1 if errors were found
1216 so that we can pause to let the user know... */
1217 static int source_rc (const char *rcfile, BUFFER * err)
1220 int line = 0, rc = 0, conv = 0;
1222 char *linebuf = NULL;
1223 char *currentline = NULL;
1227 if ((f = mutt_open_read (rcfile, &pid)) == NULL) {
1228 snprintf (err->data, err->dsize, "%s: %s", rcfile, strerror (errno));
1233 while ((linebuf = mutt_read_line(linebuf, &buflen, f, &line)) != NULL) {
1234 conv = ConfigCharset && (*ConfigCharset) && mod_cset.charset;
1236 currentline = m_strdup(linebuf);
1239 mutt_convert_string (¤tline, ConfigCharset, mod_cset.charset, 0);
1242 currentline = linebuf;
1247 if (mutt_parse_rc_line (currentline, &token, err) == -1) {
1248 mutt_error (_("Error in %s, line %d: %s"), rcfile, line, err->data);
1249 if (--rc < -MAXERRS) {
1251 p_delete(¤tline);
1260 p_delete(¤tline);
1262 p_delete(&token.data);
1266 mutt_wait_filter (pid);
1268 /* the muttrc source keyword */
1269 snprintf (err->data, err->dsize,
1270 rc >= -MAXERRS ? _("source: errors in %s")
1271 : _("source: reading aborted due too many errors in %s"),
1280 static int parse_source (BUFFER * tmp, BUFFER * s,
1281 unsigned long data __attribute__ ((unused)),
1284 char path[_POSIX_PATH_MAX];
1288 if (mutt_extract_token (tmp, s, 0) != 0) {
1289 snprintf (err->data, err->dsize, _("source: error at %s"), s->dptr);
1293 m_strcpy(path, sizeof(path), tmp->data);
1294 mutt_expand_path (path, sizeof(path));
1296 rc += source_rc (path, err);
1298 while (MoreArgs (s));
1300 return ((rc < 0) ? -1 : 0);
1303 /* line command to execute
1305 token scratch buffer to be used by parser. caller should free
1306 token->data when finished. the reason for this variable is
1307 to avoid having to allocate and deallocate a lot of memory
1308 if we are parsing many lines. the caller can pass in the
1309 memory to use, which avoids having to create new space for
1310 every call to this function.
1312 err where to write error messages */
1313 int mutt_parse_rc_line (const char *line, BUFFER * token, BUFFER * err)
1319 expn.data = expn.dptr = line;
1320 expn.dsize = m_strlen(line);
1324 expn.dptr = vskipspaces(expn.dptr);
1325 while (*expn.dptr) {
1326 if (*expn.dptr == '#')
1327 break; /* rest of line is a comment */
1328 if (*expn.dptr == ';') {
1332 mutt_extract_token (token, &expn, 0);
1333 for (i = 0; Commands[i].name; i++) {
1334 if (!m_strcmp(token->data, Commands[i].name)) {
1335 if (Commands[i].func (token, &expn, Commands[i].data, err) != 0)
1340 if (!Commands[i].name) {
1341 snprintf (err->data, err->dsize, _("%s: unknown command"),
1342 NONULL (token->data));
1349 p_delete(&expn.data);
1354 #define NUMVARS (sizeof(MuttVars)/sizeof(MuttVars[0]))
1355 #define NUMCOMMANDS (sizeof(Commands)/sizeof(Commands[0]))
1356 /* initial string that starts completion. No telling how much crap
1357 * the user has typed so far. Allocate LONG_STRING just to be sure! */
1358 char User_typed[LONG_STRING] = { 0 };
1360 int Num_matched = 0; /* Number of matches for completion */
1361 char Completed[STRING] = { 0 }; /* completed string (command or variable) */
1362 const char *Matches[MAX (NUMVARS, NUMCOMMANDS) + 1]; /* all the matches + User_typed */
1364 /* helper function for completion. Changes the dest buffer if
1365 necessary/possible to aid completion.
1366 dest == completion result gets here.
1367 src == candidate for completion.
1368 try == user entered data for completion.
1369 len == length of dest buffer.
1371 static void candidate (char *dest, char *try, const char *src, int len)
1375 if (strstr (src, try) == src) {
1376 Matches[Num_matched++] = src;
1378 m_strcpy(dest, len, src);
1380 for (l = 0; src[l] && src[l] == dest[l]; l++);
1386 int mutt_command_complete (char *buffer, ssize_t len, int pos, int numtabs)
1390 int spaces; /* keep track of the number of leading spaces on the line */
1392 buffer = vskipspaces(buffer);
1393 spaces = buffer - pt;
1395 pt = buffer + pos - spaces;
1396 while ((pt > buffer) && !isspace ((unsigned char) *pt))
1399 if (pt == buffer) { /* complete cmd */
1400 /* first TAB. Collect all the matches */
1403 m_strcpy(User_typed, sizeof(User_typed), pt);
1404 p_clear(Matches, countof(Matches));
1405 p_clear(Completed, countof(Completed));
1406 for (num = 0; Commands[num].name; num++)
1407 candidate (Completed, User_typed, Commands[num].name,
1409 Matches[Num_matched++] = User_typed;
1411 /* All matches are stored. Longest non-ambiguous string is ""
1412 * i.e. dont change 'buffer'. Fake successful return this time */
1413 if (User_typed[0] == 0)
1417 if (Completed[0] == 0 && User_typed[0])
1420 /* Num_matched will _always_ be atleast 1 since the initial
1421 * user-typed string is always stored */
1422 if (numtabs == 1 && Num_matched == 2)
1423 snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
1424 else if (numtabs > 1 && Num_matched > 2)
1425 /* cycle thru all the matches */
1426 snprintf (Completed, sizeof(Completed), "%s",
1427 Matches[(numtabs - 2) % Num_matched]);
1429 /* return the completed command */
1430 m_strcpy(buffer, len - spaces, Completed);
1432 else if (!m_strncmp(buffer, "set", 3)
1433 || !m_strncmp(buffer, "unset", 5)
1434 || !m_strncmp(buffer, "reset", 5)
1435 || !m_strncmp(buffer, "toggle", 6)) { /* complete variables */
1436 const char *prefixes[] = { "no", "inv", "?", "&", NULL };
1439 /* loop through all the possible prefixes (no, inv, ...) */
1440 if (!m_strncmp(buffer, "set", 3)) {
1441 for (num = 0; prefixes[num]; num++) {
1442 if (!m_strncmp(pt, prefixes[num], m_strlen(prefixes[num]))) {
1443 pt += m_strlen(prefixes[num]);
1449 /* first TAB. Collect all the matches */
1452 m_strcpy(User_typed, sizeof(User_typed), pt);
1453 p_clear(Matches, countof(Matches));
1454 p_clear(Completed, countof(Completed));
1455 for (num = 0; MuttVars[num].option; num++)
1456 candidate(Completed, User_typed, MuttVars[num].option,
1458 Matches[Num_matched++] = User_typed;
1460 /* All matches are stored. Longest non-ambiguous string is ""
1461 * i.e. dont change 'buffer'. Fake successful return this time */
1462 if (User_typed[0] == 0)
1466 if (Completed[0] == 0 && User_typed[0])
1469 /* Num_matched will _always_ be atleast 1 since the initial
1470 * user-typed string is always stored */
1471 if (numtabs == 1 && Num_matched == 2)
1472 snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
1473 else if (numtabs > 1 && Num_matched > 2)
1474 /* cycle thru all the matches */
1475 snprintf (Completed, sizeof(Completed), "%s",
1476 Matches[(numtabs - 2) % Num_matched]);
1478 m_strcpy(pt, buffer + len - pt - spaces, Completed);
1480 else if (!m_strncmp(buffer, "exec", 4)) {
1481 struct binding_t *menu = km_get_table (CurrentMenu);
1483 if (!menu && CurrentMenu != MENU_PAGER)
1487 /* first TAB. Collect all the matches */
1490 m_strcpy(User_typed, sizeof(User_typed), pt);
1491 p_clear(Matches, countof(Matches));
1492 p_clear(Completed, countof(Completed));
1493 for (num = 0; menu[num].name; num++)
1494 candidate (Completed, User_typed, menu[num].name, sizeof(Completed));
1495 /* try the generic menu */
1496 if (Completed[0] == 0 && CurrentMenu != MENU_PAGER) {
1498 for (num = 0; menu[num].name; num++)
1499 candidate (Completed, User_typed, menu[num].name,
1502 Matches[Num_matched++] = User_typed;
1504 /* All matches are stored. Longest non-ambiguous string is ""
1505 * i.e. dont change 'buffer'. Fake successful return this time */
1506 if (User_typed[0] == 0)
1510 if (Completed[0] == 0 && User_typed[0])
1513 /* Num_matched will _always_ be atleast 1 since the initial
1514 * user-typed string is always stored */
1515 if (numtabs == 1 && Num_matched == 2)
1516 snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
1517 else if (numtabs > 1 && Num_matched > 2)
1518 /* cycle thru all the matches */
1519 snprintf (Completed, sizeof(Completed), "%s",
1520 Matches[(numtabs - 2) % Num_matched]);
1522 m_strcpy(pt, buffer + len - pt - spaces, Completed);
1530 int mutt_var_value_complete (char *buffer, ssize_t len, int pos)
1532 char var[STRING], *pt = buffer;
1534 struct option_t* option = NULL;
1539 buffer = vskipspaces(buffer);
1540 spaces = buffer - pt;
1542 pt = buffer + pos - spaces;
1543 while ((pt > buffer) && !isspace ((unsigned char) *pt))
1545 pt++; /* move past the space */
1546 if (*pt == '=') /* abort if no var before the '=' */
1549 if (m_strncmp(buffer, "set", 3) == 0) {
1550 m_strcpy(var, sizeof(var), pt);
1551 /* ignore the trailing '=' when comparing */
1552 var[m_strlen(var) - 1] = 0;
1553 if (!(option = hash_find (ConfigOptions, var)))
1554 return 0; /* no such variable. */
1556 char tmp[LONG_STRING], tmp2[LONG_STRING];
1558 ssize_t dlen = buffer + len - pt - spaces;
1559 const char *vals[] = { "no", "yes", "ask-no", "ask-yes" };
1563 if ((DTYPE (option->type) == DT_STR) ||
1564 (DTYPE (option->type) == DT_PATH) ||
1565 (DTYPE (option->type) == DT_RX)) {
1566 m_strcpy(tmp, sizeof(tmp), NONULL(*((char **)option->data)));
1567 if (DTYPE (option->type) == DT_PATH)
1568 mutt_pretty_mailbox (tmp);
1570 else if (DTYPE (option->type) == DT_QUAD)
1571 m_strcpy(tmp, sizeof(tmp), vals[quadoption(option->data)]);
1572 else if (DTYPE (option->type) == DT_NUM)
1573 snprintf (tmp, sizeof(tmp), "%d", (*((short *) option->data)));
1574 else if (DTYPE (option->type) == DT_SORT) {
1575 const struct mapping_t *map;
1578 switch (option->type & DT_SUBTYPE_MASK) {
1580 map = SortAliasMethods;
1582 case DT_SORT_BROWSER:
1583 map = SortBrowserMethods;
1586 map = SortKeyMethods;
1592 p = mutt_getnamebyvalue(*((short *) option->data) & SORT_MASK, map);
1593 snprintf(tmp, sizeof(tmp), "%s%s%s",
1594 (*((short *)option->data) & SORT_REVERSE) ? "reverse-" : "",
1595 (*((short *)option->data) & SORT_LAST) ? "last-" : "", p);
1597 else if (DTYPE (option->type) == DT_MAGIC) {
1599 switch (DefaultMagic) {
1612 m_strcpy(tmp, sizeof(tmp), p);
1614 else if (DTYPE (option->type) == DT_BOOL)
1615 m_strcpy(tmp, sizeof(tmp), option(option->data) ? "yes" : "no");
1619 for (s = tmp, d = tmp2; *s && (d - tmp2) < ssizeof(tmp2) - 2;) {
1620 if (*s == '\\' || *s == '"')
1626 m_strcpy(tmp, sizeof(tmp), pt);
1627 snprintf (pt, dlen, "%s\"%s\"", tmp, tmp2);
1635 /* Implement the -Q command line flag */
1636 int mutt_query_variables (string_list_t * queries)
1640 char errbuff[STRING];
1641 char command[STRING];
1649 err.dsize = sizeof(errbuff);
1651 for (p = queries; p; p = p->next) {
1652 snprintf (command, sizeof(command), "set ?%s\n", p->data);
1653 if (mutt_parse_rc_line (command, &token, &err) == -1) {
1654 fprintf (stderr, "%s\n", err.data);
1655 p_delete(&token.data);
1658 printf ("%s\n", err.data);
1661 p_delete(&token.data);
1665 static int mutt_execute_commands (string_list_t * p)
1668 char errstr[STRING];
1672 err.dsize = sizeof(errstr);
1674 for (; p; p = p->next) {
1675 if (mutt_parse_rc_line (p->data, &token, &err) != 0) {
1676 fprintf (stderr, _("Error in command line: %s\n"), err.data);
1677 p_delete(&token.data);
1681 p_delete(&token.data);
1685 void mutt_init (int skip_sys_rc, string_list_t * commands)
1689 char buffer[STRING], error[STRING];
1690 int default_rc = 0, need_pause = 0;
1696 err.dsize = sizeof(error);
1698 ConfigOptions = hash_new (sizeof(MuttVars) * 2, 0);
1699 for (i = 0; MuttVars[i].option; i++) {
1700 hash_insert (ConfigOptions, MuttVars[i].option, &MuttVars[i]);
1704 * XXX - use something even more difficult to predict?
1706 snprintf (AttachmentMarker, sizeof(AttachmentMarker),
1707 "\033]9;%ld\a", (long) time (NULL));
1710 /* Get some information about the user */
1711 if ((pw = getpwuid (getuid ()))) {
1713 mutt_gecos_name(rnbuf, sizeof(rnbuf), pw, mod_core.gecos_mask);
1714 Realname = m_strdup(rnbuf);
1722 if ((f = safe_fopen (SYSCONFDIR "/nntpserver", "r"))) {
1724 fgets (buffer, sizeof(buffer), f);
1725 p = vskipspaces(buffer);
1727 while (*q && !isspace(*q))
1730 NewsServer = m_strdup(p);
1734 if ((p = getenv ("NNTPSERVER")))
1735 NewsServer = m_strdup(p);
1738 if ((p = getenv("MAIL") ?: getenv("MAILDIR"))) {
1739 Spoolfile = m_strdup(p);
1741 mutt_concat_path(buffer, sizeof(buffer), NONULL(mod_core.homedir), MAILPATH);
1742 Spoolfile = m_strdup(buffer);
1745 if ((p = getenv ("REPLYTO")) != NULL) {
1748 snprintf (buffer, sizeof(buffer), "Reply-To: %s", p);
1751 buf.data = buf.dptr = buffer;
1752 buf.dsize = m_strlen(buffer);
1755 parse_my_hdr (&token, &buf, 0, &err);
1756 p_delete(&token.data);
1759 /* Set standard defaults */
1760 hash_map (ConfigOptions, mutt_set_default, 0);
1761 hash_map (ConfigOptions, mutt_restore_default, 0);
1763 CurrentMenu = MENU_MAIN;
1766 /* Unset suspend by default if we're the session leader */
1767 if (getsid (0) == getpid ())
1768 unset_option (OPTSUSPEND);
1771 mutt_init_history ();
1774 snprintf (buffer, sizeof(buffer), "%s/.madmuttrc", NONULL(mod_core.homedir));
1775 if (access (buffer, F_OK) == -1)
1776 snprintf (buffer, sizeof(buffer), "%s/.madmutt/madmuttrc",
1777 NONULL(mod_core.homedir));
1780 Muttrc = m_strdup(buffer);
1783 m_strcpy(buffer, sizeof(buffer), Muttrc);
1785 mutt_expand_path (buffer, sizeof(buffer));
1786 Muttrc = m_strdup(buffer);
1789 /* Process the global rc file if it exists and the user hasn't explicity
1790 requested not to via "-n". */
1792 snprintf (buffer, sizeof(buffer), "%s/Madmuttrc-%s", SYSCONFDIR,
1794 if (access (buffer, F_OK) == -1)
1795 snprintf (buffer, sizeof(buffer), "%s/Madmuttrc", SYSCONFDIR);
1796 if (access (buffer, F_OK) == -1)
1797 snprintf (buffer, sizeof(buffer), "%s/Madmuttrc-%s", PKGDATADIR,
1799 if (access (buffer, F_OK) == -1)
1800 snprintf (buffer, sizeof(buffer), "%s/Madmuttrc", PKGDATADIR);
1801 if (access (buffer, F_OK) != -1) {
1802 if (source_rc (buffer, &err) != 0) {
1803 fputs (err.data, stderr);
1804 fputc ('\n', stderr);
1810 /* Read the user's initialization file. */
1811 if (access (Muttrc, F_OK) != -1) {
1812 if (!option (OPTNOCURSES))
1814 if (source_rc (Muttrc, &err) != 0) {
1815 fputs (err.data, stderr);
1816 fputc ('\n', stderr);
1820 else if (!default_rc) {
1821 /* file specified by -F does not exist */
1822 snprintf (buffer, sizeof(buffer), "%s: %s", Muttrc, strerror (errno));
1823 mutt_endwin (buffer);
1828 snprintf(buffer, sizeof(buffer), "%s/.madmutt.lua", NONULL(mod_core.homedir));
1829 if (access(buffer, F_OK) < 0)
1830 snprintf(buffer, sizeof(buffer), "%s/.madmutt/cfg.lua", NONULL(mod_core.homedir));
1831 if (!access(buffer, F_OK)) {
1832 need_pause = luaM_wrap(mutt_error, luaM_dofile(buffer));
1836 if (mutt_execute_commands (commands) != 0)
1839 /* warn about synonym variables */
1843 fprintf (stderr, _("Warning: the following synonym variables were found:\n"));
1845 for (syn = Synonyms; syn; syn = syn->next) {
1846 fprintf(stderr, "$%s ($%s should be used) (%s:%d)\n",
1847 syn->o ? NONULL(syn->o->option) : "",
1848 syn->n ? NONULL(syn->n->option) : "",
1849 NONULL(syn->f), syn->l);
1851 fprintf (stderr, _("Warning: synonym variables are scheduled"
1852 " for removal.\n"));
1853 syn_list_wipe(&Synonyms);
1857 if (need_pause && !option (OPTNOCURSES)) {
1858 if (mutt_any_key_to_continue (NULL) == -1)
1863 int mutt_get_hook_type (const char *name)
1865 struct command_t *c;
1867 for (c = Commands; c->name; c++)
1868 if (c->func == mutt_parse_hook && ascii_strcasecmp (c->name, name) == 0)
1873 /* dump out the value of all the variables we have */
1874 int mutt_dump_variables (int full) {
1877 /* get all non-synonyms into list... */
1878 for (i = 0; MuttVars[i].option; i++) {
1879 struct option_t *option = MuttVars + i;
1880 char buf[LONG_STRING];
1883 mutt_option_value(option->option, buf, sizeof(buf));
1884 if (!m_strcmp(buf, option->init))
1889 FuncTable[DTYPE(option->type)].opt_tostr(buf, sizeof(buf), option);
1890 printf ("%s\n", buf);
1893 printf ("\n# vi""m:set ft=muttrc:\n");