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