]> granicus.if.org Git - mutt/blob - hook.c
Convert smime_invoke_import() and helpers to use buffer pool.
[mutt] / hook.c
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
37 typedef 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
46 static HOOK *Hooks = NULL;
47 static HASH *IdxFmtHooks = NULL;
48
49 static int current_hook_type = 0;
50
51 int 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
221 cleanup:
222   mutt_buffer_pool_release (&command);
223   mutt_buffer_pool_release (&pattern);
224   return rc;
225 }
226
227 static 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 */
240 static 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
266 static 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
279 static void delete_idxfmt_hooks (void)
280 {
281   hash_destroy (&IdxFmtHooks, delete_idxfmt_hooklist);
282 }
283
284 int 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
375 out:
376   mutt_buffer_pool_release (&name);
377   mutt_buffer_pool_release (&pattern);
378   mutt_buffer_pool_release (&fmtstring);
379
380   return rc;
381 }
382
383 int 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
425 void 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
464 char *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
477 void 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
519 static int
520 mutt_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
543 void 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
573 void 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
603 static 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
616 static 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
630 char *mutt_charset_hook (const char *chs)
631 {
632   return _mutt_string_hook (chs, MUTT_CHARSETHOOK);
633 }
634
635 char *mutt_iconv_hook (const char *chs)
636 {
637   return _mutt_string_hook (chs, MUTT_ICONVHOOK);
638 }
639
640 LIST *mutt_crypt_hook (ADDRESS *adr)
641 {
642   return _mutt_list_hook (adr->mailbox, MUTT_CRYPTHOOK);
643 }
644
645 #ifdef USE_SOCKET
646 void 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
694 const 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 }