/*
 * Copyright notice from original mutt:
 * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
 * Copyright (C) 1999-2000 Thomas Roessler <roessler@does-not-exist.org>
 *
 * This file is part of mutt-ng, see http://www.muttng.org/.
 * It's licensed under the GNU General Public License,
 * please see the file GPL in the top level source directory.
 */

#if HAVE_CONFIG_H
# include "config.h"
#endif

#include "mutt.h"
#include "ascii.h"
#include "enter.h"
#include "handler.h"
#include "recvattach.h"
#include "mutt_curses.h"
#include "mutt_menu.h"
#include "rfc1524.h"
#include "mime.h"
#include "attach.h"
#include "mapping.h"
#include "mx.h"
#include "copy.h"
#include "mutt_crypt.h"

#include "lib/mem.h"
#include "lib/intl.h"
#include "lib/str.h"

#include <ctype.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>

static const char *Mailbox_is_read_only = N_("Mailbox is read-only.");
static char LastSaveFolder[_POSIX_PATH_MAX] = "";

#define CHECK_READONLY if (Context->readonly) \
{\
    mutt_flushinp (); \
    mutt_error _(Mailbox_is_read_only); \
    break; \
}

#define SW              (option(OPTMBOXPANE)?SidebarWidth:0)

static struct mapping_t AttachHelp[] = {
  {N_("Exit"), OP_EXIT},
  {N_("Save"), OP_SAVE},
  {N_("Pipe"), OP_PIPE},
  {N_("Print"), OP_PRINT},
  {N_("Help"), OP_HELP},
  {NULL}
};

static int mutt_extract_path (char *filename, char *path)
{
  char *tmp = mem_malloc (sizeof (char) * _POSIX_PATH_MAX);
  char *help_ptr;

  help_ptr = tmp;

  while (*filename != '\0') {
    if (*filename == '/') {
      *help_ptr++ = *filename++;
      *help_ptr++ = '\0';
      strcat (path, tmp);
      help_ptr = tmp;
    }
    *help_ptr++ = *filename++;
  }
  mem_free (&tmp);
  return 0;
}

void mutt_update_tree (ATTACHPTR ** idx, short idxlen)
{
  char buf[STRING];
  char *s;
  int x;

  for (x = 0; x < idxlen; x++) {
    idx[x]->num = x;
    if (2 * (idx[x]->level + 2) < sizeof (buf)) {
      if (idx[x]->level) {
        s = buf + 2 * (idx[x]->level - 1);
        *s++ = (idx[x]->content->next) ? M_TREE_LTEE : M_TREE_LLCORNER;
        *s++ = M_TREE_HLINE;
        *s++ = M_TREE_RARROW;
      }
      else
        s = buf;
      *s = 0;
    }

    if (idx[x]->tree) {
      if (str_cmp (idx[x]->tree, buf) != 0)
        str_replace (&idx[x]->tree, buf);
    }
    else
      idx[x]->tree = str_dup (buf);

    if (2 * (idx[x]->level + 2) < sizeof (buf) && idx[x]->level) {
      s = buf + 2 * (idx[x]->level - 1);
      *s++ = (idx[x]->content->next) ? '\005' : '\006';
      *s++ = '\006';
    }
  }
}

ATTACHPTR **mutt_gen_attach_list (BODY * m,
                                  int parent_type,
                                  ATTACHPTR ** idx,
                                  short *idxlen,
                                  short *idxmax, int level, int compose)
{
  ATTACHPTR *new;
  int i;

  for (; m; m = m->next) {
    if (*idxlen == *idxmax) {
      mem_realloc (&idx, sizeof (ATTACHPTR *) * ((*idxmax) += 5));
      for (i = *idxlen; i < *idxmax; i++)
        idx[i] = NULL;
    }

    if (m->type == TYPEMULTIPART && m->parts
        && (compose
            || (parent_type == -1
                && ascii_strcasecmp ("alternative", m->subtype)))
        && (!(WithCrypto & APPLICATION_PGP)
            || !mutt_is_multipart_encrypted (m))
      ) {
      idx =
        mutt_gen_attach_list (m->parts, m->type, idx, idxlen, idxmax, level,
                              compose);
    }
    else {
      if (!idx[*idxlen])
        idx[*idxlen] = (ATTACHPTR *) mem_calloc (1, sizeof (ATTACHPTR));

      new = idx[(*idxlen)++];
      new->content = m;
      m->aptr = new;
      new->parent_type = parent_type;
      new->level = level;

      /* We don't support multipart messages in the compose menu yet */
      if (!compose && !m->collapsed &&
          ((m->type == TYPEMULTIPART && (!(WithCrypto & APPLICATION_PGP)
                                         || !mutt_is_multipart_encrypted (m))
           )
           || mutt_is_message_type (m->type, m->subtype))) {
        idx =
          mutt_gen_attach_list (m->parts, m->type, idx, idxlen, idxmax,
                                level + 1, compose);
      }
    }
  }

  if (level == 0)
    mutt_update_tree (idx, *idxlen);

  return (idx);
}

