/*
 * ========================================================================
 * Copyright 2013-2022 Eduardo Chappa
 * Copyright 2006-2008 University of Washington
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * ========================================================================
 */

#include "../pith/headers.h"
#include "../pith/mimedesc.h"
#include "../pith/mimetype.h"
#include "../pith/state.h"
#include "../pith/conf.h"
#include "../pith/mailview.h"
#include "../pith/rfc2231.h"
#include "../pith/editorial.h"
#include "../pith/mailpart.h"
#include "../pith/mailcap.h"
#include "../pith/smime.h"


/* internal prototypes */
int         mime_known_text_subtype(char *);
ATTACH_S   *next_attachment(void);
void	    format_mime_size(char *, size_t, BODY *, int);
int	    mime_show(BODY *);


/*
 * Def's to help in sorting out multipart/alternative
 */
#define	SHOW_NONE	0
#define	SHOW_PARTS	1
#define	SHOW_ALL_EXT	2
#define	SHOW_ALL	3

/*
 * Def's to control format_mime_size output
 */
#define	FMS_NONE	0x00
#define	FMS_SPACE	0x01


/*----------------------------------------------------------------------
    Add lines to the attachments structure
    
  Args: body   -- body of the part being described
        prefix -- The prefix for numbering the parts
        num    -- The number of this specific part
        should_show -- Flag indicating which of alternate parts should be shown
	multalt     -- Flag indicating the part is one of the multipart
			alternative parts (so suppress editorial comment)

Result: The ps_global->attachments data structure is filled in. This
is called recursively to descend through all the parts of a message. 
The description strings filled in are malloced and should be freed.

  ----*/
