PageRenderTime 130ms CodeModel.GetById 37ms app.highlight 84ms RepoModel.GetById 0ms app.codeStats 0ms

/recvattach.c

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