#if !defined(lint) && !defined(DOS)
static char rcsid[] = "$Id: mailpart.c,v 4.15 1994/10/05 22:13:25 hubert Exp $";
#endif
/*----------------------------------------------------------------------

            T H E    P I N E    M A I L   S Y S T E M

   Laurence Lundblade and Mike Seibel
   Networks and Distributed Computing
   Computing and Communications
   University of Washington
   Administration Builiding, AG-44
   Seattle, Washington, 98195, USA
   Internet: lgl@CAC.Washington.EDU
             mikes@CAC.Washington.EDU

   Please address all bugs and comments to "pine-bugs@cac.washington.edu"

   Copyright 1989-1994  University of Washington

    Permission to use, copy, modify, and distribute this software and its
   documentation for any purpose and without fee to the University of
   Washington is hereby granted, provided that the above copyright notice
   appears in all copies and that both the above copyright notice and this
   permission notice appear in supporting documentation, and that the name
   of the University of Washington not be used in advertising or publicity
   pertaining to distribution of the software without specific, written
   prior permission.  This software is made available "as is", and
   THE UNIVERSITY OF WASHINGTON DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED,
   WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED
   WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN
   NO EVENT SHALL THE UNIVERSITY OF WASHINGTON BE LIABLE FOR ANY SPECIAL,
   INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
   LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT
   (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN CONNECTION
   WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  
   Pine and Pico are trademarks of the University of Washington.
   No commercial use of these trademarks may be made without prior
   written permission of the University of Washington.

   Pine is in part based on The Elm Mail System:
    ***********************************************************************
    *  The Elm Mail System  -  Revision: 2.13                             *
    *                                                                     *
    * 			Copyright (c) 1986, 1987 Dave Taylor              *
    * 			Copyright (c) 1988, 1989 USENET Community Trust   *
    ***********************************************************************
 

  ----------------------------------------------------------------------*/

/*======================================================================
     mailpart.c
     The meat and pototoes of attachment processing here.

  ====*/

#include "headers.h"


/*
 * Information used to paint and maintain a line on the attachment
 * screen.
 */
typedef struct atdisp_line {
    char	     *dstring;			/* alloc'd var value string  */
    ATTACH_S	     *attp;			/* actual attachment pointer */
    struct atdisp_line *next, *prev;
} ATDISP_S;


/*
 * struct defining attachment screen's current state
 */
typedef struct att_screen {
    ATDISP_S  *current,
	      *top_line;
} ATT_SCREEN_S;
static ATT_SCREEN_S *att_screen;


#define	next_attline(p)	((p) ? (p)->next : NULL)
#define	prev_attline(p)	((p) ? (p)->prev : NULL)

#define HEADER_LINES	2
#define BOTTOM_LINES	3
#define	FIRST_DLINE	2		/* first screen line for display */


static struct key att_index_keys[] = 
       {{"?","Help",0},	       {NULL,NULL, 0},       {"E","Exit Index",0},
	{"V","[View]",0},      {"P","PrevAttch", 0}, {"N","NextAttch", 0},
        {"-","PrevPage",0},    {"Spc","NextPage",0}, {NULL,NULL,0},
	{"S","Save",0},        {"|","Pipe",0},       {"W","WhereIs",0}};
static struct key_menu att_index_keymenu = {sizeof(att_index_keys)/(sizeof(att_index_keys[0])*12), 0, 0,0,0,0, att_index_keys};
#define	ATT_PIPE_KEY	10
#define	ATT_VIEW_KEY	3


#ifdef	ANSI
int	  display_attachment(long, ATTACH_S *);
void	  display_text_att(long, ATTACH_S *);
void	  display_msg_att(long, ATTACH_S *);
void	  run_viewer(char *, BODY *);
ATDISP_S *new_attline(ATDISP_S **);
void	  free_attline(ATDISP_S **);
ATDISP_S *first_attline(ATDISP_S *);
void	  attachment_screen_redrawer();
int	  attachment_screen_updater(struct pine *, ATDISP_S *, ATT_SCREEN_S *);

#else
int       display_attachment();
void	  display_text_att();
void	  display_msg_att();
void      run_viewer();
ATDISP_S *new_attline();
void	  free_attline();
ATDISP_S *first_attline();
void	  attachment_screen_redrawer();
int	  attachment_screen_updater();
#endif



/*----------------------------------------------------------------------
   Provide attachments in browser for selected action

  Args: ps -- pointer to pine structure
	msgmap -- struct containing current message to display

  Result: 

  Handle painting and navigation of attachment index.  It would be nice
  to someday handle message/rfc822 segments in a neat way (i.e., enable
  forwarding, take address, etc.).
 ----*/