void
describe_mime(struct mail_bodystruct *body, char *prefix, int num,
	      int should_show, int multalt, int flags)
{
#define NUMXLEN 512
    PART      *part;
    char       numx[NUMXLEN], string[800], *description;
    int        n, named = 0, can_display_ext;
    ATTACH_S  *a;

    if(!body)
      return;

    if(body->type == TYPEMULTIPART){
	int alt_to_show = 0;

        if(strucmp(body->subtype, "alternative") == 0){
	    int effort, best_effort = SHOW_NONE;

	    /*---- Figure out which alternative part to display ---*/
	    /*
	     * This is kind of complicated because some TEXT types
	     * are more displayable than others.  We don't want to
	     * commit to displaying a text-part alternative that we
	     * don't directly recognize unless that's all there is.
	     */
	    for(part=body->nested.part, n=1; part; part=part->next, n++)
	      if(flags & FM_FORCEPREFPLN
	         || (!(flags & FM_FORCENOPREFPLN)
		     && F_ON(F_PREFER_PLAIN_TEXT, ps_global)
		     && part->body.type == TYPETEXT
		     && (!part->body.subtype
			 || !strucmp(part->body.subtype, "PLAIN")))){
		if((effort = mime_show(&part->body)) != SHOW_ALL_EXT){
		  best_effort = effort;
		  alt_to_show = n;
		  break;
		}
	      }
	      else if((effort = mime_show(&part->body)) >= best_effort
		      && (part->body.type != TYPETEXT || mime_known_text_subtype(part->body.subtype))
		      && effort != SHOW_ALL_EXT){
		best_effort = effort;
		alt_to_show = n;
	      }
	      else if(part->body.type == TYPETEXT && alt_to_show == 0){
		  best_effort = effort;
		  alt_to_show = n;
	      }
	}
	else if(!strucmp(body->subtype, "digest")){
	    memset(a = next_attachment(), 0, sizeof(ATTACH_S));
	    if(*prefix){
		prefix[n = strlen(prefix) - 1] = '\0';
		a->number		       = cpystr(prefix);
		prefix[n] = '.';
	    }
	    else
	      a->number = cpystr("");

	    a->description     = cpystr("Multipart/Digest");
	    a->body	       = body;
	    a->can_display     = MCD_INTERNAL;
	    (a+1)->description = NULL;
	}
#ifdef SMIME
	else if(!strucmp(body->subtype, OUR_PKCS7_ENCLOSURE_SUBTYPE)){
	    memset(a = next_attachment(), 0, sizeof(ATTACH_S));
	    if(*prefix){
		prefix[n = strlen(prefix) - 1] = '\0';
		a->number		       = cpystr(prefix);
		prefix[n] = '.';
	    }
	    else
	      a->number = cpystr("");

	    a->description     = body->description ? cpystr(body->description)
						   : cpystr("");
	    a->body	       = body;
	    a->can_display     = MCD_INTERNAL;
	    (a+1)->description = NULL;
	}
#endif /* SMIME */
	else if(mailcap_can_display(body->type, body->subtype, body, 0)
		|| (can_display_ext 
		    = mailcap_can_display(body->type, body->subtype, body, 1))){
	    memset(a = next_attachment(), 0, sizeof(ATTACH_S));
	    if(*prefix){
		prefix[n = strlen(prefix) - 1] = '\0';
		a->number		       = cpystr(prefix);
		prefix[n] = '.';
	    }
	    else
	      a->number = cpystr("");

	    snprintf(string, sizeof(string), "%s/%s", body_type_names(body->type),
		     body->subtype);
	    string[sizeof(string)-1] = '\0';
	    a->description	   = cpystr(string);
	    a->body		   = body;
	    a->can_display	   = MCD_EXTERNAL;
	    if(can_display_ext)
	      a->can_display      |= MCD_EXT_PROMPT;
	    (a+1)->description	   = NULL;
	}

	for(part=body->nested.part, n=1; part; part=part->next, n++){
	    snprintf(numx, sizeof(numx), "%s%d.", prefix, n);
	    numx[sizeof(numx)-1] = '\0';
	    /*
	     * Last arg to describe_mime here. If we have chosen one part
	     * of a multipart/alternative to display then we suppress
	     * the editorial messages on the other parts.
	     */
	    describe_mime(&(part->body),
			  (part->body.type == TYPEMULTIPART) ? numx : prefix,
			  n, should_show && (n == alt_to_show || !alt_to_show),
			  alt_to_show != 0, flags);
	}
    }
    else{
	char tmp1[MAILTMPLEN], tmp2[MAILTMPLEN];
	size_t ll;

	a = next_attachment();
	format_mime_size(a->size, sizeof(a->size), body, FMS_SPACE);

	a->suppress_editorial = (multalt != 0);

	snprintf(tmp1, sizeof(tmp1), "%s", body->description ? body->description : "");
	tmp1[sizeof(tmp1)-1] = '\0';
	snprintf(tmp2, sizeof(tmp2), "%s", (!body->description && body->type == TYPEMESSAGE && body->encoding <= ENCBINARY && body->subtype && strucmp(body->subtype, "rfc822") == 0 && body->nested.msg->env && body->nested.msg->env->subject) ? body->nested.msg->env->subject : "");
	tmp2[sizeof(tmp2)-1] = '\0';

	description = (body->description)
		        ? (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
							  SIZEOF_20KBUF, tmp1)
			: (body->type == TYPEMESSAGE
			   && body->encoding <= ENCBINARY
			   && body->subtype
			   && strucmp(body->subtype, "rfc822") == 0
			   && body->nested.msg->env
			   && body->nested.msg->env->subject)
			   ? (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, SIZEOF_20KBUF, tmp2)
			   : (body->type == TYPEMESSAGE
			      && body->subtype
			      && !strucmp(body->subtype, "delivery-status"))
				? "Delivery Status"
				: NULL;

	description = iutf8ncpy((char *)(tmp_20k_buf+1000), description, 1000);
        snprintf(string, sizeof(string), "%s%s%s%s",
		      type_desc(body->type,body->subtype,body->parameter,
				body->disposition.type ? body->disposition.parameter : NULL, 0),
                (description && description[0]) ? ", \"" : "",
                (description && description[0]) ? description : "",
                (description && description[0]) ? "\"": "");
	string[sizeof(string)-1] = '\0';
        a->description = cpystr(string);
        a->body        = body;

	if(body->disposition.type){
	    named = strucmp(body->disposition.type, "inline");
	}
	else{
	    char *value;


	    /*
	     * This test remains for backward compatibility
	     */
	    if(body && (value = parameter_val(body->parameter, "name")) != NULL){
		named = strucmp(value, "Message Body");
		fs_give((void **) &value);
	    }
	}

	/*
	 * Make sure we have the tools available to display the
	 * type/subtype, *AND* that we can decode it if needed.
	 * Of course, if it's text, we display it anyway in the
	 * mail_view_screen so put off testing mailcap until we're
	 * explicitly asked to display that segment 'cause it could
	 * be expensive to test...
	 */
	if((body->type == TYPETEXT && !named)
	   || MIME_VCARD(body->type,body->subtype)
	   || MIME_VCALENDAR(body->type, body->subtype)){
	    a->test_deferred = 1;
	    a->can_display = MCD_INTERNAL;
	}
	else{
	    a->test_deferred = 0;
	    a->can_display = mime_can_display(body->type, body->subtype, body);
	}

	/*
	 * Deferred means we can display it
	 */
	a->shown = MIME_VCALENDAR(body->type, body->subtype)
		   || ((a->can_display & MCD_INTERNAL)
		    && !MIME_VCARD(body->type,body->subtype)
		    && (!named || multalt
			|| (body->type == TYPETEXT && num == 1
			    && !(*prefix && strcmp(prefix,"1."))))
		    && (body->type != TYPEMESSAGE
			|| (body->type == TYPEMESSAGE
			    && body->encoding <= ENCBINARY))
		    && should_show);
	ll = (strlen(prefix) + 16) * sizeof(char);
	a->number = (char *) fs_get(ll);
        snprintf(a->number, ll, "%s%d",prefix, num);
	a->number[ll-1] = '\0';
        (a+1)->description = NULL;
        if(body->type == TYPEMESSAGE && body->encoding <= ENCBASE64
	   && body->subtype && strucmp(body->subtype, "rfc822") == 0){
	    body = body->nested.msg->body;	/* NUMXLEN = sizeof(numx) */
	    snprintf(numx, sizeof(numx), "%.*s%d.", NUMXLEN-20, prefix, num);
	    numx[sizeof(numx)-1] = '\0';
	    describe_mime(body, numx, 1, should_show, 0, flags);
        }
    }
}


