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