/* %c = character set: convert?
 * %C = character set
 * %D = deleted flag
 * %d = description
 * %e = MIME content-transfer-encoding
 * %f = filename
 * %I = content-disposition, either I (inline) or A (attachment)
 * %t = tagged flag
 * %T = tree chars
 * %m = major MIME type
 * %M = MIME subtype
 * %n = attachment number
 * %s = size
 * %u = unlink 
 */
const char *mutt_attach_fmt (char *dest,
                             size_t destlen,
                             char op,
                             const char *src,
                             const char *prefix,
                             const char *ifstring,
                             const char *elsestring,
                             unsigned long data, format_flag flags)
{
  char fmt[16];
  char tmp[SHORT_STRING];
  char charset[SHORT_STRING];
  ATTACHPTR *aptr = (ATTACHPTR *) data;
  int optional = (flags & M_FORMAT_OPTIONAL);
  size_t l;

  switch (op) {
  case 'C':
    if (!optional) {
      if (mutt_is_text_part (aptr->content) &&
          mutt_get_body_charset (charset, sizeof (charset), aptr->content))
        mutt_format_s (dest, destlen, prefix, charset);
      else
        mutt_format_s (dest, destlen, prefix, "");
    }
    else if (!mutt_is_text_part (aptr->content) ||
             !mutt_get_body_charset (charset, sizeof (charset),
                                     aptr->content))
      optional = 0;
    break;
  case 'c':
    /* XXX */
    if (!optional) {
      snprintf (fmt, sizeof (fmt), "%%%sc", prefix);
      snprintf (dest, destlen, fmt, aptr->content->type != TYPETEXT ||
                aptr->content->noconv ? 'n' : 'c');
    }
    else if (aptr->content->type != TYPETEXT || aptr->content->noconv)
      optional = 0;
    break;
  case 'd':
    if (!optional) {
      if (aptr->content->description) {
        mutt_format_s (dest, destlen, prefix, aptr->content->description);
        break;
      }
      if (mutt_is_message_type (aptr->content->type, aptr->content->subtype)
          && MsgFmt && aptr->content->hdr) {
        char s[SHORT_STRING];

        _mutt_make_string (s, sizeof (s), MsgFmt, NULL, aptr->content->hdr,
                           M_FORMAT_FORCESUBJ | M_FORMAT_MAKEPRINT |
                           M_FORMAT_ARROWCURSOR);
        if (*s) {
          mutt_format_s (dest, destlen, prefix, s);
          break;
        }
      }
      if (!aptr->content->filename) {
        mutt_format_s (dest, destlen, prefix, "<no description>");
        break;
      }
    }
    else if (aptr->content->description ||
             (mutt_is_message_type
              (aptr->content->type, aptr->content->subtype)
              && MsgFmt && aptr->content->hdr))
      break;
    /* FALLS THROUGH TO 'f' */
  case 'f':
    if (!optional) {
      if (aptr->content->filename && *aptr->content->filename == '/') {
        char path[_POSIX_PATH_MAX];

        strfcpy (path, aptr->content->filename, sizeof (path));
        mutt_pretty_mailbox (path);
        mutt_format_s (dest, destlen, prefix, path);
      }
      else
        mutt_format_s (dest, destlen, prefix,
                       NONULL (aptr->content->filename));
    }
    else if (!aptr->content->filename)
      optional = 0;
    break;
  case 'D':
    if (!optional)
      snprintf (dest, destlen, "%c", aptr->content->deleted ? 'D' : ' ');
    else if (!aptr->content->deleted)
      optional = 0;
    break;
  case 'e':
    if (!optional)
      mutt_format_s (dest, destlen, prefix,
                     ENCODING (aptr->content->encoding));
    break;
  case 'I':
    if (!optional) {
      snprintf (dest, destlen, "%c",
                (aptr->content->disposition == DISPINLINE) ? 'I' : 'A');
    }
    break;
  case 'm':
    if (!optional)
      mutt_format_s (dest, destlen, prefix, TYPE (aptr->content));
    break;
  case 'M':
    if (!optional)
      mutt_format_s (dest, destlen, prefix, aptr->content->subtype);
    else if (!aptr->content->subtype)
      optional = 0;
    break;
  case 'n':
    if (!optional) {
      snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
      snprintf (dest, destlen, fmt, aptr->num + 1);
    }
    break;
  case 'Q':
    if (optional)
      optional = aptr->content->attach_qualifies;
    else {
      snprintf (fmt, sizeof (fmt), "%%%sc", prefix);
      mutt_format_s (dest, destlen, fmt, "Q");
    }
    break;
  case 's':
    if (flags & M_FORMAT_STAT_FILE) {
      struct stat st;

      stat (aptr->content->filename, &st);
      l = st.st_size;
    }
    else
      l = aptr->content->length;

    if (!optional) {
      mutt_pretty_size (tmp, sizeof (tmp), l);
      mutt_format_s (dest, destlen, prefix, tmp);
    }
    else if (l == 0)
      optional = 0;

    break;
  case 't':
    if (!optional)
      snprintf (dest, destlen, "%c", aptr->content->tagged ? '*' : ' ');
    else if (!aptr->content->tagged)
      optional = 0;
    break;
  case 'T':
    if (!optional)
      mutt_format_s_tree (dest, destlen, prefix, NONULL (aptr->tree));
    else if (!aptr->tree)
      optional = 0;
    break;
  case 'u':
    if (!optional)
      snprintf (dest, destlen, "%c", aptr->content->unlink ? '-' : ' ');
    else if (!aptr->content->unlink)
      optional = 0;
    break;
  case 'X':
    if (optional)
      optional = (aptr->content->attach_count + aptr->content->attach_qualifies) != 0;
    else {
      snprintf (fmt, sizeof (fmt), "%%%sd", prefix);
      snprintf (dest, destlen, fmt, aptr->content->attach_count + aptr->content->attach_qualifies);
    }
    break;
  default:
    *dest = 0;
  }

  if (optional)
    mutt_FormatString (dest, destlen, ifstring, mutt_attach_fmt, data, 0);
  else if (flags & M_FORMAT_OPTIONAL)
    mutt_FormatString (dest, destlen, elsestring, mutt_attach_fmt, data, 0);
  return (src);
}