int
mime_known_text_subtype(char *subtype)
{
    char **p;
    static char *known_types[] = {
	"plain",
	"html",
	"enriched",
	"richtext",
	NULL
    };

    if(!(subtype && *subtype))
      return(1);

    for(p = known_types; *p; p++)
      if(!strucmp(subtype, *p))
	return(1);
    return(0);
}


/*
 * Returns attribute value or NULL.
 * Value returned needs to be freed by caller
 */
char *
parameter_val(PARAMETER *param, char *attribute)
{
    if(!(param && attribute && attribute[0]))
      return(NULL);

    return(rfc2231_get_param(param, attribute, NULL, NULL));
}


/*
 * Get sender_filename, the filename set by the sender in the attachment.
 * If a sender_filename buffer is passed in, the answer is copied to it
 * and a pointer to it is returned. If sender_filename is passed in as NULL
 * then an allocated copy of the sender filename is returned instead.
 * If ext_ptr is non-NULL then it is set to point to the extension name.
 * It is not a separate copy, it points into the string sender_filename.
 */
char *
get_filename_parameter(char *sender_filename, size_t sfsize, BODY *body, char **ext_ptr)
{
    char *p = NULL;
    char *decoded_name = NULL;
    char *filename = NULL;
    char  tmp[1000];

    if(!body)
      return(NULL);

    if(sender_filename){
	if(sfsize <= 0)
	  return(NULL);

	sender_filename[0] = '\0';
    }

    /*
     * First check for Content-Disposition's "filename" parameter and
     * if that isn't found for the deprecated Content-Type "name" parameter.
     */
    if((p = parameter_val(body->disposition.parameter, "filename"))
       || (p = parameter_val(body->parameter, "name"))){

	/*
	 * If somebody sent us and incorrectly rfc2047 encoded
	 * parameter value instead of what rfc2231 suggest we
	 * grudglingly try to fix it.
	 */
	if(p[0] == '=' && p[1] == '?')
	  decoded_name = (char *) rfc1522_decode_to_utf8((unsigned char *) tmp,
							 sizeof(tmp), p);

	if(!decoded_name)
	  decoded_name = p;

	filename = last_cmpnt(decoded_name);

	if(!filename)
	  filename = decoded_name;
    }

    if(filename){
	if(sender_filename){
	    strncpy(sender_filename, filename, sfsize-1);
	    sender_filename[sfsize-1] = '\0';
	}
	else
	  sender_filename = cpystr(filename);
    }

    if(p)
      fs_give((void **) &p);

    /* ext_ptr will end up pointing into sender_filename string */
    if(ext_ptr && sender_filename)
      mt_get_file_ext(sender_filename, ext_ptr);

    return(sender_filename);
}


