mutt stable branch with some hacks
at master 565 lines 14 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; 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