b157f05119b5e03a3c22e54e611930138a49d259
[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_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_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_SAVEHOOK | M_FCCHOOK | M_MESSAGEHOOK | M_REPLYHOOK))
164   {
165     if ((pat = mutt_pattern_comp (pattern.data,
166            (data & (M_SENDHOOK | 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,
274                   _("unhook: Can't delete a %s from within a %s."),
275                   buf->data, buf->data);
276         return -1;
277       }
278       delete_hooks (type);
279     }
280   }
281   return 0;
282 }
283
284 void mutt_folder_hook (char *path)
285 {
286   HOOK *tmp = Hooks;
287   BUFFER err, token;
288   char buf[STRING];
289
290   current_hook_type = M_FOLDERHOOK;
291   
292   err.data = buf;
293   err.dsize = sizeof (buf);
294   memset (&token, 0, sizeof (token));
295   for (; tmp; tmp = tmp->next)
296   {
297     if(!tmp->command)
298       continue;
299
300     if (tmp->type & M_FOLDERHOOK)
301     {
302       if ((regexec (tmp->rx.rx, path, 0, NULL, 0) == 0) ^ tmp->rx.not)
303       {
304         if (mutt_parse_rc_line (tmp->command, &token, &err) == -1)
305         {
306           mutt_error ("%s", err.data);
307           FREE (&token.data);
308           mutt_sleep (1);       /* pause a moment to let the user see the error */
309           current_hook_type = 0;
310           return;
311         }
312       }
313     }
314   }
315   FREE (&token.data);
316   
317   current_hook_type = 0;
318 }
319
320 char *mutt_find_hook (int type, const char *pat)
321 {
322   HOOK *tmp = Hooks;
323
324   for (; tmp; tmp = tmp->next)
325     if (tmp->type & type)
326     {
327       if (regexec (tmp->rx.rx, pat, 0, NULL, 0) == 0)
328         return (tmp->command);
329     }
330   return (NULL);
331 }
332
333 void mutt_message_hook (CONTEXT *ctx, HEADER *hdr, int type)
334 {
335   BUFFER err, token;
336   HOOK *hook;
337   char buf[STRING];
338
339   current_hook_type = type;
340   
341   err.data = buf;
342   err.dsize = sizeof (buf);
343   memset (&token, 0, sizeof (token));
344   for (hook = Hooks; hook; hook = hook->next)
345   {
346     if(!hook->command)
347       continue;
348
349     if (hook->type & type)
350       if ((mutt_pattern_exec (hook->pattern, 0, ctx, hdr) > 0) ^ hook->rx.not)
351         if (mutt_parse_rc_line (hook->command, &token, &err) != 0)
352         {
353           FREE (&token.data);
354           mutt_error ("%s", err.data);
355           mutt_sleep (1);
356           current_hook_type = 0;
357           return;
358         }
359   }
360   FREE (&token.data);
361   current_hook_type = 0;
362 }
363
364 static int
365 mutt_addr_hook (char *path, size_t pathlen, int type, CONTEXT *ctx, HEADER *hdr)
366 {
367   HOOK *hook;
368
369   /* determine if a matching hook exists */
370   for (hook = Hooks; hook; hook = hook->next)
371   {
372     if(!hook->command)
373       continue;
374
375     if (hook->type & type)
376       if ((mutt_pattern_exec (hook->pattern, 0, ctx, hdr) > 0) ^ hook->rx.not)
377       {
378         mutt_make_string (path, pathlen, hook->command, ctx, hdr);
379         return 0;
380       }
381   }
382
383   return -1;
384 }
385
386 void mutt_default_save (char *path, size_t pathlen, HEADER *hdr)
387 {
388   *path = 0;
389   if (mutt_addr_hook (path, pathlen, M_SAVEHOOK, Context, hdr) != 0)
390   {
391     char tmp[_POSIX_PATH_MAX];
392     ADDRESS *adr;
393     ENVELOPE *env = hdr->env;
394     int fromMe = mutt_addr_is_user (env->from);
395
396     if (!fromMe && env->reply_to && env->reply_to->mailbox)
397       adr = env->reply_to;
398     else if (!fromMe && env->from && env->from->mailbox)
399       adr = env->from;
400     else if (env->to && env->to->mailbox)
401       adr = env->to;
402     else if (env->cc && env->cc->mailbox)
403       adr = env->cc;
404     else
405       adr = NULL;
406     if (adr)
407     {
408       mutt_safe_path (tmp, sizeof (tmp), adr);
409       snprintf (path, pathlen, "=%s", tmp);
410     }
411   }
412 }
413
414 void mutt_select_fcc (char *path, size_t pathlen, HEADER *hdr)
415 {
416   ADDRESS *adr;
417   char buf[_POSIX_PATH_MAX];
418   ENVELOPE *env = hdr->env;
419
420   if (mutt_addr_hook (path, pathlen, M_FCCHOOK, NULL, hdr) != 0)
421   {
422     if ((option (OPTSAVENAME) || option (OPTFORCENAME)) &&
423         (env->to || env->cc || env->bcc))
424     {
425       adr = env->to ? env->to : (env->cc ? env->cc : env->bcc);
426       mutt_safe_path (buf, sizeof (buf), adr);
427       mutt_concat_path (path, NONULL(Maildir), buf, pathlen);
428       if (!option (OPTFORCENAME) && mx_access (path, W_OK) != 0)
429         strfcpy (path, NONULL (Outbox), pathlen);
430     }
431     else
432       strfcpy (path, NONULL (Outbox), pathlen);
433   }
434   mutt_pretty_mailbox (path);
435 }
436
437 static char *_mutt_string_hook (const char *match, int hook)
438 {
439   HOOK *tmp = Hooks;
440
441   for (; tmp; tmp = tmp->next)
442   {
443     if ((tmp->type & hook) && ((match &&
444          regexec (tmp->rx.rx, match, 0, NULL, 0) == 0) ^ tmp->rx.not))
445       return (tmp->command);
446   }
447   return (NULL);
448 }
449
450 char *mutt_charset_hook (const char *chs)
451 {
452   return _mutt_string_hook (chs, M_CHARSETHOOK);
453 }
454
455 char *mutt_iconv_hook (const char *chs)
456 {
457   return _mutt_string_hook (chs, M_ICONVHOOK);
458 }
459
460 char *mutt_crypt_hook (ADDRESS *adr)
461 {
462   return _mutt_string_hook (adr->mailbox, M_CRYPTHOOK);
463 }
464
465 #ifdef USE_SOCKET
466 void mutt_account_hook (const char* url)
467 {
468   HOOK* hook;
469   BUFFER token;
470   BUFFER err;
471   char buf[STRING];
472
473   err.data = buf;
474   err.dsize = sizeof (buf);
475   memset (&token, 0, sizeof (token));
476
477   for (hook = Hooks; hook; hook = hook->next)
478   {
479     if (! (hook->command && (hook->type & M_ACCOUNTHOOK)))
480       continue;
481
482     if ((regexec (hook->rx.rx, url, 0, NULL, 0) == 0) ^ hook->rx.not)
483     {
484       if (mutt_parse_rc_line (hook->command, &token, &err) == -1)
485       {
486         FREE (&token.data);
487         mutt_error ("%s", err.data);
488         mutt_sleep (1);
489
490         return;
491       }
492     }
493   }
494
495   FREE (&token.data);
496 }
497 #endif