/*----------------------------------------------------------------------
  Return a pointer to the next attachment struct
    
  Args: none

  ----*/
ATTACH_S *
next_attachment(void)
{
    ATTACH_S *a;
    int       n;

    for(a = ps_global->atmts; a->description; a++)
      ;

    if((n = a - ps_global->atmts) + 1 >= ps_global->atmts_allocated){
	ps_global->atmts_allocated *= 2;
	fs_resize((void **)&ps_global->atmts,
		  ps_global->atmts_allocated * sizeof(ATTACH_S));
	a = &ps_global->atmts[n];
    }

    return(a);
}



/*----------------------------------------------------------------------
   Zero out the attachments structure and free up storage
  ----*/
void
zero_atmts(ATTACH_S *atmts)
{
    ATTACH_S *a;

    for(a = atmts; a->description != NULL; a++){
	a->tmpdir      = NULL;		/* short lived variabled, cleared right after use */
	a->cid_tmpfile = NULL;		/* resetting pointer is enough */
	fs_give((void **)&(a->description));
	fs_give((void **)&(a->number));
    }

    atmts->description = NULL;
}


char *
body_type_names(int t)
{
#define TLEN 31
    static char body_type[TLEN + 1];
    char   *p;

    body_type[0] = '\0';
    strncpy(body_type,				/* copy the given type */
	    (t > -1 && t < TYPEMAX && body_types[t])
	      ? body_types[t] : "Other", TLEN);
    body_type[sizeof(body_type)-1] = '\0';

    for(p = body_type + 1; *p; p++)		/* make it presentable */
      if(isascii((unsigned char) (*p)) && isupper((unsigned char) (*p)))
	*p = tolower((unsigned char)(*p));

    return(body_type);				/* present it */
}


/*----------------------------------------------------------------------
  Mapping table use to neatly display charset parameters
 ----*/

static struct set_names {
    char *rfcname,
	 *humanname;
} charset_names[] = {
    {"US-ASCII",		"Plain Text"},
    {"ISO-8859-1",		"Latin 1 (Western Europe)"},
    {"ISO-8859-2",		"Latin 2 (Eastern Europe)"},
    {"ISO-8859-3",		"Latin 3 (Southern Europe)"},
    {"ISO-8859-4",		"Latin 4 (Northern Europe)"},
    {"ISO-8859-5",		"Latin & Cyrillic"},
    {"ISO-8859-6",		"Latin & Arabic"},
    {"ISO-8859-7",		"Latin & Greek"},
    {"ISO-8859-8",		"Latin & Hebrew"},
    {"ISO-8859-9",		"Latin 5 (Turkish)"},
    {"ISO-8859-10",		"Latin 6 (Nordic)"},
    {"ISO-8859-11",		"Latin & Thai"},
    {"ISO-8859-13",		"Latin 7 (Baltic)"},
    {"ISO-8859-14",		"Latin 8 (Celtic)"},
    {"ISO-8859-15",		"Latin 9 (Euro)"},
    {"KOI8-R",			"Latin & Russian"},
    {"KOI8-U",			"Latin & Ukrainian"},
    {"VISCII",			"Latin & Vietnamese"},
    {"GB2312",			"Latin & Simplified Chinese"},
    {"BIG5",			"Latin & Traditional Chinese"},
    {"EUC-JP",			"Latin & Japanese"},
    {"Shift-JIS",		"Latin & Japanese"},
    {"Shift_JIS",		"Latin & Japanese"},
    {"EUC-KR",			"Latin & Korean"},
    {"ISO-2022-CN",		"Latin & Chinese"},
    {"ISO-2022-JP",		"Latin & Japanese"},
    {"ISO-2022-KR",		"Latin & Korean"},
    {"UTF-7",			"7-bit encoded Unicode"},
    {"UTF-8",			"Internet-standard Unicode"},
    {"ISO-2022-JP-2",		"Multilingual"},
    {NULL,			NULL}
};