void
attachment_screen(ps, msgmap)
    struct pine *ps;
    MSGNO_S     *msgmap;
{
    int		  i, ch, orig_ch, done = 0, changes = 0, dline,
		  nl = 0, sl = 0, old_cols = -1;
    long	  msgno;
    char	 *p;
    ATTACH_S	 *atmp;
    ATDISP_S	 *current = NULL, *ctmp = NULL;
    ATT_SCREEN_S  screen;

    if(mn_total_cur(msgmap) > 1L){
	q_status_message(0, 0, 3,
		     "\007Can only view one message's attachments at a time!");
	return;
    }
    else if(ps->atmts && !(ps->atmts + 1)->description)
      q_status_message1(0, 1, 3,
	"Message %s has only one part (the message body), and no attachments.",
	long2string(mn_get_cur(msgmap)));

    /*
     * find the longest number and size strings
     */
    for(atmp = ps->atmts; atmp && atmp->description; atmp++){
	if((i = strlen(atmp->number)) > nl)
	  nl = i;

	if((i = strlen(atmp->size)) > sl)
	  sl = i;
    }

    /*
     * Then, allocate and initialize attachment line list...
     */
    for(atmp = ps->atmts; atmp && atmp->description; atmp++)
      new_attline(&current)->attp = atmp;

    screen.current     = screen.top_line = NULL;
    msgno	       = mn_m2raw(msgmap, mn_get_cur(msgmap));
    ps->mangled_screen = 1;			/* build display */
    current	       = first_attline(current);
    ps->redrawer       = attachment_screen_redrawer;
    att_screen	       = &screen;

    while(!done){
	if(ps->mangled_screen){
	    /*
	     * build/rebuild display lines
	     */
	    if(old_cols != ps->ttyo->screen_cols){
		old_cols = ps->ttyo->screen_cols;
		for(ctmp=first_attline(current);
		    ctmp && (atmp = ctmp->attp) && atmp->description;
		    ctmp=next_attline(ctmp)){
		    size_t len, dlen;

		    if(ctmp->dstring)
		      fs_give((void **)&ctmp->dstring);

		    len = max(80, ps->ttyo->screen_cols) * sizeof(char);
		    ctmp->dstring = (char *)fs_get(len + 1);
		    ctmp->dstring[len] = '\0';
		    memset(ctmp->dstring, ' ', len);
		    p = ctmp->dstring + 4;
		    for(i = 0; atmp->number[i]; i++)
		      *p++ = atmp->number[i];

		    /* add 3 spaces, plus pad number, and right justify size */
		    p += 3 + nl - i + (sl - strlen(atmp->size));

		    for(i = 0; atmp->size[i]; i++)
		      *p++ = atmp->size[i];

		    p += 3;

		    /* copy the description up to dstrings length */
		    dlen = strlen(atmp->description);
		    strncpy(p, atmp->description,
			    min(dlen, len - (p - ctmp->dstring)));
		}
	    }

	    ps->mangled_header = 1;
	    ps->mangled_footer = 1;
	    ps->mangled_body   = 1;
	    ps->mangled_screen = 0;
	}

	/*----------- Check for new mail -----------*/
        if(new_mail(NULL, 0,ch==NO_OP_IDLE ? 0 : ch==NO_OP_COMMAND ?1 :2) >= 0)
          ps->mangled_header = 1;

	if(ps->mangled_header){
	    set_titlebar("ATTACHMENT INDEX", ps->mail_stream,
			 ps->context_current, ps->cur_folder, ps->msgmap, 1,
			 MessageNumber, 0, 0);
	    ps->mangled_header = 0;
	}

	dline = attachment_screen_updater(ps, current, &screen);

	/*---- This displays new mail notification, or errors ---*/
        display_message(ch);

        /*------ Read the command from the keyboard ----*/
	if(ps->mangled_footer){
	    bitmap_t	 bitmap;

	    setbitmap(bitmap);
	    ps->mangled_footer = 0;
	    if(F_OFF(F_ENABLE_PIPE, ps))
	      clrbitn(ATT_PIPE_KEY, bitmap);  /* always clear for DOS */

	    draw_keymenu(&att_index_keymenu, bitmap, ps->ttyo->screen_cols,
			 -2, 0, FirstMenu,0);
	}

	MoveCursor(max(0, ps->ttyo->screen_rows - 3), 0);
	ch = orig_ch = read_command();

        if(ch <= 0xff && isupper(ch))
          ch = tolower(ch);

	switch(ch){
	  case '?' :				/* help! */
	  case ctrl('G'):
	  case PF1 :
	    helper(h_attachment_screen, "HELP FOR ATTACHMENT INDEX", 0);
	    ps->mangled_screen = 1;
	    break;

	  case 'e' :				/* exit attachment screen */
	  case PF3 :
	    done++;
	    break;

	  case 'n' :				/* next list element */
	  case '\t' :
	  case ctrl('F') :
	  case KEY_RIGHT :
	  case ctrl('N'):			/* down arrow */
	  case KEY_DOWN :
	  case PF6 :
	    ch = KEY_DOWN;
	    if(ctmp = next_attline(current))
	      current = ctmp;

	    break;

	  case 'p' :				/* previous list element */
	  case ctrl('B') :
	  case KEY_LEFT :
	  case ctrl('P') :			/* up arrow */
	  case KEY_UP :
	  case PF5 :
	    ch = KEY_UP;
	    if(ctmp = prev_attline(current))
	      current = ctmp;

	    break;

	  case '+' :				/* page forward */
	  case ' ' :
	  case ctrl('V') :
	  case PF8 :
	    ch = KEY_DOWN;
	    while(dline++ < ps->ttyo->screen_rows - BOTTOM_LINES)
	      if(ctmp = next_attline(current))
		current = ctmp;
	      else
		break;

	    break;

	  case '-' :				/* page backward */
	  case ctrl('Y') :
	  case PF7 :
	    ch = KEY_UP;
	    while(dline-- > FIRST_DLINE)
	      if(ctmp = prev_attline(current))
		current = ctmp;
	      else
		break;

	    while(++dline < ps->ttyo->screen_rows - BOTTOM_LINES)
	      if(ctmp = prev_attline(current))
		current = ctmp;
	      else
		break;

	    break;

	  case 'w' :				/* whereis */
	  case ctrl('W') :
	  case PF12 :
	    /*--- get string  ---*/
	    {int   rc, found = 0;
	     char *result = NULL, buf[64];
	     static char last[64], tmp[64];
	     HelpType help;

	     ps->mangled_footer = 1;
	     buf[0] = '\0';
	     sprintf(tmp, "Word to find %s%.40s%s: ",
		     (last[0]) ? "[" : "",
		     (last[0]) ? last : "",
		     (last[0]) ? "]" : "");
	     help = NO_HELP;
	     while(1){
		 rc = optionally_enter(buf,-3,0,63,1,0,tmp,NULL,help,0);
		 if(rc == 3)
		   help = help == NO_HELP ? h_attach_index_whereis : NO_HELP;
		 else if(rc == 0 || rc == 1 || !buf[0]){
		     if(rc == 0 && !buf[0] && last[0])
		       strcpy(buf, last);

		     break;
		 }
	     }

	     if(rc == 0 && buf[0]){
		 ch   = KEY_DOWN;
		 ctmp = current;
		 while(ctmp = next_attline(ctmp))
		   if(srchstr(ctmp->dstring, buf)){
		       found++;
		       break;
		   }

		 if(!found){
		     ctmp = first_attline(current);

		     while(ctmp != current)
		       if(srchstr(ctmp->dstring, buf)){
			   found++;
			   break;
		       }
		       else
			 ctmp = next_attline(ctmp);
		 }
	     }
	     else
	       result = "WhereIs cancelled";

	     if(found && ctmp){
		 strcpy(last, buf);
		 result  = "Word found";
		 current = ctmp;
	     }

	     q_status_message(0,0,3,result ? result : "Word not found");
	    }

	    break;

          case ctrl('Z'):			/* suspend pine! */
            if(!have_job_control()){
		bogus_command(orig_ch, F_ON(F_USE_FK, ps) ? "F1" : "?");
		break;
	    }

	    if(F_OFF(F_CAN_SUSPEND, ps)){
		q_status_message(1, 1, 3,
			    "\007Pine suspension not enabled - see help text");
		break;
	    }
	    else
	      do_suspend(ps);			/* fall thru to redraw */

	  case ctrl('L'):			/* redraw */
          case KEY_RESIZE:
	    ClearScreen();
	    ps->mangled_screen = 1;
	    break;

	  case 'v':				/* View command */
	  case ctrl('M'):
	  case PF4 :
	    display_attachment(msgno, current->attp);
	    break;

	  case 's':				/* Save command */
	  case PF10 :
	    save_attachment(-3, msgno, current->attp);
	    ps->mangled_footer = 1;
	    break;

	  case '|':				/* Pipe command */
	  case PF11 :
	    if(F_ON(F_ENABLE_PIPE, ps)){
		pipe_attachment(msgno, current->attp);
		ps->mangled_footer = 1;
		break;
	    }					/* fall thru to complain */

	  default:
	    bogus_command(orig_ch, F_ON(F_USE_FK, ps) ? "F1" : "?");

	  case NO_OP_IDLE:			/* simple timeout */
	  case NO_OP_COMMAND:
	    break;
	}
    }

    for(current = first_attline(current); current;){	/* clean up */
	ctmp = current->next;
	free_attline(&current);
	current = ctmp;
    }

    ps->mangled_screen = 1;
    return;
}



