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"
27 #include "send_smtp.h"
34 static const struct mapping_t* get_sortmap (struct option_t* option);
35 static int parse_sort (struct option_t* dst, const char *s,
36 const struct mapping_t *map,
37 char* errbuf, ssize_t errlen);
39 static hash_t *ConfigOptions = NULL;
41 /* for synonym warning reports: synonym found during parsing */
42 typedef struct syn_t {
46 struct option_t* n; /* new */
47 struct option_t* o; /* old */
51 static void syn_wipe(syn_t *syn) {
55 DO_DELETE(syn_t, syn);
56 DO_SLIST(syn_t, syn, syn_delete);
58 /* for synonym warning reports: list of synonyms found */
59 static syn_t *Synonyms = NULL;
60 /* for synonym warning reports: current rc file */
61 static const char* CurRCFile = NULL;
62 /* for synonym warning reports: current rc line */
63 static int CurRCLine = 0;
65 /* prototypes for checking for special vars */
66 static int check_history (const char* option, unsigned long val,
67 char* errbuf, ssize_t errlen);
68 /* this checks that numbers are >= 0 */
69 static int check_num (const char* option, unsigned long val,
70 char* errbuf, ssize_t errlen);
72 /* use this to check only */
73 static int check_special (const char* option, unsigned long val,
74 char* errbuf, ssize_t errlen);
76 /* variable <-> sanity check function mappings
77 * when changing these, make sure the proper _from_string handler
82 int (*check) (const char* option, unsigned long val,
83 char* errbuf, ssize_t errlen);
86 { "smtp_use_tls", send_smtp_check_usetls },
88 { "history", check_history },
89 { "pager_index_lines", check_num },
94 static void bool_to_string (char* dst, ssize_t dstlen,
95 struct option_t* option) {
96 snprintf (dst, dstlen, "%s=%s", option->option,
97 option (option->data) ? "yes" : "no");
100 static int bool_from_string (struct option_t* dst, const char* val,
101 char* errbuf __attribute__ ((unused)),
102 ssize_t errlen __attribute__ ((unused))) {
107 if (ascii_strncasecmp (val, "yes", 3) == 0)
109 else if (ascii_strncasecmp (val, "no", 2) == 0)
115 set_option (dst->data);
117 unset_option (dst->data);
121 static void num_to_string (char* dst, ssize_t dstlen,
122 struct option_t* option) {
124 const char* fmt = (m_strcmp(option->option, "umask") == 0) ?
126 snprintf (dst, dstlen, fmt, option->option,
127 *((short*) option->data));
130 static int num_from_string (struct option_t* dst, const char* val,
131 char* errbuf, ssize_t errlen) {
132 int num = 0, old = 0;
138 num = strtol (val, &t, 0);
140 if (m_strisempty(val) || *t || (short) num != num) {
142 snprintf (errbuf, errlen, _("'%s' is invalid for $%s"),
148 /* just temporarily accept new val so that check_special for
149 * $history already has it when doing history's init() */
150 old = *((short*) dst->data);
151 *((short*) dst->data) = (short) num;
153 if (!check_special (dst->option, (unsigned long) num, errbuf, errlen)) {
154 *((short*) dst->data) = old;
161 static void str_to_string (char* dst, ssize_t dstlen,
162 struct option_t* option) {
163 snprintf (dst, dstlen, "%s=\"%s\"", option->option,
164 NONULL (*((char**) option->data)));
167 static int path_from_string (struct option_t* dst, const char* val,
168 char* errbuf __attribute__ ((unused)), ssize_t errlen __attribute__ ((unused))) {
169 char path[_POSIX_PATH_MAX];
174 if (m_strisempty(val)) {
175 p_delete((char**) dst->data);
180 m_strcpy(path, sizeof(path), val);
181 mutt_expand_path (path, sizeof(path));
182 m_strreplace((char **) dst->data, path);
186 static int str_from_string (struct option_t* dst, const char* val,
187 char* errbuf, ssize_t errlen) {
191 if (!check_special (dst->option, (unsigned long) val, errbuf, errlen))
194 m_strreplace((char**) dst->data, val);
198 static void quad_to_string (char* dst, ssize_t dstlen,
199 struct option_t* option) {
200 const char *vals[] = { "no", "yes", "ask-no", "ask-yes" };
201 snprintf (dst, dstlen, "%s=%s", option->option,
202 vals[quadoption (option->data)]);
205 static int quad_from_string (struct option_t* dst, const char* val,
206 char* errbuf __attribute__ ((unused)), ssize_t errlen __attribute__ ((unused))) {
211 if (ascii_strncasecmp (val, "yes", 3) == 0)
213 else if (ascii_strncasecmp (val, "no", 2) == 0)
215 else if (ascii_strncasecmp (val, "ask-yes", 7) == 0)
217 else if (ascii_strncasecmp (val, "ask-no", 6) == 0)
223 set_quadoption (dst->data, flag);
227 static void sort_to_string (char* dst, ssize_t dstlen,
228 struct option_t* option) {
229 const struct mapping_t *map = get_sortmap (option);
230 const char *p = NULL;
233 snprintf (dst, sizeof(dst), "%s=unknown", option->option);
237 p = mutt_getnamebyvalue(*((short *)option->data) & SORT_MASK, map);
239 snprintf (dst, dstlen, "%s=%s%s%s", option->option,
240 (*((short *) option->data) & SORT_REVERSE) ?
242 (*((short *) option->data) & SORT_LAST) ? "last-" :
246 static int sort_from_string (struct option_t* dst, const char* val,
247 char* errbuf, ssize_t errlen) {
248 const struct mapping_t *map = NULL;
249 if (!(map = get_sortmap (dst))) {
251 snprintf (errbuf, errlen, _("%s: Unknown type."),
255 if (parse_sort (dst, val, map, errbuf, errlen) == -1)
260 static void rx_to_string (char* dst, ssize_t dstlen,
261 struct option_t* option) {
262 rx_t* p = (rx_t*) option->data;
263 snprintf (dst, dstlen, "%s=\"%s\"", option->option,
264 NONULL (p->pattern));
267 static int rx_from_string (struct option_t* dst, const char* val,
268 char* errbuf, ssize_t errlen) {
271 int flags = 0, e = 0, neg = 0;
277 if (option (OPTATTACHMSG) && !m_strcmp(dst->option, "reply_regexp")) {
279 snprintf (errbuf, errlen,
280 "Operation not permitted when in attach-message mode.");
284 if (!((rx_t*) dst->data))
285 *((rx_t**) dst->data) = p_new(rx_t, 1);
287 p = (rx_t*) dst->data;
289 /* something to do? */
290 if (m_strisempty(val) || (p->pattern && m_strcmp(p->pattern, val) == 0))
293 if (m_strcmp(dst->option, "mask") != 0)
294 flags |= mutt_which_case (val);
297 if (m_strcmp(dst->option, "mask") == 0 && *s == '!') {
302 rx = p_new(regex_t, 1);
304 if ((e = REGCOMP (rx, s, flags)) != 0) {
305 regerror (e, rx, errbuf, errlen);
316 m_strreplace(&p->pattern, val);
320 if (m_strcmp(dst->option, "reply_regexp") == 0)
321 mutt_adjust_all_subjects ();
326 static void magic_to_string (char* dst, ssize_t dstlen,
327 struct option_t* option) {
328 const char* s = NULL;
329 switch (option->data) {
330 case M_MBOX: s = "mbox"; break;
331 case M_MMDF: s = "MMDF"; break;
332 case M_MH: s = "MH"; break;
333 case M_MAILDIR: s = "Maildir"; break;
334 default: s = "unknown"; break;
336 snprintf (dst, dstlen, "%s=%s", option->option, s);
339 static int magic_from_string (struct option_t* dst, const char* val,
340 char* errbuf __attribute__ ((unused)), ssize_t errlen __attribute__ ((unused))) {
343 if (!dst || m_strisempty(val))
345 if (ascii_strncasecmp (val, "mbox", 4) == 0)
347 else if (ascii_strncasecmp (val, "mmdf", 4) == 0)
349 else if (ascii_strncasecmp (val, "mh", 2) == 0)
351 else if (ascii_strncasecmp (val, "maildir", 7) == 0)
357 *((short*) dst->data) = flag;
364 void (*opt_tostr) (char* dst, ssize_t dstlen, struct option_t* option);
365 int (*opt_fromstr) (struct option_t* dst, const char* val,
366 char* errbuf, ssize_t errlen);
368 { 0, NULL, NULL }, /* there's no DT_ type with 0 */
369 { DT_BOOL, bool_to_string, bool_from_string },
370 { DT_NUM, num_to_string, num_from_string },
371 { DT_STR, str_to_string, str_from_string },
372 { DT_PATH, str_to_string, path_from_string },
373 { DT_QUAD, quad_to_string, quad_from_string },
374 { DT_SORT, sort_to_string, sort_from_string },
375 { DT_RX, rx_to_string, rx_from_string },
376 { DT_MAGIC, magic_to_string, magic_from_string },
380 int mutt_option_value (const char* val, char* dst, ssize_t dstlen) {
381 struct option_t* option = NULL;
382 char* tmp = NULL, *t = NULL;
385 if (!(option = hash_find (ConfigOptions, val))) {
389 tmp = p_new(char, dstlen+1);
390 FuncTable[DTYPE(option->type)].opt_tostr (tmp, dstlen, option);
392 /* as we get things of type $var=value and don't want to bloat the
393 * above "just" for expansion, we do the stripping here */
394 t = strchr (tmp, '=');
398 if (t[l-1] == '"' && *t == '"') {
403 memcpy (dst, t, l+1);
409 static void toggle_quadoption (int opt)
412 int b = (opt % 4) * 2;
414 QuadOptions[n] ^= (1 << b);
417 void set_quadoption (int opt, int flag)
420 int b = (opt % 4) * 2;
422 QuadOptions[n] &= ~(0x3 << b);
423 QuadOptions[n] |= (flag & 0x3) << b;
426 int quadoption (int opt)
429 int b = (opt % 4) * 2;
431 return (QuadOptions[n] >> b) & 0x3;
434 int query_quadoption2(int v, const char *prompt)
442 v = mutt_yesorno(prompt, (v == M_ASKYES));
443 CLEARLINE (LINES - 1);
448 int query_quadoption (int opt, const char *prompt)
450 int v = quadoption (opt);
458 v = mutt_yesorno (prompt, (v == M_ASKYES));
459 CLEARLINE (LINES - 1);
466 /* always wise to do what someone else did before */
467 static void _attachments_clean (void) {
469 if (Context && Context->msgcount) {
470 for (i = 0; i < Context->msgcount; i++)
471 Context->hdrs[i]->attach_valid = 0;
475 static int parse_attach_list (BUFFER *buf, BUFFER *s, string_list_t **ldata,
476 BUFFER *err __attribute__ ((unused))) {
478 string_list_t *listp, *lastp;
483 /* Find the last item in the list that data points to. */
485 for (listp = *ldata; listp; listp = listp->next) {
486 a = (ATTACH_MATCH *)listp->data;
491 mutt_extract_token (buf, s, 0);
493 if (!buf->data || *buf->data == '\0')
496 a = p_new(ATTACH_MATCH, 1);
498 /* some cheap hacks that I expect to remove */
499 if (!m_strcasecmp(buf->data, "any"))
500 a->major = m_strdup("*/.*");
501 else if (!m_strcasecmp(buf->data, "none"))
502 a->major = m_strdup("cheap_hack/this_should_never_match");
504 a->major = m_strdup(buf->data);
506 if ((p = strchr(a->major, '/'))) {
511 a->minor = "unknown";
514 len = m_strlen(a->minor);
515 tmpminor = p_new(char, len + 3);
516 m_strcpy(&tmpminor[1], len + 3, a->minor);
518 tmpminor[len+1] = '$';
519 tmpminor[len+2] = '\0';
521 a->major_int = mutt_check_mime_type(a->major);
522 regcomp(&a->minor_rx, tmpminor, REG_ICASE|REG_EXTENDED);
526 listp = p_new(string_list_t, 1);
527 listp->data = (char *)a;
536 while (MoreArgs (s));
538 _attachments_clean();
542 static int parse_unattach_list (BUFFER *buf, BUFFER *s, string_list_t **ldata,
543 BUFFER *err __attribute__ ((unused))) {
545 string_list_t *lp, *lastp, *newlp;
551 mutt_extract_token (buf, s, 0);
553 if (!m_strcasecmp(buf->data, "any"))
554 tmp = m_strdup("*/.*");
555 else if (!m_strcasecmp(buf->data, "none"))
556 tmp = m_strdup("cheap_hack/this_should_never_match");
558 tmp = m_strdup(buf->data);
560 if ((minor = strchr(tmp, '/'))) {
564 minor = m_strdup("unknown");
566 major = mutt_check_mime_type(tmp);
568 /* We must do our own walk here because string_list_remove() will only
569 * remove the string_list_t->data, not anything pointed to by the string_list_t->data. */
571 for(lp = *ldata; lp; ) {
572 a = (ATTACH_MATCH *)lp->data;
573 if (a->major_int == major && !m_strcasecmp(minor, a->minor)) {
574 regfree(&a->minor_rx);
577 /* Relink backward */
579 lastp->next = lp->next;
584 p_delete(&lp->data); /* same as a */
594 while (MoreArgs (s));
597 _attachments_clean();
601 static int print_attach_list (string_list_t *lp, char op, const char *name) {
603 printf("attachments %c%s %s/%s\n", op, name,
604 ((ATTACH_MATCH *)lp->data)->major,
605 ((ATTACH_MATCH *)lp->data)->minor);
612 static int parse_attachments (BUFFER *buf, BUFFER *s,
613 unsigned long data __attribute__ ((unused)),
616 string_list_t **listp;
618 mutt_extract_token(buf, s, 0);
619 if (!buf->data || *buf->data == '\0') {
620 m_strcpy(err->data, err->dsize, _("attachments: no disposition"));
624 category = buf->data;
630 printf("\nCurrent attachments settings:\n\n");
631 print_attach_list(AttachAllow, '+', "A");
632 print_attach_list(AttachExclude, '-', "A");
633 print_attach_list(InlineAllow, '+', "I");
634 print_attach_list(InlineExclude, '-', "I");
635 set_option (OPTFORCEREDRAWINDEX);
636 set_option (OPTFORCEREDRAWPAGER);
637 mutt_any_key_to_continue (NULL);
641 if (op != '+' && op != '-') {
645 if (!m_strncasecmp(category, "attachment", strlen(category))) {
647 listp = &AttachAllow;
649 listp = &AttachExclude;
651 else if (!m_strncasecmp(category, "inline", strlen(category))) {
653 listp = &InlineAllow;
655 listp = &InlineExclude;
657 m_strcpy(err->data, err->dsize, _("attachments: invalid disposition"));
661 return parse_attach_list(buf, s, listp, err);
664 static int parse_unattachments (BUFFER *buf, BUFFER *s, unsigned long data __attribute__ ((unused)), BUFFER *err) {
666 string_list_t **listp;
668 mutt_extract_token(buf, s, 0);
669 if (!buf->data || *buf->data == '\0') {
670 m_strcpy(err->data, err->dsize, _("unattachments: no disposition"));
676 if (op != '+' && op != '-') {
680 if (!m_strncasecmp(p, "attachment", strlen(p))) {
682 listp = &AttachAllow;
684 listp = &AttachExclude;
686 else if (!m_strncasecmp(p, "inline", strlen(p))) {
688 listp = &InlineAllow;
690 listp = &InlineExclude;
693 m_strcpy(err->data, err->dsize, _("unattachments: invalid disposition"));
697 return parse_unattach_list(buf, s, listp, err);
700 static int parse_unalias (BUFFER * buf, BUFFER * s,
701 unsigned long data __attribute__ ((unused)),
702 BUFFER * err __attribute__ ((unused)))
704 alias_t *tmp, **last;
707 mutt_extract_token (buf, s, 0);
709 if (!m_strcmp("*", buf->data) == 0) {
710 if (CurrentMenu == MENU_ALIAS) {
711 for (tmp = Aliases; tmp; tmp = tmp->next)
713 set_option(OPTFORCEREDRAWINDEX);
715 alias_list_wipe(&Aliases);
721 for (last = &Aliases; *last; last = &(*last)->next) {
722 if (!m_strcasecmp(buf->data, (*last)->name)) {
723 if (CurrentMenu == MENU_ALIAS) {
725 set_option (OPTFORCEREDRAWINDEX);
727 tmp = alias_list_pop(last);
733 } while (MoreArgs(s));
738 static int parse_alias (BUFFER * buf, BUFFER * s,
739 unsigned long data __attribute__ ((unused)),
746 m_strcpy(err->data, err->dsize, _("alias: no address"));
750 mutt_extract_token (buf, s, 0);
752 /* check to see if an alias with this name already exists */
753 for (last = &Aliases; *last; last = &(*last)->next) {
754 if (!m_strcasecmp((*last)->name, buf->data))
759 /* create a new alias */
761 (*last)->name = m_strdup(buf->data);
762 /* give the main addressbook code a chance */
763 if (CurrentMenu == MENU_ALIAS)
764 set_option (OPTMENUCALLER);
766 /* override the previous value */
767 address_list_wipe(&(*last)->addr);
768 if (CurrentMenu == MENU_ALIAS)
769 set_option (OPTFORCEREDRAWINDEX);
772 mutt_extract_token(buf, s, M_TOKEN_QUOTE | M_TOKEN_SPACE);
773 (*last)->addr = mutt_parse_adrlist((*last)->addr, buf->data);
774 if (mutt_addrlist_to_idna((*last)->addr, &estr)) {
775 snprintf (err->data, err->dsize,
776 _("Warning: Bad IDN '%s' in alias '%s'.\n"), estr, (*last)->name);
785 parse_unmy_hdr(BUFFER * buf, BUFFER * s,
786 unsigned long data __attribute__ ((unused)),
787 BUFFER * err __attribute__ ((unused)))
790 mutt_extract_token (buf, s, 0);
792 if (!m_strcmp("*", buf->data)) {
793 string_list_wipe(&UserHeader);
795 string_list_t **last = &UserHeader;
796 ssize_t l = m_strlen(buf->data);
798 if (buf->data[l - 1] == ':')
802 if (!ascii_strncasecmp(buf->data, (*last)->data, l)
803 && (*last)->data[l] == ':')
805 string_list_t *tmp = string_list_pop(last);
806 string_item_delete(&tmp);
808 last = &(*last)->next;
812 } while (MoreArgs(s));
817 static int parse_my_hdr (BUFFER * buf, BUFFER * s, unsigned long data __attribute__ ((unused)),
824 mutt_extract_token (buf, s, M_TOKEN_SPACE | M_TOKEN_QUOTE);
825 if ((p = strpbrk (buf->data, ": \t")) == NULL || *p != ':') {
826 m_strcpy(err->data, err->dsize, _("invalid header field"));
829 keylen = p - buf->data + 1;
832 for (tmp = UserHeader;; tmp = tmp->next) {
833 /* see if there is already a field by this name */
834 if (ascii_strncasecmp (buf->data, tmp->data, keylen) == 0) {
835 /* replace the old value */
836 p_delete(&tmp->data);
837 tmp->data = buf->data;
844 tmp->next = string_item_new();
848 tmp = string_item_new();
851 tmp->data = buf->data;
857 parse_sort (struct option_t* dst, const char *s, const struct mapping_t *map,
858 char* errbuf, ssize_t errlen) {
861 if (m_strncmp("reverse-", s, 8) == 0) {
863 flags = SORT_REVERSE;
866 if (m_strncmp("last-", s, 5) == 0) {
871 if ((i = mutt_getvaluebyname (s, map)) == -1) {
873 snprintf (errbuf, errlen, _("'%s' is invalid for $%s"), s, dst->option);
877 *((short*) dst->data) = i | flags;
881 /* if additional data more == 1, we want to resolve synonyms */
882 static void mutt_set_default(const char *name __attribute__ ((unused)), void* p, unsigned long more)
884 char buf[LONG_STRING];
885 struct option_t *ptr = p;
887 if (!ptr || *ptr->init || !FuncTable[DTYPE (ptr->type)].opt_fromstr)
890 mutt_option_value(ptr->option, buf, sizeof(buf));
891 if (m_strlen(ptr->init) == 0 && buf && *buf)
892 ptr->init = m_strdup(buf);
895 static int init_expand (char** dst, struct option_t* src) {
901 if (DTYPE(src->type) == DT_STR || DTYPE(src->type) == DT_PATH) {
902 /* only expand for string as it's the only place where
903 * we want to expand vars right now */
904 if (src->init && *src->init) {
907 len = m_strlen(src->init) + 2;
908 in.data = p_new(char, len + 1);
909 snprintf (in.data, len, "\"%s\"", src->init);
912 mutt_extract_token (&token, &in, 0);
913 if (token.data && *token.data)
914 *dst = m_strdup(token.data);
918 p_delete(&token.data);
922 /* for non-string: take value as is */
923 *dst = m_strdup(src->init);
927 /* if additional data more == 1, we want to resolve synonyms */
928 static void mutt_restore_default (const char* name __attribute__ ((unused)),
929 void* p, unsigned long more) {
931 struct option_t* ptr = (struct option_t*) p;
936 if (FuncTable[DTYPE (ptr->type)].opt_fromstr) {
937 init_expand (&init, ptr);
938 if (!FuncTable[DTYPE (ptr->type)].opt_fromstr (ptr, init, errbuf,
940 if (!option (OPTNOCURSES))
942 fprintf (stderr, _("Invalid default setting for $%s found: \"%s\".\n"
943 "Please report this error: \"%s\"\n"),
944 ptr->option, NONULL (init), errbuf);
950 if (ptr->flags & R_INDEX)
951 set_option (OPTFORCEREDRAWINDEX);
952 if (ptr->flags & R_PAGER)
953 set_option (OPTFORCEREDRAWPAGER);
954 if (ptr->flags & R_RESORT_SUB)
955 set_option (OPTSORTSUBTHREADS);
956 if (ptr->flags & R_RESORT)
957 set_option (OPTNEEDRESORT);
958 if (ptr->flags & R_RESORT_INIT)
959 set_option (OPTRESORTINIT);
960 if (ptr->flags & R_TREE)
961 set_option (OPTREDRAWTREE);
964 static int check_num (const char* option, unsigned long p,
965 char* errbuf, ssize_t errlen) {
968 snprintf (errbuf, errlen, _("'%d' is invalid for $%s"), (int) p, option);
974 static int check_history (const char* option __attribute__ ((unused)), unsigned long p,
975 char* errbuf, ssize_t errlen) {
976 if (!check_num ("history", p, errbuf, errlen))
978 mutt_init_history ();
982 static int check_special (const char* name, unsigned long val,
983 char* errbuf, ssize_t errlen) {
986 for (i = 0; SpecialVars[i].name; i++) {
987 if (m_strcmp(SpecialVars[i].name, name) == 0) {
988 return (SpecialVars[i].check (SpecialVars[i].name,
989 val, errbuf, errlen));
995 static const struct mapping_t* get_sortmap (struct option_t* option) {
996 const struct mapping_t* map = NULL;
998 switch (option->type & DT_SUBTYPE_MASK) {
1000 map = SortAliasMethods;
1002 case DT_SORT_BROWSER:
1003 map = SortBrowserMethods;
1006 map = SortKeyMethods;
1009 map = SortAuxMethods;
1018 #define CHECK_PAGER \
1019 if ((CurrentMenu == MENU_PAGER) && \
1020 (!option || (option->flags & R_RESORT))) \
1022 snprintf (err->data, err->dsize, \
1023 _("Not available in this menu.")); \
1027 static int parse_set (BUFFER * tmp, BUFFER * s, unsigned long data,
1030 int query, unset, inv, reset, r = 0;
1031 struct option_t* option = NULL;
1033 while (MoreArgs (s)) {
1034 /* reset state variables */
1036 unset = data & M_SET_UNSET;
1037 inv = data & M_SET_INV;
1038 reset = data & M_SET_RESET;
1040 if (*s->dptr == '?') {
1044 else if (m_strncmp("no", s->dptr, 2) == 0) {
1048 else if (m_strncmp("inv", s->dptr, 3) == 0) {
1052 else if (*s->dptr == '&') {
1057 /* get the variable name */
1058 mutt_extract_token (tmp, s, M_TOKEN_EQUAL);
1059 option = hash_find(ConfigOptions, tmp->data);
1060 if (!option && !(reset && m_strcmp("all", tmp->data) == 0)) {
1061 snprintf (err->data, err->dsize, _("%s: unknown variable"), tmp->data);
1064 s->dptr = vskipspaces(s->dptr);
1067 if (query || unset || inv) {
1068 snprintf (err->data, err->dsize, _("prefix is illegal with reset"));
1072 if (s && *s->dptr == '=') {
1073 snprintf (err->data, err->dsize, _("value is illegal with reset"));
1077 if (!m_strcmp("all", tmp->data)) {
1078 if (CurrentMenu == MENU_PAGER) {
1079 snprintf (err->data, err->dsize, _("Not available in this menu."));
1082 hash_map (ConfigOptions, mutt_restore_default, 1);
1083 set_option (OPTFORCEREDRAWINDEX);
1084 set_option (OPTFORCEREDRAWPAGER);
1085 set_option (OPTSORTSUBTHREADS);
1086 set_option (OPTNEEDRESORT);
1087 set_option (OPTRESORTINIT);
1088 set_option (OPTREDRAWTREE);
1092 mutt_restore_default (NULL, option, 1);
1095 else if (DTYPE (option->type) == DT_BOOL) {
1096 /* XXX this currently ignores the function table
1097 * as we don't get invert and stuff into it */
1098 if (s && *s->dptr == '=') {
1099 if (unset || inv || query) {
1100 snprintf (err->data, err->dsize, "Usage: set variable=yes|no");
1105 mutt_extract_token (tmp, s, 0);
1106 if (ascii_strcasecmp ("yes", tmp->data) == 0)
1108 else if (ascii_strcasecmp ("no", tmp->data) == 0)
1111 snprintf (err->data, err->dsize, "Usage: set variable=yes|no");
1117 bool_to_string (err->data, err->dsize, option);
1123 unset_option (option->data);
1125 toggle_option (option->data);
1127 set_option (option->data);
1129 else if (DTYPE (option->type) == DT_STR ||
1130 DTYPE (option->type) == DT_PATH ||
1131 DTYPE (option->type) == DT_MAGIC ||
1132 DTYPE (option->type) == DT_NUM ||
1133 DTYPE (option->type) == DT_SORT ||
1134 DTYPE (option->type) == DT_RX)
1136 /* XXX maybe we need to get unset into handlers? */
1137 if (DTYPE (option->type) == DT_STR || DTYPE (option->type) == DT_PATH) {
1140 p_delete((void **)(void *)&option->data);
1145 if (query || *s->dptr != '=') {
1146 FuncTable[DTYPE (option->type)].opt_tostr
1147 (err->data, err->dsize, option);
1153 mutt_extract_token (tmp, s, 0);
1154 if (!FuncTable[DTYPE (option->type)].opt_fromstr
1155 (option, tmp->data, err->data, err->dsize))
1158 else if (DTYPE (option->type) == DT_QUAD) {
1161 quad_to_string (err->data, err->dsize, option);
1165 if (*s->dptr == '=') {
1168 mutt_extract_token (tmp, s, 0);
1169 if (ascii_strcasecmp ("yes", tmp->data) == 0)
1170 set_quadoption (option->data, M_YES);
1171 else if (ascii_strcasecmp ("no", tmp->data) == 0)
1172 set_quadoption (option->data, M_NO);
1173 else if (ascii_strcasecmp ("ask-yes", tmp->data) == 0)
1174 set_quadoption (option->data, M_ASKYES);
1175 else if (ascii_strcasecmp ("ask-no", tmp->data) == 0)
1176 set_quadoption (option->data, M_ASKNO);
1178 snprintf (err->data, err->dsize, _("'%s' is invalid for $%s\n"),
1179 tmp->data, option->option);
1186 toggle_quadoption (option->data);
1188 set_quadoption (option->data, M_NO);
1190 set_quadoption (option->data, M_YES);
1194 snprintf (err->data, err->dsize, _("%s: unknown type"),
1200 if (option->flags & R_INDEX)
1201 set_option (OPTFORCEREDRAWINDEX);
1202 if (option->flags & R_PAGER)
1203 set_option (OPTFORCEREDRAWPAGER);
1204 if (option->flags & R_RESORT_SUB)
1205 set_option (OPTSORTSUBTHREADS);
1206 if (option->flags & R_RESORT)
1207 set_option (OPTNEEDRESORT);
1208 if (option->flags & R_RESORT_INIT)
1209 set_option (OPTRESORTINIT);
1210 if (option->flags & R_TREE)
1211 set_option (OPTREDRAWTREE);
1218 /* reads the specified initialization file. returns -1 if errors were found
1219 so that we can pause to let the user know... */
1220 static int source_rc (const char *rcfile, BUFFER * err)
1223 int line = 0, rc = 0, conv = 0;
1225 char *linebuf = NULL;
1226 char *currentline = NULL;
1230 if ((f = mutt_open_read (rcfile, &pid)) == NULL) {
1231 snprintf (err->data, err->dsize, "%s: %s", rcfile, strerror (errno));
1236 while ((linebuf = mutt_read_line(linebuf, &buflen, f, &line)) != NULL) {
1237 conv = ConfigCharset && (*ConfigCharset) && MCharset.charset;
1239 currentline = m_strdup(linebuf);
1242 mutt_convert_string (¤tline, ConfigCharset, MCharset.charset, 0);
1245 currentline = linebuf;
1250 if (mutt_parse_rc_line (currentline, &token, err) == -1) {
1251 mutt_error (_("Error in %s, line %d: %s"), rcfile, line, err->data);
1252 if (--rc < -MAXERRS) {
1254 p_delete(¤tline);
1263 p_delete(¤tline);
1265 p_delete(&token.data);
1269 mutt_wait_filter (pid);
1271 /* the muttrc source keyword */
1272 snprintf (err->data, err->dsize,
1273 rc >= -MAXERRS ? _("source: errors in %s")
1274 : _("source: reading aborted due too many errors in %s"),
1283 static int parse_source (BUFFER * tmp, BUFFER * s,
1284 unsigned long data __attribute__ ((unused)),
1287 char path[_POSIX_PATH_MAX];
1291 if (mutt_extract_token (tmp, s, 0) != 0) {
1292 snprintf (err->data, err->dsize, _("source: error at %s"), s->dptr);
1296 m_strcpy(path, sizeof(path), tmp->data);
1297 mutt_expand_path (path, sizeof(path));
1299 rc += source_rc (path, err);
1301 while (MoreArgs (s));
1303 return ((rc < 0) ? -1 : 0);
1306 /* line command to execute
1308 token scratch buffer to be used by parser. caller should free
1309 token->data when finished. the reason for this variable is
1310 to avoid having to allocate and deallocate a lot of memory
1311 if we are parsing many lines. the caller can pass in the
1312 memory to use, which avoids having to create new space for
1313 every call to this function.
1315 err where to write error messages */
1316 int mutt_parse_rc_line (const char *line, BUFFER * token, BUFFER * err)
1322 expn.data = expn.dptr = line;
1323 expn.dsize = m_strlen(line);
1327 expn.dptr = vskipspaces(expn.dptr);
1328 while (*expn.dptr) {
1329 if (*expn.dptr == '#')
1330 break; /* rest of line is a comment */
1331 if (*expn.dptr == ';') {
1335 mutt_extract_token (token, &expn, 0);
1336 for (i = 0; Commands[i].name; i++) {
1337 if (!m_strcmp(token->data, Commands[i].name)) {
1338 if (Commands[i].func (token, &expn, Commands[i].data, err) != 0)
1343 if (!Commands[i].name) {
1344 snprintf (err->data, err->dsize, _("%s: unknown command"),
1345 NONULL (token->data));
1352 p_delete(&expn.data);
1357 #define NUMVARS (sizeof(MuttVars)/sizeof(MuttVars[0]))
1358 #define NUMCOMMANDS (sizeof(Commands)/sizeof(Commands[0]))
1359 /* initial string that starts completion. No telling how much crap
1360 * the user has typed so far. Allocate LONG_STRING just to be sure! */
1361 char User_typed[LONG_STRING] = { 0 };
1363 int Num_matched = 0; /* Number of matches for completion */
1364 char Completed[STRING] = { 0 }; /* completed string (command or variable) */
1365 const char *Matches[MAX (NUMVARS, NUMCOMMANDS) + 1]; /* all the matches + User_typed */
1367 /* helper function for completion. Changes the dest buffer if
1368 necessary/possible to aid completion.
1369 dest == completion result gets here.
1370 src == candidate for completion.
1371 try == user entered data for completion.
1372 len == length of dest buffer.
1374 static void candidate (char *dest, char *try, const char *src, int len)
1378 if (strstr (src, try) == src) {
1379 Matches[Num_matched++] = src;
1381 m_strcpy(dest, len, src);
1383 for (l = 0; src[l] && src[l] == dest[l]; l++);
1389 int mutt_command_complete (char *buffer, ssize_t len, int pos, int numtabs)
1393 int spaces; /* keep track of the number of leading spaces on the line */
1395 buffer = vskipspaces(buffer);
1396 spaces = buffer - pt;
1398 pt = buffer + pos - spaces;
1399 while ((pt > buffer) && !isspace ((unsigned char) *pt))
1402 if (pt == buffer) { /* complete cmd */
1403 /* first TAB. Collect all the matches */
1406 m_strcpy(User_typed, sizeof(User_typed), pt);
1407 p_clear(Matches, countof(Matches));
1408 p_clear(Completed, countof(Completed));
1409 for (num = 0; Commands[num].name; num++)
1410 candidate (Completed, User_typed, Commands[num].name,
1412 Matches[Num_matched++] = User_typed;
1414 /* All matches are stored. Longest non-ambiguous string is ""
1415 * i.e. dont change 'buffer'. Fake successful return this time */
1416 if (User_typed[0] == 0)
1420 if (Completed[0] == 0 && User_typed[0])
1423 /* Num_matched will _always_ be atleast 1 since the initial
1424 * user-typed string is always stored */
1425 if (numtabs == 1 && Num_matched == 2)
1426 snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
1427 else if (numtabs > 1 && Num_matched > 2)
1428 /* cycle thru all the matches */
1429 snprintf (Completed, sizeof(Completed), "%s",
1430 Matches[(numtabs - 2) % Num_matched]);
1432 /* return the completed command */
1433 m_strcpy(buffer, len - spaces, Completed);
1435 else if (!m_strncmp(buffer, "set", 3)
1436 || !m_strncmp(buffer, "unset", 5)
1437 || !m_strncmp(buffer, "reset", 5)
1438 || !m_strncmp(buffer, "toggle", 6)) { /* complete variables */
1439 const char *prefixes[] = { "no", "inv", "?", "&", NULL };
1442 /* loop through all the possible prefixes (no, inv, ...) */
1443 if (!m_strncmp(buffer, "set", 3)) {
1444 for (num = 0; prefixes[num]; num++) {
1445 if (!m_strncmp(pt, prefixes[num], m_strlen(prefixes[num]))) {
1446 pt += m_strlen(prefixes[num]);
1452 /* first TAB. Collect all the matches */
1455 m_strcpy(User_typed, sizeof(User_typed), pt);
1456 p_clear(Matches, countof(Matches));
1457 p_clear(Completed, countof(Completed));
1458 for (num = 0; MuttVars[num].option; num++)
1459 candidate(Completed, User_typed, MuttVars[num].option,
1461 Matches[Num_matched++] = User_typed;
1463 /* All matches are stored. Longest non-ambiguous string is ""
1464 * i.e. dont change 'buffer'. Fake successful return this time */
1465 if (User_typed[0] == 0)
1469 if (Completed[0] == 0 && User_typed[0])
1472 /* Num_matched will _always_ be atleast 1 since the initial
1473 * user-typed string is always stored */
1474 if (numtabs == 1 && Num_matched == 2)
1475 snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
1476 else if (numtabs > 1 && Num_matched > 2)
1477 /* cycle thru all the matches */
1478 snprintf (Completed, sizeof(Completed), "%s",
1479 Matches[(numtabs - 2) % Num_matched]);
1481 m_strcpy(pt, buffer + len - pt - spaces, Completed);
1483 else if (!m_strncmp(buffer, "exec", 4)) {
1484 struct binding_t *menu = km_get_table (CurrentMenu);
1486 if (!menu && CurrentMenu != MENU_PAGER)
1490 /* first TAB. Collect all the matches */
1493 m_strcpy(User_typed, sizeof(User_typed), pt);
1494 p_clear(Matches, countof(Matches));
1495 p_clear(Completed, countof(Completed));
1496 for (num = 0; menu[num].name; num++)
1497 candidate (Completed, User_typed, menu[num].name, sizeof(Completed));
1498 /* try the generic menu */
1499 if (Completed[0] == 0 && CurrentMenu != MENU_PAGER) {
1501 for (num = 0; menu[num].name; num++)
1502 candidate (Completed, User_typed, menu[num].name,
1505 Matches[Num_matched++] = User_typed;
1507 /* All matches are stored. Longest non-ambiguous string is ""
1508 * i.e. dont change 'buffer'. Fake successful return this time */
1509 if (User_typed[0] == 0)
1513 if (Completed[0] == 0 && User_typed[0])
1516 /* Num_matched will _always_ be atleast 1 since the initial
1517 * user-typed string is always stored */
1518 if (numtabs == 1 && Num_matched == 2)
1519 snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
1520 else if (numtabs > 1 && Num_matched > 2)
1521 /* cycle thru all the matches */
1522 snprintf (Completed, sizeof(Completed), "%s",
1523 Matches[(numtabs - 2) % Num_matched]);
1525 m_strcpy(pt, buffer + len - pt - spaces, Completed);
1533 int mutt_var_value_complete (char *buffer, ssize_t len, int pos)
1535 char var[STRING], *pt = buffer;
1537 struct option_t* option = NULL;
1542 buffer = vskipspaces(buffer);
1543 spaces = buffer - pt;
1545 pt = buffer + pos - spaces;
1546 while ((pt > buffer) && !isspace ((unsigned char) *pt))
1548 pt++; /* move past the space */
1549 if (*pt == '=') /* abort if no var before the '=' */
1552 if (m_strncmp(buffer, "set", 3) == 0) {
1553 m_strcpy(var, sizeof(var), pt);
1554 /* ignore the trailing '=' when comparing */
1555 var[m_strlen(var) - 1] = 0;
1556 if (!(option = hash_find (ConfigOptions, var)))
1557 return 0; /* no such variable. */
1559 char tmp[LONG_STRING], tmp2[LONG_STRING];
1561 ssize_t dlen = buffer + len - pt - spaces;
1562 const char *vals[] = { "no", "yes", "ask-no", "ask-yes" };
1566 if ((DTYPE (option->type) == DT_STR) ||
1567 (DTYPE (option->type) == DT_PATH) ||
1568 (DTYPE (option->type) == DT_RX)) {
1569 m_strcpy(tmp, sizeof(tmp), NONULL(*((char **)option->data)));
1570 if (DTYPE (option->type) == DT_PATH)
1571 mutt_pretty_mailbox (tmp);
1573 else if (DTYPE (option->type) == DT_QUAD)
1574 m_strcpy(tmp, sizeof(tmp), vals[quadoption(option->data)]);
1575 else if (DTYPE (option->type) == DT_NUM)
1576 snprintf (tmp, sizeof(tmp), "%d", (*((short *) option->data)));
1577 else if (DTYPE (option->type) == DT_SORT) {
1578 const struct mapping_t *map;
1581 switch (option->type & DT_SUBTYPE_MASK) {
1583 map = SortAliasMethods;
1585 case DT_SORT_BROWSER:
1586 map = SortBrowserMethods;
1589 map = SortKeyMethods;
1595 p = mutt_getnamebyvalue(*((short *) option->data) & SORT_MASK, map);
1596 snprintf(tmp, sizeof(tmp), "%s%s%s",
1597 (*((short *)option->data) & SORT_REVERSE) ? "reverse-" : "",
1598 (*((short *)option->data) & SORT_LAST) ? "last-" : "", p);
1600 else if (DTYPE (option->type) == DT_MAGIC) {
1602 switch (DefaultMagic) {
1618 m_strcpy(tmp, sizeof(tmp), p);
1620 else if (DTYPE (option->type) == DT_BOOL)
1621 m_strcpy(tmp, sizeof(tmp), option(option->data) ? "yes" : "no");
1625 for (s = tmp, d = tmp2; *s && (d - tmp2) < ssizeof(tmp2) - 2;) {
1626 if (*s == '\\' || *s == '"')
1632 m_strcpy(tmp, sizeof(tmp), pt);
1633 snprintf (pt, dlen, "%s\"%s\"", tmp, tmp2);
1641 /* Implement the -Q command line flag */
1642 int mutt_query_variables (string_list_t * queries)
1646 char errbuff[STRING];
1647 char command[STRING];
1655 err.dsize = sizeof(errbuff);
1657 for (p = queries; p; p = p->next) {
1658 snprintf (command, sizeof(command), "set ?%s\n", p->data);
1659 if (mutt_parse_rc_line (command, &token, &err) == -1) {
1660 fprintf (stderr, "%s\n", err.data);
1661 p_delete(&token.data);
1664 printf ("%s\n", err.data);
1667 p_delete(&token.data);
1671 static int mutt_execute_commands (string_list_t * p)
1674 char errstr[STRING];
1678 err.dsize = sizeof(errstr);
1680 for (; p; p = p->next) {
1681 if (mutt_parse_rc_line (p->data, &token, &err) != 0) {
1682 fprintf (stderr, _("Error in command line: %s\n"), err.data);
1683 p_delete(&token.data);
1687 p_delete(&token.data);
1691 void mutt_init (int skip_sys_rc, string_list_t * commands)
1695 char buffer[STRING], error[STRING];
1696 int default_rc = 0, need_pause = 0;
1702 err.dsize = sizeof(error);
1704 ConfigOptions = hash_new (sizeof(MuttVars) * 2, 0);
1705 for (i = 0; MuttVars[i].option; i++) {
1706 hash_insert (ConfigOptions, MuttVars[i].option, &MuttVars[i]);
1710 * XXX - use something even more difficult to predict?
1712 snprintf (AttachmentMarker, sizeof(AttachmentMarker),
1713 "\033]9;%ld\a", (long) time (NULL));
1716 /* Get some information about the user */
1717 if ((pw = getpwuid (getuid ()))) {
1719 mutt_gecos_name(rnbuf, sizeof(rnbuf), pw, MCore.gecos_mask);
1720 Realname = m_strdup(rnbuf);
1728 if ((f = safe_fopen (SYSCONFDIR "/nntpserver", "r"))) {
1730 fgets (buffer, sizeof(buffer), f);
1731 p = vskipspaces(buffer);
1733 while (*q && !isspace(*q))
1736 NewsServer = m_strdup(p);
1740 if ((p = getenv ("NNTPSERVER")))
1741 NewsServer = m_strdup(p);
1744 if ((p = getenv("MAIL") ?: getenv("MAILDIR"))) {
1745 Spoolfile = m_strdup(p);
1748 mutt_concat_path(buffer, sizeof(buffer), NONULL(MCore.homedir), MAILPATH);
1750 mutt_concat_path(buffer, sizeof(buffer), MAILPATH, NONULL(MCore.username));
1752 Spoolfile = m_strdup(buffer);
1755 if ((p = getenv ("REPLYTO")) != NULL) {
1758 snprintf (buffer, sizeof(buffer), "Reply-To: %s", p);
1761 buf.data = buf.dptr = buffer;
1762 buf.dsize = m_strlen(buffer);
1765 parse_my_hdr (&token, &buf, 0, &err);
1766 p_delete(&token.data);
1769 /* Set standard defaults */
1770 hash_map (ConfigOptions, mutt_set_default, 0);
1771 hash_map (ConfigOptions, mutt_restore_default, 0);
1773 CurrentMenu = MENU_MAIN;
1776 /* Unset suspend by default if we're the session leader */
1777 if (getsid (0) == getpid ())
1778 unset_option (OPTSUSPEND);
1781 mutt_init_history ();
1784 snprintf (buffer, sizeof(buffer), "%s/.madmuttrc", NONULL(MCore.homedir));
1785 if (access (buffer, F_OK) == -1)
1786 snprintf (buffer, sizeof(buffer), "%s/.madmutt/madmuttrc",
1787 NONULL(MCore.homedir));
1790 Muttrc = m_strdup(buffer);
1793 m_strcpy(buffer, sizeof(buffer), Muttrc);
1795 mutt_expand_path (buffer, sizeof(buffer));
1796 Muttrc = m_strdup(buffer);
1799 /* Process the global rc file if it exists and the user hasn't explicity
1800 requested not to via "-n". */
1802 snprintf (buffer, sizeof(buffer), "%s/Madmuttrc-%s", SYSCONFDIR,
1804 if (access (buffer, F_OK) == -1)
1805 snprintf (buffer, sizeof(buffer), "%s/Madmuttrc", SYSCONFDIR);
1806 if (access (buffer, F_OK) == -1)
1807 snprintf (buffer, sizeof(buffer), "%s/Madmuttrc-%s", PKGDATADIR,
1809 if (access (buffer, F_OK) == -1)
1810 snprintf (buffer, sizeof(buffer), "%s/Madmuttrc", PKGDATADIR);
1811 if (access (buffer, F_OK) != -1) {
1812 if (source_rc (buffer, &err) != 0) {
1813 fputs (err.data, stderr);
1814 fputc ('\n', stderr);
1820 /* Read the user's initialization file. */
1821 if (access (Muttrc, F_OK) != -1) {
1822 if (!option (OPTNOCURSES))
1824 if (source_rc (Muttrc, &err) != 0) {
1825 fputs (err.data, stderr);
1826 fputc ('\n', stderr);
1830 else if (!default_rc) {
1831 /* file specified by -F does not exist */
1832 snprintf (buffer, sizeof(buffer), "%s: %s", Muttrc, strerror (errno));
1833 mutt_endwin (buffer);
1838 snprintf(buffer, sizeof(buffer), "%s/.madmutt.lua", NONULL(MCore.homedir));
1839 if (access(buffer, F_OK) < 0)
1840 snprintf(buffer, sizeof(buffer), "%s/.madmutt/cfg.lua", NONULL(MCore.homedir));
1841 if (!access(buffer, F_OK)) {
1842 need_pause = luaM_wrap(mutt_error, luaM_dofile(buffer));
1846 if (mutt_execute_commands (commands) != 0)
1849 /* warn about synonym variables */
1853 fprintf (stderr, _("Warning: the following synonym variables were found:\n"));
1855 for (syn = Synonyms; syn; syn = syn->next) {
1856 fprintf(stderr, "$%s ($%s should be used) (%s:%d)\n",
1857 syn->o ? NONULL(syn->o->option) : "",
1858 syn->n ? NONULL(syn->n->option) : "",
1859 NONULL(syn->f), syn->l);
1861 fprintf (stderr, _("Warning: synonym variables are scheduled"
1862 " for removal.\n"));
1863 syn_list_wipe(&Synonyms);
1867 if (need_pause && !option (OPTNOCURSES)) {
1868 if (mutt_any_key_to_continue (NULL) == -1)
1873 int mutt_get_hook_type (const char *name)
1875 struct command_t *c;
1877 for (c = Commands; c->name; c++)
1878 if (c->func == mutt_parse_hook && ascii_strcasecmp (c->name, name) == 0)
1883 /* dump out the value of all the variables we have */
1884 int mutt_dump_variables (int full) {
1887 /* get all non-synonyms into list... */
1888 for (i = 0; MuttVars[i].option; i++) {
1889 struct option_t *option = MuttVars + i;
1890 char buf[LONG_STRING];
1893 mutt_option_value(option->option, buf, sizeof(buf));
1894 if (!m_strcmp(buf, option->init))
1899 FuncTable[DTYPE(option->type)].opt_tostr(buf, sizeof(buf), option);
1900 printf ("%s\n", buf);
1903 printf ("\n# vi""m:set ft=muttrc:\n");