text: /*
   * ========================================================================
   * Copyright 2013-2021 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));
  }
  