543e61558e0d1a25cdcd15d955199324a1546c4f
[apps/madmutt.git] / hook.c
1 /* 
2  * Copyright (C) 1996-2002 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., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
17  */
18
19 #include "mutt.h"
20 #include "mailbox.h"
21 #include "mutt_crypt.h"
22
23 #ifdef USE_COMPRESSED
24 #include "compress.h"
25 #endif
26
27 #include <limits.h>
28 #include <string.h>
29 #include <stdlib.h>
30 #include <ctype.h>
31 #include <unistd.h>
32
33 typedef struct hook
34 {
35   int type;             /* hook type */
36   REGEXP rx;            /* regular expression */
37   char *command;        /* filename, command or pattern to execute */
38   pattern_t *pattern;   /* used for fcc,save,send-hook */
39   struct hook *next;
40 } HOOK;
41
42 static HOOK *Hooks = NULL;
43
44 static int current_hook_type = 0;
45
46 int mutt_parse_hook (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err)
47 {
48   HOOK *ptr;
49   BUFFER command, pattern;
50   int rc, not = 0;
51   regex_t *rx = NULL;
52   pattern_t *pat = NULL;
53   char path[_POSIX_PATH_MAX];
54
55   memset (&pattern, 0, sizeof (pattern));
56   memset (&command, 0, sizeof (command));
57
58   if (*s->dptr == '!')
59   {
60     s->dptr++;
61     SKIPWS (s->dptr);
62     not = 1;
63   }
64
65   mutt_extract_token (&pattern, s, 0);
66
67   if (!MoreArgs (s))
68   {
69     strfcpy (err->data, _("too few arguments"), err->dsize);
70     goto error;
71   }
72
73   mutt_extract_token (&command, s, (data & (M_FOLDERHOOK | M_SENDHOOK | M_SEND2HOOK | M_ACCOUNTHOOK | M_REPLYHOOK)) ?  M_TOKEN_SPACE : 0);
74
75   if (!command.data)
76   {
77     strfcpy (err->data, _("too few arguments"), err->dsize);
78     goto error;
79   }
80
81   if (MoreArgs (s))
82   {
83     strfcpy (err->data, _("too many arguments"), err->dsize);
84     goto error;
85   }
86
87   if (data & (M_FOLDERHOOK | M_MBOXHOOK))
88   {
89     strfcpy (path, pattern.data, sizeof (path));
90     _mutt_expand_path (path, sizeof (path), 1);
91     FREE (&pattern.data);
92     memset (&pattern, 0, sizeof (pattern));
93     pattern.data = safe_strdup (path);
94   }
95 #ifdef USE_COMPRESSED
96   else if (data & (M_APPENDHOOK | M_OPENHOOK | M_CLOSEHOOK))
97   {
98     if (mutt_test_compress_command (command.data))
99     {
100       strfcpy (err->data, _("bad formatted command string"), err->dsize);
101       return (-1);
102     }
103   }
104 #endif
105   else if (DefaultHook && !(data & (M_CHARSETHOOK | M_ACCOUNTHOOK))
106            && (!WithCrypto || !(data & M_CRYPTHOOK))
107       )
108   {
109     char tmp[HUGE_STRING];
110
111     strfcpy (tmp, pattern.data, sizeof (tmp));
112     mutt_check_simple (tmp, sizeof (tmp), DefaultHook);
113     FREE (&pattern.data);
114     memset (&pattern, 0, sizeof (pattern));
115     pattern.data = safe_strdup (tmp);
116   }
117
118   if (data & (M_MBOXHOOK | M_SAVEHOOK | M_FCCHOOK))
119   {
120     strfcpy (path, command.data, sizeof (path));
121     mutt_expand_path (path, sizeof (path));
122     FREE (&command.data);
123     memset (&command, 0, sizeof (command));
124     command.data = safe_strdup (path);
125   }
126
127   /* check to make sure that a matching hook doesn't already exist */
128   for (ptr = Hooks; ptr; ptr = ptr->next)
129   {
130     if (ptr->type == data &&
131         ptr->rx.not == not &&
132         !mutt_strcmp (pattern.data, ptr->rx.pattern))
133     {
134       if (data & (M_FOLDERHOOK | M_SENDHOOK | M_SEND2HOOK | M_MESSAGEHOOK | M_ACCOUNTHOOK | M_REPLYHOOK))
135       {
136         /* these hooks allow multiple commands with the same
137          * pattern, so if we've already seen this pattern/command pair, just
138          * ignore it instead of creating a duplicate */
139         if (!mutt_strcmp (ptr->command, command.data))
140         {
141           FREE (&command.data);
142           FREE (&pattern.data);
143           return 0;
144         }
145       }
146       else
147       {
148         /* other hooks only allow one command per pattern, so update the
149          * entry with the new command.  this currently does not change the
150          * order of execution of the hooks, which i think is desirable since
151          * a common action to perform is to change the default (.) entry
152          * based upon some other information. */
153         FREE (&ptr->command);
154         ptr->command = command.data;
155         FREE (&pattern.data);
156         return 0;
157       }
158     }
159     if (!ptr->next)
160       break;
161   }
162
163   if (data & (M_SENDHOOK | M_SEND2HOOK | M_SAVEHOOK | M_FCCHOOK | M_MESSAGEHOOK | M_REPLYHOOK))
164   {
165     if ((pat = mutt_pattern_comp (pattern.data,
166            (data & (M_SENDHOOK | M_SEND2HOOK | M_FCCHOOK)) ? 0 : M_FULL_MSG,
167                                   err)) == NULL)
168       goto error;
169   }
170   else
171   {
172     rx = safe_malloc (sizeof (regex_t));
173 #ifdef M_CRYPTHOOK
174     if ((rc = REGCOMP (rx, NONULL(pattern.data), ((data & (M_CRYPTHOOK|M_CHARSETHOOK)) ? REG_ICASE : 0))) != 0)
175 #else
176     if ((rc = REGCOMP (rx, NONULL(pattern.data), (data & (M_CHARSETHOOK|M_ICONVHOOK)) ? REG_ICASE : 0)) != 0)
177 #endif /* M_CRYPTHOOK */
178     {
179       regerror (rc, rx, err->data, err->dsize);
180       regfree (rx);
181       FREE (&rx);
182       goto error;
183     }
184   }
185
186   if (ptr)
187   {
188     ptr->next = safe_calloc (1, sizeof (HOOK));
189     ptr = ptr->next;
190   }
191   else
192     Hooks = ptr = safe_calloc (1, sizeof (HOOK));
193   ptr->type = data;
194   ptr->command = command.data;
195   ptr->pattern = pat;
196   ptr->rx.pattern = pattern.data;
197   ptr->rx.rx = rx;
198   ptr->rx.not = not;
199   return 0;
200
201 error:
202   FREE (&pattern.data);
203   FREE (&command.data);
204   return (-1);
205 }
206
207 static void delete_hook (HOOK *h)
208 {
209   FREE (&h->command);
210   FREE (&h->rx.pattern);
211   if (h->rx.rx)
212   {
213     regfree (h->rx.rx);
214   }
215   mutt_pattern_free (&h->pattern);
216   FREE (&h);
217 }
218
219 /* Deletes all hooks of type ``type'', or all defined hooks if ``type'' is 0 */
220 static void delete_hooks (int type)
221 {
222   HOOK *h;
223   HOOK *prev;
224
225   while (h = Hooks, h && (type == 0 || type == h->type))
226   {
227     Hooks = h->next;
228     delete_hook (h);
229   }
230
231   prev = h; /* Unused assignment to avoid compiler warnings */
232
233   while (h)
234   {
235     if (type == h->type)
236     {
237       prev->next = h->next;
238       delete_hook (h);
239     }
240     else
241       prev = h;
242     h = prev->next;
243   }
244 }
245
246 int mutt_parse_unhook (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err)
247 {
248   while (MoreArgs (s))
249   {
250     mutt_extract_token (buf, s, 0);
251     if (mutt_strcmp ("*", buf->data) == 0)
252     {
253       if (current_hook_type)
254       {
255         snprintf (err->data, err->dsize,
256                   _("unhook: Can't do unhook * from within a hook."));
257         return -1;
258       }
259       delete_hooks (0);
260     }
261     else
262     {
263       int type = mutt_get_hook_type (buf->data);
264
265       if (!type)
266       {
267         snprintf (err->data, err->dsize,
268                  _("unhook: unknown hook type: %s"), buf->data);
269         return (-1);
270       }
271       if (current_hook_type == type)
272       {
273         snprintf (err->data, err->dsize, _("unhook: Can't delete a %s from within a %s."),
274                   buf->data, buf->data);
275         return -1;
276       }
277       delete_hooks (type);
278     }
279   }
280   return 0;
281 }
282
283 void mutt_folder_hook (char *path)
284 {
285   HOOK *tmp = Hooks;
286   BUFFER err, token;
287   char buf[STRING];
288
289   current_hook_type = M_FOLDERHOOK;
290   
291   err.data = buf;
292   err.dsize = sizeof (buf);
293   memset (&token, 0, sizeof (token));
294   for (; tmp; tmp = tmp->next)
295   {
296     if(!tmp->command)
297       continue;
298
299     if (tmp->type & M_FOLDERHOOK)
300     {
301       if ((regexec (tmp->rx.rx, path, 0, NULL, 0) == 0) ^ tmp->rx.not)
302       {
303         if (mutt_parse_rc_line (tmp->command, &token, &err) == -1)
304         {
305           mutt_error ("%s", err.data);
306           FREE (&token.data);
307           mutt_sleep (1);       /* pause a moment to let the user see the error */
308           current_hook_type = 0;
309           return;
310         }
311       }
312     }
313   }
314   FREE (&token.data);
315   
316   current_hook_type = 0;
317 }
318
319 char *mutt_find_hook (int type, const char *pat)
320 {
321   HOOK *tmp = Hooks;
322
323   for (; tmp; tmp = tmp->next)
324     if (tmp->type & type)
325     {
326       if (regexec (tmp->rx.rx, pat, 0, NULL, 0) == 0)
327         return (tmp->command);
328     }
329   return (NULL);
330 }
331
332 void mutt_message_hook (CONTEXT *ctx, HEADER *hdr, int type)
333 {
334   BUFFER err, token;
335   HOOK *hook;
336   char buf[STRING];
337
338   current_hook_type = type;
339   
340   err.data = buf;
341   err.dsize = sizeof (buf);
342   memset (&token, 0, sizeof (token));
343   for (hook = Hooks; hook; hook = hook->next)
344   {
345     if(!hook->command)
346       continue;
347
348     if (hook->type & type)
349       if ((mutt_pattern_exec (hook->pattern, 0, ctx, hdr) > 0) ^ hook->rx.not)
350         if (mutt_parse_rc_line (hook->command, &token, &err) != 0)
351         {
352           FREE (&token.data);
353           mutt_error ("%s", err.data);
354           mutt_sleep (1);
355           current_hook_type = 0;
356           return;
357         }
358   }
359   FREE (&token.data);
360   current_hook_type = 0;
361 }
362
363 static int
364 mutt_addr_hook (char *path, size_t pathlen, int type, CONTEXT *ctx, HEADER *hdr)
365 {
366   HOOK *hook;
367
368   /* determine if a matching hook exists */
369   for (hook = Hooks; hook; hook = hook->next)
370   {
371     if(!hook->command)
372       continue;
373
374     if (hook->type & type)
375       if ((mutt_pattern_exec (hook->pattern, 0, ctx, hdr) > 0) ^ hook->rx.not)
376       {
377         mutt_make_string (path, pathlen, hook->command, ctx, hdr);
378         return 0;
379       }
380   }
381
382   return -1;
383 }
384
385 void mutt_default_save (char *path, size_t pathlen, HEADER *hdr)
386 {
387   *path = 0;
388   if (mutt_addr_hook (path, pathlen, M_SAVEHOOK, Context, hdr) != 0)
389   {
390     char tmp[_POSIX_PATH_MAX];
391     ADDRESS *adr;
392     ENVELOPE *env = hdr->env;
393     int fromMe = mutt_addr_is_user (env->from);
394
395     if (!fromMe && env->reply_to && env->reply_to->mailbox)
396       adr = env->reply_to;
397     else if (!fromMe && env->from && env->from->mailbox)
398       adr = env->from;
399     else if (env->to && env->to->mailbox)
400       adr = env->to;
401     else if (env->cc && env->cc->mailbox)
402       adr = env->cc;
403     else
404       adr = NULL;
405     if (adr)
406     {
407       mutt_safe_path (tmp, sizeof (tmp), adr);
408       snprintf (path, pathlen, "=%s", tmp);
409     }
410   }
411 }
412
413 void mutt_select_fcc (char *path, size_t pathlen, HEADER *hdr)
414 {
415   ADDRESS *adr;
416   char buf[_POSIX_PATH_MAX];
417   ENVELOPE *env = hdr->env;
418
419   if (mutt_addr_hook (path, pathlen, M_FCCHOOK, NULL, hdr) != 0)
420   {
421     if ((option (OPTSAVENAME) || option (OPTFORCENAME)) &&
422         (env->to || env->cc || env->bcc))
423     {
424       adr = env->to ? env->to : (env->cc ? env->cc : env->bcc);
425       mutt_safe_path (buf, sizeof (buf), adr);
426       mutt_concat_path (path, NONULL(Maildir), buf, pathlen);
427       if (!option (OPTFORCENAME) && mx_access (path, W_OK) != 0)
428         strfcpy (path, NONULL (Outbox), pathlen);
429     }
430     else
431       strfcpy (path, NONULL (Outbox), pathlen);
432   }
433   mutt_pretty_mailbox (path);
434 }
435
436 static char *_mutt_string_hook (const char *match, int hook)
437 {
438   HOOK *tmp = Hooks;
439
440   for (; tmp; tmp = tmp->next)
441   {
442     if ((tmp->type & hook) && ((match &&
443          regexec (tmp->rx.rx, match, 0, NULL, 0) == 0) ^ tmp->rx.not))
444       return (tmp->command);
445   }
446   return (NULL);
447 }
448
449 char *mutt_charset_hook (const char *chs)
450 {
451   return _mutt_string_hook (chs, M_CHARSETHOOK);
452 }
453
454 char *mutt_iconv_hook (const char *chs)
455 {
456   return _mutt_string_hook (chs, M_ICONVHOOK);
457 }
458
459 char *mutt_crypt_hook (ADDRESS *adr)
460 {
461   return _mutt_string_hook (adr->mailbox, M_CRYPTHOOK);
462 }
463
464 #ifdef USE_SOCKET
465 void mutt_account_hook (const char* url)
466 {
467   HOOK* hook;
468   BUFFER token;
469   BUFFER err;
470   char buf[STRING];
471
472   err.data = buf;
473   err.dsize = sizeof (buf);
474   memset (&token, 0, sizeof (token));
475
476   for (hook = Hooks; hook; hook = hook->next)
477   {
478     if (! (hook->command && (hook->type & M_ACCOUNTHOOK)))
479       continue;
480
481     if ((regexec (hook->rx.rx, url, 0, NULL, 0) == 0) ^ hook->rx.not)
482     {
483       if (mutt_parse_rc_line (hook->command, &token, &err) == -1)
484       {
485         FREE (&token.data);
486         mutt_error ("%s", err.data);
487         mutt_sleep (1);
488
489         return;
490       }
491     }
492   }
493
494   FREE (&token.data);
495 }
496 #endif