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