2 * Copyright notice from original mutt:
3 * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
5 * Parts were written/modified by:
6 * Rocco Rutte <pdmef@cs.tu-berlin.de>
8 * This file is part of mutt-ng, see http://www.muttng.org/.
9 * It's licensed under the GNU General Public License,
10 * please see the file GPL in the top level source directory.
13 #include <lib-lib/lib-lib.h>
15 #include <lib-lua/lib-lua.h>
16 #include <lib-sys/unix.h>
17 #include <lib-ui/lib-ui.h>
18 #include <lib-ui/history.h>
19 #include <lib-mx/mx.h>
26 #include "mutt_idna.h"
33 static const struct mapping_t* get_sortmap (struct option_t* option);
34 static int parse_sort (struct option_t* dst, const char *s,
35 const struct mapping_t *map,
36 char* errbuf, ssize_t errlen);
38 static hash_t *ConfigOptions = NULL;
40 /* for synonym warning reports: synonym found during parsing */
41 typedef struct syn_t {
45 struct option_t* n; /* new */
46 struct option_t* o; /* old */
50 static void syn_wipe(syn_t *syn) {
54 DO_DELETE(syn_t, syn);
55 DO_SLIST(syn_t, syn, syn_delete);
57 /* for synonym warning reports: list of synonyms found */
58 static syn_t *Synonyms = NULL;
59 /* for synonym warning reports: current rc file */
60 static const char* CurRCFile = NULL;
61 /* for synonym warning reports: current rc line */
62 static int CurRCLine = 0;
64 /* prototypes for checking for special vars */
65 static int check_history (const char* option, unsigned long val,
66 char* errbuf, ssize_t errlen);
67 /* this checks that numbers are >= 0 */
68 static int check_num (const char* option, unsigned long val,
69 char* errbuf, ssize_t errlen);
71 /* use this to check only */
72 static int check_special (const char* option, unsigned long val,
73 char* errbuf, ssize_t errlen);
75 /* variable <-> sanity check function mappings
76 * when changing these, make sure the proper _from_string handler
81 int (*check) (const char* option, unsigned long val,
82 char* errbuf, ssize_t errlen);
84 { "history", check_history },
85 { "pager_index_lines", check_num },
90 static void bool_to_string (char* dst, ssize_t dstlen,
91 struct option_t* option) {
92 snprintf (dst, dstlen, "%s=%s", option->option,
93 option (option->data) ? "yes" : "no");
96 static int bool_from_string (struct option_t* dst, const char* val,
97 char* errbuf __attribute__ ((unused)),
98 ssize_t errlen __attribute__ ((unused))) {
103 if (ascii_strncasecmp (val, "yes", 3) == 0)
105 else if (ascii_strncasecmp (val, "no", 2) == 0)
111 set_option (dst->data);
113 unset_option (dst->data);
117 static void num_to_string (char* dst, ssize_t dstlen,
118 struct option_t* option) {
120 const char* fmt = (m_strcmp(option->option, "umask") == 0) ?
122 snprintf (dst, dstlen, fmt, option->option,
123 *((short*) option->data));
126 static int num_from_string (struct option_t* dst, const char* val,
127 char* errbuf, ssize_t errlen) {
128 int num = 0, old = 0;
134 num = strtol (val, &t, 0);
136 if (m_strisempty(val) || *t || (short) num != num) {
138 snprintf (errbuf, errlen, _("'%s' is invalid for $%s"),
144 /* just temporarily accept new val so that check_special for
145 * $history already has it when doing history's init() */
146 old = *((short*) dst->data);
147 *((short*) dst->data) = (short) num;
149 if (!check_special (dst->option, (unsigned long) num, errbuf, errlen)) {
150 *((short*) dst->data) = old;
157 static void str_to_string (char* dst, ssize_t dstlen,
158 struct option_t* option) {
159 snprintf (dst, dstlen, "%s=\"%s\"", option->option,
160 NONULL (*((char**) option->data)));
163 static int path_from_string (struct option_t* dst, const char* val,
164 char* errbuf __attribute__ ((unused)), ssize_t errlen __attribute__ ((unused))) {
165 char path[_POSIX_PATH_MAX];
170 if (m_strisempty(val)) {
171 p_delete((char**) dst->data);
176 m_strcpy(path, sizeof(path), val);
177 mutt_expand_path (path, sizeof(path));
178 m_strreplace((char **) dst->data, path);
182 static int str_from_string (struct option_t* dst, const char* val,
183 char* errbuf, ssize_t errlen) {
187 if (!check_special (dst->option, (unsigned long) val, errbuf, errlen))
190 m_strreplace((char**) dst->data, val);
194 static void quad_to_string (char* dst, ssize_t dstlen,
195 struct option_t* option) {
196 const char *vals[] = { "no", "yes", "ask-no", "ask-yes" };
197 snprintf (dst, dstlen, "%s=%s", option->option,
198 vals[quadoption (option->data)]);
201 static int quad_from_string (struct option_t* dst, const char* val,
202 char* errbuf __attribute__ ((unused)), ssize_t errlen __attribute__ ((unused))) {
207 if (ascii_strncasecmp (val, "yes", 3) == 0)
209 else if (ascii_strncasecmp (val, "no", 2) == 0)
211 else if (ascii_strncasecmp (val, "ask-yes", 7) == 0)
213 else if (ascii_strncasecmp (val, "ask-no", 6) == 0)
219 set_quadoption (dst->data, flag);
223 static void sort_to_string (char* dst, ssize_t dstlen,
224 struct option_t* option) {
225 const struct mapping_t *map = get_sortmap (option);
226 const char *p = NULL;
229 snprintf (dst, sizeof(dst), "%s=unknown", option->option);
233 p = mutt_getnamebyvalue(*((short *)option->data) & SORT_MASK, map);
235 snprintf (dst, dstlen, "%s=%s%s%s", option->option,
236 (*((short *) option->data) & SORT_REVERSE) ?
238 (*((short *) option->data) & SORT_LAST) ? "last-" :
242 static int sort_from_string (struct option_t* dst, const char* val,
243 char* errbuf, ssize_t errlen) {
244 const struct mapping_t *map = NULL;
245 if (!(map = get_sortmap (dst))) {
247 snprintf (errbuf, errlen, _("%s: Unknown type."),
251 if (parse_sort (dst, val, map, errbuf, errlen) == -1)
256 static void rx_to_string (char* dst, ssize_t dstlen,
257 struct option_t* option) {
258 rx_t* p = (rx_t*) option->data;
259 snprintf (dst, dstlen, "%s=\"%s\"", option->option,
260 NONULL (p->pattern));
263 static int rx_from_string (struct option_t* dst, const char* val,
264 char* errbuf, ssize_t errlen) {
267 int flags = 0, e = 0, neg = 0;
273 if (option (OPTATTACHMSG) && !m_strcmp(dst->option, "reply_regexp")) {
275 snprintf (errbuf, errlen,
276 "Operation not permitted when in attach-message mode.");
280 if (!((rx_t*) dst->data))
281 *((rx_t**) dst->data) = p_new(rx_t, 1);
283 p = (rx_t*) dst->data;
285 /* something to do? */
286 if (m_strisempty(val) || (p->pattern && m_strcmp(p->pattern, val) == 0))
289 if (m_strcmp(dst->option, "mask") != 0)
290 flags |= mutt_which_case (val);
293 if (m_strcmp(dst->option, "mask") == 0 && *s == '!') {
298 rx = p_new(regex_t, 1);
300 if ((e = REGCOMP (rx, s, flags)) != 0) {
301 regerror (e, rx, errbuf, errlen);
312 m_strreplace(&p->pattern, val);
316 if (m_strcmp(dst->option, "reply_regexp") == 0)
317 mutt_adjust_all_subjects ();
322 static void magic_to_string (char* dst, ssize_t dstlen,
323 struct option_t* option) {
324 const char* s = NULL;
325 switch (option->data) {
326 case M_MBOX: s = "mbox"; break;
327 case M_MH: s = "MH"; break;
328 case M_MAILDIR: s = "Maildir"; break;
329 default: s = "unknown"; break;
331 snprintf (dst, dstlen, "%s=%s", option->option, s);
334 static int magic_from_string (struct option_t* dst, const char* val,
335 char *errbuf, ssize_t errlen)
339 if (!dst || m_strisempty(val))
341 if (ascii_strncasecmp (val, "mbox", 4) == 0)
343 else if (ascii_strncasecmp (val, "mh", 2) == 0)
345 else if (ascii_strncasecmp (val, "maildir", 7) == 0)
351 *((short*) dst->data) = flag;
358 void (*opt_tostr) (char* dst, ssize_t dstlen, struct option_t* option);
359 int (*opt_fromstr) (struct option_t* dst, const char* val,
360 char* errbuf, ssize_t errlen);
362 { 0, NULL, NULL }, /* there's no DT_ type with 0 */
363 { DT_BOOL, bool_to_string, bool_from_string },
364 { DT_NUM, num_to_string, num_from_string },
365 { DT_STR, str_to_string, str_from_string },
366 { DT_PATH, str_to_string, path_from_string },
367 { DT_QUAD, quad_to_string, quad_from_string },
368 { DT_SORT, sort_to_string, sort_from_string },
369 { DT_RX, rx_to_string, rx_from_string },
370 { DT_MAGIC, magic_to_string, magic_from_string },
374 int mutt_option_value (const char* val, char* dst, ssize_t dstlen) {
375 struct option_t* option = NULL;
376 char* tmp = NULL, *t = NULL;
379 if (!(option = hash_find (ConfigOptions, val))) {
383 tmp = p_new(char, dstlen+1);
384 FuncTable[DTYPE(option->type)].opt_tostr (tmp, dstlen, option);
386 /* as we get things of type $var=value and don't want to bloat the
387 * above "just" for expansion, we do the stripping here */
388 t = strchr (tmp, '=');
392 if (t[l-1] == '"' && *t == '"') {
397 memcpy (dst, t, l+1);
403 static void toggle_quadoption (int opt)
406 int b = (opt % 4) * 2;
408 QuadOptions[n] ^= (1 << b);
411 void set_quadoption (int opt, int flag)
414 int b = (opt % 4) * 2;
416 QuadOptions[n] &= ~(0x3 << b);
417 QuadOptions[n] |= (flag & 0x3) << b;
420 int quadoption (int opt)
423 int b = (opt % 4) * 2;
425 return (QuadOptions[n] >> b) & 0x3;
428 int query_quadoption2(int v, const char *prompt)
436 return mutt_yesorno(prompt, (v == M_ASKYES));
440 int query_quadoption (int opt, const char *prompt)
442 int v = quadoption (opt);
450 return mutt_yesorno(prompt, (v == M_ASKYES));
454 /* always wise to do what someone else did before */
455 static void _attachments_clean (void) {
457 if (Context && Context->msgcount) {
458 for (i = 0; i < Context->msgcount; i++)
459 Context->hdrs[i]->attach_valid = 0;
463 static int parse_attach_list (BUFFER *buf, BUFFER *s, string_list_t **ldata,
464 BUFFER *err __attribute__ ((unused))) {
466 string_list_t *listp, *lastp;
471 /* Find the last item in the list that data points to. */
473 for (listp = *ldata; listp; listp = listp->next) {
474 a = (ATTACH_MATCH *)listp->data;
479 mutt_extract_token (buf, s, 0);
481 if (!buf->data || *buf->data == '\0')
484 a = p_new(ATTACH_MATCH, 1);
486 /* some cheap hacks that I expect to remove */
487 if (!m_strcasecmp(buf->data, "any"))
488 a->major = m_strdup("*/.*");
489 else if (!m_strcasecmp(buf->data, "none"))
490 a->major = m_strdup("cheap_hack/this_should_never_match");
492 a->major = m_strdup(buf->data);
494 if ((p = strchr(a->major, '/'))) {
499 a->minor = "unknown";
502 len = m_strlen(a->minor);
503 tmpminor = p_new(char, len + 3);
504 m_strcpy(&tmpminor[1], len + 3, a->minor);
506 tmpminor[len+1] = '$';
507 tmpminor[len+2] = '\0';
509 a->major_int = mutt_check_mime_type(a->major);
510 regcomp(&a->minor_rx, tmpminor, REG_ICASE|REG_EXTENDED);
514 listp = p_new(string_list_t, 1);
515 listp->data = (char *)a;
524 while (MoreArgs (s));
526 _attachments_clean();
530 static int parse_unattach_list (BUFFER *buf, BUFFER *s, string_list_t **ldata,
531 BUFFER *err __attribute__ ((unused))) {
533 string_list_t *lp, *lastp, *newlp;
539 mutt_extract_token (buf, s, 0);
541 if (!m_strcasecmp(buf->data, "any"))
542 tmp = m_strdup("*/.*");
543 else if (!m_strcasecmp(buf->data, "none"))
544 tmp = m_strdup("cheap_hack/this_should_never_match");
546 tmp = m_strdup(buf->data);
548 if ((minor = strchr(tmp, '/'))) {
552 minor = m_strdup("unknown");
554 major = mutt_check_mime_type(tmp);
556 /* We must do our own walk here because string_list_remove() will only
557 * remove the string_list_t->data, not anything pointed to by the string_list_t->data. */
559 for(lp = *ldata; lp; ) {
560 a = (ATTACH_MATCH *)lp->data;
561 if (a->major_int == major && !m_strcasecmp(minor, a->minor)) {
562 regfree(&a->minor_rx);
565 /* Relink backward */
567 lastp->next = lp->next;
572 p_delete(&lp->data); /* same as a */
582 while (MoreArgs (s));
585 _attachments_clean();
589 static int print_attach_list (string_list_t *lp, char op, const char *name) {
591 printf("attachments %c%s %s/%s\n", op, name,
592 ((ATTACH_MATCH *)lp->data)->major,
593 ((ATTACH_MATCH *)lp->data)->minor);
600 static int parse_attachments (BUFFER *buf, BUFFER *s,
601 unsigned long data __attribute__ ((unused)),
604 string_list_t **listp;
606 mutt_extract_token(buf, s, 0);
607 if (!buf->data || *buf->data == '\0') {
608 m_strcpy(err->data, err->dsize, _("attachments: no disposition"));
612 category = buf->data;
618 printf("\nCurrent attachments settings:\n\n");
619 print_attach_list(AttachAllow, '+', "A");
620 print_attach_list(AttachExclude, '-', "A");
621 print_attach_list(InlineAllow, '+', "I");
622 print_attach_list(InlineExclude, '-', "I");
623 set_option (OPTFORCEREDRAWINDEX);
624 set_option (OPTFORCEREDRAWPAGER);
625 mutt_any_key_to_continue (NULL);
629 if (op != '+' && op != '-') {
633 if (!m_strncasecmp(category, "attachment", strlen(category))) {
635 listp = &AttachAllow;
637 listp = &AttachExclude;
639 else if (!m_strncasecmp(category, "inline", strlen(category))) {
641 listp = &InlineAllow;
643 listp = &InlineExclude;
645 m_strcpy(err->data, err->dsize, _("attachments: invalid disposition"));
649 return parse_attach_list(buf, s, listp, err);
652 static int parse_unattachments (BUFFER *buf, BUFFER *s, unsigned long data __attribute__ ((unused)), BUFFER *err) {
654 string_list_t **listp;
656 mutt_extract_token(buf, s, 0);
657 if (!buf->data || *buf->data == '\0') {
658 m_strcpy(err->data, err->dsize, _("unattachments: no disposition"));
664 if (op != '+' && op != '-') {
668 if (!m_strncasecmp(p, "attachment", strlen(p))) {
670 listp = &AttachAllow;
672 listp = &AttachExclude;
674 else if (!m_strncasecmp(p, "inline", strlen(p))) {
676 listp = &InlineAllow;
678 listp = &InlineExclude;
681 m_strcpy(err->data, err->dsize, _("unattachments: invalid disposition"));
685 return parse_unattach_list(buf, s, listp, err);
688 static int parse_unalias (BUFFER * buf, BUFFER * s,
689 unsigned long data __attribute__ ((unused)),
690 BUFFER * err __attribute__ ((unused)))
692 alias_t *tmp, **last;
695 mutt_extract_token (buf, s, 0);
697 if (!m_strcmp("*", buf->data) == 0) {
698 if (CurrentMenu == MENU_ALIAS) {
699 for (tmp = Aliases; tmp; tmp = tmp->next)
701 set_option(OPTFORCEREDRAWINDEX);
703 alias_list_wipe(&Aliases);
709 for (last = &Aliases; *last; last = &(*last)->next) {
710 if (!m_strcasecmp(buf->data, (*last)->name)) {
711 if (CurrentMenu == MENU_ALIAS) {
713 set_option (OPTFORCEREDRAWINDEX);
715 tmp = alias_list_pop(last);
721 } while (MoreArgs(s));
726 static int parse_alias (BUFFER * buf, BUFFER * s,
727 unsigned long data __attribute__ ((unused)),
734 m_strcpy(err->data, err->dsize, _("alias: no address"));
738 mutt_extract_token (buf, s, 0);
740 /* check to see if an alias with this name already exists */
741 for (last = &Aliases; *last; last = &(*last)->next) {
742 if (!m_strcasecmp((*last)->name, buf->data))
747 /* create a new alias */
749 (*last)->name = m_strdup(buf->data);
750 /* give the main addressbook code a chance */
751 if (CurrentMenu == MENU_ALIAS)
752 set_option (OPTMENUCALLER);
754 /* override the previous value */
755 address_list_wipe(&(*last)->addr);
756 if (CurrentMenu == MENU_ALIAS)
757 set_option (OPTFORCEREDRAWINDEX);
760 mutt_extract_token(buf, s, M_TOKEN_QUOTE | M_TOKEN_SPACE);
761 (*last)->addr = mutt_parse_adrlist((*last)->addr, buf->data);
762 if (mutt_addrlist_to_idna((*last)->addr, &estr)) {
763 snprintf (err->data, err->dsize,
764 _("Warning: Bad IDN '%s' in alias '%s'.\n"), estr, (*last)->name);
773 parse_unmy_hdr(BUFFER * buf, BUFFER * s,
774 unsigned long data __attribute__ ((unused)),
775 BUFFER * err __attribute__ ((unused)))
778 mutt_extract_token (buf, s, 0);
780 if (!m_strcmp("*", buf->data)) {
781 string_list_wipe(&UserHeader);
783 string_list_t **last = &UserHeader;
784 ssize_t l = m_strlen(buf->data);
786 if (buf->data[l - 1] == ':')
790 if (!ascii_strncasecmp(buf->data, (*last)->data, l)
791 && (*last)->data[l] == ':')
793 string_list_t *tmp = string_list_pop(last);
794 string_item_delete(&tmp);
796 last = &(*last)->next;
800 } while (MoreArgs(s));
805 static int parse_my_hdr (BUFFER * buf, BUFFER * s, unsigned long data __attribute__ ((unused)),
812 mutt_extract_token (buf, s, M_TOKEN_SPACE | M_TOKEN_QUOTE);
813 if ((p = strpbrk (buf->data, ": \t")) == NULL || *p != ':') {
814 m_strcpy(err->data, err->dsize, _("invalid header field"));
817 keylen = p - buf->data + 1;
820 for (tmp = UserHeader;; tmp = tmp->next) {
821 /* see if there is already a field by this name */
822 if (ascii_strncasecmp (buf->data, tmp->data, keylen) == 0) {
823 /* replace the old value */
824 p_delete(&tmp->data);
825 tmp->data = buf->data;
832 tmp->next = string_item_new();
836 tmp = string_item_new();
839 tmp->data = buf->data;
845 parse_sort (struct option_t* dst, const char *s, const struct mapping_t *map,
846 char* errbuf, ssize_t errlen) {
849 if (m_strncmp("reverse-", s, 8) == 0) {
851 flags = SORT_REVERSE;
854 if (m_strncmp("last-", s, 5) == 0) {
859 if ((i = mutt_getvaluebyname (s, map)) == -1) {
861 snprintf (errbuf, errlen, _("'%s' is invalid for $%s"), s, dst->option);
865 *((short*) dst->data) = i | flags;
869 /* if additional data more == 1, we want to resolve synonyms */
870 static void mutt_set_default(const char *name __attribute__ ((unused)), void* p, unsigned long more)
872 char buf[LONG_STRING];
873 struct option_t *ptr = p;
875 if (!ptr || *ptr->init || !FuncTable[DTYPE (ptr->type)].opt_fromstr)
878 mutt_option_value(ptr->option, buf, sizeof(buf));
879 if (m_strlen(ptr->init) == 0 && *buf)
880 ptr->init = m_strdup(buf);
883 static int init_expand (char** dst, struct option_t* src) {
889 if (DTYPE(src->type) == DT_STR || DTYPE(src->type) == DT_PATH) {
890 /* only expand for string as it's the only place where
891 * we want to expand vars right now */
892 if (src->init && *src->init) {
895 len = m_strlen(src->init) + 2;
896 in.data = p_new(char, len + 1);
897 snprintf (in.data, len, "\"%s\"", src->init);
900 mutt_extract_token (&token, &in, 0);
901 if (token.data && *token.data)
902 *dst = m_strdup(token.data);
906 p_delete(&token.data);
910 /* for non-string: take value as is */
911 *dst = m_strdup(src->init);
915 /* if additional data more == 1, we want to resolve synonyms */
916 static void mutt_restore_default (const char* name __attribute__ ((unused)),
917 void* p, unsigned long more) {
919 struct option_t* ptr = (struct option_t*) p;
924 if (FuncTable[DTYPE (ptr->type)].opt_fromstr) {
925 init_expand (&init, ptr);
926 if (!FuncTable[DTYPE (ptr->type)].opt_fromstr (ptr, init, errbuf,
928 if (!option (OPTNOCURSES))
930 fprintf (stderr, _("Invalid default setting for $%s found: \"%s\".\n"
931 "Please report this error: \"%s\"\n"),
932 ptr->option, NONULL (init), errbuf);
938 set_option (OPTFORCEREDRAWINDEX);
939 set_option (OPTFORCEREDRAWPAGER);
940 set_option (OPTSORTSUBTHREADS);
941 set_option (OPTNEEDRESORT);
942 set_option (OPTRESORTINIT);
943 set_option (OPTREDRAWTREE);
946 static int check_num (const char* option, unsigned long p,
947 char* errbuf, ssize_t errlen) {
950 snprintf (errbuf, errlen, _("'%d' is invalid for $%s"), (int) p, option);
956 static int check_history (const char* option __attribute__ ((unused)), unsigned long p,
957 char* errbuf, ssize_t errlen) {
958 if (!check_num ("history", p, errbuf, errlen))
960 mutt_init_history ();
964 static int check_special (const char* name, unsigned long val,
965 char* errbuf, ssize_t errlen) {
968 for (i = 0; SpecialVars[i].name; i++) {
969 if (m_strcmp(SpecialVars[i].name, name) == 0) {
970 return (SpecialVars[i].check (SpecialVars[i].name,
971 val, errbuf, errlen));
977 static const struct mapping_t* get_sortmap (struct option_t* option) {
978 const struct mapping_t* map = NULL;
980 switch (option->type & DT_SUBTYPE_MASK) {
982 map = SortAliasMethods;
984 case DT_SORT_BROWSER:
985 map = SortBrowserMethods;
988 map = SortKeyMethods;
991 map = SortAuxMethods;
1000 static int parse_set (BUFFER * tmp, BUFFER * s, unsigned long data,
1003 int query, unset, inv, reset, r = 0;
1004 struct option_t* option = NULL;
1006 while (MoreArgs (s)) {
1007 /* reset state variables */
1009 unset = data & M_SET_UNSET;
1010 inv = data & M_SET_INV;
1011 reset = data & M_SET_RESET;
1013 if (*s->dptr == '?') {
1017 else if (m_strncmp("no", s->dptr, 2) == 0) {
1021 else if (m_strncmp("inv", s->dptr, 3) == 0) {
1025 else if (*s->dptr == '&') {
1030 /* get the variable name */
1031 mutt_extract_token (tmp, s, M_TOKEN_EQUAL);
1032 option = hash_find(ConfigOptions, tmp->data);
1033 if (!option && !(reset && m_strcmp("all", tmp->data) == 0)) {
1034 snprintf (err->data, err->dsize, _("%s: unknown variable"), tmp->data);
1037 s->dptr = vskipspaces(s->dptr);
1040 if (query || unset || inv) {
1041 snprintf (err->data, err->dsize, _("prefix is illegal with reset"));
1045 if (s && *s->dptr == '=') {
1046 snprintf (err->data, err->dsize, _("value is illegal with reset"));
1050 if (!m_strcmp("all", tmp->data)) {
1051 if (CurrentMenu == MENU_PAGER) {
1052 snprintf (err->data, err->dsize, _("Not available in this menu."));
1055 hash_map (ConfigOptions, mutt_restore_default, 1);
1056 set_option (OPTFORCEREDRAWINDEX);
1057 set_option (OPTFORCEREDRAWPAGER);
1058 set_option (OPTSORTSUBTHREADS);
1059 set_option (OPTNEEDRESORT);
1060 set_option (OPTRESORTINIT);
1061 set_option (OPTREDRAWTREE);
1064 mutt_restore_default (NULL, option, 1);
1067 else if (DTYPE (option->type) == DT_BOOL) {
1068 /* XXX this currently ignores the function table
1069 * as we don't get invert and stuff into it */
1070 if (s && *s->dptr == '=') {
1071 if (unset || inv || query) {
1072 snprintf (err->data, err->dsize, "Usage: set variable=yes|no");
1077 mutt_extract_token (tmp, s, 0);
1078 if (ascii_strcasecmp ("yes", tmp->data) == 0)
1080 else if (ascii_strcasecmp ("no", tmp->data) == 0)
1083 snprintf (err->data, err->dsize, "Usage: set variable=yes|no");
1089 bool_to_string (err->data, err->dsize, option);
1094 unset_option (option->data);
1096 toggle_option (option->data);
1098 set_option (option->data);
1100 else if (DTYPE (option->type) == DT_STR ||
1101 DTYPE (option->type) == DT_PATH ||
1102 DTYPE (option->type) == DT_MAGIC ||
1103 DTYPE (option->type) == DT_NUM ||
1104 DTYPE (option->type) == DT_SORT ||
1105 DTYPE (option->type) == DT_RX)
1107 /* XXX maybe we need to get unset into handlers? */
1108 if (DTYPE (option->type) == DT_STR || DTYPE (option->type) == DT_PATH) {
1110 p_delete((void **)(void *)&option->data);
1115 if (query || *s->dptr != '=') {
1116 FuncTable[DTYPE (option->type)].opt_tostr
1117 (err->data, err->dsize, option);
1122 mutt_extract_token (tmp, s, 0);
1123 if (!FuncTable[DTYPE (option->type)].opt_fromstr
1124 (option, tmp->data, err->data, err->dsize))
1127 else if (DTYPE (option->type) == DT_QUAD) {
1130 quad_to_string (err->data, err->dsize, option);
1134 if (*s->dptr == '=') {
1136 mutt_extract_token (tmp, s, 0);
1137 if (ascii_strcasecmp ("yes", tmp->data) == 0)
1138 set_quadoption (option->data, M_YES);
1139 else if (ascii_strcasecmp ("no", tmp->data) == 0)
1140 set_quadoption (option->data, M_NO);
1141 else if (ascii_strcasecmp ("ask-yes", tmp->data) == 0)
1142 set_quadoption (option->data, M_ASKYES);
1143 else if (ascii_strcasecmp ("ask-no", tmp->data) == 0)
1144 set_quadoption (option->data, M_ASKNO);
1146 snprintf (err->data, err->dsize, _("'%s' is invalid for $%s\n"),
1147 tmp->data, option->option);
1154 toggle_quadoption (option->data);
1156 set_quadoption (option->data, M_NO);
1158 set_quadoption (option->data, M_YES);
1162 snprintf (err->data, err->dsize, _("%s: unknown type"),
1168 set_option (OPTFORCEREDRAWINDEX);
1169 set_option (OPTFORCEREDRAWPAGER);
1170 set_option (OPTSORTSUBTHREADS);
1171 set_option (OPTNEEDRESORT);
1172 set_option (OPTRESORTINIT);
1173 set_option (OPTREDRAWTREE);
1180 /* reads the specified initialization file. returns -1 if errors were found
1181 so that we can pause to let the user know... */
1182 static int source_rc (const char *rcfile, BUFFER * err)
1185 int line = 0, rc = 0, conv = 0;
1187 char *linebuf = NULL;
1188 char *currentline = NULL;
1192 if ((f = mutt_open_read (rcfile, &pid)) == NULL) {
1193 snprintf (err->data, err->dsize, "%s: %s", rcfile, strerror (errno));
1198 while ((linebuf = mutt_read_line(linebuf, &buflen, f, &line)) != NULL) {
1199 conv = ConfigCharset && (*ConfigCharset) && mod_cset.charset;
1201 currentline = m_strdup(linebuf);
1204 mutt_convert_string (¤tline, ConfigCharset, mod_cset.charset, 0);
1207 currentline = linebuf;
1212 if (mutt_parse_rc_line (currentline, &token, err) == -1) {
1213 mutt_error (_("Error in %s, line %d: %s"), rcfile, line, err->data);
1214 if (--rc < -MAXERRS) {
1216 p_delete(¤tline);
1225 p_delete(¤tline);
1227 p_delete(&token.data);
1231 mutt_wait_filter (pid);
1233 /* the muttrc source keyword */
1234 snprintf (err->data, err->dsize,
1235 rc >= -MAXERRS ? _("source: errors in %s")
1236 : _("source: reading aborted due too many errors in %s"),
1245 static int parse_source (BUFFER * tmp, BUFFER * s,
1246 unsigned long data __attribute__ ((unused)),
1249 char path[_POSIX_PATH_MAX];
1253 if (mutt_extract_token (tmp, s, 0) != 0) {
1254 snprintf (err->data, err->dsize, _("source: error at %s"), s->dptr);
1258 m_strcpy(path, sizeof(path), tmp->data);
1259 mutt_expand_path (path, sizeof(path));
1261 rc += source_rc (path, err);
1263 while (MoreArgs (s));
1265 return ((rc < 0) ? -1 : 0);
1268 /* line command to execute
1270 token scratch buffer to be used by parser. caller should free
1271 token->data when finished. the reason for this variable is
1272 to avoid having to allocate and deallocate a lot of memory
1273 if we are parsing many lines. the caller can pass in the
1274 memory to use, which avoids having to create new space for
1275 every call to this function.
1277 err where to write error messages */
1278 int mutt_parse_rc_line (const char *line, BUFFER * token, BUFFER * err)
1284 expn.data = expn.dptr = line;
1285 expn.dsize = m_strlen(line);
1289 expn.dptr = vskipspaces(expn.dptr);
1290 while (*expn.dptr) {
1291 if (*expn.dptr == '#')
1292 break; /* rest of line is a comment */
1293 if (*expn.dptr == ';') {
1297 mutt_extract_token (token, &expn, 0);
1298 for (i = 0; Commands[i].name; i++) {
1299 if (!m_strcmp(token->data, Commands[i].name)) {
1300 if (Commands[i].func (token, &expn, Commands[i].data, err) != 0)
1305 if (!Commands[i].name) {
1306 snprintf (err->data, err->dsize, _("%s: unknown command"),
1307 NONULL (token->data));
1314 p_delete(&expn.data);
1319 #define NUMVARS (sizeof(MuttVars)/sizeof(MuttVars[0]))
1320 #define NUMCOMMANDS (sizeof(Commands)/sizeof(Commands[0]))
1321 /* initial string that starts completion. No telling how much crap
1322 * the user has typed so far. Allocate LONG_STRING just to be sure! */
1323 char User_typed[LONG_STRING] = { 0 };
1325 int Num_matched = 0; /* Number of matches for completion */
1326 char Completed[STRING] = { 0 }; /* completed string (command or variable) */
1327 const char *Matches[MAX (NUMVARS, NUMCOMMANDS) + 1]; /* all the matches + User_typed */
1329 /* helper function for completion. Changes the dest buffer if
1330 necessary/possible to aid completion.
1331 dest == completion result gets here.
1332 src == candidate for completion.
1333 try == user entered data for completion.
1334 len == length of dest buffer.
1336 static void candidate (char *dest, char *try, const char *src, int len)
1340 if (strstr (src, try) == src) {
1341 Matches[Num_matched++] = src;
1343 m_strcpy(dest, len, src);
1345 for (l = 0; src[l] && src[l] == dest[l]; l++);
1351 int mutt_command_complete (char *buffer, ssize_t len, int pos, int numtabs)
1355 int spaces; /* keep track of the number of leading spaces on the line */
1357 buffer = vskipspaces(buffer);
1358 spaces = buffer - pt;
1360 pt = buffer + pos - spaces;
1361 while ((pt > buffer) && !isspace ((unsigned char) *pt))
1364 if (pt == buffer) { /* complete cmd */
1365 /* first TAB. Collect all the matches */
1368 m_strcpy(User_typed, sizeof(User_typed), pt);
1369 p_clear(Matches, countof(Matches));
1370 p_clear(Completed, countof(Completed));
1371 for (num = 0; Commands[num].name; num++)
1372 candidate (Completed, User_typed, Commands[num].name,
1374 Matches[Num_matched++] = User_typed;
1376 /* All matches are stored. Longest non-ambiguous string is ""
1377 * i.e. dont change 'buffer'. Fake successful return this time */
1378 if (User_typed[0] == 0)
1382 if (Completed[0] == 0 && User_typed[0])
1385 /* Num_matched will _always_ be atleast 1 since the initial
1386 * user-typed string is always stored */
1387 if (numtabs == 1 && Num_matched == 2)
1388 snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
1389 else if (numtabs > 1 && Num_matched > 2)
1390 /* cycle thru all the matches */
1391 snprintf (Completed, sizeof(Completed), "%s",
1392 Matches[(numtabs - 2) % Num_matched]);
1394 /* return the completed command */
1395 m_strcpy(buffer, len - spaces, Completed);
1397 else if (!m_strncmp(buffer, "set", 3)
1398 || !m_strncmp(buffer, "unset", 5)
1399 || !m_strncmp(buffer, "reset", 5)
1400 || !m_strncmp(buffer, "toggle", 6)) { /* complete variables */
1401 const char *prefixes[] = { "no", "inv", "?", "&", NULL };
1404 /* loop through all the possible prefixes (no, inv, ...) */
1405 if (!m_strncmp(buffer, "set", 3)) {
1406 for (num = 0; prefixes[num]; num++) {
1407 if (!m_strncmp(pt, prefixes[num], m_strlen(prefixes[num]))) {
1408 pt += m_strlen(prefixes[num]);
1414 /* first TAB. Collect all the matches */
1417 m_strcpy(User_typed, sizeof(User_typed), pt);
1418 p_clear(Matches, countof(Matches));
1419 p_clear(Completed, countof(Completed));
1420 for (num = 0; MuttVars[num].option; num++)
1421 candidate(Completed, User_typed, MuttVars[num].option,
1423 Matches[Num_matched++] = User_typed;
1425 /* All matches are stored. Longest non-ambiguous string is ""
1426 * i.e. dont change 'buffer'. Fake successful return this time */
1427 if (User_typed[0] == 0)
1431 if (Completed[0] == 0 && User_typed[0])
1434 /* Num_matched will _always_ be atleast 1 since the initial
1435 * user-typed string is always stored */
1436 if (numtabs == 1 && Num_matched == 2)
1437 snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
1438 else if (numtabs > 1 && Num_matched > 2)
1439 /* cycle thru all the matches */
1440 snprintf (Completed, sizeof(Completed), "%s",
1441 Matches[(numtabs - 2) % Num_matched]);
1443 m_strcpy(pt, buffer + len - pt - spaces, Completed);
1445 else if (!m_strncmp(buffer, "exec", 4)) {
1446 struct binding_t *menu = km_get_table (CurrentMenu);
1448 if (!menu && CurrentMenu != MENU_PAGER)
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; menu[num].name; num++)
1459 candidate (Completed, User_typed, menu[num].name, sizeof(Completed));
1460 /* try the generic menu */
1461 if (Completed[0] == 0 && CurrentMenu != MENU_PAGER) {
1463 for (num = 0; menu[num].name; num++)
1464 candidate (Completed, User_typed, menu[num].name,
1467 Matches[Num_matched++] = User_typed;
1469 /* All matches are stored. Longest non-ambiguous string is ""
1470 * i.e. dont change 'buffer'. Fake successful return this time */
1471 if (User_typed[0] == 0)
1475 if (Completed[0] == 0 && User_typed[0])
1478 /* Num_matched will _always_ be atleast 1 since the initial
1479 * user-typed string is always stored */
1480 if (numtabs == 1 && Num_matched == 2)
1481 snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
1482 else if (numtabs > 1 && Num_matched > 2)
1483 /* cycle thru all the matches */
1484 snprintf (Completed, sizeof(Completed), "%s",
1485 Matches[(numtabs - 2) % Num_matched]);
1487 m_strcpy(pt, buffer + len - pt - spaces, Completed);
1495 int mutt_var_value_complete (char *buffer, ssize_t len, int pos)
1497 char var[STRING], *pt = buffer;
1499 struct option_t* option = NULL;
1504 buffer = vskipspaces(buffer);
1505 spaces = buffer - pt;
1507 pt = buffer + pos - spaces;
1508 while ((pt > buffer) && !isspace ((unsigned char) *pt))
1510 pt++; /* move past the space */
1511 if (*pt == '=') /* abort if no var before the '=' */
1514 if (m_strncmp(buffer, "set", 3) == 0) {
1515 m_strcpy(var, sizeof(var), pt);
1516 /* ignore the trailing '=' when comparing */
1517 var[m_strlen(var) - 1] = 0;
1518 if (!(option = hash_find (ConfigOptions, var)))
1519 return 0; /* no such variable. */
1521 char tmp[LONG_STRING], tmp2[LONG_STRING];
1523 ssize_t dlen = buffer + len - pt - spaces;
1524 const char *vals[] = { "no", "yes", "ask-no", "ask-yes" };
1528 if ((DTYPE (option->type) == DT_STR) ||
1529 (DTYPE (option->type) == DT_PATH) ||
1530 (DTYPE (option->type) == DT_RX)) {
1531 m_strcpy(tmp, sizeof(tmp), NONULL(*((char **)option->data)));
1532 if (DTYPE (option->type) == DT_PATH)
1533 mutt_pretty_mailbox (tmp);
1535 else if (DTYPE (option->type) == DT_QUAD)
1536 m_strcpy(tmp, sizeof(tmp), vals[quadoption(option->data)]);
1537 else if (DTYPE (option->type) == DT_NUM)
1538 snprintf (tmp, sizeof(tmp), "%d", (*((short *) option->data)));
1539 else if (DTYPE (option->type) == DT_SORT) {
1540 const struct mapping_t *map;
1543 switch (option->type & DT_SUBTYPE_MASK) {
1545 map = SortAliasMethods;
1547 case DT_SORT_BROWSER:
1548 map = SortBrowserMethods;
1551 map = SortKeyMethods;
1557 p = mutt_getnamebyvalue(*((short *) option->data) & SORT_MASK, map);
1558 snprintf(tmp, sizeof(tmp), "%s%s%s",
1559 (*((short *)option->data) & SORT_REVERSE) ? "reverse-" : "",
1560 (*((short *)option->data) & SORT_LAST) ? "last-" : "", p);
1562 else if (DTYPE (option->type) == DT_MAGIC) {
1564 switch (DefaultMagic) {
1577 m_strcpy(tmp, sizeof(tmp), p);
1579 else if (DTYPE (option->type) == DT_BOOL)
1580 m_strcpy(tmp, sizeof(tmp), option(option->data) ? "yes" : "no");
1584 for (s = tmp, d = tmp2; *s && (d - tmp2) < ssizeof(tmp2) - 2;) {
1585 if (*s == '\\' || *s == '"')
1591 m_strcpy(tmp, sizeof(tmp), pt);
1592 snprintf (pt, dlen, "%s\"%s\"", tmp, tmp2);
1600 static int mutt_execute_commands (string_list_t * p)
1603 char errstr[STRING];
1607 err.dsize = sizeof(errstr);
1609 for (; p; p = p->next) {
1610 if (mutt_parse_rc_line (p->data, &token, &err) != 0) {
1611 fprintf (stderr, _("Error in command line: %s\n"), err.data);
1612 p_delete(&token.data);
1616 p_delete(&token.data);
1620 void mutt_init (int skip_sys_rc, string_list_t * commands)
1624 char buffer[STRING], error[STRING];
1625 int default_rc = 0, need_pause = 0;
1631 err.dsize = sizeof(error);
1633 ConfigOptions = hash_new (sizeof(MuttVars) * 2, 0);
1634 for (i = 0; MuttVars[i].option; i++) {
1635 hash_insert (ConfigOptions, MuttVars[i].option, &MuttVars[i]);
1639 * XXX - use something even more difficult to predict?
1641 snprintf (AttachmentMarker, sizeof(AttachmentMarker),
1642 "\033]9;%ld\a", (long) time (NULL));
1645 /* Get some information about the user */
1646 if ((pw = getpwuid (getuid ()))) {
1648 mutt_gecos_name(rnbuf, sizeof(rnbuf), pw, mod_core.gecos_mask);
1649 Realname = m_strdup(rnbuf);
1652 if ((p = getenv("MAIL") ?: getenv("MAILDIR"))) {
1653 Spoolfile = m_strdup(p);
1655 mutt_concat_path(buffer, sizeof(buffer), NONULL(mod_core.homedir), MAILPATH);
1656 Spoolfile = m_strdup(buffer);
1659 if ((p = getenv ("REPLYTO")) != NULL) {
1662 snprintf (buffer, sizeof(buffer), "Reply-To: %s", p);
1665 buf.data = buf.dptr = buffer;
1666 buf.dsize = m_strlen(buffer);
1669 parse_my_hdr (&token, &buf, 0, &err);
1670 p_delete(&token.data);
1673 /* Set standard defaults */
1674 hash_map (ConfigOptions, mutt_set_default, 0);
1675 hash_map (ConfigOptions, mutt_restore_default, 0);
1677 CurrentMenu = MENU_MAIN;
1678 mutt_init_history ();
1681 snprintf (buffer, sizeof(buffer), "%s/.madmuttrc", NONULL(mod_core.homedir));
1682 if (access (buffer, F_OK) == -1)
1683 snprintf (buffer, sizeof(buffer), "%s/.madmutt/madmuttrc",
1684 NONULL(mod_core.homedir));
1687 Muttrc = m_strdup(buffer);
1690 m_strcpy(buffer, sizeof(buffer), Muttrc);
1692 mutt_expand_path (buffer, sizeof(buffer));
1693 Muttrc = m_strdup(buffer);
1696 /* Process the global rc file if it exists and the user hasn't explicity
1697 requested not to via "-n". */
1699 snprintf (buffer, sizeof(buffer), "%s/Madmuttrc-%s", SYSCONFDIR,
1701 if (access (buffer, F_OK) == -1)
1702 snprintf (buffer, sizeof(buffer), "%s/Madmuttrc", SYSCONFDIR);
1703 if (access (buffer, F_OK) == -1)
1704 snprintf (buffer, sizeof(buffer), "%s/Madmuttrc-%s", PKGDATADIR,
1706 if (access (buffer, F_OK) == -1)
1707 snprintf (buffer, sizeof(buffer), "%s/Madmuttrc", PKGDATADIR);
1708 if (access (buffer, F_OK) != -1) {
1709 if (source_rc (buffer, &err) != 0) {
1710 fputs (err.data, stderr);
1711 fputc ('\n', stderr);
1717 /* Read the user's initialization file. */
1718 if (access (Muttrc, F_OK) != -1) {
1719 if (!option (OPTNOCURSES))
1721 if (source_rc (Muttrc, &err) != 0) {
1722 fputs (err.data, stderr);
1723 fputc ('\n', stderr);
1727 else if (!default_rc) {
1728 /* file specified by -F does not exist */
1729 snprintf (buffer, sizeof(buffer), "%s: %s", Muttrc, strerror (errno));
1730 mutt_endwin (buffer);
1735 snprintf(buffer, sizeof(buffer), "%s/.madmutt.lua", NONULL(mod_core.homedir));
1736 if (access(buffer, F_OK) < 0)
1737 snprintf(buffer, sizeof(buffer), "%s/.madmutt/cfg.lua", NONULL(mod_core.homedir));
1738 if (!access(buffer, F_OK)) {
1739 need_pause = luaM_wrap(mutt_error, luaM_dofile(buffer));
1743 if (mutt_execute_commands (commands) != 0)
1746 /* warn about synonym variables */
1750 fprintf (stderr, _("Warning: the following synonym variables were found:\n"));
1752 for (syn = Synonyms; syn; syn = syn->next) {
1753 fprintf(stderr, "$%s ($%s should be used) (%s:%d)\n",
1754 syn->o ? NONULL(syn->o->option) : "",
1755 syn->n ? NONULL(syn->n->option) : "",
1756 NONULL(syn->f), syn->l);
1758 fprintf (stderr, _("Warning: synonym variables are scheduled"
1759 " for removal.\n"));
1760 syn_list_wipe(&Synonyms);
1764 if (need_pause && !option (OPTNOCURSES)) {
1765 if (mutt_any_key_to_continue (NULL) == -1)
1770 int mutt_get_hook_type (const char *name)
1772 struct command_t *c;
1774 for (c = Commands; c->name; c++)
1775 if (c->func == mutt_parse_hook && ascii_strcasecmp (c->name, name) == 0)