mutt stable branch with some hacks
1/*
2 * Copyright (C) 1996-2002,2004,2007 Michael R. Elkins <me@mutt.org>, and others
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 "mutt.h"
24#include "mailbox.h"
25#include "mutt_crypt.h"
26
27#ifdef USE_COMPRESSED
28#include "compress.h"
29#endif
30
31#include <limits.h>
32#include <string.h>
33#include <stdlib.h>
34#include <ctype.h>
35#include <unistd.h>
36
37typedef struct hook
38{
39 int type; /* hook type */
40 REGEXP rx; /* regular expression */
41 char *command; /* filename, command or pattern to execute */
42 pattern_t *pattern; /* used for fcc,save,send-hook */
43 struct hook *next;
44} HOOK;
45
46static HOOK *Hooks = NULL;
47static HASH *IdxFmtHooks = NULL;
48
49static int current_hook_type = 0;
50
51int mutt_parse_hook (BUFFER *buf, BUFFER *s, union pointer_long_t udata, BUFFER *err)
52{
53 HOOK *ptr;
54 BUFFER *command, *pattern;
55 int rc = -1, not = 0;
56 regex_t *rx = NULL;
57 pattern_t *pat = NULL;
58 long data = udata.l;
59
60 command = mutt_buffer_pool_get ();
61 pattern = mutt_buffer_pool_get ();
62
63 if (*s->dptr == '!')
64 {
65 s->dptr++;
66 SKIPWS (s->dptr);
67 not = 1;
68 }
69
70 mutt_extract_token (pattern, s, 0);
71
72 if (!MoreArgs (s))
73 {
74 strfcpy (err->data, _("too few arguments"), err->dsize);
75 goto cleanup;
76 }
77
78 mutt_extract_token (command, s, (data & (MUTT_FOLDERHOOK | MUTT_SENDHOOK | MUTT_SEND2HOOK | MUTT_ACCOUNTHOOK | MUTT_REPLYHOOK)) ? MUTT_TOKEN_SPACE : 0);
79
80 if (!mutt_buffer_len (command))
81 {
82 strfcpy (err->data, _("too few arguments"), err->dsize);
83 goto cleanup;
84 }
85
86 if (MoreArgs (s))
87 {
88 strfcpy (err->data, _("too many arguments"), err->dsize);
89 goto cleanup;
90 }
91
92 if (data & (MUTT_FOLDERHOOK | MUTT_MBOXHOOK))
93 {
94 BUFFER *tmp = NULL;
95
96 /* Accidentally using the ^ mailbox shortcut in the .muttrc is a
97 * common mistake */
98 if ((*(pattern->data) == '^') && (!CurrentFolder))
99 {
100 strfcpy (err->data, _("current mailbox shortcut '^' is unset"), err->dsize);
101 goto cleanup;
102 }
103
104 tmp = mutt_buffer_pool_get ();
105 mutt_buffer_strcpy (tmp, mutt_b2s (pattern));
106 _mutt_buffer_expand_path (tmp, 1);
107
108 /* Check for other mailbox shortcuts that expand to the empty string.
109 * This is likely a mistake too */
110 if (!mutt_buffer_len (tmp) && mutt_buffer_len (pattern))
111 {
112 strfcpy (err->data, _("mailbox shortcut expanded to empty regexp"), err->dsize);
113 mutt_buffer_pool_release (&tmp);
114 goto cleanup;
115 }
116
117 mutt_buffer_strcpy (pattern, mutt_b2s (tmp));
118 mutt_buffer_pool_release (&tmp);
119 }
120#ifdef USE_COMPRESSED
121 else if (data & (MUTT_APPENDHOOK | MUTT_OPENHOOK | MUTT_CLOSEHOOK))
122 {
123 if (mutt_comp_valid_command (mutt_b2s (command)) == 0)
124 {
125 strfcpy (err->data, _("badly formatted command string"), err->dsize);
126 goto cleanup;
127 }
128 }
129#endif
130 else if (DefaultHook && !(data & (MUTT_CHARSETHOOK | MUTT_ICONVHOOK | MUTT_ACCOUNTHOOK))
131 && (!WithCrypto || !(data & MUTT_CRYPTHOOK))
132 )
133 {
134 /* At this stage remain only message-hooks, reply-hooks, send-hooks,
135 * send2-hooks, save-hooks, and fcc-hooks: All those allowing full
136 * patterns. If given a simple regexp, we expand $default_hook.
137 */
138 mutt_check_simple (pattern, DefaultHook);
139 }
140
141 if (data & (MUTT_MBOXHOOK | MUTT_SAVEHOOK | MUTT_FCCHOOK))
142 {
143 mutt_buffer_expand_path (command);
144 }
145
146 /* check to make sure that a matching hook doesn't already exist */
147 for (ptr = Hooks; ptr; ptr = ptr->next)
148 {
149 if (ptr->type == data &&
150 ptr->rx.not == not &&
151 !mutt_strcmp (mutt_b2s (pattern), ptr->rx.pattern))
152 {
153 if (data & (MUTT_FOLDERHOOK | MUTT_SENDHOOK | MUTT_SEND2HOOK | MUTT_MESSAGEHOOK | MUTT_ACCOUNTHOOK | MUTT_REPLYHOOK | MUTT_CRYPTHOOK))
154 {
155 /* these hooks allow multiple commands with the same
156 * pattern, so if we've already seen this pattern/command pair, just
157 * ignore it instead of creating a duplicate */
158 if (!mutt_strcmp (ptr->command, mutt_b2s (command)))
159 {
160 rc = 0;
161 goto cleanup;
162 }
163 }
164 else
165 {
166 /* other hooks only allow one command per pattern, so update the
167 * entry with the new command. this currently does not change the
168 * order of execution of the hooks, which i think is desirable since
169 * a common action to perform is to change the default (.) entry
170 * based upon some other information. */
171 FREE (&ptr->command);
172 ptr->command = safe_strdup (mutt_b2s (command));
173 rc = 0;
174 goto cleanup;
175 }
176 }
177 if (!ptr->next)
178 break;
179 }
180
181 if (data & (MUTT_SENDHOOK | MUTT_SEND2HOOK | MUTT_SAVEHOOK | MUTT_FCCHOOK | MUTT_MESSAGEHOOK | MUTT_REPLYHOOK))
182 {
183 if ((pat = mutt_pattern_comp (pattern->data,
184 (data & (MUTT_SENDHOOK | MUTT_SEND2HOOK | MUTT_FCCHOOK)) ? 0 : MUTT_FULL_MSG,
185 err)) == NULL)
186 goto cleanup;
187 }
188 else
189 {
190 int rv;
191 /* Hooks not allowing full patterns: Check syntax of regexp */
192 rx = safe_malloc (sizeof (regex_t));
193#ifdef MUTT_CRYPTHOOK
194 if ((rv = REGCOMP (rx, mutt_b2s (pattern), ((data & (MUTT_CRYPTHOOK|MUTT_CHARSETHOOK|MUTT_ICONVHOOK)) ? REG_ICASE : 0))) != 0)
195#else
196 if ((rv = REGCOMP (rx, mutt_b2s (pattern), (data & (MUTT_CHARSETHOOK|MUTT_ICONVHOOK)) ? REG_ICASE : 0)) != 0)
197#endif /* MUTT_CRYPTHOOK */
198 {
199 regerror (rv, rx, err->data, err->dsize);
200 FREE (&rx);
201 goto cleanup;
202 }
203 }
204
205 if (ptr)
206 {
207 ptr->next = safe_calloc (1, sizeof (HOOK));
208 ptr = ptr->next;
209 }
210 else
211 Hooks = ptr = safe_calloc (1, sizeof (HOOK));
212 ptr->type = data;
213 ptr->command = safe_strdup (mutt_b2s (command));
214 ptr->pattern = pat;
215 ptr->rx.pattern = safe_strdup (mutt_b2s (pattern));
216 ptr->rx.rx = rx;
217 ptr->rx.not = not;
218
219 rc = 0;
220
221cleanup:
222 mutt_buffer_pool_release (&command);
223 mutt_buffer_pool_release (&pattern);
224 return rc;
225}
226
227static void delete_hook (HOOK *h)
228{
229 FREE (&h->command);
230 FREE (&h->rx.pattern);
231 if (h->rx.rx)
232 {
233 regfree (h->rx.rx);
234 }
235 mutt_pattern_free (&h->pattern);
236 FREE (&h);
237}
238
239/* Deletes all hooks of type ``type'', or all defined hooks if ``type'' is 0 */
240static void delete_hooks (int type)
241{
242 HOOK *h;
243 HOOK *prev;
244
245 while (h = Hooks, h && (type == 0 || type == h->type))
246 {
247 Hooks = h->next;
248 delete_hook (h);
249 }
250
251 prev = h; /* Unused assignment to avoid compiler warnings */
252
253 while (h)
254 {
255 if (type == h->type)
256 {
257 prev->next = h->next;
258 delete_hook (h);
259 }
260 else
261 prev = h;
262 h = prev->next;
263 }
264}
265
266static void delete_idxfmt_hooklist (void *list)
267{
268 HOOK *h, *next;
269
270 h = (HOOK *)list;
271 while (h)
272 {
273 next = h->next;
274 delete_hook (h);
275 h = next;
276 }
277}
278
279static void delete_idxfmt_hooks (void)
280{
281 hash_destroy (&IdxFmtHooks, delete_idxfmt_hooklist);
282}
283
284int mutt_parse_idxfmt_hook (BUFFER *buf, BUFFER *s, union pointer_long_t udata, BUFFER *err)
285{
286 HOOK *hooks, *ptr;
287 BUFFER *name, *pattern, *fmtstring;
288 int rc = -1, not = 0;
289 pattern_t *pat = NULL;
290 long data = udata.l;
291
292 name = mutt_buffer_pool_get ();
293 pattern = mutt_buffer_pool_get ();
294 fmtstring = mutt_buffer_pool_get ();
295
296 if (!IdxFmtHooks)
297 IdxFmtHooks = hash_create (30, MUTT_HASH_STRDUP_KEYS);
298
299 if (!MoreArgs (s))
300 {
301 strfcpy (err->data, _("not enough arguments"), err->dsize);
302 goto out;
303 }
304 mutt_extract_token (name, s, 0);
305 hooks = hash_find (IdxFmtHooks, mutt_b2s (name));
306
307 if (*s->dptr == '!')
308 {
309 s->dptr++;
310 SKIPWS (s->dptr);
311 not = 1;
312 }
313 mutt_extract_token (pattern, s, 0);
314
315 if (!MoreArgs (s))
316 {
317 strfcpy (err->data, _("too few arguments"), err->dsize);
318 goto out;
319 }
320 mutt_extract_token (fmtstring, s, 0);
321
322 if (MoreArgs (s))
323 {
324 strfcpy (err->data, _("too many arguments"), err->dsize);
325 goto out;
326 }
327
328 if (DefaultHook)
329 mutt_check_simple (pattern, DefaultHook);
330
331 /* check to make sure that a matching hook doesn't already exist */
332 for (ptr = hooks; ptr; ptr = ptr->next)
333 {
334 if ((ptr->rx.not == not) &&
335 !mutt_strcmp (mutt_b2s (pattern), ptr->rx.pattern))
336 {
337 FREE (&ptr->command);
338 ptr->command = safe_strdup (mutt_b2s (fmtstring));
339 rc = 0;
340 goto out;
341 }
342 if (!ptr->next)
343 break;
344 }
345
346 /* MUTT_PATTERN_DYNAMIC sets so that date ranges are regenerated during
347 * matching. This of course is slower, but index-format-hook is commonly
348 * used for date ranges, and they need to be evaluated relative to "now", not
349 * the hook compilation time.
350 */
351 if ((pat = mutt_pattern_comp (pattern->data,
352 MUTT_FULL_MSG | MUTT_PATTERN_DYNAMIC,
353 err)) == NULL)
354 goto out;
355
356 if (ptr)
357 {
358 ptr->next = safe_calloc (1, sizeof (HOOK));
359 ptr = ptr->next;
360 }
361 else
362 ptr = safe_calloc (1, sizeof (HOOK));
363 ptr->type = data;
364 ptr->command = safe_strdup (mutt_b2s (fmtstring));
365 ptr->pattern = pat;
366 ptr->rx.pattern = safe_strdup (mutt_b2s (pattern));
367 ptr->rx.rx = NULL;
368 ptr->rx.not = not;
369
370 if (!hooks)
371 hash_insert (IdxFmtHooks, mutt_b2s (name), ptr);
372
373 rc = 0;
374
375out:
376 mutt_buffer_pool_release (&name);
377 mutt_buffer_pool_release (&pattern);
378 mutt_buffer_pool_release (&fmtstring);
379
380 return rc;
381}
382
383int mutt_parse_unhook (BUFFER *buf, BUFFER *s, union pointer_long_t udata, BUFFER *err)
384{
385 while (MoreArgs (s))
386 {
387 mutt_extract_token (buf, s, 0);
388 if (mutt_strcmp ("*", buf->data) == 0)
389 {
390 if (current_hook_type)
391 {
392 snprintf (err->data, err->dsize, "%s",
393 _("unhook: Can't do unhook * from within a hook."));
394 return -1;
395 }
396 delete_hooks (0);
397 delete_idxfmt_hooks ();
398 }
399 else
400 {
401 int type = mutt_get_hook_type (buf->data);
402
403 if (!type)
404 {
405 snprintf (err->data, err->dsize,
406 _("unhook: unknown hook type: %s"), buf->data);
407 return (-1);
408 }
409 if (current_hook_type == type)
410 {
411 snprintf (err->data, err->dsize,
412 _("unhook: Can't delete a %s from within a %s."),
413 buf->data, buf->data);
414 return -1;
415 }
416 if (type == MUTT_IDXFMTHOOK)
417 delete_idxfmt_hooks ();
418 else
419 delete_hooks (type);
420 }
421 }
422 return 0;
423}
424
425void mutt_folder_hook (const char *path)
426{
427 HOOK *tmp = Hooks;
428 BUFFER err, token;
429
430 current_hook_type = MUTT_FOLDERHOOK;
431
432 mutt_buffer_init (&err);
433 err.dsize = STRING;
434 err.data = safe_malloc (err.dsize);
435 mutt_buffer_init (&token);
436 for (; tmp; tmp = tmp->next)
437 {
438 if (!tmp->command)
439 continue;
440
441 if (tmp->type & MUTT_FOLDERHOOK)
442 {
443 if ((regexec (tmp->rx.rx, path, 0, NULL, 0) == 0) ^ tmp->rx.not)
444 {
445 if (mutt_parse_rc_line (tmp->command, &token, &err) == -1)
446 {
447 mutt_error ("%s", err.data);
448 FREE (&token.data);
449 mutt_sleep (1); /* pause a moment to let the user see the error */
450 current_hook_type = 0;
451 FREE (&err.data);
452
453 return;
454 }
455 }
456 }
457 }
458 FREE (&token.data);
459 FREE (&err.data);
460
461 current_hook_type = 0;
462}
463
464char *mutt_find_hook (int type, const char *pat)
465{
466 HOOK *tmp = Hooks;
467
468 for (; tmp; tmp = tmp->next)
469 if (tmp->type & type)
470 {
471 if (regexec (tmp->rx.rx, pat, 0, NULL, 0) == 0)
472 return (tmp->command);
473 }
474 return (NULL);
475}
476
477void mutt_message_hook (CONTEXT *ctx, HEADER *hdr, int type)
478{
479 BUFFER err, token;
480 HOOK *hook;
481 pattern_cache_t cache;
482
483 current_hook_type = type;
484
485 mutt_buffer_init (&err);
486 err.dsize = STRING;
487 err.data = safe_malloc (err.dsize);
488 mutt_buffer_init (&token);
489 memset (&cache, 0, sizeof (cache));
490 for (hook = Hooks; hook; hook = hook->next)
491 {
492 if (!hook->command)
493 continue;
494
495 if (hook->type & type)
496 if ((mutt_pattern_exec (hook->pattern, 0, ctx, hdr, &cache) > 0) ^ hook->rx.not)
497 {
498 if (mutt_parse_rc_line (hook->command, &token, &err) != 0)
499 {
500 FREE (&token.data);
501 mutt_error ("%s", err.data);
502 mutt_sleep (1);
503 current_hook_type = 0;
504 FREE (&err.data);
505
506 return;
507 }
508 /* Executing arbitrary commands could affect the pattern results,
509 * so the cache has to be wiped */
510 memset (&cache, 0, sizeof (cache));
511 }
512 }
513 FREE (&token.data);
514 FREE (&err.data);
515
516 current_hook_type = 0;
517}
518
519static int
520mutt_addr_hook (char *path, size_t pathlen, int type, CONTEXT *ctx, HEADER *hdr)
521{
522 HOOK *hook;
523 pattern_cache_t cache;
524
525 memset (&cache, 0, sizeof (cache));
526 /* determine if a matching hook exists */
527 for (hook = Hooks; hook; hook = hook->next)
528 {
529 if (!hook->command)
530 continue;
531
532 if (hook->type & type)
533 if ((mutt_pattern_exec (hook->pattern, 0, ctx, hdr, &cache) > 0) ^ hook->rx.not)
534 {
535 mutt_make_string (path, pathlen, hook->command, ctx, hdr);
536 return 0;
537 }
538 }
539
540 return -1;
541}
542
543void mutt_default_save (char *path, size_t pathlen, HEADER *hdr)
544{
545 *path = 0;
546 if (mutt_addr_hook (path, pathlen, MUTT_SAVEHOOK, Context, hdr) != 0)
547 {
548 BUFFER *tmp = NULL;
549 ADDRESS *adr;
550 ENVELOPE *env = hdr->env;
551 int fromMe = mutt_addr_is_user (env->from);
552
553 if (!fromMe && env->reply_to && env->reply_to->mailbox)
554 adr = env->reply_to;
555 else if (!fromMe && env->from && env->from->mailbox)
556 adr = env->from;
557 else if (env->to && env->to->mailbox)
558 adr = env->to;
559 else if (env->cc && env->cc->mailbox)
560 adr = env->cc;
561 else
562 adr = NULL;
563 if (adr)
564 {
565 tmp = mutt_buffer_pool_get ();
566 mutt_safe_path (tmp, adr);
567 snprintf (path, pathlen, "=%s", mutt_b2s (tmp));
568 mutt_buffer_pool_release (&tmp);
569 }
570 }
571}
572
573void mutt_select_fcc (BUFFER *path, HEADER *hdr)
574{
575 ADDRESS *adr;
576 BUFFER *buf = NULL;
577 ENVELOPE *env = hdr->env;
578
579 mutt_buffer_increase_size (path, _POSIX_PATH_MAX);
580
581 if (mutt_addr_hook (path->data, path->dsize, MUTT_FCCHOOK, NULL, hdr) != 0)
582 {
583 if ((option (OPTSAVENAME) || option (OPTFORCENAME)) &&
584 (env->to || env->cc || env->bcc))
585 {
586 adr = env->to ? env->to : (env->cc ? env->cc : env->bcc);
587 buf = mutt_buffer_pool_get ();
588 mutt_safe_path (buf, adr);
589 mutt_buffer_concat_path (path, NONULL(Maildir), mutt_b2s (buf));
590 mutt_buffer_pool_release (&buf);
591 if (!option (OPTFORCENAME) && mx_access (mutt_b2s (path), W_OK) != 0)
592 mutt_buffer_strcpy (path, NONULL (Outbox));
593 }
594 else
595 mutt_buffer_strcpy (path, NONULL (Outbox));
596 }
597 else
598 mutt_buffer_fix_dptr (path);
599
600 mutt_buffer_pretty_mailbox (path);
601}
602
603static char *_mutt_string_hook (const char *match, int hook)
604{
605 HOOK *tmp = Hooks;
606
607 for (; tmp; tmp = tmp->next)
608 {
609 if ((tmp->type & hook) &&
610 ((match && regexec (tmp->rx.rx, match, 0, NULL, 0) == 0) ^ tmp->rx.not))
611 return (tmp->command);
612 }
613 return (NULL);
614}
615
616static LIST *_mutt_list_hook (const char *match, int hook)
617{
618 HOOK *tmp = Hooks;
619 LIST *matches = NULL;
620
621 for (; tmp; tmp = tmp->next)
622 {
623 if ((tmp->type & hook) &&
624 ((match && regexec (tmp->rx.rx, match, 0, NULL, 0) == 0) ^ tmp->rx.not))
625 matches = mutt_add_list (matches, tmp->command);
626 }
627 return (matches);
628}
629
630char *mutt_charset_hook (const char *chs)
631{
632 return _mutt_string_hook (chs, MUTT_CHARSETHOOK);
633}
634
635char *mutt_iconv_hook (const char *chs)
636{
637 return _mutt_string_hook (chs, MUTT_ICONVHOOK);
638}
639
640LIST *mutt_crypt_hook (ADDRESS *adr)
641{
642 return _mutt_list_hook (adr->mailbox, MUTT_CRYPTHOOK);
643}
644
645#ifdef USE_SOCKET
646void mutt_account_hook (const char* url)
647{
648 /* parsing commands with URLs in an account hook can cause a recursive
649 * call. We just skip processing if this occurs. Typically such commands
650 * belong in a folder-hook -- perhaps we should warn the user. */
651 static int inhook = 0;
652
653 HOOK* hook;
654 BUFFER token;
655 BUFFER err;
656
657 if (inhook)
658 return;
659
660 mutt_buffer_init (&err);
661 err.dsize = STRING;
662 err.data = safe_malloc (err.dsize);
663 mutt_buffer_init (&token);
664
665 for (hook = Hooks; hook; hook = hook->next)
666 {
667 if (! (hook->command && (hook->type & MUTT_ACCOUNTHOOK)))
668 continue;
669
670 if ((regexec (hook->rx.rx, url, 0, NULL, 0) == 0) ^ hook->rx.not)
671 {
672 inhook = 1;
673
674 if (mutt_parse_rc_line (hook->command, &token, &err) == -1)
675 {
676 FREE (&token.data);
677 mutt_error ("%s", err.data);
678 FREE (&err.data);
679 mutt_sleep (1);
680
681 inhook = 0;
682 return;
683 }
684
685 inhook = 0;
686 }
687 }
688
689 FREE (&token.data);
690 FREE (&err.data);
691}
692#endif
693
694const char *mutt_idxfmt_hook (const char *name, CONTEXT *ctx, HEADER *hdr)
695{
696 HOOK *hooklist, *hook;
697 pattern_cache_t cache;
698 const char *fmtstring = NULL;
699
700 if (!IdxFmtHooks)
701 return NULL;
702
703 current_hook_type = MUTT_IDXFMTHOOK;
704 hooklist = hash_find (IdxFmtHooks, name);
705 memset (&cache, 0, sizeof (cache));
706
707 for (hook = hooklist; hook; hook = hook->next)
708 {
709 if ((mutt_pattern_exec (hook->pattern, 0, ctx, hdr, &cache) > 0) ^ hook->rx.not)
710 {
711 fmtstring = hook->command;
712 break;
713 }
714 }
715
716 current_hook_type = 0;
717
718 return fmtstring;
719}