mutt stable branch with some hacks
at jcs 719 lines 18 kB view raw
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; 47static HASH *IdxFmtHooks = NULL; 48 49static int current_hook_type = 0; 50 51int 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 221cleanup: 222 mutt_buffer_pool_release (&command); 223 mutt_buffer_pool_release (&pattern); 224 return rc; 225} 226 227static 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 */ 240static 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 266static 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 279static void delete_idxfmt_hooks (void) 280{ 281 hash_destroy (&IdxFmtHooks, delete_idxfmt_hooklist); 282} 283 284int 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 375out: 376 mutt_buffer_pool_release (&name); 377 mutt_buffer_pool_release (&pattern); 378 mutt_buffer_pool_release (&fmtstring); 379 380 return rc; 381} 382 383int 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 425void 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 464char *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 477void 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 519static int 520mutt_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 543void 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 573void 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 603static 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 616static 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 630char *mutt_charset_hook (const char *chs) 631{ 632 return _mutt_string_hook (chs, MUTT_CHARSETHOOK); 633} 634 635char *mutt_iconv_hook (const char *chs) 636{ 637 return _mutt_string_hook (chs, MUTT_ICONVHOOK); 638} 639 640LIST *mutt_crypt_hook (ADDRESS *adr) 641{ 642 return _mutt_list_hook (adr->mailbox, MUTT_CRYPTHOOK); 643} 644 645#ifdef USE_SOCKET 646void 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 694const 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}