mutt stable branch with some hacks
1/*
2 * Copyright (C) 1996-2002,2010,2013,2016 Michael R. Elkins <me@mutt.org>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 */
18
19#if HAVE_CONFIG_H
20# include "config.h"
21#endif
22
23#include "version.h"
24#include "mutt.h"
25#include "mapping.h"
26#include "mutt_curses.h"
27#include "mutt_menu.h"
28#include "mutt_regex.h"
29#include "history.h"
30#include "keymap.h"
31#include "mbyte.h"
32#include "charset.h"
33#include "mutt_crypt.h"
34#include "mutt_idna.h"
35#include "group.h"
36
37#if defined(USE_SSL)
38#include "mutt_ssl.h"
39#endif
40
41
42
43#include "mx.h"
44#include "init.h"
45#include "mailbox.h"
46
47#include <ctype.h>
48#include <stdlib.h>
49#include <unistd.h>
50#include <string.h>
51#include <sys/utsname.h>
52#include <errno.h>
53#include <sys/wait.h>
54#include <sys/time.h>
55
56#define CHECK_PAGER \
57 if ((CurrentMenu == MENU_PAGER) && (idx >= 0) && \
58 (MuttVars[idx].flags & R_RESORT)) \
59 { \
60 snprintf (err->data, err->dsize, "%s", \
61 _("Not available in this menu.")); \
62 return (-1); \
63 }
64
65typedef struct myvar
66{
67 char *name;
68 char *value;
69 struct myvar* next;
70} myvar_t;
71
72static myvar_t* MyVars;
73
74static int var_to_string (int idx, char* val, size_t len);
75
76static void myvar_set (const char* var, const char* val);
77static const char* myvar_get (const char* var);
78static void myvar_del (const char* var);
79
80extern char **envlist;
81
82static void toggle_quadoption (int opt)
83{
84 int n = opt/4;
85 int b = (opt % 4) * 2;
86
87 QuadOptions[n] ^= (1 << b);
88}
89
90void set_quadoption (int opt, int flag)
91{
92 int n = opt/4;
93 int b = (opt % 4) * 2;
94
95 QuadOptions[n] &= ~(0x3 << b);
96 QuadOptions[n] |= (flag & 0x3) << b;
97}
98
99int quadoption (int opt)
100{
101 int n = opt/4;
102 int b = (opt % 4) * 2;
103
104 return (QuadOptions[n] >> b) & 0x3;
105}
106
107int query_quadoption (int opt, const char *prompt)
108{
109 int v = quadoption (opt);
110
111 switch (v)
112 {
113 case MUTT_YES:
114 case MUTT_NO:
115 return (v);
116
117 default:
118 v = mutt_yesorno (prompt, (v == MUTT_ASKYES));
119 mutt_window_clearline (MuttMessageWindow, 0);
120 return (v);
121 }
122
123 /* not reached */
124}
125
126/* given the variable ``s'', return the index into the rc_vars array which
127 matches, or -1 if the variable is not found. */
128static int mutt_option_index (char *s)
129{
130 int i;
131
132 for (i = 0; MuttVars[i].option; i++)
133 if (mutt_strcmp (s, MuttVars[i].option) == 0)
134 return (MuttVars[i].type == DT_SYN ? mutt_option_index ((char *) MuttVars[i].data.p) : i);
135 return (-1);
136}
137
138int mutt_extract_token (BUFFER *dest, BUFFER *tok, int flags)
139{
140 char ch;
141 char qc = 0; /* quote char */
142 char *pc;
143
144 mutt_buffer_clear (dest);
145
146 SKIPWS (tok->dptr);
147 while ((ch = *tok->dptr))
148 {
149 if (!qc)
150 {
151 if ((ISSPACE (ch) && !(flags & MUTT_TOKEN_SPACE)) ||
152 (ch == '#' && !(flags & MUTT_TOKEN_COMMENT)) ||
153 (ch == '=' && (flags & MUTT_TOKEN_EQUAL)) ||
154 (ch == ';' && !(flags & MUTT_TOKEN_SEMICOLON)) ||
155 ((flags & MUTT_TOKEN_PATTERN) && strchr ("~%=!|", ch)))
156 break;
157 }
158
159 tok->dptr++;
160
161 if (ch == qc)
162 qc = 0; /* end of quote */
163 else if (!qc && (ch == '\'' || ch == '"') && !(flags & MUTT_TOKEN_QUOTE))
164 qc = ch;
165 else if (ch == '\\' && qc != '\'')
166 {
167 if (!*tok->dptr)
168 return -1; /* premature end of token */
169 switch (ch = *tok->dptr++)
170 {
171 case 'c':
172 case 'C':
173 if (!*tok->dptr)
174 return -1; /* premature end of token */
175 mutt_buffer_addch (dest, (toupper ((unsigned char) *tok->dptr)
176 - '@') & 0x7f);
177 tok->dptr++;
178 break;
179 case 'r':
180 mutt_buffer_addch (dest, '\r');
181 break;
182 case 'n':
183 mutt_buffer_addch (dest, '\n');
184 break;
185 case 't':
186 mutt_buffer_addch (dest, '\t');
187 break;
188 case 'f':
189 mutt_buffer_addch (dest, '\f');
190 break;
191 case 'e':
192 mutt_buffer_addch (dest, '\033');
193 break;
194 default:
195 if (isdigit ((unsigned char) ch) &&
196 isdigit ((unsigned char) *tok->dptr) &&
197 isdigit ((unsigned char) *(tok->dptr + 1)))
198 {
199
200 mutt_buffer_addch (dest, (ch << 6) + (*tok->dptr << 3) + *(tok->dptr + 1) - 3504);
201 tok->dptr += 2;
202 }
203 else
204 mutt_buffer_addch (dest, ch);
205 }
206 }
207 else if (ch == '^' && (flags & MUTT_TOKEN_CONDENSE))
208 {
209 if (!*tok->dptr)
210 return -1; /* premature end of token */
211 ch = *tok->dptr++;
212 if (ch == '^')
213 mutt_buffer_addch (dest, ch);
214 else if (ch == '[')
215 mutt_buffer_addch (dest, '\033');
216 else if (isalpha ((unsigned char) ch))
217 mutt_buffer_addch (dest, toupper ((unsigned char) ch) - '@');
218 else
219 {
220 mutt_buffer_addch (dest, '^');
221 mutt_buffer_addch (dest, ch);
222 }
223 }
224 else if (ch == '`' && (!qc || qc == '"'))
225 {
226 FILE *fp;
227 pid_t pid;
228 char *cmd, *ptr;
229 size_t expnlen;
230 BUFFER expn;
231 int line = 0;
232
233 pc = tok->dptr;
234 do
235 {
236 if ((pc = strpbrk (pc, "\\`")))
237 {
238 /* skip any quoted chars */
239 if (*pc == '\\')
240 pc += 2;
241 }
242 } while (pc && *pc != '`');
243 if (!pc)
244 {
245 dprint (1, (debugfile, "mutt_get_token: mismatched backticks\n"));
246 return (-1);
247 }
248 cmd = mutt_substrdup (tok->dptr, pc);
249 if ((pid = mutt_create_filter (cmd, NULL, &fp, NULL)) < 0)
250 {
251 dprint (1, (debugfile, "mutt_get_token: unable to fork command: %s", cmd));
252 FREE (&cmd);
253 return (-1);
254 }
255 FREE (&cmd);
256
257 tok->dptr = pc + 1;
258
259 /* read line */
260 mutt_buffer_init (&expn);
261 expn.data = mutt_read_line (NULL, &expn.dsize, fp, &line, 0);
262 safe_fclose (&fp);
263 mutt_wait_filter (pid);
264
265 /* if we got output, make a new string consisting of the shell output
266 plus whatever else was left on the original line */
267 /* BUT: If this is inside a quoted string, directly add output to
268 * the token */
269 if (expn.data && qc)
270 {
271 mutt_buffer_addstr (dest, expn.data);
272 FREE (&expn.data);
273 }
274 else if (expn.data)
275 {
276 expnlen = mutt_strlen (expn.data);
277 tok->dsize = expnlen + mutt_strlen (tok->dptr) + 1;
278 ptr = safe_malloc (tok->dsize);
279 memcpy (ptr, expn.data, expnlen);
280 strcpy (ptr + expnlen, tok->dptr); /* __STRCPY_CHECKED__ */
281 if (tok->destroy)
282 FREE (&tok->data);
283 tok->data = ptr;
284 tok->dptr = ptr;
285 tok->destroy = 1; /* mark that the caller should destroy this data */
286 ptr = NULL;
287 FREE (&expn.data);
288 }
289 }
290 else if (ch == '$' && (!qc || qc == '"') && (*tok->dptr == '{' || isalpha ((unsigned char) *tok->dptr)))
291 {
292 const char *env = NULL;
293 char *var = NULL;
294 int idx;
295
296 if (*tok->dptr == '{')
297 {
298 tok->dptr++;
299 if ((pc = strchr (tok->dptr, '}')))
300 {
301 var = mutt_substrdup (tok->dptr, pc);
302 tok->dptr = pc + 1;
303 }
304 }
305 else
306 {
307 for (pc = tok->dptr; isalnum ((unsigned char) *pc) || *pc == '_'; pc++)
308 ;
309 var = mutt_substrdup (tok->dptr, pc);
310 tok->dptr = pc;
311 }
312 if (var)
313 {
314 if ((env = getenv (var)) || (env = myvar_get (var)))
315 mutt_buffer_addstr (dest, env);
316 else if ((idx = mutt_option_index (var)) != -1)
317 {
318 /* expand settable mutt variables */
319 char val[LONG_STRING];
320
321 if (var_to_string (idx, val, sizeof (val)))
322 mutt_buffer_addstr (dest, val);
323 }
324 FREE (&var);
325 }
326 }
327 else
328 mutt_buffer_addch (dest, ch);
329 }
330 mutt_buffer_addch (dest, 0); /* terminate the string */
331 SKIPWS (tok->dptr);
332 return 0;
333}
334
335static void mutt_free_opt (struct option_t* p)
336{
337 REGEXP* pp;
338
339 switch (p->type & DT_MASK)
340 {
341 case DT_ADDR:
342 rfc822_free_address ((ADDRESS**)p->data.p);
343 break;
344 case DT_RX:
345 pp = (REGEXP*)p->data.p;
346 FREE (&pp->pattern);
347 if (pp->rx)
348 {
349 regfree (pp->rx);
350 FREE (&pp->rx);
351 }
352 break;
353 case DT_PATH:
354 case DT_STR:
355 FREE ((char**)p->data.p); /* __FREE_CHECKED__ */
356 break;
357 }
358}
359
360/* clean up before quitting */
361void mutt_free_opts (void)
362{
363 int i;
364
365 for (i = 0; MuttVars[i].option; i++)
366 mutt_free_opt (MuttVars + i);
367
368 mutt_free_rx_list (&Alternates);
369 mutt_free_rx_list (&UnAlternates);
370 mutt_free_rx_list (&MailLists);
371 mutt_free_rx_list (&UnMailLists);
372 mutt_free_rx_list (&SubscribedLists);
373 mutt_free_rx_list (&UnSubscribedLists);
374 mutt_free_rx_list (&NoSpamList);
375}
376
377static void add_to_list (LIST **list, const char *str)
378{
379 LIST *t, *last = NULL;
380
381 /* don't add a NULL or empty string to the list */
382 if (!str || *str == '\0')
383 return;
384
385 /* check to make sure the item is not already on this list */
386 for (last = *list; last; last = last->next)
387 {
388 if (ascii_strcasecmp (str, last->data) == 0)
389 {
390 /* already on the list, so just ignore it */
391 last = NULL;
392 break;
393 }
394 if (!last->next)
395 break;
396 }
397
398 if (!*list || last)
399 {
400 t = (LIST *) safe_calloc (1, sizeof (LIST));
401 t->data = safe_strdup (str);
402 if (last)
403 last->next = t;
404 else
405 *list = t;
406 }
407}
408
409int mutt_add_to_rx_list (RX_LIST **list, const char *s, int flags, BUFFER *err)
410{
411 RX_LIST *t, *last = NULL;
412 REGEXP *rx;
413
414 if (!s || !*s)
415 return 0;
416
417 if (!(rx = mutt_compile_regexp (s, flags)))
418 {
419 snprintf (err->data, err->dsize, "Bad regexp: %s\n", s);
420 return -1;
421 }
422
423 /* check to make sure the item is not already on this list */
424 for (last = *list; last; last = last->next)
425 {
426 if (ascii_strcasecmp (rx->pattern, last->rx->pattern) == 0)
427 {
428 /* already on the list, so just ignore it */
429 last = NULL;
430 break;
431 }
432 if (!last->next)
433 break;
434 }
435
436 if (!*list || last)
437 {
438 t = mutt_new_rx_list();
439 t->rx = rx;
440 if (last)
441 last->next = t;
442 else
443 *list = t;
444 }
445 else /* duplicate */
446 mutt_free_regexp (&rx);
447
448 return 0;
449}
450
451static int remove_from_replace_list (REPLACE_LIST **list, const char *pat);
452
453static int add_to_replace_list (REPLACE_LIST **list, const char *pat, const char *templ, BUFFER *err)
454{
455 REPLACE_LIST *t = NULL, *last = NULL;
456 REGEXP *rx;
457 int n;
458 const char *p;
459
460 if (!pat || !*pat || !templ)
461 return 0;
462
463 if (!(rx = mutt_compile_regexp (pat, REG_ICASE)))
464 {
465 snprintf (err->data, err->dsize, _("Bad regexp: %s"), pat);
466 return -1;
467 }
468
469 /* check to make sure the item is not already on this list */
470 for (last = *list; last; last = last->next)
471 {
472 if (ascii_strcasecmp (rx->pattern, last->rx->pattern) == 0)
473 {
474 /* Already on the list. Formerly we just skipped this case, but
475 * now we're supporting removals, which means we're supporting
476 * re-adds conceptually. So we probably want this to imply a
477 * removal, then do an add. We can achieve the removal by freeing
478 * the template, and leaving t pointed at the current item.
479 */
480 t = last;
481 FREE(&t->template);
482 break;
483 }
484 if (!last->next)
485 break;
486 }
487
488 /* If t is set, it's pointing into an extant REPLACE_LIST* that we want to
489 * update. Otherwise we want to make a new one to link at the list's end.
490 */
491 if (!t)
492 {
493 t = mutt_new_replace_list();
494 t->rx = rx;
495 if (last)
496 last->next = t;
497 else
498 *list = t;
499 }
500
501 /* Now t is the REPLACE_LIST* that we want to modify. It is prepared. */
502 t->template = safe_strdup(templ);
503
504 /* Find highest match number in template string */
505 t->nmatch = 0;
506 for (p = templ; *p;)
507 {
508 if (*p == '%')
509 {
510 n = atoi(++p);
511 if (n > t->nmatch)
512 t->nmatch = n;
513 while (*p && isdigit((int)*p))
514 ++p;
515 }
516 else
517 ++p;
518 }
519
520 if (t->nmatch > t->rx->rx->re_nsub)
521 {
522 snprintf (err->data, err->dsize, "%s", _("Not enough subexpressions for "
523 "template"));
524 remove_from_replace_list(list, pat);
525 return -1;
526 }
527
528 t->nmatch++; /* match 0 is always the whole expr */
529
530 return 0;
531}
532
533static int remove_from_replace_list (REPLACE_LIST **list, const char *pat)
534{
535 REPLACE_LIST *cur, *prev;
536 int nremoved = 0;
537
538 /* Being first is a special case. */
539 cur = *list;
540 if (!cur)
541 return 0;
542 if (cur->rx && !mutt_strcmp(cur->rx->pattern, pat))
543 {
544 *list = cur->next;
545 mutt_free_regexp(&cur->rx);
546 FREE(&cur->template);
547 FREE(&cur);
548 return 1;
549 }
550
551 prev = cur;
552 for (cur = prev->next; cur;)
553 {
554 if (!mutt_strcmp(cur->rx->pattern, pat))
555 {
556 prev->next = cur->next;
557 mutt_free_regexp(&cur->rx);
558 FREE(&cur->template);
559 FREE(&cur);
560 cur = prev->next;
561 ++nremoved;
562 }
563 else
564 cur = cur->next;
565 }
566
567 return nremoved;
568}
569
570
571static void remove_from_list (LIST **l, const char *str)
572{
573 LIST *p, *last = NULL;
574
575 if (mutt_strcmp ("*", str) == 0)
576 mutt_free_list (l); /* ``unCMD *'' means delete all current entries */
577 else
578 {
579 p = *l;
580 last = NULL;
581 while (p)
582 {
583 if (ascii_strcasecmp (str, p->data) == 0)
584 {
585 FREE (&p->data);
586 if (last)
587 last->next = p->next;
588 else
589 (*l) = p->next;
590 FREE (&p);
591 }
592 else
593 {
594 last = p;
595 p = p->next;
596 }
597 }
598 }
599}
600
601static void free_mbchar_table (mbchar_table **t)
602{
603 if (!t || !*t)
604 return;
605
606 FREE (&(*t)->chars);
607 FREE (&(*t)->segmented_str);
608 FREE (&(*t)->orig_str);
609 FREE (t); /* __FREE_CHECKED__ */
610}
611
612static mbchar_table *parse_mbchar_table (const char *s)
613{
614 mbchar_table *t;
615 size_t slen, k;
616 mbstate_t mbstate;
617 char *d;
618
619 t = safe_calloc (1, sizeof (mbchar_table));
620 slen = mutt_strlen (s);
621 if (!slen)
622 return t;
623
624 t->orig_str = safe_strdup (s);
625 /* This could be more space efficient. However, being used on tiny
626 * strings (Tochars and StChars), the overhead is not great. */
627 t->chars = safe_calloc (slen, sizeof (char *));
628 d = t->segmented_str = safe_calloc (slen * 2, sizeof (char));
629
630 memset (&mbstate, 0, sizeof (mbstate));
631 while (slen && (k = mbrtowc (NULL, s, slen, &mbstate)))
632 {
633 if (k == (size_t)(-1) || k == (size_t)(-2))
634 {
635 dprint (1, (debugfile,
636 "parse_mbchar_table: mbrtowc returned %d converting %s in %s\n",
637 (k == (size_t)(-1)) ? -1 : -2,
638 s, t->orig_str));
639 if (k == (size_t)(-1))
640 memset (&mbstate, 0, sizeof (mbstate));
641 k = (k == (size_t)(-1)) ? 1 : slen;
642 }
643
644 slen -= k;
645 t->chars[t->len++] = d;
646 while (k--)
647 *d++ = *s++;
648 *d++ = '\0';
649 }
650
651 return t;
652}
653
654static int parse_unignore (BUFFER *buf, BUFFER *s, union pointer_long_t udata, BUFFER *err)
655{
656 do
657 {
658 mutt_extract_token (buf, s, 0);
659
660 /* don't add "*" to the unignore list */
661 if (strcmp (buf->data, "*"))
662 add_to_list (&UnIgnore, buf->data);
663
664 remove_from_list (&Ignore, buf->data);
665 }
666 while (MoreArgs (s));
667
668 return 0;
669}
670
671static int parse_ignore (BUFFER *buf, BUFFER *s, union pointer_long_t udata, BUFFER *err)
672{
673 do
674 {
675 mutt_extract_token (buf, s, 0);
676 remove_from_list (&UnIgnore, buf->data);
677 add_to_list (&Ignore, buf->data);
678 }
679 while (MoreArgs (s));
680
681 return 0;
682}
683
684static int parse_list (BUFFER *buf, BUFFER *s, union pointer_long_t udata, BUFFER *err)
685{
686 LIST **data = udata.p;
687 do
688 {
689 mutt_extract_token (buf, s, 0);
690 add_to_list (data, buf->data);
691 }
692 while (MoreArgs (s));
693
694 return 0;
695}
696
697static int parse_echo (BUFFER *buf, BUFFER *s, union pointer_long_t udata, BUFFER *err)
698{
699 if (!MoreArgs (s))
700 {
701 strfcpy (err->data, _("not enough arguments"), err->dsize);
702 return -1;
703 }
704 mutt_extract_token (buf, s, 0);
705 set_option (OPTFORCEREFRESH);
706 mutt_message ("%s", buf->data);
707 unset_option (OPTFORCEREFRESH);
708 mutt_sleep (0);
709
710 return 0;
711}
712
713static void _alternates_clean (void)
714{
715 int i;
716 if (Context && Context->msgcount)
717 {
718 for (i = 0; i < Context->msgcount; i++)
719 Context->hdrs[i]->recip_valid = 0;
720 }
721}
722
723static int parse_alternates (BUFFER *buf, BUFFER *s, union pointer_long_t udata, BUFFER *err)
724{
725 group_context_t *gc = NULL;
726
727 _alternates_clean();
728
729 do
730 {
731 mutt_extract_token (buf, s, 0);
732
733 if (parse_group_context (&gc, buf, s, err) == -1)
734 goto bail;
735
736 mutt_remove_from_rx_list (&UnAlternates, buf->data);
737
738 if (mutt_add_to_rx_list (&Alternates, buf->data, REG_ICASE, err) != 0)
739 goto bail;
740
741 if (mutt_group_context_add_rx (gc, buf->data, REG_ICASE, err) != 0)
742 goto bail;
743 }
744 while (MoreArgs (s));
745
746 mutt_group_context_destroy (&gc);
747 return 0;
748
749bail:
750 mutt_group_context_destroy (&gc);
751 return -1;
752}
753
754static int parse_unalternates (BUFFER *buf, BUFFER *s, union pointer_long_t udata, BUFFER *err)
755{
756 _alternates_clean();
757 do
758 {
759 mutt_extract_token (buf, s, 0);
760 mutt_remove_from_rx_list (&Alternates, buf->data);
761
762 if (mutt_strcmp (buf->data, "*") &&
763 mutt_add_to_rx_list (&UnAlternates, buf->data, REG_ICASE, err) != 0)
764 return -1;
765
766 }
767 while (MoreArgs (s));
768
769 return 0;
770}
771
772static int parse_replace_list (BUFFER *buf, BUFFER *s, union pointer_long_t udata, BUFFER *err)
773{
774 REPLACE_LIST **list = (REPLACE_LIST **)udata.p;
775 BUFFER templ;
776
777 memset(&templ, 0, sizeof(templ));
778
779 /* First token is a regexp. */
780 if (!MoreArgs(s))
781 {
782 strfcpy(err->data, _("not enough arguments"), err->dsize);
783 return -1;
784 }
785 mutt_extract_token(buf, s, 0);
786
787 /* Second token is a replacement template */
788 if (!MoreArgs(s))
789 {
790 strfcpy(err->data, _("not enough arguments"), err->dsize);
791 return -1;
792 }
793 mutt_extract_token(&templ, s, 0);
794
795 if (add_to_replace_list(list, buf->data, templ.data, err) != 0)
796 {
797 FREE(&templ.data);
798 return -1;
799 }
800 FREE(&templ.data);
801
802 return 0;
803}
804
805static int parse_unreplace_list (BUFFER *buf, BUFFER *s, union pointer_long_t udata, BUFFER *err)
806{
807 REPLACE_LIST **list = (REPLACE_LIST **)udata.p;
808
809 /* First token is a regexp. */
810 if (!MoreArgs(s))
811 {
812 strfcpy(err->data, _("not enough arguments"), err->dsize);
813 return -1;
814 }
815
816 mutt_extract_token(buf, s, 0);
817
818 /* "*" is a special case. */
819 if (!mutt_strcmp (buf->data, "*"))
820 {
821 mutt_free_replace_list (list);
822 return 0;
823 }
824
825 remove_from_replace_list(list, buf->data);
826 return 0;
827}
828
829
830static void clear_subject_mods (void)
831{
832 int i;
833 if (Context && Context->msgcount)
834 {
835 for (i = 0; i < Context->msgcount; i++)
836 FREE(&Context->hdrs[i]->env->disp_subj);
837 }
838}
839
840
841static int parse_subjectrx_list (BUFFER *buf, BUFFER *s, union pointer_long_t udata, BUFFER *err)
842{
843 int rc;
844
845 rc = parse_replace_list(buf, s, udata, err);
846 if (rc == 0)
847 clear_subject_mods();
848 return rc;
849}
850
851
852static int parse_unsubjectrx_list (BUFFER *buf, BUFFER *s, union pointer_long_t udata, BUFFER *err)
853{
854 int rc;
855
856 rc = parse_unreplace_list(buf, s, udata, err);
857 if (rc == 0)
858 clear_subject_mods();
859 return rc;
860}
861
862
863static int parse_spam_list (BUFFER *buf, BUFFER *s, union pointer_long_t udata, BUFFER *err)
864{
865 BUFFER templ;
866 long data = udata.l;
867
868 mutt_buffer_init (&templ);
869
870 /* Insist on at least one parameter */
871 if (!MoreArgs(s))
872 {
873 if (data == MUTT_SPAM)
874 strfcpy(err->data, _("spam: no matching pattern"), err->dsize);
875 else
876 strfcpy(err->data, _("nospam: no matching pattern"), err->dsize);
877 return -1;
878 }
879
880 /* Extract the first token, a regexp */
881 mutt_extract_token (buf, s, 0);
882
883 /* data should be either MUTT_SPAM or MUTT_NOSPAM. MUTT_SPAM is for spam commands. */
884 if (data == MUTT_SPAM)
885 {
886 /* If there's a second parameter, it's a template for the spam tag. */
887 if (MoreArgs(s))
888 {
889 mutt_extract_token (&templ, s, 0);
890
891 /* Add to the spam list. */
892 if (add_to_replace_list (&SpamList, buf->data, templ.data, err) != 0)
893 {
894 FREE(&templ.data);
895 return -1;
896 }
897 FREE(&templ.data);
898 }
899
900 /* If not, try to remove from the nospam list. */
901 else
902 {
903 mutt_remove_from_rx_list(&NoSpamList, buf->data);
904 }
905
906 return 0;
907 }
908
909 /* MUTT_NOSPAM is for nospam commands. */
910 else if (data == MUTT_NOSPAM)
911 {
912 /* nospam only ever has one parameter. */
913
914 /* "*" is a special case. */
915 if (!mutt_strcmp(buf->data, "*"))
916 {
917 mutt_free_replace_list (&SpamList);
918 mutt_free_rx_list (&NoSpamList);
919 return 0;
920 }
921
922 /* If it's on the spam list, just remove it. */
923 if (remove_from_replace_list(&SpamList, buf->data) != 0)
924 return 0;
925
926 /* Otherwise, add it to the nospam list. */
927 if (mutt_add_to_rx_list (&NoSpamList, buf->data, REG_ICASE, err) != 0)
928 return -1;
929
930 return 0;
931 }
932
933 /* This should not happen. */
934 strfcpy(err->data, "This is no good at all.", err->dsize);
935 return -1;
936}
937
938
939static int parse_unlist (BUFFER *buf, BUFFER *s, union pointer_long_t udata, BUFFER *err)
940{
941 LIST **data = udata.p;
942 do
943 {
944 mutt_extract_token (buf, s, 0);
945 /*
946 * Check for deletion of entire list
947 */
948 if (mutt_strcmp (buf->data, "*") == 0)
949 {
950 mutt_free_list (data);
951 break;
952 }
953 remove_from_list (data, buf->data);
954 }
955 while (MoreArgs (s));
956
957 return 0;
958}
959
960/* These two functions aren't sidebar-specific, but they are currently only
961 * used by the sidebar_whitelist/unsidebar_whitelist commands.
962 * Putting in an #ifdef to silence an unused function warning when the sidebar
963 * is disabled.
964 */
965#ifdef USE_SIDEBAR
966static int parse_path_list (BUFFER *buf, BUFFER *s, union pointer_long_t udata, BUFFER *err)
967{
968 BUFFER *path;
969 LIST **data = udata.p;
970
971 path = mutt_buffer_pool_get ();
972 do
973 {
974 mutt_extract_token (path, s, 0);
975 mutt_buffer_expand_path (path);
976 add_to_list (data, mutt_b2s (path));
977 }
978 while (MoreArgs (s));
979 mutt_buffer_pool_release (&path);
980
981 return 0;
982}
983
984static int parse_path_unlist (BUFFER *buf, BUFFER *s, union pointer_long_t udata, BUFFER *err)
985{
986 BUFFER *path;
987 LIST **data = udata.p;
988
989 path = mutt_buffer_pool_get ();
990 do
991 {
992 mutt_extract_token (path, s, 0);
993 /*
994 * Check for deletion of entire list
995 */
996 if (mutt_strcmp (mutt_b2s (path), "*") == 0)
997 {
998 mutt_free_list (data);
999 break;
1000 }
1001 mutt_buffer_expand_path (path);
1002 remove_from_list (data, mutt_b2s (path));
1003 }
1004 while (MoreArgs (s));
1005 mutt_buffer_pool_release (&path);
1006
1007 return 0;
1008}
1009#endif /* USE_SIDEBAR */
1010
1011static int parse_lists (BUFFER *buf, BUFFER *s, union pointer_long_t udata, BUFFER *err)
1012{
1013 group_context_t *gc = NULL;
1014
1015 do
1016 {
1017 mutt_extract_token (buf, s, 0);
1018
1019 if (parse_group_context (&gc, buf, s, err) == -1)
1020 goto bail;
1021
1022 mutt_remove_from_rx_list (&UnMailLists, buf->data);
1023
1024 if (mutt_add_to_rx_list (&MailLists, buf->data, REG_ICASE, err) != 0)
1025 goto bail;
1026
1027 if (mutt_group_context_add_rx (gc, buf->data, REG_ICASE, err) != 0)
1028 goto bail;
1029 }
1030 while (MoreArgs (s));
1031
1032 mutt_group_context_destroy (&gc);
1033 return 0;
1034
1035bail:
1036 mutt_group_context_destroy (&gc);
1037 return -1;
1038}
1039
1040typedef enum group_state_t {
1041 NONE, RX, ADDR
1042} group_state_t;
1043
1044static int parse_group (BUFFER *buf, BUFFER *s, union pointer_long_t udata, BUFFER *err)
1045{
1046 group_context_t *gc = NULL;
1047 group_state_t state = NONE;
1048 ADDRESS *addr = NULL;
1049 char *estr = NULL;
1050 long data = udata.l;
1051
1052 do
1053 {
1054 mutt_extract_token (buf, s, 0);
1055 if (parse_group_context (&gc, buf, s, err) == -1)
1056 goto bail;
1057
1058 if (data == MUTT_UNGROUP && !mutt_strcasecmp (buf->data, "*"))
1059 {
1060 if (mutt_group_context_clear (&gc) < 0)
1061 goto bail;
1062 goto out;
1063 }
1064
1065 if (!mutt_strcasecmp (buf->data, "-rx"))
1066 state = RX;
1067 else if (!mutt_strcasecmp (buf->data, "-addr"))
1068 state = ADDR;
1069 else
1070 {
1071 switch (state)
1072 {
1073 case NONE:
1074 snprintf (err->data, err->dsize, _("%sgroup: missing -rx or -addr."),
1075 data == MUTT_UNGROUP ? "un" : "");
1076 goto bail;
1077
1078 case RX:
1079 if (data == MUTT_GROUP &&
1080 mutt_group_context_add_rx (gc, buf->data, REG_ICASE, err) != 0)
1081 goto bail;
1082 else if (data == MUTT_UNGROUP &&
1083 mutt_group_context_remove_rx (gc, buf->data) < 0)
1084 goto bail;
1085 break;
1086
1087 case ADDR:
1088 if ((addr = mutt_parse_adrlist (NULL, buf->data)) == NULL)
1089 goto bail;
1090 if (mutt_addrlist_to_intl (addr, &estr))
1091 {
1092 snprintf (err->data, err->dsize, _("%sgroup: warning: bad IDN '%s'.\n"),
1093 data == MUTT_UNGROUP ? "un" : "", estr);
1094 FREE (&estr);
1095 rfc822_free_address (&addr);
1096 goto bail;
1097 }
1098 if (data == MUTT_GROUP)
1099 mutt_group_context_add_adrlist (gc, addr);
1100 else if (data == MUTT_UNGROUP)
1101 mutt_group_context_remove_adrlist (gc, addr);
1102 rfc822_free_address (&addr);
1103 break;
1104 }
1105 }
1106 } while (MoreArgs (s));
1107
1108out:
1109 mutt_group_context_destroy (&gc);
1110 return 0;
1111
1112bail:
1113 mutt_group_context_destroy (&gc);
1114 return -1;
1115}
1116
1117/* always wise to do what someone else did before */
1118static void _attachments_clean (void)
1119{
1120 int i;
1121 if (Context && Context->msgcount)
1122 {
1123 for (i = 0; i < Context->msgcount; i++)
1124 Context->hdrs[i]->attach_valid = 0;
1125 }
1126}
1127
1128static int parse_attach_list (BUFFER *buf, BUFFER *s, LIST **ldata, BUFFER *err)
1129{
1130 ATTACH_MATCH *a;
1131 LIST *listp, *lastp;
1132 char *p;
1133 char *tmpminor;
1134 int len;
1135 int ret;
1136
1137 /* Find the last item in the list that data points to. */
1138 lastp = NULL;
1139 dprint(5, (debugfile, "parse_attach_list: ldata = %p, *ldata = %p\n",
1140 (void *)ldata, (void *)*ldata));
1141 for (listp = *ldata; listp; listp = listp->next)
1142 {
1143 a = (ATTACH_MATCH *)listp->data;
1144 dprint(5, (debugfile, "parse_attach_list: skipping %s/%s\n",
1145 a->major, a->minor));
1146 lastp = listp;
1147 }
1148
1149 do
1150 {
1151 mutt_extract_token (buf, s, 0);
1152
1153 if (!buf->data || *buf->data == '\0')
1154 continue;
1155
1156 a = safe_malloc(sizeof(ATTACH_MATCH));
1157
1158 /* some cheap hacks that I expect to remove */
1159 if (!ascii_strcasecmp(buf->data, "any"))
1160 a->major = safe_strdup("*/.*");
1161 else if (!ascii_strcasecmp(buf->data, "none"))
1162 a->major = safe_strdup("cheap_hack/this_should_never_match");
1163 else
1164 a->major = safe_strdup(buf->data);
1165
1166 if ((p = strchr(a->major, '/')))
1167 {
1168 *p = '\0';
1169 ++p;
1170 a->minor = p;
1171 }
1172 else
1173 {
1174 a->minor = "unknown";
1175 }
1176
1177 len = strlen(a->minor);
1178 tmpminor = safe_malloc(len+3);
1179 strcpy(&tmpminor[1], a->minor); /* __STRCPY_CHECKED__ */
1180 tmpminor[0] = '^';
1181 tmpminor[len+1] = '$';
1182 tmpminor[len+2] = '\0';
1183
1184 a->major_int = mutt_check_mime_type(a->major);
1185 ret = REGCOMP(&a->minor_rx, tmpminor, REG_ICASE);
1186
1187 FREE(&tmpminor);
1188
1189 if (ret)
1190 {
1191 regerror(ret, &a->minor_rx, err->data, err->dsize);
1192 FREE(&a->major);
1193 FREE(&a);
1194 return -1;
1195 }
1196
1197 dprint(5, (debugfile, "parse_attach_list: added %s/%s [%d]\n",
1198 a->major, a->minor, a->major_int));
1199
1200 listp = safe_malloc(sizeof(LIST));
1201 listp->data = (char *)a;
1202 listp->next = NULL;
1203 if (lastp)
1204 {
1205 lastp->next = listp;
1206 }
1207 else
1208 {
1209 *ldata = listp;
1210 }
1211 lastp = listp;
1212 }
1213 while (MoreArgs (s));
1214
1215 _attachments_clean();
1216 return 0;
1217}
1218
1219static int parse_unattach_list (BUFFER *buf, BUFFER *s, LIST **ldata, BUFFER *err)
1220{
1221 ATTACH_MATCH *a;
1222 LIST *lp, *lastp, *newlp;
1223 char *tmp;
1224 int major;
1225 char *minor;
1226
1227 do
1228 {
1229 mutt_extract_token (buf, s, 0);
1230
1231 if (!ascii_strcasecmp(buf->data, "any"))
1232 tmp = safe_strdup("*/.*");
1233 else if (!ascii_strcasecmp(buf->data, "none"))
1234 tmp = safe_strdup("cheap_hack/this_should_never_match");
1235 else
1236 tmp = safe_strdup(buf->data);
1237
1238 if ((minor = strchr(tmp, '/')))
1239 {
1240 *minor = '\0';
1241 ++minor;
1242 }
1243 else
1244 {
1245 minor = "unknown";
1246 }
1247 major = mutt_check_mime_type(tmp);
1248
1249 /* We must do our own walk here because remove_from_list() will only
1250 * remove the LIST->data, not anything pointed to by the LIST->data. */
1251 lastp = NULL;
1252 for (lp = *ldata; lp; )
1253 {
1254 a = (ATTACH_MATCH *)lp->data;
1255 dprint(5, (debugfile, "parse_unattach_list: check %s/%s [%d] : %s/%s [%d]\n",
1256 a->major, a->minor, a->major_int, tmp, minor, major));
1257 if (a->major_int == major && !mutt_strcasecmp(minor, a->minor))
1258 {
1259 dprint(5, (debugfile, "parse_unattach_list: removed %s/%s [%d]\n",
1260 a->major, a->minor, a->major_int));
1261 regfree(&a->minor_rx);
1262 FREE(&a->major);
1263
1264 /* Relink backward */
1265 if (lastp)
1266 lastp->next = lp->next;
1267 else
1268 *ldata = lp->next;
1269
1270 newlp = lp->next;
1271 FREE(&lp->data); /* same as a */
1272 FREE(&lp);
1273 lp = newlp;
1274 continue;
1275 }
1276
1277 lastp = lp;
1278 lp = lp->next;
1279 }
1280
1281 }
1282 while (MoreArgs (s));
1283
1284 FREE(&tmp);
1285 _attachments_clean();
1286 return 0;
1287}
1288
1289static int print_attach_list (LIST *lp, char op, char *name)
1290{
1291 while (lp)
1292 {
1293 printf("attachments %c%s %s/%s\n", op, name,
1294 ((ATTACH_MATCH *)lp->data)->major,
1295 ((ATTACH_MATCH *)lp->data)->minor);
1296 lp = lp->next;
1297 }
1298
1299 return 0;
1300}
1301
1302
1303static int parse_attachments (BUFFER *buf, BUFFER *s, union pointer_long_t udata, BUFFER *err)
1304{
1305 char op, *category;
1306 LIST **listp;
1307
1308 mutt_extract_token(buf, s, 0);
1309 if (!buf->data || *buf->data == '\0')
1310 {
1311 strfcpy(err->data, _("attachments: no disposition"), err->dsize);
1312 return -1;
1313 }
1314
1315 category = buf->data;
1316 op = *category++;
1317
1318 if (op == '?')
1319 {
1320 mutt_endwin (NULL);
1321 fflush (stdout);
1322 printf("\nCurrent attachments settings:\n\n");
1323 print_attach_list(AttachAllow, '+', "A");
1324 print_attach_list(AttachExclude, '-', "A");
1325 print_attach_list(InlineAllow, '+', "I");
1326 print_attach_list(InlineExclude, '-', "I");
1327 mutt_any_key_to_continue (NULL);
1328 return 0;
1329 }
1330
1331 if (op != '+' && op != '-')
1332 {
1333 op = '+';
1334 category--;
1335 }
1336 if (!ascii_strncasecmp(category, "attachment", strlen(category)))
1337 {
1338 if (op == '+')
1339 listp = &AttachAllow;
1340 else
1341 listp = &AttachExclude;
1342 }
1343 else if (!ascii_strncasecmp(category, "inline", strlen(category)))
1344 {
1345 if (op == '+')
1346 listp = &InlineAllow;
1347 else
1348 listp = &InlineExclude;
1349 }
1350 else
1351 {
1352 strfcpy(err->data, _("attachments: invalid disposition"), err->dsize);
1353 return -1;
1354 }
1355
1356 return parse_attach_list(buf, s, listp, err);
1357}
1358
1359/*
1360 * Cleanup a single LIST item who's data is an ATTACH_MATCH
1361 */
1362static void free_attachments_data (char **data)
1363{
1364 if (data == NULL || *data == NULL) return;
1365 ATTACH_MATCH *a = (ATTACH_MATCH *) *data;
1366 regfree(&a->minor_rx);
1367 FREE (&a->major);
1368 FREE (data); /*__FREE_CHECKED__*/
1369}
1370
1371static int parse_unattachments (BUFFER *buf, BUFFER *s, union pointer_long_t udata, BUFFER *err)
1372{
1373 char op, *p;
1374 LIST **listp;
1375
1376 mutt_extract_token(buf, s, 0);
1377 if (!buf->data || *buf->data == '\0')
1378 {
1379 strfcpy(err->data, _("unattachments: no disposition"), err->dsize);
1380 return -1;
1381 }
1382
1383 p = buf->data;
1384 op = *p++;
1385
1386 if (op == '*')
1387 {
1388 mutt_free_list_generic(&AttachAllow, free_attachments_data);
1389 mutt_free_list_generic(&AttachExclude, free_attachments_data);
1390 mutt_free_list_generic(&InlineAllow, free_attachments_data);
1391 mutt_free_list_generic(&InlineExclude, free_attachments_data);
1392 _attachments_clean();
1393 return 0;
1394 }
1395
1396 if (op != '+' && op != '-')
1397 {
1398 op = '+';
1399 p--;
1400 }
1401 if (!ascii_strncasecmp(p, "attachment", strlen(p)))
1402 {
1403 if (op == '+')
1404 listp = &AttachAllow;
1405 else
1406 listp = &AttachExclude;
1407 }
1408 else if (!ascii_strncasecmp(p, "inline", strlen(p)))
1409 {
1410 if (op == '+')
1411 listp = &InlineAllow;
1412 else
1413 listp = &InlineExclude;
1414 }
1415 else
1416 {
1417 strfcpy(err->data, _("unattachments: invalid disposition"), err->dsize);
1418 return -1;
1419 }
1420
1421 return parse_unattach_list(buf, s, listp, err);
1422}
1423
1424static int parse_unlists (BUFFER *buf, BUFFER *s, union pointer_long_t udata, BUFFER *err)
1425{
1426 hash_destroy (&AutoSubscribeCache, NULL);
1427 do
1428 {
1429 mutt_extract_token (buf, s, 0);
1430 mutt_remove_from_rx_list (&SubscribedLists, buf->data);
1431 mutt_remove_from_rx_list (&MailLists, buf->data);
1432
1433 if (mutt_strcmp (buf->data, "*") &&
1434 mutt_add_to_rx_list (&UnMailLists, buf->data, REG_ICASE, err) != 0)
1435 return -1;
1436 }
1437 while (MoreArgs (s));
1438
1439 return 0;
1440}
1441
1442static int parse_subscribe (BUFFER *buf, BUFFER *s, union pointer_long_t udata, BUFFER *err)
1443{
1444 group_context_t *gc = NULL;
1445
1446 do
1447 {
1448 mutt_extract_token (buf, s, 0);
1449
1450 if (parse_group_context (&gc, buf, s, err) == -1)
1451 goto bail;
1452
1453 mutt_remove_from_rx_list (&UnMailLists, buf->data);
1454 mutt_remove_from_rx_list (&UnSubscribedLists, buf->data);
1455
1456 if (mutt_add_to_rx_list (&MailLists, buf->data, REG_ICASE, err) != 0)
1457 goto bail;
1458 if (mutt_add_to_rx_list (&SubscribedLists, buf->data, REG_ICASE, err) != 0)
1459 goto bail;
1460 if (mutt_group_context_add_rx (gc, buf->data, REG_ICASE, err) != 0)
1461 goto bail;
1462 }
1463 while (MoreArgs (s));
1464
1465 mutt_group_context_destroy (&gc);
1466 return 0;
1467
1468bail:
1469 mutt_group_context_destroy (&gc);
1470 return -1;
1471}
1472
1473static int parse_unsubscribe (BUFFER *buf, BUFFER *s, union pointer_long_t udata, BUFFER *err)
1474{
1475 hash_destroy (&AutoSubscribeCache, NULL);
1476 do
1477 {
1478 mutt_extract_token (buf, s, 0);
1479 mutt_remove_from_rx_list (&SubscribedLists, buf->data);
1480
1481 if (mutt_strcmp (buf->data, "*") &&
1482 mutt_add_to_rx_list (&UnSubscribedLists, buf->data, REG_ICASE, err) != 0)
1483 return -1;
1484 }
1485 while (MoreArgs (s));
1486
1487 return 0;
1488}
1489
1490static int parse_unalias (BUFFER *buf, BUFFER *s, union pointer_long_t udata, BUFFER *err)
1491{
1492 ALIAS *tmp, *last = NULL;
1493
1494 do
1495 {
1496 mutt_extract_token (buf, s, 0);
1497
1498 if (mutt_strcmp ("*", buf->data) == 0)
1499 {
1500 if (CurrentMenu == MENU_ALIAS)
1501 {
1502 for (tmp = Aliases; tmp ; tmp = tmp->next)
1503 tmp->del = 1;
1504 mutt_set_current_menu_redraw_full ();
1505 }
1506 else
1507 mutt_free_alias (&Aliases);
1508 break;
1509 }
1510 else
1511 for (tmp = Aliases; tmp; tmp = tmp->next)
1512 {
1513 if (mutt_strcasecmp (buf->data, tmp->name) == 0)
1514 {
1515 if (CurrentMenu == MENU_ALIAS)
1516 {
1517 tmp->del = 1;
1518 mutt_set_current_menu_redraw_full ();
1519 break;
1520 }
1521
1522 if (last)
1523 last->next = tmp->next;
1524 else
1525 Aliases = tmp->next;
1526 tmp->next = NULL;
1527 mutt_free_alias (&tmp);
1528 break;
1529 }
1530 last = tmp;
1531 }
1532 }
1533 while (MoreArgs (s));
1534 return 0;
1535}
1536
1537static int parse_alias (BUFFER *buf, BUFFER *s, union pointer_long_t udata, BUFFER *err)
1538{
1539 ALIAS *tmp = Aliases;
1540 ALIAS *last = NULL;
1541 char *estr = NULL;
1542 group_context_t *gc = NULL;
1543
1544 if (!MoreArgs (s))
1545 {
1546 strfcpy (err->data, _("alias: no address"), err->dsize);
1547 return (-1);
1548 }
1549
1550 mutt_extract_token (buf, s, 0);
1551
1552 if (parse_group_context (&gc, buf, s, err) == -1)
1553 return -1;
1554
1555 /* check to see if an alias with this name already exists */
1556 for (; tmp; tmp = tmp->next)
1557 {
1558 if (!mutt_strcasecmp (tmp->name, buf->data))
1559 break;
1560 last = tmp;
1561 }
1562
1563 if (!tmp)
1564 {
1565 /* create a new alias */
1566 tmp = (ALIAS *) safe_calloc (1, sizeof (ALIAS));
1567 tmp->self = tmp;
1568 tmp->name = safe_strdup (buf->data);
1569 /* give the main addressbook code a chance */
1570 if (CurrentMenu == MENU_ALIAS)
1571 set_option (OPTMENUCALLER);
1572 }
1573 else
1574 {
1575 mutt_alias_delete_reverse (tmp);
1576 /* override the previous value */
1577 rfc822_free_address (&tmp->addr);
1578 if (CurrentMenu == MENU_ALIAS)
1579 mutt_set_current_menu_redraw_full ();
1580 }
1581
1582 mutt_extract_token (buf, s, MUTT_TOKEN_QUOTE | MUTT_TOKEN_SPACE | MUTT_TOKEN_SEMICOLON);
1583 dprint (3, (debugfile, "parse_alias: Second token is '%s'.\n",
1584 buf->data));
1585
1586 tmp->addr = mutt_parse_adrlist (tmp->addr, buf->data);
1587
1588 if (last)
1589 last->next = tmp;
1590 else
1591 Aliases = tmp;
1592 if (mutt_addrlist_to_intl (tmp->addr, &estr))
1593 {
1594 snprintf (err->data, err->dsize, _("Warning: Bad IDN '%s' in alias '%s'.\n"),
1595 estr, tmp->name);
1596 FREE (&estr);
1597 goto bail;
1598 }
1599
1600 mutt_group_context_add_adrlist (gc, tmp->addr);
1601 mutt_alias_add_reverse (tmp);
1602
1603#ifdef DEBUG
1604 if (debuglevel >= 2)
1605 {
1606 ADDRESS *a;
1607 /* A group is terminated with an empty address, so check a->mailbox */
1608 for (a = tmp->addr; a && a->mailbox; a = a->next)
1609 {
1610 if (!a->group)
1611 dprint (3, (debugfile, "parse_alias: %s\n",
1612 a->mailbox));
1613 else
1614 dprint (3, (debugfile, "parse_alias: Group %s\n",
1615 a->mailbox));
1616 }
1617 }
1618#endif
1619 mutt_group_context_destroy (&gc);
1620 return 0;
1621
1622bail:
1623 mutt_group_context_destroy (&gc);
1624 return -1;
1625}
1626
1627static int
1628parse_unmy_hdr (BUFFER *buf, BUFFER *s, union pointer_long_t udata, BUFFER *err)
1629{
1630 LIST *last = NULL;
1631 LIST *tmp = UserHeader;
1632 LIST *ptr;
1633 size_t l;
1634
1635 do
1636 {
1637 mutt_extract_token (buf, s, 0);
1638 if (mutt_strcmp ("*", buf->data) == 0)
1639 mutt_free_list (&UserHeader);
1640 else
1641 {
1642 tmp = UserHeader;
1643 last = NULL;
1644
1645 l = mutt_strlen (buf->data);
1646 if (buf->data[l - 1] == ':')
1647 l--;
1648
1649 while (tmp)
1650 {
1651 if (ascii_strncasecmp (buf->data, tmp->data, l) == 0 && tmp->data[l] == ':')
1652 {
1653 ptr = tmp;
1654 if (last)
1655 last->next = tmp->next;
1656 else
1657 UserHeader = tmp->next;
1658 tmp = tmp->next;
1659 ptr->next = NULL;
1660 mutt_free_list (&ptr);
1661 }
1662 else
1663 {
1664 last = tmp;
1665 tmp = tmp->next;
1666 }
1667 }
1668 }
1669 }
1670 while (MoreArgs (s));
1671 return 0;
1672}
1673
1674static int parse_my_hdr (BUFFER *buf, BUFFER *s, union pointer_long_t udata, BUFFER *err)
1675{
1676 LIST *tmp;
1677 size_t keylen;
1678 char *p;
1679
1680 mutt_extract_token (buf, s, MUTT_TOKEN_SPACE | MUTT_TOKEN_QUOTE);
1681 if ((p = strpbrk (buf->data, ": \t")) == NULL || *p != ':')
1682 {
1683 strfcpy (err->data, _("invalid header field"), err->dsize);
1684 return (-1);
1685 }
1686 keylen = p - buf->data + 1;
1687
1688 if (UserHeader)
1689 {
1690 for (tmp = UserHeader; ; tmp = tmp->next)
1691 {
1692 /* see if there is already a field by this name */
1693 if (ascii_strncasecmp (buf->data, tmp->data, keylen) == 0)
1694 {
1695 /* replace the old value */
1696 FREE (&tmp->data);
1697 tmp->data = buf->data;
1698 mutt_buffer_init (buf);
1699 return 0;
1700 }
1701 if (!tmp->next)
1702 break;
1703 }
1704 tmp->next = mutt_new_list ();
1705 tmp = tmp->next;
1706 }
1707 else
1708 {
1709 tmp = mutt_new_list ();
1710 UserHeader = tmp;
1711 }
1712 tmp->data = buf->data;
1713 mutt_buffer_init (buf);
1714 return 0;
1715}
1716
1717static int
1718parse_sort (short *val, const char *s, const struct mapping_t *map, BUFFER *err)
1719{
1720 int i, flags = 0;
1721
1722 if (mutt_strncmp ("reverse-", s, 8) == 0)
1723 {
1724 s += 8;
1725 flags = SORT_REVERSE;
1726 }
1727
1728 if (mutt_strncmp ("last-", s, 5) == 0)
1729 {
1730 s += 5;
1731 flags |= SORT_LAST;
1732 }
1733
1734 if ((i = mutt_getvaluebyname (s, map)) == -1)
1735 {
1736 snprintf (err->data, err->dsize, _("%s: unknown sorting method"), s);
1737 return (-1);
1738 }
1739
1740 *val = i | flags;
1741
1742 return 0;
1743}
1744
1745static void mutt_set_default (struct option_t *p)
1746{
1747 switch (p->type & DT_MASK)
1748 {
1749 case DT_STR:
1750 case DT_PATH:
1751 if (!p->init.p && *((char **) p->data.p))
1752 p->init.p = safe_strdup (* ((char **) p->data.p));
1753 break;
1754 case DT_ADDR:
1755 if (!p->init.p && *((ADDRESS **) p->data.p))
1756 {
1757 char tmp[HUGE_STRING];
1758 *tmp = '\0';
1759 rfc822_write_address (tmp, sizeof (tmp), *((ADDRESS **) p->data.p), 0);
1760 p->init.p = safe_strdup (tmp);
1761 }
1762 break;
1763 case DT_RX:
1764 {
1765 REGEXP *pp = (REGEXP *) p->data.p;
1766 if (!p->init.p && pp->pattern)
1767 p->init.p = safe_strdup (pp->pattern);
1768 break;
1769 }
1770 }
1771}
1772
1773static void mutt_restore_default (struct option_t *p)
1774{
1775 switch (p->type & DT_MASK)
1776 {
1777 case DT_STR:
1778 mutt_str_replace ((char **) p->data.p, (char *) p->init.p);
1779 break;
1780 case DT_MBCHARTBL:
1781 free_mbchar_table ((mbchar_table **)p->data.p);
1782 *((mbchar_table **) p->data.p) = parse_mbchar_table ((char *) p->init.p);
1783 break;
1784 case DT_PATH:
1785 FREE((char **) p->data.p); /* __FREE_CHECKED__ */
1786 if (p->init.p)
1787 {
1788 BUFFER *path;
1789 path = mutt_buffer_pool_get ();
1790 mutt_buffer_strcpy (path, (char *) p->init.p);
1791 mutt_buffer_expand_path (path);
1792 *((char **) p->data.p) = safe_strdup (mutt_b2s (path));
1793 mutt_buffer_pool_release (&path);
1794 }
1795 break;
1796 case DT_ADDR:
1797 rfc822_free_address ((ADDRESS **) p->data.p);
1798 if (p->init.p)
1799 *((ADDRESS **) p->data.p) = rfc822_parse_adrlist (NULL, (char *) p->init.p);
1800 break;
1801 case DT_BOOL:
1802 if (p->init.l)
1803 set_option (p->data.l);
1804 else
1805 unset_option (p->data.l);
1806 break;
1807 case DT_QUAD:
1808 set_quadoption (p->data.l, p->init.l);
1809 break;
1810 case DT_NUM:
1811 case DT_SORT:
1812 case DT_MAGIC:
1813 *((short *) p->data.p) = p->init.l;
1814 break;
1815 case DT_LNUM:
1816 *((long *) p->data.p) = p->init.l;
1817 break;
1818 case DT_RX:
1819 {
1820 REGEXP *pp = (REGEXP *) p->data.p;
1821 int flags = 0;
1822
1823 FREE (&pp->pattern);
1824 if (pp->rx)
1825 {
1826 regfree (pp->rx);
1827 FREE (&pp->rx);
1828 }
1829
1830 if (p->init.p)
1831 {
1832 char *s = (char *) p->init.p;
1833
1834 pp->rx = safe_calloc (1, sizeof (regex_t));
1835 pp->pattern = safe_strdup ((char *) p->init.p);
1836 if (mutt_strcmp (p->option, "mask") != 0)
1837 flags |= mutt_which_case ((const char *) p->init.p);
1838 if (mutt_strcmp (p->option, "mask") == 0 && *s == '!')
1839 {
1840 s++;
1841 pp->not = 1;
1842 }
1843 if (REGCOMP (pp->rx, s, flags) != 0)
1844 {
1845 fprintf (stderr, _("mutt_restore_default(%s): error in regexp: %s\n"),
1846 p->option, pp->pattern);
1847 FREE (&pp->pattern);
1848 FREE (&pp->rx);
1849 }
1850 }
1851 }
1852 break;
1853 }
1854
1855 if (p->flags & R_INDEX)
1856 mutt_set_menu_redraw_full (MENU_MAIN);
1857 if (p->flags & R_PAGER)
1858 mutt_set_menu_redraw_full (MENU_PAGER);
1859 if (p->flags & R_PAGER_FLOW)
1860 {
1861 mutt_set_menu_redraw_full (MENU_PAGER);
1862 mutt_set_menu_redraw (MENU_PAGER, REDRAW_FLOW);
1863 }
1864 if (p->flags & R_RESORT_SUB)
1865 set_option (OPTSORTSUBTHREADS);
1866 if (p->flags & R_RESORT)
1867 set_option (OPTNEEDRESORT);
1868 if (p->flags & R_RESORT_INIT)
1869 set_option (OPTRESORTINIT);
1870 if (p->flags & R_TREE)
1871 set_option (OPTREDRAWTREE);
1872 if (p->flags & R_REFLOW)
1873 mutt_reflow_windows ();
1874#ifdef USE_SIDEBAR
1875 if (p->flags & R_SIDEBAR)
1876 mutt_set_current_menu_redraw (REDRAW_SIDEBAR);
1877#endif
1878 if (p->flags & R_MENU)
1879 mutt_set_current_menu_redraw_full ();
1880}
1881
1882static size_t escape_string (char *dst, size_t len, const char* src)
1883{
1884 char* p = dst;
1885
1886 if (!len)
1887 return 0;
1888 len--; /* save room for \0 */
1889#define ESC_CHAR(C) do { *p++ = '\\'; if (p - dst < len) *p++ = C; } while (0)
1890 while (p - dst < len && src && *src)
1891 {
1892 switch (*src)
1893 {
1894 case '\n':
1895 ESC_CHAR('n');
1896 break;
1897 case '\r':
1898 ESC_CHAR('r');
1899 break;
1900 case '\t':
1901 ESC_CHAR('t');
1902 break;
1903 default:
1904 if ((*src == '\\' || *src == '"') && p - dst < len - 1)
1905 *p++ = '\\';
1906 *p++ = *src;
1907 }
1908 src++;
1909 }
1910#undef ESC_CHAR
1911 *p = '\0';
1912 return p - dst;
1913}
1914
1915static void pretty_var (char *dst, size_t len, const char *option, const char *val)
1916{
1917 char *p;
1918
1919 if (!len)
1920 return;
1921
1922 strfcpy (dst, option, len);
1923 len--; /* save room for \0 */
1924 p = dst + mutt_strlen (dst);
1925
1926 if (p - dst < len)
1927 *p++ = '=';
1928 if (p - dst < len)
1929 *p++ = '"';
1930 p += escape_string (p, len - (p - dst) + 1, val); /* \0 terminate it */
1931 if (p - dst < len)
1932 *p++ = '"';
1933 *p = 0;
1934}
1935
1936static int check_charset (struct option_t *opt, const char *val)
1937{
1938 char *p, *q = NULL, *s = safe_strdup (val);
1939 int rc = 0, strict = strcmp (opt->option, "send_charset") == 0;
1940
1941 /* $charset should be nonempty, and a single value - not a colon
1942 * delimited list */
1943 if (mutt_strcmp (opt->option, "charset") == 0)
1944 {
1945 if (!s || (strchr (s, ':') != NULL))
1946 {
1947 FREE (&s);
1948 return -1;
1949 }
1950 }
1951
1952 /* other values can be empty */
1953 if (!s)
1954 return rc;
1955
1956 for (p = strtok_r (s, ":", &q); p; p = strtok_r (NULL, ":", &q))
1957 {
1958 if (!*p)
1959 continue;
1960 if (mutt_check_charset (p, strict) < 0)
1961 {
1962 rc = -1;
1963 break;
1964 }
1965 }
1966
1967 FREE(&s);
1968 return rc;
1969}
1970
1971char **mutt_envlist (void)
1972{
1973 return envlist;
1974}
1975
1976/* Helper function for parse_setenv().
1977 * It's broken out because some other parts of mutt (filter.c) need
1978 * to set/overwrite environment variables in envlist before execing.
1979 */
1980void mutt_envlist_set (const char *name, const char *value, int overwrite)
1981{
1982 char **envp = envlist;
1983 char work[LONG_STRING];
1984 int count, len;
1985
1986 len = mutt_strlen (name);
1987
1988 /* Look for current slot to overwrite */
1989 count = 0;
1990 while (envp && *envp)
1991 {
1992 if (!mutt_strncmp (name, *envp, len) && (*envp)[len] == '=')
1993 {
1994 if (!overwrite)
1995 return;
1996 break;
1997 }
1998 envp++;
1999 count++;
2000 }
2001
2002 /* Format var=value string */
2003 snprintf (work, sizeof(work), "%s=%s", NONULL (name), NONULL (value));
2004
2005 /* If slot found, overwrite */
2006 if (envp && *envp)
2007 mutt_str_replace (envp, work);
2008
2009 /* If not found, add new slot */
2010 else
2011 {
2012 safe_realloc (&envlist, sizeof(char *) * (count + 2));
2013 envlist[count] = safe_strdup (work);
2014 envlist[count + 1] = NULL;
2015 }
2016}
2017
2018static int parse_setenv(BUFFER *tmp, BUFFER *s, union pointer_long_t udata, BUFFER *err)
2019{
2020 int query, unset, len;
2021 char *name, **save, **envp = envlist;
2022 int count = 0;
2023 long data = udata.l;
2024
2025 query = 0;
2026 unset = data & MUTT_SET_UNSET;
2027
2028 if (!MoreArgs (s))
2029 {
2030 strfcpy (err->data, _("too few arguments"), err->dsize);
2031 return -1;
2032 }
2033
2034 if (*s->dptr == '?')
2035 {
2036 query = 1;
2037 s->dptr++;
2038 }
2039
2040 /* get variable name */
2041 mutt_extract_token (tmp, s, MUTT_TOKEN_EQUAL);
2042 len = strlen (tmp->data);
2043
2044 if (query)
2045 {
2046 int found = 0;
2047 while (envp && *envp)
2048 {
2049 if (!mutt_strncmp (tmp->data, *envp, len))
2050 {
2051 if (!found)
2052 {
2053 mutt_endwin (NULL);
2054 found = 1;
2055 }
2056 puts (*envp);
2057 }
2058 envp++;
2059 }
2060
2061 if (found)
2062 {
2063 mutt_any_key_to_continue (NULL);
2064 return 0;
2065 }
2066
2067 snprintf (err->data, err->dsize, _("%s is unset"), tmp->data);
2068 return -1;
2069 }
2070
2071 if (unset)
2072 {
2073 count = 0;
2074 while (envp && *envp)
2075 {
2076 if (!mutt_strncmp (tmp->data, *envp, len) && (*envp)[len] == '=')
2077 {
2078 /* shuffle down */
2079 save = envp++;
2080 while (*envp)
2081 {
2082 *save++ = *envp++;
2083 count++;
2084 }
2085 *save = NULL;
2086 safe_realloc (&envlist, sizeof(char *) * (count+1));
2087 return 0;
2088 }
2089 envp++;
2090 count++;
2091 }
2092 return -1;
2093 }
2094
2095 /* set variable */
2096
2097 if (*s->dptr == '=')
2098 {
2099 s->dptr++;
2100 SKIPWS (s->dptr);
2101 }
2102
2103 if (!MoreArgs (s))
2104 {
2105 strfcpy (err->data, _("too few arguments"), err->dsize);
2106 return -1;
2107 }
2108
2109 name = safe_strdup (tmp->data);
2110 mutt_extract_token (tmp, s, 0);
2111 mutt_envlist_set (name, tmp->data, 1);
2112 FREE (&name);
2113
2114 return 0;
2115}
2116
2117static int parse_set (BUFFER *tmp, BUFFER *s, union pointer_long_t udata, BUFFER *err)
2118{
2119 int query, unset, inv, reset, r = 0;
2120 int idx = -1;
2121 const char *p;
2122 BUFFER *scratch = NULL;
2123 char* myvar;
2124 long data = udata.l;
2125
2126 while (MoreArgs (s))
2127 {
2128 /* reset state variables */
2129 query = 0;
2130 unset = data & MUTT_SET_UNSET;
2131 inv = data & MUTT_SET_INV;
2132 reset = data & MUTT_SET_RESET;
2133 myvar = NULL;
2134
2135 if (*s->dptr == '?')
2136 {
2137 query = 1;
2138 s->dptr++;
2139 }
2140 else if (mutt_strncmp ("no", s->dptr, 2) == 0)
2141 {
2142 s->dptr += 2;
2143 unset = !unset;
2144 }
2145 else if (mutt_strncmp ("inv", s->dptr, 3) == 0)
2146 {
2147 s->dptr += 3;
2148 inv = !inv;
2149 }
2150 else if (*s->dptr == '&')
2151 {
2152 reset = 1;
2153 s->dptr++;
2154 }
2155
2156 /* get the variable name */
2157 mutt_extract_token (tmp, s, MUTT_TOKEN_EQUAL);
2158
2159 if (!mutt_strncmp ("my_", tmp->data, 3))
2160 myvar = tmp->data;
2161 else if ((idx = mutt_option_index (tmp->data)) == -1 &&
2162 !(reset && !mutt_strcmp ("all", tmp->data)))
2163 {
2164 snprintf (err->data, err->dsize, _("%s: unknown variable"), tmp->data);
2165 return (-1);
2166 }
2167 SKIPWS (s->dptr);
2168
2169 if (reset)
2170 {
2171 if (query || unset || inv)
2172 {
2173 snprintf (err->data, err->dsize, "%s", _("prefix is illegal with reset"));
2174 return (-1);
2175 }
2176
2177 if (s && *s->dptr == '=')
2178 {
2179 snprintf (err->data, err->dsize, "%s", _("value is illegal with reset"));
2180 return (-1);
2181 }
2182
2183 if (!mutt_strcmp ("all", tmp->data))
2184 {
2185 if (CurrentMenu == MENU_PAGER)
2186 {
2187 snprintf (err->data, err->dsize, "%s", _("Not available in this menu."));
2188 return (-1);
2189 }
2190 for (idx = 0; MuttVars[idx].option; idx++)
2191 mutt_restore_default (&MuttVars[idx]);
2192 mutt_set_current_menu_redraw_full ();
2193 set_option (OPTSORTSUBTHREADS);
2194 set_option (OPTNEEDRESORT);
2195 set_option (OPTRESORTINIT);
2196 set_option (OPTREDRAWTREE);
2197 return 0;
2198 }
2199 else
2200 {
2201 CHECK_PAGER;
2202 if (myvar)
2203 myvar_del (myvar);
2204 else
2205 mutt_restore_default (&MuttVars[idx]);
2206 }
2207 }
2208 else if (!myvar && DTYPE (MuttVars[idx].type) == DT_BOOL)
2209 {
2210 if (s && *s->dptr == '=')
2211 {
2212 if (unset || inv || query)
2213 {
2214 snprintf (err->data, err->dsize, "%s", _("Usage: set variable=yes|no"));
2215 return (-1);
2216 }
2217
2218 s->dptr++;
2219 mutt_extract_token (tmp, s, 0);
2220 if (ascii_strcasecmp ("yes", tmp->data) == 0)
2221 unset = inv = 0;
2222 else if (ascii_strcasecmp ("no", tmp->data) == 0)
2223 unset = 1;
2224 else
2225 {
2226 snprintf (err->data, err->dsize, "%s", _("Usage: set variable=yes|no"));
2227 return (-1);
2228 }
2229 }
2230
2231 if (query)
2232 {
2233 snprintf (err->data, err->dsize, option (MuttVars[idx].data.l)
2234 ? _("%s is set") : _("%s is unset"), tmp->data);
2235 return 0;
2236 }
2237
2238 CHECK_PAGER;
2239 if (unset)
2240 unset_option (MuttVars[idx].data.l);
2241 else if (inv)
2242 toggle_option (MuttVars[idx].data.l);
2243 else
2244 set_option (MuttVars[idx].data.l);
2245 }
2246 else if (myvar || DTYPE (MuttVars[idx].type) == DT_STR ||
2247 DTYPE (MuttVars[idx].type) == DT_PATH ||
2248 DTYPE (MuttVars[idx].type) == DT_ADDR ||
2249 DTYPE (MuttVars[idx].type) == DT_MBCHARTBL)
2250 {
2251 if (unset)
2252 {
2253 CHECK_PAGER;
2254 if (myvar)
2255 myvar_del (myvar);
2256 else if (DTYPE (MuttVars[idx].type) == DT_ADDR)
2257 rfc822_free_address ((ADDRESS **) MuttVars[idx].data.p);
2258 else if (DTYPE (MuttVars[idx].type) == DT_MBCHARTBL)
2259 free_mbchar_table ((mbchar_table **) MuttVars[idx].data.p);
2260 else
2261 /* MuttVars[idx].data.p is already 'char**' (or some 'void**') or...
2262 * so cast to 'void*' is okay */
2263 FREE (MuttVars[idx].data.p); /* __FREE_CHECKED__ */
2264 }
2265 else if (query || *s->dptr != '=')
2266 {
2267 char _tmp[LONG_STRING];
2268 const char *val = NULL;
2269
2270 if (myvar)
2271 {
2272 if ((val = myvar_get (myvar)))
2273 {
2274 pretty_var (err->data, err->dsize, myvar, val);
2275 break;
2276 }
2277 else
2278 {
2279 snprintf (err->data, err->dsize, _("%s: unknown variable"), myvar);
2280 return (-1);
2281 }
2282 }
2283 else if (DTYPE (MuttVars[idx].type) == DT_ADDR)
2284 {
2285 _tmp[0] = '\0';
2286 rfc822_write_address (_tmp, sizeof (_tmp), *((ADDRESS **) MuttVars[idx].data.p), 0);
2287 val = _tmp;
2288 }
2289 else if (DTYPE (MuttVars[idx].type) == DT_PATH)
2290 {
2291 _tmp[0] = '\0';
2292 strfcpy (_tmp, NONULL(*((char **) MuttVars[idx].data.p)), sizeof (_tmp));
2293 mutt_pretty_mailbox (_tmp, sizeof (_tmp));
2294 val = _tmp;
2295 }
2296 else if (DTYPE (MuttVars[idx].type) == DT_MBCHARTBL)
2297 {
2298 mbchar_table *mbt = (*((mbchar_table **) MuttVars[idx].data.p));
2299 val = mbt ? NONULL (mbt->orig_str) : "";
2300 }
2301 else
2302 val = *((char **) MuttVars[idx].data.p);
2303
2304 /* user requested the value of this variable */
2305 pretty_var (err->data, err->dsize, MuttVars[idx].option, NONULL(val));
2306 break;
2307 }
2308 else
2309 {
2310 CHECK_PAGER;
2311 s->dptr++;
2312
2313 if (myvar)
2314 {
2315 /* myvar is a pointer to tmp and will be lost on extract_token */
2316 myvar = safe_strdup (myvar);
2317 myvar_del (myvar);
2318 }
2319
2320 mutt_extract_token (tmp, s, 0);
2321
2322 if (myvar)
2323 {
2324 myvar_set (myvar, tmp->data);
2325 FREE (&myvar);
2326 myvar="don't resort";
2327 }
2328 else if (DTYPE (MuttVars[idx].type) == DT_PATH)
2329 {
2330 /* MuttVars[idx].data is already 'char**' (or some 'void**') or...
2331 * so cast to 'void*' is okay */
2332 FREE (MuttVars[idx].data.p); /* __FREE_CHECKED__ */
2333
2334 scratch = mutt_buffer_pool_get ();
2335 mutt_buffer_strcpy (scratch, tmp->data);
2336 mutt_buffer_expand_path (scratch);
2337 *((char **) MuttVars[idx].data.p) = safe_strdup (mutt_b2s (scratch));
2338 mutt_buffer_pool_release (&scratch);
2339 }
2340 else if (DTYPE (MuttVars[idx].type) == DT_STR)
2341 {
2342 if (strstr (MuttVars[idx].option, "charset") &&
2343 check_charset (&MuttVars[idx], tmp->data) < 0)
2344 {
2345 snprintf (err->data, err->dsize, _("Invalid value for option %s: \"%s\""),
2346 MuttVars[idx].option, tmp->data);
2347 return (-1);
2348 }
2349
2350 FREE (MuttVars[idx].data.p); /* __FREE_CHECKED__ */
2351 *((char **) MuttVars[idx].data.p) = safe_strdup (tmp->data);
2352 if (mutt_strcmp (MuttVars[idx].option, "charset") == 0)
2353 mutt_set_charset (Charset);
2354 }
2355 else if (DTYPE (MuttVars[idx].type) == DT_MBCHARTBL)
2356 {
2357 free_mbchar_table ((mbchar_table **) MuttVars[idx].data.p);
2358 *((mbchar_table **) MuttVars[idx].data.p) = parse_mbchar_table (tmp->data);
2359 }
2360 else
2361 {
2362 rfc822_free_address ((ADDRESS **) MuttVars[idx].data.p);
2363 *((ADDRESS **) MuttVars[idx].data.p) = rfc822_parse_adrlist (NULL, tmp->data);
2364 }
2365 }
2366 }
2367 else if (DTYPE(MuttVars[idx].type) == DT_RX)
2368 {
2369 REGEXP *ptr = (REGEXP *) MuttVars[idx].data.p;
2370 regex_t *rx;
2371 int e, flags = 0;
2372
2373 if (query || *s->dptr != '=')
2374 {
2375 /* user requested the value of this variable */
2376 pretty_var (err->data, err->dsize, MuttVars[idx].option, NONULL(ptr->pattern));
2377 break;
2378 }
2379
2380 if (option(OPTATTACHMSG) && !mutt_strcmp(MuttVars[idx].option, "reply_regexp"))
2381 {
2382 snprintf (err->data, err->dsize, "Operation not permitted when in attach-message mode.");
2383 r = -1;
2384 break;
2385 }
2386
2387 CHECK_PAGER;
2388 s->dptr++;
2389
2390 /* copy the value of the string */
2391 mutt_extract_token (tmp, s, 0);
2392
2393 if (!ptr->pattern || mutt_strcmp (ptr->pattern, tmp->data) != 0)
2394 {
2395 int not = 0;
2396
2397 /* $mask is case-sensitive */
2398 if (mutt_strcmp (MuttVars[idx].option, "mask") != 0)
2399 flags |= mutt_which_case (tmp->data);
2400
2401 p = tmp->data;
2402 if (mutt_strcmp (MuttVars[idx].option, "mask") == 0)
2403 {
2404 if (*p == '!')
2405 {
2406 not = 1;
2407 p++;
2408 }
2409 }
2410
2411 rx = (regex_t *) safe_malloc (sizeof (regex_t));
2412 if ((e = REGCOMP (rx, p, flags)) != 0)
2413 {
2414 regerror (e, rx, err->data, err->dsize);
2415 FREE (&rx);
2416 break;
2417 }
2418
2419 /* get here only if everything went smootly */
2420 if (ptr->pattern)
2421 {
2422 FREE (&ptr->pattern);
2423 regfree ((regex_t *) ptr->rx);
2424 FREE (&ptr->rx);
2425 }
2426
2427 ptr->pattern = safe_strdup (tmp->data);
2428 ptr->rx = rx;
2429 ptr->not = not;
2430
2431 /* $reply_regexp and $alterantes require special treatment */
2432
2433 if (Context && Context->msgcount &&
2434 mutt_strcmp (MuttVars[idx].option, "reply_regexp") == 0)
2435 {
2436 regmatch_t pmatch[1];
2437 int i;
2438
2439#define CUR_ENV Context->hdrs[i]->env
2440 for (i = 0; i < Context->msgcount; i++)
2441 {
2442 if (CUR_ENV && CUR_ENV->subject)
2443 {
2444 CUR_ENV->real_subj =
2445 (regexec (ReplyRegexp.rx, CUR_ENV->subject, 1, pmatch, 0)) ?
2446 CUR_ENV->subject :
2447 CUR_ENV->subject + pmatch[0].rm_eo;
2448 }
2449 }
2450#undef CUR_ENV
2451 }
2452 }
2453 }
2454 else if (DTYPE(MuttVars[idx].type) == DT_MAGIC)
2455 {
2456 if (query || *s->dptr != '=')
2457 {
2458 switch (DefaultMagic)
2459 {
2460 case MUTT_MBOX:
2461 p = "mbox";
2462 break;
2463 case MUTT_MMDF:
2464 p = "MMDF";
2465 break;
2466 case MUTT_MH:
2467 p = "MH";
2468 break;
2469 case MUTT_MAILDIR:
2470 p = "Maildir";
2471 break;
2472 default:
2473 p = "unknown";
2474 break;
2475 }
2476 snprintf (err->data, err->dsize, "%s=%s", MuttVars[idx].option, p);
2477 break;
2478 }
2479
2480 CHECK_PAGER;
2481 s->dptr++;
2482
2483 /* copy the value of the string */
2484 mutt_extract_token (tmp, s, 0);
2485 if (mx_set_magic (tmp->data))
2486 {
2487 snprintf (err->data, err->dsize, _("%s: invalid mailbox type"), tmp->data);
2488 r = -1;
2489 break;
2490 }
2491 }
2492 else if (DTYPE(MuttVars[idx].type) == DT_NUM)
2493 {
2494 short *ptr = (short *) MuttVars[idx].data.p;
2495 short val;
2496 int rc;
2497
2498 if (query || *s->dptr != '=')
2499 {
2500 val = *ptr;
2501 /* compatibility alias */
2502 if (mutt_strcmp (MuttVars[idx].option, "wrapmargin") == 0)
2503 val = *ptr < 0 ? -*ptr : 0;
2504
2505 /* user requested the value of this variable */
2506 snprintf (err->data, err->dsize, "%s=%d", MuttVars[idx].option, val);
2507 break;
2508 }
2509
2510 CHECK_PAGER;
2511 s->dptr++;
2512
2513 mutt_extract_token (tmp, s, 0);
2514 rc = mutt_atos (tmp->data, (short *) &val);
2515
2516 if (rc < 0 || !*tmp->data)
2517 {
2518 snprintf (err->data, err->dsize, _("%s: invalid value (%s)"), tmp->data,
2519 rc == -1 ? _("format error") : _("number overflow"));
2520 r = -1;
2521 break;
2522 }
2523 else
2524 *ptr = val;
2525
2526 /* these ones need a sanity check */
2527 if (mutt_strcmp (MuttVars[idx].option, "history") == 0)
2528 {
2529 if (*ptr < 0)
2530 *ptr = 0;
2531 mutt_init_history ();
2532 }
2533 else if (mutt_strcmp (MuttVars[idx].option, "error_history") == 0)
2534 {
2535 if (*ptr < 0)
2536 *ptr = 0;
2537 mutt_error_history_init ();
2538 }
2539 else if (mutt_strcmp (MuttVars[idx].option, "pager_index_lines") == 0)
2540 {
2541 if (*ptr < 0)
2542 *ptr = 0;
2543 }
2544 else if (mutt_strcmp (MuttVars[idx].option, "wrapmargin") == 0)
2545 {
2546 if (*ptr < 0)
2547 *ptr = 0;
2548 else
2549 *ptr = -*ptr;
2550 }
2551#ifdef USE_IMAP
2552 else if (mutt_strcmp (MuttVars[idx].option, "imap_pipeline_depth") == 0)
2553 {
2554 if (*ptr < 0)
2555 *ptr = 0;
2556 }
2557#endif
2558 }
2559 else if (DTYPE(MuttVars[idx].type) == DT_LNUM)
2560 {
2561 long *ptr = (long *) MuttVars[idx].data.p;
2562 long val;
2563 int rc;
2564
2565 if (query || *s->dptr != '=')
2566 {
2567 val = *ptr;
2568
2569 /* user requested the value of this variable */
2570 snprintf (err->data, err->dsize, "%s=%ld", MuttVars[idx].option, val);
2571 break;
2572 }
2573
2574 CHECK_PAGER;
2575 s->dptr++;
2576
2577 mutt_extract_token (tmp, s, 0);
2578 rc = mutt_atol (tmp->data, (long *) &val);
2579
2580 if (rc < 0 || !*tmp->data)
2581 {
2582 snprintf (err->data, err->dsize, _("%s: invalid value (%s)"), tmp->data,
2583 rc == -1 ? _("format error") : _("number overflow"));
2584 r = -1;
2585 break;
2586 }
2587 else
2588 *ptr = val;
2589
2590 }
2591 else if (DTYPE (MuttVars[idx].type) == DT_QUAD)
2592 {
2593 if (query)
2594 {
2595 static const char * const vals[] = { "no", "yes", "ask-no", "ask-yes" };
2596
2597 snprintf (err->data, err->dsize, "%s=%s", MuttVars[idx].option,
2598 vals [ quadoption (MuttVars[idx].data.l) ]);
2599 break;
2600 }
2601
2602 CHECK_PAGER;
2603 if (*s->dptr == '=')
2604 {
2605 s->dptr++;
2606 mutt_extract_token (tmp, s, 0);
2607 if (ascii_strcasecmp ("yes", tmp->data) == 0)
2608 set_quadoption (MuttVars[idx].data.l, MUTT_YES);
2609 else if (ascii_strcasecmp ("no", tmp->data) == 0)
2610 set_quadoption (MuttVars[idx].data.l, MUTT_NO);
2611 else if (ascii_strcasecmp ("ask-yes", tmp->data) == 0)
2612 set_quadoption (MuttVars[idx].data.l, MUTT_ASKYES);
2613 else if (ascii_strcasecmp ("ask-no", tmp->data) == 0)
2614 set_quadoption (MuttVars[idx].data.l, MUTT_ASKNO);
2615 else
2616 {
2617 snprintf (err->data, err->dsize, _("%s: invalid value"), tmp->data);
2618 r = -1;
2619 break;
2620 }
2621 }
2622 else
2623 {
2624 if (inv)
2625 toggle_quadoption (MuttVars[idx].data.l);
2626 else if (unset)
2627 set_quadoption (MuttVars[idx].data.l, MUTT_NO);
2628 else
2629 set_quadoption (MuttVars[idx].data.l, MUTT_YES);
2630 }
2631 }
2632 else if (DTYPE (MuttVars[idx].type) == DT_SORT)
2633 {
2634 const struct mapping_t *map = NULL;
2635
2636 switch (MuttVars[idx].type & DT_SUBTYPE_MASK)
2637 {
2638 case DT_SORT_ALIAS:
2639 map = SortAliasMethods;
2640 break;
2641 case DT_SORT_BROWSER:
2642 map = SortBrowserMethods;
2643 break;
2644 case DT_SORT_KEYS:
2645 if ((WithCrypto & APPLICATION_PGP))
2646 map = SortKeyMethods;
2647 break;
2648 case DT_SORT_AUX:
2649 map = SortAuxMethods;
2650 break;
2651 case DT_SORT_SIDEBAR:
2652 map = SortSidebarMethods;
2653 break;
2654 default:
2655 map = SortMethods;
2656 break;
2657 }
2658
2659 if (!map)
2660 {
2661 snprintf (err->data, err->dsize, _("%s: Unknown type."), MuttVars[idx].option);
2662 r = -1;
2663 break;
2664 }
2665
2666 if (query || *s->dptr != '=')
2667 {
2668 p = mutt_getnamebyvalue (*((short *) MuttVars[idx].data.p) & SORT_MASK, map);
2669
2670 snprintf (err->data, err->dsize, "%s=%s%s%s", MuttVars[idx].option,
2671 (*((short *) MuttVars[idx].data.p) & SORT_REVERSE) ? "reverse-" : "",
2672 (*((short *) MuttVars[idx].data.p) & SORT_LAST) ? "last-" : "",
2673 p);
2674 return 0;
2675 }
2676 CHECK_PAGER;
2677 s->dptr++;
2678 mutt_extract_token (tmp, s , 0);
2679
2680 if (parse_sort ((short *) MuttVars[idx].data.p, tmp->data, map, err) == -1)
2681 {
2682 r = -1;
2683 break;
2684 }
2685 }
2686 else
2687 {
2688 snprintf (err->data, err->dsize, _("%s: unknown type"), MuttVars[idx].option);
2689 r = -1;
2690 break;
2691 }
2692
2693 if (!myvar)
2694 {
2695 if (MuttVars[idx].flags & R_INDEX)
2696 mutt_set_menu_redraw_full (MENU_MAIN);
2697 if (MuttVars[idx].flags & R_PAGER)
2698 mutt_set_menu_redraw_full (MENU_PAGER);
2699 if (MuttVars[idx].flags & R_PAGER_FLOW)
2700 {
2701 mutt_set_menu_redraw_full (MENU_PAGER);
2702 mutt_set_menu_redraw (MENU_PAGER, REDRAW_FLOW);
2703 }
2704 if (MuttVars[idx].flags & R_RESORT_SUB)
2705 set_option (OPTSORTSUBTHREADS);
2706 if (MuttVars[idx].flags & R_RESORT)
2707 set_option (OPTNEEDRESORT);
2708 if (MuttVars[idx].flags & R_RESORT_INIT)
2709 set_option (OPTRESORTINIT);
2710 if (MuttVars[idx].flags & R_TREE)
2711 set_option (OPTREDRAWTREE);
2712 if (MuttVars[idx].flags & R_REFLOW)
2713 mutt_reflow_windows ();
2714#ifdef USE_SIDEBAR
2715 if (MuttVars[idx].flags & R_SIDEBAR)
2716 mutt_set_current_menu_redraw (REDRAW_SIDEBAR);
2717#endif
2718 if (MuttVars[idx].flags & R_MENU)
2719 mutt_set_current_menu_redraw_full ();
2720 }
2721 }
2722 return (r);
2723}
2724
2725#define MAXERRS 128
2726
2727/* reads the specified initialization file. returns -1 if errors were found
2728 so that we can pause to let the user know... */
2729static int source_rc (const char *rcfile, BUFFER *err)
2730{
2731 FILE *f;
2732 int line = 0, rc = 0, conv = 0;
2733 BUFFER token;
2734 char *linebuf = NULL;
2735 char *currentline = NULL;
2736 size_t buflen;
2737 pid_t pid;
2738
2739 dprint (2, (debugfile, "Reading configuration file '%s'.\n",
2740 rcfile));
2741
2742 if ((f = mutt_open_read (rcfile, &pid)) == NULL)
2743 {
2744 snprintf (err->data, err->dsize, "%s: %s", rcfile, strerror (errno));
2745 return (-1);
2746 }
2747
2748 mutt_buffer_init (&token);
2749 while ((linebuf = mutt_read_line (linebuf, &buflen, f, &line, MUTT_CONT)) != NULL)
2750 {
2751 conv=ConfigCharset && Charset;
2752 if (conv)
2753 {
2754 currentline=safe_strdup(linebuf);
2755 if (!currentline) continue;
2756 mutt_convert_string(¤tline, ConfigCharset, Charset, 0);
2757 }
2758 else
2759 currentline=linebuf;
2760
2761 if (mutt_parse_rc_line (currentline, &token, err) == -1)
2762 {
2763 mutt_error (_("Error in %s, line %d: %s"), rcfile, line, err->data);
2764 if (--rc < -MAXERRS)
2765 {
2766 if (conv) FREE(¤tline);
2767 break;
2768 }
2769 }
2770 else
2771 {
2772 if (rc < 0)
2773 rc = -1;
2774 }
2775 if (conv)
2776 FREE(¤tline);
2777 }
2778 FREE (&token.data);
2779 FREE (&linebuf);
2780 safe_fclose (&f);
2781 if (pid != -1)
2782 mutt_wait_filter (pid);
2783 if (rc)
2784 {
2785 /* the muttrc source keyword */
2786 snprintf (err->data, err->dsize, rc >= -MAXERRS ? _("source: errors in %s")
2787 : _("source: reading aborted due to too many errors in %s"), rcfile);
2788 rc = -1;
2789 }
2790 return (rc);
2791}
2792
2793#undef MAXERRS
2794
2795static int parse_source (BUFFER *tmp, BUFFER *s, union pointer_long_t udata, BUFFER *err)
2796{
2797 BUFFER *path;
2798 int rc;
2799
2800 if (mutt_extract_token (tmp, s, 0) != 0)
2801 {
2802 snprintf (err->data, err->dsize, _("source: error at %s"), s->dptr);
2803 return (-1);
2804 }
2805 if (MoreArgs (s))
2806 {
2807 strfcpy (err->data, _("source: too many arguments"), err->dsize);
2808 return (-1);
2809 }
2810
2811 path = mutt_buffer_new ();
2812 mutt_buffer_strcpy (path, tmp->data);
2813 mutt_buffer_expand_path (path);
2814 rc = source_rc (mutt_b2s (path), err);
2815 mutt_buffer_free (&path);
2816
2817 return rc;
2818}
2819
2820/* line command to execute
2821
2822 token scratch buffer to be used by parser. caller should free
2823 token->data when finished. the reason for this variable is
2824 to avoid having to allocate and deallocate a lot of memory
2825 if we are parsing many lines. the caller can pass in the
2826 memory to use, which avoids having to create new space for
2827 every call to this function.
2828
2829 err where to write error messages */
2830int mutt_parse_rc_line (/* const */ char *line, BUFFER *token, BUFFER *err)
2831{
2832 int i, r = -1;
2833 BUFFER expn;
2834
2835 if (!line || !*line)
2836 return 0;
2837
2838 mutt_buffer_init (&expn);
2839 expn.data = expn.dptr = line;
2840 expn.dsize = mutt_strlen (line);
2841
2842 *err->data = 0;
2843
2844 SKIPWS (expn.dptr);
2845 while (*expn.dptr)
2846 {
2847 if (*expn.dptr == '#')
2848 break; /* rest of line is a comment */
2849 if (*expn.dptr == ';')
2850 {
2851 expn.dptr++;
2852 continue;
2853 }
2854 mutt_extract_token (token, &expn, 0);
2855 for (i = 0; Commands[i].name; i++)
2856 {
2857 if (!mutt_strcmp (token->data, Commands[i].name))
2858 {
2859 if (Commands[i].func (token, &expn, Commands[i].data, err) != 0)
2860 goto finish;
2861 break;
2862 }
2863 }
2864 if (!Commands[i].name)
2865 {
2866 snprintf (err->data, err->dsize, _("%s: unknown command"), NONULL (token->data));
2867 goto finish;
2868 }
2869 }
2870 r = 0;
2871finish:
2872 if (expn.destroy)
2873 FREE (&expn.data);
2874 return (r);
2875}
2876
2877
2878#define NUMVARS (sizeof (MuttVars)/sizeof (MuttVars[0]))
2879#define NUMCOMMANDS (sizeof (Commands)/sizeof (Commands[0]))
2880/* initial string that starts completion. No telling how much crap
2881 * the user has typed so far. Allocate LONG_STRING just to be sure! */
2882static char User_typed [LONG_STRING] = {0};
2883
2884static int Num_matched = 0; /* Number of matches for completion */
2885static char Completed [STRING] = {0}; /* completed string (command or variable) */
2886static const char **Matches;
2887/* this is a lie until mutt_init runs: */
2888static int Matches_listsize = MAX(NUMVARS,NUMCOMMANDS) + 10;
2889
2890static void matches_ensure_morespace(int current)
2891{
2892 int base_space, extra_space, space;
2893
2894 if (current > Matches_listsize - 2)
2895 {
2896 base_space = MAX(NUMVARS,NUMCOMMANDS) + 1;
2897 extra_space = Matches_listsize - base_space;
2898 extra_space *= 2;
2899 space = base_space + extra_space;
2900 safe_realloc (&Matches, space * sizeof (char *));
2901 memset (&Matches[current + 1], 0, space - current);
2902 Matches_listsize = space;
2903 }
2904}
2905
2906/* helper function for completion. Changes the dest buffer if
2907 necessary/possible to aid completion.
2908 dest == completion result gets here.
2909 src == candidate for completion.
2910 try == user entered data for completion.
2911 len == length of dest buffer.
2912*/
2913static void candidate (char *dest, char *try, const char *src, int len)
2914{
2915 int l;
2916
2917 if (strstr (src, try) == src)
2918 {
2919 matches_ensure_morespace (Num_matched);
2920 Matches[Num_matched++] = src;
2921 if (dest[0] == 0)
2922 strfcpy (dest, src, len);
2923 else
2924 {
2925 for (l = 0; src[l] && src[l] == dest[l]; l++);
2926 dest[l] = 0;
2927 }
2928 }
2929}
2930
2931int mutt_command_complete (char *buffer, size_t len, int pos, int numtabs)
2932{
2933 char *pt = buffer;
2934 int num;
2935 int spaces; /* keep track of the number of leading spaces on the line */
2936 myvar_t *myv;
2937
2938 SKIPWS (buffer);
2939 spaces = buffer - pt;
2940
2941 pt = buffer + pos - spaces;
2942 while ((pt > buffer) && !isspace ((unsigned char) *pt))
2943 pt--;
2944
2945 if (pt == buffer) /* complete cmd */
2946 {
2947 /* first TAB. Collect all the matches */
2948 if (numtabs == 1)
2949 {
2950 Num_matched = 0;
2951 strfcpy (User_typed, pt, sizeof (User_typed));
2952 memset (Matches, 0, Matches_listsize);
2953 memset (Completed, 0, sizeof (Completed));
2954 for (num = 0; Commands[num].name; num++)
2955 candidate (Completed, User_typed, Commands[num].name, sizeof (Completed));
2956 matches_ensure_morespace (Num_matched);
2957 Matches[Num_matched++] = User_typed;
2958
2959 /* All matches are stored. Longest non-ambiguous string is ""
2960 * i.e. don't change 'buffer'. Fake successful return this time */
2961 if (User_typed[0] == 0)
2962 return 1;
2963 }
2964
2965 if (Completed[0] == 0 && User_typed[0])
2966 return 0;
2967
2968 /* Num_matched will _always_ be at least 1 since the initial
2969 * user-typed string is always stored */
2970 if (numtabs == 1 && Num_matched == 2)
2971 snprintf(Completed, sizeof(Completed),"%s", Matches[0]);
2972 else if (numtabs > 1 && Num_matched > 2)
2973 /* cycle thru all the matches */
2974 snprintf(Completed, sizeof(Completed), "%s",
2975 Matches[(numtabs - 2) % Num_matched]);
2976
2977 /* return the completed command */
2978 strncpy (buffer, Completed, len - spaces);
2979 }
2980 else if (!mutt_strncmp (buffer, "set", 3)
2981 || !mutt_strncmp (buffer, "unset", 5)
2982 || !mutt_strncmp (buffer, "reset", 5)
2983 || !mutt_strncmp (buffer, "toggle", 6))
2984 { /* complete variables */
2985 static const char * const prefixes[] = { "no", "inv", "?", "&", 0 };
2986
2987 pt++;
2988 /* loop through all the possible prefixes (no, inv, ...) */
2989 if (!mutt_strncmp (buffer, "set", 3))
2990 {
2991 for (num = 0; prefixes[num]; num++)
2992 {
2993 if (!mutt_strncmp (pt, prefixes[num], mutt_strlen (prefixes[num])))
2994 {
2995 pt += mutt_strlen (prefixes[num]);
2996 break;
2997 }
2998 }
2999 }
3000
3001 /* first TAB. Collect all the matches */
3002 if (numtabs == 1)
3003 {
3004 Num_matched = 0;
3005 strfcpy (User_typed, pt, sizeof (User_typed));
3006 memset (Matches, 0, Matches_listsize);
3007 memset (Completed, 0, sizeof (Completed));
3008 for (num = 0; MuttVars[num].option; num++)
3009 candidate (Completed, User_typed, MuttVars[num].option, sizeof (Completed));
3010 for (myv = MyVars; myv; myv = myv->next)
3011 candidate (Completed, User_typed, myv->name, sizeof (Completed));
3012 matches_ensure_morespace (Num_matched);
3013 Matches[Num_matched++] = User_typed;
3014
3015 /* All matches are stored. Longest non-ambiguous string is ""
3016 * i.e. don't change 'buffer'. Fake successful return this time */
3017 if (User_typed[0] == 0)
3018 return 1;
3019 }
3020
3021 if (Completed[0] == 0 && User_typed[0])
3022 return 0;
3023
3024 /* Num_matched will _always_ be at least 1 since the initial
3025 * user-typed string is always stored */
3026 if (numtabs == 1 && Num_matched == 2)
3027 snprintf(Completed, sizeof(Completed),"%s", Matches[0]);
3028 else if (numtabs > 1 && Num_matched > 2)
3029 /* cycle thru all the matches */
3030 snprintf(Completed, sizeof(Completed), "%s",
3031 Matches[(numtabs - 2) % Num_matched]);
3032
3033 strncpy (pt, Completed, buffer + len - pt - spaces);
3034 }
3035 else if (!mutt_strncmp (buffer, "exec", 4))
3036 {
3037 const struct binding_t *menu = km_get_table (CurrentMenu);
3038
3039 if (!menu && CurrentMenu != MENU_PAGER)
3040 menu = OpGeneric;
3041
3042 pt++;
3043 /* first TAB. Collect all the matches */
3044 if (numtabs == 1)
3045 {
3046 Num_matched = 0;
3047 strfcpy (User_typed, pt, sizeof (User_typed));
3048 memset (Matches, 0, Matches_listsize);
3049 memset (Completed, 0, sizeof (Completed));
3050 for (num = 0; menu[num].name; num++)
3051 candidate (Completed, User_typed, menu[num].name, sizeof (Completed));
3052 /* try the generic menu */
3053 if (Completed[0] == 0 && CurrentMenu != MENU_PAGER)
3054 {
3055 menu = OpGeneric;
3056 for (num = 0; menu[num].name; num++)
3057 candidate (Completed, User_typed, menu[num].name, sizeof (Completed));
3058 }
3059 matches_ensure_morespace (Num_matched);
3060 Matches[Num_matched++] = User_typed;
3061
3062 /* All matches are stored. Longest non-ambiguous string is ""
3063 * i.e. don't change 'buffer'. Fake successful return this time */
3064 if (User_typed[0] == 0)
3065 return 1;
3066 }
3067
3068 if (Completed[0] == 0 && User_typed[0])
3069 return 0;
3070
3071 /* Num_matched will _always_ be at least 1 since the initial
3072 * user-typed string is always stored */
3073 if (numtabs == 1 && Num_matched == 2)
3074 snprintf(Completed, sizeof(Completed),"%s", Matches[0]);
3075 else if (numtabs > 1 && Num_matched > 2)
3076 /* cycle thru all the matches */
3077 snprintf(Completed, sizeof(Completed), "%s",
3078 Matches[(numtabs - 2) % Num_matched]);
3079
3080 strncpy (pt, Completed, buffer + len - pt - spaces);
3081 }
3082 else
3083 return 0;
3084
3085 return 1;
3086}
3087
3088int mutt_var_value_complete (char *buffer, size_t len, int pos)
3089{
3090 char var[STRING], *pt = buffer;
3091 int spaces;
3092
3093 if (buffer[0] == 0)
3094 return 0;
3095
3096 SKIPWS (buffer);
3097 spaces = buffer - pt;
3098
3099 pt = buffer + pos - spaces;
3100 while ((pt > buffer) && !isspace ((unsigned char) *pt))
3101 pt--;
3102 pt++; /* move past the space */
3103 if (*pt == '=') /* abort if no var before the '=' */
3104 return 0;
3105
3106 if (mutt_strncmp (buffer, "set", 3) == 0)
3107 {
3108 int idx;
3109 char val[LONG_STRING];
3110 const char *myvarval;
3111
3112 strfcpy (var, pt, sizeof (var));
3113 /* ignore the trailing '=' when comparing */
3114 var[mutt_strlen (var) - 1] = 0;
3115 if ((idx = mutt_option_index (var)) == -1)
3116 {
3117 if ((myvarval = myvar_get(var)) != NULL)
3118 {
3119 pretty_var (pt, len - (pt - buffer), var, myvarval);
3120 return 1;
3121 }
3122 return 0; /* no such variable. */
3123 }
3124 else if (var_to_string (idx, val, sizeof (val)))
3125 {
3126 snprintf (pt, len - (pt - buffer), "%s=\"%s\"", var, val);
3127 return 1;
3128 }
3129 }
3130 return 0;
3131}
3132
3133static int var_to_string (int idx, char* val, size_t len)
3134{
3135 char tmp[LONG_STRING];
3136 static const char * const vals[] = { "no", "yes", "ask-no", "ask-yes" };
3137
3138 tmp[0] = '\0';
3139
3140 if ((DTYPE(MuttVars[idx].type) == DT_STR) ||
3141 (DTYPE(MuttVars[idx].type) == DT_PATH) ||
3142 (DTYPE(MuttVars[idx].type) == DT_RX))
3143 {
3144 strfcpy (tmp, NONULL (*((char **) MuttVars[idx].data.p)), sizeof (tmp));
3145 if (DTYPE (MuttVars[idx].type) == DT_PATH)
3146 mutt_pretty_mailbox (tmp, sizeof (tmp));
3147 }
3148 else if (DTYPE (MuttVars[idx].type) == DT_MBCHARTBL)
3149 {
3150 mbchar_table *mbt = (*((mbchar_table **) MuttVars[idx].data.p));
3151 strfcpy (tmp, mbt ? NONULL (mbt->orig_str) : "", sizeof (tmp));
3152 }
3153 else if (DTYPE (MuttVars[idx].type) == DT_ADDR)
3154 {
3155 rfc822_write_address (tmp, sizeof (tmp), *((ADDRESS **) MuttVars[idx].data.p), 0);
3156 }
3157 else if (DTYPE (MuttVars[idx].type) == DT_QUAD)
3158 strfcpy (tmp, vals[quadoption (MuttVars[idx].data.l)], sizeof (tmp));
3159 else if (DTYPE (MuttVars[idx].type) == DT_NUM)
3160 {
3161 short sval = *((short *) MuttVars[idx].data.p);
3162
3163 /* avert your eyes, gentle reader */
3164 if (mutt_strcmp (MuttVars[idx].option, "wrapmargin") == 0)
3165 sval = sval > 0 ? 0 : -sval;
3166
3167 snprintf (tmp, sizeof (tmp), "%d", sval);
3168 }
3169 else if (DTYPE (MuttVars[idx].type) == DT_LNUM)
3170 {
3171 long sval = *((long *) MuttVars[idx].data.p);
3172
3173 snprintf (tmp, sizeof (tmp), "%ld", sval);
3174 }
3175 else if (DTYPE (MuttVars[idx].type) == DT_SORT)
3176 {
3177 const struct mapping_t *map;
3178 const char *p;
3179
3180 switch (MuttVars[idx].type & DT_SUBTYPE_MASK)
3181 {
3182 case DT_SORT_ALIAS:
3183 map = SortAliasMethods;
3184 break;
3185 case DT_SORT_BROWSER:
3186 map = SortBrowserMethods;
3187 break;
3188 case DT_SORT_KEYS:
3189 if ((WithCrypto & APPLICATION_PGP))
3190 map = SortKeyMethods;
3191 else
3192 map = SortMethods;
3193 break;
3194 default:
3195 map = SortMethods;
3196 break;
3197 }
3198 p = mutt_getnamebyvalue (*((short *) MuttVars[idx].data.p) & SORT_MASK, map);
3199 snprintf (tmp, sizeof (tmp), "%s%s%s",
3200 (*((short *) MuttVars[idx].data.p) & SORT_REVERSE) ? "reverse-" : "",
3201 (*((short *) MuttVars[idx].data.p) & SORT_LAST) ? "last-" : "",
3202 p);
3203 }
3204 else if (DTYPE (MuttVars[idx].type) == DT_MAGIC)
3205 {
3206 char *p;
3207
3208 switch (DefaultMagic)
3209 {
3210 case MUTT_MBOX:
3211 p = "mbox";
3212 break;
3213 case MUTT_MMDF:
3214 p = "MMDF";
3215 break;
3216 case MUTT_MH:
3217 p = "MH";
3218 break;
3219 case MUTT_MAILDIR:
3220 p = "Maildir";
3221 break;
3222 default:
3223 p = "unknown";
3224 }
3225 strfcpy (tmp, p, sizeof (tmp));
3226 }
3227 else if (DTYPE (MuttVars[idx].type) == DT_BOOL)
3228 strfcpy (tmp, option (MuttVars[idx].data.l) ? "yes" : "no", sizeof (tmp));
3229 else
3230 return 0;
3231
3232 escape_string (val, len - 1, tmp);
3233
3234 return 1;
3235}
3236
3237/* Implement the -Q command line flag */
3238int mutt_query_variables (LIST *queries)
3239{
3240 LIST *p;
3241
3242 char command[STRING];
3243
3244 BUFFER err, token;
3245
3246 mutt_buffer_init (&err);
3247 mutt_buffer_init (&token);
3248
3249 err.dsize = STRING;
3250 err.data = safe_malloc (err.dsize);
3251
3252 for (p = queries; p; p = p->next)
3253 {
3254 snprintf (command, sizeof (command), "set ?%s\n", p->data);
3255 if (mutt_parse_rc_line (command, &token, &err) == -1)
3256 {
3257 fprintf (stderr, "%s\n", err.data);
3258 FREE (&token.data);
3259 FREE (&err.data);
3260
3261 return 1;
3262 }
3263 printf ("%s\n", err.data);
3264 }
3265
3266 FREE (&token.data);
3267 FREE (&err.data);
3268
3269 return 0;
3270}
3271
3272/* dump out the value of all the variables we have */
3273int mutt_dump_variables (void)
3274{
3275 int i;
3276
3277 char command[STRING];
3278
3279 BUFFER err, token;
3280
3281 mutt_buffer_init (&err);
3282 mutt_buffer_init (&token);
3283
3284 err.dsize = STRING;
3285 err.data = safe_malloc (err.dsize);
3286
3287 for (i = 0; MuttVars[i].option; i++)
3288 {
3289 if (MuttVars[i].type == DT_SYN)
3290 continue;
3291
3292 snprintf (command, sizeof (command), "set ?%s\n", MuttVars[i].option);
3293 if (mutt_parse_rc_line (command, &token, &err) == -1)
3294 {
3295 fprintf (stderr, "%s\n", err.data);
3296 FREE (&token.data);
3297 FREE (&err.data);
3298
3299 return 1;
3300 }
3301 printf("%s\n", err.data);
3302 }
3303
3304 FREE (&token.data);
3305 FREE (&err.data);
3306
3307 return 0;
3308}
3309
3310const char *mutt_getnamebyvalue (int val, const struct mapping_t *map)
3311{
3312 int i;
3313
3314 for (i=0; map[i].name; i++)
3315 if (map[i].value == val)
3316 return (map[i].name);
3317 return NULL;
3318}
3319
3320int mutt_getvaluebyname (const char *name, const struct mapping_t *map)
3321{
3322 int i;
3323
3324 for (i = 0; map[i].name; i++)
3325 if (ascii_strcasecmp (map[i].name, name) == 0)
3326 return (map[i].value);
3327 return (-1);
3328}
3329
3330#ifdef DEBUG
3331static void start_debug (void)
3332{
3333 int i;
3334 BUFFER *buf, *buf2;
3335
3336 buf = mutt_buffer_pool_get ();
3337 buf2 = mutt_buffer_pool_get ();
3338
3339 /* rotate the old debug logs */
3340 for (i=3; i>=0; i--)
3341 {
3342 mutt_buffer_printf (buf, "%s/.muttdebug%d", NONULL(Homedir), i);
3343 mutt_buffer_printf (buf2, "%s/.muttdebug%d", NONULL(Homedir), i+1);
3344 rename (mutt_b2s (buf), mutt_b2s (buf2));
3345 }
3346 if ((debugfile = safe_fopen(mutt_b2s (buf), "w")) != NULL)
3347 {
3348 setbuf (debugfile, NULL); /* don't buffer the debugging output! */
3349 dprint(1,(debugfile,"Mutt/%s (%s) debugging at level %d\n",
3350 MUTT_VERSION, ReleaseDate, debuglevel));
3351 }
3352
3353 mutt_buffer_pool_release (&buf);
3354 mutt_buffer_pool_release (&buf2);
3355}
3356#endif
3357
3358static int mutt_execute_commands (LIST *p)
3359{
3360 BUFFER err, token;
3361
3362 mutt_buffer_init (&err);
3363 err.dsize = STRING;
3364 err.data = safe_malloc (err.dsize);
3365 mutt_buffer_init (&token);
3366 for (; p; p = p->next)
3367 {
3368 if (mutt_parse_rc_line (p->data, &token, &err) != 0)
3369 {
3370 fprintf (stderr, _("Error in command line: %s\n"), err.data);
3371 FREE (&token.data);
3372 FREE (&err.data);
3373
3374 return -1;
3375 }
3376 }
3377 FREE (&token.data);
3378 FREE (&err.data);
3379
3380 return 0;
3381}
3382
3383static void mutt_srandom (void)
3384{
3385 struct timeval tv;
3386 unsigned seed;
3387
3388 gettimeofday(&tv, NULL);
3389 /* POSIX.1-2008 states that seed is 'unsigned' without specifying its width.
3390 * Use as many of the lower order bits from the current time of day as the seed.
3391 * If the upper bound is truncated, that is fine.
3392 *
3393 * tv_sec is integral of type integer or float. Cast to 'long long' before
3394 * bitshift in case it is a float.
3395 */
3396 seed = ((LONGLONG) tv.tv_sec << 20) | tv.tv_usec;
3397 srandom(seed);
3398}
3399
3400static char* mutt_find_cfg (const char *home, const char *xdg_cfg_home)
3401{
3402 const char* names[] =
3403 {
3404 "muttrc-" MUTT_VERSION,
3405 "muttrc",
3406 NULL,
3407 };
3408
3409 const char* locations[][2] =
3410 {
3411 { home, ".", },
3412 { home, ".mutt/" },
3413 { xdg_cfg_home, "mutt/", },
3414 { NULL, NULL },
3415 };
3416
3417 int i;
3418
3419 for (i = 0; locations[i][0] || locations[i][1]; i++)
3420 {
3421 int j;
3422
3423 if (!locations[i][0])
3424 continue;
3425
3426 for (j = 0; names[j]; j++)
3427 {
3428 char buffer[STRING];
3429
3430 snprintf (buffer, sizeof (buffer),
3431 "%s/%s%s", locations[i][0], locations[i][1], names[j]);
3432 if (access (buffer, F_OK) == 0)
3433 return safe_strdup(buffer);
3434 }
3435 }
3436
3437 return NULL;
3438}
3439
3440void mutt_init (int skip_sys_rc, LIST *commands)
3441{
3442 struct passwd *pw;
3443 struct utsname utsname;
3444 char *p, buffer[STRING];
3445 char *domain = NULL;
3446 int i, need_pause = 0;
3447 BUFFER err;
3448
3449 mutt_buffer_init (&err);
3450 mutt_buffer_increase_size (&err, STRING);
3451
3452 Groups = hash_create (1031, 0);
3453 /* reverse alias keys need to be strdup'ed because of idna conversions */
3454 ReverseAlias = hash_create (1031, MUTT_HASH_STRCASECMP | MUTT_HASH_STRDUP_KEYS |
3455 MUTT_HASH_ALLOW_DUPS);
3456
3457 mutt_menu_init ();
3458 mutt_srandom ();
3459 mutt_buffer_pool_init ();
3460
3461 /*
3462 * XXX - use something even more difficult to predict?
3463 */
3464 snprintf (AttachmentMarker, sizeof (AttachmentMarker),
3465 "\033]9;%ld\a", (long) time (NULL));
3466 snprintf (ProtectedHeaderMarker, sizeof (ProtectedHeaderMarker),
3467 "\033]8;%ld\a", (long) time (NULL));
3468
3469 /* on one of the systems I use, getcwd() does not return the same prefix
3470 as is listed in the passwd file */
3471 if ((p = getenv ("HOME")))
3472 Homedir = safe_strdup (p);
3473
3474 /* Get some information about the user */
3475 if ((pw = getpwuid (getuid ())))
3476 {
3477 char rnbuf[STRING];
3478
3479 Username = safe_strdup (pw->pw_name);
3480 if (!Homedir)
3481 Homedir = safe_strdup (pw->pw_dir);
3482
3483 Realname = safe_strdup (mutt_gecos_name (rnbuf, sizeof (rnbuf), pw));
3484 Shell = safe_strdup (pw->pw_shell);
3485 endpwent ();
3486 }
3487 else
3488 {
3489 if (!Homedir)
3490 {
3491 mutt_endwin (NULL);
3492 fputs (_("unable to determine home directory"), stderr);
3493 exit (1);
3494 }
3495 if ((p = getenv ("USER")))
3496 Username = safe_strdup (p);
3497 else
3498 {
3499 mutt_endwin (NULL);
3500 fputs (_("unable to determine username"), stderr);
3501 exit (1);
3502 }
3503 Shell = safe_strdup ((p = getenv ("SHELL")) ? p : "/bin/sh");
3504 }
3505
3506#ifdef DEBUG
3507 /* Start up debugging mode if requested */
3508 if (debuglevel > 0)
3509 start_debug ();
3510#endif
3511
3512 /* And about the host... */
3513
3514#ifdef DOMAIN
3515 domain = safe_strdup (DOMAIN);
3516#endif /* DOMAIN */
3517
3518 /*
3519 * The call to uname() shouldn't fail, but if it does, the system is horribly
3520 * broken, and the system's networking configuration is in an unreliable
3521 * state. We should bail.
3522 */
3523 if ((uname (&utsname)) == -1)
3524 {
3525 mutt_endwin (NULL);
3526 perror (_("unable to determine nodename via uname()"));
3527 exit (1);
3528 }
3529
3530 /* some systems report the FQDN instead of just the hostname */
3531 if ((p = strchr (utsname.nodename, '.')))
3532 Hostname = mutt_substrdup (utsname.nodename, p);
3533 else
3534 Hostname = safe_strdup (utsname.nodename);
3535
3536 /* now get FQDN. Use configured domain first, DNS next, then uname */
3537 if (domain)
3538 {
3539 /* we have a compile-time domain name, use that for Fqdn */
3540 Fqdn = safe_malloc (mutt_strlen (domain) + mutt_strlen (Hostname) + 2);
3541 sprintf (Fqdn, "%s.%s", NONULL(Hostname), domain); /* __SPRINTF_CHECKED__ */
3542 }
3543 else if (!(getdnsdomainname (buffer, sizeof buffer)))
3544 {
3545 Fqdn = safe_malloc (mutt_strlen (buffer) + mutt_strlen (Hostname) + 2);
3546 sprintf (Fqdn, "%s.%s", NONULL(Hostname), buffer); /* __SPRINTF_CHECKED__ */
3547 }
3548 else
3549 /*
3550 * DNS failed, use the nodename. Whether or not the nodename had a '.' in
3551 * it, we can use the nodename as the FQDN. On hosts where DNS is not
3552 * being used, e.g. small network that relies on hosts files, a short host
3553 * name is all that is required for SMTP to work correctly. It could be
3554 * wrong, but we've done the best we can, at this point the onus is on the
3555 * user to provide the correct hostname if the nodename won't work in their
3556 * network.
3557 */
3558 Fqdn = safe_strdup(utsname.nodename);
3559
3560
3561 if ((p = getenv ("MAIL")))
3562 Spoolfile = safe_strdup (p);
3563 else if ((p = getenv ("MAILDIR")))
3564 Spoolfile = safe_strdup (p);
3565 else
3566 {
3567#ifdef HOMESPOOL
3568 mutt_concat_path (buffer, NONULL (Homedir), MAILPATH, sizeof (buffer));
3569#else
3570 mutt_concat_path (buffer, MAILPATH, NONULL(Username), sizeof (buffer));
3571#endif
3572 Spoolfile = safe_strdup (buffer);
3573 }
3574
3575 if ((p = getenv ("MAILCAPS")))
3576 MailcapPath = safe_strdup (p);
3577 else
3578 {
3579 /* Default search path from RFC1524 */
3580 MailcapPath = safe_strdup ("~/.mailcap:" PKGDATADIR "/mailcap:" SYSCONFDIR "/mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap");
3581 }
3582
3583 Tempdir = safe_strdup ((p = getenv ("TMPDIR")) ? p : "/tmp");
3584
3585 p = getenv ("VISUAL");
3586 if (!p)
3587 {
3588 p = getenv ("EDITOR");
3589 if (!p)
3590 p = "vi";
3591 }
3592 Editor = safe_strdup (p);
3593 Visual = safe_strdup (p);
3594
3595 if ((p = getenv ("REPLYTO")) != NULL)
3596 {
3597 BUFFER buf, token;
3598 union pointer_long_t udata = {.l=0};
3599
3600 snprintf (buffer, sizeof (buffer), "Reply-To: %s", p);
3601
3602 mutt_buffer_init (&buf);
3603 buf.data = buf.dptr = buffer;
3604 buf.dsize = mutt_strlen (buffer);
3605
3606 mutt_buffer_init (&token);
3607 parse_my_hdr (&token, &buf, udata, &err);
3608 FREE (&token.data);
3609 }
3610
3611 if ((p = getenv ("EMAIL")) != NULL)
3612 From = rfc822_parse_adrlist (NULL, p);
3613
3614 mutt_set_langinfo_charset ();
3615 mutt_set_charset (Charset);
3616
3617 Matches = safe_calloc (Matches_listsize, sizeof (char *));
3618
3619 /* Set standard defaults */
3620 for (i = 0; MuttVars[i].option; i++)
3621 {
3622 mutt_set_default (&MuttVars[i]);
3623 mutt_restore_default (&MuttVars[i]);
3624 }
3625
3626 CurrentMenu = MENU_MAIN;
3627
3628
3629#ifndef LOCALES_HACK
3630 /* Do we have a locale definition? */
3631 if (((p = getenv ("LC_ALL")) != NULL && p[0]) ||
3632 ((p = getenv ("LANG")) != NULL && p[0]) ||
3633 ((p = getenv ("LC_CTYPE")) != NULL && p[0]))
3634 set_option (OPTLOCALES);
3635#endif
3636
3637#ifdef HAVE_GETSID
3638 /* Unset suspend by default if we're the session leader */
3639 if (getsid(0) == getpid())
3640 unset_option (OPTSUSPEND);
3641#endif
3642
3643 mutt_init_history ();
3644 mutt_error_history_init ();
3645
3646 /* RFC2368, "4. Unsafe headers"
3647 * The creator of a mailto URL cannot expect the resolver of a URL to
3648 * understand more than the "subject" and "body" headers. Clients that
3649 * resolve mailto URLs into mail messages should be able to correctly
3650 * create RFC 822-compliant mail messages using the "subject" and "body"
3651 * headers.
3652 */
3653 add_to_list(&MailtoAllow, "body");
3654 add_to_list(&MailtoAllow, "subject");
3655
3656 if (!Muttrc)
3657 {
3658 char *xdg_cfg_home = getenv ("XDG_CONFIG_HOME");
3659
3660 if (!xdg_cfg_home && Homedir)
3661 {
3662 snprintf (buffer, sizeof (buffer), "%s/.config", Homedir);
3663 xdg_cfg_home = buffer;
3664 }
3665
3666 Muttrc = mutt_find_cfg (Homedir, xdg_cfg_home);
3667 }
3668 else
3669 {
3670 strfcpy (buffer, Muttrc, sizeof (buffer));
3671 FREE (&Muttrc);
3672 mutt_expand_path (buffer, sizeof (buffer));
3673 Muttrc = safe_strdup (buffer);
3674 if (access (Muttrc, F_OK))
3675 {
3676 snprintf (buffer, sizeof (buffer), "%s: %s", Muttrc, strerror (errno));
3677 mutt_endwin (buffer);
3678 exit (1);
3679 }
3680 }
3681
3682 if (Muttrc)
3683 {
3684 FREE (&AliasFile);
3685 AliasFile = safe_strdup (Muttrc);
3686 }
3687
3688 /* Process the global rc file if it exists and the user hasn't explicitly
3689 requested not to via "-n". */
3690 if (!skip_sys_rc)
3691 {
3692 snprintf (buffer, sizeof(buffer), "%s/Muttrc-%s", SYSCONFDIR, MUTT_VERSION);
3693 if (access (buffer, F_OK) == -1)
3694 snprintf (buffer, sizeof(buffer), "%s/Muttrc", SYSCONFDIR);
3695 if (access (buffer, F_OK) == -1)
3696 snprintf (buffer, sizeof (buffer), "%s/Muttrc-%s", PKGDATADIR, MUTT_VERSION);
3697 if (access (buffer, F_OK) == -1)
3698 snprintf (buffer, sizeof (buffer), "%s/Muttrc", PKGDATADIR);
3699 if (access (buffer, F_OK) != -1)
3700 {
3701 if (source_rc (buffer, &err) != 0)
3702 {
3703 fputs (err.data, stderr);
3704 fputc ('\n', stderr);
3705 need_pause = 1;
3706 }
3707 }
3708 }
3709
3710 /* Read the user's initialization file. */
3711 if (Muttrc)
3712 {
3713 if (!option (OPTNOCURSES))
3714 endwin ();
3715 if (source_rc (Muttrc, &err) != 0)
3716 {
3717 fputs (err.data, stderr);
3718 fputc ('\n', stderr);
3719 need_pause = 1;
3720 }
3721 }
3722
3723 if (mutt_execute_commands (commands) != 0)
3724 need_pause = 1;
3725
3726 if (need_pause && !option (OPTNOCURSES))
3727 {
3728 if (mutt_any_key_to_continue (NULL) == -1)
3729 mutt_exit(1);
3730 }
3731
3732 mutt_read_histfile ();
3733
3734 FREE (&err.data);
3735}
3736
3737int mutt_get_hook_type (const char *name)
3738{
3739 const struct command_t *c;
3740
3741 for (c = Commands ; c->name ; c++)
3742 if ((c->func == mutt_parse_hook || c->func == mutt_parse_idxfmt_hook) &&
3743 ascii_strcasecmp (c->name, name) == 0)
3744 return c->data.l;
3745 return 0;
3746}
3747
3748static int parse_group_context (group_context_t **ctx, BUFFER *buf, BUFFER *s, BUFFER *err)
3749{
3750 while (!mutt_strcasecmp (buf->data, "-group"))
3751 {
3752 if (!MoreArgs (s))
3753 {
3754 strfcpy (err->data, _("-group: no group name"), err->dsize);
3755 goto bail;
3756 }
3757
3758 mutt_extract_token (buf, s, 0);
3759
3760 mutt_group_context_add (ctx, mutt_pattern_group (buf->data));
3761
3762 if (!MoreArgs (s))
3763 {
3764 strfcpy (err->data, _("out of arguments"), err->dsize);
3765 goto bail;
3766 }
3767
3768 mutt_extract_token (buf, s, 0);
3769 }
3770
3771 return 0;
3772
3773bail:
3774 mutt_group_context_destroy (ctx);
3775 return -1;
3776}
3777
3778static void myvar_set (const char* var, const char* val)
3779{
3780 myvar_t** cur;
3781
3782 for (cur = &MyVars; *cur; cur = &((*cur)->next))
3783 if (!mutt_strcmp ((*cur)->name, var))
3784 break;
3785
3786 if (!*cur)
3787 *cur = safe_calloc (1, sizeof (myvar_t));
3788
3789 if (!(*cur)->name)
3790 (*cur)->name = safe_strdup (var);
3791
3792 mutt_str_replace (&(*cur)->value, val);
3793}
3794
3795static void myvar_del (const char* var)
3796{
3797 myvar_t **cur;
3798 myvar_t *tmp;
3799
3800
3801 for (cur = &MyVars; *cur; cur = &((*cur)->next))
3802 if (!mutt_strcmp ((*cur)->name, var))
3803 break;
3804
3805 if (*cur)
3806 {
3807 tmp = (*cur)->next;
3808 FREE (&(*cur)->name);
3809 FREE (&(*cur)->value);
3810 FREE (cur); /* __FREE_CHECKED__ */
3811 *cur = tmp;
3812 }
3813}
3814
3815static const char* myvar_get (const char* var)
3816{
3817 myvar_t* cur;
3818
3819 for (cur = MyVars; cur; cur = cur->next)
3820 if (!mutt_strcmp (cur->name, var))
3821 return NONULL(cur->value);
3822
3823 return NULL;
3824}
3825
3826int mutt_label_complete (char *buffer, size_t len, int numtabs)
3827{
3828 char *pt = buffer;
3829 int spaces; /* keep track of the number of leading spaces on the line */
3830
3831 if (!Context || !Context->label_hash)
3832 return 0;
3833
3834 SKIPWS (buffer);
3835 spaces = buffer - pt;
3836
3837 /* first TAB. Collect all the matches */
3838 if (numtabs == 1)
3839 {
3840 struct hash_elem *entry;
3841 struct hash_walk_state state;
3842
3843 Num_matched = 0;
3844 strfcpy (User_typed, buffer, sizeof (User_typed));
3845 memset (Matches, 0, Matches_listsize);
3846 memset (Completed, 0, sizeof (Completed));
3847 memset (&state, 0, sizeof(state));
3848 while ((entry = hash_walk(Context->label_hash, &state)))
3849 candidate (Completed, User_typed, entry->key.strkey, sizeof (Completed));
3850 matches_ensure_morespace (Num_matched);
3851 qsort(Matches, Num_matched, sizeof(char *), (sort_t *) mutt_strcasecmp);
3852 Matches[Num_matched++] = User_typed;
3853
3854 /* All matches are stored. Longest non-ambiguous string is ""
3855 * i.e. dont change 'buffer'. Fake successful return this time */
3856 if (User_typed[0] == 0)
3857 return 1;
3858 }
3859
3860 if (Completed[0] == 0 && User_typed[0])
3861 return 0;
3862
3863 /* Num_matched will _always_ be atleast 1 since the initial
3864 * user-typed string is always stored */
3865 if (numtabs == 1 && Num_matched == 2)
3866 snprintf(Completed, sizeof(Completed), "%s", Matches[0]);
3867 else if (numtabs > 1 && Num_matched > 2)
3868 /* cycle thru all the matches */
3869 snprintf(Completed, sizeof(Completed), "%s",
3870 Matches[(numtabs - 2) % Num_matched]);
3871
3872 /* return the completed label */
3873 strncpy (buffer, Completed, len - spaces);
3874
3875 return 1;
3876}