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