const, style
[apps/madmutt.git] / init.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
4  *
5  * Parts were written/modified by:
6  * Rocco Rutte <pdmef@cs.tu-berlin.de>
7  *
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.
11  */
12
13 #include <lib-lib/lib-lib.h>
14
15 #include <lib-lua/lib-lua.h>
16 #include <lib-sys/unix.h>
17 #include <lib-ui/curses.h>
18 #include <lib-ui/history.h>
19 #include <lib-mx/mx.h>
20
21 #include "mutt.h"
22 #include "keymap.h"
23 #include "crypt.h"
24 #include "charset.h"
25 #include "thread.h"
26 #include "mutt_idna.h"
27 #include "alias.h"
28 #include "init.h"
29
30 /*
31  * prototypes
32  */
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);
37
38 static hash_t *ConfigOptions = NULL;
39
40 /* for synonym warning reports: synonym found during parsing */
41 typedef struct syn_t {
42   struct syn_t *next;
43   char* f;              /* file */
44   int l;                /* line */
45   struct option_t* n;   /* new */
46   struct option_t* o;   /* old */
47 } syn_t;
48
49 DO_INIT(syn_t, syn);
50 static void syn_wipe(syn_t *syn) {
51     p_delete(&syn->f);
52 }
53 DO_NEW(syn_t, syn);
54 DO_DELETE(syn_t, syn);
55 DO_SLIST(syn_t, syn, syn_delete);
56
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;
63
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);
70
71 /* use this to check only */
72 static int check_special (const char* option, unsigned long val,
73                           char* errbuf, ssize_t errlen);
74
75 /* variable <-> sanity check function mappings
76  * when changing these, make sure the proper _from_string handler
77  * does this checking!
78  */
79 static struct {
80   const char* name;
81   int (*check) (const char* option, unsigned long val,
82                 char* errbuf, ssize_t errlen);
83 } SpecialVars[] = {
84 #ifdef USE_LIBESMTP
85   { "smtp_use_tls",             send_smtp_check_usetls },
86 #endif
87   { "history",                  check_history },
88   { "pager_index_lines",        check_num },
89   /* last */
90   { NULL,         NULL }
91 };
92
93 static void bool_to_string (char* dst, ssize_t dstlen,
94                             struct option_t* option) {
95   snprintf (dst, dstlen, "%s=%s", option->option,
96             option (option->data) ? "yes" : "no");
97 }
98
99 static int bool_from_string (struct option_t* dst, const char* val,
100                              char* errbuf __attribute__ ((unused)),
101                              ssize_t errlen __attribute__ ((unused))) {
102   int flag = -1;
103
104   if (!dst)
105     return (0);
106   if (ascii_strncasecmp (val, "yes", 3) == 0)
107     flag = 1;
108   else if (ascii_strncasecmp (val, "no", 2) == 0)
109     flag = 0;
110
111   if (flag < 0)
112     return (0);
113   if (flag)
114     set_option (dst->data);
115   else
116     unset_option (dst->data);
117   return (1);
118 }
119
120 static void num_to_string (char* dst, ssize_t dstlen,
121                            struct option_t* option) {
122   /* XXX puke */
123   const char* fmt = (m_strcmp(option->option, "umask") == 0) ?
124                     "%s=%04o" : "%s=%d";
125   snprintf (dst, dstlen, fmt, option->option,
126             *((short*) option->data));
127 }
128
129 static int num_from_string (struct option_t* dst, const char* val,
130                             char* errbuf, ssize_t errlen) {
131   int num = 0, old = 0;
132   char* t = NULL;
133
134   if (!dst)
135     return (0);
136
137   num = strtol (val, &t, 0);
138
139   if (m_strisempty(val) || *t || (short) num != num) {
140     if (errbuf) {
141       snprintf (errbuf, errlen, _("'%s' is invalid for $%s"),
142                 val, dst->option);
143     }
144     return (0);
145   }
146
147   /* just temporarily accept new val so that check_special for
148    * $history already has it when doing history's init() */
149   old = *((short*) dst->data);
150   *((short*) dst->data) = (short) num;
151
152   if (!check_special (dst->option, (unsigned long) num, errbuf, errlen)) {
153     *((short*) dst->data) = old;
154     return (0);
155   }
156
157   return (1);
158 }
159
160 static void str_to_string (char* dst, ssize_t dstlen,
161                            struct option_t* option) {
162   snprintf (dst, dstlen, "%s=\"%s\"", option->option,
163             NONULL (*((char**) option->data)));
164 }
165
166 static int path_from_string (struct option_t* dst, const char* val,
167                              char* errbuf __attribute__ ((unused)), ssize_t errlen __attribute__ ((unused))) {
168   char path[_POSIX_PATH_MAX];
169
170   if (!dst)
171     return (0);
172
173   if (m_strisempty(val)) {
174     p_delete((char**) dst->data);
175     return (1);
176   }
177
178   path[0] = '\0';
179   m_strcpy(path, sizeof(path), val);
180   mutt_expand_path (path, sizeof(path));
181   m_strreplace((char **) dst->data, path);
182   return (1);
183 }
184
185 static int str_from_string (struct option_t* dst, const char* val,
186                             char* errbuf, ssize_t errlen) {
187   if (!dst)
188     return (0);
189
190   if (!check_special (dst->option, (unsigned long) val, errbuf, errlen))
191     return (0);
192
193   m_strreplace((char**) dst->data, val);
194   return (1);
195 }
196
197 static void quad_to_string (char* dst, ssize_t dstlen,
198                             struct option_t* option) {
199   const char *vals[] = { "no", "yes", "ask-no", "ask-yes" };
200   snprintf (dst, dstlen, "%s=%s", option->option,
201             vals[quadoption (option->data)]);
202 }
203
204 static int quad_from_string (struct option_t* dst, const char* val,
205                              char* errbuf __attribute__ ((unused)), ssize_t errlen __attribute__ ((unused))) {
206   int flag = -1;
207
208   if (!dst)
209     return (0);
210   if (ascii_strncasecmp (val, "yes", 3) == 0)
211     flag = M_YES;
212   else if (ascii_strncasecmp (val, "no", 2) == 0)
213     flag = M_NO;
214   else if (ascii_strncasecmp (val, "ask-yes", 7) == 0)
215     flag = M_ASKYES;
216   else if (ascii_strncasecmp (val, "ask-no", 6) == 0)
217     flag = M_ASKNO;
218
219   if (flag < 0)
220     return (0);
221
222   set_quadoption (dst->data, flag);
223   return (1);
224 }
225
226 static void sort_to_string (char* dst, ssize_t dstlen,
227                             struct option_t* option) {
228   const struct mapping_t *map = get_sortmap (option);
229   const char *p = NULL;
230
231   if (!map) {
232     snprintf (dst, sizeof(dst), "%s=unknown", option->option);
233     return;
234   }
235
236   p = mutt_getnamebyvalue(*((short *)option->data) & SORT_MASK, map);
237
238   snprintf (dst, dstlen, "%s=%s%s%s", option->option,
239             (*((short *) option->data) & SORT_REVERSE) ?
240             "reverse-" : "",
241             (*((short *) option->data) & SORT_LAST) ? "last-" :
242             "", NONULL (p));
243 }
244
245 static int sort_from_string (struct option_t* dst, const char* val,
246                              char* errbuf, ssize_t errlen) {
247   const struct mapping_t *map = NULL;
248   if (!(map = get_sortmap (dst))) {
249     if (errbuf)
250       snprintf (errbuf, errlen, _("%s: Unknown type."),
251                 dst->option);
252     return (0);
253   }
254   if (parse_sort (dst, val, map, errbuf, errlen) == -1)
255     return (0);
256   return (1);
257 }
258
259 static void rx_to_string (char* dst, ssize_t dstlen,
260                           struct option_t* option) {
261   rx_t* p = (rx_t*) option->data;
262   snprintf (dst, dstlen, "%s=\"%s\"", option->option,
263             NONULL (p->pattern));
264 }
265
266 static int rx_from_string (struct option_t* dst, const char* val,
267                            char* errbuf, ssize_t errlen) {
268   rx_t* p = NULL;
269   regex_t* rx = NULL;
270   int flags = 0, e = 0, neg = 0;
271   char* s = NULL;
272
273   if (!dst)
274     return (0);
275
276   if (option (OPTATTACHMSG) && !m_strcmp(dst->option, "reply_regexp")) {
277     if (errbuf)
278       snprintf (errbuf, errlen,
279                 "Operation not permitted when in attach-message mode.");
280     return (0);
281   }
282
283   if (!((rx_t*) dst->data))
284     *((rx_t**) dst->data) = p_new(rx_t, 1);
285
286   p = (rx_t*) dst->data;
287
288   /* something to do? */
289   if (m_strisempty(val) || (p->pattern && m_strcmp(p->pattern, val) == 0))
290     return (1);
291
292   if (m_strcmp(dst->option, "mask") != 0)
293     flags |= mutt_which_case (val);
294
295   s = (char*) val;
296   if (m_strcmp(dst->option, "mask") == 0 && *s == '!') {
297     neg = 1;
298     s++;
299   }
300
301   rx = p_new(regex_t, 1);
302
303   if ((e = REGCOMP (rx, s, flags)) != 0) {
304     regerror (e, rx, errbuf, errlen);
305     regfree (rx);
306     p_delete(&rx);
307     return (0);
308   }
309
310   if (p->rx) {
311     regfree (p->rx);
312     p_delete(&p->rx);
313   }
314
315   m_strreplace(&p->pattern, val);
316   p->rx = rx;
317   p->neg = neg;
318
319   if (m_strcmp(dst->option, "reply_regexp") == 0)
320     mutt_adjust_all_subjects ();
321
322   return (1);
323 }
324
325 static void magic_to_string (char* dst, ssize_t dstlen,
326                              struct option_t* option) {
327   const char* s = NULL;
328   switch (option->data) {
329     case M_MBOX:    s = "mbox"; break;
330     case M_MH:      s = "MH"; break;
331     case M_MAILDIR: s = "Maildir"; break;
332     default:        s = "unknown"; break;
333   }
334   snprintf (dst, dstlen, "%s=%s", option->option, s);
335 }
336
337 static int magic_from_string (struct option_t* dst, const char* val,
338                               char *errbuf, ssize_t errlen)
339 {
340   int flag = -1;
341
342   if (!dst || m_strisempty(val))
343     return (0);
344   if (ascii_strncasecmp (val, "mbox", 4) == 0)
345     flag = M_MBOX;
346   else if (ascii_strncasecmp (val, "mh", 2) == 0)
347     flag = M_MH;
348   else if (ascii_strncasecmp (val, "maildir", 7) == 0)
349     flag = M_MAILDIR;
350
351   if (flag < 0)
352     return (0);
353
354   *((short*) dst->data) = flag;
355   return (1);
356
357 }
358
359 static struct {
360   unsigned short type;
361   void (*opt_tostr) (char* dst, ssize_t dstlen, struct option_t* option);
362   int (*opt_fromstr) (struct option_t* dst, const char* val,
363                           char* errbuf, ssize_t errlen);
364 } FuncTable[] = {
365   { 0,          NULL,             NULL }, /* there's no DT_ type with 0 */
366   { DT_BOOL,    bool_to_string,   bool_from_string },
367   { DT_NUM,     num_to_string,    num_from_string },
368   { DT_STR,     str_to_string,    str_from_string },
369   { DT_PATH,    str_to_string,    path_from_string },
370   { DT_QUAD,    quad_to_string,   quad_from_string },
371   { DT_SORT,    sort_to_string,   sort_from_string },
372   { DT_RX,      rx_to_string,     rx_from_string },
373   { DT_MAGIC,   magic_to_string,  magic_from_string },
374 };
375
376
377 int mutt_option_value (const char* val, char* dst, ssize_t dstlen) {
378   struct option_t* option = NULL;
379   char* tmp = NULL, *t = NULL;
380   ssize_t l = 0;
381
382   if (!(option = hash_find (ConfigOptions, val))) {
383     *dst = '\0';
384     return (0);
385   }
386   tmp = p_new(char, dstlen+1);
387   FuncTable[DTYPE(option->type)].opt_tostr (tmp, dstlen, option);
388
389   /* as we get things of type $var=value and don't want to bloat the
390    * above "just" for expansion, we do the stripping here */
391   t = strchr (tmp, '=');
392   t++;
393   l = m_strlen(t);
394   if (l >= 2) {
395     if (t[l-1] == '"' && *t == '"') {
396       t[l-1] = '\0';
397       t++;
398     }
399   }
400   memcpy (dst, t, l+1);
401   p_delete(&tmp);
402
403   return (1);
404 }
405
406 static void toggle_quadoption (int opt)
407 {
408   int n = opt / 4;
409   int b = (opt % 4) * 2;
410
411   QuadOptions[n] ^= (1 << b);
412 }
413
414 void set_quadoption (int opt, int flag)
415 {
416   int n = opt / 4;
417   int b = (opt % 4) * 2;
418
419   QuadOptions[n] &= ~(0x3 << b);
420   QuadOptions[n] |= (flag & 0x3) << b;
421 }
422
423 int quadoption (int opt)
424 {
425   int n = opt / 4;
426   int b = (opt % 4) * 2;
427
428   return (QuadOptions[n] >> b) & 0x3;
429 }
430
431 int query_quadoption2(int v, const char *prompt)
432 {
433   switch (v) {
434   case M_YES:
435   case M_NO:
436     return (v);
437
438   default:
439     v = mutt_yesorno(prompt, (v == M_ASKYES));
440     CLEARLINE (LINES - 1);
441     return (v);
442   }
443 }
444
445 int query_quadoption (int opt, const char *prompt)
446 {
447   int v = quadoption (opt);
448
449   switch (v) {
450   case M_YES:
451   case M_NO:
452     return (v);
453
454   default:
455     v = mutt_yesorno (prompt, (v == M_ASKYES));
456     CLEARLINE (LINES - 1);
457     return (v);
458   }
459
460   /* not reached */
461 }
462
463 /* always wise to do what someone else did before */
464 static void _attachments_clean (void) {
465   int i;
466   if (Context && Context->msgcount) {
467     for (i = 0; i < Context->msgcount; i++)
468       Context->hdrs[i]->attach_valid = 0;
469   }
470 }
471
472 static int parse_attach_list (BUFFER *buf, BUFFER *s, string_list_t **ldata,
473                               BUFFER *err __attribute__ ((unused))) {
474   ATTACH_MATCH *a;
475   string_list_t *listp, *lastp;
476   char *p;
477   char *tmpminor;
478   int len;
479
480   /* Find the last item in the list that data points to. */
481   lastp = NULL;
482   for (listp = *ldata; listp; listp = listp->next) {
483     a = (ATTACH_MATCH *)listp->data;
484     lastp = listp;
485   }
486
487   do {
488     mutt_extract_token (buf, s, 0);
489
490     if (!buf->data || *buf->data == '\0')
491       continue;
492
493     a = p_new(ATTACH_MATCH, 1);
494
495     /* some cheap hacks that I expect to remove */
496     if (!m_strcasecmp(buf->data, "any"))
497       a->major = m_strdup("*/.*");
498     else if (!m_strcasecmp(buf->data, "none"))
499       a->major = m_strdup("cheap_hack/this_should_never_match");
500     else
501       a->major = m_strdup(buf->data);
502
503     if ((p = strchr(a->major, '/'))) {
504       *p = '\0';
505       ++p;
506       a->minor = p;
507     } else {
508       a->minor = "unknown";
509     }
510
511     len = m_strlen(a->minor);
512     tmpminor = p_new(char, len + 3);
513     m_strcpy(&tmpminor[1], len + 3, a->minor);
514     tmpminor[0] = '^';
515     tmpminor[len+1] = '$';
516     tmpminor[len+2] = '\0';
517
518     a->major_int = mutt_check_mime_type(a->major);
519     regcomp(&a->minor_rx, tmpminor, REG_ICASE|REG_EXTENDED);
520
521     p_delete(&tmpminor);
522
523     listp = p_new(string_list_t, 1);
524     listp->data = (char *)a;
525     listp->next = NULL;
526     if (lastp) {
527       lastp->next = listp;
528     } else {
529       *ldata = listp;
530     }
531     lastp = listp;
532   }
533   while (MoreArgs (s));
534
535   _attachments_clean();
536   return 0;
537 }
538
539 static int parse_unattach_list (BUFFER *buf, BUFFER *s, string_list_t **ldata,
540                                 BUFFER *err __attribute__ ((unused))) {
541   ATTACH_MATCH *a;
542   string_list_t *lp, *lastp, *newlp;
543   char *tmp;
544   int major;
545   char *minor;
546
547   do {
548     mutt_extract_token (buf, s, 0);
549
550     if (!m_strcasecmp(buf->data, "any"))
551       tmp = m_strdup("*/.*");
552     else if (!m_strcasecmp(buf->data, "none"))
553       tmp = m_strdup("cheap_hack/this_should_never_match");
554     else
555       tmp = m_strdup(buf->data);
556
557     if ((minor = strchr(tmp, '/'))) {
558       *minor = '\0';
559       ++minor;
560     } else {
561       minor = m_strdup("unknown");
562     }
563     major = mutt_check_mime_type(tmp);
564
565     /* We must do our own walk here because string_list_remove() will only
566      * remove the string_list_t->data, not anything pointed to by the string_list_t->data. */
567     lastp = NULL;
568     for(lp = *ldata; lp; ) {
569       a = (ATTACH_MATCH *)lp->data;
570       if (a->major_int == major && !m_strcasecmp(minor, a->minor)) {
571         regfree(&a->minor_rx);
572         p_delete(&a->major);
573
574         /* Relink backward */
575         if (lastp)
576           lastp->next = lp->next;
577         else
578           *ldata = lp->next;
579
580         newlp = lp->next;
581         p_delete(&lp->data); /* same as a */
582         p_delete(&lp);
583         lp = newlp;
584         continue;
585       }
586
587       lastp = lp;
588       lp = lp->next;
589     }
590   }
591   while (MoreArgs (s));
592
593   p_delete(&tmp);
594   _attachments_clean();
595   return 0;
596 }
597
598 static int print_attach_list (string_list_t *lp, char op, const char *name) {
599   while (lp) {
600     printf("attachments %c%s %s/%s\n", op, name,
601            ((ATTACH_MATCH *)lp->data)->major,
602            ((ATTACH_MATCH *)lp->data)->minor);
603     lp = lp->next;
604   }
605
606   return 0;
607 }
608
609 static int parse_attachments (BUFFER *buf, BUFFER *s,
610                               unsigned long data __attribute__ ((unused)),
611                               BUFFER *err) {
612   char op, *category;
613   string_list_t **listp;
614
615   mutt_extract_token(buf, s, 0);
616   if (!buf->data || *buf->data == '\0') {
617     m_strcpy(err->data, err->dsize, _("attachments: no disposition"));
618     return -1;
619   }
620
621   category = buf->data;
622   op = *category++;
623
624   if (op == '?') {
625     mutt_endwin (NULL);
626     fflush (stdout);
627     printf("\nCurrent attachments settings:\n\n");
628     print_attach_list(AttachAllow, '+', "A");
629     print_attach_list(AttachExclude, '-', "A");
630     print_attach_list(InlineAllow, '+', "I");
631     print_attach_list(InlineExclude, '-', "I");
632     set_option (OPTFORCEREDRAWINDEX);
633     set_option (OPTFORCEREDRAWPAGER);
634     mutt_any_key_to_continue (NULL);
635     return 0;
636   }
637
638   if (op != '+' && op != '-') {
639     op = '+';
640     category--;
641   }
642   if (!m_strncasecmp(category, "attachment", strlen(category))) {
643     if (op == '+')
644       listp = &AttachAllow;
645     else
646       listp = &AttachExclude;
647   }
648   else if (!m_strncasecmp(category, "inline", strlen(category))) {
649     if (op == '+')
650       listp = &InlineAllow;
651     else
652       listp = &InlineExclude;
653   } else {
654     m_strcpy(err->data, err->dsize, _("attachments: invalid disposition"));
655     return -1;
656   }
657
658   return parse_attach_list(buf, s, listp, err);
659 }
660
661 static int parse_unattachments (BUFFER *buf, BUFFER *s, unsigned long data __attribute__ ((unused)), BUFFER *err) {
662   char op, *p;
663   string_list_t **listp;
664
665   mutt_extract_token(buf, s, 0);
666   if (!buf->data || *buf->data == '\0') {
667     m_strcpy(err->data, err->dsize, _("unattachments: no disposition"));
668     return -1;
669   }
670
671   p = buf->data;
672   op = *p++;
673   if (op != '+' && op != '-') {
674     op = '+';
675     p--;
676   }
677   if (!m_strncasecmp(p, "attachment", strlen(p))) {
678     if (op == '+')
679       listp = &AttachAllow;
680     else
681       listp = &AttachExclude;
682   }
683   else if (!m_strncasecmp(p, "inline", strlen(p))) {
684     if (op == '+')
685       listp = &InlineAllow;
686     else
687       listp = &InlineExclude;
688   }
689   else {
690     m_strcpy(err->data, err->dsize, _("unattachments: invalid disposition"));
691     return -1;
692   }
693
694   return parse_unattach_list(buf, s, listp, err);
695 }
696
697 static int parse_unalias (BUFFER * buf, BUFFER * s,
698                           unsigned long data __attribute__ ((unused)),
699                           BUFFER * err __attribute__ ((unused)))
700 {
701     alias_t *tmp, **last;
702
703     do {
704         mutt_extract_token (buf, s, 0);
705
706         if (!m_strcmp("*", buf->data) == 0) {
707             if (CurrentMenu == MENU_ALIAS) {
708                 for (tmp = Aliases; tmp; tmp = tmp->next)
709                     tmp->del = 1;
710                 set_option(OPTFORCEREDRAWINDEX);
711             } else {
712                 alias_list_wipe(&Aliases);
713             }
714             break;
715         }
716
717         last = &Aliases;
718         for (last = &Aliases; *last; last = &(*last)->next) {
719             if (!m_strcasecmp(buf->data, (*last)->name)) {
720                 if (CurrentMenu == MENU_ALIAS) {
721                     (*last)->del = 1;
722                     set_option (OPTFORCEREDRAWINDEX);
723                 } else {
724                     tmp = alias_list_pop(last);
725                     alias_delete(&tmp);
726                 }
727                 break;
728             }
729         }
730     } while (MoreArgs(s));
731
732     return 0;
733 }
734
735 static int parse_alias (BUFFER * buf, BUFFER * s,
736                         unsigned long data __attribute__ ((unused)),
737                         BUFFER * err)
738 {
739     alias_t **last;
740     char *estr = NULL;
741
742     if (!MoreArgs (s)) {
743         m_strcpy(err->data, err->dsize, _("alias: no address"));
744         return (-1);
745     }
746
747     mutt_extract_token (buf, s, 0);
748
749     /* check to see if an alias with this name already exists */
750     for (last = &Aliases; *last; last = &(*last)->next) {
751         if (!m_strcasecmp((*last)->name, buf->data))
752             break;
753     }
754
755     if (!*last) {
756         /* create a new alias */
757         *last = alias_new();
758         (*last)->name = m_strdup(buf->data);
759         /* give the main addressbook code a chance */
760         if (CurrentMenu == MENU_ALIAS)
761             set_option (OPTMENUCALLER);
762     } else {
763         /* override the previous value */
764         address_list_wipe(&(*last)->addr);
765         if (CurrentMenu == MENU_ALIAS)
766             set_option (OPTFORCEREDRAWINDEX);
767     }
768
769     mutt_extract_token(buf, s, M_TOKEN_QUOTE | M_TOKEN_SPACE);
770     (*last)->addr = mutt_parse_adrlist((*last)->addr, buf->data);
771     if (mutt_addrlist_to_idna((*last)->addr, &estr)) {
772         snprintf (err->data, err->dsize,
773                   _("Warning: Bad IDN '%s' in alias '%s'.\n"), estr, (*last)->name);
774         p_delete(&estr);
775         return -1;
776     }
777
778     return 0;
779 }
780
781 static int
782 parse_unmy_hdr(BUFFER * buf, BUFFER * s,
783                unsigned long data __attribute__ ((unused)),
784                BUFFER * err __attribute__ ((unused)))
785 {
786     do {
787         mutt_extract_token (buf, s, 0);
788
789         if (!m_strcmp("*", buf->data)) {
790             string_list_wipe(&UserHeader);
791         } else {
792             string_list_t **last = &UserHeader;
793             ssize_t l = m_strlen(buf->data);
794
795             if (buf->data[l - 1] == ':')
796                 l--;
797
798             while (*last) {
799                 if (!ascii_strncasecmp(buf->data, (*last)->data, l)
800                 && (*last)->data[l] == ':')
801                 {
802                     string_list_t *tmp = string_list_pop(last);
803                     string_item_delete(&tmp);
804                 } else {
805                     last = &(*last)->next;
806                 }
807             }
808         }
809     } while (MoreArgs(s));
810
811     return 0;
812 }
813
814 static int parse_my_hdr (BUFFER * buf, BUFFER * s, unsigned long data __attribute__ ((unused)),
815                          BUFFER * err)
816 {
817   string_list_t *tmp;
818   ssize_t keylen;
819   char *p;
820
821   mutt_extract_token (buf, s, M_TOKEN_SPACE | M_TOKEN_QUOTE);
822   if ((p = strpbrk (buf->data, ": \t")) == NULL || *p != ':') {
823     m_strcpy(err->data, err->dsize, _("invalid header field"));
824     return (-1);
825   }
826   keylen = p - buf->data + 1;
827
828   if (UserHeader) {
829     for (tmp = UserHeader;; tmp = tmp->next) {
830       /* see if there is already a field by this name */
831       if (ascii_strncasecmp (buf->data, tmp->data, keylen) == 0) {
832         /* replace the old value */
833         p_delete(&tmp->data);
834         tmp->data = buf->data;
835         p_clear(buf, 1);
836         return 0;
837       }
838       if (!tmp->next)
839         break;
840     }
841     tmp->next = string_item_new();
842     tmp = tmp->next;
843   }
844   else {
845     tmp = string_item_new();
846     UserHeader = tmp;
847   }
848   tmp->data = buf->data;
849   p_clear(buf, 1);
850   return 0;
851 }
852
853 static int
854 parse_sort (struct option_t* dst, const char *s, const struct mapping_t *map,
855             char* errbuf, ssize_t errlen) {
856   int i, flags = 0;
857
858   if (m_strncmp("reverse-", s, 8) == 0) {
859     s += 8;
860     flags = SORT_REVERSE;
861   }
862
863   if (m_strncmp("last-", s, 5) == 0) {
864     s += 5;
865     flags |= SORT_LAST;
866   }
867
868   if ((i = mutt_getvaluebyname (s, map)) == -1) {
869     if (errbuf)
870       snprintf (errbuf, errlen, _("'%s' is invalid for $%s"), s, dst->option);
871     return (-1);
872   }
873
874   *((short*) dst->data) = i | flags;
875   return 0;
876 }
877
878 /* if additional data more == 1, we want to resolve synonyms */
879 static void mutt_set_default(const char *name __attribute__ ((unused)), void* p, unsigned long more)
880 {
881     char buf[LONG_STRING];
882     struct option_t *ptr = p;
883
884     if (!ptr || *ptr->init || !FuncTable[DTYPE (ptr->type)].opt_fromstr)
885         return;
886
887     mutt_option_value(ptr->option, buf, sizeof(buf));
888     if (m_strlen(ptr->init) == 0 && buf && *buf)
889         ptr->init = m_strdup(buf);
890 }
891
892 static int init_expand (char** dst, struct option_t* src) {
893   BUFFER token, in;
894   ssize_t len = 0;
895
896   p_delete(dst);
897
898   if (DTYPE(src->type) == DT_STR || DTYPE(src->type) == DT_PATH) {
899     /* only expand for string as it's the only place where
900      * we want to expand vars right now */
901     if (src->init && *src->init) {
902       p_clear(&token, 1);
903       p_clear(&in, 1);
904       len = m_strlen(src->init) + 2;
905       in.data = p_new(char, len + 1);
906       snprintf (in.data, len, "\"%s\"", src->init);
907       in.dptr = in.data;
908       in.dsize = len;
909       mutt_extract_token (&token, &in, 0);
910       if (token.data && *token.data)
911         *dst = m_strdup(token.data);
912       else
913         *dst = m_strdup("");
914       p_delete(&in.data);
915       p_delete(&token.data);
916     } else
917       *dst = m_strdup("");
918   } else
919     /* for non-string: take value as is */
920     *dst = m_strdup(src->init);
921   return (1);
922 }
923
924 /* if additional data more == 1, we want to resolve synonyms */
925 static void mutt_restore_default (const char* name __attribute__ ((unused)),
926                                   void* p, unsigned long more) {
927   char errbuf[STRING];
928   struct option_t* ptr = (struct option_t*) p;
929   char* init = NULL;
930
931   if (!ptr)
932     return;
933   if (FuncTable[DTYPE (ptr->type)].opt_fromstr) {
934     init_expand (&init, ptr);
935     if (!FuncTable[DTYPE (ptr->type)].opt_fromstr (ptr, init, errbuf,
936                                                        sizeof(errbuf))) {
937       if (!option (OPTNOCURSES))
938         mutt_endwin (NULL);
939       fprintf (stderr, _("Invalid default setting for $%s found: \"%s\".\n"
940                          "Please report this error: \"%s\"\n"),
941                ptr->option, NONULL (init), errbuf);
942       exit (1);
943     }
944     p_delete(&init);
945   }
946
947   if (ptr->flags & R_INDEX)
948     set_option (OPTFORCEREDRAWINDEX);
949   if (ptr->flags & R_PAGER)
950     set_option (OPTFORCEREDRAWPAGER);
951   if (ptr->flags & R_RESORT_SUB)
952     set_option (OPTSORTSUBTHREADS);
953   if (ptr->flags & R_RESORT)
954     set_option (OPTNEEDRESORT);
955   if (ptr->flags & R_RESORT_INIT)
956     set_option (OPTRESORTINIT);
957   if (ptr->flags & R_TREE)
958     set_option (OPTREDRAWTREE);
959 }
960
961 static int check_num (const char* option, unsigned long p,
962                       char* errbuf, ssize_t errlen) {
963   if ((int) p < 0) {
964     if (errbuf)
965       snprintf (errbuf, errlen, _("'%d' is invalid for $%s"), (int) p, option);
966     return (0);
967   }
968   return (1);
969 }
970
971 static int check_history (const char* option __attribute__ ((unused)), unsigned long p,
972                           char* errbuf, ssize_t errlen) {
973   if (!check_num ("history", p, errbuf, errlen))
974     return (0);
975   mutt_init_history ();
976   return (1);
977 }
978
979 static int check_special (const char* name, unsigned long val,
980                           char* errbuf, ssize_t errlen) {
981   int i = 0;
982
983   for (i = 0; SpecialVars[i].name; i++) {
984     if (m_strcmp(SpecialVars[i].name, name) == 0) {
985       return (SpecialVars[i].check (SpecialVars[i].name,
986                                     val, errbuf, errlen));
987     }
988   }
989   return (1);
990 }
991
992 static const struct mapping_t* get_sortmap (struct option_t* option) {
993   const struct mapping_t* map = NULL;
994
995   switch (option->type & DT_SUBTYPE_MASK) {
996   case DT_SORT_ALIAS:
997     map = SortAliasMethods;
998     break;
999   case DT_SORT_BROWSER:
1000     map = SortBrowserMethods;
1001     break;
1002   case DT_SORT_KEYS:
1003     map = SortKeyMethods;
1004     break;
1005   case DT_SORT_AUX:
1006     map = SortAuxMethods;
1007     break;
1008   default:
1009     map = SortMethods;
1010     break;
1011   }
1012   return (map);
1013 }
1014
1015 #define CHECK_PAGER \
1016   if ((CurrentMenu == MENU_PAGER) && \
1017       (!option || (option->flags & R_RESORT))) \
1018   { \
1019     snprintf (err->data, err->dsize, \
1020               _("Not available in this menu.")); \
1021     return (-1); \
1022   }
1023
1024 static int parse_set (BUFFER * tmp, BUFFER * s, unsigned long data,
1025                       BUFFER * err)
1026 {
1027   int query, unset, inv, reset, r = 0;
1028   struct option_t* option = NULL;
1029
1030   while (MoreArgs (s)) {
1031     /* reset state variables */
1032     query = 0;
1033     unset = data & M_SET_UNSET;
1034     inv = data & M_SET_INV;
1035     reset = data & M_SET_RESET;
1036
1037     if (*s->dptr == '?') {
1038       query = 1;
1039       s->dptr++;
1040     }
1041     else if (m_strncmp("no", s->dptr, 2) == 0) {
1042       s->dptr += 2;
1043       unset = !unset;
1044     }
1045     else if (m_strncmp("inv", s->dptr, 3) == 0) {
1046       s->dptr += 3;
1047       inv = !inv;
1048     }
1049     else if (*s->dptr == '&') {
1050       reset = 1;
1051       s->dptr++;
1052     }
1053
1054     /* get the variable name */
1055     mutt_extract_token (tmp, s, M_TOKEN_EQUAL);
1056     option = hash_find(ConfigOptions, tmp->data);
1057     if (!option && !(reset && m_strcmp("all", tmp->data) == 0)) {
1058       snprintf (err->data, err->dsize, _("%s: unknown variable"), tmp->data);
1059       return (-1);
1060     }
1061     s->dptr = vskipspaces(s->dptr);
1062
1063     if (reset) {
1064       if (query || unset || inv) {
1065         snprintf (err->data, err->dsize, _("prefix is illegal with reset"));
1066         return (-1);
1067       }
1068
1069       if (s && *s->dptr == '=') {
1070         snprintf (err->data, err->dsize, _("value is illegal with reset"));
1071         return (-1);
1072       }
1073
1074       if (!m_strcmp("all", tmp->data)) {
1075         if (CurrentMenu == MENU_PAGER) {
1076           snprintf (err->data, err->dsize, _("Not available in this menu."));
1077           return (-1);
1078         }
1079         hash_map (ConfigOptions, mutt_restore_default, 1);
1080         set_option (OPTFORCEREDRAWINDEX);
1081         set_option (OPTFORCEREDRAWPAGER);
1082         set_option (OPTSORTSUBTHREADS);
1083         set_option (OPTNEEDRESORT);
1084         set_option (OPTRESORTINIT);
1085         set_option (OPTREDRAWTREE);
1086         return (0);
1087       } else {
1088         CHECK_PAGER;
1089         mutt_restore_default (NULL, option, 1);
1090       }
1091     }
1092     else if (DTYPE (option->type) == DT_BOOL) {
1093       /* XXX this currently ignores the function table
1094        * as we don't get invert and stuff into it */
1095       if (s && *s->dptr == '=') {
1096         if (unset || inv || query) {
1097           snprintf (err->data, err->dsize, "Usage: set variable=yes|no");
1098           return (-1);
1099         }
1100
1101         s->dptr++;
1102         mutt_extract_token (tmp, s, 0);
1103         if (ascii_strcasecmp ("yes", tmp->data) == 0)
1104           unset = inv = 0;
1105         else if (ascii_strcasecmp ("no", tmp->data) == 0)
1106           unset = 1;
1107         else {
1108           snprintf (err->data, err->dsize, "Usage: set variable=yes|no");
1109           return (-1);
1110         }
1111       }
1112
1113       if (query) {
1114         bool_to_string (err->data, err->dsize, option);
1115         return 0;
1116       }
1117
1118       CHECK_PAGER;
1119       if (unset)
1120         unset_option (option->data);
1121       else if (inv)
1122         toggle_option (option->data);
1123       else
1124         set_option (option->data);
1125     }
1126     else if (DTYPE (option->type) == DT_STR ||
1127              DTYPE (option->type) == DT_PATH ||
1128              DTYPE (option->type) == DT_MAGIC ||
1129              DTYPE (option->type) == DT_NUM ||
1130              DTYPE (option->type) == DT_SORT ||
1131              DTYPE (option->type) == DT_RX)
1132     {
1133       /* XXX maybe we need to get unset into handlers? */
1134       if (DTYPE (option->type) == DT_STR || DTYPE (option->type) == DT_PATH) {
1135         if (unset) {
1136           CHECK_PAGER;
1137           p_delete((void **)(void *)&option->data);
1138           break;
1139         }
1140       }
1141
1142       if (query || *s->dptr != '=') {
1143         FuncTable[DTYPE (option->type)].opt_tostr
1144           (err->data, err->dsize, option);
1145         break;
1146       }
1147
1148       CHECK_PAGER;
1149       s->dptr++;
1150       mutt_extract_token (tmp, s, 0);
1151       if (!FuncTable[DTYPE (option->type)].opt_fromstr
1152           (option, tmp->data, err->data, err->dsize))
1153         r = -1;
1154     }
1155     else if (DTYPE (option->type) == DT_QUAD) {
1156
1157       if (query) {
1158         quad_to_string (err->data, err->dsize, option);
1159         break;
1160       }
1161
1162       if (*s->dptr == '=') {
1163         CHECK_PAGER;
1164         s->dptr++;
1165         mutt_extract_token (tmp, s, 0);
1166         if (ascii_strcasecmp ("yes", tmp->data) == 0)
1167           set_quadoption (option->data, M_YES);
1168         else if (ascii_strcasecmp ("no", tmp->data) == 0)
1169           set_quadoption (option->data, M_NO);
1170         else if (ascii_strcasecmp ("ask-yes", tmp->data) == 0)
1171           set_quadoption (option->data, M_ASKYES);
1172         else if (ascii_strcasecmp ("ask-no", tmp->data) == 0)
1173           set_quadoption (option->data, M_ASKNO);
1174         else {
1175           snprintf (err->data, err->dsize, _("'%s' is invalid for $%s\n"),
1176                     tmp->data, option->option);
1177           r = -1;
1178           break;
1179         }
1180       }
1181       else {
1182         if (inv)
1183           toggle_quadoption (option->data);
1184         else if (unset)
1185           set_quadoption (option->data, M_NO);
1186         else
1187           set_quadoption (option->data, M_YES);
1188       }
1189     }
1190     else {
1191       snprintf (err->data, err->dsize, _("%s: unknown type"),
1192                 option->option);
1193       r = -1;
1194       break;
1195     }
1196
1197     if (option->flags & R_INDEX)
1198       set_option (OPTFORCEREDRAWINDEX);
1199     if (option->flags & R_PAGER)
1200       set_option (OPTFORCEREDRAWPAGER);
1201     if (option->flags & R_RESORT_SUB)
1202       set_option (OPTSORTSUBTHREADS);
1203     if (option->flags & R_RESORT)
1204       set_option (OPTNEEDRESORT);
1205     if (option->flags & R_RESORT_INIT)
1206       set_option (OPTRESORTINIT);
1207     if (option->flags & R_TREE)
1208       set_option (OPTREDRAWTREE);
1209   }
1210   return (r);
1211 }
1212
1213 #define MAXERRS 128
1214
1215 /* reads the specified initialization file.  returns -1 if errors were found
1216    so that we can pause to let the user know...  */
1217 static int source_rc (const char *rcfile, BUFFER * err)
1218 {
1219   FILE *f;
1220   int line = 0, rc = 0, conv = 0;
1221   BUFFER token;
1222   char *linebuf = NULL;
1223   char *currentline = NULL;
1224   ssize_t buflen;
1225   pid_t pid;
1226
1227   if ((f = mutt_open_read (rcfile, &pid)) == NULL) {
1228     snprintf (err->data, err->dsize, "%s: %s", rcfile, strerror (errno));
1229     return (-1);
1230   }
1231
1232   p_clear(&token, 1);
1233   while ((linebuf = mutt_read_line(linebuf, &buflen, f, &line)) != NULL) {
1234     conv = ConfigCharset && (*ConfigCharset) && mod_cset.charset;
1235     if (conv) {
1236       currentline = m_strdup(linebuf);
1237       if (!currentline)
1238         continue;
1239       mutt_convert_string (&currentline, ConfigCharset, mod_cset.charset, 0);
1240     }
1241     else
1242       currentline = linebuf;
1243
1244     CurRCLine = line;
1245     CurRCFile = rcfile;
1246
1247     if (mutt_parse_rc_line (currentline, &token, err) == -1) {
1248       mutt_error (_("Error in %s, line %d: %s"), rcfile, line, err->data);
1249       if (--rc < -MAXERRS) {
1250         if (conv)
1251           p_delete(&currentline);
1252         break;
1253       }
1254     }
1255     else {
1256       if (rc < 0)
1257         rc = -1;
1258     }
1259     if (conv)
1260       p_delete(&currentline);
1261   }
1262   p_delete(&token.data);
1263   p_delete(&linebuf);
1264   m_fclose(&f);
1265   if (pid != -1)
1266     mutt_wait_filter (pid);
1267   if (rc) {
1268     /* the muttrc source keyword */
1269     snprintf (err->data, err->dsize,
1270               rc >= -MAXERRS ? _("source: errors in %s")
1271               : _("source: reading aborted due too many errors in %s"),
1272               rcfile);
1273     rc = -1;
1274   }
1275   return (rc);
1276 }
1277
1278 #undef MAXERRS
1279
1280 static int parse_source (BUFFER * tmp, BUFFER * s,
1281                          unsigned long data __attribute__ ((unused)),
1282                          BUFFER * err)
1283 {
1284   char path[_POSIX_PATH_MAX];
1285   int rc = 0;
1286
1287   do {
1288     if (mutt_extract_token (tmp, s, 0) != 0) {
1289       snprintf (err->data, err->dsize, _("source: error at %s"), s->dptr);
1290       return (-1);
1291     }
1292
1293     m_strcpy(path, sizeof(path), tmp->data);
1294     mutt_expand_path (path, sizeof(path));
1295
1296     rc += source_rc (path, err);
1297   }
1298   while (MoreArgs (s));
1299
1300   return ((rc < 0) ? -1 : 0);
1301 }
1302
1303 /* line         command to execute
1304
1305    token        scratch buffer to be used by parser.  caller should free
1306                 token->data when finished.  the reason for this variable is
1307                 to avoid having to allocate and deallocate a lot of memory
1308                 if we are parsing many lines.  the caller can pass in the
1309                 memory to use, which avoids having to create new space for
1310                 every call to this function.
1311
1312    err          where to write error messages */
1313 int mutt_parse_rc_line (const char *line, BUFFER * token, BUFFER * err)
1314 {
1315   int i, r = -1;
1316   BUFFER expn;
1317
1318   p_clear(&expn, 1);
1319   expn.data = expn.dptr = line;
1320   expn.dsize = m_strlen(line);
1321
1322   *err->data = 0;
1323
1324   expn.dptr = vskipspaces(expn.dptr);
1325   while (*expn.dptr) {
1326     if (*expn.dptr == '#')
1327       break;                    /* rest of line is a comment */
1328     if (*expn.dptr == ';') {
1329       expn.dptr++;
1330       continue;
1331     }
1332     mutt_extract_token (token, &expn, 0);
1333     for (i = 0; Commands[i].name; i++) {
1334       if (!m_strcmp(token->data, Commands[i].name)) {
1335         if (Commands[i].func (token, &expn, Commands[i].data, err) != 0)
1336           goto finish;
1337         break;
1338       }
1339     }
1340     if (!Commands[i].name) {
1341       snprintf (err->data, err->dsize, _("%s: unknown command"),
1342                 NONULL (token->data));
1343       goto finish;
1344     }
1345   }
1346   r = 0;
1347 finish:
1348   if (expn.destroy)
1349     p_delete(&expn.data);
1350   return (r);
1351 }
1352
1353
1354 #define NUMVARS (sizeof(MuttVars)/sizeof(MuttVars[0]))
1355 #define NUMCOMMANDS (sizeof(Commands)/sizeof(Commands[0]))
1356 /* initial string that starts completion. No telling how much crap
1357  * the user has typed so far. Allocate LONG_STRING just to be sure! */
1358 char User_typed[LONG_STRING] = { 0 };
1359
1360 int Num_matched = 0;            /* Number of matches for completion */
1361 char Completed[STRING] = { 0 }; /* completed string (command or variable) */
1362 const char *Matches[MAX (NUMVARS, NUMCOMMANDS) + 1];  /* all the matches + User_typed */
1363
1364 /* helper function for completion.  Changes the dest buffer if
1365    necessary/possible to aid completion.
1366         dest == completion result gets here.
1367         src == candidate for completion.
1368         try == user entered data for completion.
1369         len == length of dest buffer.
1370 */
1371 static void candidate (char *dest, char *try, const char *src, int len)
1372 {
1373   int l;
1374
1375   if (strstr (src, try) == src) {
1376     Matches[Num_matched++] = src;
1377     if (dest[0] == 0)
1378       m_strcpy(dest, len, src);
1379     else {
1380       for (l = 0; src[l] && src[l] == dest[l]; l++);
1381       dest[l] = 0;
1382     }
1383   }
1384 }
1385
1386 int mutt_command_complete (char *buffer, ssize_t len, int pos, int numtabs)
1387 {
1388   char *pt = buffer;
1389   int num;
1390   int spaces;                   /* keep track of the number of leading spaces on the line */
1391
1392   buffer = vskipspaces(buffer);
1393   spaces = buffer - pt;
1394
1395   pt = buffer + pos - spaces;
1396   while ((pt > buffer) && !isspace ((unsigned char) *pt))
1397     pt--;
1398
1399   if (pt == buffer) {           /* complete cmd */
1400     /* first TAB. Collect all the matches */
1401     if (numtabs == 1) {
1402       Num_matched = 0;
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; Commands[num].name; num++)
1407         candidate (Completed, User_typed, Commands[num].name,
1408                    sizeof(Completed));
1409       Matches[Num_matched++] = User_typed;
1410
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)
1414         return 1;
1415     }
1416
1417     if (Completed[0] == 0 && User_typed[0])
1418       return 0;
1419
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]);
1428
1429     /* return the completed command */
1430     m_strcpy(buffer, len - spaces, Completed);
1431   }
1432   else if (!m_strncmp(buffer, "set", 3)
1433            || !m_strncmp(buffer, "unset", 5)
1434            || !m_strncmp(buffer, "reset", 5)
1435            || !m_strncmp(buffer, "toggle", 6)) {    /* complete variables */
1436     const char *prefixes[] = { "no", "inv", "?", "&", NULL };
1437
1438     pt++;
1439     /* loop through all the possible prefixes (no, inv, ...) */
1440     if (!m_strncmp(buffer, "set", 3)) {
1441       for (num = 0; prefixes[num]; num++) {
1442         if (!m_strncmp(pt, prefixes[num], m_strlen(prefixes[num]))) {
1443           pt += m_strlen(prefixes[num]);
1444           break;
1445         }
1446       }
1447     }
1448
1449     /* first TAB. Collect all the matches */
1450     if (numtabs == 1) {
1451       Num_matched = 0;
1452       m_strcpy(User_typed, sizeof(User_typed), pt);
1453       p_clear(Matches, countof(Matches));
1454       p_clear(Completed, countof(Completed));
1455       for (num = 0; MuttVars[num].option; num++)
1456         candidate(Completed, User_typed, MuttVars[num].option,
1457                   sizeof(Completed));
1458       Matches[Num_matched++] = User_typed;
1459
1460       /* All matches are stored. Longest non-ambiguous string is ""
1461        * i.e. dont change 'buffer'. Fake successful return this time */
1462       if (User_typed[0] == 0)
1463         return 1;
1464     }
1465
1466     if (Completed[0] == 0 && User_typed[0])
1467       return 0;
1468
1469     /* Num_matched will _always_ be atleast 1 since the initial
1470      * user-typed string is always stored */
1471     if (numtabs == 1 && Num_matched == 2)
1472       snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
1473     else if (numtabs > 1 && Num_matched > 2)
1474       /* cycle thru all the matches */
1475       snprintf (Completed, sizeof(Completed), "%s",
1476                 Matches[(numtabs - 2) % Num_matched]);
1477
1478     m_strcpy(pt, buffer + len - pt - spaces, Completed);
1479   }
1480   else if (!m_strncmp(buffer, "exec", 4)) {
1481     struct binding_t *menu = km_get_table (CurrentMenu);
1482
1483     if (!menu && CurrentMenu != MENU_PAGER)
1484       menu = OpGeneric;
1485
1486     pt++;
1487     /* first TAB. Collect all the matches */
1488     if (numtabs == 1) {
1489       Num_matched = 0;
1490       m_strcpy(User_typed, sizeof(User_typed), pt);
1491       p_clear(Matches, countof(Matches));
1492       p_clear(Completed, countof(Completed));
1493       for (num = 0; menu[num].name; num++)
1494         candidate (Completed, User_typed, menu[num].name, sizeof(Completed));
1495       /* try the generic menu */
1496       if (Completed[0] == 0 && CurrentMenu != MENU_PAGER) {
1497         menu = OpGeneric;
1498         for (num = 0; menu[num].name; num++)
1499           candidate (Completed, User_typed, menu[num].name,
1500                      sizeof(Completed));
1501       }
1502       Matches[Num_matched++] = User_typed;
1503
1504       /* All matches are stored. Longest non-ambiguous string is ""
1505        * i.e. dont change 'buffer'. Fake successful return this time */
1506       if (User_typed[0] == 0)
1507         return 1;
1508     }
1509
1510     if (Completed[0] == 0 && User_typed[0])
1511       return 0;
1512
1513     /* Num_matched will _always_ be atleast 1 since the initial
1514      * user-typed string is always stored */
1515     if (numtabs == 1 && Num_matched == 2)
1516       snprintf (Completed, sizeof(Completed), "%s", Matches[0]);
1517     else if (numtabs > 1 && Num_matched > 2)
1518       /* cycle thru all the matches */
1519       snprintf (Completed, sizeof(Completed), "%s",
1520                 Matches[(numtabs - 2) % Num_matched]);
1521
1522     m_strcpy(pt, buffer + len - pt - spaces, Completed);
1523   }
1524   else
1525     return 0;
1526
1527   return 1;
1528 }
1529
1530 int mutt_var_value_complete (char *buffer, ssize_t len, int pos)
1531 {
1532   char var[STRING], *pt = buffer;
1533   int spaces;
1534   struct option_t* option = NULL;
1535
1536   if (buffer[0] == 0)
1537     return 0;
1538
1539   buffer = vskipspaces(buffer);
1540   spaces = buffer - pt;
1541
1542   pt = buffer + pos - spaces;
1543   while ((pt > buffer) && !isspace ((unsigned char) *pt))
1544     pt--;
1545   pt++;                         /* move past the space */
1546   if (*pt == '=')               /* abort if no var before the '=' */
1547     return 0;
1548
1549   if (m_strncmp(buffer, "set", 3) == 0) {
1550     m_strcpy(var, sizeof(var), pt);
1551     /* ignore the trailing '=' when comparing */
1552     var[m_strlen(var) - 1] = 0;
1553     if (!(option = hash_find (ConfigOptions, var)))
1554       return 0;                 /* no such variable. */
1555     else {
1556       char tmp[LONG_STRING], tmp2[LONG_STRING];
1557       char *s, *d;
1558       ssize_t dlen = buffer + len - pt - spaces;
1559       const char *vals[] = { "no", "yes", "ask-no", "ask-yes" };
1560
1561       tmp[0] = '\0';
1562
1563       if ((DTYPE (option->type) == DT_STR) ||
1564           (DTYPE (option->type) == DT_PATH) ||
1565           (DTYPE (option->type) == DT_RX)) {
1566         m_strcpy(tmp, sizeof(tmp), NONULL(*((char **)option->data)));
1567         if (DTYPE (option->type) == DT_PATH)
1568           mutt_pretty_mailbox (tmp);
1569       }
1570       else if (DTYPE (option->type) == DT_QUAD)
1571         m_strcpy(tmp, sizeof(tmp), vals[quadoption(option->data)]);
1572       else if (DTYPE (option->type) == DT_NUM)
1573         snprintf (tmp, sizeof(tmp), "%d", (*((short *) option->data)));
1574       else if (DTYPE (option->type) == DT_SORT) {
1575         const struct mapping_t *map;
1576         const char *p;
1577
1578         switch (option->type & DT_SUBTYPE_MASK) {
1579         case DT_SORT_ALIAS:
1580           map = SortAliasMethods;
1581           break;
1582         case DT_SORT_BROWSER:
1583           map = SortBrowserMethods;
1584           break;
1585         case DT_SORT_KEYS:
1586           map = SortKeyMethods;
1587           break;
1588         default:
1589           map = SortMethods;
1590           break;
1591         }
1592         p = mutt_getnamebyvalue(*((short *) option->data) & SORT_MASK, map);
1593         snprintf(tmp, sizeof(tmp), "%s%s%s",
1594                  (*((short *)option->data) & SORT_REVERSE) ? "reverse-" : "",
1595                  (*((short *)option->data) & SORT_LAST) ? "last-" : "", p);
1596       }
1597       else if (DTYPE (option->type) == DT_MAGIC) {
1598         const char *p;
1599         switch (DefaultMagic) {
1600           case M_MBOX:
1601             p = "mbox";
1602             break;
1603           case M_MH:
1604             p = "MH";
1605             break;
1606           case M_MAILDIR:
1607             p = "Maildir";
1608             break;
1609           default:
1610             p = "unknown";
1611         }
1612         m_strcpy(tmp, sizeof(tmp), p);
1613       }
1614       else if (DTYPE (option->type) == DT_BOOL)
1615         m_strcpy(tmp, sizeof(tmp), option(option->data) ? "yes" : "no");
1616       else
1617         return 0;
1618
1619       for (s = tmp, d = tmp2; *s && (d - tmp2) < ssizeof(tmp2) - 2;) {
1620         if (*s == '\\' || *s == '"')
1621           *d++ = '\\';
1622         *d++ = *s++;
1623       }
1624       *d = '\0';
1625
1626       m_strcpy(tmp, sizeof(tmp), pt);
1627       snprintf (pt, dlen, "%s\"%s\"", tmp, tmp2);
1628
1629       return 1;
1630     }
1631   }
1632   return 0;
1633 }
1634
1635 /* Implement the -Q command line flag */
1636 int mutt_query_variables (string_list_t * queries)
1637 {
1638   string_list_t *p;
1639
1640   char errbuff[STRING];
1641   char command[STRING];
1642
1643   BUFFER err, token;
1644
1645   p_clear(&err, 1);
1646   p_clear(&token, 1);
1647
1648   err.data = errbuff;
1649   err.dsize = sizeof(errbuff);
1650
1651   for (p = queries; p; p = p->next) {
1652     snprintf (command, sizeof(command), "set ?%s\n", p->data);
1653     if (mutt_parse_rc_line (command, &token, &err) == -1) {
1654       fprintf (stderr, "%s\n", err.data);
1655       p_delete(&token.data);
1656       return 1;
1657     }
1658     printf ("%s\n", err.data);
1659   }
1660
1661   p_delete(&token.data);
1662   return 0;
1663 }
1664
1665 static int mutt_execute_commands (string_list_t * p)
1666 {
1667   BUFFER err, token;
1668   char errstr[STRING];
1669
1670   p_clear(&err, 1);
1671   err.data = errstr;
1672   err.dsize = sizeof(errstr);
1673   p_clear(&token, 1);
1674   for (; p; p = p->next) {
1675     if (mutt_parse_rc_line (p->data, &token, &err) != 0) {
1676       fprintf (stderr, _("Error in command line: %s\n"), err.data);
1677       p_delete(&token.data);
1678       return (-1);
1679     }
1680   }
1681   p_delete(&token.data);
1682   return 0;
1683 }
1684
1685 void mutt_init (int skip_sys_rc, string_list_t * commands)
1686 {
1687   struct passwd *pw;
1688   const char *p;
1689   char buffer[STRING], error[STRING];
1690   int default_rc = 0, need_pause = 0;
1691   int i;
1692   BUFFER err;
1693
1694   p_clear(&err, 1);
1695   err.data = error;
1696   err.dsize = sizeof(error);
1697
1698   ConfigOptions = hash_new (sizeof(MuttVars) * 2, 0);
1699   for (i = 0; MuttVars[i].option; i++) {
1700     hash_insert (ConfigOptions, MuttVars[i].option, &MuttVars[i]);
1701   }
1702
1703   /*
1704    * XXX - use something even more difficult to predict?
1705    */
1706   snprintf (AttachmentMarker, sizeof(AttachmentMarker),
1707             "\033]9;%ld\a", (long) time (NULL));
1708
1709   luaM_initialize();
1710   /* Get some information about the user */
1711   if ((pw = getpwuid (getuid ()))) {
1712     char rnbuf[STRING];
1713     mutt_gecos_name(rnbuf, sizeof(rnbuf), pw, mod_core.gecos_mask);
1714     Realname = m_strdup(rnbuf);
1715   }
1716
1717 #ifdef USE_NNTP
1718   {
1719     FILE *f;
1720     char *q;
1721
1722     if ((f = safe_fopen (SYSCONFDIR "/nntpserver", "r"))) {
1723       buffer[0] = '\0';
1724       fgets (buffer, sizeof(buffer), f);
1725       p = vskipspaces(buffer);
1726       q = (char*)p;
1727       while (*q && !isspace(*q))
1728         q++;
1729       *q = '\0';
1730       NewsServer = m_strdup(p);
1731       m_fclose(&f);
1732     }
1733   }
1734   if ((p = getenv ("NNTPSERVER")))
1735     NewsServer = m_strdup(p);
1736 #endif
1737
1738   if ((p = getenv("MAIL") ?: getenv("MAILDIR"))) {
1739     Spoolfile = m_strdup(p);
1740   } else {
1741     mutt_concat_path(buffer, sizeof(buffer), NONULL(mod_core.homedir), MAILPATH);
1742     Spoolfile = m_strdup(buffer);
1743   }
1744
1745   if ((p = getenv ("REPLYTO")) != NULL) {
1746     BUFFER buf, token;
1747
1748     snprintf (buffer, sizeof(buffer), "Reply-To: %s", p);
1749
1750     p_clear(&buf, 1);
1751     buf.data = buf.dptr = buffer;
1752     buf.dsize = m_strlen(buffer);
1753
1754     p_clear(&token, 1);
1755     parse_my_hdr (&token, &buf, 0, &err);
1756     p_delete(&token.data);
1757   }
1758
1759   /* Set standard defaults */
1760   hash_map (ConfigOptions, mutt_set_default, 0);
1761   hash_map (ConfigOptions, mutt_restore_default, 0);
1762
1763   CurrentMenu = MENU_MAIN;
1764
1765 #ifdef HAVE_GETSID
1766   /* Unset suspend by default if we're the session leader */
1767   if (getsid (0) == getpid ())
1768     unset_option (OPTSUSPEND);
1769 #endif
1770
1771   mutt_init_history ();
1772
1773   if (!Muttrc) {
1774       snprintf (buffer, sizeof(buffer), "%s/.madmuttrc", NONULL(mod_core.homedir));
1775     if (access (buffer, F_OK) == -1)
1776       snprintf (buffer, sizeof(buffer), "%s/.madmutt/madmuttrc",
1777                 NONULL(mod_core.homedir));
1778
1779     default_rc = 1;
1780     Muttrc = m_strdup(buffer);
1781   }
1782   else {
1783     m_strcpy(buffer, sizeof(buffer), Muttrc);
1784     p_delete(&Muttrc);
1785     mutt_expand_path (buffer, sizeof(buffer));
1786     Muttrc = m_strdup(buffer);
1787   }
1788
1789   /* Process the global rc file if it exists and the user hasn't explicity
1790      requested not to via "-n".  */
1791   if (!skip_sys_rc) {
1792     snprintf (buffer, sizeof(buffer), "%s/Madmuttrc-%s", SYSCONFDIR,
1793               MUTT_VERSION);
1794     if (access (buffer, F_OK) == -1)
1795       snprintf (buffer, sizeof(buffer), "%s/Madmuttrc", SYSCONFDIR);
1796     if (access (buffer, F_OK) == -1)
1797       snprintf (buffer, sizeof(buffer), "%s/Madmuttrc-%s", PKGDATADIR,
1798                 MUTT_VERSION);
1799     if (access (buffer, F_OK) == -1)
1800       snprintf (buffer, sizeof(buffer), "%s/Madmuttrc", PKGDATADIR);
1801     if (access (buffer, F_OK) != -1) {
1802       if (source_rc (buffer, &err) != 0) {
1803         fputs (err.data, stderr);
1804         fputc ('\n', stderr);
1805         need_pause = 1;
1806       }
1807     }
1808   }
1809
1810   /* Read the user's initialization file.  */
1811   if (access (Muttrc, F_OK) != -1) {
1812     if (!option (OPTNOCURSES))
1813       mutt_endwin (NULL);
1814     if (source_rc (Muttrc, &err) != 0) {
1815       fputs (err.data, stderr);
1816       fputc ('\n', stderr);
1817       need_pause = 1;
1818     }
1819   }
1820   else if (!default_rc) {
1821     /* file specified by -F does not exist */
1822     snprintf (buffer, sizeof(buffer), "%s: %s", Muttrc, strerror (errno));
1823     mutt_endwin (buffer);
1824     exit (1);
1825   }
1826
1827   /* LUA {{{ */
1828   snprintf(buffer, sizeof(buffer), "%s/.madmutt.lua", NONULL(mod_core.homedir));
1829   if (access(buffer, F_OK) < 0)
1830       snprintf(buffer, sizeof(buffer), "%s/.madmutt/cfg.lua", NONULL(mod_core.homedir));
1831   if (!access(buffer, F_OK)) {
1832       need_pause = luaM_wrap(mutt_error, luaM_dofile(buffer));
1833   }
1834   /* }}} */
1835
1836   if (mutt_execute_commands (commands) != 0)
1837     need_pause = 1;
1838
1839   /* warn about synonym variables */
1840   if (Synonyms) {
1841     syn_t *syn;
1842
1843     fprintf (stderr, _("Warning: the following synonym variables were found:\n"));
1844
1845     for (syn = Synonyms; syn; syn = syn->next) {
1846       fprintf(stderr, "$%s ($%s should be used) (%s:%d)\n",
1847               syn->o ? NONULL(syn->o->option) : "",
1848               syn->n ? NONULL(syn->n->option) : "",
1849               NONULL(syn->f), syn->l);
1850     }
1851     fprintf (stderr, _("Warning: synonym variables are scheduled"
1852                        " for removal.\n"));
1853     syn_list_wipe(&Synonyms);
1854     need_pause = 1;
1855   }
1856
1857   if (need_pause && !option (OPTNOCURSES)) {
1858     if (mutt_any_key_to_continue (NULL) == -1)
1859       mutt_exit (1);
1860   }
1861 }
1862
1863 int mutt_get_hook_type (const char *name)
1864 {
1865   struct command_t *c;
1866
1867   for (c = Commands; c->name; c++)
1868     if (c->func == mutt_parse_hook && ascii_strcasecmp (c->name, name) == 0)
1869       return c->data;
1870   return 0;
1871 }
1872
1873 /* dump out the value of all the variables we have */
1874 int mutt_dump_variables (int full) {
1875     ssize_t i = 0;
1876
1877     /* get all non-synonyms into list... */
1878     for (i = 0; MuttVars[i].option; i++) {
1879         struct option_t *option = MuttVars + i;
1880         char buf[LONG_STRING];
1881
1882         if (!full) {
1883             mutt_option_value(option->option, buf, sizeof(buf));
1884             if (!m_strcmp(buf, option->init))
1885                 continue;
1886         }
1887
1888         printf("set ");
1889         FuncTable[DTYPE(option->type)].opt_tostr(buf, sizeof(buf), option);
1890         printf ("%s\n", buf);
1891     }
1892
1893     printf ("\n# vi""m:set ft=muttrc:\n");
1894     return 0;
1895 }