static void attach_entry (char *b, size_t blen, MUTTMENU * menu, int num)
{
  int w=(COLS-SW)>blen?blen:(COLS-SW);
  mutt_FormatString (b, w, NONULL (AttachFormat), mutt_attach_fmt,
                     (unsigned long) (((ATTACHPTR **) menu->data)[num]),
                     M_FORMAT_ARROWCURSOR);
}

int mutt_tag_attach (MUTTMENU * menu, int n, int m)
{
  BODY *cur = ((ATTACHPTR **) menu->data)[n]->content;
  int ot = cur->tagged;

  cur->tagged = (m >= 0 ? m : !cur->tagged);
  return cur->tagged - ot;
}

int mutt_is_message_type (int type, const char *subtype)
{
  if (type != TYPEMESSAGE)
    return 0;

  subtype = NONULL (subtype);
  return (ascii_strcasecmp (subtype, "rfc822") == 0
          || ascii_strcasecmp (subtype, "news") == 0);
}

static int mutt_query_save_attachment (FILE * fp, BODY * body, HEADER * hdr,
                                       char **directory)
{
  char *prompt;
  char buf[_POSIX_PATH_MAX], tfile[_POSIX_PATH_MAX];
  char path[_POSIX_PATH_MAX] = "";
  int is_message;
  int append = 0;
  int rc;
  int ret = -1;

  if (body->filename) {
    if (directory && *directory)
      mutt_concat_path (buf, *directory, mutt_basename (body->filename),
                        sizeof (buf));
    else
      strfcpy (buf, body->filename, sizeof (buf));
  }
  else if (body->hdr &&
           body->encoding != ENCBASE64 &&
           body->encoding != ENCQUOTEDPRINTABLE &&
           mutt_is_message_type (body->type, body->subtype))
    mutt_default_save (buf, sizeof (buf), body->hdr);
  else
    buf[0] = 0;

  prompt = _("Save to file ('#' for last used folder): ");
  while (prompt) {
    ret =
      mutt_get_field (prompt, buf, sizeof (buf),
                      M_FILE | M_CLEAR | M_LASTFOLDER);
    if (((ret != 0) && (ret != 2)) || (!buf[0] && ret != 2))
      return -1;

    if (ret == 2) {
      char tmpbuf[_POSIX_PATH_MAX];

      snprintf (tmpbuf, sizeof (tmpbuf), "%s%s", LastSaveFolder, buf);
      strfcpy (buf, tmpbuf, sizeof (buf));
      ret = mutt_get_field (_("Save to file: ")
                            , buf, sizeof (buf), M_FILE);
      if ((ret != 0) || (!buf[0]))
        return -1;
    }
    else {
      mutt_extract_path (buf, path);
      strfcpy (LastSaveFolder, path, sizeof (LastSaveFolder));
    }

    prompt = NULL;
    mutt_expand_path (buf, sizeof (buf));

    is_message = (fp &&
                  body->hdr &&
                  body->encoding != ENCBASE64 &&
                  body->encoding != ENCQUOTEDPRINTABLE &&
                  mutt_is_message_type (body->type, body->subtype));

    if (is_message) {
      struct stat st;

      /* check to make sure that this file is really the one the user wants */
      if ((rc = mutt_save_confirm (buf, &st)) == 1) {
        prompt = _("Save to file: ");
        continue;
      }
      else if (rc == -1)
        return -1;
      strfcpy (tfile, buf, sizeof (tfile));
    }
    else {
      if ((rc =
           mutt_check_overwrite (body->filename, buf, tfile, sizeof (tfile),
                                 &append, directory)) == -1)
        return -1;
      else if (rc == 1) {
        prompt = _("Save to file: ");
        continue;
      }
    }

    mutt_message _("Saving...");

    if (mutt_save_attachment
        (fp, body, tfile, append,
         (hdr || !is_message) ? hdr : body->hdr) == 0) {
      mutt_message _("Attachment saved.");

      return 0;
    }
    else {
      prompt = _("Save to file: ");
      continue;
    }
  }
  return 0;
}