/*----------------------------------------------------------------------
  Return a nicely formatted description of the type of the part
 ----*/

char *
type_desc(int type, char *subtype, PARAMETER *params, PARAMETER *disp_params, int full)
{
    static char  type_d[200];
    int		 i;
    char	*p, *parmval;

    p = type_d;
    sstrncpy(&p, body_type_names(type), sizeof(type_d)-(p-type_d));
    if(full && subtype){
	*p++ = '/';
	sstrncpy(&p, subtype, sizeof(type_d)-(p-type_d));
    }

    type_d[sizeof(type_d)-1] = '\0';

    switch(type){
      case TYPETEXT:
	parmval = parameter_val(params, "charset");

        if(parmval){
	    for(i = 0; charset_names[i].rfcname; i++)
	      if(!strucmp(parmval, charset_names[i].rfcname)){
		  if(!strucmp(parmval, ps_global->display_charmap
			      ? ps_global->display_charmap : "us-ascii")
		     || !strucmp(parmval, "us-ascii"))
		    i = -1;

		  break;
	      }

	    if(i >= 0){			/* charset to write */
		if(charset_names[i].rfcname){
		    sstrncpy(&p, " (charset: ", sizeof(type_d)-(p-type_d));
		    sstrncpy(&p, charset_names[i].rfcname
				  ? charset_names[i].rfcname : "Unknown", sizeof(type_d)-(p-type_d));
		    if(full){
			sstrncpy(&p, " \"", sizeof(type_d)-(p-type_d));
			sstrncpy(&p, charset_names[i].humanname
				? charset_names[i].humanname
				: parmval, sizeof(type_d)-(p-type_d));
			if(sizeof(type_d)-(p-type_d) > 0)
			  *p++ = '\"';
		    }

		    sstrncpy(&p, ")", sizeof(type_d)-(p-type_d));
		}
		else{
		    sstrncpy(&p, " (charset: ", sizeof(type_d)-(p-type_d));
		    sstrncpy(&p, parmval, sizeof(type_d)-(p-type_d));
		    sstrncpy(&p, ")", sizeof(type_d)-(p-type_d));
		}
	    }

	    fs_give((void **) &parmval);
        }

        break;

      case TYPEMESSAGE:
	if(full && subtype && strucmp(subtype, "external-body") == 0)
	  if((parmval = parameter_val(params, "access-type")) != NULL){
	      snprintf(p, sizeof(type_d)-(p-type_d), " (%s%s)", full ? "Access: " : "", parmval);
	      fs_give((void **) &parmval);
	  }

	break;

      default:
        break;
    }

    if(full && type != TYPEMULTIPART && type != TYPEMESSAGE){
	unsigned char decodebuf[10000];
	if((parmval = parameter_val(params, "name")) != NULL){
	    rfc1522_decode_to_utf8(decodebuf, sizeof(decodebuf), parmval);
	    snprintf(p, sizeof(type_d)-(p-type_d), " (Name: \"%s\")", decodebuf);
	    fs_give((void **) &parmval);
	}
	else if((parmval = parameter_val(disp_params, "filename")) != NULL){
	    rfc1522_decode_to_utf8(decodebuf, sizeof(decodebuf), parmval);
	    snprintf(p, sizeof(type_d)-(p-type_d), " (Filename: \"%s\")", decodebuf);
	    fs_give((void **) &parmval);
	}
    }

    type_d[sizeof(type_d)-1] = '\0';

    return(type_d);
}
     



