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;
47
48static int current_hook_type = 0;
49
50int mutt_parse_hook (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err)
51{
52 HOOK *ptr;
53 BUFFER command, pattern;
54 int rc, not = 0;
55 regex_t *rx = NULL;
56 pattern_t *pat = NULL;
57 char path[_POSIX_PATH_MAX];
58
59 mutt_buffer_init (&pattern);
60 mutt_buffer_init (&command);
61
62 if (*s->dptr == '!')
63 {
64 s->dptr++;
65 SKIPWS (s->dptr);
66 not = 1;
67 }
68
69 mutt_extract_token (&pattern, s, 0);
70
71 if (!MoreArgs (s))
72 {
73 strfcpy (err->data, _("too few arguments"), err->dsize);
74 goto error;
75 }
76
77 mutt_extract_token (&command, s, (data & (MUTT_FOLDERHOOK | MUTT_SENDHOOK | MUTT_SEND2HOOK | MUTT_ACCOUNTHOOK | MUTT_REPLYHOOK)) ? MUTT_TOKEN_SPACE : 0);
78
79 if (!command.data)
80 {
81 strfcpy (err->data, _("too few arguments"), err->dsize);
82 goto error;
83 }
84
85 if (MoreArgs (s))
86 {
87 strfcpy (err->data, _("too many arguments"), err->dsize);
88 goto error;
89 }
90
91 if (data & (MUTT_FOLDERHOOK | MUTT_MBOXHOOK))
92 {
93 /* Accidentally using the ^ mailbox shortcut in the .muttrc is a
94 * common mistake */
95 if ((*pattern.data == '^') && (! CurrentFolder))
96 {
97 strfcpy (err->data, _("current mailbox shortcut '^' is unset"), err->dsize);
98 goto error;
99 }
100
101 strfcpy (path, pattern.data, sizeof (path));
102 _mutt_expand_path (path, sizeof (path), 1);
103
104 /* Check for other mailbox shortcuts that expand to the empty string.
105 * This is likely a mistake too */
106 if (!*path && *pattern.data)
107 {
108 strfcpy (err->data, _("mailbox shortcut expanded to empty regexp"), err->dsize);
109 goto error;
110 }
111
112 FREE (&pattern.data);
113 memset (&pattern, 0, sizeof (pattern));
114 pattern.data = safe_strdup (path);
115 }
116#ifdef USE_COMPRESSED
117 else if (data & (MUTT_APPENDHOOK | MUTT_OPENHOOK | MUTT_CLOSEHOOK)) {
118 if (mutt_comp_valid_command (command.data) == 0) {
119 strfcpy (err->data, _("badly formatted command string"), err->dsize);
120 return -1;
121 }
122 }
123#endif
124 else if (DefaultHook && !(data & (MUTT_CHARSETHOOK | MUTT_ICONVHOOK | MUTT_ACCOUNTHOOK))
125 && (!WithCrypto || !(data & MUTT_CRYPTHOOK))
126 )
127 {
128 char tmp[HUGE_STRING];
129
130 /* At this stage remain only message-hooks, reply-hooks, send-hooks,
131 * send2-hooks, save-hooks, and fcc-hooks: All those allowing full
132 * patterns. If given a simple regexp, we expand $default_hook.
133 */
134 strfcpy (tmp, pattern.data, sizeof (tmp));
135 mutt_check_simple (tmp, sizeof (tmp), DefaultHook);
136 FREE (&pattern.data);
137 memset (&pattern, 0, sizeof (pattern));
138 pattern.data = safe_strdup (tmp);
139 }
140
141 if (data & (MUTT_MBOXHOOK | MUTT_SAVEHOOK | MUTT_FCCHOOK))
142 {
143 strfcpy (path, command.data, sizeof (path));
144 mutt_expand_path (path, sizeof (path));
145 FREE (&command.data);
146 memset (&command, 0, sizeof (command));
147 command.data = safe_strdup (path);
148 }
149
150 /* check to make sure that a matching hook doesn't already exist */
151 for (ptr = Hooks; ptr; ptr = ptr->next)
152 {
153 if (ptr->type == data &&
154 ptr->rx.not == not &&
155 !mutt_strcmp (pattern.data, ptr->rx.pattern))
156 {
157 if (data & (MUTT_FOLDERHOOK | MUTT_SENDHOOK | MUTT_SEND2HOOK | MUTT_MESSAGEHOOK | MUTT_ACCOUNTHOOK | MUTT_REPLYHOOK | MUTT_CRYPTHOOK))
158 {
159 /* these hooks allow multiple commands with the same
160 * pattern, so if we've already seen this pattern/command pair, just
161 * ignore it instead of creating a duplicate */
162 if (!mutt_strcmp (ptr->command, command.data))
163 {
164 FREE (&command.data);
165 FREE (&pattern.data);
166 return 0;
167 }
168 }
169 else
170 {
171 /* other hooks only allow one command per pattern, so update the
172 * entry with the new command. this currently does not change the
173 * order of execution of the hooks, which i think is desirable since
174 * a common action to perform is to change the default (.) entry
175 * based upon some other information. */
176 FREE (&ptr->command);
177 ptr->command = command.data;
178 FREE (&pattern.data);
179 return 0;
180 }
181 }
182 if (!ptr->next)
183 break;
184 }
185
186 if (data & (MUTT_SENDHOOK | MUTT_SEND2HOOK | MUTT_SAVEHOOK | MUTT_FCCHOOK | MUTT_MESSAGEHOOK | MUTT_REPLYHOOK))
187 {
188 if ((pat = mutt_pattern_comp (pattern.data,
189 (data & (MUTT_SENDHOOK | MUTT_SEND2HOOK | MUTT_FCCHOOK)) ? 0 : MUTT_FULL_MSG,
190 err)) == NULL)
191 goto error;
192 }
193 else
194 {
195 /* Hooks not allowing full patterns: Check syntax of regexp */
196 rx = safe_malloc (sizeof (regex_t));
197#ifdef MUTT_CRYPTHOOK
198 if ((rc = REGCOMP (rx, NONULL(pattern.data), ((data & (MUTT_CRYPTHOOK|MUTT_CHARSETHOOK|MUTT_ICONVHOOK)) ? REG_ICASE : 0))) != 0)
199#else
200 if ((rc = REGCOMP (rx, NONULL(pattern.data), (data & (MUTT_CHARSETHOOK|MUTT_ICONVHOOK)) ? REG_ICASE : 0)) != 0)
201#endif /* MUTT_CRYPTHOOK */
202 {
203 regerror (rc, rx, err->data, err->dsize);
204 FREE (&rx);
205 goto error;
206 }
207 }
208
209 if (ptr)
210 {
211 ptr->next = safe_calloc (1, sizeof (HOOK));
212 ptr = ptr->next;
213 }
214 else
215 Hooks = ptr = safe_calloc (1, sizeof (HOOK));
216 ptr->type = data;
217 ptr->command = command.data;
218 ptr->pattern = pat;
219 ptr->rx.pattern = pattern.data;
220 ptr->rx.rx = rx;
221 ptr->rx.not = not;
222 return 0;
223
224error:
225 FREE (&pattern.data);
226 FREE (&command.data);
227 return (-1);
228}
229
230static void delete_hook (HOOK *h)
231{
232 FREE (&h->command);
233 FREE (&h->rx.pattern);
234 if (h->rx.rx)
235 {
236 regfree (h->rx.rx);
237 }
238 mutt_pattern_free (&h->pattern);
239 FREE (&h);
240}
241
242/* Deletes all hooks of type ``type'', or all defined hooks if ``type'' is 0 */
243static void delete_hooks (int type)
244{
245 HOOK *h;
246 HOOK *prev;
247
248 while (h = Hooks, h && (type == 0 || type == h->type))
249 {
250 Hooks = h->next;
251 delete_hook (h);
252 }
253
254 prev = h; /* Unused assignment to avoid compiler warnings */
255
256 while (h)
257 {
258 if (type == h->type)
259 {
260 prev->next = h->next;
261 delete_hook (h);
262 }
263 else
264 prev = h;
265 h = prev->next;
266 }
267}
268
269int mutt_parse_unhook (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err)
270{
271 while (MoreArgs (s))
272 {
273 mutt_extract_token (buf, s, 0);
274 if (mutt_strcmp ("*", buf->data) == 0)
275 {
276 if (current_hook_type)
277 {
278 snprintf (err->data, err->dsize,
279 _("unhook: Can't do unhook * from within a hook."));
280 return -1;
281 }
282 delete_hooks (0);
283 }
284 else
285 {
286 int type = mutt_get_hook_type (buf->data);
287
288 if (!type)
289 {
290 snprintf (err->data, err->dsize,
291 _("unhook: unknown hook type: %s"), buf->data);
292 return (-1);
293 }
294 if (current_hook_type == type)
295 {
296 snprintf (err->data, err->dsize,
297 _("unhook: Can't delete a %s from within a %s."),
298 buf->data, buf->data);
299 return -1;
300 }
301 delete_hooks (type);
302 }
303 }
304 return 0;
305}
306
307void mutt_folder_hook (char *path)
308{
309 HOOK *tmp = Hooks;
310 BUFFER err, token;
311
312 current_hook_type = MUTT_FOLDERHOOK;
313
314 mutt_buffer_init (&err);
315 err.dsize = STRING;
316 err.data = safe_malloc (err.dsize);
317 mutt_buffer_init (&token);
318 for (; tmp; tmp = tmp->next)
319 {
320 if(!tmp->command)
321 continue;
322
323 if (tmp->type & MUTT_FOLDERHOOK)
324 {
325 if ((regexec (tmp->rx.rx, path, 0, NULL, 0) == 0) ^ tmp->rx.not)
326 {
327 if (mutt_parse_rc_line (tmp->command, &token, &err) == -1)
328 {
329 mutt_error ("%s", err.data);
330 FREE (&token.data);
331 mutt_sleep (1); /* pause a moment to let the user see the error */
332 current_hook_type = 0;
333 FREE (&err.data);
334
335 return;
336 }
337 }
338 }
339 }
340 FREE (&token.data);
341 FREE (&err.data);
342
343 current_hook_type = 0;
344}
345
346char *mutt_find_hook (int type, const char *pat)
347{
348 HOOK *tmp = Hooks;
349
350 for (; tmp; tmp = tmp->next)
351 if (tmp->type & type)
352 {
353 if (regexec (tmp->rx.rx, pat, 0, NULL, 0) == 0)
354 return (tmp->command);
355 }
356 return (NULL);
357}
358
359void mutt_message_hook (CONTEXT *ctx, HEADER *hdr, int type)
360{
361 BUFFER err, token;
362 HOOK *hook;
363 pattern_cache_t cache;
364
365 current_hook_type = type;
366
367 mutt_buffer_init (&err);
368 err.dsize = STRING;
369 err.data = safe_malloc (err.dsize);
370 mutt_buffer_init (&token);
371 memset (&cache, 0, sizeof (cache));
372 for (hook = Hooks; hook; hook = hook->next)
373 {
374 if(!hook->command)
375 continue;
376
377 if (hook->type & type)
378 if ((mutt_pattern_exec (hook->pattern, 0, ctx, hdr, &cache) > 0) ^ hook->rx.not)
379 {
380 if (mutt_parse_rc_line (hook->command, &token, &err) != 0)
381 {
382 FREE (&token.data);
383 mutt_error ("%s", err.data);
384 mutt_sleep (1);
385 current_hook_type = 0;
386 FREE (&err.data);
387
388 return;
389 }
390 /* Executing arbitrary commands could affect the pattern results,
391 * so the cache has to be wiped */
392 memset (&cache, 0, sizeof (cache));
393 }
394 }
395 FREE (&token.data);
396 FREE (&err.data);
397
398 current_hook_type = 0;
399}
400
401static int
402mutt_addr_hook (char *path, size_t pathlen, int type, CONTEXT *ctx, HEADER *hdr)
403{
404 HOOK *hook;
405 pattern_cache_t cache;
406
407 memset (&cache, 0, sizeof (cache));
408 /* determine if a matching hook exists */
409 for (hook = Hooks; hook; hook = hook->next)
410 {
411 if(!hook->command)
412 continue;
413
414 if (hook->type & type)
415 if ((mutt_pattern_exec (hook->pattern, 0, ctx, hdr, &cache) > 0) ^ hook->rx.not)
416 {
417 mutt_make_string (path, pathlen, hook->command, ctx, hdr);
418 return 0;
419 }
420 }
421
422 return -1;
423}
424
425void mutt_default_save (char *path, size_t pathlen, HEADER *hdr)
426{
427 *path = 0;
428 if (mutt_addr_hook (path, pathlen, MUTT_SAVEHOOK, Context, hdr) != 0)
429 {
430 char tmp[_POSIX_PATH_MAX];
431 ADDRESS *adr;
432 ENVELOPE *env = hdr->env;
433 int fromMe = mutt_addr_is_user (env->from);
434
435 if (!fromMe && env->reply_to && env->reply_to->mailbox)
436 adr = env->reply_to;
437 else if (!fromMe && env->from && env->from->mailbox)
438 adr = env->from;
439 else if (env->to && env->to->mailbox)
440 adr = env->to;
441 else if (env->cc && env->cc->mailbox)
442 adr = env->cc;
443 else
444 adr = NULL;
445 if (adr)
446 {
447 mutt_safe_path (tmp, sizeof (tmp), adr);
448 snprintf (path, pathlen, "=%s", tmp);
449 }
450 }
451}
452
453void mutt_select_fcc (char *path, size_t pathlen, HEADER *hdr)
454{
455 ADDRESS *adr;
456 char buf[_POSIX_PATH_MAX];
457 ENVELOPE *env = hdr->env;
458
459 if (mutt_addr_hook (path, pathlen, MUTT_FCCHOOK, NULL, hdr) != 0)
460 {
461 if ((option (OPTSAVENAME) || option (OPTFORCENAME)) &&
462 (env->to || env->cc || env->bcc))
463 {
464 adr = env->to ? env->to : (env->cc ? env->cc : env->bcc);
465 mutt_safe_path (buf, sizeof (buf), adr);
466 mutt_concat_path (path, NONULL(Maildir), buf, pathlen);
467 if (!option (OPTFORCENAME) && mx_access (path, W_OK) != 0)
468 strfcpy (path, NONULL (Outbox), pathlen);
469 }
470 else
471 strfcpy (path, NONULL (Outbox), pathlen);
472 }
473 mutt_pretty_mailbox (path, pathlen);
474}
475
476static char *_mutt_string_hook (const char *match, int hook)
477{
478 HOOK *tmp = Hooks;
479
480 for (; tmp; tmp = tmp->next)
481 {
482 if ((tmp->type & hook) && ((match &&
483 regexec (tmp->rx.rx, match, 0, NULL, 0) == 0) ^ tmp->rx.not))
484 return (tmp->command);
485 }
486 return (NULL);
487}
488
489static LIST *_mutt_list_hook (const char *match, int hook)
490{
491 HOOK *tmp = Hooks;
492 LIST *matches = NULL;
493
494 for (; tmp; tmp = tmp->next)
495 {
496 if ((tmp->type & hook) &&
497 ((match && regexec (tmp->rx.rx, match, 0, NULL, 0) == 0) ^ tmp->rx.not))
498 matches = mutt_add_list (matches, tmp->command);
499 }
500 return (matches);
501}
502
503char *mutt_charset_hook (const char *chs)
504{
505 return _mutt_string_hook (chs, MUTT_CHARSETHOOK);
506}
507
508char *mutt_iconv_hook (const char *chs)
509{
510 return _mutt_string_hook (chs, MUTT_ICONVHOOK);
511}
512
513LIST *mutt_crypt_hook (ADDRESS *adr)
514{
515 return _mutt_list_hook (adr->mailbox, MUTT_CRYPTHOOK);
516}
517
518#ifdef USE_SOCKET
519void mutt_account_hook (const char* url)
520{
521 /* parsing commands with URLs in an account hook can cause a recursive
522 * call. We just skip processing if this occurs. Typically such commands
523 * belong in a folder-hook -- perhaps we should warn the user. */
524 static int inhook = 0;
525
526 HOOK* hook;
527 BUFFER token;
528 BUFFER err;
529
530 if (inhook)
531 return;
532
533 mutt_buffer_init (&err);
534 err.dsize = STRING;
535 err.data = safe_malloc (err.dsize);
536 mutt_buffer_init (&token);
537
538 for (hook = Hooks; hook; hook = hook->next)
539 {
540 if (! (hook->command && (hook->type & MUTT_ACCOUNTHOOK)))
541 continue;
542
543 if ((regexec (hook->rx.rx, url, 0, NULL, 0) == 0) ^ hook->rx.not)
544 {
545 inhook = 1;
546
547 if (mutt_parse_rc_line (hook->command, &token, &err) == -1)
548 {
549 FREE (&token.data);
550 mutt_error ("%s", err.data);
551 FREE (&err.data);
552 mutt_sleep (1);
553
554 inhook = 0;
555 return;
556 }
557
558 inhook = 0;
559 }
560 }
561
562 FREE (&token.data);
563 FREE (&err.data);
564}
565#endif