void mutt_save_attachment_list (FILE * fp, int tag, BODY * top, HEADER * hdr,
                                MUTTMENU * menu)
{
  char buf[_POSIX_PATH_MAX], tfile[_POSIX_PATH_MAX];
  char *directory = NULL;
  int rc = 1;
  int last = menu ? menu->current : -1;
  FILE *fpout;

  buf[0] = 0;

  for (; top; top = top->next) {
    if (!tag || top->tagged) {
      if (!option (OPTATTACHSPLIT)) {
        if (!buf[0]) {
          int append = 0;

          strfcpy (buf, NONULL (top->filename), sizeof (buf));
          if (mutt_get_field (_("Save to file: "), buf, sizeof (buf),
                              M_FILE | M_CLEAR) != 0 || !buf[0])
            return;
          mutt_expand_path (buf, sizeof (buf));
          if (mutt_check_overwrite (top->filename, buf, tfile,
                                    sizeof (tfile), &append, NULL))
            return;
          rc = mutt_save_attachment (fp, top, tfile, append, hdr);
          if (rc == 0 && AttachSep && (fpout = fopen (tfile, "a")) != NULL) {
            fprintf (fpout, "%s", AttachSep);
            fclose (fpout);
          }
        }
        else {
          rc = mutt_save_attachment (fp, top, tfile, M_SAVE_APPEND, hdr);
          if (rc == 0 && AttachSep && (fpout = fopen (tfile, "a")) != NULL) {
            fprintf (fpout, "%s", AttachSep);
            fclose (fpout);
          }
        }
      }
      else {
        if (tag && menu && top->aptr) {
          menu->oldcurrent = menu->current;
          menu->current = top->aptr->num;
          menu_check_recenter (menu);
          menu->redraw |= REDRAW_MOTION;

          menu_redraw (menu);
        }
        if (mutt_query_save_attachment (fp, top, hdr, &directory) == -1)
          break;
      }
    }
    else if (top->parts)
      mutt_save_attachment_list (fp, 1, top->parts, hdr, menu);
    if (!tag)
      break;
  }

  mem_free (&directory);

  if (tag && menu) {
    menu->oldcurrent = menu->current;
    menu->current = last;
    menu_check_recenter (menu);
    menu->redraw |= REDRAW_MOTION;
  }

  if (!option (OPTATTACHSPLIT) && (rc == 0))
    mutt_message _("Attachment saved.");
}

static void
mutt_query_pipe_attachment (char *command, FILE * fp, BODY * body, int filter)
{
  char tfile[_POSIX_PATH_MAX];
  char warning[STRING + _POSIX_PATH_MAX];

  if (filter) {
    snprintf (warning, sizeof (warning),
              _("WARNING!  You are about to overwrite %s, continue?"),
              body->filename);
    if (mutt_yesorno (warning, M_NO) != M_YES) {
      CLEARLINE (LINES - 1);
      return;
    }
    mutt_mktemp (tfile);
  }
  else
    tfile[0] = 0;

  if (mutt_pipe_attachment (fp, body, command, tfile)) {
    if (filter) {
      mutt_unlink (body->filename);
      mutt_rename_file (tfile, body->filename);
      mutt_update_encoding (body);
      mutt_message _("Attachment filtered.");
    }
  }
  else {
    if (filter && tfile[0])
      mutt_unlink (tfile);
  }
}

static void pipe_attachment (FILE * fp, BODY * b, STATE * state)
{
  FILE *ifp;

  if (fp) {
    state->fpin = fp;
    mutt_decode_attachment (b, state);
    if (AttachSep)
      state_puts (AttachSep, state);
  }
  else {
    if ((ifp = fopen (b->filename, "r")) == NULL) {
      mutt_perror ("fopen");
      return;
    }
    mutt_copy_stream (ifp, state->fpout);
    fclose (ifp);
    if (AttachSep)
      state_puts (AttachSep, state);
  }
}

static void
pipe_attachment_list (char *command, FILE * fp, int tag, BODY * top,
                      int filter, STATE * state)
{
  for (; top; top = top->next) {
    if (!tag || top->tagged) {
      if (!filter && !option (OPTATTACHSPLIT))
        pipe_attachment (fp, top, state);
      else
        mutt_query_pipe_attachment (command, fp, top, filter);
    }
    else if (top->parts)
      pipe_attachment_list (command, fp, tag, top->parts, filter, state);
    if (!tag)
      break;
  }
}

