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