void
format_mime_size(char *string, size_t stringlen, struct mail_bodystruct *b, int flags)
{
    char tmp[10], *p = NULL;
    char *origstring;


    if(stringlen <= 0)
      return;

    origstring = string;

    if(flags & FMS_SPACE)
      *string++ = ' ';

    switch(b->encoding){
      case ENCBASE64 :
	if(b->type == TYPETEXT){
	  if(flags & FMS_SPACE)
	    *(string-1) = '~';
	  else
	    *string++ = '~';
	}

	strncpy(p = string, byte_string((3 * b->size.bytes) / 4), stringlen-(string-origstring));
	break;

      default :
      case ENCQUOTEDPRINTABLE :
	if(flags & FMS_SPACE)
	  *(string-1) = '~';
	else
	  *string++ = '~';

      case ENC8BIT :
      case ENC7BIT :
	if(b->type == TYPETEXT)
	  /* lines with no CRLF aren't counted, just add one so it makes more sense */
	  snprintf(string, stringlen-(string-origstring), "%s lines", comatose(b->size.lines+1));
	else
	  strncpy(p = string, byte_string(b->size.bytes), stringlen-(string-origstring));

	break;
    }

    origstring[stringlen-1] = '\0';

    if(p){
	for(; *p && (isascii((unsigned char) *p) && (isdigit((unsigned char) *p)
		     || ispunct((unsigned char) *p))); p++)
	  ;

	snprintf(tmp, sizeof(tmp), (flags & FMS_SPACE) ? " %-5.5s" : " %s", p);
	tmp[sizeof(tmp)-1] = '\0';
	strncpy(p, tmp, stringlen-(p-origstring));
    }

    origstring[stringlen-1] = '\0';
}

        

/*----------------------------------------------------------------------
   Determine if we can show all, some or none of the parts of a body

Args: body --- The message body to check

Returns: SHOW_ALL, SHOW_ALL_EXT, SHOW_PART or SHOW_NONE depending on
	 how much of the body can be shown and who can show it.
 ----*/     
int
mime_show(struct mail_bodystruct *body)
{
    int   effort, best_effort;
    PART *p;

    if(!body)
      return(SHOW_NONE);

    switch(body->type) {
      case TYPEMESSAGE:
	if(!strucmp(body->subtype, "rfc822"))
	  return(mime_show(body->nested.msg->body) == SHOW_ALL
		 ? SHOW_ALL: SHOW_PARTS);
	/* else fall thru to default case... */

      default:
	/*
	 * Since we're testing for internal displayability, give the
	 * internal result over an external viewer
	 */
	effort = mime_can_display(body->type, body->subtype, body);
	if(effort == MCD_NONE)
	  return(SHOW_NONE);
	else if(effort & MCD_INTERNAL)
	  return(SHOW_ALL);
	else
	  return(SHOW_ALL_EXT);

      case TYPEMULTIPART:
	best_effort = SHOW_NONE;
        for(p = body->nested.part; p; p = p->next)
	  if((effort = mime_show(&p->body)) > best_effort)
	    best_effort = effort;

	return(best_effort);
    }
}


/*
 * fcc_size_guess
 */
long
fcc_size_guess(struct mail_bodystruct *body)
{
    long  size = 0L;

    if(body){
	if(body->type == TYPEMULTIPART){
	    PART *part;

	    for(part = body->nested.part; part; part = part->next)
	      size += fcc_size_guess(&part->body);
	}
	else{
	    size = body->size.bytes;
	    /*
	     * If it is ENCBINARY we will be base64 encoding it. This
	     * ideally increases the size by a factor of 4/3, but there
	     * is a per-line increase in that because of the CRLFs and
	     * because the number of characters in the line might not
	     * be a factor of 3. So push it up by 3/2 instead. This still
	     * won't catch all the cases. In particular, attachments with
	     * lots of short lines (< 10) will expand by more than that,
	     * but that's ok since this is an optimization. That's why
	     * so_cs_puts uses the 3/2 factor when it does a resize, so
	     * that it won't have to resize linearly until it gets there.
	     */
	    if(body->encoding == ENCBINARY)
	      size = 3*size/2;
	}
    }

    return(size);
}



/*----------------------------------------------------------------------
    Format a strings describing one unshown part of a Mime message

Args: number -- A string with the part number i.e. "3.2.1"
      body   -- The body part
      type   -- 1 - Not shown, but can be
                2 - Not shown, cannot be shown
                3 - Can't print
      width  -- allowed width per line of editorial comment
      pc     -- function used to write the description comment

Result: formatted description written to object ref'd by "pc"
 ----*/