/*----------------------------------------------------------------------
  allocate and attach a fresh attachment line struct

  Args: current -- display line to attache new struct to

  Returns: newly alloc'd attachment display line
  ----*/
ATDISP_S *
new_attline(current)
    ATDISP_S **current;
{
    ATDISP_S *p;

    p = (ATDISP_S *)fs_get(sizeof(ATDISP_S));
    memset((void *)p, 0, sizeof(ATDISP_S));
    if(current){
	if(*current){
	    p->next	     = (*current)->next;
	    (*current)->next = p;
	    p->prev	     = *current;
	    if(p->next)
	      p->next->prev = p;
	}

	*current = p;
    }

    return(p);
}



/*----------------------------------------------------------------------
  Release system resources associated with attachment display line

  Args: p -- line to free

  Result: 
  ----*/
void
free_attline(p)
    ATDISP_S **p;
{
    if(p){
	if((*p)->dstring)
	  fs_give((void **)&(*p)->dstring);

	fs_give((void **)p);
    }
}



/*----------------------------------------------------------------------
  Manage display of the attachment screen menu body.

  Args: ps -- pine struct pointer
	current -- currently selected display line
	screen -- reference points for display painting

  Result: 
 */
int
attachment_screen_updater(ps, current, screen)
    struct pine  *ps;
    ATDISP_S	 *current;
    ATT_SCREEN_S *screen;
{
    int	      dline, return_line = FIRST_DLINE;
    ATDISP_S *top_line, *ctmp;

    /* calculate top line of display */
    dline = 0;
    ctmp = top_line = first_attline(current);
    do
      if(((dline++)%(ps->ttyo->screen_rows-HEADER_LINES-BOTTOM_LINES)) == 0)
	top_line = ctmp;
    while(ctmp != current && (ctmp = next_attline(ctmp)));

    /* mangled body or new page, force redraw */
    if(ps->mangled_body || screen->top_line != top_line)
      screen->current = NULL;

    /* loop thru painting what's needed */
    for(dline = 0, ctmp = top_line;
	dline < ps->ttyo->screen_rows - BOTTOM_LINES - HEADER_LINES;
	dline++, ctmp = next_attline(ctmp)){

	/*
	 * only fall thru painting if something needs painting...
	 */
	if(!(!screen->current || ctmp == screen->current || ctmp == current))
	  continue;

	if(ctmp && ctmp->dstring){
	    char *p = tmp_20k_buf;
	    int   i, j, x = 0;
	    if(F_ON(F_FORCE_LOW_SPEED,ps) || ps->low_speed){
		x = 2;
		if(ctmp == current){
		    return_line = dline + FIRST_DLINE;
		    PutLine0(dline + FIRST_DLINE, 0, "->");
		}
		else
		  PutLine0(dline + FIRST_DLINE, 0, "  ");

		/*
		 * Only paint lines if we have to...
		 */
		if(screen->current)
		  continue;
	    }
	    else if(ctmp == current)
	      StartInverse();

	    /*
	     * Copy the value to a temp buffer expanding tabs, and
	     * making sure not to write beyond screen right...
	     */
	    for(i=0,j=x; ctmp->dstring[i] && j < ps->ttyo->screen_cols; i++){
		if(ctmp->dstring[i] == ctrl('I')){
		    do
		      *p++ = ' ';
		    while(j < ps_global->ttyo->screen_cols && ((++j)&0x07));
			  
		}
		else{
		    *p++ = ctmp->dstring[i];
		    j++;
		}
	    }

	    *p = '\0';
	    PutLine0(dline + FIRST_DLINE, x, tmp_20k_buf + x);

	    if(ctmp == current
	       && !(F_ON(F_FORCE_LOW_SPEED,ps) || ps->low_speed))
	      EndInverse();
	}
	else{
	    MoveCursor(dline + FIRST_DLINE, 0);
	    CleartoEOLN();
	}
    }

    ps->mangled_body = 0;
    screen->top_line = top_line;
    screen->current  = current;
    return(return_line);
}


