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 int magic_from_string (struct option_t* dst, const char* val,
323 char *errbuf, ssize_t errlen)
327 if (!dst || m_strisempty(val))
329 if (ascii_strncasecmp (val, "mbox", 4) == 0)
331 else if (ascii_strncasecmp (val, "mh", 2) == 0)
333 else if (ascii_strncasecmp (val, "maildir", 7) == 0)
339 *((short*) dst->data) = flag;
346 void (*opt_tostr) (char* dst, ssize_t dstlen, struct option_t* option);
347 int (*opt_fromstr) (struct option_t* dst, const char* val,
348 char* errbuf, ssize_t errlen);
350 { 0, NULL, NULL }, /* there's no DT_ type with 0 */
351 { DT_BOOL, bool_to_string, bool_from_string },
352 { DT_NUM, num_to_string, num_from_string },
353 { DT_STR, str_to_string, str_from_string },
354 { DT_PATH, str_to_string, path_from_string },
355 { DT_QUAD, quad_to_string, quad_from_string },
356 { DT_SORT, sort_to_string, sort_from_string },
357 { DT_RX, rx_to_string, rx_from_string },
361 int mutt_option_value (const char* val, char* dst, ssize_t dstlen) {
362 struct option_t* option = NULL;
363 char* tmp = NULL, *t = NULL;
366 if (!(option = hash_find (ConfigOptions, val))) {
370 tmp = p_new(char, dstlen+1);
371 FuncTable[DTYPE(option->type)].opt_tostr (tmp, dstlen, option);
373 /* as we get things of type $var=value and don't want to bloat the
374 * above "just" for expansion, we do the stripping here */
375 t = strchr (tmp, '=');
379 if (t[l-1] == '"' && *t == '"') {
384 memcpy (dst, t, l+1);
390 static void toggle_quadoption (int opt)
393 int b = (opt % 4) * 2;
395 QuadOptions[n] ^= (1 << b);
398 void set_quadoption (int opt, int flag)
401 int b = (opt % 4) * 2;
403 QuadOptions[n] &= ~(0x3 << b);
404 QuadOptions[n] |= (flag & 0x3) << b;
407 int quadoption (int opt)
410 int b = (opt % 4) * 2;
412 return (QuadOptions[n] >> b) & 0x3;
415 int query_quadoption2(int v, const char *prompt)
423 return mutt_yesorno(prompt, (v == M_ASKYES));
427 int query_quadoption (int opt, const char *prompt)
429 int v = quadoption (opt);
437 return mutt_yesorno(prompt, (v == M_ASKYES));
441 /* always wise to do what someone else did before */
442 static void _attachments_clean (void) {
444 if (Context && Context->msgcount) {
445 for (i = 0; i < Context->msgcount; i++)
446 Context->hdrs[i]->attach_valid = 0;
450 static int parse_attach_list (BUFFER *buf, BUFFER *s, string_list_t **ldata,
451 BUFFER *err __attribute__ ((unused))) {
453 string_list_t *listp, *lastp;
458 /* Find the last item in the list that data points to. */
460 for (listp = *ldata; listp; listp = listp->next) {
461 a = (ATTACH_MATCH *)listp->data;
466 mutt_extract_token (buf, s, 0);
468 if (!buf->data || *buf->data == '\0')
471 a = p_new(ATTACH_MATCH, 1);
473 /* some cheap hacks that I expect to remove */
474 if (!m_strcasecmp(buf->data, "any"))
475 a->major = m_strdup("*/.*");
476 else if (!m_strcasecmp(buf->data, "none"))
477 a->major = m_strdup("cheap_hack/this_should_never_match");
479 a->major = m_strdup(buf->data);
481 if ((p = strchr(a->major, '/'))) {
486 a->minor = "unknown";
489 len = m_strlen(a->minor);
490 tmpminor = p_new(char, len + 3);
491 m_strcpy(&tmpminor[1], len + 3, a->minor);
493 tmpminor[len+1] = '$';
494 tmpminor[len+2] = '\0';
496 a->major_int = mutt_check_mime_type(a->major);
497 regcomp(&a->minor_rx, tmpminor, REG_ICASE|REG_EXTENDED);
501 listp = p_new(string_list_t, 1);
502 listp->data = (char *)a;
511 while (MoreArgs (s));
513 _attachments_clean();
517 static int parse_unattach_list (BUFFER *buf, BUFFER *s, string_list_t **ldata,
518 BUFFER *err __attribute__ ((unused))) {
520 string_list_t *lp, *lastp, *newlp;
526 mutt_extract_token (buf, s, 0);
528 if (!m_strcasecmp(buf->data, "any"))
529 tmp = m_strdup("*/.*");
530 else if (!m_strcasecmp(buf->data, "none"))
531 tmp = m_strdup("cheap_hack/this_should_never_match");
533 tmp = m_strdup(buf->data);
535 if ((minor = strchr(tmp, '/'))) {
539 minor = m_strdup("unknown");
541 major = mutt_check_mime_type(tmp);
543 /* We must do our own walk here because string_list_remove() will only
544 * remove the string_list_t->data, not anything pointed to by the string_list_t->data. */
546 for(lp = *ldata; lp; ) {
547 a = (ATTACH_MATCH *)lp->data;
548 if (a->major_int == major && !m_strcasecmp(minor, a->minor)) {
549 regfree(&a->minor_rx);
552 /* Relink backward */
554 lastp->next = lp->next;
559 p_delete(&lp->data); /* same as a */
569 while (MoreArgs (s));
572 _attachments_clean();
576 static int print_attach_list (string_list_t *lp, char op, const char *name) {
578 printf("attachments %c%s %s/%s\n", op, name,
579 ((ATTACH_MATCH *)lp->data)->major,
580 ((ATTACH_MATCH *)lp->data)->minor);
587 static int parse_attachments (BUFFER *buf, BUFFER *s,
588 unsigned long data __attribute__ ((unused)),
591 string_list_t **listp;
593 mutt_extract_token(buf, s, 0);
594 if (!buf->data || *buf->data == '\0') {
595 m_strcpy(err->data, err->dsize, _("attachments: no disposition"));
599 category = buf->data;
605 printf("\nCurrent attachments settings:\n\n");
606 print_attach_list(AttachAllow, '+', "A");
607 print_attach_list(AttachExclude, '-', "A");
608 print_attach_list(InlineAllow, '+', "I");
609 print_attach_list(InlineExclude, '-', "I");
610 set_option (OPTFORCEREDRAWINDEX);
611 set_option (OPTFORCEREDRAWPAGER);
612 mutt_any_key_to_continue (NULL);
616 if (op != '+' && op != '-') {
620 if (!m_strncasecmp(category, "attachment", strlen(category))) {
622 listp = &AttachAllow;
624 listp = &AttachExclude;
626 else if (!m_strncasecmp(category, "inline", strlen(category))) {
628 listp = &InlineAllow;
630 listp = &InlineExclude;
632 m_strcpy(err->data, err->dsize, _("attachments: invalid disposition"));
636 return parse_attach_list(buf, s, listp, err);
639 static int parse_unattachments (BUFFER *buf, BUFFER *s, unsigned long data __attribute__ ((unused)), BUFFER *err) {
641 string_list_t **listp;
643 mutt_extract_token(buf, s, 0);
644 if (!buf->data || *buf->data == '\0') {
645 m_strcpy(err->data, err->dsize, _("unattachments: no disposition"));
651 if (op != '+' && op != '-') {
655 if (!m_strncasecmp(p, "attachment", strlen(p))) {
657 listp = &AttachAllow;
659 listp = &AttachExclude;
661 else if (!m_strncasecmp(p, "inline", strlen(p))) {
663 listp = &InlineAllow;
665 listp = &InlineExclude;
668 m_strcpy(err->data, err->dsize, _("unattachments: invalid disposition"));
672 return parse_unattach_list(buf, s, listp, err);
675 static int parse_unalias (BUFFER * buf, BUFFER * s,
676 unsigned long data __attribute__ ((unused)),
677 BUFFER * err __attribute__ ((unused)))
679 alias_t *tmp, **last;
682 mutt_extract_token (buf, s, 0);
684 if (!m_strcmp("*", buf->data) == 0) {
685 if (CurrentMenu == MENU_ALIAS) {
686 for (tmp = Aliases; tmp; tmp = tmp->next)
688 set_option(OPTFORCEREDRAWINDEX);
690 alias_list_wipe(&Aliases);
696 for (last = &Aliases; *last; last = &(*last)->next) {
697 if (!m_strcasecmp(buf->data, (*last)->name)) {
698 if (CurrentMenu == MENU_ALIAS) {
700 set_option (OPTFORCEREDRAWINDEX);
702 tmp = alias_list_pop(last);
708 } while (MoreArgs(s));
713 static int parse_alias (BUFFER * buf, BUFFER * s,
714 unsigned long data __attribute__ ((unused)),
721 m_strcpy(err->data, err->dsize, _("alias: no address"));
725 mutt_extract_token (buf, s, 0);
727 /* check to see if an alias with this name already exists */
728 for (last = &Aliases; *last; last = &(*last)->next) {
729 if (!m_strcasecmp((*last)->name, buf->data))
734 /* create a new alias */
736 (*last)->name = m_strdup(buf->data);
737 /* give the main addressbook code a chance */
738 if (CurrentMenu == MENU_ALIAS)
739 set_option (OPTMENUCALLER);
741 /* override the previous value */
742 address_list_wipe(&(*last)->addr);
743 if (CurrentMenu == MENU_ALIAS)
744 set_option (OPTFORCEREDRAWINDEX);
747 mutt_extract_token(buf, s, M_TOKEN_QUOTE | M_TOKEN_SPACE);
748 (*last)->addr = mutt_parse_adrlist((*last)->addr, buf->data);
749 if (mutt_addrlist_to_idna((*last)->addr, &estr)) {
750 snprintf (err->data, err->dsize,
751 _("Warning: Bad IDN '%s' in alias '%s'.\n"), estr, (*last)->name);
760 parse_unmy_hdr(BUFFER * buf, BUFFER * s,
761 unsigned long data __attribute__ ((unused)),
762 BUFFER * err __attribute__ ((unused)))
765 mutt_extract_token (buf, s, 0);
767 if (!m_strcmp("*", buf->data)) {
768 string_list_wipe(&UserHeader);
770 string_list_t **last = &UserHeader;
771 ssize_t l = m_strlen(buf->data);
773 if (buf->data[l - 1] == ':')
777 if (!ascii_strncasecmp(buf->data, (*last)->data, l)
778 && (*last)->data[l] == ':')
780 string_list_t *tmp = string_list_pop(last);
781 string_item_delete(&tmp);
783 last = &(*last)->next;
787 } while (MoreArgs(s));
792 static int parse_my_hdr (BUFFER * buf, BUFFER * s, unsigned long data __attribute__ ((unused)),
799 mutt_extract_token (buf, s, M_TOKEN_SPACE | M_TOKEN_QUOTE);
800 if ((p = strpbrk (buf->data, ": \t")) == NULL || *p != ':') {
801 m_strcpy(err->data, err->dsize, _("invalid header field"));
804 keylen = p - buf->data + 1;
807 for (tmp = UserHeader;; tmp = tmp->next) {
808 /* see if there is already a field by this name */
809 if (ascii_strncasecmp (buf->data, tmp->data, keylen) == 0) {
810 /* replace the old value */
811 p_delete(&tmp->data);
812 tmp->data = buf->data;
819 tmp->next = string_item_new();
823 tmp = string_item_new();
826 tmp->data = buf->data;
832 parse_sort (struct option_t* dst, const char *s, const struct mapping_t *map,
833 char* errbuf, ssize_t errlen) {
836 if (m_strncmp("reverse-", s, 8) == 0) {
838 flags = SORT_REVERSE;
841 if (m_strncmp("last-", s, 5) == 0) {
846 if ((i = mutt_getvaluebyname (s, map)) == -1) {
848 snprintf (errbuf, errlen, _("'%s' is invalid for $%s"), s, dst->option);
852 *((short*) dst->data) = i | flags;
856 /* if additional data more == 1, we want to resolve synonyms */
857 static void mutt_set_default(const char *name __attribute__ ((unused)), void* p, unsigned long more)
859 char buf[LONG_STRING];
860 struct option_t *ptr = p;
862 if (!ptr || *ptr->init || !FuncTable[DTYPE (ptr->type)].opt_fromstr)
865 mutt_option_value(ptr->option, buf, sizeof(buf));
866 if (m_strlen(ptr->init) == 0 && *buf)
867 ptr->init = m_strdup(buf);
870 static int init_expand (char** dst, struct option_t* src) {
876 if (DTYPE(src->type) == DT_STR || DTYPE(src->type) == DT_PATH) {
877 /* only expand for string as it's the only place where
878 * we want to expand vars right now */
879 if (src->init && *src->init) {
882 len = m_strlen(src->init) + 2;
883 in.data = p_new(char, len + 1);
884 snprintf (in.data, len, "\"%s\"", src->init);
887 mutt_extract_token (&token, &in, 0);
888 if (token.data && *token.data)
889 *dst = m_strdup(token.data);
893 p_delete(&token.data);
897 /* for non-string: take value as is */
898 *dst = m_strdup(src->init);
902 /* if additional data more == 1, we want to resolve synonyms */
903 static void mutt_restore_default (const char* name __attribute__ ((unused)),
904 void* p, unsigned long more) {
906 struct option_t* ptr = (struct option_t*) p;
911 if (FuncTable[DTYPE (ptr->type)].opt_fromstr) {
912 init_expand (&init, ptr);
913 if (!FuncTable[DTYPE (ptr->type)].opt_fromstr (ptr, init, errbuf,
915 if (!option (OPTNOCURSES))
917 fprintf (stderr, _("Invalid default setting for $%s found: \"%s\".\n"
918 "Please report this error: \"%s\"\n"),
919 ptr->option, NONULL (init), errbuf);
925 set_option (OPTFORCEREDRAWINDEX);
926 set_option (OPTFORCEREDRAWPAGER);
927 set_option (OPTSORTSUBTHREADS);
928 set_option (OPTNEEDRESORT);
929 set_option (OPTRESORTINIT);
930 set_option (OPTREDRAWTREE);
933 static int check_num (const char* option, unsigned long p,
934 char* errbuf, ssize_t errlen) {
937 snprintf (errbuf, errlen, _("'%d' is invalid for $%s"), (int) p, option);
943 static int check_history (const char* option __attribute__ ((unused)), unsigned long p,
944 char* errbuf, ssize_t errlen) {
945 if (!check_num ("history", p, errbuf, errlen))
947 mutt_init_history ();
951 static int check_special (const char* name, unsigned long val,
952 char* errbuf, ssize_t errlen) {
955 for (i = 0; SpecialVars[i].name; i++) {
956 if (m_strcmp(SpecialVars[i].name, name) == 0) {
957 return (SpecialVars[i].check (SpecialVars[i].name,
958 val, errbuf, errlen));
964 static const struct mapping_t* get_sortmap (struct option_t* option) {
965 const struct mapping_t* map = NULL;
967 switch (option->type & DT_SUBTYPE_MASK) {
969 map = SortAliasMethods;
971 case DT_SORT_BROWSER:
972 map = SortBrowserMethods;
975 map = SortKeyMethods;
978 map = SortAuxMethods;
987 static int parse_set (BUFFER * tmp, BUFFER * s, unsigned long data,
990 int query, unset, inv, reset, r = 0;
991 struct option_t* option = NULL;
993 while (MoreArgs (s)) {
994 /* reset state variables */
996 unset = data & M_SET_UNSET;
997 inv = data & M_SET_INV;
998 reset = data & M_SET_RESET;
1000 if (*s->dptr == '?') {
1004 else if (m_strncmp("no", s->dptr, 2) == 0) {
1008 else if (m_strncmp("inv", s->dptr, 3) == 0) {
1012 else if (*s->dptr == '&') {
1017 /* get the variable name */
1018 mutt_extract_token (tmp, s, M_TOKEN_EQUAL);
1019 option = hash_find(ConfigOptions, tmp->data);
1020 if (!option && !(reset && m_strcmp("all", tmp->data) == 0)) {
1021 snprintf (err->data, err->dsize, _("%s: unknown variable"), tmp->data);
1024 s->dptr = vskipspaces(s->dptr);
1027 if (query || unset || inv) {
1028 snprintf (err->data, err->dsize, _("prefix is illegal with reset"));
1032 if (s && *s->dptr == '=') {
1033 snprintf (err->data, err->dsize, _("value is illegal with reset"));
1037 if (!m_strcmp("all", tmp->data)) {
1038 if (CurrentMenu == MENU_PAGER) {
1039 snprintf (err->data, err->dsize, _("Not available in this menu."));
1042 hash_map (ConfigOptions, mutt_restore_default, 1);
1043 set_option (OPTFORCEREDRAWINDEX);
1044 set_option (OPTFORCEREDRAWPAGER);
1045 set_option (OPTSORTSUBTHREADS);
1046 set_option (OPTNEEDRESORT);
1047 set_option (OPTRESORTINIT);
1048 set_option (OPTREDRAWTREE);
1051 mutt_restore_default (NULL, option, 1);
1054 else if (DTYPE (option->type) == DT_BOOL) {
1055 /* XXX this currently ignores the function table
1056 * as we don't get invert and stuff into it */
1057 if (s && *s->dptr == '=') {
1058 if (unset || inv || query) {
1059 snprintf (err->data, err->dsize, "Usage: set variable=yes|no");
1064 mutt_extract_token (tmp, s, 0);
1065 if (ascii_strcasecmp ("yes", tmp->data) == 0)
1067 else if (ascii_strcasecmp ("no", tmp->data) == 0)
1070 snprintf (err->data, err->dsize, "Usage: set variable=yes|no");
1076 bool_to_string (err->data, err->dsize, option);
1081 unset_option (option->data);
1083 toggle_option (option->data);
1085 set_option (option->data);
1087 else if (DTYPE (option->type) == DT_STR ||
1088 DTYPE (option->type) == DT_PATH ||
1089 DTYPE (option->type) == DT_NUM ||
1090 DTYPE (option->type) == DT_SORT ||
1091 DTYPE (option->type) == DT_RX)
1093 /* XXX maybe we need to get unset into handlers? */
1094 if (DTYPE (option->type) == DT_STR || DTYPE (option->type) == DT_PATH) {
1096 p_delete((void **)(void *)&option->data);
1101 if (query || *s->dptr != '=') {
1102 FuncTable[DTYPE (option->type)].opt_tostr
1103 (err->data, err->dsize, option);
1108 mutt_extract_token (tmp, s, 0);
1109 if (!FuncTable[DTYPE (option->type)].opt_fromstr
1110 (option, tmp->data, err->data, err->dsize))
1113 else if (DTYPE (option->type) == DT_QUAD) {
1116 quad_to_string (err->data, err->dsize, option);
1120 if (*s->dptr == '=') {
1122 mutt_extract_token (tmp, s, 0);
1123 if (ascii_strcasecmp ("yes", tmp->data) == 0)
1124 set_quadoption (option->data, M_YES);
1125 else if (ascii_strcasecmp ("no", tmp->data) == 0)
1126 set_quadoption (option->data, M_NO);
1127 else if (ascii_strcasecmp ("ask-yes", tmp->data) == 0)
1128 set_quadoption (option->data, M_ASKYES);
1129 else if (ascii_strcasecmp ("ask-no", tmp->data) == 0)
1130 set_quadoption (option->data, M_ASKNO);
1132 snprintf (err->data, err->dsize, _("'%s' is invalid for $%s\n"),
1133 tmp->data, option->option);
1140 toggle_quadoption (option->data);
1142 set_quadoption (option->data, M_NO);
1144 set_quadoption (option->data, M_YES);
1148 snprintf (err->data, err->dsize, _("%s: unknown type"),
1154 set_option (OPTFORCEREDRAWINDEX);
1155 set_option (OPTFORCEREDRAWPAGER);
1156 set_option (OPTSORTSUBTHREADS);
1157 set_option (OPTNEEDRESORT);
1158 set_option (OPTRESORTINIT);
1159 set_option (OPTREDRAWTREE);
1166 /* reads the specified initialization file. returns -1 if errors were found
1167 so that we can pause to let the user know... */
1168 static int source_rc (const char *rcfile, BUFFER * err)
1171 int line = 0, rc = 0, conv = 0;
1173 char *linebuf = NULL;
1174 char *currentline = NULL;
1178 if ((f = mutt_open_read (rcfile, &pid)) == NULL) {
1179 snprintf (err->data, err->dsize, "%s: %s", rcfile, strerror (errno));
1184 while ((linebuf = mutt_read_line(linebuf, &buflen, f, &line)) != NULL) {
1185 conv = ConfigCharset && (*ConfigCharset) && mod_cset.charset;
1187 currentline = m_strdup(linebuf);
1190 mutt_convert_string (¤tline, ConfigCharset, mod_cset.charset, 0);
1193 currentline = linebuf;
1198 if (mutt_parse_rc_line (currentline, &token, err) == -1) {
1199 mutt_error (_("Error in %s, line %d: %s"), rcfile, line, err->data);
1200 if (--rc < -MAXERRS) {
1202 p_delete(¤tline);
1211 p_delete(¤tline);
1213 p_delete(&token.data);
1217 mutt_wait_filter (pid);
1219 /* the muttrc source keyword */
1220 snprintf (err->data, err->dsize,
1221 rc >= -MAXERRS ? _("source: errors in %s")
1222 : _("source: reading aborted due too many errors in %s"),
1231 static int parse_source (BUFFER * tmp, BUFFER * s,
1232 unsigned long data __attribute__ ((unused)),
1235 char path[_POSIX_PATH_MAX];
1239 if (mutt_extract_token (tmp, s, 0) != 0) {
1240 snprintf (err->data, err->dsize, _("source: error at %s"), s->dptr);
1244 m_strcpy(path, sizeof(path), tmp->data);
1245 mutt_expand_path (path, sizeof(path));
1247 rc += source_rc (path, err);
1249 while (MoreArgs (s));
1251 return (rc < 0) ? -1 : 0;
1254 /* line command to execute
1256 token scratch buffer to be used by parser. caller should free
1257 token->data when finished. the reason for this variable is
1258 to avoid having to allocate and deallocate a lot of memory
1259 if we are parsing many lines. the caller can pass in the
1260 memory to use, which avoids having to create new space for
1261 every call to this function.
1263 err where to write error messages */
1264 int mutt_parse_rc_line (const char *line, BUFFER * token, BUFFER * err)
1270 expn.data = expn.dptr = line;
1271 expn.dsize = m_strlen(line);
1275 expn.dptr = vskipspaces(expn.dptr);
1276 while (*expn.dptr) {
1277 if (*expn.dptr == '#')
1278 break; /* rest of line is a comment */
1279 if (*expn.dptr == ';') {
1283 mutt_extract_token (token, &expn, 0);
1284 for (i = 0; Commands[i].name; i++) {
1285 if (!m_strcmp(token->data, Commands[i].name)) {
1286 if (Commands[i].func (token, &expn, Commands[i].data, err) != 0)
1291 if (!Commands[i].name) {
1292 snprintf (err->data, err->dsize, _("%s: unknown command"),
1293 NONULL (token->data));
1300 p_delete(&expn.data);
1305 #define NUMVARS (sizeof(MuttVars)/sizeof(MuttVars[0]))
1306 #define NUMCOMMANDS (sizeof(Commands)/sizeof(Commands[0]))
1307 /* initial string that starts completion. No telling how much crap
1308 * the user has typed so far. Allocate LONG_STRING just to be sure! */
1309 char User_typed[LONG_STRING] = { 0 };
1311 int Num_matched = 0; /* Number of matches for completion */
1312 char Completed[STRING] = { 0 }; /* completed string (command or variable) */
1313 const char *Matches[MAX (NUMVARS, NUMCOMMANDS) + 1]; /* all the matches + User_typed */
1315 /* helper function for completion. Changes the dest buffer if
1316 necessary/possible to aid completion.
1317 dest == completion result gets here.
1318 src == candidate for completion.
1319 try == user entered data for completion.
1320 len == length of dest buffer.
1322 static void candidate (char *dest, char *try, const char *src, int len)
1326 if (strstr (src, try) == src) {
1327 Matches[Num_matched++] = src;
1329 m_strcpy(dest, len, src);
1331 for (l = 0; src[l] && src[l] == dest[l]; l++);
1337 int mutt_command_complete (char *buffer, ssize_t len, int pos, int numtabs)
1341 int spaces; /* keep track of the number of leading spaces on the line */
1343 buffer = vskipspaces(buffer);
1344 spaces = buffer - pt;
1346 pt = buffer + pos - spaces;
1347 while ((pt > buffer) && !isspace ((unsigned char) *pt))
1350 if (pt == buffer) { /* complete cmd */
1351 /* first TAB. Collect all the matches */
1354 m_strcpy(User_typed, sizeof(User_typed), pt);
1355 p_clear(Matches, countof(Matches));
1356 p_clear(Completed, countof(Completed));
1357 for (num = 0; Commands[num].name; num++)
1358 candidate (Completed, User_typed, Commands[num].name,
1360 Matches[Num_matched++] = User_typed;
1362 /* All matches are stored. Longest non-ambiguous string is ""
1363 * i.e. dont change 'buffer'. Fake successful return this time */
1364 if (User_typed[0] == 0)
1368 if (Completed[0] == 0 && User_typed[0])
1371 /* Num_matched will _always_ be atleast 1 since the initial
1372 * user-typed string is always stored */
1373 if (numtabs == 1 && Num_matched == 2)
1374 snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
1375 else if (numtabs > 1 && Num_matched > 2)
1376 /* cycle thru all the matches */
1377 snprintf (Completed, sizeof(Completed), "%s",
1378 Matches[(numtabs - 2) % Num_matched]);
1380 /* return the completed command */
1381 m_strcpy(buffer, len - spaces, Completed);
1383 else if (!m_strncmp(buffer, "set", 3)
1384 || !m_strncmp(buffer, "unset", 5)
1385 || !m_strncmp(buffer, "reset", 5)
1386 || !m_strncmp(buffer, "toggle", 6)) { /* complete variables */
1387 const char *prefixes[] = { "no", "inv", "?", "&", NULL };
1390 /* loop through all the possible prefixes (no, inv, ...) */
1391 if (!m_strncmp(buffer, "set", 3)) {
1392 for (num = 0; prefixes[num]; num++) {
1393 if (!m_strncmp(pt, prefixes[num], m_strlen(prefixes[num]))) {
1394 pt += m_strlen(prefixes[num]);
1400 /* first TAB. Collect all the matches */
1403 m_strcpy(User_typed, sizeof(User_typed), pt);
1404 p_clear(Matches, countof(Matches));
1405 p_clear(Completed, countof(Completed));
1406 for (num = 0; MuttVars[num].option; num++)
1407 candidate(Completed, User_typed, MuttVars[num].option,
1409 Matches[Num_matched++] = User_typed;
1411 /* All matches are stored. Longest non-ambiguous string is ""
1412 * i.e. dont change 'buffer'. Fake successful return this time */
1413 if (User_typed[0] == 0)
1417 if (Completed[0] == 0 && User_typed[0])
1420 /* Num_matched will _always_ be atleast 1 since the initial
1421 * user-typed string is always stored */
1422 if (numtabs == 1 && Num_matched == 2)
1423 snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
1424 else if (numtabs > 1 && Num_matched > 2)
1425 /* cycle thru all the matches */
1426 snprintf (Completed, sizeof(Completed), "%s",
1427 Matches[(numtabs - 2) % Num_matched]);
1429 m_strcpy(pt, buffer + len - pt - spaces, Completed);
1431 else if (!m_strncmp(buffer, "exec", 4)) {
1432 struct binding_t *menu = km_get_table (CurrentMenu);
1434 if (!menu && CurrentMenu != MENU_PAGER)
1438 /* first TAB. Collect all the matches */
1441 m_strcpy(User_typed, sizeof(User_typed), pt);
1442 p_clear(Matches, countof(Matches));
1443 p_clear(Completed, countof(Completed));
1444 for (num = 0; menu[num].name; num++)
1445 candidate (Completed, User_typed, menu[num].name, sizeof(Completed));
1446 /* try the generic menu */
1447 if (Completed[0] == 0 && CurrentMenu != MENU_PAGER) {
1449 for (num = 0; menu[num].name; num++)
1450 candidate (Completed, User_typed, menu[num].name,
1453 Matches[Num_matched++] = User_typed;
1455 /* All matches are stored. Longest non-ambiguous string is ""
1456 * i.e. dont change 'buffer'. Fake successful return this time */
1457 if (User_typed[0] == 0)
1461 if (Completed[0] == 0 && User_typed[0])
1464 /* Num_matched will _always_ be atleast 1 since the initial
1465 * user-typed string is always stored */
1466 if (numtabs == 1 && Num_matched == 2)
1467 snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
1468 else if (numtabs > 1 && Num_matched > 2)
1469 /* cycle thru all the matches */
1470 snprintf (Completed, sizeof(Completed), "%s",
1471 Matches[(numtabs - 2) % Num_matched]);
1473 m_strcpy(pt, buffer + len - pt - spaces, Completed);
1481 int mutt_var_value_complete (char *buffer, ssize_t len, int pos)
1483 char var[STRING], *pt = buffer;
1485 struct option_t* option = NULL;
1490 buffer = vskipspaces(buffer);
1491 spaces = buffer - pt;
1493 pt = buffer + pos - spaces;
1494 while ((pt > buffer) && !isspace ((unsigned char) *pt))
1496 pt++; /* move past the space */
1497 if (*pt == '=') /* abort if no var before the '=' */
1500 if (m_strncmp(buffer, "set", 3) == 0) {
1501 m_strcpy(var, sizeof(var), pt);
1502 /* ignore the trailing '=' when comparing */
1503 var[m_strlen(var) - 1] = 0;
1504 if (!(option = hash_find (ConfigOptions, var)))
1505 return 0; /* no such variable. */
1507 char tmp[LONG_STRING], tmp2[LONG_STRING];
1509 ssize_t dlen = buffer + len - pt - spaces;
1510 const char *vals[] = { "no", "yes", "ask-no", "ask-yes" };
1514 if ((DTYPE (option->type) == DT_STR) ||
1515 (DTYPE (option->type) == DT_PATH) ||
1516 (DTYPE (option->type) == DT_RX)) {
1517 m_strcpy(tmp, sizeof(tmp), NONULL(*((char **)option->data)));
1518 if (DTYPE (option->type) == DT_PATH)
1519 mutt_pretty_mailbox (tmp);
1521 else if (DTYPE (option->type) == DT_QUAD)
1522 m_strcpy(tmp, sizeof(tmp), vals[quadoption(option->data)]);
1523 else if (DTYPE (option->type) == DT_NUM)
1524 snprintf (tmp, sizeof(tmp), "%d", (*((short *) option->data)));
1525 else if (DTYPE (option->type) == DT_SORT) {
1526 const struct mapping_t *map;
1529 switch (option->type & DT_SUBTYPE_MASK) {
1531 map = SortAliasMethods;
1533 case DT_SORT_BROWSER:
1534 map = SortBrowserMethods;
1537 map = SortKeyMethods;
1543 p = mutt_getnamebyvalue(*((short *) option->data) & SORT_MASK, map);
1544 snprintf(tmp, sizeof(tmp), "%s%s%s",
1545 (*((short *)option->data) & SORT_REVERSE) ? "reverse-" : "",
1546 (*((short *)option->data) & SORT_LAST) ? "last-" : "", p);
1548 else if (DTYPE (option->type) == DT_BOOL)
1549 m_strcpy(tmp, sizeof(tmp), option(option->data) ? "yes" : "no");
1553 for (s = tmp, d = tmp2; *s && (d - tmp2) < ssizeof(tmp2) - 2;) {
1554 if (*s == '\\' || *s == '"')
1560 m_strcpy(tmp, sizeof(tmp), pt);
1561 snprintf (pt, dlen, "%s\"%s\"", tmp, tmp2);
1569 static int mutt_execute_commands (string_list_t * p)
1572 char errstr[STRING];
1576 err.dsize = sizeof(errstr);
1578 for (; p; p = p->next) {
1579 if (mutt_parse_rc_line (p->data, &token, &err) != 0) {
1580 fprintf (stderr, _("Error in command line: %s\n"), err.data);
1581 p_delete(&token.data);
1585 p_delete(&token.data);
1589 void mutt_init (int skip_sys_rc, string_list_t * commands)
1593 char buffer[STRING], error[STRING];
1594 int default_rc = 0, need_pause = 0;
1600 err.dsize = sizeof(error);
1602 ConfigOptions = hash_new (sizeof(MuttVars) * 2, 0);
1603 for (i = 0; MuttVars[i].option; i++) {
1604 hash_insert (ConfigOptions, MuttVars[i].option, &MuttVars[i]);
1608 * XXX - use something even more difficult to predict?
1610 snprintf (AttachmentMarker, sizeof(AttachmentMarker),
1611 "\033]9;%ld\a", (long) time (NULL));
1614 /* Get some information about the user */
1615 if ((pw = getpwuid (getuid ()))) {
1617 mutt_gecos_name(rnbuf, sizeof(rnbuf), pw, mod_core.gecos_mask);
1618 Realname = m_strdup(rnbuf);
1621 if ((p = getenv("MAIL") ?: getenv("MAILDIR"))) {
1622 Spoolfile = m_strdup(p);
1624 mutt_concat_path(buffer, sizeof(buffer), NONULL(mod_core.homedir), MAILPATH);
1625 Spoolfile = m_strdup(buffer);
1628 if ((p = getenv ("REPLYTO")) != NULL) {
1631 snprintf (buffer, sizeof(buffer), "Reply-To: %s", p);
1634 buf.data = buf.dptr = buffer;
1635 buf.dsize = m_strlen(buffer);
1638 parse_my_hdr (&token, &buf, 0, &err);
1639 p_delete(&token.data);
1642 /* Set standard defaults */
1643 hash_map (ConfigOptions, mutt_set_default, 0);
1644 hash_map (ConfigOptions, mutt_restore_default, 0);
1646 CurrentMenu = MENU_MAIN;
1647 mutt_init_history ();
1650 snprintf (buffer, sizeof(buffer), "%s/.madmuttrc", NONULL(mod_core.homedir));
1651 if (access (buffer, F_OK) == -1)
1652 snprintf (buffer, sizeof(buffer), "%s/.madmutt/madmuttrc",
1653 NONULL(mod_core.homedir));
1656 Muttrc = m_strdup(buffer);
1659 m_strcpy(buffer, sizeof(buffer), Muttrc);
1661 mutt_expand_path (buffer, sizeof(buffer));
1662 Muttrc = m_strdup(buffer);
1665 /* Process the global rc file if it exists and the user hasn't explicity
1666 requested not to via "-n". */
1668 snprintf (buffer, sizeof(buffer), "%s/Madmuttrc-%s", SYSCONFDIR,
1670 if (access (buffer, F_OK) == -1)
1671 snprintf (buffer, sizeof(buffer), "%s/Madmuttrc", SYSCONFDIR);
1672 if (access (buffer, F_OK) == -1)
1673 snprintf (buffer, sizeof(buffer), "%s/Madmuttrc-%s", PKGDATADIR,
1675 if (access (buffer, F_OK) == -1)
1676 snprintf (buffer, sizeof(buffer), "%s/Madmuttrc", PKGDATADIR);
1677 if (access (buffer, F_OK) != -1) {
1678 if (source_rc (buffer, &err) != 0) {
1679 fputs (err.data, stderr);
1680 fputc ('\n', stderr);
1686 /* Read the user's initialization file. */
1687 if (access (Muttrc, F_OK) != -1) {
1688 if (!option (OPTNOCURSES))
1690 if (source_rc (Muttrc, &err) != 0) {
1691 fputs (err.data, stderr);
1692 fputc ('\n', stderr);
1696 else if (!default_rc) {
1697 /* file specified by -F does not exist */
1698 snprintf (buffer, sizeof(buffer), "%s: %s", Muttrc, strerror (errno));
1699 mutt_endwin (buffer);
1704 snprintf(buffer, sizeof(buffer), "%s/.madmutt.lua", NONULL(mod_core.homedir));
1705 if (access(buffer, F_OK) < 0)
1706 snprintf(buffer, sizeof(buffer), "%s/.madmutt/cfg.lua", NONULL(mod_core.homedir));
1707 if (!access(buffer, F_OK)) {
1708 need_pause = luaM_wrap(mutt_error, luaM_dofile(buffer));
1712 if (mutt_execute_commands (commands) != 0)
1715 /* warn about synonym variables */
1719 fprintf (stderr, _("Warning: the following synonym variables were found:\n"));
1721 for (syn = Synonyms; syn; syn = syn->next) {
1722 fprintf(stderr, "$%s ($%s should be used) (%s:%d)\n",
1723 syn->o ? NONULL(syn->o->option) : "",
1724 syn->n ? NONULL(syn->n->option) : "",
1725 NONULL(syn->f), syn->l);
1727 fprintf (stderr, _("Warning: synonym variables are scheduled"
1728 " for removal.\n"));
1729 syn_list_wipe(&Synonyms);
1733 if (need_pause && !option (OPTNOCURSES)) {
1734 if (mutt_any_key_to_continue (NULL) == -1)