void mutt_pipe_attachment_list (FILE * fp, int tag, BODY * top, int filter)
{
  STATE state;
  char buf[SHORT_STRING];
  pid_t thepid;

  if (fp)
    filter = 0;                 /* sanity check: we can't filter in the recv case yet */

  buf[0] = 0;
  memset (&state, 0, sizeof (STATE));

  if (mutt_get_field ((filter ? _("Filter through: ") : _("Pipe to: ")),
                      buf, sizeof (buf), M_CMD) != 0 || !buf[0])
    return;

  mutt_expand_path (buf, sizeof (buf));

  if (!filter && !option (OPTATTACHSPLIT)) {
    mutt_endwin (NULL);
    thepid = mutt_create_filter (buf, &state.fpout, NULL, NULL);
    pipe_attachment_list (buf, fp, tag, top, filter, &state);
    fclose (state.fpout);
    if (mutt_wait_filter (thepid) != 0 || option (OPTWAITKEY))
      mutt_any_key_to_continue (NULL);
  }
  else
    pipe_attachment_list (buf, fp, tag, top, filter, &state);
}

static int can_print (BODY * top, int tag)
{
  char type[STRING];

  for (; top; top = top->next) {
    snprintf (type, sizeof (type), "%s/%s", TYPE (top), top->subtype);
    if (!tag || top->tagged) {
      if (!rfc1524_mailcap_lookup (top, type, NULL, M_PRINT)) {
        if (ascii_strcasecmp ("text/plain", top->subtype) &&
            ascii_strcasecmp ("application/postscript", top->subtype)) {
          if (!mutt_can_decode (top)) {
            mutt_error (_("I dont know how to print %s attachments!"), type);
            return (0);
          }
        }
      }
    }
    else if (top->parts)
      return (can_print (top->parts, tag));
    if (!tag)
      break;
  }
  return (1);
}

static void print_attachment_list (FILE * fp, int tag, BODY * top,
                                   STATE * state)
{
  char type[STRING];


  for (; top; top = top->next) {
    if (!tag || top->tagged) {
      snprintf (type, sizeof (type), "%s/%s", TYPE (top), top->subtype);
      if (!option (OPTATTACHSPLIT)
          && !rfc1524_mailcap_lookup (top, type, NULL, M_PRINT)) {
        if (!ascii_strcasecmp ("text/plain", top->subtype)
            || !ascii_strcasecmp ("application/postscript", top->subtype))
          pipe_attachment (fp, top, state);
        else if (mutt_can_decode (top)) {
          /* decode and print */

          char newfile[_POSIX_PATH_MAX] = "";
          FILE *ifp;

          mutt_mktemp (newfile);
          if (mutt_decode_save_attachment (fp, top, newfile, M_PRINTING, 0) ==
              0) {
            if ((ifp = fopen (newfile, "r")) != NULL) {
              mutt_copy_stream (ifp, state->fpout);
              fclose (ifp);
              if (AttachSep)
                state_puts (AttachSep, state);
            }
          }
          mutt_unlink (newfile);
        }
      }
      else
        mutt_print_attachment (fp, top);
    }
    else if (top->parts)
      print_attachment_list (fp, tag, top->parts, state);
    if (!tag)
      return;
  }
}

void mutt_print_attachment_list (FILE * fp, int tag, BODY * top)
{
  STATE state;

  pid_t thepid;

  if (query_quadoption
      (OPT_PRINT,
       tag ? _("Print tagged attachment(s)?") : _("Print attachment?")) !=
      M_YES)
    return;

  if (!option (OPTATTACHSPLIT)) {
    if (!can_print (top, tag))
      return;
    mutt_endwin (NULL);
    memset (&state, 0, sizeof (STATE));
    thepid = mutt_create_filter (NONULL (PrintCmd), &state.fpout, NULL, NULL);
    print_attachment_list (fp, tag, top, &state);
    fclose (state.fpout);
    if (mutt_wait_filter (thepid) != 0 || option (OPTWAITKEY))
      mutt_any_key_to_continue (NULL);
  }
  else
    print_attachment_list (fp, tag, top, &state);
}

static void
mutt_update_attach_index (BODY * cur, ATTACHPTR *** idxp,
                          short *idxlen, short *idxmax, MUTTMENU * menu)
{
  ATTACHPTR **idx = *idxp;

  while (--(*idxlen) >= 0)
    idx[(*idxlen)]->content = NULL;
  *idxlen = 0;

  idx = *idxp = mutt_gen_attach_list (cur, -1, idx, idxlen, idxmax, 0, 0);

  menu->max = *idxlen;
  menu->data = *idxp;

  if (menu->current >= menu->max)
    menu->current = menu->max - 1;
  menu_check_recenter (menu);
  menu->redraw |= REDRAW_INDEX;

}


