mutt stable branch with some hacks
at jcs 1415 lines 37 kB view raw
1/* 2 * Copyright (C) 1996-2000,2002,2007,2010 Michael R. Elkins <me@mutt.org> 3 * Copyright (C) 1999-2006 Thomas Roessler <roessler@does-not-exist.org> 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program; if not, write to the Free Software 17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 */ 19 20#if HAVE_CONFIG_H 21# include "config.h" 22#endif 23 24#include "mutt.h" 25#include "mutt_curses.h" 26#include "mutt_menu.h" 27#include "rfc1524.h" 28#include "mime.h" 29#include "mailbox.h" 30#include "attach.h" 31#include "mapping.h" 32#include "mx.h" 33#include "mutt_crypt.h" 34 35#include <ctype.h> 36#include <stdlib.h> 37#include <unistd.h> 38#include <sys/wait.h> 39#include <sys/stat.h> 40#include <string.h> 41#include <errno.h> 42 43static void mutt_update_recvattach_menu (ATTACH_CONTEXT *actx, MUTTMENU *menu, int init); 44 45static const char *Mailbox_is_read_only = N_("Mailbox is read-only."); 46 47#define CHECK_READONLY \ 48 if (Context->readonly) \ 49 { \ 50 mutt_flushinp (); \ 51 mutt_error _(Mailbox_is_read_only); \ 52 break; \ 53 } 54 55#define CURATTACH actx->idx[actx->v2r[menu->current]] 56 57static const struct mapping_t AttachHelp[] = { 58 { N_("Exit"), OP_EXIT }, 59 { N_("Save"), OP_SAVE }, 60 { N_("Pipe"), OP_PIPE }, 61 { N_("Print"), OP_PRINT }, 62 { N_("Help"), OP_HELP }, 63 { NULL, 0 } 64}; 65 66static void mutt_update_v2r (ATTACH_CONTEXT *actx) 67{ 68 int vindex, rindex, curlevel; 69 70 vindex = rindex = 0; 71 72 while (rindex < actx->idxlen) 73 { 74 actx->v2r[vindex++] = rindex; 75 if (actx->idx[rindex]->content->collapsed) 76 { 77 curlevel = actx->idx[rindex]->level; 78 do 79 rindex++; 80 while ((rindex < actx->idxlen) && 81 (actx->idx[rindex]->level > curlevel)); 82 } 83 else 84 rindex++; 85 } 86 87 actx->vcount = vindex; 88} 89 90void mutt_update_tree (ATTACH_CONTEXT *actx) 91{ 92 char buf[STRING]; 93 char *s; 94 int rindex, vindex; 95 96 mutt_update_v2r (actx); 97 98 for (vindex = 0; vindex < actx->vcount; vindex++) 99 { 100 rindex = actx->v2r[vindex]; 101 actx->idx[rindex]->num = vindex; 102 if (2 * (actx->idx[rindex]->level + 2) < sizeof (buf)) 103 { 104 if (actx->idx[rindex]->level) 105 { 106 s = buf + 2 * (actx->idx[rindex]->level - 1); 107 *s++ = (actx->idx[rindex]->content->next) ? MUTT_TREE_LTEE : MUTT_TREE_LLCORNER; 108 *s++ = MUTT_TREE_HLINE; 109 *s++ = MUTT_TREE_RARROW; 110 } 111 else 112 s = buf; 113 *s = 0; 114 } 115 116 if (actx->idx[rindex]->tree) 117 { 118 if (mutt_strcmp (actx->idx[rindex]->tree, buf) != 0) 119 mutt_str_replace (&actx->idx[rindex]->tree, buf); 120 } 121 else 122 actx->idx[rindex]->tree = safe_strdup (buf); 123 124 if (2 * (actx->idx[rindex]->level + 2) < sizeof (buf) && actx->idx[rindex]->level) 125 { 126 s = buf + 2 * (actx->idx[rindex]->level - 1); 127 *s++ = (actx->idx[rindex]->content->next) ? '\005' : '\006'; 128 *s++ = '\006'; 129 } 130 } 131} 132 133/* %c = character set: convert? 134 * %C = character set 135 * %D = deleted flag 136 * %d = description 137 * %e = MIME content-transfer-encoding 138 * %F = filename for content-disposition header 139 * %f = filename 140 * %I = content-disposition, either I (inline) or A (attachment) 141 * %t = tagged flag 142 * %T = tree chars 143 * %m = major MIME type 144 * %M = MIME subtype 145 * %n = attachment number 146 * %s = size 147 * %u = unlink 148 */ 149const char *mutt_attach_fmt (char *dest, 150 size_t destlen, 151 size_t col, 152 int cols, 153 char op, 154 const char *src, 155 const char *prefix, 156 const char *ifstring, 157 const char *elsestring, 158 unsigned long data, 159 format_flag flags) 160{ 161 char fmt[16]; 162 char tmp[SHORT_STRING]; 163 char charset[SHORT_STRING]; 164 ATTACHPTR *aptr = (ATTACHPTR *) data; 165 int optional = (flags & MUTT_FORMAT_OPTIONAL); 166 size_t l; 167 168 switch (op) 169 { 170 case 'C': 171 if (!optional) 172 { 173 if (mutt_is_text_part (aptr->content) && 174 mutt_get_body_charset (charset, sizeof (charset), aptr->content)) 175 mutt_format_s (dest, destlen, prefix, charset); 176 else 177 mutt_format_s (dest, destlen, prefix, ""); 178 } 179 else if (!mutt_is_text_part (aptr->content) || 180 !mutt_get_body_charset (charset, sizeof (charset), aptr->content)) 181 optional = 0; 182 break; 183 case 'c': 184 /* XXX */ 185 if (!optional) 186 { 187 snprintf (fmt, sizeof (fmt), "%%%sc", prefix); 188 snprintf (dest, destlen, fmt, aptr->content->type != TYPETEXT || 189 aptr->content->noconv ? 'n' : 'c'); 190 } 191 else if (aptr->content->type != TYPETEXT || aptr->content->noconv) 192 optional = 0; 193 break; 194 case 'd': 195 if (!optional) 196 { 197 if (aptr->content->description) 198 { 199 mutt_format_s (dest, destlen, prefix, aptr->content->description); 200 break; 201 } 202 if (mutt_is_message_type(aptr->content->type, aptr->content->subtype) && 203 MsgFmt && aptr->content->hdr) 204 { 205 char s[SHORT_STRING]; 206 _mutt_make_string (s, sizeof (s), MsgFmt, NULL, aptr->content->hdr, 207 MUTT_FORMAT_FORCESUBJ | MUTT_FORMAT_ARROWCURSOR); 208 if (*s) 209 { 210 mutt_format_s (dest, destlen, prefix, s); 211 break; 212 } 213 } 214 if (!aptr->content->d_filename && !aptr->content->filename) 215 { 216 mutt_format_s (dest, destlen, prefix, "<no description>"); 217 break; 218 } 219 } 220 else if (aptr->content->description || 221 (mutt_is_message_type (aptr->content->type, aptr->content->subtype) 222 && MsgFmt && aptr->content->hdr)) 223 break; 224 /* fall through */ 225 case 'F': 226 if (!optional) 227 { 228 if (aptr->content->d_filename) 229 { 230 mutt_format_s (dest, destlen, prefix, aptr->content->d_filename); 231 break; 232 } 233 } 234 else if (!aptr->content->d_filename && !aptr->content->filename) 235 { 236 optional = 0; 237 break; 238 } 239 /* fall through */ 240 case 'f': 241 if (!optional) 242 { 243 if (aptr->content->filename && *aptr->content->filename == '/') 244 { 245 BUFFER *path; 246 247 path = mutt_buffer_pool_get (); 248 mutt_buffer_strcpy (path, aptr->content->filename); 249 mutt_buffer_pretty_mailbox (path); 250 mutt_format_s (dest, destlen, prefix, mutt_b2s (path)); 251 mutt_buffer_pool_release (&path); 252 } 253 else 254 mutt_format_s (dest, destlen, prefix, NONULL (aptr->content->filename)); 255 } 256 else if (!aptr->content->filename) 257 optional = 0; 258 break; 259 case 'D': 260 if (!optional) 261 snprintf (dest, destlen, "%c", aptr->content->deleted ? 'D' : ' '); 262 else if (!aptr->content->deleted) 263 optional = 0; 264 break; 265 case 'e': 266 if (!optional) 267 mutt_format_s (dest, destlen, prefix, 268 ENCODING (aptr->content->encoding)); 269 break; 270 case 'I': 271 if (!optional) 272 { 273 const char dispchar[] = { 'I', 'A', 'F', '-' }; 274 char ch; 275 276 if (aptr->content->disposition < sizeof(dispchar)) 277 ch = dispchar[aptr->content->disposition]; 278 else 279 { 280 dprint(1, (debugfile, "ERROR: invalid content-disposition %d\n", aptr->content->disposition)); 281 ch = '!'; 282 } 283 snprintf (dest, destlen, "%c", ch); 284 } 285 break; 286 case 'm': 287 if (!optional) 288 mutt_format_s (dest, destlen, prefix, TYPE (aptr->content)); 289 break; 290 case 'M': 291 if (!optional) 292 mutt_format_s (dest, destlen, prefix, aptr->content->subtype); 293 else if (!aptr->content->subtype) 294 optional = 0; 295 break; 296 case 'n': 297 if (!optional) 298 { 299 snprintf (fmt, sizeof (fmt), "%%%sd", prefix); 300 snprintf (dest, destlen, fmt, aptr->num + 1); 301 } 302 break; 303 case 'Q': 304 if (optional) 305 optional = aptr->content->attach_qualifies; 306 else 307 { 308 snprintf (fmt, sizeof (fmt), "%%%sc", prefix); 309 mutt_format_s (dest, destlen, fmt, "Q"); 310 } 311 break; 312 case 's': 313 if (flags & MUTT_FORMAT_STAT_FILE) 314 { 315 struct stat st; 316 stat (aptr->content->filename, &st); 317 l = st.st_size; 318 } 319 else 320 l = aptr->content->length; 321 322 if (!optional) 323 { 324 mutt_pretty_size (tmp, sizeof(tmp), l); 325 mutt_format_s (dest, destlen, prefix, tmp); 326 } 327 else if (l == 0) 328 optional = 0; 329 330 break; 331 case 't': 332 if (!optional) 333 snprintf (dest, destlen, "%c", aptr->content->tagged ? '*' : ' '); 334 else if (!aptr->content->tagged) 335 optional = 0; 336 break; 337 case 'T': 338 if (!optional) 339 mutt_format_s_tree (dest, destlen, prefix, NONULL (aptr->tree)); 340 else if (!aptr->tree) 341 optional = 0; 342 break; 343 case 'u': 344 if (!optional) 345 snprintf (dest, destlen, "%c", aptr->content->unlink ? '-' : ' '); 346 else if (!aptr->content->unlink) 347 optional = 0; 348 break; 349 case 'X': 350 if (optional) 351 optional = (aptr->content->attach_count + aptr->content->attach_qualifies) != 0; 352 else 353 { 354 snprintf (fmt, sizeof (fmt), "%%%sd", prefix); 355 snprintf (dest, destlen, fmt, aptr->content->attach_count + aptr->content->attach_qualifies); 356 } 357 break; 358 default: 359 *dest = 0; 360 } 361 362 if (optional) 363 mutt_FormatString (dest, destlen, col, cols, ifstring, mutt_attach_fmt, data, 0); 364 else if (flags & MUTT_FORMAT_OPTIONAL) 365 mutt_FormatString (dest, destlen, col, cols, elsestring, mutt_attach_fmt, data, 0); 366 return (src); 367} 368 369static void attach_entry (char *b, size_t blen, MUTTMENU *menu, int num) 370{ 371 ATTACH_CONTEXT *actx = (ATTACH_CONTEXT *)menu->data; 372 373 mutt_FormatString (b, blen, 0, MuttIndexWindow->cols, NONULL (AttachFormat), mutt_attach_fmt, 374 (unsigned long) (actx->idx[actx->v2r[num]]), MUTT_FORMAT_ARROWCURSOR); 375} 376 377int mutt_tag_attach (MUTTMENU *menu, int n, int m) 378{ 379 ATTACH_CONTEXT *actx = (ATTACH_CONTEXT *)menu->data; 380 BODY *cur = actx->idx[actx->v2r[n]]->content; 381 int ot = cur->tagged; 382 383 cur->tagged = (m >= 0 ? m : !cur->tagged); 384 return cur->tagged - ot; 385} 386 387int mutt_is_message_type (int type, const char *subtype) 388{ 389 if (type != TYPEMESSAGE) 390 return 0; 391 392 subtype = NONULL(subtype); 393 return (ascii_strcasecmp (subtype, "rfc822") == 0 || ascii_strcasecmp (subtype, "news") == 0); 394} 395 396/* 397 * This prepends "./" to attachment names that start with a special character, 398 * to prevent mutt_expand_path() from expanding and saving the attachment 399 * in an unexpected location. 400 */ 401static void prepend_curdir (BUFFER *dst) 402{ 403 BUFFER *tmp = NULL; 404 405 if (!dst || !mutt_buffer_len (dst)) 406 return; 407 408 if (!strchr ("~=+@<>!-^", *dst->data)) 409 return; 410 411 tmp = mutt_buffer_pool_get (); 412 mutt_buffer_addstr (tmp, "./"); 413 mutt_buffer_addstr (tmp, mutt_b2s (dst)); 414 415 mutt_buffer_strcpy (dst, mutt_b2s (tmp)); 416 417 mutt_buffer_pool_release (&tmp); 418} 419 420static int mutt_query_save_attachment (FILE *fp, BODY *body, HEADER *hdr, char **directory) 421{ 422 char *prompt; 423 BUFFER *buf = NULL, *tfile = NULL; 424 int is_message; 425 int append = 0; 426 int rc = -1; 427 428 buf = mutt_buffer_pool_get (); 429 tfile = mutt_buffer_pool_get (); 430 431 if (body->filename) 432 { 433 if (directory && *directory) 434 mutt_buffer_concat_path (buf, *directory, mutt_basename (body->filename)); 435 else 436 mutt_buffer_strcpy (buf, body->filename); 437 } 438 else if (body->hdr && 439 body->encoding != ENCBASE64 && 440 body->encoding != ENCQUOTEDPRINTABLE && 441 mutt_is_message_type(body->type, body->subtype)) 442 { 443 mutt_default_save (buf->data, buf->dsize, body->hdr); 444 mutt_buffer_fix_dptr (buf); 445 } 446 447 prepend_curdir (buf); 448 449 prompt = _("Save to file: "); 450 while (prompt) 451 { 452 if ((mutt_buffer_get_field (prompt, buf, MUTT_FILE | MUTT_CLEAR) != 0) || 453 !mutt_buffer_len (buf)) 454 goto cleanup; 455 456 prompt = NULL; 457 mutt_buffer_expand_path (buf); 458 459 is_message = (fp && 460 body->hdr && 461 body->encoding != ENCBASE64 && 462 body->encoding != ENCQUOTEDPRINTABLE && 463 mutt_is_message_type (body->type, body->subtype)); 464 465 if (is_message) 466 { 467 struct stat st; 468 469 /* check to make sure that this file is really the one the user wants */ 470 if ((rc = mutt_save_confirm (mutt_b2s (buf), &st)) == 1) 471 { 472 prompt = _("Save to file: "); 473 continue; 474 } 475 else if (rc == -1) 476 goto cleanup; 477 mutt_buffer_strcpy (tfile, mutt_b2s (buf)); 478 } 479 else 480 { 481 if ((rc = mutt_check_overwrite (body->filename, mutt_b2s (buf), 482 tfile, &append, directory)) == -1) 483 goto cleanup; 484 else if (rc == 1) 485 { 486 prompt = _("Save to file: "); 487 continue; 488 } 489 } 490 491 mutt_message _("Saving..."); 492 if (mutt_save_attachment (fp, body, mutt_b2s (tfile), append, 493 (hdr || !is_message) ? hdr : body->hdr) == 0) 494 { 495 mutt_message _("Attachment saved."); 496 rc = 0; 497 goto cleanup; 498 } 499 else 500 { 501 prompt = _("Save to file: "); 502 continue; 503 } 504 } 505 506cleanup: 507 mutt_buffer_pool_release (&buf); 508 mutt_buffer_pool_release (&tfile); 509 return rc; 510} 511 512void mutt_save_attachment_list (ATTACH_CONTEXT *actx, FILE *fp, int tag, BODY *top, HEADER *hdr, MUTTMENU *menu) 513{ 514 BUFFER *buf = NULL, *tfile = NULL; 515 char *directory = NULL; 516 int i, rc = 1; 517 int last = menu ? menu->current : -1; 518 FILE *fpout; 519 520 buf = mutt_buffer_pool_get (); 521 tfile = mutt_buffer_pool_get (); 522 523 for (i = 0; !tag || i < actx->idxlen; i++) 524 { 525 if (tag) 526 { 527 fp = actx->idx[i]->fp; 528 top = actx->idx[i]->content; 529 } 530 if (!tag || top->tagged) 531 { 532 if (!option (OPTATTACHSPLIT)) 533 { 534 if (!mutt_buffer_len (buf)) 535 { 536 int append = 0; 537 538 mutt_buffer_strcpy (buf, mutt_basename (NONULL (top->filename))); 539 prepend_curdir (buf); 540 541 if ((mutt_buffer_get_field (_("Save to file: "), buf, 542 MUTT_FILE | MUTT_CLEAR) != 0) || 543 !mutt_buffer_len (buf)) 544 goto cleanup; 545 mutt_buffer_expand_path (buf); 546 if (mutt_check_overwrite (top->filename, mutt_b2s (buf), tfile, 547 &append, NULL)) 548 goto cleanup; 549 rc = mutt_save_attachment (fp, top, mutt_b2s (tfile), append, hdr); 550 if (rc == 0 && 551 AttachSep && 552 (fpout = fopen (mutt_b2s (tfile), "a")) != NULL) 553 { 554 fprintf(fpout, "%s", AttachSep); 555 safe_fclose (&fpout); 556 } 557 } 558 else 559 { 560 rc = mutt_save_attachment (fp, top, mutt_b2s (tfile), MUTT_SAVE_APPEND, hdr); 561 if (rc == 0 && 562 AttachSep && 563 (fpout = fopen (mutt_b2s (tfile), "a")) != NULL) 564 { 565 fprintf(fpout, "%s", AttachSep); 566 safe_fclose (&fpout); 567 } 568 } 569 } 570 else 571 { 572 if (tag && menu && top->aptr) 573 { 574 menu->oldcurrent = menu->current; 575 menu->current = top->aptr->num; 576 menu_check_recenter (menu); 577 menu->redraw |= REDRAW_MOTION; 578 579 menu_redraw (menu); 580 } 581 if (mutt_query_save_attachment (fp, top, hdr, &directory) == -1) 582 break; 583 } 584 } 585 if (!tag) 586 break; 587 } 588 589 FREE (&directory); 590 591 if (tag && menu) 592 { 593 menu->oldcurrent = menu->current; 594 menu->current = last; 595 menu_check_recenter (menu); 596 menu->redraw |= REDRAW_MOTION; 597 } 598 599 if (!option (OPTATTACHSPLIT) && (rc == 0)) 600 mutt_message _("Attachment saved."); 601 602cleanup: 603 mutt_buffer_pool_release (&buf); 604 mutt_buffer_pool_release (&tfile); 605} 606 607static void 608mutt_query_pipe_attachment (char *command, FILE *fp, BODY *body, int filter) 609{ 610 BUFFER *tfile = NULL, *warning = NULL; 611 612 tfile = mutt_buffer_pool_get (); 613 warning = mutt_buffer_pool_get (); 614 615 if (filter) 616 { 617 mutt_buffer_printf (warning, 618 _("WARNING! You are about to overwrite %s, continue?"), 619 body->filename); 620 if (mutt_yesorno (mutt_b2s (warning), MUTT_NO) != MUTT_YES) 621 { 622 mutt_window_clearline (MuttMessageWindow, 0); 623 goto cleanup; 624 } 625 mutt_buffer_mktemp (tfile); 626 } 627 628 if (mutt_pipe_attachment (fp, body, command, mutt_b2s (tfile))) 629 { 630 if (filter) 631 { 632 mutt_unlink (body->filename); 633 mutt_rename_file (mutt_b2s (tfile), body->filename); 634 mutt_update_encoding (body); 635 mutt_message _("Attachment filtered."); 636 } 637 } 638 else 639 { 640 if (filter && mutt_buffer_len (tfile)) 641 mutt_unlink (mutt_b2s (tfile)); 642 } 643 644cleanup: 645 mutt_buffer_pool_release (&tfile); 646 mutt_buffer_pool_release (&warning); 647} 648 649static void pipe_attachment (FILE *fp, BODY *b, STATE *state) 650{ 651 FILE *ifp; 652 653 if (fp) 654 { 655 state->fpin = fp; 656 mutt_decode_attachment (b, state); 657 if (AttachSep) 658 state_puts (AttachSep, state); 659 } 660 else 661 { 662 if ((ifp = fopen (b->filename, "r")) == NULL) 663 { 664 mutt_perror ("fopen"); 665 return; 666 } 667 mutt_copy_stream (ifp, state->fpout); 668 safe_fclose (&ifp); 669 if (AttachSep) 670 state_puts (AttachSep, state); 671 } 672} 673 674static void 675pipe_attachment_list (char *command, ATTACH_CONTEXT *actx, FILE *fp, int tag, 676 BODY *top, int filter, STATE *state) 677{ 678 int i; 679 680 for (i = 0; !tag || i < actx->idxlen; i++) 681 { 682 if (tag) 683 { 684 fp = actx->idx[i]->fp; 685 top = actx->idx[i]->content; 686 } 687 if (!tag || top->tagged) 688 { 689 if (!filter && !option (OPTATTACHSPLIT)) 690 pipe_attachment (fp, top, state); 691 else 692 mutt_query_pipe_attachment (command, fp, top, filter); 693 } 694 if (!tag) 695 break; 696 } 697} 698 699void mutt_pipe_attachment_list (ATTACH_CONTEXT *actx, FILE *fp, int tag, BODY *top, int filter) 700{ 701 STATE state; 702 char buf[SHORT_STRING]; 703 pid_t thepid; 704 705 if (fp) 706 filter = 0; /* sanity check: we can't filter in the recv case yet */ 707 708 buf[0] = 0; 709 memset (&state, 0, sizeof (STATE)); 710 /* perform charset conversion on text attachments when piping */ 711 state.flags = MUTT_CHARCONV; 712 713 if (mutt_get_field ((filter ? _("Filter through: ") : _("Pipe to: ")), 714 buf, sizeof (buf), MUTT_CMD) != 0 || !buf[0]) 715 return; 716 717 mutt_expand_path (buf, sizeof (buf)); 718 719 if (!filter && !option (OPTATTACHSPLIT)) 720 { 721 mutt_endwin (NULL); 722 thepid = mutt_create_filter (buf, &state.fpout, NULL, NULL); 723 pipe_attachment_list (buf, actx, fp, tag, top, filter, &state); 724 safe_fclose (&state.fpout); 725 if (mutt_wait_filter (thepid) != 0 || option (OPTWAITKEY)) 726 mutt_any_key_to_continue (NULL); 727 } 728 else 729 pipe_attachment_list (buf, actx, fp, tag, top, filter, &state); 730} 731 732static int can_print (ATTACH_CONTEXT *actx, BODY *top, int tag) 733{ 734 char type [STRING]; 735 int i; 736 737 for (i = 0; !tag || i < actx->idxlen; i++) 738 { 739 if (tag) 740 top = actx->idx[i]->content; 741 snprintf (type, sizeof (type), "%s/%s", TYPE (top), top->subtype); 742 if (!tag || top->tagged) 743 { 744 if (!rfc1524_mailcap_lookup (top, type, sizeof(type), NULL, MUTT_PRINT)) 745 { 746 if (ascii_strcasecmp ("text/plain", top->subtype) && 747 ascii_strcasecmp ("application/postscript", top->subtype)) 748 { 749 if (!mutt_can_decode (top)) 750 { 751 mutt_error (_("I don't know how to print %s attachments!"), type); 752 return (0); 753 } 754 } 755 } 756 } 757 if (!tag) 758 break; 759 } 760 return (1); 761} 762 763static void print_attachment_list (ATTACH_CONTEXT *actx, FILE *fp, int tag, BODY *top, STATE *state) 764{ 765 int i; 766 char type [STRING]; 767 768 for (i = 0; !tag || i < actx->idxlen; i++) 769 { 770 if (tag) 771 { 772 fp = actx->idx[i]->fp; 773 top = actx->idx[i]->content; 774 } 775 if (!tag || top->tagged) 776 { 777 snprintf (type, sizeof (type), "%s/%s", TYPE (top), top->subtype); 778 if (!option (OPTATTACHSPLIT) && 779 !rfc1524_mailcap_lookup (top, type, sizeof(type), NULL, MUTT_PRINT)) 780 { 781 if (!ascii_strcasecmp ("text/plain", top->subtype) || 782 !ascii_strcasecmp ("application/postscript", top->subtype)) 783 pipe_attachment (fp, top, state); 784 else if (mutt_can_decode (top)) 785 { 786 /* decode and print */ 787 788 BUFFER *newfile = NULL; 789 FILE *ifp; 790 791 newfile = mutt_buffer_pool_get (); 792 mutt_buffer_mktemp (newfile); 793 if (mutt_decode_save_attachment (fp, top, mutt_b2s (newfile), 794 MUTT_PRINTING, 0) == 0) 795 { 796 if ((ifp = fopen (mutt_b2s (newfile), "r")) != NULL) 797 { 798 mutt_copy_stream (ifp, state->fpout); 799 safe_fclose (&ifp); 800 if (AttachSep) 801 state_puts (AttachSep, state); 802 } 803 } 804 mutt_unlink (mutt_b2s (newfile)); 805 mutt_buffer_pool_release (&newfile); 806 } 807 } 808 else 809 mutt_print_attachment (fp, top); 810 } 811 if (!tag) 812 break; 813 } 814} 815 816void mutt_print_attachment_list (ATTACH_CONTEXT *actx, FILE *fp, int tag, BODY *top) 817{ 818 STATE state; 819 820 pid_t thepid; 821 if (query_quadoption (OPT_PRINT, tag ? _("Print tagged attachment(s)?") : _("Print attachment?")) != MUTT_YES) 822 return; 823 824 if (!option (OPTATTACHSPLIT)) 825 { 826 if (!can_print (actx, top, tag)) 827 return; 828 mutt_endwin (NULL); 829 memset (&state, 0, sizeof (STATE)); 830 thepid = mutt_create_filter (NONULL (PrintCmd), &state.fpout, NULL, NULL); 831 print_attachment_list (actx, fp, tag, top, &state); 832 safe_fclose (&state.fpout); 833 if (mutt_wait_filter (thepid) != 0 || option (OPTWAITKEY)) 834 mutt_any_key_to_continue (NULL); 835 } 836 else 837 print_attachment_list (actx, fp, tag, top, &state); 838} 839 840static void recvattach_extract_pgp_keys (ATTACH_CONTEXT *actx, MUTTMENU *menu) 841{ 842 int i; 843 844 if (!menu->tagprefix) 845 crypt_pgp_extract_keys_from_attachment_list (CURATTACH->fp, 0, CURATTACH->content); 846 else 847 { 848 for (i = 0; i < actx->idxlen; i++) 849 if (actx->idx[i]->content->tagged) 850 crypt_pgp_extract_keys_from_attachment_list (actx->idx[i]->fp, 0, 851 actx->idx[i]->content); 852 } 853} 854 855static int recvattach_pgp_check_traditional (ATTACH_CONTEXT *actx, MUTTMENU *menu) 856{ 857 int i, rv = 0; 858 859 if (!menu->tagprefix) 860 rv = crypt_pgp_check_traditional (CURATTACH->fp, CURATTACH->content, 1); 861 else 862 { 863 for (i = 0; i < actx->idxlen; i++) 864 if (actx->idx[i]->content->tagged) 865 rv = rv || crypt_pgp_check_traditional (actx->idx[i]->fp, actx->idx[i]->content, 1); 866 } 867 868 return rv; 869} 870 871static void recvattach_edit_content_type (ATTACH_CONTEXT *actx, MUTTMENU *menu, HEADER *hdr) 872{ 873 int i; 874 875 if (mutt_edit_content_type (hdr, CURATTACH->content, CURATTACH->fp) == 1) 876 { 877 /* The mutt_update_recvattach_menu() will overwrite any changes 878 * made to a decrypted CURATTACH->content, so warn the user. */ 879 if (CURATTACH->decrypted) 880 { 881 mutt_message _("Structural changes to decrypted attachments are not supported"); 882 mutt_sleep (1); 883 } 884 /* Editing the content type can rewrite the body structure. */ 885 for (i = 0; i < actx->idxlen; i++) 886 actx->idx[i]->content = NULL; 887 mutt_actx_free_entries (actx); 888 mutt_update_recvattach_menu (actx, menu, 1); 889 } 890} 891 892int 893mutt_attach_display_loop (MUTTMENU *menu, int op, HEADER *hdr, 894 ATTACH_CONTEXT *actx, int recv) 895{ 896 do 897 { 898 switch (op) 899 { 900 case OP_DISPLAY_HEADERS: 901 toggle_option (OPTWEED); 902 /* fall through */ 903 904 case OP_VIEW_ATTACH: 905 op = mutt_view_attachment (CURATTACH->fp, CURATTACH->content, MUTT_REGULAR, 906 hdr, actx); 907 break; 908 909 case OP_NEXT_ENTRY: 910 case OP_MAIN_NEXT_UNDELETED: /* hack */ 911 if (menu->current < menu->max - 1) 912 { 913 menu->current++; 914 op = OP_VIEW_ATTACH; 915 } 916 else 917 op = OP_NULL; 918 break; 919 case OP_PREV_ENTRY: 920 case OP_MAIN_PREV_UNDELETED: /* hack */ 921 if (menu->current > 0) 922 { 923 menu->current--; 924 op = OP_VIEW_ATTACH; 925 } 926 else 927 op = OP_NULL; 928 break; 929 case OP_EDIT_TYPE: 930 /* when we edit the content-type, we should redisplay the attachment 931 immediately */ 932 if (recv) 933 recvattach_edit_content_type (actx, menu, hdr); 934 else 935 mutt_edit_content_type (hdr, CURATTACH->content, CURATTACH->fp); 936 937 menu->redraw |= REDRAW_INDEX; 938 op = OP_VIEW_ATTACH; 939 break; 940 /* functions which are passed through from the pager */ 941 case OP_CHECK_TRADITIONAL: 942 if (!(WithCrypto & APPLICATION_PGP) || (hdr && hdr->security & PGP_TRADITIONAL_CHECKED)) 943 { 944 op = OP_NULL; 945 break; 946 } 947 /* fall through */ 948 case OP_ATTACH_COLLAPSE: 949 if (recv) 950 return op; 951 /* fall through */ 952 default: 953 op = OP_NULL; 954 } 955 } 956 while (op != OP_NULL); 957 958 return op; 959} 960 961void mutt_generate_recvattach_list (ATTACH_CONTEXT *actx, 962 HEADER *hdr, 963 BODY *parts, 964 FILE *fp, 965 int parent_type, 966 int level, 967 int decrypted) 968{ 969 ATTACHPTR *new; 970 BODY *m; 971 BODY *new_body = NULL; 972 FILE *new_fp = NULL; 973 int type, need_secured, secured; 974 975 for (m = parts; m; m = m->next) 976 { 977 need_secured = secured = 0; 978 979 if ((WithCrypto & APPLICATION_SMIME) && 980 (type = mutt_is_application_smime (m))) 981 { 982 need_secured = 1; 983 984 if (type & ENCRYPT) 985 { 986 if (!crypt_valid_passphrase (APPLICATION_SMIME)) 987 goto decrypt_failed; 988 989 if (hdr->env) 990 crypt_smime_getkeys (hdr->env); 991 } 992 993 secured = !crypt_smime_decrypt_mime (fp, &new_fp, m, &new_body); 994 /* If the decrypt/verify-opaque doesn't generate mime output, an 995 * empty text/plain type will still be returned by 996 * mutt_read_mime_header(). We can't distinguish an actual part 997 * from a failure, so only use a text/plain that results from a single 998 * top-level part. */ 999 if (secured && 1000 new_body->type == TYPETEXT && 1001 !ascii_strcasecmp ("plain", new_body->subtype) && 1002 (parts != m || m->next)) 1003 { 1004 mutt_free_body (&new_body); 1005 safe_fclose (&new_fp); 1006 goto decrypt_failed; 1007 } 1008 1009 if (secured && (type & ENCRYPT)) 1010 hdr->security |= SMIMEENCRYPT; 1011 } 1012 1013 if ((WithCrypto & APPLICATION_PGP) && 1014 (mutt_is_multipart_encrypted (m) || 1015 mutt_is_malformed_multipart_pgp_encrypted (m))) 1016 { 1017 need_secured = 1; 1018 1019 if (!crypt_valid_passphrase (APPLICATION_PGP)) 1020 goto decrypt_failed; 1021 1022 secured = !crypt_pgp_decrypt_mime (fp, &new_fp, m, &new_body); 1023 1024 if (secured) 1025 hdr->security |= PGPENCRYPT; 1026 } 1027 1028 if (need_secured && secured) 1029 { 1030 mutt_actx_add_fp (actx, new_fp); 1031 mutt_actx_add_body (actx, new_body); 1032 mutt_generate_recvattach_list (actx, hdr, new_body, new_fp, parent_type, level, 1); 1033 continue; 1034 } 1035 1036decrypt_failed: 1037 /* Fall through and show the original parts if decryption fails */ 1038 if (need_secured && !secured) 1039 mutt_error _("Can't decrypt encrypted message!"); 1040 1041 /* Strip out the top level multipart */ 1042 if (m->type == TYPEMULTIPART && 1043 m->parts && 1044 !need_secured && 1045 (parent_type == -1 && ascii_strcasecmp ("alternative", m->subtype))) 1046 { 1047 mutt_generate_recvattach_list (actx, hdr, m->parts, fp, m->type, level, decrypted); 1048 } 1049 else 1050 { 1051 new = (ATTACHPTR *) safe_calloc (1, sizeof (ATTACHPTR)); 1052 mutt_actx_add_attach (actx, new); 1053 1054 new->content = m; 1055 new->fp = fp; 1056 m->aptr = new; 1057 new->parent_type = parent_type; 1058 new->level = level; 1059 new->decrypted = decrypted; 1060 1061 if (m->type == TYPEMULTIPART) 1062 mutt_generate_recvattach_list (actx, hdr, m->parts, fp, m->type, level + 1, decrypted); 1063 else if (mutt_is_message_type (m->type, m->subtype)) 1064 { 1065 mutt_generate_recvattach_list (actx, m->hdr, m->parts, fp, m->type, level + 1, decrypted); 1066 hdr->security |= m->hdr->security; 1067 } 1068 } 1069 } 1070} 1071 1072void mutt_attach_init (ATTACH_CONTEXT *actx) 1073{ 1074 int i; 1075 1076 for (i = 0; i < actx->idxlen; i++) 1077 { 1078 actx->idx[i]->content->tagged = 0; 1079 if (option (OPTDIGESTCOLLAPSE) && 1080 actx->idx[i]->content->type == TYPEMULTIPART && 1081 !ascii_strcasecmp (actx->idx[i]->content->subtype, "digest")) 1082 actx->idx[i]->content->collapsed = 1; 1083 else 1084 actx->idx[i]->content->collapsed = 0; 1085 } 1086} 1087 1088static void mutt_update_recvattach_menu (ATTACH_CONTEXT *actx, MUTTMENU *menu, int init) 1089{ 1090 if (init) 1091 { 1092 mutt_generate_recvattach_list (actx, actx->hdr, actx->hdr->content, 1093 actx->root_fp, -1, 0, 0); 1094 mutt_attach_init (actx); 1095 menu->data = actx; 1096 } 1097 1098 mutt_update_tree (actx); 1099 1100 menu->max = actx->vcount; 1101 1102 if (menu->current >= menu->max) 1103 menu->current = menu->max - 1; 1104 menu_check_recenter (menu); 1105 menu->redraw |= REDRAW_INDEX; 1106} 1107 1108static void attach_collapse (ATTACH_CONTEXT *actx, MUTTMENU *menu) 1109{ 1110 int rindex, curlevel; 1111 1112 CURATTACH->content->collapsed = !CURATTACH->content->collapsed; 1113 /* When expanding, expand all the children too */ 1114 if (CURATTACH->content->collapsed) 1115 return; 1116 1117 curlevel = CURATTACH->level; 1118 rindex = actx->v2r[menu->current] + 1; 1119 1120 while ((rindex < actx->idxlen) && 1121 (actx->idx[rindex]->level > curlevel)) 1122 { 1123 if (option (OPTDIGESTCOLLAPSE) && 1124 actx->idx[rindex]->content->type == TYPEMULTIPART && 1125 !ascii_strcasecmp (actx->idx[rindex]->content->subtype, "digest")) 1126 actx->idx[rindex]->content->collapsed = 1; 1127 else 1128 actx->idx[rindex]->content->collapsed = 0; 1129 rindex++; 1130 } 1131} 1132 1133static const char *Function_not_permitted = N_("Function not permitted in attach-message mode."); 1134 1135#define CHECK_ATTACH \ 1136 if (option(OPTATTACHMSG)) \ 1137 { \ 1138 mutt_flushinp (); \ 1139 mutt_error _(Function_not_permitted); \ 1140 break; \ 1141 } 1142 1143 1144 1145 1146void mutt_view_attachments (HEADER *hdr) 1147{ 1148 char helpstr[LONG_STRING]; 1149 MUTTMENU *menu; 1150 BODY *cur = NULL; 1151 MESSAGE *msg; 1152 ATTACH_CONTEXT *actx; 1153 int flags = 0; 1154 int op = OP_NULL; 1155 int i; 1156 1157 /* make sure we have parsed this message */ 1158 mutt_parse_mime_message (Context, hdr); 1159 1160 mutt_message_hook (Context, hdr, MUTT_MESSAGEHOOK); 1161 1162 if ((msg = mx_open_message (Context, hdr->msgno)) == NULL) 1163 return; 1164 1165 menu = mutt_new_menu (MENU_ATTACH); 1166 menu->title = _("Attachments"); 1167 menu->make_entry = attach_entry; 1168 menu->tag = mutt_tag_attach; 1169 menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_ATTACH, AttachHelp); 1170 mutt_push_current_menu (menu); 1171 1172 actx = safe_calloc (sizeof(ATTACH_CONTEXT), 1); 1173 actx->hdr = hdr; 1174 actx->root_fp = msg->fp; 1175 mutt_update_recvattach_menu (actx, menu, 1); 1176 1177 FOREVER 1178 { 1179 if (op == OP_NULL) 1180 op = mutt_menuLoop (menu); 1181 switch (op) 1182 { 1183 case OP_ATTACH_VIEW_MAILCAP: 1184 mutt_view_attachment (CURATTACH->fp, CURATTACH->content, MUTT_MAILCAP, 1185 hdr, actx); 1186 menu->redraw = REDRAW_FULL; 1187 break; 1188 1189 case OP_ATTACH_VIEW_TEXT: 1190 mutt_view_attachment (CURATTACH->fp, CURATTACH->content, MUTT_AS_TEXT, 1191 hdr, actx); 1192 menu->redraw = REDRAW_FULL; 1193 break; 1194 1195 case OP_DISPLAY_HEADERS: 1196 case OP_VIEW_ATTACH: 1197 op = mutt_attach_display_loop (menu, op, hdr, actx, 1); 1198 menu->redraw = REDRAW_FULL; 1199 continue; 1200 1201 case OP_ATTACH_COLLAPSE: 1202 if (!CURATTACH->content->parts) 1203 { 1204 mutt_error _("There are no subparts to show!"); 1205 break; 1206 } 1207 attach_collapse (actx, menu); 1208 mutt_update_recvattach_menu (actx, menu, 0); 1209 break; 1210 1211 case OP_FORGET_PASSPHRASE: 1212 crypt_forget_passphrase (); 1213 break; 1214 1215 case OP_EXTRACT_KEYS: 1216 if ((WithCrypto & APPLICATION_PGP)) 1217 { 1218 recvattach_extract_pgp_keys (actx, menu); 1219 menu->redraw = REDRAW_FULL; 1220 } 1221 break; 1222 1223 case OP_CHECK_TRADITIONAL: 1224 if ((WithCrypto & APPLICATION_PGP) && 1225 recvattach_pgp_check_traditional (actx, menu)) 1226 { 1227 hdr->security = crypt_query (cur); 1228 menu->redraw = REDRAW_FULL; 1229 } 1230 break; 1231 1232 case OP_PRINT: 1233 mutt_print_attachment_list (actx, CURATTACH->fp, menu->tagprefix, 1234 CURATTACH->content); 1235 break; 1236 1237 case OP_PIPE: 1238 mutt_pipe_attachment_list (actx, CURATTACH->fp, menu->tagprefix, 1239 CURATTACH->content, 0); 1240 break; 1241 1242 case OP_SAVE: 1243 mutt_save_attachment_list (actx, CURATTACH->fp, menu->tagprefix, 1244 CURATTACH->content, hdr, menu); 1245 1246 if (!menu->tagprefix && option (OPTRESOLVE) && menu->current < menu->max - 1) 1247 menu->current++; 1248 1249 menu->redraw = REDRAW_MOTION_RESYNCH | REDRAW_FULL; 1250 break; 1251 1252 case OP_DELETE: 1253 CHECK_READONLY; 1254 1255#ifdef USE_POP 1256 if (Context->magic == MUTT_POP) 1257 { 1258 mutt_flushinp (); 1259 mutt_error _("Can't delete attachment from POP server."); 1260 break; 1261 } 1262#endif 1263 1264 if (WithCrypto && (hdr->security & ENCRYPT)) 1265 { 1266 mutt_message _( 1267 "Deletion of attachments from encrypted messages is unsupported."); 1268 break; 1269 } 1270 if (WithCrypto && (hdr->security & (SIGN | PARTSIGN))) 1271 { 1272 mutt_message _( 1273 "Deletion of attachments from signed messages may invalidate the signature."); 1274 } 1275 if (!menu->tagprefix) 1276 { 1277 if (CURATTACH->parent_type == TYPEMULTIPART) 1278 { 1279 CURATTACH->content->deleted = 1; 1280 if (option (OPTRESOLVE) && menu->current < menu->max - 1) 1281 { 1282 menu->current++; 1283 menu->redraw = REDRAW_MOTION_RESYNCH; 1284 } 1285 else 1286 menu->redraw = REDRAW_CURRENT; 1287 } 1288 else 1289 mutt_message _( 1290 "Only deletion of multipart attachments is supported."); 1291 } 1292 else 1293 { 1294 int x; 1295 1296 for (x = 0; x < menu->max; x++) 1297 { 1298 if (actx->idx[x]->content->tagged) 1299 { 1300 if (actx->idx[x]->parent_type == TYPEMULTIPART) 1301 { 1302 actx->idx[x]->content->deleted = 1; 1303 menu->redraw = REDRAW_INDEX; 1304 } 1305 else 1306 mutt_message _( 1307 "Only deletion of multipart attachments is supported."); 1308 } 1309 } 1310 } 1311 break; 1312 1313 case OP_UNDELETE: 1314 CHECK_READONLY; 1315 if (!menu->tagprefix) 1316 { 1317 CURATTACH->content->deleted = 0; 1318 if (option (OPTRESOLVE) && menu->current < menu->max - 1) 1319 { 1320 menu->current++; 1321 menu->redraw = REDRAW_MOTION_RESYNCH; 1322 } 1323 else 1324 menu->redraw = REDRAW_CURRENT; 1325 } 1326 else 1327 { 1328 int x; 1329 1330 for (x = 0; x < menu->max; x++) 1331 { 1332 if (actx->idx[x]->content->tagged) 1333 { 1334 actx->idx[x]->content->deleted = 0; 1335 menu->redraw = REDRAW_INDEX; 1336 } 1337 } 1338 } 1339 break; 1340 1341 case OP_RESEND: 1342 CHECK_ATTACH; 1343 mutt_attach_resend (CURATTACH->fp, hdr, actx, 1344 menu->tagprefix ? NULL : CURATTACH->content); 1345 menu->redraw = REDRAW_FULL; 1346 break; 1347 1348 case OP_BOUNCE_MESSAGE: 1349 CHECK_ATTACH; 1350 mutt_attach_bounce (CURATTACH->fp, hdr, actx, 1351 menu->tagprefix ? NULL : CURATTACH->content); 1352 menu->redraw = REDRAW_FULL; 1353 break; 1354 1355 case OP_FORWARD_MESSAGE: 1356 CHECK_ATTACH; 1357 mutt_attach_forward (CURATTACH->fp, hdr, actx, 1358 menu->tagprefix ? NULL : CURATTACH->content); 1359 menu->redraw = REDRAW_FULL; 1360 break; 1361 1362 case OP_COMPOSE_TO_SENDER: 1363 CHECK_ATTACH; 1364 mutt_attach_mail_sender (CURATTACH->fp, hdr, actx, 1365 menu->tagprefix ? NULL : CURATTACH->content); 1366 menu->redraw = REDRAW_FULL; 1367 break; 1368 1369 case OP_REPLY: 1370 case OP_GROUP_REPLY: 1371 case OP_GROUP_CHAT_REPLY: 1372 case OP_LIST_REPLY: 1373 1374 CHECK_ATTACH; 1375 1376 flags = SENDREPLY | 1377 (op == OP_GROUP_REPLY ? SENDGROUPREPLY : 0) | 1378 (op == OP_GROUP_CHAT_REPLY ? SENDGROUPCHATREPLY : 0) | 1379 (op == OP_LIST_REPLY ? SENDLISTREPLY : 0); 1380 mutt_attach_reply (CURATTACH->fp, hdr, actx, 1381 menu->tagprefix ? NULL : CURATTACH->content, flags); 1382 menu->redraw = REDRAW_FULL; 1383 break; 1384 1385 case OP_EDIT_TYPE: 1386 recvattach_edit_content_type (actx, menu, hdr); 1387 menu->redraw |= REDRAW_INDEX; 1388 break; 1389 1390 case OP_EXIT: 1391 mx_close_message (Context, &msg); 1392 1393 hdr->attach_del = 0; 1394 for (i = 0; i < actx->idxlen; i++) 1395 if (actx->idx[i]->content && 1396 actx->idx[i]->content->deleted) 1397 { 1398 hdr->attach_del = 1; 1399 break; 1400 } 1401 if (hdr->attach_del) 1402 hdr->changed = 1; 1403 1404 mutt_free_attach_context (&actx); 1405 1406 mutt_pop_current_menu (menu); 1407 mutt_menuDestroy (&menu); 1408 return; 1409 } 1410 1411 op = OP_NULL; 1412 } 1413 1414 /* not reached */ 1415}