char *
part_desc(char *number, BODY *body, int type, int width, int flags, gf_io_t pc)
{
    char *t;
    char buftmp[MAILTMPLEN], sizebuf[256];

    if(!gf_puts(NEWLINE, pc))
      return("No space for description");

    format_mime_size(sizebuf, 256, body, FMS_NONE);

    snprintf(buftmp, sizeof(buftmp), "%s", body->description ? body->description : "");
    buftmp[sizeof(buftmp)-1] = '\0';
    snprintf(tmp_20k_buf+10000, SIZEOF_20KBUF-10000, "Part %s, %s%.2048s%s%s %s.",
            number,
            body->description == NULL ? "" : "\"",
            body->description == NULL ? ""
	      : (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, 10000, buftmp),
            body->description == NULL ? "" : "\"  ",
            type_desc(body->type, body->subtype, body->parameter, NULL, 1),
	    sizebuf);
    tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';

    iutf8ncpy((char *)tmp_20k_buf, (char *)(tmp_20k_buf+10000), 10000);
    tmp_20k_buf[10000] = '\0';

    t = &tmp_20k_buf[strlen(tmp_20k_buf)];

#ifdef SMIME
    /* if smime and not attempting print */
    if(F_OFF(F_DONT_DO_SMIME, ps_global) && is_pkcs7_body(body) && type != 3){

    	sstrncpy(&t, "\015\012", SIZEOF_20KBUF-(t-tmp_20k_buf));
	
	if(ps_global->smime && ps_global->smime->need_passphrase){
	    sstrncpy(&t,
	    "This part is a PKCS7 S/MIME enclosure. "
	    "You may be able to view it by entering the correct passphrase "
	    "with the \"Decrypt\" command.",
	    SIZEOF_20KBUF-(t-tmp_20k_buf));
	}
	else{
	    sstrncpy(&t,
	    "This part is a PKCS7 S/MIME enclosure. "
	    "Press \"^E\" for more information.",
	    SIZEOF_20KBUF-(t-tmp_20k_buf));
	}
    
    } else 
#endif

    if(type){
	sstrncpy(&t, "\015\012", SIZEOF_20KBUF-(t-tmp_20k_buf));
	switch(type) {
	  case 1:
	    if(MIME_VCARD(body->type,body->subtype))
	      sstrncpy(&t,
	   /* TRANSLATORS: This is the description of an attachment that isn't being
	      shown but that can be viewed or saved. */
	   _("Not Shown. Use the \"V\" command to view or save to address book."), SIZEOF_20KBUF-(t-tmp_20k_buf));
	    else
	      sstrncpy(&t,
	   /* TRANSLATORS: This is the description of an attachment that isn't being
	      shown but that can be viewed or saved. */
	       _("Not Shown. Use the \"V\" command to view or save this part."), SIZEOF_20KBUF-(t-tmp_20k_buf));

	    break;

	  case 2:
	    sstrncpy(&t, "Cannot ", SIZEOF_20KBUF-(t-tmp_20k_buf));
	    if(body->type != TYPEAUDIO && body->type != TYPEVIDEO)
	      sstrncpy(&t, "dis", SIZEOF_20KBUF-(t-tmp_20k_buf));

	    sstrncpy(&t, 
	       "play this part. Press \"V\" then \"S\" to save in a file.", SIZEOF_20KBUF-(t-tmp_20k_buf));
	    break;

	  case 3:
	    sstrncpy(&t, _("Unable to print this part."), SIZEOF_20KBUF-(t-tmp_20k_buf));
	    break;
	}
    }

    if(!(t = format_editorial(tmp_20k_buf, width, flags, NULL, pc))){
	if(!gf_puts(NEWLINE, pc))
	  t = "No space for description";
    }

    return(t);
}


/*----------------------------------------------------------------------
       Can we display this type/subtype?

   Args: type       -- the MIME type to check
         subtype    -- the MIME subtype
         params     -- parameters
	 use_viewer -- tell caller he should run external viewer cmd to view

 Result: Returns:

	 MCD_NONE	if we can't display this type at all
	 MCD_INTERNAL	if we can display it internally
	 MCD_EXTERNAL	if it can be displayed via an external viewer

 ----*/
int
mime_can_display(int type, char *subtype, BODY *body)
{
    return((mailcap_can_display(type, subtype, body, 0)
	      ? MCD_EXTERNAL 
	    : (mailcap_can_display(type, subtype, body, 1) 
	       ? (MCD_EXT_PROMPT | MCD_EXTERNAL) : MCD_NONE))
	   | ((type == TYPETEXT || type == TYPEMESSAGE
	       || MIME_VCARD(type,subtype))
	        ? MCD_INTERNAL : MCD_NONE));
}