int
mutt_attach_display_loop (MUTTMENU * menu, int op, FILE * fp, HEADER * hdr,
                          BODY * cur, ATTACHPTR *** idxp, short *idxlen,
                          short *idxmax, int recv)
{
  ATTACHPTR **idx = *idxp;

#if 0
  int old_optweed = option (OPTWEED);

  set_option (OPTWEED);
#endif

  do {
    switch (op) {
    case OP_DISPLAY_HEADERS:
      toggle_option (OPTWEED);
      /* fall through */

    case OP_VIEW_ATTACH:
      op = mutt_view_attachment (fp, idx[menu->current]->content, M_REGULAR,
                                 hdr, idx, *idxlen);
      break;

    case OP_NEXT_ENTRY:
    case OP_MAIN_NEXT_UNDELETED:       /* hack */
      if (menu->current < menu->max - 1) {
        menu->current++;
        op = OP_VIEW_ATTACH;
      }
      else
        op = OP_NULL;
      break;
    case OP_PREV_ENTRY:
    case OP_MAIN_PREV_UNDELETED:       /* hack */
      if (menu->current > 0) {
        menu->current--;
        op = OP_VIEW_ATTACH;
      }
      else
        op = OP_NULL;
      break;
    case OP_EDIT_TYPE:
      /* when we edit the content-type, we should redisplay the attachment
         immediately */
      mutt_edit_content_type (hdr, idx[menu->current]->content, fp);
      if (idxmax) {
        mutt_update_attach_index (cur, idxp, idxlen, idxmax, menu);
        idx = *idxp;
      }
      op = OP_VIEW_ATTACH;
      break;
      /* functions which are passed through from the pager */
    case OP_CHECK_TRADITIONAL:
      if (!(WithCrypto & APPLICATION_PGP)
          || (hdr && hdr->security & PGP_TRADITIONAL_CHECKED)) {
        op = OP_NULL;
        break;
      }
      /* fall through */
    case OP_ATTACH_COLLAPSE:
      if (recv)
        return op;
    default:
      op = OP_NULL;
    }
  }
  while (op != OP_NULL);

#if 0
  if (option (OPTWEED) != old_optweed)
    toggle_option (OPTWEED);
#endif
  return op;
}

static void attach_collapse (BODY * b, short collapse, short init,
                             short just_one)
{
  short i;

  for (; b; b = b->next) {
    i = init || b->collapsed;
    if (i && option (OPTDIGESTCOLLAPSE) && b->type == TYPEMULTIPART
        && !ascii_strcasecmp (b->subtype, "digest"))
      attach_collapse (b->parts, 1, 1, 0);
    else if (b->type == TYPEMULTIPART
             || mutt_is_message_type (b->type, b->subtype))
      attach_collapse (b->parts, collapse, i, 0);
    b->collapsed = collapse;
    if (just_one)
      return;
  }
}

void mutt_attach_init (BODY * b)
{
  for (; b; b = b->next) {
    b->tagged = 0;
    b->collapsed = 0;
    if (b->parts)
      mutt_attach_init (b->parts);
  }
}

static const char *Function_not_permitted =
N_("Function not permitted in attach-message mode.");

#define CHECK_ATTACH if(option(OPTATTACHMSG)) \
		     {\
			mutt_flushinp (); \
			mutt_error _(Function_not_permitted); \
			break; \
		     }




