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