PageRenderTime 80ms CodeModel.GetById 31ms app.highlight 42ms RepoModel.GetById 1ms app.codeStats 0ms

/recvattach.c

https://git.sr.ht/~kevin8t8/mutt/
C | 1424 lines | 1195 code | 165 blank | 64 comment | 291 complexity | bfd5482091bc3a92de88a09f589c5e67 MD5 | raw file
Possible License(s): AGPL-1.0
   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                             void *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                     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 ||
 394          ascii_strcasecmp (subtype, "news") == 0 ||
 395          ascii_strcasecmp (subtype, "global") == 0);
 396}
 397
 398/*
 399 * This prepends "./" to attachment names that start with a special character,
 400 * to prevent mutt_expand_path() from expanding and saving the attachment
 401 * in an unexpected location.
 402 */
 403static void prepend_curdir (BUFFER *dst)
 404{
 405  BUFFER *tmp = NULL;
 406
 407  if (!dst || !mutt_buffer_len (dst))
 408    return;
 409
 410  if (!strchr ("~=+@<>!-^", *dst->data))
 411    return;
 412
 413  tmp = mutt_buffer_pool_get ();
 414  mutt_buffer_addstr (tmp, "./");
 415  mutt_buffer_addstr (tmp, mutt_b2s (dst));
 416
 417  mutt_buffer_strcpy (dst, mutt_b2s (tmp));
 418
 419  mutt_buffer_pool_release (&tmp);
 420}
 421
 422static int mutt_query_save_attachment (FILE *fp, BODY *body, HEADER *hdr, char **directory)
 423{
 424  char *prompt;
 425  BUFFER *buf = NULL, *tfile = NULL;
 426  int is_message;
 427  int append = 0;
 428  int rc = -1;
 429
 430  buf = mutt_buffer_pool_get ();
 431  tfile = mutt_buffer_pool_get ();
 432
 433  if (body->filename)
 434  {
 435    if (directory && *directory)
 436      mutt_buffer_concat_path (buf, *directory, mutt_basename (body->filename));
 437    else
 438      mutt_buffer_strcpy (buf, body->filename);
 439  }
 440  else if (body->hdr &&
 441           body->encoding != ENCBASE64 &&
 442           body->encoding != ENCQUOTEDPRINTABLE &&
 443           mutt_is_message_type(body->type, body->subtype))
 444  {
 445    mutt_default_save (buf->data, buf->dsize, body->hdr);
 446    mutt_buffer_fix_dptr (buf);
 447  }
 448
 449  prepend_curdir (buf);
 450
 451  prompt = _("Save to file: ");
 452  while (prompt)
 453  {
 454    if ((mutt_buffer_get_field (prompt, buf, MUTT_FILE | MUTT_CLEAR) != 0) ||
 455        !mutt_buffer_len (buf))
 456      goto cleanup;
 457
 458    prompt = NULL;
 459    mutt_buffer_expand_path (buf);
 460
 461    is_message = (fp &&
 462		  body->hdr &&
 463		  body->encoding != ENCBASE64 &&
 464		  body->encoding != ENCQUOTEDPRINTABLE &&
 465		  mutt_is_message_type (body->type, body->subtype));
 466
 467    if (is_message)
 468    {
 469      struct stat st;
 470
 471      /* check to make sure that this file is really the one the user wants */
 472      if ((rc = mutt_save_confirm (mutt_b2s (buf), &st)) == 1)
 473      {
 474	prompt = _("Save to file: ");
 475	continue;
 476      }
 477      else if (rc == -1)
 478	goto cleanup;
 479      mutt_buffer_strcpy (tfile, mutt_b2s (buf));
 480    }
 481    else
 482    {
 483      if ((rc = mutt_check_overwrite (body->filename, mutt_b2s (buf),
 484                                      tfile, &append, directory)) == -1)
 485	goto cleanup;
 486      else if (rc == 1)
 487      {
 488	prompt = _("Save to file: ");
 489	continue;
 490      }
 491    }
 492
 493    mutt_message _("Saving...");
 494    if (mutt_save_attachment (fp, body, mutt_b2s (tfile), append,
 495                              (hdr || !is_message) ? hdr : body->hdr) == 0)
 496    {
 497      mutt_message _("Attachment saved.");
 498      rc = 0;
 499      goto cleanup;
 500    }
 501    else
 502    {
 503      prompt = _("Save to file: ");
 504      continue;
 505    }
 506  }
 507
 508cleanup:
 509  mutt_buffer_pool_release (&buf);
 510  mutt_buffer_pool_release (&tfile);
 511  return rc;
 512}
 513
 514void mutt_save_attachment_list (ATTACH_CONTEXT *actx, FILE *fp, int tag, BODY *top, HEADER *hdr, MUTTMENU *menu)
 515{
 516  BUFFER *buf = NULL, *tfile = NULL;
 517  char *directory = NULL;
 518  int i, rc = 1;
 519  int last = menu ? menu->current : -1;
 520  FILE *fpout;
 521
 522  buf = mutt_buffer_pool_get ();
 523  tfile = mutt_buffer_pool_get ();
 524
 525  for (i = 0; !tag || i < actx->idxlen; i++)
 526  {
 527    if (tag)
 528    {
 529      fp = actx->idx[i]->fp;
 530      top = actx->idx[i]->content;
 531    }
 532    if (!tag || top->tagged)
 533    {
 534      if (!option (OPTATTACHSPLIT))
 535      {
 536	if (!mutt_buffer_len (buf))
 537	{
 538	  int append = 0;
 539
 540	  mutt_buffer_strcpy (buf, mutt_basename (NONULL (top->filename)));
 541	  prepend_curdir (buf);
 542
 543	  if ((mutt_buffer_get_field (_("Save to file: "), buf,
 544                                     MUTT_FILE | MUTT_CLEAR) != 0) ||
 545              !mutt_buffer_len (buf))
 546            goto cleanup;
 547	  mutt_buffer_expand_path (buf);
 548	  if (mutt_check_overwrite (top->filename, mutt_b2s (buf), tfile,
 549				    &append, NULL))
 550	    goto cleanup;
 551	  rc = mutt_save_attachment (fp, top, mutt_b2s (tfile), append, hdr);
 552	  if (rc == 0 &&
 553              AttachSep &&
 554              (fpout = fopen (mutt_b2s (tfile), "a")) != NULL)
 555	  {
 556	    fprintf(fpout, "%s", AttachSep);
 557	    safe_fclose (&fpout);
 558	  }
 559	}
 560	else
 561	{
 562	  rc = mutt_save_attachment (fp, top, mutt_b2s (tfile), MUTT_SAVE_APPEND, hdr);
 563	  if (rc == 0 &&
 564              AttachSep &&
 565              (fpout = fopen (mutt_b2s (tfile), "a")) != NULL)
 566	  {
 567	    fprintf(fpout, "%s", AttachSep);
 568	    safe_fclose (&fpout);
 569	  }
 570	}
 571      }
 572      else
 573      {
 574	if (tag && menu && top->aptr)
 575	{
 576	  menu->oldcurrent = menu->current;
 577	  menu->current = top->aptr->num;
 578	  menu_check_recenter (menu);
 579	  menu->redraw |= REDRAW_MOTION;
 580
 581	  menu_redraw (menu);
 582	}
 583	if (mutt_query_save_attachment (fp, top, hdr, &directory) == -1)
 584	  break;
 585      }
 586    }
 587    if (!tag)
 588      break;
 589  }
 590
 591  FREE (&directory);
 592
 593  if (tag && menu)
 594  {
 595    menu->oldcurrent = menu->current;
 596    menu->current = last;
 597    menu_check_recenter (menu);
 598    menu->redraw |= REDRAW_MOTION;
 599  }
 600
 601  if (!option (OPTATTACHSPLIT) && (rc == 0))
 602    mutt_message _("Attachment saved.");
 603
 604cleanup:
 605  mutt_buffer_pool_release (&buf);
 606  mutt_buffer_pool_release (&tfile);
 607}
 608
 609static void
 610mutt_query_pipe_attachment (const char *command, FILE *fp, BODY *body, int filter)
 611{
 612  BUFFER *tfile = NULL, *warning = NULL;
 613
 614  tfile = mutt_buffer_pool_get ();
 615  warning = mutt_buffer_pool_get ();
 616
 617  if (filter)
 618  {
 619    mutt_buffer_printf (warning,
 620	      _("WARNING!  You are about to overwrite %s, continue?"),
 621	      body->filename);
 622    if (mutt_yesorno (mutt_b2s (warning), MUTT_NO) != MUTT_YES)
 623    {
 624      mutt_window_clearline (MuttMessageWindow, 0);
 625      goto cleanup;
 626    }
 627    mutt_buffer_mktemp (tfile);
 628  }
 629
 630  if (mutt_pipe_attachment (fp, body, command, mutt_b2s (tfile)))
 631  {
 632    if (filter)
 633    {
 634      mutt_unlink (body->filename);
 635      mutt_rename_file (mutt_b2s (tfile), body->filename);
 636      mutt_update_encoding (body);
 637      mutt_message _("Attachment filtered.");
 638    }
 639  }
 640  else
 641  {
 642    if (filter && mutt_buffer_len (tfile))
 643      mutt_unlink (mutt_b2s (tfile));
 644  }
 645
 646cleanup:
 647  mutt_buffer_pool_release (&tfile);
 648  mutt_buffer_pool_release (&warning);
 649}
 650
 651static void pipe_attachment (FILE *fp, BODY *b, STATE *state)
 652{
 653  FILE *ifp;
 654
 655  if (fp)
 656  {
 657    state->fpin = fp;
 658    mutt_decode_attachment (b, state);
 659    if (AttachSep)
 660      state_puts (AttachSep, state);
 661  }
 662  else
 663  {
 664    if ((ifp = fopen (b->filename, "r")) == NULL)
 665    {
 666      mutt_perror ("fopen");
 667      return;
 668    }
 669    mutt_copy_stream (ifp, state->fpout);
 670    safe_fclose (&ifp);
 671    if (AttachSep)
 672      state_puts (AttachSep, state);
 673  }
 674}
 675
 676static void
 677pipe_attachment_list (const char *command, ATTACH_CONTEXT *actx, FILE *fp, int tag,
 678                      BODY *top, int filter, STATE *state)
 679{
 680  int i;
 681
 682  for (i = 0; !tag || i < actx->idxlen; i++)
 683  {
 684    if (tag)
 685    {
 686      fp = actx->idx[i]->fp;
 687      top = actx->idx[i]->content;
 688    }
 689    if (!tag || top->tagged)
 690    {
 691      if (!filter && !option (OPTATTACHSPLIT))
 692	pipe_attachment (fp, top, state);
 693      else
 694	mutt_query_pipe_attachment (command, fp, top, filter);
 695    }
 696    if (!tag)
 697      break;
 698  }
 699}
 700
 701void mutt_pipe_attachment_list (ATTACH_CONTEXT *actx, FILE *fp, int tag, BODY *top, int filter)
 702{
 703  STATE state;
 704  BUFFER *buf = NULL;
 705  pid_t thepid;
 706
 707  if (fp)
 708    filter = 0; /* sanity check: we can't filter in the recv case yet */
 709
 710  buf = mutt_buffer_pool_get ();
 711
 712  memset (&state, 0, sizeof (STATE));
 713  /* perform charset conversion on text attachments when piping */
 714  state.flags = MUTT_CHARCONV;
 715
 716  if (mutt_buffer_get_field ((filter ? _("Filter through: ") : _("Pipe to: ")),
 717                             buf, MUTT_CMD) != 0)
 718    goto cleanup;
 719
 720  if (!mutt_buffer_len (buf))
 721    goto cleanup;
 722
 723  mutt_buffer_expand_path (buf);
 724
 725  if (!filter && !option (OPTATTACHSPLIT))
 726  {
 727    mutt_endwin (NULL);
 728    thepid = mutt_create_filter (mutt_b2s (buf), &state.fpout, NULL, NULL);
 729    pipe_attachment_list (mutt_b2s (buf), actx, fp, tag, top, filter, &state);
 730    safe_fclose (&state.fpout);
 731    if (mutt_wait_filter (thepid) != 0 || option (OPTWAITKEY))
 732      mutt_any_key_to_continue (NULL);
 733  }
 734  else
 735    pipe_attachment_list (mutt_b2s (buf), actx, fp, tag, top, filter, &state);
 736
 737cleanup:
 738  mutt_buffer_pool_release (&buf);
 739}
 740
 741static int can_print (ATTACH_CONTEXT *actx, BODY *top, int tag)
 742{
 743  char type [STRING];
 744  int i;
 745
 746  for (i = 0; !tag || i < actx->idxlen; i++)
 747  {
 748    if (tag)
 749      top = actx->idx[i]->content;
 750    snprintf (type, sizeof (type), "%s/%s", TYPE (top), top->subtype);
 751    if (!tag || top->tagged)
 752    {
 753      if (!rfc1524_mailcap_lookup (top, type, sizeof(type), NULL, MUTT_PRINT))
 754      {
 755	if (ascii_strcasecmp ("text/plain", top->subtype) &&
 756	    ascii_strcasecmp ("application/postscript", top->subtype))
 757	{
 758	  if (!mutt_can_decode (top))
 759	  {
 760	    mutt_error (_("I don't know how to print %s attachments!"), type);
 761	    return (0);
 762	  }
 763	}
 764      }
 765    }
 766    if (!tag)
 767      break;
 768  }
 769  return (1);
 770}
 771
 772static void print_attachment_list (ATTACH_CONTEXT *actx, FILE *fp, int tag, BODY *top, STATE *state)
 773{
 774  int i;
 775  char type [STRING];
 776
 777  for (i = 0; !tag || i < actx->idxlen; i++)
 778  {
 779    if (tag)
 780    {
 781      fp = actx->idx[i]->fp;
 782      top = actx->idx[i]->content;
 783    }
 784    if (!tag || top->tagged)
 785    {
 786      snprintf (type, sizeof (type), "%s/%s", TYPE (top), top->subtype);
 787      if (!option (OPTATTACHSPLIT) &&
 788          !rfc1524_mailcap_lookup (top, type, sizeof(type), NULL, MUTT_PRINT))
 789      {
 790	if (!ascii_strcasecmp ("text/plain", top->subtype) ||
 791	    !ascii_strcasecmp ("application/postscript", top->subtype))
 792	  pipe_attachment (fp, top, state);
 793	else if (mutt_can_decode (top))
 794	{
 795	  /* decode and print */
 796
 797	  BUFFER *newfile = NULL;
 798	  FILE *ifp;
 799
 800          newfile = mutt_buffer_pool_get ();
 801	  mutt_buffer_mktemp (newfile);
 802	  if (mutt_decode_save_attachment (fp, top, mutt_b2s (newfile),
 803                                           MUTT_PRINTING, 0) == 0)
 804	  {
 805	    if ((ifp = fopen (mutt_b2s (newfile), "r")) != NULL)
 806	    {
 807	      mutt_copy_stream (ifp, state->fpout);
 808	      safe_fclose (&ifp);
 809	      if (AttachSep)
 810		state_puts (AttachSep, state);
 811	    }
 812	  }
 813	  mutt_unlink (mutt_b2s (newfile));
 814          mutt_buffer_pool_release (&newfile);
 815	}
 816      }
 817      else
 818	mutt_print_attachment (fp, top);
 819    }
 820    if (!tag)
 821      break;
 822  }
 823}
 824
 825void mutt_print_attachment_list (ATTACH_CONTEXT *actx, FILE *fp, int tag, BODY *top)
 826{
 827  STATE state;
 828
 829  pid_t thepid;
 830  if (query_quadoption (OPT_PRINT, tag ? _("Print tagged attachment(s)?") : _("Print attachment?")) != MUTT_YES)
 831    return;
 832
 833  if (!option (OPTATTACHSPLIT))
 834  {
 835    if (!can_print (actx, top, tag))
 836      return;
 837    mutt_endwin (NULL);
 838    memset (&state, 0, sizeof (STATE));
 839    thepid = mutt_create_filter (NONULL (PrintCmd), &state.fpout, NULL, NULL);
 840    print_attachment_list (actx, fp, tag, top, &state);
 841    safe_fclose (&state.fpout);
 842    if (mutt_wait_filter (thepid) != 0 || option (OPTWAITKEY))
 843      mutt_any_key_to_continue (NULL);
 844  }
 845  else
 846    print_attachment_list (actx, fp, tag, top, &state);
 847}
 848
 849static void recvattach_extract_pgp_keys (ATTACH_CONTEXT *actx, MUTTMENU *menu)
 850{
 851  int i;
 852
 853  if (!menu->tagprefix)
 854    crypt_pgp_extract_keys_from_attachment_list (CURATTACH->fp, 0, CURATTACH->content);
 855  else
 856  {
 857    for (i = 0; i < actx->idxlen; i++)
 858      if (actx->idx[i]->content->tagged)
 859        crypt_pgp_extract_keys_from_attachment_list (actx->idx[i]->fp, 0,
 860                                                     actx->idx[i]->content);
 861  }
 862}
 863
 864static int recvattach_pgp_check_traditional (ATTACH_CONTEXT *actx, MUTTMENU *menu)
 865{
 866  int i, rv = 0;
 867
 868  if (!menu->tagprefix)
 869    rv = crypt_pgp_check_traditional (CURATTACH->fp, CURATTACH->content, 1);
 870  else
 871  {
 872    for (i = 0; i < actx->idxlen; i++)
 873      if (actx->idx[i]->content->tagged)
 874        rv = rv || crypt_pgp_check_traditional (actx->idx[i]->fp, actx->idx[i]->content, 1);
 875  }
 876
 877  return rv;
 878}
 879
 880static void recvattach_edit_content_type (ATTACH_CONTEXT *actx, MUTTMENU *menu, HEADER *hdr)
 881{
 882  int i;
 883
 884  if (mutt_edit_content_type (hdr, CURATTACH->content, CURATTACH->fp) == 1)
 885  {
 886    /* The mutt_update_recvattach_menu() will overwrite any changes
 887     * made to a decrypted CURATTACH->content, so warn the user. */
 888    if (CURATTACH->decrypted)
 889    {
 890      mutt_message _("Structural changes to decrypted attachments are not supported");
 891      mutt_sleep (1);
 892    }
 893    /* Editing the content type can rewrite the body structure. */
 894    for (i = 0; i < actx->idxlen; i++)
 895      actx->idx[i]->content = NULL;
 896    mutt_actx_free_entries (actx);
 897    mutt_update_recvattach_menu (actx, menu, 1);
 898  }
 899}
 900
 901int
 902mutt_attach_display_loop (MUTTMENU *menu, int op, HEADER *hdr,
 903			  ATTACH_CONTEXT *actx, int recv)
 904{
 905  do
 906  {
 907    switch (op)
 908    {
 909      case OP_DISPLAY_HEADERS:
 910	toggle_option (OPTWEED);
 911	/* fall through */
 912
 913      case OP_VIEW_ATTACH:
 914	op = mutt_view_attachment (CURATTACH->fp, CURATTACH->content, MUTT_REGULAR,
 915				   hdr, actx);
 916	break;
 917
 918      case OP_NEXT_ENTRY:
 919      case OP_MAIN_NEXT_UNDELETED: /* hack */
 920	if (menu->current < menu->max - 1)
 921	{
 922	  menu->current++;
 923	  op = OP_VIEW_ATTACH;
 924	}
 925	else
 926	  op = OP_NULL;
 927	break;
 928      case OP_PREV_ENTRY:
 929      case OP_MAIN_PREV_UNDELETED: /* hack */
 930	if (menu->current > 0)
 931	{
 932	  menu->current--;
 933	  op = OP_VIEW_ATTACH;
 934	}
 935	else
 936	  op = OP_NULL;
 937	break;
 938      case OP_EDIT_TYPE:
 939	/* when we edit the content-type, we should redisplay the attachment
 940	   immediately */
 941        if (recv)
 942          recvattach_edit_content_type (actx, menu, hdr);
 943        else
 944          mutt_edit_content_type (hdr, CURATTACH->content, CURATTACH->fp);
 945
 946        menu->redraw |= REDRAW_INDEX;
 947        op = OP_VIEW_ATTACH;
 948	break;
 949      /* functions which are passed through from the pager */
 950      case OP_CHECK_TRADITIONAL:
 951        if (!(WithCrypto & APPLICATION_PGP) || (hdr && hdr->security & PGP_TRADITIONAL_CHECKED))
 952        {
 953          op = OP_NULL;
 954          break;
 955        }
 956        /* fall through */
 957      case OP_ATTACH_COLLAPSE:
 958        if (recv)
 959          return op;
 960        /* fall through */
 961      default:
 962	op = OP_NULL;
 963    }
 964  }
 965  while (op != OP_NULL);
 966
 967  return op;
 968}
 969
 970void mutt_generate_recvattach_list (ATTACH_CONTEXT *actx,
 971                                    HEADER *hdr,
 972                                    BODY *parts,
 973                                    FILE *fp,
 974                                    int parent_type,
 975                                    int level,
 976                                    int decrypted)
 977{
 978  ATTACHPTR *new;
 979  BODY *m;
 980  BODY *new_body = NULL;
 981  FILE *new_fp = NULL;
 982  int type, need_secured, secured;
 983
 984  for (m = parts; m; m = m->next)
 985  {
 986    need_secured = secured = 0;
 987
 988    if ((WithCrypto & APPLICATION_SMIME) &&
 989        (type = mutt_is_application_smime (m)))
 990    {
 991      need_secured = 1;
 992
 993      if (type & ENCRYPT)
 994      {
 995        if (!crypt_valid_passphrase (APPLICATION_SMIME))
 996          goto decrypt_failed;
 997
 998        if (hdr->env)
 999          crypt_smime_getkeys (hdr->env);
1000      }
1001
1002      secured = !crypt_smime_decrypt_mime (fp, &new_fp, m, &new_body);
1003      /* If the decrypt/verify-opaque doesn't generate mime output, an
1004       * empty text/plain type will still be returned by
1005       * mutt_read_mime_header().  We can't distinguish an actual part
1006       * from a failure, so only use a text/plain that results from a single
1007       * top-level part. */
1008      if (secured &&
1009          new_body->type == TYPETEXT &&
1010          !ascii_strcasecmp ("plain", new_body->subtype) &&
1011          (parts != m || m->next))
1012      {
1013        mutt_free_body (&new_body);
1014        safe_fclose (&new_fp);
1015        goto decrypt_failed;
1016      }
1017
1018      if (secured && (type & ENCRYPT))
1019        hdr->security |= SMIMEENCRYPT;
1020    }
1021
1022    if ((WithCrypto & APPLICATION_PGP) &&
1023        (mutt_is_multipart_encrypted (m) ||
1024         mutt_is_malformed_multipart_pgp_encrypted (m)))
1025    {
1026      need_secured = 1;
1027
1028      if (!crypt_valid_passphrase (APPLICATION_PGP))
1029        goto decrypt_failed;
1030
1031      secured = !crypt_pgp_decrypt_mime (fp, &new_fp, m, &new_body);
1032
1033      if (secured)
1034        hdr->security |= PGPENCRYPT;
1035    }
1036
1037    if (need_secured && secured)
1038    {
1039      mutt_actx_add_fp (actx, new_fp);
1040      mutt_actx_add_body (actx, new_body);
1041      mutt_generate_recvattach_list (actx, hdr, new_body, new_fp, parent_type, level, 1);
1042      continue;
1043    }
1044
1045decrypt_failed:
1046    /* Fall through and show the original parts if decryption fails */
1047    if (need_secured && !secured)
1048      mutt_error _("Can't decrypt encrypted message!");
1049
1050    /* Strip out the top level multipart */
1051    if (m->type == TYPEMULTIPART &&
1052        m->parts &&
1053        !need_secured &&
1054        (parent_type == -1 && ascii_strcasecmp ("alternative", m->subtype)))
1055    {
1056      mutt_generate_recvattach_list (actx, hdr, m->parts, fp, m->type, level, decrypted);
1057    }
1058    else
1059    {
1060      new = (ATTACHPTR *) safe_calloc (1, sizeof (ATTACHPTR));
1061      mutt_actx_add_attach (actx, new);
1062
1063      new->content = m;
1064      new->fp = fp;
1065      m->aptr = new;
1066      new->parent_type = parent_type;
1067      new->level = level;
1068      new->decrypted = decrypted;
1069
1070      if (m->type == TYPEMULTIPART)
1071        mutt_generate_recvattach_list (actx, hdr, m->parts, fp, m->type, level + 1, decrypted);
1072      else if (mutt_is_message_type (m->type, m->subtype))
1073      {
1074        mutt_generate_recvattach_list (actx, m->hdr, m->parts, fp, m->type, level + 1, decrypted);
1075        hdr->security |= m->hdr->security;
1076      }
1077    }
1078  }
1079}
1080
1081void mutt_attach_init (ATTACH_CONTEXT *actx)
1082{
1083  int i;
1084
1085  for (i = 0; i < actx->idxlen; i++)
1086  {
1087    actx->idx[i]->content->tagged = 0;
1088    if (option (OPTDIGESTCOLLAPSE) &&
1089        actx->idx[i]->content->type == TYPEMULTIPART &&
1090	!ascii_strcasecmp (actx->idx[i]->content->subtype, "digest"))
1091      actx->idx[i]->content->collapsed = 1;
1092    else
1093      actx->idx[i]->content->collapsed = 0;
1094  }
1095}
1096
1097static void mutt_update_recvattach_menu (ATTACH_CONTEXT *actx, MUTTMENU *menu, int init)
1098{
1099  if (init)
1100  {
1101    mutt_generate_recvattach_list (actx, actx->hdr, actx->hdr->content,
1102                                   actx->root_fp, -1, 0, 0);
1103    mutt_attach_init (actx);
1104    menu->data = actx;
1105  }
1106
1107  mutt_update_tree (actx);
1108
1109  menu->max  = actx->vcount;
1110
1111  if (menu->current >= menu->max)
1112    menu->current = menu->max - 1;
1113  menu_check_recenter (menu);
1114  menu->redraw |= REDRAW_INDEX;
1115}
1116
1117static void attach_collapse (ATTACH_CONTEXT *actx, MUTTMENU *menu)
1118{
1119  int rindex, curlevel;
1120
1121  CURATTACH->content->collapsed = !CURATTACH->content->collapsed;
1122  /* When expanding, expand all the children too */
1123  if (CURATTACH->content->collapsed)
1124    return;
1125
1126  curlevel = CURATTACH->level;
1127  rindex = actx->v2r[menu->current] + 1;
1128
1129  while ((rindex < actx->idxlen) &&
1130         (actx->idx[rindex]->level > curlevel))
1131  {
1132    if (option (OPTDIGESTCOLLAPSE) &&
1133        actx->idx[rindex]->content->type == TYPEMULTIPART &&
1134	!ascii_strcasecmp (actx->idx[rindex]->content->subtype, "digest"))
1135      actx->idx[rindex]->content->collapsed = 1;
1136    else
1137      actx->idx[rindex]->content->collapsed = 0;
1138    rindex++;
1139  }
1140}
1141
1142static const char *Function_not_permitted = N_("Function not permitted in attach-message mode.");
1143
1144#define CHECK_ATTACH                            \
1145  if (option(OPTATTACHMSG))                     \
1146  {                                             \
1147    mutt_flushinp ();                           \
1148    mutt_error _(Function_not_permitted);       \
1149    break;                                      \
1150  }
1151
1152
1153
1154
1155void mutt_view_attachments (HEADER *hdr)
1156{
1157  char helpstr[LONG_STRING];
1158  MUTTMENU *menu;
1159  BODY *cur = NULL;
1160  MESSAGE *msg;
1161  ATTACH_CONTEXT *actx;
1162  int flags = 0;
1163  int op = OP_NULL;
1164  int i;
1165
1166  /* make sure we have parsed this message */
1167  mutt_parse_mime_message (Context, hdr);
1168
1169  mutt_message_hook (Context, hdr, MUTT_MESSAGEHOOK);
1170
1171  if ((msg = mx_open_message (Context, hdr->msgno)) == NULL)
1172    return;
1173
1174  menu = mutt_new_menu (MENU_ATTACH);
1175  menu->title = _("Attachments");
1176  menu->make_entry = attach_entry;
1177  menu->tag = mutt_tag_attach;
1178  menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_ATTACH, AttachHelp);
1179  mutt_push_current_menu (menu);
1180
1181  actx = safe_calloc (sizeof(ATTACH_CONTEXT), 1);
1182  actx->hdr = hdr;
1183  actx->root_fp = msg->fp;
1184  mutt_update_recvattach_menu (actx, menu, 1);
1185
1186  FOREVER
1187  {
1188    if (op == OP_NULL)
1189      op = mutt_menuLoop (menu);
1190    switch (op)
1191    {
1192      case OP_ATTACH_VIEW_MAILCAP:
1193	mutt_view_attachment (CURATTACH->fp, CURATTACH->content, MUTT_MAILCAP,
1194			      hdr, actx);
1195	menu->redraw = REDRAW_FULL;
1196	break;
1197
1198      case OP_ATTACH_VIEW_TEXT:
1199	mutt_view_attachment (CURATTACH->fp, CURATTACH->content, MUTT_AS_TEXT,
1200			      hdr, actx);
1201	menu->redraw = REDRAW_FULL;
1202	break;
1203
1204      case OP_DISPLAY_HEADERS:
1205      case OP_VIEW_ATTACH:
1206        op = mutt_attach_display_loop (menu, op, hdr, actx, 1);
1207        menu->redraw = REDRAW_FULL;
1208        continue;
1209
1210      case OP_ATTACH_COLLAPSE:
1211        if (!CURATTACH->content->parts)
1212        {
1213	  mutt_error _("There are no subparts to show!");
1214	  break;
1215	}
1216        attach_collapse (actx, menu);
1217        mutt_update_recvattach_menu (actx, menu, 0);
1218        break;
1219
1220      case OP_FORGET_PASSPHRASE:
1221        crypt_forget_passphrase ();
1222        break;
1223
1224      case OP_EXTRACT_KEYS:
1225        if ((WithCrypto & APPLICATION_PGP))
1226        {
1227          recvattach_extract_pgp_keys (actx, menu);
1228          menu->redraw = REDRAW_FULL;
1229        }
1230        break;
1231
1232      case OP_CHECK_TRADITIONAL:
1233        if ((WithCrypto & APPLICATION_PGP) &&
1234            recvattach_pgp_check_traditional (actx, menu))
1235        {
1236	  hdr->security = crypt_query (cur);
1237	  menu->redraw = REDRAW_FULL;
1238	}
1239        break;
1240
1241      case OP_PRINT:
1242	mutt_print_attachment_list (actx, CURATTACH->fp, menu->tagprefix,
1243                                    CURATTACH->content);
1244	break;
1245
1246      case OP_PIPE:
1247	mutt_pipe_attachment_list (actx, CURATTACH->fp, menu->tagprefix,
1248                                   CURATTACH->content, 0);
1249	break;
1250
1251      case OP_SAVE:
1252	mutt_save_attachment_list (actx, CURATTACH->fp, menu->tagprefix,
1253                                   CURATTACH->content, hdr, menu);
1254
1255        if (!menu->tagprefix && option (OPTRESOLVE) && menu->current < menu->max - 1)
1256	  menu->current++;
1257
1258        menu->redraw = REDRAW_MOTION_RESYNCH | REDRAW_FULL;
1259	break;
1260
1261      case OP_DELETE:
1262	CHECK_READONLY;
1263
1264#ifdef USE_POP
1265	if (Context->magic == MUTT_POP)
1266	{
1267	  mutt_flushinp ();
1268	  mutt_error _("Can't delete attachment from POP server.");
1269	  break;
1270	}
1271#endif
1272
1273        if (WithCrypto && (hdr->security & ENCRYPT))
1274        {
1275          mutt_message _(
1276            "Deletion of attachments from encrypted messages is unsupported.");
1277          break;
1278        }
1279        if (WithCrypto && (hdr->security & (SIGN | PARTSIGN)))
1280        {
1281          mutt_message _(
1282            "Deletion of attachments from signed messages may invalidate the signature.");
1283        }
1284        if (!menu->tagprefix)
1285        {
1286          if (CURATTACH->parent_type == TYPEMULTIPART)
1287          {
1288            CURATTACH->content->deleted = 1;
1289            if (option (OPTRESOLVE) && menu->current < menu->max - 1)
1290            {
1291              menu->current++;
1292              menu->redraw = REDRAW_MOTION_RESYNCH;
1293            }
1294            else
1295              menu->redraw = REDRAW_CURRENT;
1296          }
1297          else
1298            mutt_message _(
1299              "Only deletion of multipart attachments is supported.");
1300        }
1301        else
1302        {
1303          int x;
1304
1305          for (x = 0; x < menu->max; x++)
1306          {
1307            if (actx->idx[x]->content->tagged)
1308            {
1309              if (actx->idx[x]->parent_type == TYPEMULTIPART)
1310              {
1311                actx->idx[x]->content->deleted = 1;
1312                menu->redraw = REDRAW_INDEX;
1313              }
1314              else
1315                mutt_message _(
1316                  "Only deletion of multipart attachments is supported.");
1317            }
1318          }
1319        }
1320        break;
1321
1322      case OP_UNDELETE:
1323        CHECK_READONLY;
1324        if (!menu->tagprefix)
1325        {
1326          CURATTACH->content->deleted = 0;
1327          if (option (OPTRESOLVE) && menu->current < menu->max - 1)
1328          {
1329            menu->current++;
1330            menu->redraw = REDRAW_MOTION_RESYNCH;
1331          }
1332          else
1333            menu->redraw = REDRAW_CURRENT;
1334        }
1335        else
1336        {
1337          int x;
1338
1339          for (x = 0; x < menu->max; x++)
1340          {
1341            if (actx->idx[x]->content->tagged)
1342            {
1343              actx->idx[x]->content->deleted = 0;
1344              menu->redraw = REDRAW_INDEX;
1345            }
1346          }
1347        }
1348        break;
1349
1350      case OP_RESEND:
1351        CHECK_ATTACH;
1352        mutt_attach_resend (CURATTACH->fp, hdr, actx,
1353                            menu->tagprefix ? NULL : CURATTACH->content);
1354        menu->redraw = REDRAW_FULL;
1355      	break;
1356
1357      case OP_BOUNCE_MESSAGE:
1358        CHECK_ATTACH;
1359        mutt_attach_bounce (CURATTACH->fp, hdr, actx,
1360                            menu->tagprefix ? NULL : CURATTACH->content);
1361        menu->redraw = REDRAW_FULL;
1362      	break;
1363
1364      case OP_FORWARD_MESSAGE:
1365        CHECK_ATTACH;
1366        mutt_attach_forward (CURATTACH->fp, hdr, actx,
1367			     menu->tagprefix ? NULL : CURATTACH->content);
1368        menu->redraw = REDRAW_FULL;
1369        break;
1370
1371      case OP_COMPOSE_TO_SENDER:
1372        CHECK_ATTACH;
1373        mutt_attach_mail_sender (CURATTACH->fp, hdr, actx,
1374                                 menu->tagprefix ? NULL : CURATTACH->content);
1375        menu->redraw = REDRAW_FULL;
1376        break;
1377
1378      case OP_REPLY:
1379      case OP_GROUP_REPLY:
1380      case OP_GROUP_CHAT_REPLY:
1381      case OP_LIST_REPLY:
1382
1383        CHECK_ATTACH;
1384
1385        flags = SENDREPLY | SENDBACKGROUNDEDIT |
1386	  (op == OP_GROUP_REPLY ? SENDGROUPREPLY : 0) |
1387	  (op == OP_GROUP_CHAT_REPLY ? SENDGROUPCHATREPLY : 0) |
1388	  (op == OP_LIST_REPLY ? SENDLISTREPLY : 0);
1389        mutt_attach_reply (CURATTACH->fp, hdr, actx,
1390			   menu->tagprefix ? NULL : CURATTACH->content, flags);
1391	menu->redraw = REDRAW_FULL;
1392	break;
1393
1394      case OP_EDIT_TYPE:
1395        recvattach_edit_content_type (actx, menu, hdr);
1396        menu->redraw |= REDRAW_INDEX;
1397	break;
1398
1399      case OP_EXIT:
1400	mx_close_message (Context, &msg);
1401
1402	hdr->attach_del = 0;
1403        for (i = 0; i < actx->idxlen; i++)
1404	  if (actx->idx[i]->content &&
1405              actx->idx[i]->content->deleted)
1406          {
1407	    hdr->attach_del = 1;
1408            break;
1409          }
1410	if (hdr->attach_del)
1411	  hdr->changed = 1;
1412
1413        mutt_free_attach_context (&actx);
1414
1415        mutt_pop_current_menu (menu);
1416	mutt_menuDestroy  (&menu);
1417	return;
1418    }
1419
1420    op = OP_NULL;
1421  }
1422
1423  /* not reached */
1424}