/*----------------------------------------------------------------------
  Redraw the attachment screen based on the global "att_screen" struct

  Args: none

  Result: 
  ----*/
void
attachment_screen_redrawer()
{
    bitmap_t	 bitmap;

    ClearScreen();

    set_titlebar("ATTACHMENT INDEX", ps_global->mail_stream,
		 ps_global->context_current, ps_global->cur_folder,
		 ps_global->msgmap, 1, FolderName,0,0);

    ps_global->mangled_body = 1;
    (void)attachment_screen_updater(ps_global,att_screen->current,att_screen);

    setbitmap(bitmap);
    draw_keymenu(&att_index_keymenu, bitmap, ps_global->ttyo->screen_cols,
		 -2, 0, FirstMenu, 0);
}



/*----------------------------------------------------------------------
  Seek back from the given display line to the beginning of the list

  Args: p -- display linked list

  Result: 
  ----*/
ATDISP_S *
first_attline(p)
    ATDISP_S *p;
{
    while(p && p->prev)
      p = p->prev;

    return(p);
}



/*----------------------------------------------------------------------
  Save the given attachment associated with the given message no

  Args: ps

  Result: 
  ----*/
void
save_attachment(qline, msgno, a)
     int       qline;
     long      msgno;
     ATTACH_S *a;
{
    char	filename[MAXPATH+1], full_filename[MAXPATH+1], *ill;
    HelpType	help;
    char       *l_string, prompt_buf[200];
    int         r, is_text;
    long        len;
    PARAMETER  *param;
    gf_io_t     pc;
    STORE_S    *store;
    char       *err;

    is_text = a->body->type == TYPETEXT;

    /*-------  Figure out suggested file name ----*/
    filename[0] = '\0';
    for(param = a->body->parameter; param; param = param->next)
      if(param->value && strucmp(param->attribute, "name") == 0){
	  strcpy(filename, param->value);
	  break;
      }

    dprint(9, (debugfile, "save_attachment(name: %s)\n", filename));

    /*---------- Prompt the user for the file name -------------*/
    help = NO_HELP;
    while(1) {
	sprintf(prompt_buf, "Copy attachment to file in %s directory: ",
		F_ON(F_USE_CURRENT_DIR, ps_global) ? "current" : "home");
	r = optionally_enter(filename, qline, 0, MAXPATH, 1, 0, prompt_buf,
			     NULL, help, 0);

        /*--- Help ----*/
        if(r == 3) {
            help = (help == NO_HELP) ? h_oe_export : NO_HELP;
            continue;
        }

        if(r == 1 || filename[0] == '\0') {
            q_status_message(0, 0, 2, "Save attachment cancelled");
            return;
        }

        if(r == 4)
          continue;

        /* check out and expand file name. give possible error messages */
        strcpy(full_filename, filename);
        removing_trailing_white_space(full_filename);
        removing_leading_white_space(full_filename);
        if((ill = filter_filename(filename)) != NULL) {
            q_status_message1(0, 1, 3, "\007%s in file name", ill);
            display_message('x');
            sleep(3);
            continue;
        }
#ifndef	DOS
        if(full_filename[0] == '~') {
            if(fnexpand(full_filename, sizeof(full_filename)) == NULL) {
                char *p = strindex(full_filename, '/');
                if(p != NULL)
                  *p = '\0';
                q_status_message1(1, 1, 3,
                    "\007Error expanding file name: \"%s\" unknown user",
                    full_filename);
                display_message('x');
                sleep(3);
                continue;
            }
        } else if(full_filename[0] != '/') {
#else
        if(full_filename[0] != '\\' && full_filename[1] != ':') {
#endif
	    if(F_ON(F_USE_CURRENT_DIR, ps_global))
              (void)strcpy(full_filename, filename);
	    else
              build_path(full_filename, ps_global->home_dir, filename);
        }

        break; /* Must have got an OK file name */
    }

    if(ps_global->restricted) {
        q_status_message(1, 2, 4, "\007Pine demo can't save attachments");
        return;
    }
     

    /*----------- Write the contents to the file -------------*/
    if(can_access(full_filename, ACCESS_EXISTS) == 0) {
	static ESCKEY_S access_opts[] = {
	    {'o', 'o', "O", "Overwrite"},
	    {'a', 'a', "A", "Append"},
	    {-1, 0, NULL, NULL}};

        sprintf(prompt_buf,
		"File \"%s%s\" already exists.  Overwrite or append it ? ",
		((r = strlen(full_filename)) > 20) ? "..." : "",
                full_filename + ((r > 20) ? r - 20 : 0));

	switch(radio_buttons(prompt_buf, -3, 0, access_opts, 'a',
			    'x', 0, NO_HELP, 0)){
	  case 'o' :
	    if(unlink(full_filename) < 0){	/* BUG: breaks links */
		q_status_message2(1, 3, 5, "\007Error deleting old %s: %s",
				  full_filename, error_description(errno));
		return;
	    }

	    break;

	  case 'a' :
	    break;

	  case 'x' :
	  default :
            q_status_message(0, 0, 2, "Save of attachment cancelled");
            return;
	}
    }

    if((store = so_get(FileStar, full_filename, WRITE_ACCESS)) == NULL){
	q_status_message2(1, 3, 5, "\007Error opening destination %s: %s",
			  full_filename, error_description(errno));
	return;
    }

    gf_set_so_writec(&pc, store);

    err = detach(ps_global->mail_stream,msgno,a->body,a->number,&len,pc,NULL);
    if(!err){
        l_string = cpystr(byte_string(len));
        q_status_message7(0, 2, 4, "Part %s, %s%s written to \"%s\"%s%s%s",
                         a->number, 
                         is_text ?   comatose(a->body->size.lines) :
                                     l_string,
                         is_text ?   " lines" : "",
                         full_filename,
                         is_text || len == a->body->size.bytes ?
                                        "" : "(decoded from ",
                         is_text || len == a->body->size.bytes ?
                                        "" : byte_string(a->body->size.bytes),
                         is_text || len == a->body->size.bytes ?
                                        "" : ")");
        fs_give((void **)&l_string);
    }
    else{
      q_status_message2(1, 3, 5,"\007%s: Error writing attachment to \"%s\"",
                        err, full_filename);
    }

    so_give(&store);
}


/*----------------------------------------------------------------------
  Unpack and display the given attachment associated with given message no.

  Args: msgno -- message no attachment is part of
	a -- attachment to display

  Returns: 0 on success, non-zero (and error message queued) otherwise
  ----*/        
int
display_attachment(msgno, a)
     long      msgno;
     ATTACH_S *a;
{
    char    *filename;
    STORE_S *store;
    gf_io_t  pc;
    filter_t aux_filter[2];
    char    *err;
#ifndef DOS
    char     prefix[8];
#endif

    /*------- Display the attachment -------*/
    if(!a->can_display) {
        /*----- Can't display this type ------*/
	if(!mime_can_display(a->body->type, a->body->subtype,
			     a->body->parameter, (int *)NULL)) {
	  q_status_message3(1, 3, 5,
		      "\007Don't know how to display attachment format %s%s%s",
                      body_type_names(a->body->type),
                      a->body->subtype != NULL ? "/" : "",
                      a->body->subtype != NULL ? a->body->subtype :"");
	} else {
	  q_status_message1(1, 3, 5,
			    "\007Don't know how to unpack \"%s\" encoding",
			    body_encodings[(a->body->encoding <= ENCMAX)
					     ? a->body->encoding : ENCOTHER]);

	}
        return(1);
    }
    else if(!a->use_external_viewer){
	if(a->body->type == TYPETEXT)
	  display_text_att(msgno, a);
	else if(a->body->type == TYPEMESSAGE)
	  display_msg_att(msgno, a);

	ps_global->mangled_screen = 1;
	return(0);
    }

    /*------ Write the image to a temporary file ------*/
#ifdef	DOS
    filename = temp_nam(NULL, "im");
#else
    sprintf(prefix, "img-%.3s", (a->body->subtype) ? a->body->subtype : "unk");
    filename = temp_nam(NULL, prefix);
#endif

    if((store = so_get(FileStar, filename, WRITE_ACCESS)) == NULL){
        q_status_message2(1, 3, 5,
                          "\007Error \"%s\", Can't write file %s",
                          error_description(errno), filename);
        return(1);
    }

    gf_set_so_writec(&pc, store);
    err = detach(ps_global->mail_stream,msgno,a->body,a->number,NULL,pc,NULL);
    if(err){
	q_status_message2(1, 3, 5,
		  "\007%s: Error saving image to temp file %s", err, filename);
	return(1);
    }

    so_give(&store);

    /*----- Run the viewer process ----*/
    run_viewer(filename, a->body);
#ifdef VMS
    delete(filename);
#endif	/* VMS */
    fs_give((void **)&filename);
    return(0);
}


/*----------------------------------------------------------------------
   Fish the required command from mailcap and run it

  Args: image_file -- The name of the file to pass viewer
	body -- body struct containing type/subtype of part

A side effect may be that scrolltool is called as well if
exec_mailcap_cmd has any substantial output...
 ----*/
void
run_viewer(image_file, body)
     char *image_file;
     BODY *body;
{
    char *cmd            = NULL;
    int   needs_terminal = 0;

    q_status_message(0,0,3,"Displaying attachment...");
    display_message('x');

    if(cmd = mailcap_build_command(body, image_file, &needs_terminal)){
	exec_mailcap_cmd(cmd, image_file, needs_terminal);
	fs_give((void **)&cmd);
    }
    else
      q_status_message1(0,1,4,"Cannot display %s attachment",
		     type_desc(body->type, body->subtype, body->parameter, 1));
}



/*----------------------------------------------------------------------
  Detach and provide for browsing a text body part

  Args: msgno -- raw message number to get part from
	 a -- attachment struct for the desired part

  Result: 
 ----*/
void
display_text_att(msgno, a)
    long      msgno;
    ATTACH_S *a;
{
    STORE_S        *store;
    gf_io_t         pc;
    SourceType	    src = CharStar;

    clear_index_cache_ent(msgno);

    /* BUG, should check this return code */
    (void)mail_fetchstructure(ps_global->mail_stream, msgno, NULL);

    /* initialize a storage object */
#ifdef	DOS
    if(a->body->size.bytes > MAX_MSG_INCORE
       || strcmp(ps_global->mail_stream->dtb->name, "nntp") == 0){
	src = FileStar;
    }
    else
      src = CharStar;
#endif

    if(store = so_get(src, NULL, EDIT_ACCESS)){
	gf_set_so_writec(&pc, store);
	(void) decode_text(a, msgno, pc, QStatus, 1);
	scrolltool(so_text(store), "ATTACHED TEXT", (int *)NULL,
		   AttachText, src, a);
	so_give(&store);	/* free resources associated with store */
    }
    else
      q_status_message(0,2,3,"\007Error allocating space for attachment.");
}


/*----------------------------------------------------------------------
  Detach and provide for browsing a body part of type "MESSAGE"

  Args: msgno -- message number to get partrom
	 a -- attachment struct for the desired part

  Result: 
 ----*/
void
display_msg_att(msgno, a)
    long      msgno;
    ATTACH_S *a;
{
    STORE_S        *store;
    gf_io_t         pc;
    SourceType	    src = CharStar;

    clear_index_cache_ent(msgno);

    /* BUG, should check this return code */
    (void)mail_fetchstructure(ps_global->mail_stream, msgno, NULL);

    /* initialize a storage object */
#ifdef	DOS
    if(a->body->size.bytes > MAX_MSG_INCORE
       || strcmp(ps_global->mail_stream->dtb->name, "nntp") == 0){
	src = FileStar;
    }
    else
      src = CharStar;
#endif

    if(!(store = so_get(src, NULL, EDIT_ACCESS))){
	q_status_message(0,2,3,"\007Error allocating space for message.");
	return;
    }

    gf_set_so_writec(&pc, store);

    if(a->body->subtype && strucmp(a->body->subtype, "rfc822") == 0){
	format_envelope(a->body->contents.msg.env, pc);
	gf_puts(NEWLINE, pc);
	if((a+1)->body && (a+1)->body->type == TYPETEXT)
	  (void) decode_text(a+1, msgno, pc, QStatus, 1);
	else
	  gf_puts("[Can't display first non-text segment]", pc);
    }
    else if(a->body->subtype 
	    && strucmp(a->body->subtype, "external-body") == 0) {
	gf_puts("This part is not included and can be fetched as follows:",pc);
	gf_puts(NEWLINE, pc);
	gf_puts(NEWLINE, pc);
	gf_puts(display_parameters(a->body->parameter), pc);
    }
    else
      (void) decode_text(a, msgno, pc, QStatus, 1);

    scrolltool(so_text(store), "ATTACHED MESSAGE", (int *)NULL,
	       AttachText, src, a);

    so_give(&store);	/* free resources associated with store */
}



/*----------------------------------------------------------------------

  ----*/        
void
pipe_attachment(msgno, a)
     long      msgno;
     ATTACH_S *a;
{
  char   *err;
  char   *resultfilename = NULL ;
  char    prompt[80] ;
  int     done = 0 ;
  PIPE_S *syspipe;
  HelpType help;

  if(ps_global->restricted){
      q_status_message(0,2,2 ,"\007Pine demo can't pipe attachments");
      return;
  }

  sprintf(prompt, "Pipe attachment %s to : ", a->number) ;
  help = NO_HELP;
  while(!done) {
    flush_status_messages() ;
    switch(optionally_enter(ps_global->pipe_command, -3, 0, MAXPATH, 1, 0, 
			    prompt, NULL, help, 0)){
    case -1 :
      q_status_message(0,2,2 ,"\007Internal problem encountered");
      done++;
      break;
      
    case 0 :
      if(ps_global->pipe_command[0] == '\0'){
	  q_status_message(0, 1, 2, "Pipe command cancelled");
      }else if(syspipe = open_system_pipe(ps_global->pipe_command,
				    &resultfilename, PIPE_USER)){
	  gf_io_t pc;		/* wire up a generic putchar */
	  gf_set_writec(&pc, syspipe->ifile, 0L, FileStar);

	  /*------ Write the image to a temporary file ------*/
	  err = detach(ps_global->mail_stream, msgno, a->body, a->number,
		       (long *)NULL, pc, NULL);
	  if(err)
	    q_status_message1(1,3,5,"\007Error detaching for pipe: %s",err);

	  close_system_pipe(&syspipe, PIPE_USER);
	  if(err)
	    unlink(resultfilename);
	  else
	    display_system_pipe_output(resultfilename, "PIPE ATTACHMENT");

	  fs_give((void **)&resultfilename);
      } else {
	  q_status_message(0,2,2, "\007Error opening pipe") ;
      }
      done++;
      break;

    case 1 :
      q_status_message(0,2,2 ,"Pipe cancelled");
      done++;
      break;

    case 3 :
      help = (help == NO_HELP) ? h_pipe_attach : NO_HELP;
      break;
      
    case 2 :                              /* no place to escape to */
    case 4 :                              /* can't suspend */
      default :
	break;   
    }
  }
}



/*----------------------------------------------------------------------
  detach the given body part using the given encoding

  Args: a bunch

  Returns: NULL on success, error message otherwise
  ----*/
char *
detach(stream, msg_no, body, part_no, len, pc, aux_filters)
     MAILSTREAM *stream;		/* c-client stream to use         */
     long        msg_no;		/* message number to deal with	  */
     BODY       *body;			/* body pointer 		  */
     char       *part_no;		/* part number of message 	  */
     long       *len;			/* returns bytes read in this arg */
     gf_io_t     pc;			/* where to put it		  */
     filter_t   *aux_filters;		/* null terminated array of filts */
{
    unsigned long  length, rv;
    char          *status, *contents;
    gf_io_t        gc;
    SourceType     src = CharStar;
    static char    err_string[100];
#ifdef	DOS
    char	  *tmpfile_name = NULL;		/* name our own tmp file */
    extern unsigned char  *xlate_to_codepage;
#endif

    err_string[0] = '\0';

#ifdef	DOS
    if(body->size.bytes > MAX_MSG_INCORE || !strcmp(stream->dtb->name,"nntp")){
	src = FileStar;
	if(!(tmpfile_name = temp_nam(NULL, "dt"))
	   || !(append_file = fopen(tmpfile_name, "w+b"))){
	    if(tmpfile_name)
	      fs_give((void **)&tmpfile_name);

	    sprintf(err_string, "Can't detach!  Temp file %s",
		    error_description(errno));
	    return(err_string);
	}

	mail_parameters(stream, SET_GETS, (void *)dos_gets);
    }
    else
      mail_parameters(stream, SET_GETS, (void *)NULL);
#endif	/* DOS */

    /*
     * go grab the requested body part
     */
    contents = mail_fetchbody(stream, msg_no, part_no, &length);
    if(contents == NULL) {
	sprintf(err_string, "Unable to access body part %s", part_no);
	rv = 0L;
	goto fini;
    }

    rv = (length) ? length : 1L;

    gf_filter_init();			/* prepare to filter it! */

    switch(body->encoding) {		/* handle decoding */
      case ENC7BIT:
      case ENC8BIT:
      case ENCBINARY:
        break;

      case ENCBASE64:
	gf_link_filter(gf_b64_binary);
        break;

      case ENCQUOTEDPRINTABLE:
	gf_link_filter(gf_qp_8bit);
        break;

      case ENCOTHER:
      default:
	dprint(1, (debugfile, "detach: unknown CTE: \"%s\" (%d)\n",
		   (body->encoding <= ENCMAX) ? body_encodings[body->encoding]
					      : "BEYOND-KNOWN-TYPES",
		   body->encoding));
	break;
    }

    while(aux_filters && *aux_filters)	/* apply externally provided filters */
      gf_link_filter(*aux_filters++);

    /*
     * Following canonical model, after decoding convert newlines from
     * crlf to local convention.
     */
    if(body->type == TYPETEXT || (body->type == TYPEMESSAGE
				  && !strucmp(body->subtype, "rfc822"))){
	gf_link_filter(gf_nvtnl_local);
#ifdef	DOS
	/*
	 * When detaching a text part AND it's US-ASCII OR it
	 * matches what the user's defined as our charset,
	 * translate it...
	 */
	if(mime_can_display(body->type, body->subtype, body->parameter,
								(int *)NULL)
	   && xlate_to_codepage){
	    gf_translate_opt(xlate_to_codepage, 256);
	    gf_link_filter(gf_translate);
	}
#endif
    }

#ifdef	LATER
    dprint(9, (debugfile, "Attachment lengths: %ld (decoded) %lud (encoded)\n",
               *decoded_len, length));
#endif

    gf_set_readc(&gc,
#ifdef	DOS
		 /*
		  * If we fetched this to a file, make sure we set
		  * up the storage object's getchar function accordingly...
		  */
		 (src == FileStar) ? (void *)append_file :
#endif
		 contents, length, src);

    if(status = gf_pipe(gc, pc)) {
	sprintf(err_string, "Formatting error: %s", status);
        rv = 0L;
    }

  fini :

#ifdef	DOS
    /*
     * free up file pointer, and delete tmpfile opened for
     * dos_gets.  Again, we can't use DOS' tmpfile() as it writes root
     * sheesh.
     */
    if(src == FileStar){
	fclose(append_file);
	append_file = NULL;
	unlink(tmpfile_name);
	fs_give((void **)&tmpfile_name);
	mail_parameters(stream, SET_GETS, (void *)NULL);
	mail_gc(stream, GC_TEXTS);
    }
#endif

    if (len)
      *len = rv;

    return((err_string[0] == '\0') ? NULL : err_string);
}

