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_MH: s = "MH"; break;
332 case M_MAILDIR: s = "Maildir"; break;
333 default: s = "unknown"; break;
335 snprintf (dst, dstlen, "%s=%s", option->option, s);
338 static int magic_from_string (struct option_t* dst, const char* val,
339 char *errbuf, ssize_t errlen)
343 if (!dst || m_strisempty(val))
345 if (ascii_strncasecmp (val, "mbox", 4) == 0)
347 else if (ascii_strncasecmp (val, "mh", 2) == 0)
349 else if (ascii_strncasecmp (val, "maildir", 7) == 0)
355 *((short*) dst->data) = flag;
362 void (*opt_tostr) (char* dst, ssize_t dstlen, struct option_t* option);
363 int (*opt_fromstr) (struct option_t* dst, const char* val,
364 char* errbuf, ssize_t errlen);
366 { 0, NULL, NULL }, /* there's no DT_ type with 0 */
367 { DT_BOOL, bool_to_string, bool_from_string },
368 { DT_NUM, num_to_string, num_from_string },
369 { DT_STR, str_to_string, str_from_string },
370 { DT_PATH, str_to_string, path_from_string },
371 { DT_QUAD, quad_to_string, quad_from_string },
372 { DT_SORT, sort_to_string, sort_from_string },
373 { DT_RX, rx_to_string, rx_from_string },
374 { DT_MAGIC, magic_to_string, magic_from_string },
378 int mutt_option_value (const char* val, char* dst, ssize_t dstlen) {
379 struct option_t* option = NULL;
380 char* tmp = NULL, *t = NULL;
383 if (!(option = hash_find (ConfigOptions, val))) {
387 tmp = p_new(char, dstlen+1);
388 FuncTable[DTYPE(option->type)].opt_tostr (tmp, dstlen, option);
390 /* as we get things of type $var=value and don't want to bloat the
391 * above "just" for expansion, we do the stripping here */
392 t = strchr (tmp, '=');
396 if (t[l-1] == '"' && *t == '"') {
401 memcpy (dst, t, l+1);
407 static void toggle_quadoption (int opt)
410 int b = (opt % 4) * 2;
412 QuadOptions[n] ^= (1 << b);
415 void set_quadoption (int opt, int flag)
418 int b = (opt % 4) * 2;
420 QuadOptions[n] &= ~(0x3 << b);
421 QuadOptions[n] |= (flag & 0x3) << b;
424 int quadoption (int opt)
427 int b = (opt % 4) * 2;
429 return (QuadOptions[n] >> b) & 0x3;
432 int query_quadoption2(int v, const char *prompt)
440 v = mutt_yesorno(prompt, (v == M_ASKYES));
441 CLEARLINE (LINES - 1);
446 int query_quadoption (int opt, const char *prompt)
448 int v = quadoption (opt);
456 v = mutt_yesorno (prompt, (v == M_ASKYES));
457 CLEARLINE (LINES - 1);
464 /* always wise to do what someone else did before */
465 static void _attachments_clean (void) {
467 if (Context && Context->msgcount) {
468 for (i = 0; i < Context->msgcount; i++)
469 Context->hdrs[i]->attach_valid = 0;
473 static int parse_attach_list (BUFFER *buf, BUFFER *s, string_list_t **ldata,
474 BUFFER *err __attribute__ ((unused))) {
476 string_list_t *listp, *lastp;
481 /* Find the last item in the list that data points to. */
483 for (listp = *ldata; listp; listp = listp->next) {
484 a = (ATTACH_MATCH *)listp->data;
489 mutt_extract_token (buf, s, 0);
491 if (!buf->data || *buf->data == '\0')
494 a = p_new(ATTACH_MATCH, 1);
496 /* some cheap hacks that I expect to remove */
497 if (!m_strcasecmp(buf->data, "any"))
498 a->major = m_strdup("*/.*");
499 else if (!m_strcasecmp(buf->data, "none"))
500 a->major = m_strdup("cheap_hack/this_should_never_match");
502 a->major = m_strdup(buf->data);
504 if ((p = strchr(a->major, '/'))) {
509 a->minor = "unknown";
512 len = m_strlen(a->minor);
513 tmpminor = p_new(char, len + 3);
514 m_strcpy(&tmpminor[1], len + 3, a->minor);
516 tmpminor[len+1] = '$';
517 tmpminor[len+2] = '\0';
519 a->major_int = mutt_check_mime_type(a->major);
520 regcomp(&a->minor_rx, tmpminor, REG_ICASE|REG_EXTENDED);
524 listp = p_new(string_list_t, 1);
525 listp->data = (char *)a;
534 while (MoreArgs (s));
536 _attachments_clean();
540 static int parse_unattach_list (BUFFER *buf, BUFFER *s, string_list_t **ldata,
541 BUFFER *err __attribute__ ((unused))) {
543 string_list_t *lp, *lastp, *newlp;
549 mutt_extract_token (buf, s, 0);
551 if (!m_strcasecmp(buf->data, "any"))
552 tmp = m_strdup("*/.*");
553 else if (!m_strcasecmp(buf->data, "none"))
554 tmp = m_strdup("cheap_hack/this_should_never_match");
556 tmp = m_strdup(buf->data);
558 if ((minor = strchr(tmp, '/'))) {
562 minor = m_strdup("unknown");
564 major = mutt_check_mime_type(tmp);
566 /* We must do our own walk here because string_list_remove() will only
567 * remove the string_list_t->data, not anything pointed to by the string_list_t->data. */
569 for(lp = *ldata; lp; ) {
570 a = (ATTACH_MATCH *)lp->data;
571 if (a->major_int == major && !m_strcasecmp(minor, a->minor)) {
572 regfree(&a->minor_rx);
575 /* Relink backward */
577 lastp->next = lp->next;
582 p_delete(&lp->data); /* same as a */
592 while (MoreArgs (s));
595 _attachments_clean();
599 static int print_attach_list (string_list_t *lp, char op, const char *name) {
601 printf("attachments %c%s %s/%s\n", op, name,
602 ((ATTACH_MATCH *)lp->data)->major,
603 ((ATTACH_MATCH *)lp->data)->minor);
610 static int parse_attachments (BUFFER *buf, BUFFER *s,
611 unsigned long data __attribute__ ((unused)),
614 string_list_t **listp;
616 mutt_extract_token(buf, s, 0);
617 if (!buf->data || *buf->data == '\0') {
618 m_strcpy(err->data, err->dsize, _("attachments: no disposition"));
622 category = buf->data;
628 printf("\nCurrent attachments settings:\n\n");
629 print_attach_list(AttachAllow, '+', "A");
630 print_attach_list(AttachExclude, '-', "A");
631 print_attach_list(InlineAllow, '+', "I");
632 print_attach_list(InlineExclude, '-', "I");
633 set_option (OPTFORCEREDRAWINDEX);
634 set_option (OPTFORCEREDRAWPAGER);
635 mutt_any_key_to_continue (NULL);
639 if (op != '+' && op != '-') {
643 if (!m_strncasecmp(category, "attachment", strlen(category))) {
645 listp = &AttachAllow;
647 listp = &AttachExclude;
649 else if (!m_strncasecmp(category, "inline", strlen(category))) {
651 listp = &InlineAllow;
653 listp = &InlineExclude;
655 m_strcpy(err->data, err->dsize, _("attachments: invalid disposition"));
659 return parse_attach_list(buf, s, listp, err);
662 static int parse_unattachments (BUFFER *buf, BUFFER *s, unsigned long data __attribute__ ((unused)), BUFFER *err) {
664 string_list_t **listp;
666 mutt_extract_token(buf, s, 0);
667 if (!buf->data || *buf->data == '\0') {
668 m_strcpy(err->data, err->dsize, _("unattachments: no disposition"));
674 if (op != '+' && op != '-') {
678 if (!m_strncasecmp(p, "attachment", strlen(p))) {
680 listp = &AttachAllow;
682 listp = &AttachExclude;
684 else if (!m_strncasecmp(p, "inline", strlen(p))) {
686 listp = &InlineAllow;
688 listp = &InlineExclude;
691 m_strcpy(err->data, err->dsize, _("unattachments: invalid disposition"));
695 return parse_unattach_list(buf, s, listp, err);
698 static int parse_unalias (BUFFER * buf, BUFFER * s,
699 unsigned long data __attribute__ ((unused)),
700 BUFFER * err __attribute__ ((unused)))
702 alias_t *tmp, **last;
705 mutt_extract_token (buf, s, 0);
707 if (!m_strcmp("*", buf->data) == 0) {
708 if (CurrentMenu == MENU_ALIAS) {
709 for (tmp = Aliases; tmp; tmp = tmp->next)
711 set_option(OPTFORCEREDRAWINDEX);
713 alias_list_wipe(&Aliases);
719 for (last = &Aliases; *last; last = &(*last)->next) {
720 if (!m_strcasecmp(buf->data, (*last)->name)) {
721 if (CurrentMenu == MENU_ALIAS) {
723 set_option (OPTFORCEREDRAWINDEX);
725 tmp = alias_list_pop(last);
731 } while (MoreArgs(s));
736 static int parse_alias (BUFFER * buf, BUFFER * s,
737 unsigned long data __attribute__ ((unused)),
744 m_strcpy(err->data, err->dsize, _("alias: no address"));
748 mutt_extract_token (buf, s, 0);
750 /* check to see if an alias with this name already exists */
751 for (last = &Aliases; *last; last = &(*last)->next) {
752 if (!m_strcasecmp((*last)->name, buf->data))
757 /* create a new alias */
759 (*last)->name = m_strdup(buf->data);
760 /* give the main addressbook code a chance */
761 if (CurrentMenu == MENU_ALIAS)
762 set_option (OPTMENUCALLER);
764 /* override the previous value */
765 address_list_wipe(&(*last)->addr);
766 if (CurrentMenu == MENU_ALIAS)
767 set_option (OPTFORCEREDRAWINDEX);
770 mutt_extract_token(buf, s, M_TOKEN_QUOTE | M_TOKEN_SPACE);
771 (*last)->addr = mutt_parse_adrlist((*last)->addr, buf->data);
772 if (mutt_addrlist_to_idna((*last)->addr, &estr)) {
773 snprintf (err->data, err->dsize,
774 _("Warning: Bad IDN '%s' in alias '%s'.\n"), estr, (*last)->name);
783 parse_unmy_hdr(BUFFER * buf, BUFFER * s,
784 unsigned long data __attribute__ ((unused)),
785 BUFFER * err __attribute__ ((unused)))
788 mutt_extract_token (buf, s, 0);
790 if (!m_strcmp("*", buf->data)) {
791 string_list_wipe(&UserHeader);
793 string_list_t **last = &UserHeader;
794 ssize_t l = m_strlen(buf->data);
796 if (buf->data[l - 1] == ':')
800 if (!ascii_strncasecmp(buf->data, (*last)->data, l)
801 && (*last)->data[l] == ':')
803 string_list_t *tmp = string_list_pop(last);
804 string_item_delete(&tmp);
806 last = &(*last)->next;
810 } while (MoreArgs(s));
815 static int parse_my_hdr (BUFFER * buf, BUFFER * s, unsigned long data __attribute__ ((unused)),
822 mutt_extract_token (buf, s, M_TOKEN_SPACE | M_TOKEN_QUOTE);
823 if ((p = strpbrk (buf->data, ": \t")) == NULL || *p != ':') {
824 m_strcpy(err->data, err->dsize, _("invalid header field"));
827 keylen = p - buf->data + 1;
830 for (tmp = UserHeader;; tmp = tmp->next) {
831 /* see if there is already a field by this name */
832 if (ascii_strncasecmp (buf->data, tmp->data, keylen) == 0) {
833 /* replace the old value */
834 p_delete(&tmp->data);
835 tmp->data = buf->data;
842 tmp->next = string_item_new();
846 tmp = string_item_new();
849 tmp->data = buf->data;
855 parse_sort (struct option_t* dst, const char *s, const struct mapping_t *map,
856 char* errbuf, ssize_t errlen) {
859 if (m_strncmp("reverse-", s, 8) == 0) {
861 flags = SORT_REVERSE;
864 if (m_strncmp("last-", s, 5) == 0) {
869 if ((i = mutt_getvaluebyname (s, map)) == -1) {
871 snprintf (errbuf, errlen, _("'%s' is invalid for $%s"), s, dst->option);
875 *((short*) dst->data) = i | flags;
879 /* if additional data more == 1, we want to resolve synonyms */
880 static void mutt_set_default(const char *name __attribute__ ((unused)), void* p, unsigned long more)
882 char buf[LONG_STRING];
883 struct option_t *ptr = p;
885 if (!ptr || *ptr->init || !FuncTable[DTYPE (ptr->type)].opt_fromstr)
888 mutt_option_value(ptr->option, buf, sizeof(buf));
889 if (m_strlen(ptr->init) == 0 && buf && *buf)
890 ptr->init = m_strdup(buf);
893 static int init_expand (char** dst, struct option_t* src) {
899 if (DTYPE(src->type) == DT_STR || DTYPE(src->type) == DT_PATH) {
900 /* only expand for string as it's the only place where
901 * we want to expand vars right now */
902 if (src->init && *src->init) {
905 len = m_strlen(src->init) + 2;
906 in.data = p_new(char, len + 1);
907 snprintf (in.data, len, "\"%s\"", src->init);
910 mutt_extract_token (&token, &in, 0);
911 if (token.data && *token.data)
912 *dst = m_strdup(token.data);
916 p_delete(&token.data);
920 /* for non-string: take value as is */
921 *dst = m_strdup(src->init);
925 /* if additional data more == 1, we want to resolve synonyms */
926 static void mutt_restore_default (const char* name __attribute__ ((unused)),
927 void* p, unsigned long more) {
929 struct option_t* ptr = (struct option_t*) p;
934 if (FuncTable[DTYPE (ptr->type)].opt_fromstr) {
935 init_expand (&init, ptr);
936 if (!FuncTable[DTYPE (ptr->type)].opt_fromstr (ptr, init, errbuf,
938 if (!option (OPTNOCURSES))
940 fprintf (stderr, _("Invalid default setting for $%s found: \"%s\".\n"
941 "Please report this error: \"%s\"\n"),
942 ptr->option, NONULL (init), errbuf);
948 if (ptr->flags & R_INDEX)
949 set_option (OPTFORCEREDRAWINDEX);
950 if (ptr->flags & R_PAGER)
951 set_option (OPTFORCEREDRAWPAGER);
952 if (ptr->flags & R_RESORT_SUB)
953 set_option (OPTSORTSUBTHREADS);
954 if (ptr->flags & R_RESORT)
955 set_option (OPTNEEDRESORT);
956 if (ptr->flags & R_RESORT_INIT)
957 set_option (OPTRESORTINIT);
958 if (ptr->flags & R_TREE)
959 set_option (OPTREDRAWTREE);
962 static int check_num (const char* option, unsigned long p,
963 char* errbuf, ssize_t errlen) {
966 snprintf (errbuf, errlen, _("'%d' is invalid for $%s"), (int) p, option);
972 static int check_history (const char* option __attribute__ ((unused)), unsigned long p,
973 char* errbuf, ssize_t errlen) {
974 if (!check_num ("history", p, errbuf, errlen))
976 mutt_init_history ();
980 static int check_special (const char* name, unsigned long val,
981 char* errbuf, ssize_t errlen) {
984 for (i = 0; SpecialVars[i].name; i++) {
985 if (m_strcmp(SpecialVars[i].name, name) == 0) {
986 return (SpecialVars[i].check (SpecialVars[i].name,
987 val, errbuf, errlen));
993 static const struct mapping_t* get_sortmap (struct option_t* option) {
994 const struct mapping_t* map = NULL;
996 switch (option->type & DT_SUBTYPE_MASK) {
998 map = SortAliasMethods;
1000 case DT_SORT_BROWSER:
1001 map = SortBrowserMethods;
1004 map = SortKeyMethods;
1007 map = SortAuxMethods;
1016 #define CHECK_PAGER \
1017 if ((CurrentMenu == MENU_PAGER) && \
1018 (!option || (option->flags & R_RESORT))) \
1020 snprintf (err->data, err->dsize, \
1021 _("Not available in this menu.")); \
1025 static int parse_set (BUFFER * tmp, BUFFER * s, unsigned long data,
1028 int query, unset, inv, reset, r = 0;
1029 struct option_t* option = NULL;
1031 while (MoreArgs (s)) {
1032 /* reset state variables */
1034 unset = data & M_SET_UNSET;
1035 inv = data & M_SET_INV;
1036 reset = data & M_SET_RESET;
1038 if (*s->dptr == '?') {
1042 else if (m_strncmp("no", s->dptr, 2) == 0) {
1046 else if (m_strncmp("inv", s->dptr, 3) == 0) {
1050 else if (*s->dptr == '&') {
1055 /* get the variable name */
1056 mutt_extract_token (tmp, s, M_TOKEN_EQUAL);
1057 option = hash_find(ConfigOptions, tmp->data);
1058 if (!option && !(reset && m_strcmp("all", tmp->data) == 0)) {
1059 snprintf (err->data, err->dsize, _("%s: unknown variable"), tmp->data);
1062 s->dptr = vskipspaces(s->dptr);
1065 if (query || unset || inv) {
1066 snprintf (err->data, err->dsize, _("prefix is illegal with reset"));
1070 if (s && *s->dptr == '=') {
1071 snprintf (err->data, err->dsize, _("value is illegal with reset"));
1075 if (!m_strcmp("all", tmp->data)) {
1076 if (CurrentMenu == MENU_PAGER) {
1077 snprintf (err->data, err->dsize, _("Not available in this menu."));
1080 hash_map (ConfigOptions, mutt_restore_default, 1);
1081 set_option (OPTFORCEREDRAWINDEX);
1082 set_option (OPTFORCEREDRAWPAGER);
1083 set_option (OPTSORTSUBTHREADS);
1084 set_option (OPTNEEDRESORT);
1085 set_option (OPTRESORTINIT);
1086 set_option (OPTREDRAWTREE);
1090 mutt_restore_default (NULL, option, 1);
1093 else if (DTYPE (option->type) == DT_BOOL) {
1094 /* XXX this currently ignores the function table
1095 * as we don't get invert and stuff into it */
1096 if (s && *s->dptr == '=') {
1097 if (unset || inv || query) {
1098 snprintf (err->data, err->dsize, "Usage: set variable=yes|no");
1103 mutt_extract_token (tmp, s, 0);
1104 if (ascii_strcasecmp ("yes", tmp->data) == 0)
1106 else if (ascii_strcasecmp ("no", tmp->data) == 0)
1109 snprintf (err->data, err->dsize, "Usage: set variable=yes|no");
1115 bool_to_string (err->data, err->dsize, option);
1121 unset_option (option->data);
1123 toggle_option (option->data);
1125 set_option (option->data);
1127 else if (DTYPE (option->type) == DT_STR ||
1128 DTYPE (option->type) == DT_PATH ||
1129 DTYPE (option->type) == DT_MAGIC ||
1130 DTYPE (option->type) == DT_NUM ||
1131 DTYPE (option->type) == DT_SORT ||
1132 DTYPE (option->type) == DT_RX)
1134 /* XXX maybe we need to get unset into handlers? */
1135 if (DTYPE (option->type) == DT_STR || DTYPE (option->type) == DT_PATH) {
1138 p_delete((void **)(void *)&option->data);
1143 if (query || *s->dptr != '=') {
1144 FuncTable[DTYPE (option->type)].opt_tostr
1145 (err->data, err->dsize, option);
1151 mutt_extract_token (tmp, s, 0);
1152 if (!FuncTable[DTYPE (option->type)].opt_fromstr
1153 (option, tmp->data, err->data, err->dsize))
1156 else if (DTYPE (option->type) == DT_QUAD) {
1159 quad_to_string (err->data, err->dsize, option);
1163 if (*s->dptr == '=') {
1166 mutt_extract_token (tmp, s, 0);
1167 if (ascii_strcasecmp ("yes", tmp->data) == 0)
1168 set_quadoption (option->data, M_YES);
1169 else if (ascii_strcasecmp ("no", tmp->data) == 0)
1170 set_quadoption (option->data, M_NO);
1171 else if (ascii_strcasecmp ("ask-yes", tmp->data) == 0)
1172 set_quadoption (option->data, M_ASKYES);
1173 else if (ascii_strcasecmp ("ask-no", tmp->data) == 0)
1174 set_quadoption (option->data, M_ASKNO);
1176 snprintf (err->data, err->dsize, _("'%s' is invalid for $%s\n"),
1177 tmp->data, option->option);
1184 toggle_quadoption (option->data);
1186 set_quadoption (option->data, M_NO);
1188 set_quadoption (option->data, M_YES);
1192 snprintf (err->data, err->dsize, _("%s: unknown type"),
1198 if (option->flags & R_INDEX)
1199 set_option (OPTFORCEREDRAWINDEX);
1200 if (option->flags & R_PAGER)
1201 set_option (OPTFORCEREDRAWPAGER);
1202 if (option->flags & R_RESORT_SUB)
1203 set_option (OPTSORTSUBTHREADS);
1204 if (option->flags & R_RESORT)
1205 set_option (OPTNEEDRESORT);
1206 if (option->flags & R_RESORT_INIT)
1207 set_option (OPTRESORTINIT);
1208 if (option->flags & R_TREE)
1209 set_option (OPTREDRAWTREE);
1216 /* reads the specified initialization file. returns -1 if errors were found
1217 so that we can pause to let the user know... */
1218 static int source_rc (const char *rcfile, BUFFER * err)
1221 int line = 0, rc = 0, conv = 0;
1223 char *linebuf = NULL;
1224 char *currentline = NULL;
1228 if ((f = mutt_open_read (rcfile, &pid)) == NULL) {
1229 snprintf (err->data, err->dsize, "%s: %s", rcfile, strerror (errno));
1234 while ((linebuf = mutt_read_line(linebuf, &buflen, f, &line)) != NULL) {
1235 conv = ConfigCharset && (*ConfigCharset) && MCharset.charset;
1237 currentline = m_strdup(linebuf);
1240 mutt_convert_string (¤tline, ConfigCharset, MCharset.charset, 0);
1243 currentline = linebuf;
1248 if (mutt_parse_rc_line (currentline, &token, err) == -1) {
1249 mutt_error (_("Error in %s, line %d: %s"), rcfile, line, err->data);
1250 if (--rc < -MAXERRS) {
1252 p_delete(¤tline);
1261 p_delete(¤tline);
1263 p_delete(&token.data);
1267 mutt_wait_filter (pid);
1269 /* the muttrc source keyword */
1270 snprintf (err->data, err->dsize,
1271 rc >= -MAXERRS ? _("source: errors in %s")
1272 : _("source: reading aborted due too many errors in %s"),
1281 static int parse_source (BUFFER * tmp, BUFFER * s,
1282 unsigned long data __attribute__ ((unused)),
1285 char path[_POSIX_PATH_MAX];
1289 if (mutt_extract_token (tmp, s, 0) != 0) {
1290 snprintf (err->data, err->dsize, _("source: error at %s"), s->dptr);
1294 m_strcpy(path, sizeof(path), tmp->data);
1295 mutt_expand_path (path, sizeof(path));
1297 rc += source_rc (path, err);
1299 while (MoreArgs (s));
1301 return ((rc < 0) ? -1 : 0);
1304 /* line command to execute
1306 token scratch buffer to be used by parser. caller should free
1307 token->data when finished. the reason for this variable is
1308 to avoid having to allocate and deallocate a lot of memory
1309 if we are parsing many lines. the caller can pass in the
1310 memory to use, which avoids having to create new space for
1311 every call to this function.
1313 err where to write error messages */
1314 int mutt_parse_rc_line (const char *line, BUFFER * token, BUFFER * err)
1320 expn.data = expn.dptr = line;
1321 expn.dsize = m_strlen(line);
1325 expn.dptr = vskipspaces(expn.dptr);
1326 while (*expn.dptr) {
1327 if (*expn.dptr == '#')
1328 break; /* rest of line is a comment */
1329 if (*expn.dptr == ';') {
1333 mutt_extract_token (token, &expn, 0);
1334 for (i = 0; Commands[i].name; i++) {
1335 if (!m_strcmp(token->data, Commands[i].name)) {
1336 if (Commands[i].func (token, &expn, Commands[i].data, err) != 0)
1341 if (!Commands[i].name) {
1342 snprintf (err->data, err->dsize, _("%s: unknown command"),
1343 NONULL (token->data));
1350 p_delete(&expn.data);
1355 #define NUMVARS (sizeof(MuttVars)/sizeof(MuttVars[0]))
1356 #define NUMCOMMANDS (sizeof(Commands)/sizeof(Commands[0]))
1357 /* initial string that starts completion. No telling how much crap
1358 * the user has typed so far. Allocate LONG_STRING just to be sure! */
1359 char User_typed[LONG_STRING] = { 0 };
1361 int Num_matched = 0; /* Number of matches for completion */
1362 char Completed[STRING] = { 0 }; /* completed string (command or variable) */
1363 const char *Matches[MAX (NUMVARS, NUMCOMMANDS) + 1]; /* all the matches + User_typed */
1365 /* helper function for completion. Changes the dest buffer if
1366 necessary/possible to aid completion.
1367 dest == completion result gets here.
1368 src == candidate for completion.
1369 try == user entered data for completion.
1370 len == length of dest buffer.
1372 static void candidate (char *dest, char *try, const char *src, int len)
1376 if (strstr (src, try) == src) {
1377 Matches[Num_matched++] = src;
1379 m_strcpy(dest, len, src);
1381 for (l = 0; src[l] && src[l] == dest[l]; l++);
1387 int mutt_command_complete (char *buffer, ssize_t len, int pos, int numtabs)
1391 int spaces; /* keep track of the number of leading spaces on the line */
1393 buffer = vskipspaces(buffer);
1394 spaces = buffer - pt;
1396 pt = buffer + pos - spaces;
1397 while ((pt > buffer) && !isspace ((unsigned char) *pt))
1400 if (pt == buffer) { /* complete cmd */
1401 /* first TAB. Collect all the matches */
1404 m_strcpy(User_typed, sizeof(User_typed), pt);
1405 p_clear(Matches, countof(Matches));
1406 p_clear(Completed, countof(Completed));
1407 for (num = 0; Commands[num].name; num++)
1408 candidate (Completed, User_typed, Commands[num].name,
1410 Matches[Num_matched++] = User_typed;
1412 /* All matches are stored. Longest non-ambiguous string is ""
1413 * i.e. dont change 'buffer'. Fake successful return this time */
1414 if (User_typed[0] == 0)
1418 if (Completed[0] == 0 && User_typed[0])
1421 /* Num_matched will _always_ be atleast 1 since the initial
1422 * user-typed string is always stored */
1423 if (numtabs == 1 && Num_matched == 2)
1424 snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
1425 else if (numtabs > 1 && Num_matched > 2)
1426 /* cycle thru all the matches */
1427 snprintf (Completed, sizeof(Completed), "%s",
1428 Matches[(numtabs - 2) % Num_matched]);
1430 /* return the completed command */
1431 m_strcpy(buffer, len - spaces, Completed);
1433 else if (!m_strncmp(buffer, "set", 3)
1434 || !m_strncmp(buffer, "unset", 5)
1435 || !m_strncmp(buffer, "reset", 5)
1436 || !m_strncmp(buffer, "toggle", 6)) { /* complete variables */
1437 const char *prefixes[] = { "no", "inv", "?", "&", NULL };
1440 /* loop through all the possible prefixes (no, inv, ...) */
1441 if (!m_strncmp(buffer, "set", 3)) {
1442 for (num = 0; prefixes[num]; num++) {
1443 if (!m_strncmp(pt, prefixes[num], m_strlen(prefixes[num]))) {
1444 pt += m_strlen(prefixes[num]);
1450 /* first TAB. Collect all the matches */
1453 m_strcpy(User_typed, sizeof(User_typed), pt);
1454 p_clear(Matches, countof(Matches));
1455 p_clear(Completed, countof(Completed));
1456 for (num = 0; MuttVars[num].option; num++)
1457 candidate(Completed, User_typed, MuttVars[num].option,
1459 Matches[Num_matched++] = User_typed;
1461 /* All matches are stored. Longest non-ambiguous string is ""
1462 * i.e. dont change 'buffer'. Fake successful return this time */
1463 if (User_typed[0] == 0)
1467 if (Completed[0] == 0 && User_typed[0])
1470 /* Num_matched will _always_ be atleast 1 since the initial
1471 * user-typed string is always stored */
1472 if (numtabs == 1 && Num_matched == 2)
1473 snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
1474 else if (numtabs > 1 && Num_matched > 2)
1475 /* cycle thru all the matches */
1476 snprintf (Completed, sizeof(Completed), "%s",
1477 Matches[(numtabs - 2) % Num_matched]);
1479 m_strcpy(pt, buffer + len - pt - spaces, Completed);
1481 else if (!m_strncmp(buffer, "exec", 4)) {
1482 struct binding_t *menu = km_get_table (CurrentMenu);
1484 if (!menu && CurrentMenu != MENU_PAGER)
1488 /* first TAB. Collect all the matches */
1491 m_strcpy(User_typed, sizeof(User_typed), pt);
1492 p_clear(Matches, countof(Matches));
1493 p_clear(Completed, countof(Completed));
1494 for (num = 0; menu[num].name; num++)
1495 candidate (Completed, User_typed, menu[num].name, sizeof(Completed));
1496 /* try the generic menu */
1497 if (Completed[0] == 0 && CurrentMenu != MENU_PAGER) {
1499 for (num = 0; menu[num].name; num++)
1500 candidate (Completed, User_typed, menu[num].name,
1503 Matches[Num_matched++] = User_typed;
1505 /* All matches are stored. Longest non-ambiguous string is ""
1506 * i.e. dont change 'buffer'. Fake successful return this time */
1507 if (User_typed[0] == 0)
1511 if (Completed[0] == 0 && User_typed[0])
1514 /* Num_matched will _always_ be atleast 1 since the initial
1515 * user-typed string is always stored */
1516 if (numtabs == 1 && Num_matched == 2)
1517 snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
1518 else if (numtabs > 1 && Num_matched > 2)
1519 /* cycle thru all the matches */
1520 snprintf (Completed, sizeof(Completed), "%s",
1521 Matches[(numtabs - 2) % Num_matched]);
1523 m_strcpy(pt, buffer + len - pt - spaces, Completed);
1531 int mutt_var_value_complete (char *buffer, ssize_t len, int pos)
1533 char var[STRING], *pt = buffer;
1535 struct option_t* option = NULL;
1540 buffer = vskipspaces(buffer);
1541 spaces = buffer - pt;
1543 pt = buffer + pos - spaces;
1544 while ((pt > buffer) && !isspace ((unsigned char) *pt))
1546 pt++; /* move past the space */
1547 if (*pt == '=') /* abort if no var before the '=' */
1550 if (m_strncmp(buffer, "set", 3) == 0) {
1551 m_strcpy(var, sizeof(var), pt);
1552 /* ignore the trailing '=' when comparing */
1553 var[m_strlen(var) - 1] = 0;
1554 if (!(option = hash_find (ConfigOptions, var)))
1555 return 0; /* no such variable. */
1557 char tmp[LONG_STRING], tmp2[LONG_STRING];
1559 ssize_t dlen = buffer + len - pt - spaces;
1560 const char *vals[] = { "no", "yes", "ask-no", "ask-yes" };
1564 if ((DTYPE (option->type) == DT_STR) ||
1565 (DTYPE (option->type) == DT_PATH) ||
1566 (DTYPE (option->type) == DT_RX)) {
1567 m_strcpy(tmp, sizeof(tmp), NONULL(*((char **)option->data)));
1568 if (DTYPE (option->type) == DT_PATH)
1569 mutt_pretty_mailbox (tmp);
1571 else if (DTYPE (option->type) == DT_QUAD)
1572 m_strcpy(tmp, sizeof(tmp), vals[quadoption(option->data)]);
1573 else if (DTYPE (option->type) == DT_NUM)
1574 snprintf (tmp, sizeof(tmp), "%d", (*((short *) option->data)));
1575 else if (DTYPE (option->type) == DT_SORT) {
1576 const struct mapping_t *map;
1579 switch (option->type & DT_SUBTYPE_MASK) {
1581 map = SortAliasMethods;
1583 case DT_SORT_BROWSER:
1584 map = SortBrowserMethods;
1587 map = SortKeyMethods;
1593 p = mutt_getnamebyvalue(*((short *) option->data) & SORT_MASK, map);
1594 snprintf(tmp, sizeof(tmp), "%s%s%s",
1595 (*((short *)option->data) & SORT_REVERSE) ? "reverse-" : "",
1596 (*((short *)option->data) & SORT_LAST) ? "last-" : "", p);
1598 else if (DTYPE (option->type) == DT_MAGIC) {
1600 switch (DefaultMagic) {
1613 m_strcpy(tmp, sizeof(tmp), p);
1615 else if (DTYPE (option->type) == DT_BOOL)
1616 m_strcpy(tmp, sizeof(tmp), option(option->data) ? "yes" : "no");
1620 for (s = tmp, d = tmp2; *s && (d - tmp2) < ssizeof(tmp2) - 2;) {
1621 if (*s == '\\' || *s == '"')
1627 m_strcpy(tmp, sizeof(tmp), pt);
1628 snprintf (pt, dlen, "%s\"%s\"", tmp, tmp2);
1636 /* Implement the -Q command line flag */
1637 int mutt_query_variables (string_list_t * queries)
1641 char errbuff[STRING];
1642 char command[STRING];
1650 err.dsize = sizeof(errbuff);
1652 for (p = queries; p; p = p->next) {
1653 snprintf (command, sizeof(command), "set ?%s\n", p->data);
1654 if (mutt_parse_rc_line (command, &token, &err) == -1) {
1655 fprintf (stderr, "%s\n", err.data);
1656 p_delete(&token.data);
1659 printf ("%s\n", err.data);
1662 p_delete(&token.data);
1666 static int mutt_execute_commands (string_list_t * p)
1669 char errstr[STRING];
1673 err.dsize = sizeof(errstr);
1675 for (; p; p = p->next) {
1676 if (mutt_parse_rc_line (p->data, &token, &err) != 0) {
1677 fprintf (stderr, _("Error in command line: %s\n"), err.data);
1678 p_delete(&token.data);
1682 p_delete(&token.data);
1686 void mutt_init (int skip_sys_rc, string_list_t * commands)
1690 char buffer[STRING], error[STRING];
1691 int default_rc = 0, need_pause = 0;
1697 err.dsize = sizeof(error);
1699 ConfigOptions = hash_new (sizeof(MuttVars) * 2, 0);
1700 for (i = 0; MuttVars[i].option; i++) {
1701 hash_insert (ConfigOptions, MuttVars[i].option, &MuttVars[i]);
1705 * XXX - use something even more difficult to predict?
1707 snprintf (AttachmentMarker, sizeof(AttachmentMarker),
1708 "\033]9;%ld\a", (long) time (NULL));
1711 /* Get some information about the user */
1712 if ((pw = getpwuid (getuid ()))) {
1714 mutt_gecos_name(rnbuf, sizeof(rnbuf), pw, MCore.gecos_mask);
1715 Realname = m_strdup(rnbuf);
1723 if ((f = safe_fopen (SYSCONFDIR "/nntpserver", "r"))) {
1725 fgets (buffer, sizeof(buffer), f);
1726 p = vskipspaces(buffer);
1728 while (*q && !isspace(*q))
1731 NewsServer = m_strdup(p);
1735 if ((p = getenv ("NNTPSERVER")))
1736 NewsServer = m_strdup(p);
1739 if ((p = getenv("MAIL") ?: getenv("MAILDIR"))) {
1740 Spoolfile = m_strdup(p);
1743 mutt_concat_path(buffer, sizeof(buffer), NONULL(MCore.homedir), MAILPATH);
1745 mutt_concat_path(buffer, sizeof(buffer), MAILPATH, NONULL(MCore.username));
1747 Spoolfile = m_strdup(buffer);
1750 if ((p = getenv ("REPLYTO")) != NULL) {
1753 snprintf (buffer, sizeof(buffer), "Reply-To: %s", p);
1756 buf.data = buf.dptr = buffer;
1757 buf.dsize = m_strlen(buffer);
1760 parse_my_hdr (&token, &buf, 0, &err);
1761 p_delete(&token.data);
1764 /* Set standard defaults */
1765 hash_map (ConfigOptions, mutt_set_default, 0);
1766 hash_map (ConfigOptions, mutt_restore_default, 0);
1768 CurrentMenu = MENU_MAIN;
1771 /* Unset suspend by default if we're the session leader */
1772 if (getsid (0) == getpid ())
1773 unset_option (OPTSUSPEND);
1776 mutt_init_history ();
1779 snprintf (buffer, sizeof(buffer), "%s/.madmuttrc", NONULL(MCore.homedir));
1780 if (access (buffer, F_OK) == -1)
1781 snprintf (buffer, sizeof(buffer), "%s/.madmutt/madmuttrc",
1782 NONULL(MCore.homedir));
1785 Muttrc = m_strdup(buffer);
1788 m_strcpy(buffer, sizeof(buffer), Muttrc);
1790 mutt_expand_path (buffer, sizeof(buffer));
1791 Muttrc = m_strdup(buffer);
1794 /* Process the global rc file if it exists and the user hasn't explicity
1795 requested not to via "-n". */
1797 snprintf (buffer, sizeof(buffer), "%s/Madmuttrc-%s", SYSCONFDIR,
1799 if (access (buffer, F_OK) == -1)
1800 snprintf (buffer, sizeof(buffer), "%s/Madmuttrc", SYSCONFDIR);
1801 if (access (buffer, F_OK) == -1)
1802 snprintf (buffer, sizeof(buffer), "%s/Madmuttrc-%s", PKGDATADIR,
1804 if (access (buffer, F_OK) == -1)
1805 snprintf (buffer, sizeof(buffer), "%s/Madmuttrc", PKGDATADIR);
1806 if (access (buffer, F_OK) != -1) {
1807 if (source_rc (buffer, &err) != 0) {
1808 fputs (err.data, stderr);
1809 fputc ('\n', stderr);
1815 /* Read the user's initialization file. */
1816 if (access (Muttrc, F_OK) != -1) {
1817 if (!option (OPTNOCURSES))
1819 if (source_rc (Muttrc, &err) != 0) {
1820 fputs (err.data, stderr);
1821 fputc ('\n', stderr);
1825 else if (!default_rc) {
1826 /* file specified by -F does not exist */
1827 snprintf (buffer, sizeof(buffer), "%s: %s", Muttrc, strerror (errno));
1828 mutt_endwin (buffer);
1833 snprintf(buffer, sizeof(buffer), "%s/.madmutt.lua", NONULL(MCore.homedir));
1834 if (access(buffer, F_OK) < 0)
1835 snprintf(buffer, sizeof(buffer), "%s/.madmutt/cfg.lua", NONULL(MCore.homedir));
1836 if (!access(buffer, F_OK)) {
1837 need_pause = luaM_wrap(mutt_error, luaM_dofile(buffer));
1841 if (mutt_execute_commands (commands) != 0)
1844 /* warn about synonym variables */
1848 fprintf (stderr, _("Warning: the following synonym variables were found:\n"));
1850 for (syn = Synonyms; syn; syn = syn->next) {
1851 fprintf(stderr, "$%s ($%s should be used) (%s:%d)\n",
1852 syn->o ? NONULL(syn->o->option) : "",
1853 syn->n ? NONULL(syn->n->option) : "",
1854 NONULL(syn->f), syn->l);
1856 fprintf (stderr, _("Warning: synonym variables are scheduled"
1857 " for removal.\n"));
1858 syn_list_wipe(&Synonyms);
1862 if (need_pause && !option (OPTNOCURSES)) {
1863 if (mutt_any_key_to_continue (NULL) == -1)
1868 int mutt_get_hook_type (const char *name)
1870 struct command_t *c;
1872 for (c = Commands; c->name; c++)
1873 if (c->func == mutt_parse_hook && ascii_strcasecmp (c->name, name) == 0)
1878 /* dump out the value of all the variables we have */
1879 int mutt_dump_variables (int full) {
1882 /* get all non-synonyms into list... */
1883 for (i = 0; MuttVars[i].option; i++) {
1884 struct option_t *option = MuttVars + i;
1885 char buf[LONG_STRING];
1888 mutt_option_value(option->option, buf, sizeof(buf));
1889 if (!m_strcmp(buf, option->init))
1894 FuncTable[DTYPE(option->type)].opt_tostr(buf, sizeof(buf), option);
1895 printf ("%s\n", buf);
1898 printf ("\n# vi""m:set ft=muttrc:\n");