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-sys/mutt_ssl.h>
18 #include <lib-ui/curses.h>
19 #include <lib-ui/history.h>
20 #include <lib-mx/mx.h>
27 #include "mutt_idna.h"
28 #include "send_smtp.h"
35 static const struct mapping_t* get_sortmap (struct option_t* option);
36 static int parse_sort (struct option_t* dst, const char *s,
37 const struct mapping_t *map,
38 char* errbuf, ssize_t errlen);
40 static hash_t *ConfigOptions = NULL;
42 /* for synonym warning reports: synonym found during parsing */
43 typedef struct syn_t {
47 struct option_t* n; /* new */
48 struct option_t* o; /* old */
52 static void syn_wipe(syn_t *syn) {
56 DO_DELETE(syn_t, syn);
57 DO_SLIST(syn_t, syn, syn_delete);
59 /* for synonym warning reports: list of synonyms found */
60 static syn_t *Synonyms = NULL;
61 /* for synonym warning reports: current rc file */
62 static const char* CurRCFile = NULL;
63 /* for synonym warning reports: current rc line */
64 static int CurRCLine = 0;
66 /* prototypes for checking for special vars */
67 static int check_history (const char* option, unsigned long val,
68 char* errbuf, ssize_t errlen);
69 /* this checks that numbers are >= 0 */
70 static int check_num (const char* option, unsigned long val,
71 char* errbuf, ssize_t errlen);
73 /* use this to check only */
74 static int check_special (const char* option, unsigned long val,
75 char* errbuf, ssize_t errlen);
77 /* variable <-> sanity check function mappings
78 * when changing these, make sure the proper _from_string handler
83 int (*check) (const char* option, unsigned long val,
84 char* errbuf, ssize_t errlen);
86 #if defined (USE_LIBESMTP) && (defined (USE_SSL) || defined (USE_GNUTLS))
87 { "smtp_use_tls", send_smtp_check_usetls },
89 { "history", check_history },
90 { "pager_index_lines", check_num },
95 static void bool_to_string (char* dst, ssize_t dstlen,
96 struct option_t* option) {
97 snprintf (dst, dstlen, "%s=%s", option->option,
98 option (option->data) ? "yes" : "no");
101 static int bool_from_string (struct option_t* dst, const char* val,
102 char* errbuf __attribute__ ((unused)),
103 ssize_t errlen __attribute__ ((unused))) {
108 if (ascii_strncasecmp (val, "yes", 3) == 0)
110 else if (ascii_strncasecmp (val, "no", 2) == 0)
116 set_option (dst->data);
118 unset_option (dst->data);
122 static void num_to_string (char* dst, ssize_t dstlen,
123 struct option_t* option) {
125 const char* fmt = (m_strcmp(option->option, "umask") == 0) ?
127 snprintf (dst, dstlen, fmt, option->option,
128 *((short*) option->data));
131 static int num_from_string (struct option_t* dst, const char* val,
132 char* errbuf, ssize_t errlen) {
133 int num = 0, old = 0;
139 num = strtol (val, &t, 0);
141 if (m_strisempty(val) || *t || (short) num != num) {
143 snprintf (errbuf, errlen, _("'%s' is invalid for $%s"),
149 /* just temporarily accept new val so that check_special for
150 * $history already has it when doing history's init() */
151 old = *((short*) dst->data);
152 *((short*) dst->data) = (short) num;
154 if (!check_special (dst->option, (unsigned long) num, errbuf, errlen)) {
155 *((short*) dst->data) = old;
162 static void str_to_string (char* dst, ssize_t dstlen,
163 struct option_t* option) {
164 snprintf (dst, dstlen, "%s=\"%s\"", option->option,
165 NONULL (*((char**) option->data)));
168 static int path_from_string (struct option_t* dst, const char* val,
169 char* errbuf __attribute__ ((unused)), ssize_t errlen __attribute__ ((unused))) {
170 char path[_POSIX_PATH_MAX];
175 if (m_strisempty(val)) {
176 p_delete((char**) dst->data);
181 m_strcpy(path, sizeof(path), val);
182 mutt_expand_path (path, sizeof(path));
183 m_strreplace((char **) dst->data, path);
187 static int str_from_string (struct option_t* dst, const char* val,
188 char* errbuf, ssize_t errlen) {
192 if (!check_special (dst->option, (unsigned long) val, errbuf, errlen))
195 m_strreplace((char**) dst->data, val);
199 static void quad_to_string (char* dst, ssize_t dstlen,
200 struct option_t* option) {
201 const char *vals[] = { "no", "yes", "ask-no", "ask-yes" };
202 snprintf (dst, dstlen, "%s=%s", option->option,
203 vals[quadoption (option->data)]);
206 static int quad_from_string (struct option_t* dst, const char* val,
207 char* errbuf __attribute__ ((unused)), ssize_t errlen __attribute__ ((unused))) {
212 if (ascii_strncasecmp (val, "yes", 3) == 0)
214 else if (ascii_strncasecmp (val, "no", 2) == 0)
216 else if (ascii_strncasecmp (val, "ask-yes", 7) == 0)
218 else if (ascii_strncasecmp (val, "ask-no", 6) == 0)
224 set_quadoption (dst->data, flag);
228 static void sort_to_string (char* dst, ssize_t dstlen,
229 struct option_t* option) {
230 const struct mapping_t *map = get_sortmap (option);
231 const char *p = NULL;
234 snprintf (dst, sizeof(dst), "%s=unknown", option->option);
238 p = mutt_getnamebyvalue(*((short *)option->data) & SORT_MASK, map);
240 snprintf (dst, dstlen, "%s=%s%s%s", option->option,
241 (*((short *) option->data) & SORT_REVERSE) ?
243 (*((short *) option->data) & SORT_LAST) ? "last-" :
247 static int sort_from_string (struct option_t* dst, const char* val,
248 char* errbuf, ssize_t errlen) {
249 const struct mapping_t *map = NULL;
250 if (!(map = get_sortmap (dst))) {
252 snprintf (errbuf, errlen, _("%s: Unknown type."),
256 if (parse_sort (dst, val, map, errbuf, errlen) == -1)
261 static void rx_to_string (char* dst, ssize_t dstlen,
262 struct option_t* option) {
263 rx_t* p = (rx_t*) option->data;
264 snprintf (dst, dstlen, "%s=\"%s\"", option->option,
265 NONULL (p->pattern));
268 static int rx_from_string (struct option_t* dst, const char* val,
269 char* errbuf, ssize_t errlen) {
272 int flags = 0, e = 0, neg = 0;
278 if (option (OPTATTACHMSG) && !m_strcmp(dst->option, "reply_regexp")) {
280 snprintf (errbuf, errlen,
281 "Operation not permitted when in attach-message mode.");
285 if (!((rx_t*) dst->data))
286 *((rx_t**) dst->data) = p_new(rx_t, 1);
288 p = (rx_t*) dst->data;
290 /* something to do? */
291 if (m_strisempty(val) || (p->pattern && m_strcmp(p->pattern, val) == 0))
294 if (m_strcmp(dst->option, "mask") != 0)
295 flags |= mutt_which_case (val);
298 if (m_strcmp(dst->option, "mask") == 0 && *s == '!') {
303 rx = p_new(regex_t, 1);
305 if ((e = REGCOMP (rx, s, flags)) != 0) {
306 regerror (e, rx, errbuf, errlen);
317 m_strreplace(&p->pattern, val);
321 if (m_strcmp(dst->option, "reply_regexp") == 0)
322 mutt_adjust_all_subjects ();
327 static void magic_to_string (char* dst, ssize_t dstlen,
328 struct option_t* option) {
329 const char* s = NULL;
330 switch (option->data) {
331 case M_MBOX: s = "mbox"; break;
332 case M_MMDF: s = "MMDF"; break;
333 case M_MH: s = "MH"; break;
334 case M_MAILDIR: s = "Maildir"; break;
335 default: s = "unknown"; break;
337 snprintf (dst, dstlen, "%s=%s", option->option, s);
340 static int magic_from_string (struct option_t* dst, const char* val,
341 char* errbuf __attribute__ ((unused)), ssize_t errlen __attribute__ ((unused))) {
344 if (!dst || m_strisempty(val))
346 if (ascii_strncasecmp (val, "mbox", 4) == 0)
348 else if (ascii_strncasecmp (val, "mmdf", 4) == 0)
350 else if (ascii_strncasecmp (val, "mh", 2) == 0)
352 else if (ascii_strncasecmp (val, "maildir", 7) == 0)
358 *((short*) dst->data) = flag;
365 void (*opt_tostr) (char* dst, ssize_t dstlen, struct option_t* option);
366 int (*opt_fromstr) (struct option_t* dst, const char* val,
367 char* errbuf, ssize_t errlen);
369 { 0, NULL, NULL }, /* there's no DT_ type with 0 */
370 { DT_BOOL, bool_to_string, bool_from_string },
371 { DT_NUM, num_to_string, num_from_string },
372 { DT_STR, str_to_string, str_from_string },
373 { DT_PATH, str_to_string, path_from_string },
374 { DT_QUAD, quad_to_string, quad_from_string },
375 { DT_SORT, sort_to_string, sort_from_string },
376 { DT_RX, rx_to_string, rx_from_string },
377 { DT_MAGIC, magic_to_string, magic_from_string },
381 int mutt_option_value (const char* val, char* dst, ssize_t dstlen) {
382 struct option_t* option = NULL;
383 char* tmp = NULL, *t = NULL;
386 if (!(option = hash_find (ConfigOptions, val))) {
390 tmp = p_new(char, dstlen+1);
391 FuncTable[DTYPE(option->type)].opt_tostr (tmp, dstlen, option);
393 /* as we get things of type $var=value and don't want to bloat the
394 * above "just" for expansion, we do the stripping here */
395 t = strchr (tmp, '=');
399 if (t[l-1] == '"' && *t == '"') {
404 memcpy (dst, t, l+1);
410 static void toggle_quadoption (int opt)
413 int b = (opt % 4) * 2;
415 QuadOptions[n] ^= (1 << b);
418 void set_quadoption (int opt, int flag)
421 int b = (opt % 4) * 2;
423 QuadOptions[n] &= ~(0x3 << b);
424 QuadOptions[n] |= (flag & 0x3) << b;
427 int quadoption (int opt)
430 int b = (opt % 4) * 2;
432 return (QuadOptions[n] >> b) & 0x3;
435 int query_quadoption2(int v, const char *prompt)
443 v = mutt_yesorno(prompt, (v == M_ASKYES));
444 CLEARLINE (LINES - 1);
449 int query_quadoption (int opt, const char *prompt)
451 int v = quadoption (opt);
459 v = mutt_yesorno (prompt, (v == M_ASKYES));
460 CLEARLINE (LINES - 1);
467 /* always wise to do what someone else did before */
468 static void _attachments_clean (void) {
470 if (Context && Context->msgcount) {
471 for (i = 0; i < Context->msgcount; i++)
472 Context->hdrs[i]->attach_valid = 0;
476 static int parse_attach_list (BUFFER *buf, BUFFER *s, string_list_t **ldata,
477 BUFFER *err __attribute__ ((unused))) {
479 string_list_t *listp, *lastp;
484 /* Find the last item in the list that data points to. */
486 for (listp = *ldata; listp; listp = listp->next) {
487 a = (ATTACH_MATCH *)listp->data;
492 mutt_extract_token (buf, s, 0);
494 if (!buf->data || *buf->data == '\0')
497 a = p_new(ATTACH_MATCH, 1);
499 /* some cheap hacks that I expect to remove */
500 if (!m_strcasecmp(buf->data, "any"))
501 a->major = m_strdup("*/.*");
502 else if (!m_strcasecmp(buf->data, "none"))
503 a->major = m_strdup("cheap_hack/this_should_never_match");
505 a->major = m_strdup(buf->data);
507 if ((p = strchr(a->major, '/'))) {
512 a->minor = "unknown";
515 len = m_strlen(a->minor);
516 tmpminor = p_new(char, len + 3);
517 m_strcpy(&tmpminor[1], len + 3, a->minor);
519 tmpminor[len+1] = '$';
520 tmpminor[len+2] = '\0';
522 a->major_int = mutt_check_mime_type(a->major);
523 regcomp(&a->minor_rx, tmpminor, REG_ICASE|REG_EXTENDED);
527 listp = p_new(string_list_t, 1);
528 listp->data = (char *)a;
537 while (MoreArgs (s));
539 _attachments_clean();
543 static int parse_unattach_list (BUFFER *buf, BUFFER *s, string_list_t **ldata,
544 BUFFER *err __attribute__ ((unused))) {
546 string_list_t *lp, *lastp, *newlp;
552 mutt_extract_token (buf, s, 0);
554 if (!m_strcasecmp(buf->data, "any"))
555 tmp = m_strdup("*/.*");
556 else if (!m_strcasecmp(buf->data, "none"))
557 tmp = m_strdup("cheap_hack/this_should_never_match");
559 tmp = m_strdup(buf->data);
561 if ((minor = strchr(tmp, '/'))) {
565 minor = m_strdup("unknown");
567 major = mutt_check_mime_type(tmp);
569 /* We must do our own walk here because string_list_remove() will only
570 * remove the string_list_t->data, not anything pointed to by the string_list_t->data. */
572 for(lp = *ldata; lp; ) {
573 a = (ATTACH_MATCH *)lp->data;
574 if (a->major_int == major && !m_strcasecmp(minor, a->minor)) {
575 regfree(&a->minor_rx);
578 /* Relink backward */
580 lastp->next = lp->next;
585 p_delete(&lp->data); /* same as a */
595 while (MoreArgs (s));
598 _attachments_clean();
602 static int print_attach_list (string_list_t *lp, char op, const char *name) {
604 printf("attachments %c%s %s/%s\n", op, name,
605 ((ATTACH_MATCH *)lp->data)->major,
606 ((ATTACH_MATCH *)lp->data)->minor);
613 static int parse_attachments (BUFFER *buf, BUFFER *s,
614 unsigned long data __attribute__ ((unused)),
617 string_list_t **listp;
619 mutt_extract_token(buf, s, 0);
620 if (!buf->data || *buf->data == '\0') {
621 m_strcpy(err->data, err->dsize, _("attachments: no disposition"));
625 category = buf->data;
631 printf("\nCurrent attachments settings:\n\n");
632 print_attach_list(AttachAllow, '+', "A");
633 print_attach_list(AttachExclude, '-', "A");
634 print_attach_list(InlineAllow, '+', "I");
635 print_attach_list(InlineExclude, '-', "I");
636 set_option (OPTFORCEREDRAWINDEX);
637 set_option (OPTFORCEREDRAWPAGER);
638 mutt_any_key_to_continue (NULL);
642 if (op != '+' && op != '-') {
646 if (!m_strncasecmp(category, "attachment", strlen(category))) {
648 listp = &AttachAllow;
650 listp = &AttachExclude;
652 else if (!m_strncasecmp(category, "inline", strlen(category))) {
654 listp = &InlineAllow;
656 listp = &InlineExclude;
658 m_strcpy(err->data, err->dsize, _("attachments: invalid disposition"));
662 return parse_attach_list(buf, s, listp, err);
665 static int parse_unattachments (BUFFER *buf, BUFFER *s, unsigned long data __attribute__ ((unused)), BUFFER *err) {
667 string_list_t **listp;
669 mutt_extract_token(buf, s, 0);
670 if (!buf->data || *buf->data == '\0') {
671 m_strcpy(err->data, err->dsize, _("unattachments: no disposition"));
677 if (op != '+' && op != '-') {
681 if (!m_strncasecmp(p, "attachment", strlen(p))) {
683 listp = &AttachAllow;
685 listp = &AttachExclude;
687 else if (!m_strncasecmp(p, "inline", strlen(p))) {
689 listp = &InlineAllow;
691 listp = &InlineExclude;
694 m_strcpy(err->data, err->dsize, _("unattachments: invalid disposition"));
698 return parse_unattach_list(buf, s, listp, err);
701 static int parse_unalias (BUFFER * buf, BUFFER * s,
702 unsigned long data __attribute__ ((unused)),
703 BUFFER * err __attribute__ ((unused)))
705 alias_t *tmp, **last;
708 mutt_extract_token (buf, s, 0);
710 if (!m_strcmp("*", buf->data) == 0) {
711 if (CurrentMenu == MENU_ALIAS) {
712 for (tmp = Aliases; tmp; tmp = tmp->next)
714 set_option(OPTFORCEREDRAWINDEX);
716 alias_list_wipe(&Aliases);
722 for (last = &Aliases; *last; last = &(*last)->next) {
723 if (!m_strcasecmp(buf->data, (*last)->name)) {
724 if (CurrentMenu == MENU_ALIAS) {
726 set_option (OPTFORCEREDRAWINDEX);
728 tmp = alias_list_pop(last);
734 } while (MoreArgs(s));
739 static int parse_alias (BUFFER * buf, BUFFER * s,
740 unsigned long data __attribute__ ((unused)),
747 m_strcpy(err->data, err->dsize, _("alias: no address"));
751 mutt_extract_token (buf, s, 0);
753 /* check to see if an alias with this name already exists */
754 for (last = &Aliases; *last; last = &(*last)->next) {
755 if (!m_strcasecmp((*last)->name, buf->data))
760 /* create a new alias */
762 (*last)->name = m_strdup(buf->data);
763 /* give the main addressbook code a chance */
764 if (CurrentMenu == MENU_ALIAS)
765 set_option (OPTMENUCALLER);
767 /* override the previous value */
768 address_list_wipe(&(*last)->addr);
769 if (CurrentMenu == MENU_ALIAS)
770 set_option (OPTFORCEREDRAWINDEX);
773 mutt_extract_token(buf, s, M_TOKEN_QUOTE | M_TOKEN_SPACE);
774 (*last)->addr = mutt_parse_adrlist((*last)->addr, buf->data);
775 if (mutt_addrlist_to_idna((*last)->addr, &estr)) {
776 snprintf (err->data, err->dsize,
777 _("Warning: Bad IDN '%s' in alias '%s'.\n"), estr, (*last)->name);
786 parse_unmy_hdr(BUFFER * buf, BUFFER * s,
787 unsigned long data __attribute__ ((unused)),
788 BUFFER * err __attribute__ ((unused)))
791 mutt_extract_token (buf, s, 0);
793 if (!m_strcmp("*", buf->data)) {
794 string_list_wipe(&UserHeader);
796 string_list_t **last = &UserHeader;
797 ssize_t l = m_strlen(buf->data);
799 if (buf->data[l - 1] == ':')
803 if (!ascii_strncasecmp(buf->data, (*last)->data, l)
804 && (*last)->data[l] == ':')
806 string_list_t *tmp = string_list_pop(last);
807 string_item_delete(&tmp);
809 last = &(*last)->next;
813 } while (MoreArgs(s));
818 static int parse_my_hdr (BUFFER * buf, BUFFER * s, unsigned long data __attribute__ ((unused)),
825 mutt_extract_token (buf, s, M_TOKEN_SPACE | M_TOKEN_QUOTE);
826 if ((p = strpbrk (buf->data, ": \t")) == NULL || *p != ':') {
827 m_strcpy(err->data, err->dsize, _("invalid header field"));
830 keylen = p - buf->data + 1;
833 for (tmp = UserHeader;; tmp = tmp->next) {
834 /* see if there is already a field by this name */
835 if (ascii_strncasecmp (buf->data, tmp->data, keylen) == 0) {
836 /* replace the old value */
837 p_delete(&tmp->data);
838 tmp->data = buf->data;
845 tmp->next = string_item_new();
849 tmp = string_item_new();
852 tmp->data = buf->data;
858 parse_sort (struct option_t* dst, const char *s, const struct mapping_t *map,
859 char* errbuf, ssize_t errlen) {
862 if (m_strncmp("reverse-", s, 8) == 0) {
864 flags = SORT_REVERSE;
867 if (m_strncmp("last-", s, 5) == 0) {
872 if ((i = mutt_getvaluebyname (s, map)) == -1) {
874 snprintf (errbuf, errlen, _("'%s' is invalid for $%s"), s, dst->option);
878 *((short*) dst->data) = i | flags;
882 /* if additional data more == 1, we want to resolve synonyms */
883 static void mutt_set_default(const char *name __attribute__ ((unused)), void* p, unsigned long more)
885 char buf[LONG_STRING];
886 struct option_t *ptr = p;
888 if (!ptr || *ptr->init || !FuncTable[DTYPE (ptr->type)].opt_fromstr)
891 mutt_option_value(ptr->option, buf, sizeof(buf));
892 if (m_strlen(ptr->init) == 0 && buf && *buf)
893 ptr->init = m_strdup(buf);
896 static int init_expand (char** dst, struct option_t* src) {
902 if (DTYPE(src->type) == DT_STR || DTYPE(src->type) == DT_PATH) {
903 /* only expand for string as it's the only place where
904 * we want to expand vars right now */
905 if (src->init && *src->init) {
908 len = m_strlen(src->init) + 2;
909 in.data = p_new(char, len + 1);
910 snprintf (in.data, len, "\"%s\"", src->init);
913 mutt_extract_token (&token, &in, 0);
914 if (token.data && *token.data)
915 *dst = m_strdup(token.data);
919 p_delete(&token.data);
923 /* for non-string: take value as is */
924 *dst = m_strdup(src->init);
928 /* if additional data more == 1, we want to resolve synonyms */
929 static void mutt_restore_default (const char* name __attribute__ ((unused)),
930 void* p, unsigned long more) {
932 struct option_t* ptr = (struct option_t*) p;
937 if (FuncTable[DTYPE (ptr->type)].opt_fromstr) {
938 init_expand (&init, ptr);
939 if (!FuncTable[DTYPE (ptr->type)].opt_fromstr (ptr, init, errbuf,
941 if (!option (OPTNOCURSES))
943 fprintf (stderr, _("Invalid default setting for $%s found: \"%s\".\n"
944 "Please report this error: \"%s\"\n"),
945 ptr->option, NONULL (init), errbuf);
951 if (ptr->flags & R_INDEX)
952 set_option (OPTFORCEREDRAWINDEX);
953 if (ptr->flags & R_PAGER)
954 set_option (OPTFORCEREDRAWPAGER);
955 if (ptr->flags & R_RESORT_SUB)
956 set_option (OPTSORTSUBTHREADS);
957 if (ptr->flags & R_RESORT)
958 set_option (OPTNEEDRESORT);
959 if (ptr->flags & R_RESORT_INIT)
960 set_option (OPTRESORTINIT);
961 if (ptr->flags & R_TREE)
962 set_option (OPTREDRAWTREE);
965 static int check_num (const char* option, unsigned long p,
966 char* errbuf, ssize_t errlen) {
969 snprintf (errbuf, errlen, _("'%d' is invalid for $%s"), (int) p, option);
975 static int check_history (const char* option __attribute__ ((unused)), unsigned long p,
976 char* errbuf, ssize_t errlen) {
977 if (!check_num ("history", p, errbuf, errlen))
979 mutt_init_history ();
983 static int check_special (const char* name, unsigned long val,
984 char* errbuf, ssize_t errlen) {
987 for (i = 0; SpecialVars[i].name; i++) {
988 if (m_strcmp(SpecialVars[i].name, name) == 0) {
989 return (SpecialVars[i].check (SpecialVars[i].name,
990 val, errbuf, errlen));
996 static const struct mapping_t* get_sortmap (struct option_t* option) {
997 const struct mapping_t* map = NULL;
999 switch (option->type & DT_SUBTYPE_MASK) {
1001 map = SortAliasMethods;
1003 case DT_SORT_BROWSER:
1004 map = SortBrowserMethods;
1007 map = SortKeyMethods;
1010 map = SortAuxMethods;
1019 #define CHECK_PAGER \
1020 if ((CurrentMenu == MENU_PAGER) && \
1021 (!option || (option->flags & R_RESORT))) \
1023 snprintf (err->data, err->dsize, \
1024 _("Not available in this menu.")); \
1028 static int parse_set (BUFFER * tmp, BUFFER * s, unsigned long data,
1031 int query, unset, inv, reset, r = 0;
1032 struct option_t* option = NULL;
1034 while (MoreArgs (s)) {
1035 /* reset state variables */
1037 unset = data & M_SET_UNSET;
1038 inv = data & M_SET_INV;
1039 reset = data & M_SET_RESET;
1041 if (*s->dptr == '?') {
1045 else if (m_strncmp("no", s->dptr, 2) == 0) {
1049 else if (m_strncmp("inv", s->dptr, 3) == 0) {
1053 else if (*s->dptr == '&') {
1058 /* get the variable name */
1059 mutt_extract_token (tmp, s, M_TOKEN_EQUAL);
1060 option = hash_find(ConfigOptions, tmp->data);
1061 if (!option && !(reset && m_strcmp("all", tmp->data) == 0)) {
1062 snprintf (err->data, err->dsize, _("%s: unknown variable"), tmp->data);
1065 s->dptr = vskipspaces(s->dptr);
1068 if (query || unset || inv) {
1069 snprintf (err->data, err->dsize, _("prefix is illegal with reset"));
1073 if (s && *s->dptr == '=') {
1074 snprintf (err->data, err->dsize, _("value is illegal with reset"));
1078 if (!m_strcmp("all", tmp->data)) {
1079 if (CurrentMenu == MENU_PAGER) {
1080 snprintf (err->data, err->dsize, _("Not available in this menu."));
1083 hash_map (ConfigOptions, mutt_restore_default, 1);
1084 set_option (OPTFORCEREDRAWINDEX);
1085 set_option (OPTFORCEREDRAWPAGER);
1086 set_option (OPTSORTSUBTHREADS);
1087 set_option (OPTNEEDRESORT);
1088 set_option (OPTRESORTINIT);
1089 set_option (OPTREDRAWTREE);
1093 mutt_restore_default (NULL, option, 1);
1096 else if (DTYPE (option->type) == DT_BOOL) {
1097 /* XXX this currently ignores the function table
1098 * as we don't get invert and stuff into it */
1099 if (s && *s->dptr == '=') {
1100 if (unset || inv || query) {
1101 snprintf (err->data, err->dsize, "Usage: set variable=yes|no");
1106 mutt_extract_token (tmp, s, 0);
1107 if (ascii_strcasecmp ("yes", tmp->data) == 0)
1109 else if (ascii_strcasecmp ("no", tmp->data) == 0)
1112 snprintf (err->data, err->dsize, "Usage: set variable=yes|no");
1118 bool_to_string (err->data, err->dsize, option);
1124 unset_option (option->data);
1126 toggle_option (option->data);
1128 set_option (option->data);
1130 else if (DTYPE (option->type) == DT_STR ||
1131 DTYPE (option->type) == DT_PATH ||
1132 DTYPE (option->type) == DT_MAGIC ||
1133 DTYPE (option->type) == DT_NUM ||
1134 DTYPE (option->type) == DT_SORT ||
1135 DTYPE (option->type) == DT_RX)
1137 /* XXX maybe we need to get unset into handlers? */
1138 if (DTYPE (option->type) == DT_STR || DTYPE (option->type) == DT_PATH) {
1141 p_delete((void **)(void *)&option->data);
1146 if (query || *s->dptr != '=') {
1147 FuncTable[DTYPE (option->type)].opt_tostr
1148 (err->data, err->dsize, option);
1154 mutt_extract_token (tmp, s, 0);
1155 if (!FuncTable[DTYPE (option->type)].opt_fromstr
1156 (option, tmp->data, err->data, err->dsize))
1159 else if (DTYPE (option->type) == DT_QUAD) {
1162 quad_to_string (err->data, err->dsize, option);
1166 if (*s->dptr == '=') {
1169 mutt_extract_token (tmp, s, 0);
1170 if (ascii_strcasecmp ("yes", tmp->data) == 0)
1171 set_quadoption (option->data, M_YES);
1172 else if (ascii_strcasecmp ("no", tmp->data) == 0)
1173 set_quadoption (option->data, M_NO);
1174 else if (ascii_strcasecmp ("ask-yes", tmp->data) == 0)
1175 set_quadoption (option->data, M_ASKYES);
1176 else if (ascii_strcasecmp ("ask-no", tmp->data) == 0)
1177 set_quadoption (option->data, M_ASKNO);
1179 snprintf (err->data, err->dsize, _("'%s' is invalid for $%s\n"),
1180 tmp->data, option->option);
1187 toggle_quadoption (option->data);
1189 set_quadoption (option->data, M_NO);
1191 set_quadoption (option->data, M_YES);
1195 snprintf (err->data, err->dsize, _("%s: unknown type"),
1201 if (option->flags & R_INDEX)
1202 set_option (OPTFORCEREDRAWINDEX);
1203 if (option->flags & R_PAGER)
1204 set_option (OPTFORCEREDRAWPAGER);
1205 if (option->flags & R_RESORT_SUB)
1206 set_option (OPTSORTSUBTHREADS);
1207 if (option->flags & R_RESORT)
1208 set_option (OPTNEEDRESORT);
1209 if (option->flags & R_RESORT_INIT)
1210 set_option (OPTRESORTINIT);
1211 if (option->flags & R_TREE)
1212 set_option (OPTREDRAWTREE);
1219 /* reads the specified initialization file. returns -1 if errors were found
1220 so that we can pause to let the user know... */
1221 static int source_rc (const char *rcfile, BUFFER * err)
1224 int line = 0, rc = 0, conv = 0;
1226 char *linebuf = NULL;
1227 char *currentline = NULL;
1231 if ((f = mutt_open_read (rcfile, &pid)) == NULL) {
1232 snprintf (err->data, err->dsize, "%s: %s", rcfile, strerror (errno));
1237 while ((linebuf = mutt_read_line(linebuf, &buflen, f, &line)) != NULL) {
1238 conv = ConfigCharset && (*ConfigCharset) && MCharset.charset;
1240 currentline = m_strdup(linebuf);
1243 mutt_convert_string (¤tline, ConfigCharset, MCharset.charset, 0);
1246 currentline = linebuf;
1251 if (mutt_parse_rc_line (currentline, &token, err) == -1) {
1252 mutt_error (_("Error in %s, line %d: %s"), rcfile, line, err->data);
1253 if (--rc < -MAXERRS) {
1255 p_delete(¤tline);
1264 p_delete(¤tline);
1266 p_delete(&token.data);
1270 mutt_wait_filter (pid);
1272 /* the muttrc source keyword */
1273 snprintf (err->data, err->dsize,
1274 rc >= -MAXERRS ? _("source: errors in %s")
1275 : _("source: reading aborted due too many errors in %s"),
1284 static int parse_source (BUFFER * tmp, BUFFER * s,
1285 unsigned long data __attribute__ ((unused)),
1288 char path[_POSIX_PATH_MAX];
1292 if (mutt_extract_token (tmp, s, 0) != 0) {
1293 snprintf (err->data, err->dsize, _("source: error at %s"), s->dptr);
1297 m_strcpy(path, sizeof(path), tmp->data);
1298 mutt_expand_path (path, sizeof(path));
1300 rc += source_rc (path, err);
1302 while (MoreArgs (s));
1304 return ((rc < 0) ? -1 : 0);
1307 /* line command to execute
1309 token scratch buffer to be used by parser. caller should free
1310 token->data when finished. the reason for this variable is
1311 to avoid having to allocate and deallocate a lot of memory
1312 if we are parsing many lines. the caller can pass in the
1313 memory to use, which avoids having to create new space for
1314 every call to this function.
1316 err where to write error messages */
1317 int mutt_parse_rc_line (const char *line, BUFFER * token, BUFFER * err)
1323 expn.data = expn.dptr = line;
1324 expn.dsize = m_strlen(line);
1328 expn.dptr = vskipspaces(expn.dptr);
1329 while (*expn.dptr) {
1330 if (*expn.dptr == '#')
1331 break; /* rest of line is a comment */
1332 if (*expn.dptr == ';') {
1336 mutt_extract_token (token, &expn, 0);
1337 for (i = 0; Commands[i].name; i++) {
1338 if (!m_strcmp(token->data, Commands[i].name)) {
1339 if (Commands[i].func (token, &expn, Commands[i].data, err) != 0)
1344 if (!Commands[i].name) {
1345 snprintf (err->data, err->dsize, _("%s: unknown command"),
1346 NONULL (token->data));
1353 p_delete(&expn.data);
1358 #define NUMVARS (sizeof(MuttVars)/sizeof(MuttVars[0]))
1359 #define NUMCOMMANDS (sizeof(Commands)/sizeof(Commands[0]))
1360 /* initial string that starts completion. No telling how much crap
1361 * the user has typed so far. Allocate LONG_STRING just to be sure! */
1362 char User_typed[LONG_STRING] = { 0 };
1364 int Num_matched = 0; /* Number of matches for completion */
1365 char Completed[STRING] = { 0 }; /* completed string (command or variable) */
1366 const char *Matches[MAX (NUMVARS, NUMCOMMANDS) + 1]; /* all the matches + User_typed */
1368 /* helper function for completion. Changes the dest buffer if
1369 necessary/possible to aid completion.
1370 dest == completion result gets here.
1371 src == candidate for completion.
1372 try == user entered data for completion.
1373 len == length of dest buffer.
1375 static void candidate (char *dest, char *try, const char *src, int len)
1379 if (strstr (src, try) == src) {
1380 Matches[Num_matched++] = src;
1382 m_strcpy(dest, len, src);
1384 for (l = 0; src[l] && src[l] == dest[l]; l++);
1390 int mutt_command_complete (char *buffer, ssize_t len, int pos, int numtabs)
1394 int spaces; /* keep track of the number of leading spaces on the line */
1396 buffer = vskipspaces(buffer);
1397 spaces = buffer - pt;
1399 pt = buffer + pos - spaces;
1400 while ((pt > buffer) && !isspace ((unsigned char) *pt))
1403 if (pt == buffer) { /* complete cmd */
1404 /* first TAB. Collect all the matches */
1407 m_strcpy(User_typed, sizeof(User_typed), pt);
1408 p_clear(Matches, countof(Matches));
1409 p_clear(Completed, countof(Completed));
1410 for (num = 0; Commands[num].name; num++)
1411 candidate (Completed, User_typed, Commands[num].name,
1413 Matches[Num_matched++] = User_typed;
1415 /* All matches are stored. Longest non-ambiguous string is ""
1416 * i.e. dont change 'buffer'. Fake successful return this time */
1417 if (User_typed[0] == 0)
1421 if (Completed[0] == 0 && User_typed[0])
1424 /* Num_matched will _always_ be atleast 1 since the initial
1425 * user-typed string is always stored */
1426 if (numtabs == 1 && Num_matched == 2)
1427 snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
1428 else if (numtabs > 1 && Num_matched > 2)
1429 /* cycle thru all the matches */
1430 snprintf (Completed, sizeof(Completed), "%s",
1431 Matches[(numtabs - 2) % Num_matched]);
1433 /* return the completed command */
1434 m_strcpy(buffer, len - spaces, Completed);
1436 else if (!m_strncmp(buffer, "set", 3)
1437 || !m_strncmp(buffer, "unset", 5)
1438 || !m_strncmp(buffer, "reset", 5)
1439 || !m_strncmp(buffer, "toggle", 6)) { /* complete variables */
1440 const char *prefixes[] = { "no", "inv", "?", "&", NULL };
1443 /* loop through all the possible prefixes (no, inv, ...) */
1444 if (!m_strncmp(buffer, "set", 3)) {
1445 for (num = 0; prefixes[num]; num++) {
1446 if (!m_strncmp(pt, prefixes[num], m_strlen(prefixes[num]))) {
1447 pt += m_strlen(prefixes[num]);
1453 /* first TAB. Collect all the matches */
1456 m_strcpy(User_typed, sizeof(User_typed), pt);
1457 p_clear(Matches, countof(Matches));
1458 p_clear(Completed, countof(Completed));
1459 for (num = 0; MuttVars[num].option; num++)
1460 candidate(Completed, User_typed, MuttVars[num].option,
1462 Matches[Num_matched++] = User_typed;
1464 /* All matches are stored. Longest non-ambiguous string is ""
1465 * i.e. dont change 'buffer'. Fake successful return this time */
1466 if (User_typed[0] == 0)
1470 if (Completed[0] == 0 && User_typed[0])
1473 /* Num_matched will _always_ be atleast 1 since the initial
1474 * user-typed string is always stored */
1475 if (numtabs == 1 && Num_matched == 2)
1476 snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
1477 else if (numtabs > 1 && Num_matched > 2)
1478 /* cycle thru all the matches */
1479 snprintf (Completed, sizeof(Completed), "%s",
1480 Matches[(numtabs - 2) % Num_matched]);
1482 m_strcpy(pt, buffer + len - pt - spaces, Completed);
1484 else if (!m_strncmp(buffer, "exec", 4)) {
1485 struct binding_t *menu = km_get_table (CurrentMenu);
1487 if (!menu && CurrentMenu != MENU_PAGER)
1491 /* first TAB. Collect all the matches */
1494 m_strcpy(User_typed, sizeof(User_typed), pt);
1495 p_clear(Matches, countof(Matches));
1496 p_clear(Completed, countof(Completed));
1497 for (num = 0; menu[num].name; num++)
1498 candidate (Completed, User_typed, menu[num].name, sizeof(Completed));
1499 /* try the generic menu */
1500 if (Completed[0] == 0 && CurrentMenu != MENU_PAGER) {
1502 for (num = 0; menu[num].name; num++)
1503 candidate (Completed, User_typed, menu[num].name,
1506 Matches[Num_matched++] = User_typed;
1508 /* All matches are stored. Longest non-ambiguous string is ""
1509 * i.e. dont change 'buffer'. Fake successful return this time */
1510 if (User_typed[0] == 0)
1514 if (Completed[0] == 0 && User_typed[0])
1517 /* Num_matched will _always_ be atleast 1 since the initial
1518 * user-typed string is always stored */
1519 if (numtabs == 1 && Num_matched == 2)
1520 snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
1521 else if (numtabs > 1 && Num_matched > 2)
1522 /* cycle thru all the matches */
1523 snprintf (Completed, sizeof(Completed), "%s",
1524 Matches[(numtabs - 2) % Num_matched]);
1526 m_strcpy(pt, buffer + len - pt - spaces, Completed);
1534 int mutt_var_value_complete (char *buffer, ssize_t len, int pos)
1536 char var[STRING], *pt = buffer;
1538 struct option_t* option = NULL;
1543 buffer = vskipspaces(buffer);
1544 spaces = buffer - pt;
1546 pt = buffer + pos - spaces;
1547 while ((pt > buffer) && !isspace ((unsigned char) *pt))
1549 pt++; /* move past the space */
1550 if (*pt == '=') /* abort if no var before the '=' */
1553 if (m_strncmp(buffer, "set", 3) == 0) {
1554 m_strcpy(var, sizeof(var), pt);
1555 /* ignore the trailing '=' when comparing */
1556 var[m_strlen(var) - 1] = 0;
1557 if (!(option = hash_find (ConfigOptions, var)))
1558 return 0; /* no such variable. */
1560 char tmp[LONG_STRING], tmp2[LONG_STRING];
1562 ssize_t dlen = buffer + len - pt - spaces;
1563 const char *vals[] = { "no", "yes", "ask-no", "ask-yes" };
1567 if ((DTYPE (option->type) == DT_STR) ||
1568 (DTYPE (option->type) == DT_PATH) ||
1569 (DTYPE (option->type) == DT_RX)) {
1570 m_strcpy(tmp, sizeof(tmp), NONULL(*((char **)option->data)));
1571 if (DTYPE (option->type) == DT_PATH)
1572 mutt_pretty_mailbox (tmp);
1574 else if (DTYPE (option->type) == DT_QUAD)
1575 m_strcpy(tmp, sizeof(tmp), vals[quadoption(option->data)]);
1576 else if (DTYPE (option->type) == DT_NUM)
1577 snprintf (tmp, sizeof(tmp), "%d", (*((short *) option->data)));
1578 else if (DTYPE (option->type) == DT_SORT) {
1579 const struct mapping_t *map;
1582 switch (option->type & DT_SUBTYPE_MASK) {
1584 map = SortAliasMethods;
1586 case DT_SORT_BROWSER:
1587 map = SortBrowserMethods;
1590 map = SortKeyMethods;
1596 p = mutt_getnamebyvalue(*((short *) option->data) & SORT_MASK, map);
1597 snprintf(tmp, sizeof(tmp), "%s%s%s",
1598 (*((short *)option->data) & SORT_REVERSE) ? "reverse-" : "",
1599 (*((short *)option->data) & SORT_LAST) ? "last-" : "", p);
1601 else if (DTYPE (option->type) == DT_MAGIC) {
1603 switch (DefaultMagic) {
1619 m_strcpy(tmp, sizeof(tmp), p);
1621 else if (DTYPE (option->type) == DT_BOOL)
1622 m_strcpy(tmp, sizeof(tmp), option(option->data) ? "yes" : "no");
1626 for (s = tmp, d = tmp2; *s && (d - tmp2) < ssizeof(tmp2) - 2;) {
1627 if (*s == '\\' || *s == '"')
1633 m_strcpy(tmp, sizeof(tmp), pt);
1634 snprintf (pt, dlen, "%s\"%s\"", tmp, tmp2);
1642 /* Implement the -Q command line flag */
1643 int mutt_query_variables (string_list_t * queries)
1647 char errbuff[STRING];
1648 char command[STRING];
1656 err.dsize = sizeof(errbuff);
1658 for (p = queries; p; p = p->next) {
1659 snprintf (command, sizeof(command), "set ?%s\n", p->data);
1660 if (mutt_parse_rc_line (command, &token, &err) == -1) {
1661 fprintf (stderr, "%s\n", err.data);
1662 p_delete(&token.data);
1665 printf ("%s\n", err.data);
1668 p_delete(&token.data);
1672 static int mutt_execute_commands (string_list_t * p)
1675 char errstr[STRING];
1679 err.dsize = sizeof(errstr);
1681 for (; p; p = p->next) {
1682 if (mutt_parse_rc_line (p->data, &token, &err) != 0) {
1683 fprintf (stderr, _("Error in command line: %s\n"), err.data);
1684 p_delete(&token.data);
1688 p_delete(&token.data);
1692 void mutt_init (int skip_sys_rc, string_list_t * commands)
1696 char buffer[STRING], error[STRING];
1697 int default_rc = 0, need_pause = 0;
1703 err.dsize = sizeof(error);
1705 ConfigOptions = hash_new (sizeof(MuttVars) * 2, 0);
1706 for (i = 0; MuttVars[i].option; i++) {
1707 hash_insert (ConfigOptions, MuttVars[i].option, &MuttVars[i]);
1711 * XXX - use something even more difficult to predict?
1713 snprintf (AttachmentMarker, sizeof(AttachmentMarker),
1714 "\033]9;%ld\a", (long) time (NULL));
1717 /* Get some information about the user */
1718 if ((pw = getpwuid (getuid ()))) {
1720 mutt_gecos_name(rnbuf, sizeof(rnbuf), pw, MCore.gecos_mask);
1721 Realname = m_strdup(rnbuf);
1729 if ((f = safe_fopen (SYSCONFDIR "/nntpserver", "r"))) {
1731 fgets (buffer, sizeof(buffer), f);
1732 p = vskipspaces(buffer);
1734 while (*q && !isspace(*q))
1737 NewsServer = m_strdup(p);
1741 if ((p = getenv ("NNTPSERVER")))
1742 NewsServer = m_strdup(p);
1745 if ((p = getenv("MAIL") ?: getenv("MAILDIR"))) {
1746 Spoolfile = m_strdup(p);
1749 mutt_concat_path(buffer, sizeof(buffer), NONULL(MCore.homedir), MAILPATH);
1751 mutt_concat_path(buffer, sizeof(buffer), MAILPATH, NONULL(MCore.username));
1753 Spoolfile = m_strdup(buffer);
1756 if ((p = getenv ("REPLYTO")) != NULL) {
1759 snprintf (buffer, sizeof(buffer), "Reply-To: %s", p);
1762 buf.data = buf.dptr = buffer;
1763 buf.dsize = m_strlen(buffer);
1766 parse_my_hdr (&token, &buf, 0, &err);
1767 p_delete(&token.data);
1770 /* Set standard defaults */
1771 hash_map (ConfigOptions, mutt_set_default, 0);
1772 hash_map (ConfigOptions, mutt_restore_default, 0);
1774 CurrentMenu = MENU_MAIN;
1777 /* Unset suspend by default if we're the session leader */
1778 if (getsid (0) == getpid ())
1779 unset_option (OPTSUSPEND);
1782 mutt_init_history ();
1785 snprintf (buffer, sizeof(buffer), "%s/.madmuttrc", NONULL(MCore.homedir));
1786 if (access (buffer, F_OK) == -1)
1787 snprintf (buffer, sizeof(buffer), "%s/.madmutt/madmuttrc",
1788 NONULL(MCore.homedir));
1791 Muttrc = m_strdup(buffer);
1794 m_strcpy(buffer, sizeof(buffer), Muttrc);
1796 mutt_expand_path (buffer, sizeof(buffer));
1797 Muttrc = m_strdup(buffer);
1800 /* Process the global rc file if it exists and the user hasn't explicity
1801 requested not to via "-n". */
1803 snprintf (buffer, sizeof(buffer), "%s/Madmuttrc-%s", SYSCONFDIR,
1805 if (access (buffer, F_OK) == -1)
1806 snprintf (buffer, sizeof(buffer), "%s/Madmuttrc", SYSCONFDIR);
1807 if (access (buffer, F_OK) == -1)
1808 snprintf (buffer, sizeof(buffer), "%s/Madmuttrc-%s", PKGDATADIR,
1810 if (access (buffer, F_OK) == -1)
1811 snprintf (buffer, sizeof(buffer), "%s/Madmuttrc", PKGDATADIR);
1812 if (access (buffer, F_OK) != -1) {
1813 if (source_rc (buffer, &err) != 0) {
1814 fputs (err.data, stderr);
1815 fputc ('\n', stderr);
1821 /* Read the user's initialization file. */
1822 if (access (Muttrc, F_OK) != -1) {
1823 if (!option (OPTNOCURSES))
1825 if (source_rc (Muttrc, &err) != 0) {
1826 fputs (err.data, stderr);
1827 fputc ('\n', stderr);
1831 else if (!default_rc) {
1832 /* file specified by -F does not exist */
1833 snprintf (buffer, sizeof(buffer), "%s: %s", Muttrc, strerror (errno));
1834 mutt_endwin (buffer);
1839 snprintf(buffer, sizeof(buffer), "%s/.madmutt.lua", NONULL(MCore.homedir));
1840 if (access(buffer, F_OK) < 0)
1841 snprintf(buffer, sizeof(buffer), "%s/.madmutt/cfg.lua", NONULL(MCore.homedir));
1842 if (!access(buffer, F_OK)) {
1843 need_pause = luaM_wrap(mutt_error, luaM_dofile(buffer));
1847 if (mutt_execute_commands (commands) != 0)
1850 /* warn about synonym variables */
1854 fprintf (stderr, _("Warning: the following synonym variables were found:\n"));
1856 for (syn = Synonyms; syn; syn = syn->next) {
1857 fprintf(stderr, "$%s ($%s should be used) (%s:%d)\n",
1858 syn->o ? NONULL(syn->o->option) : "",
1859 syn->n ? NONULL(syn->n->option) : "",
1860 NONULL(syn->f), syn->l);
1862 fprintf (stderr, _("Warning: synonym variables are scheduled"
1863 " for removal.\n"));
1864 syn_list_wipe(&Synonyms);
1868 if (need_pause && !option (OPTNOCURSES)) {
1869 if (mutt_any_key_to_continue (NULL) == -1)
1874 int mutt_get_hook_type (const char *name)
1876 struct command_t *c;
1878 for (c = Commands; c->name; c++)
1879 if (c->func == mutt_parse_hook && ascii_strcasecmp (c->name, name) == 0)
1884 /* dump out the value of all the variables we have */
1885 int mutt_dump_variables (int full) {
1888 /* get all non-synonyms into list... */
1889 for (i = 0; MuttVars[i].option; i++) {
1890 struct option_t *option = MuttVars + i;
1891 char buf[LONG_STRING];
1894 mutt_option_value(option->option, buf, sizeof(buf));
1895 if (!m_strcmp(buf, option->init))
1900 FuncTable[DTYPE(option->type)].opt_tostr(buf, sizeof(buf), option);
1901 printf ("%s\n", buf);
1904 printf ("\n# vi""m:set ft=muttrc:\n");