void mutt_view_attachments (HEADER * hdr)
{
  int secured = 0;
  int need_secured = 0;

  char helpstr[SHORT_STRING];
  MUTTMENU *menu;
  BODY *cur = NULL;
  MESSAGE *msg;
  FILE *fp;
  ATTACHPTR **idx = NULL;
  short idxlen = 0;
  short idxmax = 0;
  int flags = 0;
  int op = OP_NULL;

  /* make sure we have parsed this message */
  mutt_parse_mime_message (Context, hdr);

  mutt_message_hook (Context, hdr, M_MESSAGEHOOK);

  if ((msg = mx_open_message (Context, hdr->msgno)) == NULL)
    return;


  if (WithCrypto && ((hdr->security & ENCRYPT) ||
                     (mutt_is_application_smime (hdr->content) &
                      SMIMEOPAQUE))) {
    need_secured = 1;

    if ((hdr->security & ENCRYPT) && !crypt_valid_passphrase (hdr->security)) {
      mx_close_message (&msg);
      return;
    }
    if ((WithCrypto & APPLICATION_SMIME)
        && (hdr->security & APPLICATION_SMIME)) {
      if (hdr->env)
        crypt_smime_getkeys (hdr->env);

      if (mutt_is_application_smime (hdr->content)) {
        secured = !crypt_smime_decrypt_mime (msg->fp, &fp,
                                             hdr->content, &cur);

        /* S/MIME nesting */
        if ((mutt_is_application_smime (cur) & SMIMEOPAQUE)) {
          BODY *_cur = cur;
          FILE *_fp = fp;

          fp = NULL;
          cur = NULL;
          secured = !crypt_smime_decrypt_mime (_fp, &fp, _cur, &cur);

          mutt_free_body (&_cur);
          safe_fclose (&_fp);
        }
      }
      else
        need_secured = 0;
    }
    if ((WithCrypto & APPLICATION_PGP) && (hdr->security & APPLICATION_PGP)) {
      if (mutt_is_multipart_encrypted (hdr->content))
        secured = !crypt_pgp_decrypt_mime (msg->fp, &fp, hdr->content, &cur);
      else
        need_secured = 0;
    }

    if (need_secured && !secured) {
      mx_close_message (&msg);
      mutt_error _("Can't decrypt encrypted message!");

      return;
    }
  }

  if (!WithCrypto || !need_secured) {
    fp = msg->fp;
    cur = hdr->content;
  }

  menu = mutt_new_menu ();
  menu->menu = MENU_ATTACH;
  menu->title = _("Attachments");
  menu->make_entry = attach_entry;
  menu->tag = mutt_tag_attach;
  menu->help =
    mutt_compile_help (helpstr, sizeof (helpstr), MENU_ATTACH, AttachHelp);

  mutt_attach_init (cur);
  attach_collapse (cur, 0, 1, 0);
  mutt_update_attach_index (cur, &idx, &idxlen, &idxmax, menu);

  FOREVER {
    if (op == OP_NULL)
      op = mutt_menuLoop (menu);
    switch (op) {
    case OP_ATTACH_VIEW_MAILCAP:
      mutt_view_attachment (fp, idx[menu->current]->content, M_MAILCAP,
                            hdr, idx, idxlen);
      menu->redraw = REDRAW_FULL;
      break;

    case OP_ATTACH_VIEW_TEXT:
      mutt_view_attachment (fp, idx[menu->current]->content, M_AS_TEXT,
                            hdr, idx, idxlen);
      menu->redraw = REDRAW_FULL;
      break;

    case OP_DISPLAY_HEADERS:
    case OP_VIEW_ATTACH:
      op =
        mutt_attach_display_loop (menu, op, fp, hdr, cur, &idx, &idxlen,
                                  &idxmax, 1);
      menu->redraw = REDRAW_FULL;
      continue;

    case OP_ATTACH_COLLAPSE:
      if (!idx[menu->current]->content->parts) {
        mutt_error _("There are no subparts to show!");

        break;
      }
      if (!idx[menu->current]->content->collapsed)
        attach_collapse (idx[menu->current]->content, 1, 0, 1);
      else
        attach_collapse (idx[menu->current]->content, 0, 1, 1);
      mutt_update_attach_index (cur, &idx, &idxlen, &idxmax, menu);
      break;

    case OP_FORGET_PASSPHRASE:
      crypt_forget_passphrase ();
      break;

    case OP_EXTRACT_KEYS:
      if ((WithCrypto & APPLICATION_PGP)) {
        crypt_pgp_extract_keys_from_attachment_list (fp, menu->tagprefix,
                                                     menu->
                                                     tagprefix ? cur :
                                                     idx[menu->current]->
                                                     content);
        menu->redraw = REDRAW_FULL;
      }
      break;

    case OP_CHECK_TRADITIONAL:
      if ((WithCrypto & APPLICATION_PGP)
          && crypt_pgp_check_traditional (fp, menu->tagprefix ? cur
                                          : idx[menu->current]->content,
                                          menu->tagprefix)) {
        hdr->security = crypt_query (cur);
        menu->redraw = REDRAW_FULL;
      }
      break;

    case OP_PRINT:
      mutt_print_attachment_list (fp, menu->tagprefix,
                                  menu->tagprefix ? cur : idx[menu->current]->
                                  content);
      break;

    case OP_PIPE:
      mutt_pipe_attachment_list (fp, menu->tagprefix,
                                 menu->tagprefix ? cur : idx[menu->current]->
                                 content, 0);
      break;

    case OP_SAVE:
      mutt_save_attachment_list (fp, menu->tagprefix,
                                 menu->tagprefix ? cur : idx[menu->current]->
                                 content, hdr, menu);

      if (!menu->tagprefix && option (OPTRESOLVE)
          && menu->current < menu->max - 1)
        menu->current++;

      menu->redraw = REDRAW_MOTION_RESYNCH | REDRAW_FULL;
      break;

    case OP_DELETE:
      CHECK_READONLY;

#ifdef USE_POP
      if (Context->magic == M_POP) {
        mutt_flushinp ();
        mutt_error _("Can't delete attachment from POP server.");

        break;
      }
#endif

#ifdef USE_NNTP
      if (Context->magic == M_NNTP) {
        mutt_flushinp ();
        mutt_error _("Can't delete attachment from newsserver.");

        break;
      }
#endif

      if (WithCrypto && (hdr->security & (~PGP_TRADITIONAL_CHECKED))) {
        mutt_message
          _
          ("Deletion of attachments from encrypted messages is unsupported.");
      }
      else {
        if (!menu->tagprefix) {
          if (idx[menu->current]->parent_type == TYPEMULTIPART) {
            idx[menu->current]->content->deleted = 1;
            if (option (OPTRESOLVE) && menu->current < menu->max - 1) {
              menu->current++;
              menu->redraw = REDRAW_MOTION_RESYNCH;
            }
            else
              menu->redraw = REDRAW_CURRENT;
          }
          else
            mutt_message
              _("Only deletion of multipart attachments is supported.");
        }
        else {
          int x;

          for (x = 0; x < menu->max; x++) {
            if (idx[x]->content->tagged) {
              if (idx[x]->parent_type == TYPEMULTIPART) {
                idx[x]->content->deleted = 1;
                menu->redraw = REDRAW_INDEX;
              }
              else
                mutt_message
                  _("Only deletion of multipart attachments is supported.");
            }
          }
        }
      }
      break;

    case OP_UNDELETE:
      CHECK_READONLY;
      if (!menu->tagprefix) {
        idx[menu->current]->content->deleted = 0;
        if (option (OPTRESOLVE) && menu->current < menu->max - 1) {
          menu->current++;
          menu->redraw = REDRAW_MOTION_RESYNCH;
        }
        else
          menu->redraw = REDRAW_CURRENT;
      }
      else {
        int x;

        for (x = 0; x < menu->max; x++) {
          if (idx[x]->content->tagged) {
            idx[x]->content->deleted = 0;
            menu->redraw = REDRAW_INDEX;
          }
        }
      }
      break;

    case OP_RESEND:
      CHECK_ATTACH;
      mutt_attach_resend (fp, hdr, idx, idxlen,
                          menu->tagprefix ? NULL : idx[menu->current]->
                          content);
      menu->redraw = REDRAW_FULL;
      break;

    case OP_BOUNCE_MESSAGE:
      CHECK_ATTACH;
      mutt_attach_bounce (fp, hdr, idx, idxlen,
                          menu->tagprefix ? NULL : idx[menu->current]->
                          content);
      menu->redraw = REDRAW_FULL;
      break;

    case OP_FORWARD_MESSAGE:
      CHECK_ATTACH;
      mutt_attach_forward (fp, hdr, idx, idxlen,
                           menu->tagprefix ? NULL : idx[menu->current]->
                           content, 0);
      menu->redraw = REDRAW_FULL;
      break;

#ifdef USE_NNTP
    case OP_FORWARD_TO_GROUP:
      CHECK_ATTACH;
      mutt_attach_forward (fp, hdr, idx, idxlen,
                           menu->tagprefix ? NULL : idx[menu->current]->
                           content, SENDNEWS);
      menu->redraw = REDRAW_FULL;
      break;

    case OP_FOLLOWUP:
      CHECK_ATTACH;

      if (!idx[menu->current]->content->hdr->env->followup_to ||
          str_casecmp (idx[menu->current]->content->hdr->env->followup_to,
                           "poster")
          || query_quadoption (OPT_FOLLOWUPTOPOSTER,
                               _("Reply by mail as poster prefers?")) !=
          M_YES) {
        mutt_attach_reply (fp, hdr, idx, idxlen,
                           menu->tagprefix ? NULL : idx[menu->current]->
                           content, SENDNEWS | SENDREPLY);
        menu->redraw = REDRAW_FULL;
        break;
      }
#endif

    case OP_REPLY:
    case OP_GROUP_REPLY:
    case OP_LIST_REPLY:

      CHECK_ATTACH;

      flags = SENDREPLY |
        (op == OP_GROUP_REPLY ? SENDGROUPREPLY : 0) |
        (op == OP_LIST_REPLY ? SENDLISTREPLY : 0);
      mutt_attach_reply (fp, hdr, idx, idxlen,
                         menu->tagprefix ? NULL : idx[menu->current]->content,
                         flags);
      menu->redraw = REDRAW_FULL;
      break;

    case OP_EDIT_TYPE:
      mutt_edit_content_type (hdr, idx[menu->current]->content, fp);
      mutt_update_attach_index (cur, &idx, &idxlen, &idxmax, menu);
      break;

    case OP_EXIT:
      mx_close_message (&msg);
      hdr->attach_del = 0;
      while (idxmax-- > 0) {
        if (!idx[idxmax])
          continue;
        if (idx[idxmax]->content && idx[idxmax]->content->deleted)
          hdr->attach_del = 1;
        if (idx[idxmax]->content)
          idx[idxmax]->content->aptr = NULL;
        mem_free (&idx[idxmax]->tree);
        mem_free (&idx[idxmax]);
      }
      if (hdr->attach_del)
        hdr->changed = 1;
      mem_free (&idx);
      idxmax = 0;

      if (WithCrypto && need_secured && secured) {
        fclose (fp);
        mutt_free_body (&cur);
      }

      mutt_menuDestroy (&menu);
      return;
    }

    op = OP_NULL;
  }

  /* not reached */
}


syntax highlighted by Code2HTML, v. 0.9.1