#if !defined(lint) && !defined(DOS)
static char rcsid[] = "$Id: mailview.c,v 4.431 1999/02/01 17:25:45 mikes 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"


   Pine and Pico are registered 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, Pico, and Pilot software and its included text are Copyright
   1989-1999 by the University of Washington.

   The full text of our legal notices is contained in the file called
   CPYRIGHT, included with this distribution.


   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   *
    ***********************************************************************
 

  ----------------------------------------------------------------------*/

/*======================================================================
     mailview.c
     Implements the mailview screen
     Also includes scrolltool used to display help text
  ====*/


#include "headers.h"


/*----------------------------------------------------------------------
    Saved state for scrolling text 
 ----*/
typedef struct scroll_text {
    SCROLL_S	*parms;		/* Original text (file, char *, char **)     */
    char       **text_lines,	/* Lines to display			     */
		*fname;		/* filename of line offsets in "text"	     */
    FILE	*findex;	/* file pointer to line offsets in "text"    */
    short	*line_lengths;	/* Length of each line in "text_lines"	     */
    long	 top_text_line,	/* index in "text_lines" top displayed line  */
		 num_lines;	/* number of valid pointers in "text_lines"  */
    int		 lines_allocated; /* size of "text_lines" array		     */
    struct {
	int length,		/* count of displayable lines (== PGSIZE)    */
	    width,		/* width of displayable lines		     */
	    start_line,		/* line number to start painting on	     */
	    other_lines;	/* # of lines not for scroll text	     */
    } screen;			/* screen parameters			     */
} SCRLCTRL_S;


typedef struct scroll_file {
    long	offset;
    int		len;
} SCRLFILE_S;


/*
 * Struct to help write lines do display as they're decoded
 */
struct view_write_s {
    char      *line;
    int	       index,
	       screen_line,
	       last_screen_line;
#ifdef	_WINDOWS
    long       lines;
#endif
    HANDLE_S **handles;
    STORE_S   *store;
} *g_view_write;


static HANDLE_S **g_handle_p;

static char *g_editorial_prefix, *g_editorial_postfix;

/*
 * 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


/*
 * Definitions to help scrolltool
 */
#define SCROLL_LINES_ABOVE(X)	HEADER_ROWS(X)
#define SCROLL_LINES_BELOW(X)	FOOTER_ROWS(X)
#define	SCROLL_LINES(X)		max(((X)->ttyo->screen_rows               \
			 - SCROLL_LINES_ABOVE(X) - SCROLL_LINES_BELOW(X)), 0)
#define	scroll_text_lines()	(scroll_state(SS_CUR)->num_lines)


/*
 * Definitions for various scroll state manager's functions
 */
#define	SS_NEW	1
#define	SS_CUR	2
#define	SS_FREE	3


/*
 * Def's to help page reframing based on handles
 */
#define	SHR_NONE	0
#define	SHR_CUR		1
#define	SHR_


/*
 * Handle hints.
 */
#define	HANDLE_INIT_MSG		\
  "Selectable items in text -- Use Up/Down Arrows to choose, Return to view"
#define	HANDLE_ABOVE_ERR	\
  "No selected item displayed -- Use PrevPage to bring choice into view"
#define	HANDLE_BELOW_ERR	\
  "No selected item displayed -- Use NextPage to bring choice into view"


#define	PGSIZE(X)      (ps_global->ttyo->screen_rows - (X)->screen.other_lines)
#define	ISRFCEOL(S)    (*(S) == '\015' && *((S)+1) == '\012')

#define TYPICAL_BIG_MESSAGE_LINES	200

#define	CHARSET_DISCLAIMER \
	"The following text is in the \"%.80s\" character set.\015\012Your display is set for the \"%s\" character set. \015\012Some characters may be displayed incorrectly."


/*
 * Keymenu/Command mappings for various scrolltool modes.
 */
static struct key view_keys[] = 
       {HELP_MENU,
	OTHER_MENU,
	{"<","MsgIndex",{MC_INDEX,3,{'i','<',','}},KS_FLDRINDEX},
	{">","ViewAttch",{MC_VIEW_ATCH,3,{'v','>','.'}},KS_NONE},
	PREVMSG_MENU,
	NEXTMSG_MENU,
	PREVPAGE_MENU,
	NEXTPAGE_MENU,
	DELETE_MENU,
	UNDELETE_MENU,
	REPLY_MENU,
	FORWARD_MENU,

	HELP_MENU,
	OTHER_MENU,
	MAIN_MENU,
	QUIT_MENU,
	LISTFLD_MENU,
	GOTO_MENU,
	COMPOSE_MENU,
	WHEREIS_MENU,
	PRYNTMSG_MENU,
	TAKE_MENU,
	SAVE_MENU,
	EXPORT_MENU,

	HELP_MENU,
	OTHER_MENU,
	{"Ret","[View Hilite]",{MC_VIEW_HANDLE,3,
				{ctrl('m'),ctrl('j'),KEY_RIGHT}},KS_NONE},
	{":","SelectCur",{MC_SELCUR,1,{':'}},KS_SELECTCUR},
	{"^B","Prev URL",{MC_PREV_HANDLE,1,{ctrl('B')}},KS_NONE},
	{"^F","Next URL",{MC_NEXT_HANDLE,1,{ctrl('F')}},KS_NONE},
	JUMP_MENU,
	TAB_MENU,
	HDRMODE_MENU,
	BOUNCE_MENU,
	FLAG_MENU,
	PIPE_MENU};
INST_KEY_MENU(view_keymenu, view_keys);
#define VIEW_FULL_HEADERS_KEY	32
#define	VIEW_VIEW_HANDLE	26
#define VIEW_SELECT_KEY		27
#define VIEW_PREV_HANDLE	28
#define VIEW_NEXT_HANDLE	29
#define BOUNCE_KEY		33
#define FLAG_KEY		34
#define VIEW_PIPE_KEY		35

static struct key nr_anon_view_keys[] = 
       {HELP_MENU,
	WHEREIS_MENU,
	QUIT_MENU,
	NULL_MENU,
	PREVMSG_MENU,
	NEXTMSG_MENU,
	PREVPAGE_MENU,
	NEXTPAGE_MENU,
	FWDEMAIL_MENU,
	JUMP_MENU,
	INDEX_MENU,
	NULL_MENU};
INST_KEY_MENU(nr_anon_view_keymenu, nr_anon_view_keys);

static struct key nr_view_keys[] = 
       {HELP_MENU,
	OTHER_MENU,
	QUIT_MENU,
	NULL_MENU,
	PREVMSG_MENU,
	NEXTMSG_MENU,
	PREVPAGE_MENU,
	NEXTPAGE_MENU,
	{"F", "Fwd Email", {MC_FORWARD,1,{'f'}}, KS_FORWARD},
	JUMP_MENU,
	PRYNTTXT_MENU,
	SAVE_MENU,

	HELP_MENU,
	OTHER_MENU,
	EXPORT_MENU,
	COMPOSE_MENU,
	NULL_MENU,
	NULL_MENU,
	INDEX_MENU,
	WHEREIS_MENU,
	NULL_MENU,
	NULL_MENU,
	NULL_MENU,
	NULL_MENU};
INST_KEY_MENU(nr_view_keymenu, nr_view_keys);

static struct key simple_text_keys[] =
       {HELP_MENU,
	NULL_MENU,
	{"E","Exit Viewer",{MC_EXIT,1,{'e'}},KS_EXITMODE},
	NULL_MENU,
	NULL_MENU,
	NULL_MENU,
	PREVPAGE_MENU,
	NEXTPAGE_MENU,
	PRYNTTXT_MENU,
	WHEREIS_MENU,
	FWDEMAIL_MENU,
	{"S", "Save", {MC_SAVETEXT,1,{'s'}}, KS_SAVE}};
INST_KEY_MENU(simple_text_keymenu, simple_text_keys);




/*
 * Internal prototypes
 */
int	    is_an_env_hdr PROTO((char *));
void	    format_envelope PROTO((MAILSTREAM *, long, char *, ENVELOPE *,
				   gf_io_t, long, char *));
void	    format_env_hdr PROTO((MAILSTREAM *, long, char *, ENVELOPE *,
				  gf_io_t, char *, char *));
int	    format_raw_header PROTO((MAILSTREAM *, long, char *, gf_io_t,
				     char *));
void	    format_addr_string PROTO((MAILSTREAM *, long, char *, char *,
				      ADDRESS *, char *, gf_io_t));
void	    format_newsgroup_string PROTO((char *, char *, char *, gf_io_t));
int	    format_raw_hdr_string PROTO((char *, char *, gf_io_t, char *));
int	    format_env_puts PROTO((char *, gf_io_t));
long	    format_size_guess PROTO((BODY *));
void        pine_rfc822_write_address_noquote PROTO((ADDRESS *,
						     gf_io_t, int *));
void        pine_rfc822_address PROTO((ADDRESS *, gf_io_t));
void        pine_rfc822_cat PROTO((char *, const char *, gf_io_t));
int	    delineate_this_header PROTO ((char *,char *,char *,char **,
					  char **));
void	    view_writec_init PROTO((STORE_S *, HANDLE_S **, int, int));
void	    view_writec_destroy PROTO((void));
int	    view_writec PROTO((int));
int	    view_end_scroll PROTO((SCROLL_S *));
int	    quote_editorial PROTO((long, char *, LT_INS_S **, void *));
void	    set_scroll_text PROTO((SCROLL_S *, long, SCRLCTRL_S *));
long	    scroll_scroll_text PROTO((long, HANDLE_S *, int));
static int  print_to_printer PROTO((SCROLL_S *));
int	    search_scroll_text PROTO((long, int, char *, Pos *, int *));
char	   *search_scroll_line PROTO((char *, char *, int, int));
void	    describe_mime PROTO((BODY *, char *, int, int));
void	    format_mime_size PROTO((char *, BODY *));
ATTACH_S   *next_attachment PROTO(());
void	    zero_atmts PROTO((ATTACH_S *));
void	    zero_scroll_text PROTO((void));
char	   *body_parameter PROTO((BODY *, char *));
void	    ScrollFile PROTO((long));
long	    make_file_index PROTO(());
char	   *scroll_file_line PROTO((FILE *, char *, SCRLFILE_S *, int *));
char	   *show_multipart PROTO((MESSAGECACHE *, int));
int	    mime_show PROTO((BODY *));
SCRLCTRL_S *scroll_state PROTO((int));
int	    search_text PROTO((int, long, int, char *, Pos *, int *));
void	    format_scroll_text PROTO((void));
void	    redraw_scroll_text PROTO((void));
void	    update_scroll_titlebar PROTO((long, int));
int	    find_field PROTO((char **, char *));
STRINGLIST *new_strlst PROTO((char **));
void	    free_strlst PROTO((STRINGLIST **));
void	    free_handle PROTO((HANDLE_S **));
void	    free_handle_locations PROTO((POSLIST_S **));
int	    scroll_handle_obscured PROTO((HANDLE_S *));
int	    scroll_handle_selectable PROTO((HANDLE_S *));
HANDLE_S   *scroll_handle_next_sel PROTO((HANDLE_S *));
HANDLE_S   *scroll_handle_prev_sel PROTO((HANDLE_S *));
HANDLE_S   *scroll_handle_next PROTO((HANDLE_S *));
HANDLE_S   *scroll_handle_prev PROTO((HANDLE_S *));
HANDLE_S   *scroll_handle_boundary PROTO((HANDLE_S *,
					  HANDLE_S *(*) PROTO((HANDLE_S *))));
int	    scroll_handle_column PROTO((char *, char *));
void	    scroll_handle_set_loc PROTO((POSLIST_S **, int, int));
int	    scroll_handle_prompt PROTO((HANDLE_S *, int));
int	    scroll_handle_launch PROTO((HANDLE_S *, int));
HANDLE_S   *scroll_handle_in_frame PROTO((long));
long	    scroll_handle_reframe PROTO((int, int));
int	    handle_on_line PROTO((long, int));
int	    handle_on_page PROTO((HANDLE_S *, long, long));
int	    dot_on_handle PROTO((long, int));
char	   *url_embed PROTO((int));
int	    url_hilite_hdr PROTO((long, char *, LT_INS_S **, void *));
int	    url_launch PROTO((HANDLE_S *));
int	    url_launch_too_long PROTO((int));
char	   *url_external_handler PROTO((HANDLE_S *, int));
int	    url_local_mailto PROTO((char *));
void	    url_mailto_addr PROTO((ADDRESS **, char *));
int	    url_local_imap PROTO((char *));
int	    url_bogus_imap PROTO((char **, char *, char *));
int	    url_local_nntp PROTO((char *));
int	    url_local_news PROTO((char *));
int	    url_local_phone_home PROTO((char *));
int	    url_bogus PROTO((char *, char *));
#ifdef	_WINDOWS
int	    format_message_popup PROTO((SCROLL_S *, int));
int	    simple_text_popup PROTO((SCROLL_S *, int));
int	    mswin_readscrollbuf PROTO((int));
int	    pcpine_do_scroll PROTO((int, long));
char	   *pcpine_help_scroll PROTO((char *));
int	    pcpine_resize_scroll PROTO((void));
#endif



/*----------------------------------------------------------------------
     Format a buffer with the text of the current message for browser

    Args: ps - pine state structure
  
  Result: The scrolltool is called to display the message

  Loop here viewing mail until the folder changed or a command takes
us to another screen. Inside the loop the message text is fetched and
formatted into a buffer allocated for it.  These are passed to the
scrolltool(), that displays the message and executes commands. It
returns when it's time to display a different message, when we
change folders, when it's time for a different screen, or when
there are no more messages available.
  ---*/

void
mail_view_screen(ps)
     struct pine *ps;
{
    char            last_was_full_header = 0;
    long            last_message_viewed = -1L, raw_msgno;
    OtherMenu       save_what = FirstMenu;
    int             we_cancel = 0, flags;
    MESSAGECACHE   *mc;
    ENVELOPE       *env;
    BODY           *body;
    STORE_S        *store;
    HANDLE_S	   *handles = NULL;
    SCROLL_S	    scrollargs;
    SourceType	    src = CharStar;

    dprint(1, (debugfile, "\n\n  -----  MAIL VIEW  -----\n"));

    ps->prev_screen = mail_view_screen;

    /*----------------- Loop viewing messages ------------------*/
    do {
	/*
	 * Check total to make sure there's something to view.  Check it
	 * inside the loop to make sure everything wasn't expunged while
	 * we were viewing.  If so, make sure we don't just come back.
	 */
	if(mn_get_total(ps->msgmap) <= 0L || !ps->mail_stream){
	    q_status_message(SM_ORDER, 0, 3, "No messages to read!");
	    ps->next_screen = mail_index_screen;
	    break;
	}

	we_cancel = busy_alarm(1, NULL, NULL, 0);

	if(mn_get_cur(ps->msgmap) <= 0L)
	  mn_set_cur(ps->msgmap, 1L);

	raw_msgno = mn_m2raw(ps->msgmap, mn_get_cur(ps->msgmap));
	body      = NULL;
	if(!(env = mail_fetchstructure(ps->mail_stream, raw_msgno, &body))
	   || !(mc = mail_elt(ps->mail_stream, raw_msgno))){
	    q_status_message1(SM_ORDER, 3, 3, "Error getting message %s data",
			      comatose(mn_get_cur(ps->msgmap)));
	    dprint(1, (debugfile, "!!!! ERROR fetching %s of msg %ld\n",
		       env ? "elt" : "env", mn_get_cur(ps->msgmap)));
		       
	    ps->next_screen = mail_index_screen;
	    break;
	}
	else
	  ps->unseen_in_view = !mc->seen;

#if	defined(DOS) && !defined(WIN32)
	/* 
	 * Handle big text for DOS here.
	 *
	 * judging from 1000+ message folders around here, it looks
	 * like 9X% of messages fall in the < 8k range, so it
	 * seems like this is as good a place to draw the line as any.
	 *
	 * We ALSO need to divert all news articles we read to secondary
	 * storage as its possible we're using c-client's NNTP driver
	 * which returns BOGUS sizes UNTIL the whole thing is fetched.
	 * Note: this is more a deficiency in NNTP than in c-client.
	 */
	src = (mc->rfc822_size > MAX_MSG_INCORE 
	       || strcmp(ps->mail_stream->dtb->name, "nntp") == 0)
		? TmpFileStar : CharStar;
#endif
	init_handles(&handles);

	store = so_get(src, NULL, EDIT_ACCESS);
	so_truncate(store, format_size_guess(body) + 2048);

	view_writec_init(store, &handles, SCROLL_LINES_ABOVE(ps),
			 SCROLL_LINES_ABOVE(ps) + 
			 ps->ttyo->screen_rows - (SCROLL_LINES_ABOVE(ps)
						  + SCROLL_LINES_BELOW(ps)));

	flags = FM_HANDLES | FM_DISPLAY;
	if((last_message_viewed != mn_get_cur(ps->msgmap)
	    || last_was_full_header == 1))
	  flags |= FM_NEW_MESS;

	(void) format_message(raw_msgno, env, body, flags, view_writec);

	view_writec_destroy();

	last_message_viewed  = mn_get_cur(ps->msgmap);
	last_was_full_header = ps->full_header;

	ps->next_screen = SCREEN_FUN_NULL;

	memset(&scrollargs, 0, sizeof(SCROLL_S));
	scrollargs.text.text	= so_text(store);
	scrollargs.text.src	= src;
	scrollargs.text.desc	= "message";

	/*
	 * make first selectable handle the default
	 */
	if(handles){
	    HANDLE_S *hp;

	    hp = handles;
	    while(!scroll_handle_selectable(hp) && hp != NULL)
	      hp = hp->next;

	    if(scrollargs.text.handles = hp)
	      scrollargs.body_valid = (hp == handles);
	    else
	      free_handles(&handles);
	}
	else
	  scrollargs.body_valid = 1;

	scrollargs.bar.title	= "MESSAGE TEXT";
	scrollargs.end_scroll	= view_end_scroll;
	scrollargs.resize_exit	= 1;
	scrollargs.help.text	= h_mail_view;
	scrollargs.help.title	= "HELP FOR MESSAGE TEXT VIEW";
	scrollargs.keys.menu	= &view_keymenu;
	scrollargs.keys.what    = save_what;
	setbitmap(scrollargs.keys.bitmap);
#ifndef DOS
	if(F_OFF(F_ENABLE_PIPE, ps_global))
#endif
	  clrbitn(VIEW_PIPE_KEY, scrollargs.keys.bitmap);

	if(F_OFF(F_ENABLE_BOUNCE, ps_global))
	  clrbitn(BOUNCE_KEY, scrollargs.keys.bitmap);

	if(F_OFF(F_ENABLE_FLAG, ps_global))
	  clrbitn(FLAG_KEY, scrollargs.keys.bitmap);

	if(F_OFF(F_ENABLE_AGG_OPS, ps_global))
	  clrbitn(VIEW_SELECT_KEY, scrollargs.keys.bitmap);

	if(F_OFF(F_ENABLE_FULL_HDR, ps_global))
	  clrbitn(VIEW_FULL_HEADERS_KEY, scrollargs.keys.bitmap);

	if(!handles){
	    /*
	     * NOTE: the comment below only really makes sense if we
	     *	     decide to replace the "Attachment Index" with
	     *	     a model that lets you highlight the attachments
	     *	     in the header.  In a way its consistent, but
	     *	     would mean more keymenu monkeybusiness since not
	     *	     all things would be available all the time. No wait.
	     *	     Then what would "Save" mean; the attachment, url or
	     *	     message you're currently viewing?  Would Save
	     *	     of a message then only be possible from the message
	     *	     index?  Clumsy.  what about arrow-navigation.  isn't
	     *	     that now inconsistent?  Maybe next/prev url shouldn't
	     *	     be bound to the arrow/^N/^P navigation?
	     */
	    clrbitn(VIEW_VIEW_HANDLE, scrollargs.keys.bitmap);
	    clrbitn(VIEW_PREV_HANDLE, scrollargs.keys.bitmap);
	    clrbitn(VIEW_NEXT_HANDLE, scrollargs.keys.bitmap);
	}

#ifdef	_WINDOWS
	scrollargs.mouse.popup = format_message_popup;
#endif

	scrolltool(&scrollargs);

	save_what = scrollargs.keys.what;
	ps_global->unseen_in_view = 0;
	so_give(&store);	/* free resources associated with store */
	free_handles(&handles);
#ifdef	_WINDOWS
	mswin_destroyicons();
#endif
    }
    while(ps->next_screen == SCREEN_FUN_NULL);

    if(we_cancel)
      cancel_busy_alarm(-1);
}



/*
 * view_writec_init - function to create and init struct that manages
 *		      writing to the display what we can as soon
 *		      as we can.
 */
void
view_writec_init(store, handles, first_line, last_line)
    STORE_S   *store;
    HANDLE_S **handles;
    int	       first_line;
    int	       last_line;
{
    char tmp[MAILTMPLEN];

    g_view_write = (struct view_write_s *)fs_get(sizeof(struct view_write_s));
    memset(g_view_write, 0, sizeof(struct view_write_s));
    g_view_write->store		   = store;
    g_view_write->handles	   = handles;
    g_view_write->screen_line	   = first_line;
    g_view_write->last_screen_line = last_line;

    if(!df_static_trigger(NULL, tmp)){
	g_view_write->line = (char *) fs_get((2*MAX_SCREEN_COLS)*sizeof(char));
#ifdef _WINDOWS
	mswin_beginupdate();
	scroll_setrange(0L, 0L);
#endif
	ClearLines(first_line, last_line - 1);
    }
}



void
view_writec_destroy()
{
    if(g_view_write){
	while(g_view_write->screen_line <= g_view_write->last_screen_line)
	  ClearLine(g_view_write->screen_line++);

	if(g_view_write->line)
	  fs_give((void **) &g_view_write->line);

	fs_give((void **) &g_view_write);
    }

#ifdef _WINDOWS
    mswin_endupdate();
#endif
}



/*
 * view_screen_pc - write chars into the final buffer
 */
int
view_writec(c)
    int c;
{
    if(g_view_write->line){
	if(c == '\n' || g_view_write->index == (2 * MAX_SCREEN_COLS)){
	    int rv;

	    suspend_busy_alarm();
	    ClearLine(g_view_write->screen_line);
	    PutLine0n8b(g_view_write->screen_line++, 0,
			g_view_write->line, g_view_write->index,
			g_view_write->handles ? *g_view_write->handles : NULL);

	    resume_busy_alarm(0);
	    rv = so_nputs(g_view_write->store,
			  g_view_write->line,
			  g_view_write->index);
	    g_view_write->index = 0;

	    if(g_view_write->screen_line >= g_view_write->last_screen_line){
		fs_give((void **) &g_view_write->line);
		fflush(stdout);
	    }

	    if(!rv)
	      return(0);
	}
	else{
	    g_view_write->line[g_view_write->index++] = (char) c;
	    return(1);
	}
    }
#ifdef	_WINDOWS
    else if(c == '\n' && g_view_write->lines++ > SCROLL_LINES(ps_global))
      scroll_setrange(SCROLL_LINES(ps_global),
		      g_view_write->lines + SCROLL_LINES(ps_global));
#endif

    return(so_writec(c, g_view_write->store));
}


int
view_end_scroll(sparms)
    SCROLL_S *sparms;
{
    int done = 0, result, force;
    
    if(F_ON(F_ENABLE_SPACE_AS_TAB, ps_global)){

	if(F_ON(F_ENABLE_TAB_DELETES, ps_global)){
	    long save_msgno;

	    /* Let the TAB advance cur msgno for us */
	    save_msgno = mn_get_cur(ps_global->msgmap);
	    cmd_delete(ps_global, ps_global->msgmap, 0);
	    mn_set_cur(ps_global->msgmap, save_msgno);
	}

	/* act like the user hit a TAB */
	result = process_cmd(ps_global, ps_global->mail_stream,
			     ps_global->msgmap, MC_TAB, 0, &force);

	if(result == 1)
	  done = 1;
    }

    return(done);
}




/*----------------------------------------------------------------------
    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

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(body, prefix, num, should_show)
    BODY *body;
    char *prefix;
    int   num, should_show;
{
    PART      *part;
    char       numx[512], string[200], *description;
    int        n, named = 0;
    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 ---*/
	    for(part=body->nested.part, n=1; part; part=part->next, n++)
	      if((effort = mime_show(&part->body)) >= best_effort
		 && effort != SHOW_ALL_EXT){
		  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;
	}
	else if(mailcap_can_display(body->type, body->subtype,
				    body->parameter)){
	    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("");

	    sprintf(string, "%s/%.*s", body_type_names(body->type),
		    sizeof(string) - 50, body->subtype);
	    a->description	   = cpystr(string);
	    a->body		   = body;
	    a->can_display	   = MCD_EXTERNAL;
	    (a+1)->description	   = NULL;
	}

	for(part=body->nested.part, n=1; part; part=part->next, n++){
	    sprintf(numx, "%s%d.", prefix, n);
	    describe_mime(&(part->body),
			  (part->body.type == TYPEMULTIPART) ? numx : prefix,
			  n, (n == alt_to_show || !alt_to_show));
	}
    }
    else{
	format_mime_size((a = next_attachment())->size, body);

	description = (body->description)
		        ? (char *) rfc1522_decode((unsigned char *)tmp_20k_buf,
						  body->description, NULL)
			: (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
			   : (body->type == TYPEMESSAGE
			      && body->subtype
			      && !strucmp(body->subtype, "delivery-status"))
				? "Delivery Status"
				: NULL;

        sprintf(string, "%s%s%s%s",
                strsquish(tmp_20k_buf+1000,
			  type_desc(body->type,body->subtype,body->parameter,0),
			  sizeof(string)/2 - 10),
                description ? ", \"" : "",
                description ? strsquish(tmp_20k_buf+1500, description,
					sizeof(string)/2 - 10)
			    : "",
                description ? "\"": "");

        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(value = body_parameter(body, "name")){
		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)){
	    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->parameter);
	}

	/*
	 * Deferred means we can display it
	 */
	a->shown = ((a->can_display & MCD_INTERNAL)
		    && !MIME_VCARD(body->type,body->subtype)
		    && (!named
			|| (body->type == TYPETEXT && num == 1
			    && !(*prefix && strcmp(prefix,"1."))))
		    && body->encoding <= ((body->type == TYPEMESSAGE)
					   ? ENCBINARY : ENCQUOTEDPRINTABLE)
		    && should_show);
	a->number = (char *)fs_get((strlen(prefix) + 16)* sizeof(char));
        sprintf(a->number, "%s%d",prefix, num);
        (a+1)->description = NULL;
        if(body->type == TYPEMESSAGE && body->encoding <= ENCBINARY
	   && body->subtype && strucmp(body->subtype, "rfc822") == 0){
	    body = body->nested.msg->body;
	    sprintf(numx, "%s%d.", prefix, num);
	    describe_mime(body, numx, 1, should_show);
        }
    }
}


char *
body_parameter(body, attribute)
    BODY *body;
    char *attribute;
{
    return(rfc2231_get_param(body->parameter, attribute, NULL, NULL));
}



/*----------------------------------------------------------------------
  Return a pointer to the next attachment struct
    
  Args: none

  ----*/
ATTACH_S *
next_attachment()
{
    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(atmts)
     ATTACH_S *atmts;
{
    ATTACH_S *a;

    for(a = atmts; a->description != NULL; a++){
	fs_give((void **)&(a->description));
	fs_give((void **)&(a->number));
    }

    atmts->description = NULL;
}


char *
body_type_names(t)
    int t;
{
#define TLEN 31
    static char body_type[TLEN + 1];
    char   *p;

    strncpy(body_type,				/* copy the given type */
	    (t > -1 && t < TYPEMAX && body_types[t])
	      ? body_types[t] : "Other", TLEN);
    body_type[TLEN] = '\0';

    for(p = body_type + 1; *p; p++)		/* make it presentable */
      if(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"},
    {"ISO-8859-2",		"Latin 2"},
    {"ISO-8859-3",		"Latin 3"},
    {"ISO-8859-4",		"Latin 4"},
    {"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"},
    {"X-ISO-8859-10",		"Latin 6"},
    {"KOI8-R",			"Latin & Russian"},
    {"VISCII",			"Latin & Vietnamese"},
    {"ISO-2022-JP",		"Latin & Japanese"},
    {"ISO-2022-KR",		"Latin & Korean"},
    {"UNICODE-1-1",		"Unicode"},
    {"UNICODE-1-1-UTF-7",	"Mail-safe Unicode"},
    {"ISO-2022-JP-2",		"Multilingual"},
    {NULL,			NULL}
};


/*----------------------------------------------------------------------
  Return a nicely formatted discription of the type of the part
 ----*/

char *
type_desc(type, subtype, params, full)
     int type, full;
     char *subtype;
     PARAMETER *params;
{
    static char  type_d[200];
    int		 i;
    char	*p, *parmval;

    p = type_d;
    sstrcpy(&p, body_type_names(type));
    if(full && subtype){
	*p++ = '/';
	sstrcpy(&p, strsquish(tmp_20k_buf + 500, subtype, 30));
    }

    switch(type) {
      case TYPETEXT:
	parmval = rfc2231_get_param(params, "charset", NULL, NULL);

        if(parmval){
	    for(i = 0; charset_names[i].rfcname; i++)
	      if(!strucmp(parmval, charset_names[i].rfcname)){
		  if(!strucmp(parmval, ps_global->VAR_CHAR_SET
			      ? ps_global->VAR_CHAR_SET : "us-ascii")
		     || !strucmp(parmval, "us-ascii"))
		    i = -1;

		  break;
	      }

	    if(i >= 0){			/* charset to write */
		sstrcpy(&p, " (charset: ");
		sstrcpy(&p, charset_names[i].rfcname
			      ? charset_names[i].rfcname : "Unknown");
		if(full){
		    sstrcpy(&p, " \"");
		    sstrcpy(&p, charset_names[i].humanname
			    ? charset_names[i].humanname
			    : strsquish(tmp_20k_buf + 500, parmval, 40));
		    *p++ = '\"';
		}

		sstrcpy(&p, ")");
	    }

	    fs_give((void **) &parmval);
        }

	if(full && (parmval = rfc2231_get_param(params, "name", NULL, NULL))){
	    sprintf(p, " (Name: \"%s\")",
		    strsquish(tmp_20k_buf + 500, parmval, 80));
	    fs_give((void **) &parmval);
	}

        break;

      case TYPEAPPLICATION:
        if(full && subtype && strucmp(subtype, "octet-stream") == 0)
	  if(parmval = rfc2231_get_param(params, "name", NULL, NULL)){
	      sprintf(p, " (Name: \"%s\")",
		      strsquish(tmp_20k_buf + 500, parmval, 80));
	      fs_give((void **) &parmval);
	  }

        break;

      case TYPEMESSAGE:
	if(full && subtype && strucmp(subtype, "external-body") == 0)
	  if(parmval = rfc2231_get_param(params, "access-type", NULL, NULL)){
	      sprintf(p, " (%s%s)", full ? "Access: " : "",
		      strsquish(tmp_20k_buf + 500, parmval, 80));
	      fs_give((void **) &parmval);
	  }

	break;

      default:
        break;
    }

    return(type_d);
}
     

void
format_mime_size(string, b)
     char *string;
     BODY *b;
{
    char tmp[10], *p = NULL;

    *string++ = ' ';
    switch(b->encoding){
      case ENCBASE64 :
	if(b->type == TYPETEXT)
	  *(string-1) = '~';

	strcpy(p = string, byte_string((3 * b->size.bytes) / 4));
	break;

      default :
      case ENCQUOTEDPRINTABLE :
	*(string-1) = '~';

      case ENC8BIT :
      case ENC7BIT :
	if(b->type == TYPETEXT)
	  sprintf(string, "%s lines", comatose(b->size.lines));
	else
	  strcpy(p = string, byte_string(b->size.bytes));

	break;
    }


    if(p){
	for(; *p && (isdigit((unsigned char)*p)
		     || ispunct((unsigned char)*p)); p++)
	  ;

	sprintf(tmp, " %-5.5s",p);
	strcpy(p, tmp);
    }
}

        

/*----------------------------------------------------------------------
   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(body)
     BODY *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->parameter);
	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);
    }
}



/*
 * format_size_guess -- Run down the given body summing the text/plain
 *			pieces we're likely to display.  It need only
 *			be a guess since this is intended for preallocating
 *			our display buffer...
 */
long
format_size_guess(body)
    BODY *body;
{
    long  size = 0L;
    char *free_me = NULL;

    if(body){
	if(body->type == TYPEMULTIPART){
	    PART *part;

	    for(part = body->nested.part; part; part = part->next)
	      size += format_size_guess(&part->body);
	}
	else if(body->type == TYPEMESSAGE
		&& body->subtype && !strucmp(body->subtype, "rfc822"))
	  size = format_size_guess(body->nested.msg->body);
	else if((!body->type || body->type == TYPETEXT)
		&& (!body->subtype || !strucmp(body->subtype, "plain"))
		&& ((body->disposition.type
		     && !strucmp(body->disposition.type, "inline"))
		    || !(free_me = body_parameter(body, "name"))))
	  size = body->size.bytes;

	if(free_me)
	  fs_give((void **) &free_me);
    }

    return(size);
}
        


/*----------------------------------------------------------------------
   Format a message message for viewing

 Args: msgno -- The number of the message to view
       env   -- pointer to the message's envelope
       body  -- pointer to the message's body
       flgs  -- possible flags:
		FM_DISPLAY -- Indicates formatted text is bound for
			      the display (as opposed to the printer,
			      for export, etc)
                FM_NEW_MESS -- flag indicating a different message being
			       formatted than was formatted last time 
			       function was called

Result: Returns true if no problems encountered, else false.

First the envelope is formatted; next a list of all attachments is
formatted if there is more than one. Then all the body parts are
formatted, fetching them as needed. This includes headers of included
message. Richtext is also formatted. An entry is made in the text for
parts that are not displayed or can't be displayed.  source indicates 
how and where the caller would like to have the text formatted.

 ----*/    
int
format_message(msgno, env, body, flgs, pc)
    long         msgno;
    ENVELOPE    *env;
    BODY        *body;
    int          flgs;
    gf_io_t      pc;
{
    char     *decode_err = NULL;
    HEADER_S  h;
    ATTACH_S *a;
    int       show_parts, error_found = 0, seen = 1;
    gf_io_t   gc;
    MESSAGECACHE *mc;


    mc = mail_elt(ps_global->mail_stream, msgno);
    if(mc && !mc->seen)
      seen = 0;

    /*---- format and copy envelope ----*/
    if(ps_global->full_header)
      q_status_message(SM_INFO, 0, 3,
		       "Full header mode ON.  All header text being included");

    HD_INIT(&h, ps_global->VAR_VIEW_HEADERS, ps_global->view_all_except,
	    FE_DEFAULT);
    switch(format_header(ps_global->mail_stream, msgno, NULL,
			 env, &h, NULL, flgs, pc)){
			      
      case -1 :			/* write error */
	goto write_error;

      case 1 :			/* fetch error */
	if(!(gf_puts("[ Error fetching header ]",  pc)
	     && !gf_puts(NEWLINE, pc)))
	  goto write_error;

	break;
    }

    if(!seen)
      check_point_change();

    if(body == NULL) {
        /*--- Server is not an IMAP2bis, It can't parse MIME
              so we just show the text here. Hopefully the 
              message isn't a MIME message 
          ---*/
	void *text2;
#if	defined(DOS) && !defined(WIN32)
	char *append_file_name;

	/* for now, always fetch to disk.  This could be tuned to
	 * check for message size, then decide to deal with it on disk...
	 */
	mail_parameters(ps_global->mail_stream, SET_GETS, (void *)dos_gets);
	if(!(append_file_name = temp_nam(NULL, "pv"))
	   || !(append_file = fopen(append_file_name, "w+b"))){
	    if(append_file_name)
	      fs_give((void **)&append_file_name);

	    q_status_message1(SM_ORDER,3,3,"Can't make temp file: %s",
			      error_description(errno));
	    return(0);
	}
#endif

        if(text2 = (void *)mail_fetchtext(ps_global->mail_stream, msgno)){
 	    if(!gf_puts(NEWLINE, pc))		/* write delimiter */
	      goto write_error;
#if	defined(DOS) && !defined(WIN32)
	    gf_set_readc(&gc, append_file, 0L, FileStar);
#else
	    gf_set_readc(&gc, text2, (unsigned long)strlen(text2), CharStar);
#endif
	    gf_filter_init();
	    gf_link_filter(gf_nvtnl_local, NULL);
	    if(decode_err = gf_pipe(gc, pc)){
                sprintf(tmp_20k_buf, "Formatting error: %s", decode_err);
		if(gf_puts(NEWLINE, pc) && gf_puts(NEWLINE, pc)
		   && !format_editorial(tmp_20k_buf,
					(flgs & FM_DISPLAY)
					  ? ps_global->ttyo->screen_cols : 80,
					pc)
		   && gf_puts(NEWLINE, pc))
		  decode_err = NULL;
		else
		  goto write_error;
	    }
	}

#if	defined(DOS) && !defined(WIN32)
	fclose(append_file);			/* clean up tmp file */
	append_file = NULL;
	unlink(append_file_name);
	fs_give((void **)&append_file_name);
	mail_gc(ps_global->mail_stream, GC_TEXTS);
	mail_parameters(ps_global->mail_stream, SET_GETS, (void *)NULL);
#endif

	if(!text2){
	    if(!gf_puts(NEWLINE, pc)
	       || !gf_puts("    [ERROR fetching text of message]", pc)
	       || !gf_puts(NEWLINE, pc)
	       || !gf_puts(NEWLINE, pc))
	      goto write_error;
	}

	return(1);
    }

    if(flgs & FM_NEW_MESS) {
	zero_atmts(ps_global->atmts);
	describe_mime(body, "", 1, 1);
    }

    /*=========== Format the header into the buffer =========*/
    /*----- First do the list of parts/attachments if needed ----*/
    if((flgs & FM_DISPLAY) && ps_global->atmts[1].description){
	char tmp[MAX_SCREEN_COLS + 1];
	int  n, max_num_l = 0, size_offset = 0;

	if(!(gf_puts("Parts/Attachments:", pc) && gf_puts(NEWLINE, pc)))
	  goto write_error;

	/*----- Figure max display lengths -----*/
        for(a = ps_global->atmts; a->description != NULL; a++) {
	    if((n = strlen(a->number)) > max_num_l)
	      max_num_l = n;

	    if((n = strlen(a->size)) > size_offset)
	      size_offset = n;
	}

	/*----- adjust max lengths for nice display -----*/
	max_num_l = min(max_num_l, (ps_global->ttyo->screen_cols/3));
	size_offset   += max_num_l + 12;

	/*----- Format the list of attachments -----*/
	for(a = ps_global->atmts; a->description != NULL; a++) {
	    memset(tmp, ' ', MAX_SCREEN_COLS);

	    if(msgno_part_deleted(ps_global->mail_stream, msgno, a->number))
	       tmp[1] = 'D';

	    if((n = strlen(a->number)) > max_num_l){
		strcpy(tmp + 3, "...");
		strncpy(tmp + 6, &a->number[n - (max_num_l - 3)],
			max_num_l - 3);
	    }
	    else
	      strncpy(tmp + 3, a->number, n);

	    if(a->shown)
	      strncpy(tmp + max_num_l + 4, "Shown", 5);
	    else if(a->can_display != MCD_NONE)
	      strncpy(tmp + max_num_l + 4, "  OK ", 5);

	    if(n = strlen(a->size))
	      strncpy(tmp + size_offset - n, a->size, n);

	    if((n = ps_global->ttyo->screen_cols - (size_offset + 4)) > 0)
	      strncpy(tmp + size_offset + 2, a->description, n);

	    tmp[ps_global->ttyo->screen_cols] = '\0';

	    if(F_ON(F_VIEW_SEL_ATTACH, ps_global) && (flgs & FM_HANDLES)){
		char      buf[16];
		HANDLE_S *h;

		h	    = new_handle();
		h->type	    = Attach;
		h->h.attach = a;

		sprintf(buf, "%d", h->key);

		if(!((*pc)(TAG_EMBED) && (*pc)(TAG_HANDLE)
		     && (*pc)(strlen(buf)) && gf_puts(buf, pc)
		     && (*pc)(TAG_EMBED) && (*pc)(TAG_BOLDON)))
		  goto write_error;
	    }

	    if(!(format_env_puts(tmp, pc)
		 && ((F_ON(F_VIEW_SEL_ATTACH, ps_global)
		      && (flgs & FM_HANDLES))
		      ? ((*pc)(TAG_EMBED) && (*pc)(TAG_BOLDOFF)
			 && (*pc)(TAG_EMBED) && (*pc)(TAG_INVOFF))
		      : 1)
		 && gf_puts(NEWLINE, pc)))
	      goto write_error;
        }

	if(!gf_puts("----------------------------------------", pc)
	   || !gf_puts(NEWLINE, pc))
	  goto write_error;
    }

    if(!gf_puts(NEWLINE, pc))		/* write delimiter */
      goto write_error;

    show_parts = 0;

    /*======== Now loop through formatting all the parts =======*/
    for(a = ps_global->atmts; a->description != NULL; a++) {

	if(a->body->type == TYPEMULTIPART)
	  continue;

        if(!a->shown) {
	    if((decode_err = part_desc(a->number, a->body,
				       (flgs & FM_DISPLAY)
				         ? (a->can_display != MCD_NONE)
					     ? 1 : 2
				         : 3,
				       (flgs & FM_DISPLAY)
				         ? ps_global->ttyo->screen_cols : 80,
				       pc))
	       || !gf_puts(NEWLINE, pc))
	      goto write_error;

            continue;
        } 

        switch(a->body->type){

          case TYPETEXT:
	    /*
	     * If a message is multipart *and* the first part of it
	     * is text *and that text is empty, there is a good chance that
	     * there was actually something there that c-client was
	     * unable to parse.  Here we report the empty message body
	     * and insert the raw RFC822.TEXT (if full-headers are
	     * on).
	     */
	    if(body->type == TYPEMULTIPART
	       && a == ps_global->atmts
	       && a->body->size.bytes == 0
	       && F_ON(F_ENABLE_FULL_HDR, ps_global)){
		char *err = NULL;

		sprintf(tmp_20k_buf, 
			"Empty message possibly malformed%s.",
			ps_global->full_header 
			    ? ". Displaying raw text"
			    : ". Use \"H\" to see raw text");

		if(!(gf_puts(NEWLINE, pc)
		     && !format_editorial(tmp_20k_buf,
					  (flgs & FM_DISPLAY)
					   ? ps_global->ttyo->screen_cols : 80,
					  pc)
		     && gf_puts(NEWLINE, pc) && gf_puts(NEWLINE, pc)))
		  goto write_error;

		if(ps_global->full_header
		   && (err = detach_raw(ps_global->mail_stream, msgno,
					a->number, pc, flgs))){
		    sprintf(tmp_20k_buf, 
			    "%s  [ Formatting error: %s ]%s%s",
			    NEWLINE, err, NEWLINE, NEWLINE);
		    if(!gf_puts(tmp_20k_buf, pc))
		      goto write_error;
		}

		break;
	    }

	    /*
	     * Don't write our delimiter if this text part is
	     * the first part of a message/rfc822 segment...
	     */
	    if(show_parts && a != ps_global->atmts 
	       && a[-1].body && a[-1].body->type != TYPEMESSAGE){
		sprintf(tmp_20k_buf, "Part %s: \"%.1024s\"",
			a->number, 
			a->body->description ? a->body->description
					     : "Attached Text");
		if(!(gf_puts(NEWLINE, pc)
		     && !format_editorial(tmp_20k_buf,
					  (flgs & FM_DISPLAY)
					   ? ps_global->ttyo->screen_cols : 80,
					  pc)
		     && gf_puts(NEWLINE, pc) && gf_puts(NEWLINE, pc)))
		  goto write_error;
	    }

	    error_found += decode_text(a, msgno, pc,
				       (flgs & FM_DISPLAY) ? InLine : QStatus,
				       flgs);
            break;

          case TYPEMESSAGE:
            sprintf(tmp_20k_buf, "Part %s: \"%.1024s\"",
		    a->number, 
                    a->body->description ? a->body->description
		      : (strucmp(a->body->subtype, "delivery-status") == 0)
		          ? "Delivery Status"
			  : "Included Message");

	    if(!(gf_puts(NEWLINE, pc)
		 && !format_editorial(tmp_20k_buf,
				      ps_global->ttyo->screen_cols, pc)
		 && gf_puts(NEWLINE, pc) && gf_puts(NEWLINE, pc)))
	      goto write_error;

	    if(a->body->subtype && strucmp(a->body->subtype, "rfc822") == 0){
		switch(format_header(ps_global->mail_stream, msgno, a->number,
				     a->body->nested.msg->env, &h,
				     NULL, flgs, pc)){
		  case -1 :			/* write error */
		    goto write_error;

		  case 1 :			/* fetch error */
		    if(!(gf_puts("[ Error fetching header ]",  pc)
			 && !gf_puts(NEWLINE, pc)))
		      goto write_error;

		    break;
		}
	    }
            else if(a->body->subtype 
		    && strucmp(a->body->subtype, "external-body") == 0) {
		if(!gf_puts("This part is not included and can be ", pc)
		   || !gf_puts("fetched as follows:", pc)
		   || !gf_puts(NEWLINE, pc)
		   || !gf_puts(display_parameters(a->body->parameter), pc))
		  goto write_error;
            }
	    else
	      error_found += decode_text(a, msgno, pc, 
					 (flgs&FM_DISPLAY) ? InLine : QStatus,
					 flgs);

	    if(!gf_puts(NEWLINE, pc))
	      goto write_error;

            break;

          default:
	    if(decode_err = part_desc(a->number, a->body,
				      (flgs & FM_DISPLAY) ? 1 : 3,
				      (flgs & FM_DISPLAY)
				        ? ps_global->ttyo->screen_cols : 80,
				      pc))
	      goto write_error;
        }

	show_parts++;
    }

    return(!error_found);

  write_error:

    if(!(flgs & FM_DISPLAY))
      q_status_message1(SM_ORDER, 3, 4, "Error writing message: %s",
			decode_err ? decode_err : error_description(errno));

    return(0);
}


char *
format_editorial(s, width, pc)
    char    *s;
    int	     width;
    gf_io_t  pc;
{
    int	    (*f) PROTO((long, char *, LT_INS_S **));
    gf_io_t   gc;
 
    gf_set_readc(&gc, s, strlen(s), CharStar);

    if(width > 40){
	width -= 12;
	g_editorial_prefix  = "    [ ";
	g_editorial_postfix = " ]";
    }
    else if(width > 20){
	width -= 6;
	g_editorial_prefix  = " [ ";
	g_editorial_postfix = " ]";
    }
    else{
	width -= 2;
	g_editorial_prefix  = "[";
	g_editorial_postfix = "]";
    }

    gf_filter_init();

    if(F_OFF(F_PASS_CONTROL_CHARS, ps_global)){
	gf_link_filter(gf_escape_filter, NULL);
	gf_link_filter(gf_control_filter, NULL);
    }

    gf_link_filter(gf_wrap, gf_wrap_filter_opt(width, width, 0, 0));
    gf_link_filter(gf_line_test, gf_line_test_opt(quote_editorial, NULL));
    return(gf_pipe(gc, pc));
}


int
quote_editorial(linenum, line, ins, local)
    long       linenum;
    char      *line;
    LT_INS_S **ins;
    void      *local;
{
    register char *lp, *urlp;
    int		   n;
    HANDLE_S	  *h;

    ins = gf_line_test_new_ins(ins, line, g_editorial_prefix,
			       strlen(g_editorial_prefix));
    ins = gf_line_test_new_ins(ins, line + strlen(line),
			       g_editorial_postfix,
			       strlen(g_editorial_postfix));
    return(0);
}



/*
 *  This is a list of header fields that are represented canonically
 *  by the c-client's ENVELOPE structure.  The list is used by the
 *  two functions below to decide if a given field is included in this
 *  set.
 */
static struct envelope_s {
    char *name;
    long val;
} envelope_hdrs[] = {
    {"from",		FE_FROM},
    {"sender",		FE_SENDER},
    {"date",		FE_DATE},
    {"to",		FE_TO},
    {"cc",		FE_CC},
    {"bcc",		FE_BCC},
    {"newsgroups",	FE_NEWSGROUPS},
    {"subject",		FE_SUBJECT},
    {"message-id",	FE_MESSAGEID},
    {"reply-to",	FE_REPLYTO},
    {"followup-to",	FE_FOLLOWUPTO},
    {"in-reply-to",	FE_INREPLYTO},
    {"return-path",	FE_RETURNPATH},
    {"references",	FE_REFERENCES},
    {NULL,		0}
};



/*
 * is_an_env_hdr - is this name a header in the envelope structure?
 *
 *             name - the header name to check
 */
int
is_an_env_hdr(name)
    char *name;
{
    register int i;

    for(i = 0; envelope_hdrs[i].name; i++)
      if(!strucmp(name, envelope_hdrs[i].name))
	return(1);

    return(0);
}



/*
 * Format a single field from the envelope
 */
void
format_env_hdr(stream, msgno, section, env, pc, field, prefix)
    MAILSTREAM *stream;
    long	msgno;
    char       *section;
    ENVELOPE   *env;
    gf_io_t	pc;
    char       *field;
    char       *prefix;
{
    register int i;

    for(i = 0; envelope_hdrs[i].name; i++)
      if(!strucmp(field, envelope_hdrs[i].name)){
	  format_envelope(stream, msgno, section, env, pc,
			  envelope_hdrs[i].val, prefix);
	  return;
      }
}


/*
 * Look through header string "headers", beginning with "begin", for the next
 * occurrence of header "field".  Set "start" to that.  Set "end" to point one
 * position past all of the continuation lines that go with "field".
 * That is, if "end" is converted to a null
 * character then the string "start" will be the next occurence of header
 * "field" including all of its continuation lines.  "Headers" is assumed to
 * have CRLF's as end of lines.
 *
 * If "field" is NULL, then we just leave "start" pointing to "begin" and
 * make "end" the end of that header.
 *
 * Returns 1 if found, 0 if not.
 */
int
delineate_this_header(headers, field, begin, start, end)
    char  *headers, *field, *begin;
    char **start, **end;
{
    char tmpfield[MAILTMPLEN+2]; /* copy of field with colon appended */
    char *p;
    char *begin_srch;

    if(field == NULL){
	if(!begin || !*begin || isspace((unsigned char)*begin))
	  return 0;
	else
	  *start = begin;
    }
    else{
	strncpy(tmpfield, field, MAILTMPLEN);
	tmpfield[MAILTMPLEN] = '\0';
	strcat(tmpfield, ":");

	/*
	 * We require that start is at the beginning of a line, so
	 * either it equals begin (which we assume is the beginning of a
	 * line) or it is preceded by a CRLF.
	 */
	begin_srch = begin;
	*start = srchstr(begin_srch, tmpfield);
	while(*start && *start != begin
	             && !(*start - 2 >= begin && ISRFCEOL(*start - 2))){
	    begin_srch = *start + 1;
	    *start = srchstr(begin_srch, tmpfield);
	}

	if(!*start)
	  return 0;
    }

    for(p = *start; *p; p++){
	if(ISRFCEOL(p)
	   && (!isspace((unsigned char)*(p+2)) || *(p+2) == '\015')){
	    /*
	     * The final 015 in the test above is to test for the end
	     * of the headers.
	     */
	    *end = p+2;
	    break;
	}
    }

    if(!*p)
      *end = p;
    
    return 1;
}



HANDLE_S *
get_handle(handles, key)
    HANDLE_S *handles;
    int key;
{
    HANDLE_S *h;

    if(h = handles){
	for( ; h ; h = h->next)
	  if(h->key == key)
	    return(h);

	for(h = handles->prev ; h ; h = h->prev)
	  if(h->key == key)
	    return(h);
    }

    return(NULL);
}


void
init_handles(handles)
    HANDLE_S **handles;
{
    *(g_handle_p = handles) = NULL;
    (void) url_external_specific_handler(NULL, 0);
}



HANDLE_S *
new_handle()
{
    HANDLE_S *hp, *h = NULL;

    if(g_handle_p){
	h = (HANDLE_S *) fs_get(sizeof(HANDLE_S));
	memset(h, 0, sizeof(HANDLE_S));

	/* Put it in the list */
	if(hp = *g_handle_p){
	    while(hp->next)
	      hp = hp->next;

	    h->key = hp->key + 1;
	    hp->next = h;
	    h->prev = hp;
	}
	else{
	    /* Assumption #2,340: There are NO ZERO KEY HANDLES */
	    h->key = 1;
	    *g_handle_p = h;
	}
    }

    return(h);
}


void
free_handle(h)
    HANDLE_S **h;
{
    if(h){
	if((*h)->next)				/* clip from list */
	  (*h)->next->prev = (*h)->prev;

	if((*h)->prev)
	  (*h)->prev->next = (*h)->next;

	if((*h)->type == URL){			/* destroy malloc'd data */
	    if((*h)->h.url.path)
	      fs_give((void **) &(*h)->h.url.path);

	   if((*h)->h.url.tool)
	     fs_give((void **) &(*h)->h.url.tool);

	    if((*h)->h.url.name)
	      fs_give((void **) &(*h)->h.url.name);
	}

	free_handle_locations(&(*h)->loc);

	fs_give((void **) h);
    }
}


void
free_handles(handles)
    HANDLE_S **handles;
{
    HANDLE_S *h;

    if(*handles){
	while(h = (*handles)->next)
	  free_handle(&h);

	while(h = (*handles)->prev)
	  free_handle(&h);

	free_handle(handles);
    }
}


void
free_handle_locations(l)
    POSLIST_S **l;
{
    if(*l){
	free_handle_locations(&(*l)->next);
	fs_give((void **) l);
    }
}


int
scroll_handle_prompt(handle, force)
    HANDLE_S *handle;
    int	      force;
{
    char prompt[256], tmp[MAILTMPLEN];
    int  rc, flags, local_h;
    static ESCKEY_S launch_opts[] = {
	{'y', 'y', "Y", "Yes"},
	{'n', 'n', "N", "No"},
	{-2, 0, NULL, NULL},
	{-2, 0, NULL, NULL},
	{0, 'u', "U", "editURL"},
	{0, 'a', "A", "editApp"},
	{-1, 0, NULL, NULL}};

    if(handle->type == URL){
	launch_opts[4].ch = 'u';

	if(!(local_h = !struncmp(handle->h.url.path, "x-pine-", 7))
	   && (handle->h.url.tool
	       || ((local_h = url_local_handler(handle->h.url.path) != NULL)
		   && (handle->h.url.tool = url_external_handler(handle,1)))
	       || (!local_h
		   && (handle->h.url.tool = url_external_handler(handle,0))))){
#ifdef	_WINDOWS
	    /* if NOT special DDE hack */
	    if(handle->h.url.tool[0] != '*')
#endif
	    if(ps_global->vars[V_BROWSER].is_fixed)
	      launch_opts[5].ch = -1;
	    else
	      launch_opts[5].ch = 'a';
	}
	else{
	    launch_opts[5].ch = -1;
	    if(!local_h){
	      if(ps_global->vars[V_BROWSER].is_fixed){
		  q_status_message(SM_ORDER, 3, 4,
				   "URL-Viewer is disabled by sys-admin");
		  return(0);
	      }
	      else{
		if(want_to("No URL-Viewer application defined.  Define now",
			   'y', 0, NO_HELP, WT_SEQ_SENSITIVE) == 'y'){
		    /* Prompt for the displayer? */
		    tmp[0] = '\0';
		    while(1){
			flags = OE_APPEND_CURRENT |
				OE_SEQ_SENSITIVE |
				OE_KEEP_TRAILING_SPACE;

			rc = optionally_enter(tmp, -FOOTER_ROWS(ps_global), 0,
					      MAILTMPLEN - 1,
					      "Web Browser: ",
					      NULL, NO_HELP, &flags);
			if(rc == 0){
			    if((flags & OE_USER_MODIFIED) && *tmp){
				if(can_access(tmp, EXECUTE_ACCESS) == 0){
				    int	   n;
				    char **l;

				    /*
				     * Save it for next time...
				     */
				    for(l = ps_global->VAR_BROWSER, n = 0;
					l && *l;
					l++)
				      n++; /* count */

				    l = (char **) fs_get((n+2)*sizeof(char *));
				    for(n = 0;
					ps_global->VAR_BROWSER
					  && ps_global->VAR_BROWSER[n];
					n++)
				      l[n] = cpystr(ps_global->VAR_BROWSER[n]);

				    l[n++] = cpystr(tmp);
				    l[n]   = NULL;

				    set_variable_list(V_BROWSER, l, TRUE);

				    handle->h.url.tool = cpystr(tmp);
				    break;
				}
				else{
				    q_status_message1(SM_ORDER | SM_DING, 2, 2,
						     "Browser not found: %s",
						     error_description(errno));
				    continue;
				}
			    }
			    else
			      return(0);
			}
			else if(rc == 1 || rc == -1){
			    return(0);
			}
			else if(rc == 4){
			    if(ps_global->redrawer)
			      (*ps_global->redrawer)();
			}
		    }
		}
		else
		  return(0);
	      }
	    }
	}
    }
    else
      launch_opts[4].ch = -1;

    if(force)
      return(1);

    while(1){
	sprintf(prompt, "View selected %s %s%.37s%s? ",
		(handle->type == URL) ? "URL" : "Attachment",
		(handle->type == URL) ? "\"" : "",
		(handle->type == URL) ? handle->h.url.path : "",
		(handle->type == URL)
		  ? ((strlen(handle->h.url.path) > 40) ? "...\"" : "\"") : "");

	switch(radio_buttons(prompt, -FOOTER_ROWS(ps_global),
			     launch_opts, 'y', 0, NO_HELP, RB_SEQ_SENSITIVE)){
	  case 'y' :
	    return(1);

	  case 'u' :
	    strncpy(tmp, handle->h.url.path, MAILTMPLEN - 1);
	    tmp[MAILTMPLEN - 1] = '\0';
	    while(1){
		flags = OE_APPEND_CURRENT |
			OE_SEQ_SENSITIVE |
			OE_KEEP_TRAILING_SPACE;

		rc = optionally_enter(tmp, -FOOTER_ROWS(ps_global), 0,
				      MAILTMPLEN - 1, "Edit URL: ",
				      NULL, NO_HELP, &flags);
		if(rc == 0){
		    if(flags & OE_USER_MODIFIED){
			if(handle->h.url.path)
			  fs_give((void **) &handle->h.url.path);

			handle->h.url.path = cpystr(tmp);
		    }

		    break;
		}
		else if(rc == 1 || rc == -1){
		    return(0);
		}
		else if(rc == 4){
		    if(ps_global->redrawer)
		      (*ps_global->redrawer)();
		}
	    }

	    continue;

	  case 'a' :
	    if(handle->h.url.tool){
		strncpy(tmp, handle->h.url.tool, MAILTMPLEN - 1);
		tmp[MAILTMPLEN - 1] = '\0';
	    }
	    else
	      tmp[0] = '\0';

	    while(1){
		flags = OE_APPEND_CURRENT |
			OE_SEQ_SENSITIVE |
			OE_KEEP_TRAILING_SPACE |
			OE_DISALLOW_HELP;

		sprintf(prompt, "Viewer command: ");

		rc = optionally_enter(tmp, -FOOTER_ROWS(ps_global), 0,
				      MAILTMPLEN - 1, "Viewer Command: ",
				      NULL, NO_HELP, &flags);
		if(rc == 0){
		    if(flags & OE_USER_MODIFIED){
			if(handle->h.url.tool)
			  fs_give((void **) &handle->h.url.tool);

			handle->h.url.tool = cpystr(tmp);
		    }

		    break;
		}
		else if(rc == 1 || rc == -1){
		    return(0);
		}
		else if(rc == 4){
		    if(ps_global->redrawer)
		      (*ps_global->redrawer)();
		}
	    }

	    continue;

	  case 'n' :
	  default :
	    return(0);
	}
    }
}



int
scroll_handle_launch(handle, force)
    HANDLE_S *handle;
    int	      force;
{
    switch(handle->type){
      case URL :
	if(handle->h.url.path){
	    if(scroll_handle_prompt(handle, force)){
		if(url_launch(handle)
		   || ps_global->next_screen != SCREEN_FUN_NULL)
		  return(1);	/* done with this screen */
	    }
	    else
	      return(-1);
	}

      break;

      case Attach :
	if(scroll_handle_prompt(handle, force))
	  display_attachment(mn_m2raw(ps_global->msgmap,
				      mn_get_cur(ps_global->msgmap)),
			     handle->h.attach, DA_FROM_VIEW);
	else
	  return(-1);

	break;

      case Folder :
	break;

      default :
	panic("Unexpected HANDLE type");
    }

    return(0);
}



int
scroll_handle_obscured(handle)
    HANDLE_S *handle;
{
    SCRLCTRL_S *st = scroll_state(SS_CUR);

    return(handle_on_page(handle, st->top_text_line,
			  st->top_text_line + st->screen.length));
}



/*
 * scroll_handle_in_frame -- return handle pointer to visible handle.
 */
HANDLE_S *
scroll_handle_in_frame(top_line)
    long top_line;
{
    SCRLCTRL_S *st = scroll_state(SS_CUR);
    HANDLE_S   *hp;

    switch(handle_on_page(hp = st->parms->text.handles, top_line,
			  top_line + st->screen.length)){
      case -1 :			/* handle above page */
	/* Find first handle from top of page */
	for(hp = st->parms->text.handles->next; hp; hp = hp->next)
	  if(scroll_handle_selectable(hp))
	    switch(handle_on_page(hp, top_line, top_line + st->screen.length)){
	      case  0 : return(hp);
	      case  1 : return(NULL);
	      case -1 : default : break;
	    }

	break;

      case 1 :			/* handle below page */
	/* Find first handle from top of page */
	for(hp = st->parms->text.handles->prev; hp; hp = hp->prev)
	  if(scroll_handle_selectable(hp))
	    switch(handle_on_page(hp, top_line, top_line + st->screen.length)){
	      case  0 : return(hp);
	      case -1 : return(NULL);
	      case  1 : default : break;
	    }

	break;

      case  0 :
      default :
	break;
    }

    return(hp);
}



/*
 * scroll_handle_reframe -- adjust display params to display given handle
 */
long
scroll_handle_reframe(key, center)
    int key, center;
{
    long	l, offset, dlines, start_line;
    SCRLCTRL_S *st = scroll_state(SS_CUR);

    dlines     = PGSIZE(st);
    offset     = (st->parms->text.src == FileStar) ? st->top_text_line : 0;
    start_line = st->top_text_line;

    if(key < 0)
      key = st->parms->text.handles->key;

    /* Searc down from the top line */
    for(l = start_line; l < st->num_lines; l++) {
	if(st->parms->text.src == FileStar && l > offset + dlines)
	  ScrollFile(offset += dlines);

	if(handle_on_line(l - offset, key))
	  break;
    }

    if(l < st->num_lines){
	if(l >= dlines + start_line)			/* bingo! */
	  start_line = l - ((center ? (dlines / 2) : dlines) - 1);
    }
    else{
	if(st->parms->text.src == FileStar)		/* wrap offset */
	  ScrollFile(offset = 0);

	for(l = 0; l < start_line; l++) {
	    if(st->parms->text.src == FileStar && l > offset + dlines)
	      ScrollFile(offset += dlines);

	    if(handle_on_line(l - offset, key))
	      break;
	}

	if(l == start_line)
	  panic("Internal Error: no handle found");
	else
	  start_line = l;
    }

    return(start_line);
}



int
handle_on_line(line, goal)
    long  line;
    int	  goal;
{
    int		i, n, key;
    SCRLCTRL_S *st = scroll_state(SS_CUR);

    for(i = 0; i < st->line_lengths[line]; i++)
      if(st->text_lines[line][i] == TAG_EMBED
	 && st->text_lines[line][++i] == TAG_HANDLE){
	  for(key = 0, n = st->text_lines[line][++i]; n; n--)
	    key = (key * 10) + (st->text_lines[line][++i] - '0');

	  if(key == goal)
	    return(1);
      }

    return(0);
}



int
handle_on_page(handle, first_line, last_line)
    HANDLE_S *handle;
    long      first_line, last_line;
{
    POSLIST_S *l;
    int	       rv = 0;

    if(handle && (l = handle->loc)){
	for( ; l; l = l->next)
	  if(l->where.row < first_line){
	      if(!rv)
		rv = -1;
	  }
	  else if(l->where.row >= last_line){
	      if(!rv)
		rv = 1;
	  }
	  else
	    return(0);				/* found! */
    }

    return(rv);
}



int
scroll_handle_selectable(handle)
    HANDLE_S *handle;
{
    return(handle && (handle->type != URL
		      || (handle->h.url.path && *handle->h.url.path)));
}    



HANDLE_S *
scroll_handle_next_sel(handles)
    HANDLE_S *handles;
{
    while(handles && !scroll_handle_selectable(handles = handles->next))
      ;

    return(handles);
}


HANDLE_S *
scroll_handle_prev_sel(handles)
    HANDLE_S *handles;
{
    while(handles && !scroll_handle_selectable(handles = handles->prev))
      ;

    return(handles);
}



HANDLE_S *
scroll_handle_next(handles)
    HANDLE_S *handles;
{
    HANDLE_S *next = NULL;

    if(scroll_handle_obscured(handles) <= 0){
	next = handles;
	while((next = scroll_handle_next_sel(next))
	      && scroll_handle_obscured(next))
	  ;
    }

    return(next);
}



HANDLE_S *
scroll_handle_prev(handles)
    HANDLE_S *handles;
{
    HANDLE_S *prev = NULL;

    if(scroll_handle_obscured(handles) >= 0){
	prev = handles;
	while((prev = scroll_handle_prev_sel(prev))
	      && scroll_handle_obscured(prev))
	  ;
    }

    return(prev);
}



HANDLE_S *
scroll_handle_boundary(handle, f)
    HANDLE_S *handle;
    HANDLE_S *(*f) PROTO((HANDLE_S *));
{
    HANDLE_S  *hp, *whp = NULL;

    /* Multi-line handle? Punt! */
    if(handle && (!(hp = handle)->loc->next))
      while((hp = (*f)(hp))
	    && hp->loc->where.row == handle->loc->where.row)
	whp = hp;

    return(whp);
}



int
scroll_handle_column(line, offset)
    char *line, *offset;
{
    int n, column = 0;

    while(line < offset)
      switch(*line++){
	case TAG_EMBED :
	  if(*line++ == TAG_HANDLE)
	    line += *line + 1;

	  break;

	case ESCAPE :		/* Don't count escape in len */
	  if(n = match_escapes(line))
	    line += (n - 1);

	  break;

	case TAB :
	  while(((++column) &  0x07) != 0) /* add tab's spaces */
	    ;

	  break;

	default :
	  column++;
	  break;
      }

    return(column);
}



void
scroll_handle_set_loc(lpp, line, column)
    POSLIST_S **lpp;
    int		line, column;
{
    POSLIST_S *lp;

    lp = (POSLIST_S *) fs_get(sizeof(POSLIST_S));
    lp->where.row = line;
    lp->where.col = column;
    lp->next	= NULL;
    for(; *lpp; lpp = &(*lpp)->next)
      ;

    *lpp = lp;
}



int
dot_on_handle(line, goal)
    long line;
    int  goal;
{
    int		i = 0, n, key = 0, column = -1;
    SCRLCTRL_S *st = scroll_state(SS_CUR);
    HANDLE_S *h;

    do{
	if(i >= st->line_lengths[line])
	  return(0);

	switch(st->text_lines[line][i++]){
	  case TAG_EMBED :
	    switch(st->text_lines[line][i++]){
	      case TAG_HANDLE :
		for(key = 0, n = st->text_lines[line][i++]; n; n--)
		  key = (key * 10) + (st->text_lines[line][i++] - '0');

		break;

	      case TAG_BOLDOFF :
		key = 0;
		break;
	    }

	    break;

	  case ESCAPE :		/* Don't count escape in len */
	    i += (n = match_escapes(&st->text_lines[line][i])) ? n : 1;
	    break;

	  case TAB :
	    while(((++column) &  0x07) != 0) /* add tab's spaces */
	      ;

	    break;

	  default :
	    column++;
	}
    }
    while(column < goal);

    return(key);
}



char *
url_embed(embed)
    int embed;
{
    static char buf[2] = {TAG_EMBED};
    buf[1] = embed;
    return(buf);
}



int
url_hilite(linenum, line, ins, local)
    long       linenum;
    char      *line;
    LT_INS_S **ins;
    void      *local;
{
    register char *lp, *urlp, *weburlp, *mailurlp;
    int		   n;
    HANDLE_S	  *h;

    for(lp = line, weburlp = mailurlp = NULL;
	(F_ON(F_VIEW_SEL_URL,ps_global) ? (urlp = rfc1738_scan(lp, &n)) : 0)
	  || (F_ON(F_VIEW_SEL_URL_HOST,ps_global)
	       ? (urlp = weburlp = web_host_scan(lp, &n)) : 0)
	  || (F_ON(F_SCAN_ADDR,ps_global)
	       ? (urlp = mailurlp = mail_addr_scan(lp, &n)): 0);
	lp = urlp + n){
	char buf[256];

	h	      = new_handle();
	h->type	      = URL;
	h->h.url.path = (char *) fs_get((n + 10) * sizeof(char));
	sprintf(h->h.url.path, "%s%.*s",
		weburlp ? "http://" : (mailurlp ? "mailto:" : ""), n, urlp);

	ins = gf_line_test_new_ins(ins, urlp, url_embed(TAG_BOLDON), 2);

	buf[0] = TAG_EMBED;
	buf[1] = TAG_HANDLE;
	sprintf(&buf[3], "%d", h->key);
	buf[2] = strlen(&buf[3]);
	ins = gf_line_test_new_ins(ins, urlp, buf, (int) buf[2] + 3);

	ins = gf_line_test_new_ins(ins, urlp + n, url_embed(TAG_BOLDOFF), 2);
	ins = gf_line_test_new_ins(ins, urlp + n, url_embed(TAG_INVOFF), 2);
	weburlp = mailurlp = NULL;
    }

    return(0);
}



int
url_hilite_hdr(linenum, line, ins, local)
    long       linenum;
    char      *line;
    LT_INS_S **ins;
    void      *local;
{
    register char *lp;

    if(lp = strchr(line, ':')){			/* not continuation? */
	FieldType ft;

	*lp = '\0';
	if(((ft = pine_header_standard(line)) == FreeText
	    || ft == Subject
	    || ft == TypeUnknown)
	   && strucmp(line, "message-id")
	   && strucmp(line, "newsgroups")
	   && strucmp(line, "references")
	   && strucmp(line, "in-reply-to")
	   && strucmp(line, "received")
	   && strucmp(line, "date"))
	  (void) url_hilite(linenum, lp + 1, ins, local);

	*lp = ':';
    }

    return(0);
}



int
url_hilite_abook(linenum, line, ins, local)
    long       linenum;
    char      *line;
    LT_INS_S **ins;
    void      *local;
{
    register char *lp;

    if((lp = strchr(line, ':')) &&
       !strncmp(line, AB_COMMENT_STR, strlen(AB_COMMENT_STR)))
      (void) url_hilite(linenum, lp + 1, ins, local);

    return(0);
}



/*
 * url_launch - Sniff the given url, see if we can do anything with
 *		it.  If not, hand off to user-defined app.
 *
 */
int
url_launch(handle)
    HANDLE_S *handle;
{
    int	       rv = 0;
    url_tool_t f;
#define	URL_MAX_LAUNCH	(2 * MAILTMPLEN)

    if(handle->h.url.tool){
	char	*toolp, *cmdp, *p, cmd[URL_MAX_LAUNCH + 1];
	int	mode, len, hlen, quotable, copied = 0;
	PIPE_S *syspipe;

	if((len = strlen(toolp = handle->h.url.tool)) > URL_MAX_LAUNCH)
	  return(url_launch_too_long(rv));
	  
	hlen	 = strlen(handle->h.url.path);

	/*
	 * Figure out if we need to quote the URL. If there are shell
	 * metacharacters in it we want to quote it, because we don't want
	 * the shell to interpret them. However, if the user has already
	 * quoted the URL in the command definition we don't want to quote
	 * again. So, we try to see if there are a pair of unescaped
	 * quotes surrounding _URL_ in the cmd.
	 * If we quote when we shouldn't have, it'll cause it not to work.
	 * If we don't quote when we should have, it's a possible security
	 * problem (and it still won't work).
	 */
#ifdef	_WINDOWS
	if(*toolp == '*' || (*toolp == '\"' && *(toolp+1) == '*'))
	  quotable = 0;		/* never quote */
	else
#endif
	if(strpbrk(handle->h.url.path, "&*;<>?[|~") != NULL){  /* specials? */
	    if((p = strstr(toolp, "_URL_")) != NULL){  /* explicit arg? */
		char *q;
		int   lhs_single_quote = 0,
		      lhs_double_quote = 0,
		      rhs_single_quote = 0,
		      rhs_double_quote = 0;

		/*
		 * Make sure we catch see whether or not it is quoted.
		 * Use brute force, which is cheap.
		 */

		for(q = toolp; q < p; q++){
		    if((*q == '\'' || *q == '\"') &&
		       (q == toolp || q[-1] != '\\')){
			lhs_single_quote = lhs_single_quote ? 1 : (*q == '\'');
			lhs_double_quote = lhs_double_quote ? 1 : (*q == '\"');
		    }
		}

		for(q = p+5; *q; q++){
		    if((*q == '\'' || *q == '\"') && q[-1] != '\\'){
			rhs_single_quote = rhs_single_quote ? 1 : (*q == '\'');
			rhs_double_quote = rhs_double_quote ? 1 : (*q == '\"');
		    }
		}

		quotable = !((lhs_single_quote && rhs_single_quote) ||
		             (lhs_double_quote && rhs_double_quote));
	    }
	    else
	      quotable = 1;
	}
	else
	  quotable = 0;

	/* Build the command */
	cmdp = cmd;
	while(1)
	  if((!*toolp && !copied)
	     || (*toolp == '_' && !strncmp(toolp + 1, "URL_", 4))){
	      int end_quote;

	      if(!*toolp){
		  *cmdp++ = ' ';
		  len++;
	      }

	      if(end_quote = (quotable && (cmdp == cmd || *(cmdp-1) != '\"'))){
		  *cmdp++ = '\"';
		  len += 2;
	      }

	      if((len += hlen) > URL_MAX_LAUNCH)
		return(url_launch_too_long(rv));

	      copied = 1;
	      sstrcpy(&cmdp, handle->h.url.path);
	      if(end_quote){
		  *cmdp++ = '\"';
		  *cmdp = '\0';
	      }

	      if(*toolp)
		toolp += 5;
	  }
	  else if(*toolp)
	    *cmdp++ = *toolp++;
	  else
	    break;

	mode = PIPE_RESET | PIPE_USER ;
	if(syspipe = open_system_pipe(cmd, NULL, NULL, mode, 0)){
	    close_system_pipe(&syspipe);
	    q_status_message(SM_ORDER, 0, 4, "VIEWER command completed");
	}
	else
	  q_status_message1(SM_ORDER, 3, 4,
			    "Cannot spawn command : %s", cmd);
    }
    else if(f = url_local_handler(handle->h.url.path)){
	if((*f)(handle->h.url.path) > 1)
	  rv = 1;		/* done! */
    }
    else
      q_status_message1(SM_ORDER, 2, 2,
			"\"URL-Viewer\" not defined: Can't open %s",
			handle->h.url.path);
    
    return(rv);
}


int
url_launch_too_long(return_value)
    int return_value;
{
    q_status_message(SM_ORDER | SM_DING, 3, 3,
		     "Can't spawn.  Command too long.");
    return(return_value);
}


char *
url_external_handler(handle, specific)
    HANDLE_S *handle;
    int	      specific;
{
    char **l, *test, *cmd, *p, *q, *ep;
    int	   i, specific_match;

    for(l = ps_global->VAR_BROWSER ; l && *l; l++){
	get_pair(*l, &test, &cmd, 0);
	dprint(5, (debugfile, "TEST: \"%s\"  CMD: \"%s\"\n",
		   test ? test : "<NULL>", cmd ? cmd : "<NULL>"));
	removing_quotes(cmd);
	if(valid_filter_command(&cmd)){
	    specific_match = 0;

	    if(p = test){
		while(*p && cmd)
		  if(*p == '_'){
		      if(!strncmp(p+1, "TEST(", 5)
			 && (ep = strstr(p+6, ")_"))){
			  *ep = '\0';

			  if(exec_mailcap_test_cmd(p+6) == 0){
			      p = ep + 2;
			  }
			  else{
			      dprint(5, (debugfile,"failed handler TEST\n"));
			      fs_give((void **) &cmd);
			  }
		      }
		      else if(!strncmp(p+1, "SCHEME(", 7)
			      && (ep = strstr(p+8, ")_"))){
			  *ep = '\0';

			  p += 8;
			  do
			    if(q = strchr(p, ','))
			      *q++ = '\0';
			    else
			      q = ep;
			  while(!((i = strlen(p))
				  && ((p[i-1] == ':'
				       && handle->h.url.path[i - 1] == ':')
				      || (p[i-1] != ':'
					  && handle->h.url.path[i] == ':'))
				  && !struncmp(handle->h.url.path, p, i))
				&& *(p = q));

			  if(*p){
			      specific_match = 1;
			      p = ep + 2;
			  }
			  else{
			      dprint(5, (debugfile,"failed handler SCHEME\n"));
			      fs_give((void **) &cmd);
			  }
		      }
		      else{
			  dprint(5, (debugfile, "UNKNOWN underscore test\n"));
			  fs_give((void **) &cmd);
		      }
		  }
		  else if(isspace((unsigned char) *p)){
		      p++;
		  }
		  else{
		      dprint(1, (debugfile,"bogus handler test: \"%s\"",test));
		      fs_give((void **) &cmd);
		  }

		fs_give((void **) &test);
	    }

	    if(cmd && (!specific || specific_match))
	      return(cmd);
	}

	if(test)
	  fs_give((void **) &test);

	if(cmd)
	  fs_give((void **) &cmd);
    }

    cmd = NULL;

    if(!specific){
#if	_WINDOWS
	cmd = mswin_reg_default_browser(handle->h.url.path);
#endif
	/*
	 * Last chance, anything handling "text/html" in mailcap...
	 */
	if(!cmd && mailcap_can_display(TYPETEXT, "html", NULL))
	  cmd = mailcap_build_command(TYPETEXT, "html", NULL, "_URL_", NULL);
    }

    return(cmd);
}



#define	UES_LEN	12
#define	UES_MAX	32
int
url_external_specific_handler(url, len)
    char *url;
    int   len;
{
    static char list[UES_LEN * UES_MAX];

    if(url){
	char *p;
	int   i;

	for(i = 0; i < UES_MAX && *(p = &list[i * UES_LEN]); i++)
	  if(!struncmp(p, url, len))
	    return(1);

	return(0);
    }
    else{					/* initialize! */
	char **l, *test, *cmd, *p, *p2;
	int    i = 0, n;

	memset(list, 0, UES_LEN * UES_MAX * sizeof(char));
	for(l = ps_global->VAR_BROWSER ; l && *l; l++){
	    get_pair(*l, &test, &cmd, 1);

	    if((p = srchstr(test, "_scheme(")) && (p2 = strstr(p+8, ")_"))){
		*p2 = '\0';

		for(p += 8; *p && i < UES_MAX; p += n)
		  if(p2 = strchr(p, ',')){
		      if((n = p2 - p) < UES_LEN)
			strncpy(&list[i++ * UES_LEN], p, n);
		      else
			dprint(1, (debugfile,
				   "* * * HANLDER TOO LONG: %.*s\n", n, p));

		      n++;
		  }
		  else{
		      if(strlen(p) <= UES_LEN)
			strcpy(&list[i++ * UES_LEN], p);

		      break;
		  }
	    }

	    if(test)
	      fs_give((void **) &test);

	    if(cmd)
	      fs_give((void **) &cmd);
	}
    }
}



url_tool_t
url_local_handler(s)
    char *s;
{
    int i;
    static struct url_t {
	char       *url;
	short	    len;
	url_tool_t  f;
    } handlers[] = {
	{"mailto:", 7, url_local_mailto},	/* see url_tool_t def's */
	{"imap://", 7, url_local_imap},		/* for explanations */
	{"nntp://", 7, url_local_nntp},
#ifdef	ENABLE_LDAP
	{"ldap://", 7, url_local_ldap},
#endif
	{"news:", 5, url_local_news},
	{"x-pine-gripe:", 13, gripe_gripe_to},
	{"x-pine-help:", 11, url_local_helper},
	{"x-pine-phone-home:", 18, url_local_phone_home},
	{"#", 1, url_local_fragment},
	{NULL, 0, NULL}
    };

    for(i = 0; handlers[i].url ; i++)
      if(!struncmp(s, handlers[i].url, handlers[i].len))
	return(handlers[i].f);

    return(NULL);
}



/*
 * mailto URL digester ala draft-hoffman-mailto-url-02.txt
 */
int
url_local_mailto(url)
  char *url;
{
    ENVELOPE *outgoing;
    BODY     *body = NULL;
    REPLY_S   fake_reply;
    char     *sig, *urlp, *p, *hname, *hvalue;
    int	      rv = 0;
    char     *fcc = NULL;

    outgoing		 = mail_newenvelope();
    outgoing->message_id = generate_message_id();
    body		 = mail_newbody();
    body->type		 = TYPETEXT;
    if(body->contents.text.data = (void *) so_get(PicoText,NULL,EDIT_ACCESS)){
	/*
	 * URL format is:
	 *
	 *	mailtoURL  =  "mailto:" [ to ] [ headers ]
	 *	to         =  #mailbox
	 *	headers    =  "?" header *( "&" header )
	 *	header     =  hname "=" hvalue
	 *	hname      =  *urlc
	 *	hvalue     =  *urlc
	 *
	 * NOTE2: "from" and "bcc" are intentionally excluded from
	 *	  the list of understood "header" fields
	 */
	if(p = strchr(urlp = cpystr(url+7), '?'))
	  *p++ = '\0';			/* headers?  Tie off mailbox */

	/* grok mailbox as first "to", then roll thru specific headers */
	if(*urlp)
	  rfc822_parse_adrlist(&outgoing->to,
			       rfc1738_str(urlp),
			       ps_global->maildomain);

	sig = detoken_file(ps_global->VAR_SIGNATURE_FILE, NULL,
			   2, 0, 1, NULL, NULL);
	while(p){
	    /* Find next "header" */
	    if(p = strchr(hname = p, '&'))
	      *p++ = '\0';		/* tie off "header" */

	    if(hvalue = strchr(hname, '='))
	      *hvalue++ = '\0';		/* tie off hname */

	    if(!hvalue || !strucmp(hname, "subject")){
		char *sub = rfc1738_str(hvalue ? hvalue : hname);

		if(outgoing->subject){
		    int len = strlen(outgoing->subject);

		    fs_resize((void **) &outgoing->subject,
			      (len + strlen(sub) + 2) * sizeof(char));
		    sprintf(outgoing->subject + len, " %s", sub);
		}
		else
		  outgoing->subject = cpystr(sub);
	    }
	    else if(!strucmp(hname, "to")){
		url_mailto_addr(&outgoing->to, hvalue);
	    }
	    else if(!strucmp(hname, "cc")){
		url_mailto_addr(&outgoing->cc, hvalue);
	    }
	    else if(!strucmp(hname, "body")){
		so_puts((STORE_S *)body->contents.text.data, hvalue);
		so_puts((STORE_S *)body->contents.text.data, NEWLINE);
		if(sig)
		  fs_give((void **) &sig);
	    }
	}

	fs_give((void **) &urlp);

	if(sig){
	    so_puts((STORE_S *)body->contents.text.data, sig);
	    fs_give((void **) &sig);
	}

	memset((void *)&fake_reply, 0, sizeof(fake_reply));
	fake_reply.flags = REPLY_PSEUDO;
	fake_reply.data.pico_flags = (outgoing->subject) ? P_BODY : P_HEADEND;


	fcc = get_fcc_based_on_to(outgoing->to);

	pine_send(outgoing, &body, "\"MAILTO\" COMPOSE",
		  NULL, fcc, &fake_reply, NULL, NULL, NULL, 0);
	rv++;
	ps_global->mangled_screen = 1;
    }
    else
      q_status_message(SM_ORDER | SM_DING, 3, 4,
		       "Can't create space for composer");

    if(outgoing)
      mail_free_envelope(&outgoing);

    if(body)
      pine_free_body(&body);
    
    if(fcc)
      fs_give((void **)&fcc);

    return(rv);
}



void
url_mailto_addr(a, a_raw)
    ADDRESS	**a;
    char	 *a_raw;
{
    char *p = cpystr(rfc1738_str(a_raw));

    while(*a)				/* append to address list */
      a = &(*a)->next;

    rfc822_parse_adrlist(a, p, ps_global->maildomain);
    fs_give((void **) &p);
}


/*
 * imap URL digester ala RFC 2192
 */
int
url_local_imap(url)
    char *url;
{
    char      *folder, *mailbox = NULL, *errstr = NULL, *search = NULL,
	       newfolder[MAILTMPLEN];
    int	       rv;
    long       uid = 0L, uid_val = 0L, i;
    CONTEXT_S *fake_context;

    rv = url_imap_folder(url, &folder, &uid, &uid_val, &search, 0);
    switch(rv & URL_IMAP_MASK){
      case URL_IMAP_IMAILBOXLIST :
/* BUG: deal with lsub tag */
	if(fake_context = new_context(folder, NULL)){
	    newfolder[0] = '\0';
	    if(display_folder_list(&fake_context, newfolder,
				   0, folders_for_goto))
	      if(strlen(newfolder) + 1 < MAILTMPLEN)
		mailbox = newfolder;
	}
	else
	  errstr = "Problem building URL's folder list";

	fs_give((void **) &folder);
	free_context(&fake_context);

	if(mailbox){
	    return(1);
	}
	else if(errstr)
	  q_status_message(SM_ORDER|SM_DING, 3, 3, errstr);
	else
	  cmd_cancelled("URL Launch");

	break;

      case URL_IMAP_IMESSAGEPART :
      case URL_IMAP_IMESSAGELIST :
	rv = do_broach_folder(folder, NULL);
	fs_give((void **) &folder);
	switch(rv){
	  case -1 :				/* utter failure */
	    ps_global->next_screen = main_menu_screen;
	    break;

	  case 0 :				/* same folder reopened */
	    ps_global->next_screen = mail_index_screen;
	    break;

	  case 1 :				/* requested folder open */
	    ps_global->next_screen = mail_index_screen;

	    if(uid_val != ps_global->mail_stream->uid_validity){
		/* Complain! */
		q_status_message(SM_ORDER|SM_DING, 3, 3,
		     "Warning!  Referenced folder changed since URL recorded");
	    }

	    if(uid){
		/*
		 * Make specified message the currently selected..
		 */
		for(i = 1L; i <= mn_get_total(ps_global->msgmap); i++)
		  if(mail_uid(ps_global->mail_stream, i) == uid){
		      ps_global->next_screen = mail_view_screen;
		      mn_set_cur(ps_global->msgmap, i);
		      break;
		  }

		if(i > mn_get_total(ps_global->msgmap))
		  q_status_message(SM_ORDER, 2, 3,
				   "Couldn't find specified article number");
	    }
	    else if(search){
		/*
		 * Select the specified messages
		 * and present a zoom'd index...
		 */
/* BUG: not dealing with CHARSET yet */

		mail_search_full(ps_global->mail_stream, NULL,
				 mail_criteria(search),
				 SE_NOPREFETCH | SE_FREE);

		for(i = 1L; i <= mn_get_total(ps_global->msgmap); i++)
		  if(mail_elt(ps_global->mail_stream, i)->searched)
		    set_lflag(ps_global->mail_stream,
			      ps_global->msgmap, i, MN_SLCT, 1);

		if(i = any_lflagged(ps_global->msgmap, MN_SLCT)){
		    extern long zoom_index();

		    q_status_message2(SM_ORDER, 0, 3,
				      "%s message%s selected",
				      long2string(i), plural(i));
		    /* Zoom the index! */
		    zoom_index(ps_global, ps_global->msgmap);
		}
	    }
	}

	return(1);

      default:
      case URL_IMAP_ERROR :
	break;
    }

    return(0);
}



int
url_imap_folder(true_url, folder, uid_val, uid, search, silent)
  char  *true_url, **folder;
  long  *uid_val, *uid;
  char **search;
  int	 silent;
{
    char *url, *scheme, *p, *cmd, *server = NULL,
	 *user = NULL, *auth = NULL, *mailbox = NULL,
	 *section = NULL;
    int   rv = URL_IMAP_ERROR;

    /*
     * Since we're planting nulls, operate on a temporary copy...
     */
    scheme = silent ? NULL : "IMAP";
    url = cpystr(true_url + 7);

    /* Try to pick apart the "iserver" portion */
    if(cmd = strchr(url, '/')){			/* "iserver/mailbox" ? */
	*cmd++ = '\0';
	if(p = strchr(url, '@')){		/* user | auth | pass? */
	    *p++ = '\0';
	    server = rfc1738_str(p);

	    /* only ";auth=*" supported */
	    if(p = srchstr(url, ";auth=")){
		*p = '\0';
		auth = rfc1738_str(p + 6);
	    }

	    if(*url)
	      user = rfc1738_str(url);
	}
	else
	  server = rfc1738_str(url);

	if(!*server)
	  return(url_bogus_imap(&url, scheme, "No server specified"));

	/*
	 * "iserver" in hand, pick apart the "icommand"...
	 */
	p = NULL;
	if(!*cmd || (p = srchstr(cmd, ";type="))){
	    char *criteria;

	    /*
	     * No "icommand" (all top-level folders) or "imailboxlist"...
	     */
	    if(p){
		*p	 = '\0';		/* tie off criteria */
		criteria = rfc1738_str(cmd);	/* get "enc_list_mailbox" */
		if(!strucmp(p = rfc1738_str(p+6), "lsub"))
		  rv |= URL_IMAP_IMBXLSTLSUB;
		else if(strucmp(p, "list"))
		  return(url_bogus_imap(&url, scheme,
					"Invalid list type specified"));
	    }
	    else
	      criteria = "";

	    /* build folder list from specified server/criteria/list-method */
	    *folder = (char *) fs_get((strlen(server) + strlen(criteria) + 11
				  + (user ? strlen(user) : 9)) * sizeof(char));
	    sprintf(*folder, "{%s/%s%s}[%s]", server,
		    user ? "user=" : "Anonymous", user ? user : "", criteria);
	    rv |= URL_IMAP_IMAILBOXLIST;
	}
	else{
	    if(p = srchstr(cmd, "/;uid=")){ /* "imessagepart" */
		*p = '\0';		/* tie off mailbox [uidvalidity] */
		if(section = srchstr(p += 6, "/;section=")){
		    *section = '\0';	/* tie off UID */
		    section  = rfc1738_str(section + 10);
/* BUG: verify valid section spec ala rfc 2060 */
		    dprint(2, (debugfile,
			       "-- URL IMAP FOLDER: section not used: %s\n",
			       section));
		}

		if(!(*uid = rfc1738_num(&p)) || *p) /* decode UID */
		  return(url_bogus_imap(&url, scheme, "Invalid data in UID"));

		/* optional "uidvalidity"? */
		if(p = srchstr(cmd, ";uidvalidity=")){
		    *p  = '\0';
		    p  += 13;
		    if(!(*uid_val = rfc1738_num(&p)) || *p)
		      return(url_bogus_imap(&url, scheme,
					    "Invalid UIDVALIDITY"));
		}

		mailbox = rfc1738_str(cmd);
		rv = URL_IMAP_IMESSAGEPART;
	    }
	    else{			/* "imessagelist" */
					/* optional "uidvalidity"? */
		if(p = srchstr(cmd, ";uidvalidity=")){
		    *p  = '\0';
		    p  += 13;
		    if(!(*uid_val = rfc1738_num(&p)) || *p)
		      return(url_bogus_imap(&url, scheme,
					    "Invalid UIDVALIDITY"));
		}

		/* optional "enc_search"? */
		if(p = strchr(cmd, '?')){
		    *p = '\0';
		    if(search)
		      *search = cpystr(rfc1738_str(p + 1));
/* BUG: verify valid search spec ala rfc 2060 */
		}

		mailbox = rfc1738_str(cmd);
		rv = URL_IMAP_IMESSAGELIST;
	    }

	    if(auth && *auth != '*')
	      q_status_message1(SM_ORDER, 3, 3,
				"Unsupported authentication method.  %s.",
				user ? "Using standard login"
				     : "Logging in as \"Anonymous\"");

	    /*
	     * At this point our structure should contain the
	     * digested url.  Now put it together for c-client...
	     */
	    *folder = (char *) fs_get((strlen(server) + 9
				       + (mailbox ? strlen(mailbox) : 0)
				       + (user ? strlen(user) : 9))
				      * sizeof(char));
	    sprintf(*folder, "{%s/%s%s}%s", server,
		    user ? "user=" : "Anonymous", user ? user : "", mailbox);
	}
    }
    else
      return(url_bogus_imap(&url, scheme, "Folder location not specified"));

    fs_give((void **) &url);
    return(rv);
}


url_bogus_imap(freeme, url, problem)
    char **freeme, *url, *problem;
{
    fs_give((void **) freeme);
    (void) url_bogus(url, problem);
    return(URL_IMAP_ERROR);
}



int
url_local_nntp(url)
    char *url;
{
    char folder[MAILTMPLEN], *group;
    int  group_len;
    long i, article_num;

	/* no hostport, no url, end of story */
	if(group = strchr(url + 7, '/')){
	    group++;
	    for(group_len = 0; group[group_len] && group[group_len] != '/';
		group_len++)
	      if(!rfc1738_group(&group[group_len]))
		return(url_bogus(url, "Invalid newsgroup specified"));
	      
	    if(group_len)
	      sprintf(folder, "{%.*s/nntp}#news.%.*s",
		      (group - 1) - (url + 7),
		      url + 7, group_len, group);
	    else
	      return(url_bogus(url, "No newsgroup specified"));
	}
	else
	  return(url_bogus(url, "No server specified"));

	switch(do_broach_folder(rfc1738_str(folder), NULL)){
	  case -1 :				/* utter failure */
	    ps_global->next_screen = main_menu_screen;
	    break;

	  case 0 :				/* same folder reopened */
	    ps_global->next_screen = mail_index_screen;
	    break;

	  case 1 :				/* requested folder open */
	    ps_global->next_screen = mail_index_screen;

	    /* grok article number --> c-client UID */
	    if(group[group_len++] == '/'
	       && (article_num = atol(&group[group_len]))){
		/*
		 * Make the requested article our current message
		 */
		for(i = 1; i <= mn_get_total(ps_global->msgmap); i++)
		  if(mail_uid(ps_global->mail_stream, i) == article_num){
		      ps_global->next_screen = mail_view_screen;
		      mn_set_cur(ps_global->msgmap, i);
		      break;
		  }

		if(i > mn_get_total(ps_global->msgmap))
		  q_status_message(SM_ORDER, 2, 3,
				   "Couldn't find specified article number");
	    }

	    break;
	}

	ps_global->redrawer = (void(*)())NULL;
	return(1);
}



int
url_local_news(url)
    char *url;
{
    char       folder[MAILTMPLEN], *p;
    CONTEXT_S *cntxt = NULL;

    /*
     * NOTE: NO SUPPORT for '*' or message-id
     */
    if(*(url+5)){
	if(ps_global->VAR_NNTP_SERVER && ps_global->VAR_NNTP_SERVER[0])
	  /*
	   * BUG: Only the first NNTP server is tried.
	   */
	  sprintf(folder, "{%s/nntp}#news.", ps_global->VAR_NNTP_SERVER[0]);
	else
	  strcpy(folder, "#news.");

	for(p = strcpy(folder + strlen(folder), url + 5); 
	    *p && rfc1738_group(p);
	    p++)
	  ;

	if(*p)
	  return(url_bogus(url, "Invalid newsgroup specified"));
    }
    else{			/* fish first group from newsrc */
	FOLDER_S  *f;
	int	   i, alphaorder;

	folder[0] = '\0';

	/* Find first news context */
	for(cntxt = ps_global->context_list;
	    cntxt && !(cntxt->use & CNTXT_NEWS);
	    cntxt = cntxt->next)
	  ;

	if(cntxt){
	    if(alphaorder = F_OFF(F_READ_IN_NEWSRC_ORDER, ps_global))
	      F_SET(F_READ_IN_NEWSRC_ORDER, ps_global, 1);

	    build_folder_list(NULL, cntxt, NULL, NULL, BFL_LSUB);
	    if(f = folder_entry(0, FOLDERS(cntxt)))
	      strcpy(folder, f->name);

	    free_folder_list(cntxt);

	    if(alphaorder)
	      F_SET(F_READ_IN_NEWSRC_ORDER, ps_global, 0);
	}

	if(folder[0] == '\0'){
	    q_status_message(SM_ORDER | SM_DING, 3, 3,
			     "No default newsgroup");
	    return(0);
	}
    }

    if(do_broach_folder(rfc1738_str(folder), cntxt) < 0)
      ps_global->next_screen = main_menu_screen;
    else
      ps_global->next_screen = mail_index_screen;

    ps_global->redrawer = (void(*)())NULL;

    return(1);
}



int
url_local_fragment(fragment)
    char *fragment;
{
    SCRLCTRL_S *st = scroll_state(SS_CUR);
    HANDLE_S   *hp;

    /*
     * find a handle with the fragment's name
     */
    for(hp = st->parms->text.handles; hp; hp = hp->next)
      if(hp->type == URL && hp->h.url.name
	 && !strcmp(hp->h.url.name, fragment + 1))
	break;

    if(!hp)
      for(hp = st->parms->text.handles->prev; hp; hp = hp->prev)
	if(hp->type == URL && hp->h.url.name
	   && !strcmp(hp->h.url.name, fragment + 1))
	  break;

    /*
     * set the top line of the display to contain this line
     */
    if(hp && hp->loc){
	st->top_text_line = hp->loc->where.row;
	ps_global->mangled_body = 1;
    }
    else
      q_status_message1(SM_ORDER | SM_DING, 0, 3,
			"Can't find fragment: %s", fragment);

    return(1);
}



int
url_local_phone_home(url)
    char *url;
{
    phone_home(url + 18);	/* 18 == length of "x-pine-phone-home:" */
    return(2);
}



/*
 * url_bogus - report url syntax errors and such
 */
int
url_bogus(url, reason)
    char *url, *reason;
{
    dprint(2, (debugfile, "-- bogus url \"%s\": %s\n",
	       url ? url : "<NULL URL>", reason));
    if(url)
      q_status_message3(SM_ORDER|SM_DING, 2, 3, "Malformed \"%.*s\" URL: %s",
			(void *) (strchr(url, ':') - url), url, reason);

    return(0);
}



/*----------------------------------------------------------------------
   Handle fetching and filtering a text message segment to be displayed
   by scrolltool or printed.

Args: att   -- segment to fetch
      msgno -- message number segment is a part of
      pc    -- function to write characters from segment with
      style -- Indicates special handling for error messages
      flags -- Indicates special necessary handling

Returns: 1 if errors encountered, 0 if everything went A-OK

 ----*/     
int
decode_text(att, msgno, pc, style, flags)
    ATTACH_S       *att;
    long            msgno;
    gf_io_t         pc;
    DetachErrStyle  style;
    int		    flags;
{
    FILTLIST_S	filters[7];
    char       *err, *charset;
    int		filtcnt = 0, error_found = 0, column, wrapit;

    column = (flags & FM_DISPLAY) ? ps_global->ttyo->screen_cols : 80;
    wrapit = column;

    memset(filters, 0, 7 * sizeof(FILTLIST_S));
    if(F_OFF(F_PASS_CONTROL_CHARS, ps_global)){
	filters[filtcnt++].filter = gf_escape_filter;
	filters[filtcnt++].filter = gf_control_filter;
    }

    /*
     * if it's just plain old text, look for url's
     */
    if(!(att->body->subtype && strucmp(att->body->subtype, "plain"))){
	if((F_ON(F_VIEW_SEL_URL, ps_global)
	    || F_ON(F_VIEW_SEL_URL_HOST, ps_global)
	    || F_ON(F_SCAN_ADDR, ps_global))
	   && (flags & FM_HANDLES)){
	    filters[filtcnt].filter = gf_line_test;
	    filters[filtcnt++].data = gf_line_test_opt(url_hilite, NULL);
	}
    }
    else if(!strucmp(att->body->subtype, "richtext")){
	/* maybe strip everything! */
	filters[filtcnt].filter = gf_rich2plain;
	filters[filtcnt++].data = gf_rich2plain_opt(!(flags&FM_DISPLAY));
	/* width to use for file or printer */
	if(wrapit - 5 > 0)
	  wrapit -= 5;
    }
    else if(!strucmp(att->body->subtype, "enriched")){
	filters[filtcnt].filter = gf_enriched2plain;
	filters[filtcnt++].data = gf_enriched2plain_opt(!(flags&FM_DISPLAY));
	/* width to use for file or printer */
	if(wrapit - 5 > 0)
	  wrapit -= 5;
    }
    else if(!strucmp(att->body->subtype, "html") && !ps_global->full_header){
/*BUG:	    sniff the params for "version=2.0" ala draft-ietf-html-spec-01 */
	int opts = 0;

	if(flags & FM_DISPLAY){
	    if(flags & FM_HANDLES)	/* pass on handles awareness */
	      opts |= GFHP_HANDLES;
	}
	else
	  opts |= GFHP_STRIPPED;	/* don't embed anything! */

	wrapit = 0;		/* wrap already handled! */
	filters[filtcnt].filter = gf_html2plain;
	filters[filtcnt++].data = gf_html2plain_opt(NULL, column, opts);
    }

    if(wrapit && !(flags & FM_NOWRAP)){
	filters[filtcnt].filter = gf_wrap;
	filters[filtcnt++].data = gf_wrap_filter_opt(wrapit, column, 0,
						     (flags & FM_DISPLAY)
						       ? GFW_HANDLES : 0);
    }

    /*
     * If there's a charset specified and it's not US-ASCII, and our
     * local charset's undefined or it's not the same as the specified
     * charset, put up a warning...
     */
    if(charset = rfc2231_get_param(att->body->parameter,"charset",NULL,NULL)){
	if(strucmp(charset, "us-ascii")
	   && (!ps_global->VAR_CHAR_SET
	       || strucmp(charset, ps_global->VAR_CHAR_SET))){
	    sprintf(tmp_20k_buf, CHARSET_DISCLAIMER, charset,
		    ps_global->VAR_CHAR_SET
		      ? ps_global->VAR_CHAR_SET : "US-ASCII");
	    if(format_editorial(tmp_20k_buf, column, pc)
	       || !gf_puts(NEWLINE, pc) || !gf_puts(NEWLINE, pc))
	      goto write_error;
	}

	fs_give((void **) &charset);
    }

    err = detach(ps_global->mail_stream, msgno, att->number,
		 NULL, pc, filtcnt ? filters : NULL);
    if(err) {
	error_found++;
	if(style == QStatus) {
	    q_status_message1(SM_ORDER, 3, 4, "%s", err);
	} else if(style == InLine) {
	    sprintf(tmp_20k_buf, "%s   [Error: %s]  %c%s%c%s%s",
		    NEWLINE, err,
		    att->body->description ? '\"' : ' ',
		    att->body->description ? att->body->description : "",
		    att->body->description ? '\"' : ' ',
		    NEWLINE, NEWLINE);
	    if(!gf_puts(tmp_20k_buf, pc))
	      goto write_error;
	}
    }

    if(att->body->subtype
       && (!strucmp(att->body->subtype, "richtext")
	   || !strucmp(att->body->subtype, "enriched"))
       && !(flags & FM_DISPLAY)){
	if(!gf_puts(NEWLINE, pc) || !gf_puts(NEWLINE, pc))
	  goto write_error;
    }

    return(error_found);

  write_error:
    if(style == QStatus)
      q_status_message1(SM_ORDER, 3, 4, "Error writing message: %s", 
			error_description(errno));

    return(1);
}




 
/*------------------------------------------------------------------
   This list of known escape sequences is taken from RFC's 1486 and 1554
   and draft-apng-cc-encoding, and the X11R5 source with only a remote
   understanding of what this all means...

   NOTE: if the length of these should extend beyond 4 chars, fix
	 MAX_ESC_LEN in filter.c
  ----*/
static char *known_escapes[] = {
    "(B",  "(J",  "$@",  "$B",			/* RFC 1468 */
    "$A",  "$(C", "$(D", ".A",  ".F",		/* added by RFC 1554 */
    "$)C", "$)A", "$*E", "$*X",			/* those in apng-draft */
    "$+G", "$+H", "$+I", "$+J", "$+K",
    "$+L", "$+M",
    ")I",   "-A",  "-B",  "-C",  "-D",		/* codes form X11R5 source */
    "-F",   "-G",  "-H",   "-L",  "-M",
    "-$(A", "$(B", "$)B", "$)D",
    NULL};

int
match_escapes(esc_seq)
    char *esc_seq;
{
    char **p;
    int    n;

    for(p = known_escapes; *p && strncmp(esc_seq, *p, n = strlen(*p)); p++)
      ;

    return(*p ? n + 1 : 0);
}



/*----------------------------------------------------------------------
  Format header text suitable for display

  Args: stream -- mail stream for various header text fetches
	msgno -- sequence number in stream of message we're interested in
	env -- pointer to msg's envelope
	hdrs -- struct containing what's to get formatted
	pc -- function to write header text with
	prefix -- prefix to append to each output line

  Result: 0 if all's well, -1 if write error, 1 if fetch error

  NOTE: Blank-line delimiter is NOT written here.  Newlines are written
	in the local convention.

 ----*/
#define	FHT_OK		0
#define	FHT_WRTERR	-1
#define	FHT_FTCHERR	1
int
format_header(stream, msgno, section, env, hdrs, prefix, flags, final_pc)
    MAILSTREAM *stream;
    long	msgno;
    char       *section;
    ENVELOPE   *env;
    HEADER_S   *hdrs;
    char       *prefix;
    int		flags;
    gf_io_t	final_pc;
{
    int	     rv = FHT_OK;
    int	     nfields, i;
    char    *h = NULL, **fields = NULL, **v, *q, *start,
	    *finish, *current;
    STORE_S *tmp_store;
    gf_io_t  tmp_pc, tmp_gc;

    if(tmp_store = so_get(CharStar, NULL, EDIT_ACCESS))
      gf_set_so_writec(&tmp_pc, tmp_store);
    else
      return(FHT_WRTERR);

    if(ps_global->full_header){
	rv = format_raw_header(stream, msgno, section, tmp_pc, prefix);
    }
    else{
	/*
	 * First, calculate how big a fields array we need.
	 */

	/* Custom header viewing list specified */
	if(hdrs->type == HD_LIST){
	    /* view all these headers */
	    if(!hdrs->except){
		for(nfields = 0, v = hdrs->h.l; (q = *v) != NULL; v++)
		  if(!is_an_env_hdr(q))
		    nfields++;
	    }
	    /* view all except these headers */
	    else{
		for(nfields = 0, v = hdrs->h.l; *v != NULL; v++)
		  nfields++;

		if(nfields > 1)
		  nfields--; /* subtract one for ALL_EXCEPT field */
	    }
	}
	else
	  nfields = 6;				/* default view */

	/* allocate pointer space */
	if(nfields){
	    fields = (char **)fs_get((size_t)(nfields+1) * sizeof(char *));
	    memset(fields, 0, (size_t)(nfields+1) * sizeof(char *));
	}

	if(hdrs->type == HD_LIST){
	    /* view all these headers */
	    if(!hdrs->except){
		/* put the non-envelope headers in fields */
		if(nfields)
		  for(i = 0, v = hdrs->h.l; (q = *v) != NULL; v++)
		    if(!is_an_env_hdr(q))
		      fields[i++] = q;
	    }
	    /* view all except these headers */
	    else{
		/* put the list of headers not to view in fields */
		if(nfields)
		  for(i = 0, v = hdrs->h.l; (q = *v) != NULL; v++)
		    if(strucmp(ALL_EXCEPT, q))
		      fields[i++] = q;
	    }

	    v = hdrs->h.l;
	}
	else{
	    if(nfields){
		fields[i = 0] = "Resent-Date";
		fields[++i]   = "Resent-From";
		fields[++i]   = "Resent-To";
		fields[++i]   = "Resent-cc";
		fields[++i]   = "Resent-Subject";
	    }

	    v = fields;
	}

	/* custom view with exception list */
	if(hdrs->type == HD_LIST && hdrs->except){
	    /*
	     * Go through each header in h and print it.
	     * First we check to see if it is an envelope header so we
	     * can print our envelope version of it instead of the raw version.
	     */

	    /* fetch all the other headers */
	    if(nfields)
	      h = pine_fetchheader_lines_not(stream, msgno, section, fields);

	    for(current = h;
		h && delineate_this_header(h, NULL, current, &start, &finish);
		current = finish){
		char tmp[MAILTMPLEN+1];
		char *colon_loc;

		colon_loc = strindex(start, ':');
		if(colon_loc && colon_loc < finish){
		    strncpy(tmp, start, min(colon_loc-start, MAILTMPLEN));
		    tmp[min(colon_loc-start, MAILTMPLEN)] = '\0';
		}
		else
		  colon_loc = NULL;

		if(colon_loc && is_an_env_hdr(tmp)){
		    /* pretty format for env hdrs */
		    format_env_hdr(stream, msgno, section,
				   env, tmp_pc, tmp, prefix);
		}
		else{
		    if(rv = format_raw_hdr_string(start,finish,tmp_pc,prefix))
		      goto write_error;
		    else
		      start = finish;
		}
	    }
	}
	/* custom view or default */
	else{
	    /* fetch the non-envelope headers */
	    if(nfields)
	      h = pine_fetchheader_lines(stream, msgno, section, fields);

	    /* default envelope for default view */
	    if(hdrs->type == HD_BFIELD)
	      format_envelope(stream, msgno, section, env,
			      tmp_pc, hdrs->h.b, prefix);

	    /* go through each header in list, v initialized above */
	    for(; q = *v; v++){
		if(is_an_env_hdr(q)){
		    /* pretty format for env hdrs */
		    format_env_hdr(stream, msgno, section,
				   env, tmp_pc, q, prefix);
		}
		else{
		    /*
		     * Go through h finding all occurences of this header
		     * and all continuation lines, and output.
		     */
		    for(current = h;
			h && delineate_this_header(h,q,current,&start,&finish);
			current = finish){
			if(rv = format_raw_hdr_string(start, finish,
						      tmp_pc, prefix))
			  goto write_error;
			else
			  start = finish;
		    }
		}
	    }
	}
    }

  write_error:

    gf_clear_so_writec(tmp_store);

    if(!rv){			/* valid data?  Do wrapping and filtering... */
	int	 column;
	char	*errstr, *display_filter = NULL, trigger[MAILTMPLEN];
	STORE_S *df_store = NULL;

	so_seek(tmp_store, 0L, 0);

	column = (flags & FM_DISPLAY) ? ps_global->ttyo->screen_cols : 80;

	/*
	 * Test for and act on any display filter
	 */
	if(display_filter = df_static_trigger(NULL, trigger)){
	    if(df_store = so_get(CharStar, NULL, EDIT_ACCESS)){

		gf_set_so_writec(&tmp_pc, df_store);
		gf_set_so_readc(&tmp_gc, df_store);
		if(errstr = dfilter(display_filter, tmp_store, tmp_pc, NULL)){
		    q_status_message1(SM_ORDER | SM_DING, 3, 3,
				      "Formatting error: %s", errstr);
		    rv = FHT_WRTERR;
		}
		else
		  so_seek(df_store, 0L, 0);

		gf_clear_so_writec(df_store);
	    }
	    else{
		q_status_message1(SM_ORDER | SM_DING, 3, 3,
				  "No space for filtered text: %s", errstr);
		rv = FHT_WRTERR;
	    }
	}
	else{
	    so_seek(tmp_store, 0L, 0);
	    gf_set_so_readc(&tmp_gc, tmp_store);
	}

	if(!rv){
	    gf_filter_init();
	    gf_link_filter(gf_local_nvtnl, NULL);
	    if((F_ON(F_VIEW_SEL_URL, ps_global)
		|| F_ON(F_VIEW_SEL_URL_HOST, ps_global))
	       && (flags & FM_HANDLES))
	      gf_link_filter(gf_line_test,
			     gf_line_test_opt(url_hilite_hdr, NULL));

	    gf_link_filter(gf_wrap,
			   gf_wrap_filter_opt(column, column, 4,
		     GFW_ONCOMMA | ((flags & FM_HANDLES) ? GFW_HANDLES : 0)));
	    gf_link_filter(gf_nvtnl_local, NULL);

	    if(errstr = gf_pipe(tmp_gc, final_pc)){
		rv = FHT_WRTERR;
		q_status_message1(SM_ORDER | SM_DING, 3, 3,
				  "Can't build header : %s", errstr);
	    }
	}

	if(df_store){
	    gf_clear_so_readc(df_store);
	    so_give(&df_store);
	}
	else
	  gf_clear_so_readc(tmp_store);
    }

    so_give(&tmp_store);

    if(h)
      fs_give((void **)&h);

    if(fields)
      fs_give((void **)&fields);

    return(rv);
}



/*----------------------------------------------------------------------
  Format RAW header text for display

  Args: stream --
	msgno --
	pc --
	prefix --

  Result: 0 if all's well, -1 if write error, 1 if fetch error

  NOTE: Blank-line delimiter is NOT written here.  Newlines are written
	in the local convention.

 ----*/
int
format_raw_header(stream, msgno, section, pc, prefix)
    MAILSTREAM *stream;
    long	msgno;
    char       *section;
    gf_io_t	pc;
    char       *prefix;
{
    char *h = mail_fetch_header(stream, msgno, section, NULL, NULL, FT_PEEK);

    if(h){
	if(prefix && !gf_puts(prefix, pc))
	  return(FHT_WRTERR);

	while(*h){
	    if(ISRFCEOL(h)){
		h += 2;
		if(!gf_puts(NEWLINE, pc))
		  return(FHT_WRTERR);

		if(ISRFCEOL(h))		/* all done! */
		  return(FHT_OK);

		if(prefix && !gf_puts(prefix, pc))
		  return(FHT_WRTERR);
	    }
	    else if(F_OFF(F_PASS_CONTROL_CHARS, ps_global) && CAN_DISPLAY(*h)){
		if(!((*pc)('^') && (*pc)(*h++ + '@')))
		  return(FHT_WRTERR);
	    }
	    else if(!(*pc)(*h++))
	      return(FHT_WRTERR);
	}
    }
    else
      return(FHT_FTCHERR);

    return(FHT_OK);
}



/*----------------------------------------------------------------------
  Format c-client envelope data suitable for display

  Args: stream -- stream associated with this envelope
	msgno -- message number associated with this envelope
	e -- envelope
	pc -- place to write result
	which -- which header lines to write
	prefix -- string to write before each header line

  Result: 0 if all's well, -1 if write error, 1 if fetch error

  NOTE: Blank-line delimiter is NOT written here.  Newlines are written
	in the local convention.

 ----*/
void
format_envelope(s, n, sect, e, pc, which, prefix)
    MAILSTREAM *s;
    long	n;
    char       *sect;
    ENVELOPE   *e;
    gf_io_t     pc;
    long	which;
    char       *prefix;
{
    if(!e)
      return;

    if((which & FE_DATE) && e->date) {
	if(prefix)
	  gf_puts(prefix, pc);

	gf_puts("Date: ", pc);
	format_env_puts((char *) rfc1522_decode((unsigned char *) tmp_20k_buf,
						e->date, NULL), pc);
	gf_puts(NEWLINE, pc);
    }

    if((which & FE_FROM) && e->from)
      format_addr_string(s, n, sect, "From: ", e->from, prefix, pc);

    if((which & FE_REPLYTO) && e->reply_to
       && (!e->from || !address_is_same(e->reply_to, e->from)))
      format_addr_string(s, n, sect, "Reply-To: ", e->reply_to, prefix, pc);

    if((which & FE_TO) && e->to)
      format_addr_string(s, n, sect, "To: ", e->to, prefix, pc);

    if((which & FE_CC) && e->cc)
      format_addr_string(s, n, sect, "Cc: ", e->cc, prefix, pc);

    if((which & FE_BCC) && e->bcc)
      format_addr_string(s, n, sect, "Bcc: ", e->bcc, prefix, pc);

    if((which & FE_RETURNPATH) && e->return_path)
      format_addr_string(s, n, sect, "Return-Path: ", e->return_path,
			 prefix, pc);

    if((which & FE_NEWSGROUPS) && e->newsgroups && !ps_global->nr_mode){
	format_newsgroup_string("Newsgroups: ", e->newsgroups, prefix, pc);
	if(e->ngbogus)
	  q_status_message(SM_ORDER, 0, 3,
     "Unverified Newsgroup header -- Message MAY or MAY NOT have been posted");
    }

    if((which & FE_FOLLOWUPTO) && e->followup_to)
      format_newsgroup_string("Followup-To: ", e->followup_to, prefix, pc);

    if((which & FE_SUBJECT) && e->subject && e->subject[0]){
	size_t	       n;
	unsigned char *p, *tmp = NULL;

	if(prefix)
	  gf_puts(prefix, pc);

	gf_puts("Subject: ", pc);

	if((n = strlen(e->subject)) >= 20000)
	  p = tmp = (unsigned char *) fs_get((n + 1) * sizeof(unsigned char));
	else
	  p = (unsigned char *) tmp_20k_buf;
	  
	format_env_puts((char *) rfc1522_decode(p, e->subject, NULL), pc);

	if(tmp)
	  fs_give((void **) &tmp);

	gf_puts(NEWLINE, pc);
    }

    if((which & FE_SENDER) && e->sender
       && (!e->from || !address_is_same(e->sender, e->from)))
      format_addr_string(s, n, sect, "Sender: ", e->sender, prefix, pc);

    if((which & FE_MESSAGEID) && e->message_id){
	if(prefix)
	  gf_puts(prefix, pc);

	gf_puts("Message-ID: ", pc);
	format_env_puts((char *) rfc1522_decode((unsigned char *) tmp_20k_buf,
						e->message_id, NULL), pc);
	gf_puts(NEWLINE, pc);
    }

    if((which & FE_INREPLYTO) && e->in_reply_to){
	if(prefix)
	  gf_puts(prefix, pc);

	gf_puts("In-Reply-To: ", pc);
	format_env_puts((char *) rfc1522_decode((unsigned char *) tmp_20k_buf,
						e->in_reply_to, NULL), pc);
	gf_puts(NEWLINE, pc);
    }

    if((which & FE_REFERENCES) && e->references) {
	if(prefix)
	  gf_puts(prefix, pc);

	gf_puts("References: ", pc);
	format_env_puts((char *) rfc1522_decode((unsigned char *) tmp_20k_buf,
						e->references, NULL), pc);
	gf_puts(NEWLINE, pc);
    }
}



/*----------------------------------------------------------------------
     Format an address field, wrapping lines nicely at commas

  Args: field_name  -- The name of the field we're formatting ("TO: ", ...)
        addr        -- ADDRESS structure to format
        line_prefix -- A prefix string for each line such as "> "

 Result: A formatted, malloced string is returned.

The resulting lines formatted are 80 columns wide.
  ----------------------------------------------------------------------*/
void
format_addr_string(stream, msgno, section, field_name, addr, line_prefix, pc)
    MAILSTREAM *stream;
    long	msgno;
    char       *section;
    ADDRESS    *addr;
    char       *line_prefix, *field_name;
    gf_io_t	pc;
{
    char    *ptmp, *mtmp;
    int	     trailing = 0, group = 0;
    ADDRESS *atmp;

    if(!addr)
      return;

    if(line_prefix)
      gf_puts(line_prefix, pc);

    /*
     * quickly run down address list to make sure none are patently bogus.
     * If so, just blat raw field out.
     */
    for(atmp = addr; stream && atmp; atmp = atmp->next)
      if(atmp->host && atmp->host[0] == '.'){
	  char *field, *fields[2];

	  fields[1] = NULL;
	  fields[0] = cpystr(field_name);
	  if(ptmp = strchr(fields[0], ':'))
	    *ptmp = '\0';

	  if(field = pine_fetchheader_lines(stream, msgno, section, fields)){
	      char *h, *t;

	      for(t = h = field; *h ; t++)
		if(*t == '\015' && *(t+1) == '\012'){
		    *t = '\0';			/* tie off line */
		    format_env_puts(h, pc);
		    if(*(h = (++t) + 1)){	/* set new h and skip CRLF */
			gf_puts(NEWLINE, pc);	/* more to write */
			if(line_prefix)
			  gf_puts(line_prefix, pc);
		    }
		    else
		      break;
		}
		else if(!*t){			/* shouldn't happen much */
		    if(h != t)
		      format_env_puts(h, pc);

		    break;
		}

	      fs_give((void **)&field);
	  }

	  fs_give((void **)&fields[0]);
	  gf_puts(NEWLINE, pc);
	  dprint(2, (debugfile, "Error in \"%s\" field address\n", field_name));
	  return;
      }

    gf_puts(field_name, pc);

    while(addr){
	atmp	       = addr->next;		/* remember what's next */
	addr->next     = NULL;
	if(!addr->host && addr->mailbox){
	    mtmp = addr->mailbox;
	    addr->mailbox = cpystr((char *)rfc1522_decode(
			   (unsigned char *)tmp_20k_buf, addr->mailbox, NULL));
	}

	ptmp	       = addr->personal;	/* RFC 1522 personal name? */
	addr->personal = (char *) rfc1522_decode((unsigned char *)tmp_20k_buf,
						 addr->personal, NULL);

	if(!trailing)				/* 1st pass, just address */
	  trailing++;
	else{					/* else comma, unless */
	    if(!((group == 1 && addr->host)	/* 1st addr in group, */
	       || (!addr->host && !addr->mailbox))){ /* or end of group */
		gf_puts(",", pc);
#if	0
		gf_puts(NEWLINE, pc);		/* ONE address/line please */
		gf_puts("   ", pc);
#endif
	    }

	    gf_puts(" ", pc);
	}

	pine_rfc822_write_address_noquote(addr, pc, &group);
	addr->personal = ptmp;			/* restore old personal ptr */
	if(!addr->host && addr->mailbox){
	    fs_give((void **)&addr->mailbox);
	    addr->mailbox = mtmp;
	}

	addr->next = atmp;
	addr       = atmp;
    }

    gf_puts(NEWLINE, pc);
}


static char *rspecials_minus_quote_and_dot = "()<>@,;:\\[]";
				/* RFC822 continuation, must start with CRLF */
#define RFC822CONT "\015\012    "

/* Write RFC822 address with some quoting turned off.
 * Accepts: 
 *	    address to interpret
 *
 * (This is a copy of c-client's rfc822_write_address except
 *  we don't quote double quote and dot in personal names. It writes
 *  to a gf_io_t instead of to a buffer so that we don't have to worry
 *  about fixed sized buffer overflowing.)
 *
 * The idea is that there are some places where we'd just like to display
 * the personal name as is before applying confusing quoting. However,
 * we do want to be careful not to break things that should be quoted so
 * we'll only use this where we are sure. Quoting may look ugly but it
 * doesn't usually break anything.
 */
void
pine_rfc822_write_address_noquote(adr, pc, group)
    ADDRESS *adr;
    gf_io_t  pc;
    int	    *group;
{
  extern const char *rspecials;

    if (adr->host) {		/* ordinary address? */
      if (!(adr->personal || adr->adl)) pine_rfc822_address (adr, pc);
      else {			/* no, must use phrase <route-addr> form */
        if (adr->personal)
	  pine_rfc822_cat (adr->personal, rspecials_minus_quote_and_dot, pc);

        gf_puts(" <", pc);	/* write address delimiter */
        pine_rfc822_address(adr, pc);
        gf_puts (">", pc);	/* closing delimiter */
      }

      if(*group)
	(*group)++;
    }
    else if (adr->mailbox) {	/* start of group? */
				/* yes, write group name */
      pine_rfc822_cat (adr->mailbox, rspecials, pc);

      gf_puts (": ", pc);	/* write group identifier */
      *group = 1;		/* in a group */
    }
    else if (*group) {		/* must be end of group (but be paranoid) */
      gf_puts (";", pc);
      *group = 0;		/* no longer in that group */
    }
}

/* Write RFC822 route-address to string
 * Accepts:
 *	    address to interpret
 */

void
pine_rfc822_address (adr, pc)
    ADDRESS *adr;
    gf_io_t  pc;
{
  extern char *wspecials;

  if (adr && adr->host) {	/* no-op if no address */
    if (adr->adl) {		/* have an A-D-L? */
      gf_puts (adr->adl, pc);
      gf_puts (":", pc);
    }
				/* write mailbox name */
    pine_rfc822_cat (adr->mailbox, wspecials, pc);
    if (*adr->host != '@') {	/* unless null host (HIGHLY discouraged!) */
      gf_puts ("@", pc);	/* host delimiter */
      gf_puts (adr->host, pc);	/* write host name */
    }
  }
}


/* Concatenate RFC822 string
 * Accepts: 
 *	    pointer to string to concatenate
 *	    list of special characters
 */

void
pine_rfc822_cat (src, specials, pc)
    char *src;
    const char *specials;
    gf_io_t  pc;
{
  char *s;

  if (strpbrk (src,specials)) {	/* any specials present? */
    gf_puts ("\"", pc);		/* opening quote */
				/* truly bizarre characters in there? */
    while (s = strpbrk (src,"\\\"")) {
      char save[2];

      /* turn it into a null-terminated piece */
      save[0] = *s;
      save[1] = '\0';
      *s = '\0';
      gf_puts (src, pc);	/* yes, output leader */
      *s = save[0];
      gf_puts ("\\", pc);	/* quoting */
      gf_puts (save, pc);	/* output the bizarre character */
      src = ++s;		/* continue after the bizarre character */
    }
    if (*src) gf_puts (src, pc);/* output non-bizarre string */
    gf_puts ("\"", pc);		/* closing quote */
  }
  else gf_puts (src, pc);	/* otherwise it's the easy case */
}



/*----------------------------------------------------------------------
  Format an address field, wrapping lines nicely at commas

  Args: field_name  -- The name of the field we're formatting ("TO:", Cc:...)
        newsgrps    -- ADDRESS structure to format
        line_prefix -- A prefix string for each line such as "> "

  Result: A formatted, malloced string is returned.

The resuling lines formatted are 80 columns wide.
  ----------------------------------------------------------------------*/
void
format_newsgroup_string(field_name, newsgrps, line_prefix, pc)
    char    *newsgrps;
    char    *line_prefix, *field_name;
    gf_io_t  pc;
{
    char     buf[MAILTMPLEN];
    int	     trailing = 0, llen, alen, plen = 0;
    char    *next_ng;
    
    if(!newsgrps || !*newsgrps)
      return;
    
    if(line_prefix)
      gf_puts(line_prefix, pc);

    gf_puts(field_name, pc);

    if(line_prefix)
      plen = strlen(line_prefix);

    llen = plen + strlen(field_name);
    while(*newsgrps){
        for(next_ng = newsgrps; *next_ng && *next_ng != ','; next_ng++);
        strncpy(buf, newsgrps, next_ng - newsgrps);
        buf[next_ng - newsgrps] = '\0';
        newsgrps = next_ng;
        if(*newsgrps)
          newsgrps++;
	alen = strlen(buf);
	if(!trailing){			/* first time thru, just address */
	    llen += alen;
	    trailing++;
	}
	else{				/* else preceding comma */
	    gf_puts(",", pc);
	    llen++;

	    if(alen + llen + 1 > 76){
		gf_puts(NEWLINE, pc);
		if(line_prefix)
		  gf_puts(line_prefix, pc);

		gf_puts("    ", pc);
		llen = alen + plen + 5;
	    }
	    else{
		gf_puts(" ", pc);
		llen += alen + 1;
	    }
	}

	if(alen && llen > 76){		/* handle long addresses */
	    register char *q, *p = &buf[alen-1];

	    while(p > buf){
		if(isspace((unsigned char)*p)
		   && (llen - (alen - (int)(p - buf))) < 76){
		    for(q = buf; q < p; q++)
		      (*pc)(*q);	/* write character */

		    gf_puts(NEWLINE, pc);
		    gf_puts("    ", pc);
		    gf_puts(p, pc);
		    break;
		}
		else
		  p--;
	    }

	    if(p == buf)		/* no reasonable break point */
	      gf_puts(buf, pc);
	}
	else
	  gf_puts(buf, pc);
    }

    gf_puts(NEWLINE, pc);
}



/*----------------------------------------------------------------------
  Format a text field that's part of some raw (non-envelope) message header

  Args: start --
        finish --
	pc -- 
	prefix --

  Result: Semi-digested text (RFC 1522 decoded, anyway) written with "pc"

  ----------------------------------------------------------------------*/
int
format_raw_hdr_string(start, finish, pc, prefix)
    char    *start;
    char    *finish;
    gf_io_t  pc;
    char    *prefix;
{
    register char *current;
    unsigned char *p, *tmp = NULL;
    size_t	   n;
    char	   ch;
    int		   rv = FHT_OK;

    if(prefix && !gf_puts(prefix, pc))
      return(FHT_WRTERR);

    ch = *finish;
    *finish = '\0';

    if((n = finish - start) > 20000)
      p = tmp = (unsigned char *) fs_get((n + 1) * sizeof(unsigned char));
    else
      p = (unsigned char *) tmp_20k_buf;

    if(islower((unsigned char)(*start)))
      *start = toupper((unsigned char)(*start));

    current = (char *) rfc1522_decode(p, start, NULL);

    /* output from start to finish */
    while(*current && rv == FHT_OK)
      if(ISRFCEOL(current)){
	  if(!gf_puts(NEWLINE, pc)
	     || (*(current += 2) && prefix && !gf_puts(prefix, pc)))
	    rv = FHT_WRTERR;
      }
      else if(F_OFF(F_PASS_CONTROL_CHARS, ps_global) && CAN_DISPLAY(*current)){
	  if(!((*pc)('^') && (*pc)(*current++ + '@')))
	    rv = FHT_WRTERR;
      }
      else if(!(*pc)(*current++))
	rv = FHT_WRTERR;

    if(tmp)
      fs_give((void **) &tmp);

    *finish = ch;
    return(rv);
}




/*----------------------------------------------------------------------
  Format a text field that's part of some raw (non-envelope) message header

  Args: s --
	pc -- 

  Result: Output

  ----------------------------------------------------------------------*/
int
format_env_puts(s, pc)
    char    *s;
    gf_io_t  pc;
{
    if(F_ON(F_PASS_CONTROL_CHARS, ps_global))
      return(gf_puts(s, pc));

    for(; *s; s++)
      if(CAN_DISPLAY(*s)){
	  if(!((*pc)('^') && (*pc)(*s + '@')))
	    return(0);
      }
      else if(!(*pc)(*s))
	return(0);

    return(1);
}




/*----------------------------------------------------------------------
    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"

Note that size of the strings are carefully calculated never to overflow 
the static buffer:
    number  < 20,  description limited to 100, type_desc < 200,
    size    < 20,  second line < 100           other stuff < 60
 ----*/
char *
part_desc(number, body, type, width, pc)
     char    *number;
     BODY    *body;
     int      type, width;
     gf_io_t  pc;
{
    char *t;

    if(!gf_puts(NEWLINE, pc))
      return("No space for description");

    sprintf(tmp_20k_buf, "Part %s, %s%.2048s%s%s  %s%s.",
            number,
            body->description == NULL ? "" : "\"",
            body->description == NULL ? ""
	      : (char *)rfc1522_decode((unsigned char *)(tmp_20k_buf+10000),
				       body->description, NULL),
            body->description == NULL ? "" : "\"  ",
            type_desc(body->type, body->subtype, body->parameter, 1),
            body->type == TYPETEXT ? comatose(body->size.lines) :
                                     byte_string(body->size.bytes),
            body->type == TYPETEXT ? " lines" : "");


    t = &tmp_20k_buf[strlen(tmp_20k_buf)];

    if(type){
	sstrcpy(&t, "\015\012");
	switch(type) {
	  case 1:
	    sstrcpy(&t,
	       "Not Shown. Use the \"V\" command to view or save this part.");
	    break;

	  case 2:
	    sstrcpy(&t, "Cannot ");
	    if(body->type != TYPEAUDIO && body->type != TYPEVIDEO)
	      sstrcpy(&t, "dis");

	    sstrcpy(&t, 
	       "play this part. Press \"V\" then \"S\" to save in a file.");
	    break;

	  case 3:
	    sstrcpy(&t, "Unable to print this part.");
	    break;
	}
    }

    if(!(t = format_editorial(tmp_20k_buf, width, pc))){
	if(!gf_puts(NEWLINE, pc))
	  t = "No space for description";
    }

    return(t);
}



/*----------------------------------------------------------------------
   routine for displaying text on the screen.

  Args: sparms -- structure of args controlling what happens outside
		  just the business of managing text scrolling
 
   This displays in three different kinds of text. One is an array of
lines passed in in text_array. The other is a simple long string of
characters passed in in text.

  The style determines what some of the error messages will be, and
what commands are available as different things are appropriate for
help text than for message text etc.

 ---*/

int
scrolltool(sparms)
    SCROLL_S *sparms;
{
    register long    cur_top_line,  num_display_lines;
    int              result, done, ch, cmd, found_on, found_on_col,
		     first_view, force, scroll_lines, km_size,
		     cursor_row, cursor_col, km_popped;
    struct key_menu *km;
    HANDLE_S	    *next_handle;
    bitmap_t         bitmap;
    OtherMenu        what;
    Pos		     whereis_pos;

    num_display_lines	      = SCROLL_LINES(ps_global);
    km_popped		      = 0;
    ps_global->mangled_header = 1;
    ps_global->mangled_footer = 1;
    ps_global->mangled_body   = !sparms->body_valid;
      

    what	    = sparms->keys.what;	/* which key menu to display */
    cur_top_line    = 0;
    done	    = 0;
    found_on	    = -1;
    found_on_col    = -1;
    first_view	    = 1;
    force	    = 0;
    ch		    = 'x';			/* for first time through */
    whereis_pos.row = 0;
    whereis_pos.col = 0;
    next_handle	    = sparms->text.handles;

    set_scroll_text(sparms, cur_top_line, scroll_state(SS_NEW));
    format_scroll_text();

    if(km = sparms->keys.menu){
	memcpy(bitmap, sparms->keys.bitmap, sizeof(bitmap_t));
    }
    else{
	setbitmap(bitmap);
	if(ps_global->anonymous) {
	    km = &nr_anon_view_keymenu;
	}
	else if(ps_global->nr_mode) {
	    km = &nr_view_keymenu;
	}
	else{
	  km = &simple_text_keymenu;
#ifdef	_WINDOWS
	  sparms->mouse.popup = simple_text_popup;
#endif
	}
    }

    if(!sparms->bar.title)
      sparms->bar.title = "Text";

    if(sparms->bar.style == TitleBarNone)
      sparms->bar.style = MsgTextPercent;

    switch(sparms->start.on){
      case LastPage :
	cur_top_line = max(0, scroll_text_lines() - (num_display_lines-2));
	if(F_ON(F_SHOW_CURSOR, ps_global)){
	    whereis_pos.row = scroll_text_lines() - cur_top_line;
	    found_on	    = scroll_text_lines() - 1;
	}

	break;

      case Fragment :
	if(sparms->start.frag){
	    (void) url_local_fragment(sparms->start.frag);

	    cur_top_line = scroll_state(SS_CUR)->top_text_line;

	    if(F_ON(F_SHOW_CURSOR, ps_global)){
		whereis_pos.row = scroll_text_lines() - cur_top_line;
		found_on	= scroll_text_lines() - 1;
	    }
	}

	break;

      case Handle :
	if(scroll_handle_obscured(sparms->text.handles))
	  cur_top_line = scroll_handle_reframe(-1, TRUE);

	break;

      default :			/* no-op */
	break;
    }

    /* prepare for calls below to tell us where to go */
    ps_global->next_screen = SCREEN_FUN_NULL;

    cancel_busy_alarm(-1);

    while(!done) {
	if(km_popped){
	    km_popped--;
	    if(km_popped == 0){
		clearfooter(ps_global);
		ps_global->mangled_body = 1;
	    }
	}

	if(ps_global->mangled_screen) {
	    ps_global->mangled_header = 1;
	    ps_global->mangled_footer = 1;
            ps_global->mangled_body   = 1;
	}

        if(streams_died())
          ps_global->mangled_header = 1;

        dprint(9, (debugfile, "@@@@ current:%ld\n",
		   mn_get_cur(ps_global->msgmap)));


        /*==================== All Screen painting ====================*/
        /*-------------- The title bar ---------------*/
	update_scroll_titlebar(cur_top_line, ps_global->mangled_header);

	if(ps_global->mangled_screen){
	    /* this is the only line not cleared by header, body or footer
	     * repaint calls....
	     */
	    ClearLine(1);
            ps_global->mangled_screen = 0;
	}

        /*---- Scroll or update the body of the text on the screen -------*/
        cur_top_line		= scroll_scroll_text(cur_top_line, next_handle,
						     ps_global->mangled_body);
	ps_global->redrawer	= redraw_scroll_text;
        ps_global->mangled_body = 0;

	/*--- Check to see if keymenu might change based on next_handle --*/
	if(sparms->text.handles != next_handle)
	  ps_global->mangled_footer = 1;
	
	if(next_handle)
	  sparms->text.handles = next_handle;

        /*------------- The key menu footer --------------------*/
	if(ps_global->mangled_footer || sparms->keys.each_cmd){
	    if(km_popped){
		FOOTER_ROWS(ps_global) = 3;
		clearfooter(ps_global);
	    }

	    if(F_ON(F_ARROW_NAV, ps_global)){
		menu_clear_binding(km, KEY_LEFT);
		if((cmd = menu_clear_binding(km, '<')) != MC_UNKNOWN){
		    menu_add_binding(km, '<', cmd);
		    menu_add_binding(km, KEY_LEFT, cmd);
		}
	    }

	    if(F_ON(F_ARROW_NAV, ps_global)){
		menu_clear_binding(km, KEY_RIGHT);
		if((cmd = menu_clear_binding(km, '>')) != MC_UNKNOWN){
		    menu_add_binding(km, '>', cmd);
		    menu_add_binding(km, KEY_RIGHT, cmd);
		}
	    }

	    if(sparms->keys.each_cmd){
		(*sparms->keys.each_cmd)(sparms,
				 scroll_handle_obscured(sparms->text.handles));
		memcpy(bitmap, sparms->keys.bitmap, sizeof(bitmap_t));
	    }

	    if(menu_binding_index(km, MC_JUMP) >= 0){
	      for(cmd = 0; cmd < 10; cmd++)
		if(F_ON(F_ENABLE_JUMP, ps_global))
		  (void) menu_add_binding(km, '0' + cmd, MC_JUMP);
		else
		  (void) menu_clear_binding(km, '0' + cmd);
	    }

            draw_keymenu(km, bitmap, ps_global->ttyo->screen_cols,
			 1-FOOTER_ROWS(ps_global), 0, what);
	    what = SameMenu;
	    ps_global->mangled_footer = 0;
	    if(km_popped){
		FOOTER_ROWS(ps_global) = 1;
		mark_keymenu_dirty();
	    }
	}

	if((ps_global->first_time_user || ps_global->show_new_version)
	   && first_view && sparms->text.handles
	   && (sparms->text.handles->next || sparms->text.handles->prev)
	   && !sparms->quell_help)
	  q_status_message(SM_ORDER, 0, 3, HANDLE_INIT_MSG);

	/*============ Check for New Mail and CheckPoint ============*/
        if(new_mail(force, first_view ? 0 : NM_TIMING(ch), NM_STATUS_MSG) >= 0)
	  update_scroll_titlebar(cur_top_line, 1);

	/*
	 * If an expunge of the current message happened during the
	 * new mail check we want to bail out of here. See mm_expunged.
	 */
	if(ps_global->next_screen != SCREEN_FUN_NULL){
	    done = 1;
	    continue;
	}

	if(first_view && num_display_lines >= scroll_text_lines())
	  q_status_message1(SM_INFO, 0, 1, "ALL of %s", STYLE_NAME(sparms));
			    

	force      = 0;		/* may not need to next time around */
	first_view = 0;		/* check_point a priority any more? */

	/*==================== Output the status message ==============*/
	if(!sparms->no_stat_msg){
	    if(km_popped){
		FOOTER_ROWS(ps_global) = 3;
		mark_status_unknown();
	    }

	    display_message(ch);
	    if(km_popped){
		FOOTER_ROWS(ps_global) = 1;
		mark_status_unknown();
	    }
	}

	if(F_ON(F_SHOW_CURSOR, ps_global)){
#ifdef	WINDOWS
	    if(cur_top_line != scroll_state(SS_CUR)->top_text_line)
	      whereis_pos.row = 0;
#endif
	
	    if(whereis_pos.row > 0){
		cursor_row  = SCROLL_LINES_ABOVE(ps_global)
				+ whereis_pos.row - 1;
		cursor_col  = whereis_pos.col;
	    }
	    else{
		POSLIST_S  *lp = NULL;

		if(sparms->text.handles &&
		   !scroll_handle_obscured(sparms->text.handles)){
		    SCRLCTRL_S *st = scroll_state(SS_CUR);

		    for(lp = sparms->text.handles->loc; lp; lp = lp->next)
		      if(lp->where.row >= st->top_text_line
			 && lp->where.row < st->top_text_line
							  + st->screen.length){
			  cursor_row = lp->where.row - cur_top_line
					      + SCROLL_LINES_ABOVE(ps_global);
			  cursor_col = lp->where.col;
			  break;
		      }
		}

		if(!lp){
		    cursor_col = 0;
		    /* first new line of text */
		    cursor_row  = SCROLL_LINES_ABOVE(ps_global) +
			((cur_top_line == 0) ? 0 : ps_global->viewer_overlap);
		}
	    }
	}
	else{
	    cursor_col = 0;
	    cursor_row = ps_global->ttyo->screen_rows
			    - SCROLL_LINES_BELOW(ps_global);
	}

	MoveCursor(cursor_row, cursor_col);

	/*================ Get command and validate =====================*/
#ifdef	MOUSE
#ifndef	WIN32
	if(sparms->text.handles)
#endif
	{
	    mouse_in_content(KEY_MOUSE, -1, -1, 0x5, 0);
	    register_mfunc(mouse_in_content, HEADER_ROWS(ps_global), 0,
			   ps_global->ttyo->screen_rows
						- (FOOTER_ROWS(ps_global) + 1),
			   ps_global->ttyo->screen_cols);
	}
#endif
#ifdef	_WINDOWS
	mswin_allowcopy(mswin_readscrollbuf);
	mswin_setscrollcallback(pcpine_do_scroll);

	if(sparms->help.text != NO_HELP && !ps_global->nr_mode)
	  mswin_sethelptextcallback(pcpine_help_scroll);

	mswin_setresizecallback(pcpine_resize_scroll);
#endif
        ch = read_command();
#ifdef	MOUSE
#ifndef	WIN32
	if(sparms->text.handles)
#endif
	  clear_mfunc(mouse_in_content);
#endif
#ifdef	_WINDOWS
	mswin_allowcopy(NULL);
	mswin_setscrollcallback(NULL);
	mswin_sethelptextcallback(NULL);
	mswin_clearresizecallback(pcpine_resize_scroll);
	cur_top_line = scroll_state(SS_CUR)->top_text_line;
#endif

	cmd = menu_command(ch, km);

	if(km_popped)
	  switch(cmd){
	    case MC_NONE :
	    case MC_OTHER :
	    case MC_RESIZE:
	    case MC_REPAINT :
	      km_popped++;
	      break;
	    
	    default:
	      clearfooter(ps_global);
	      break;
	  }


	/*============= Execute command =======================*/
	switch(cmd){

            /* ------ Help -------*/
	  case MC_HELP :
	    if(FOOTER_ROWS(ps_global) == 1 && km_popped == 0){
		km_popped = 2;
		ps_global->mangled_footer = 1;
		break;
	    }

	    whereis_pos.row = 0;
            if(sparms->help.text == NO_HELP || ps_global->nr_mode){
                q_status_message(SM_ORDER, 0, 5,
				 "No help text currently available");
                break;
            }

	    km_size = FOOTER_ROWS(ps_global);

	    helper(sparms->help.text, sparms->help.title, 0);

	    if(ps_global->next_screen != main_menu_screen
	       && km_size == FOOTER_ROWS(ps_global)) {
		/* Have to reset because helper uses scroll_text */
		num_display_lines	  = SCROLL_LINES(ps_global);
		ps_global->mangled_screen = 1;
	    }
	    else
	      done = 1;

            break; 


            /*---------- Roll keymenu ------*/
	  case MC_OTHER :
	    if(F_OFF(F_USE_FK, ps_global))
	      warn_other_cmds();

	    what = NextMenu;
	    ps_global->mangled_footer = 1;
	    break;
            

            /* -------- Scroll back one page -----------*/
	  case MC_PAGEUP :
	    whereis_pos.row = 0;
	    if(cur_top_line) {
		scroll_lines = min(max(num_display_lines -
			ps_global->viewer_overlap, 1), num_display_lines);
		cur_top_line -= scroll_lines;
		if(cur_top_line <= 0){
		    cur_top_line = 0;
		    q_status_message1(SM_INFO, 0, 1, "START of %s",
				      STYLE_NAME(sparms));
		}
	    }
	    else{
		q_status_message1(SM_ORDER, 0, 1, "Already at start of %s",
				  STYLE_NAME(sparms));

		/* hilite last available handle */
		if(sparms->text.handles){
		    HANDLE_S *h = sparms->text.handles;

		    while((h = scroll_handle_prev_sel(h))
			  && !scroll_handle_obscured(h))
		      next_handle = h;
		}
	    }


            break;


            /*---- Scroll down one page -------*/
	  case MC_PAGEDN :
	    if(cur_top_line + num_display_lines < scroll_text_lines()){
		whereis_pos.row = 0;
		scroll_lines = min(max(num_display_lines -
			ps_global->viewer_overlap, 1), num_display_lines);
		cur_top_line += scroll_lines;

		if(cur_top_line + num_display_lines >= scroll_text_lines())
		  q_status_message1(SM_INFO, 0, 1, "END of %s",
				    STYLE_NAME(sparms));
            }
	    else if(!sparms->end_scroll
		    || !(done = (*sparms->end_scroll)(sparms))){
		q_status_message1(SM_ORDER, 0, 1, "Already at end of %s",
				  STYLE_NAME(sparms));
		/* hilite last available handle */
		if(sparms->text.handles){
		    HANDLE_S *h = sparms->text.handles;

		    while(h = scroll_handle_next_sel(h))
		      next_handle = h;
		}
	    }

            break;


            /*------ Scroll down one line -----*/
	  case MC_CHARDOWN :
	    if(sparms->vert_handle && sparms->text.handles){
		HANDLE_S *h, *h2;
		int	  i, j, k;

		for(h = NULL,
		    i = (h2 = sparms->text.handles)->loc->where.row + 1,
		    j = h2->loc->where.col,
		    k = h2->key;
		    h2 && (!h || (h->loc->where.row == h2->loc->where.row));
		    h2 = h2->next)
		  if(h2->key != k		  /* must be different key */
		     && h2->loc->where.row >= i){ /* ... below current line */
		      if(h2->loc->where.col > j){ /* ... pref'bly to left */
			  if(!h)
			    h = h2;

			  break;
		      }
		      else
			h = h2;
		  }

		if(h){
		    whereis_pos.row = 0;
		    next_handle = h;
		    if(result = scroll_handle_obscured(next_handle)){
			long new_top;

			if(scroll_handle_obscured(sparms->text.handles)
			   && result > 0)
			  next_handle = sparms->text.handles;

			ps_global->mangled_body++;
			if(new_top = scroll_handle_reframe(next_handle->key,0))
			  cur_top_line = new_top;
		    }
		}
	    }
            else if(ch == ctrl('N') || F_ON(F_FORCE_ARROWS, ps_global)
	       || !(sparms->text.handles
		    && (next_handle
				 = scroll_handle_next(sparms->text.handles)))){
		if(cur_top_line + num_display_lines < scroll_text_lines()){
		    whereis_pos.row = 0;
		    cur_top_line++;
		    if(cur_top_line + num_display_lines >= scroll_text_lines())
		      q_status_message1(SM_INFO, 0, 1, "END of %s",
					STYLE_NAME(sparms));
		}
		else
		  q_status_message1(SM_ORDER, 0, 1, "Already at end of %s",
				    STYLE_NAME(sparms));
	    }

	    break;


            /* ------ Scroll back up one line -------*/
	  case MC_CHARUP :
	    if(sparms->vert_handle && sparms->text.handles){
		HANDLE_S *h, *h2;
		int	  i, j, k;
		
		for(h = NULL,
		    i = (h2 = sparms->text.handles)->loc->where.row - 1,
		    j = h2->loc->where.col,
		    k = h2->key;
		    h2 && (!h || (h->loc->where.row == h2->loc->where.row));
		    h2 = h2->prev)
		  if(h2->key != k		  /* must be new key */
		     && h2->loc->where.row <= i){ /* ... above current line */
		      if(h2->loc->where.col < j){ /* ... pref'bly to right */
			  if(!h)
			    h = h2;

			  break;
		      }
		      else
			h = h2;
		  }

		if(h){
		    whereis_pos.row = 0;
		    next_handle = h;
		    if(result = scroll_handle_obscured(next_handle)){
			long new_top;

			if(scroll_handle_obscured(sparms->text.handles)
			   && result < 0)
			  next_handle = sparms->text.handles;

			ps_global->mangled_body++;
			if(new_top = scroll_handle_reframe(next_handle->key,0))
			  cur_top_line = new_top;
		    }
		}
	    }
	    else if(ch == ctrl('P') || F_ON(F_FORCE_ARROWS, ps_global)
	       || !(sparms->text.handles
		    && (next_handle
				 = scroll_handle_prev(sparms->text.handles)))){
		whereis_pos.row = 0;
		if(cur_top_line){
		    cur_top_line--;
		    if(cur_top_line == 0)
		      q_status_message1(SM_INFO, 0, 1, "START of %s",
					STYLE_NAME(sparms));
		}
		else
		  q_status_message1(SM_ORDER, 0, 1, "Already at start of %s",
				    STYLE_NAME(sparms));
	    }

	    break;


	  case MC_NEXT_HANDLE :
	    if(next_handle = scroll_handle_next_sel(sparms->text.handles)){
		whereis_pos.row = 0;
		if(result = scroll_handle_obscured(next_handle)){
		    long new_top;

		    if(scroll_handle_obscured(sparms->text.handles)
		       && result > 0)
		      next_handle = sparms->text.handles;

		    ps_global->mangled_body++;
		    if(new_top = scroll_handle_reframe(next_handle->key, 0))
		      cur_top_line = new_top;
		}
	    }
	    else{
		if(scroll_handle_obscured(sparms->text.handles)){
		    long new_top;

		    ps_global->mangled_body++;
		    if(new_top = scroll_handle_reframe(-1, 0)){
			whereis_pos.row = 0;
			cur_top_line = new_top;
		    }
		}

		q_status_message1(SM_ORDER, 0, 1,
				  "Already on last item in %s",
				  STYLE_NAME(sparms));
	    }

	    break;


	  case MC_PREV_HANDLE :
	    if(next_handle = scroll_handle_prev_sel(sparms->text.handles)){
		whereis_pos.row = 0;
		if(result = scroll_handle_obscured(next_handle)){
		    long new_top;

		    if(scroll_handle_obscured(sparms->text.handles)
		       && result < 0)
		      next_handle = sparms->text.handles;

		    ps_global->mangled_body++;
		    if(new_top = scroll_handle_reframe(next_handle->key, 0))
		      cur_top_line = new_top;
		}
	    }
	    else{
		if(scroll_handle_obscured(sparms->text.handles)){
		    long new_top;

		    ps_global->mangled_body++;
		    if(new_top = scroll_handle_reframe(-1, 0)){
			whereis_pos.row = 0;
			cur_top_line = new_top;
		    }
		}

		q_status_message1(SM_ORDER, 0, 1,
				  "Already on first item in %s",
				  STYLE_NAME(sparms));
	    }

	    break;


	    /*------  View the current handle ------*/
	  case MC_VIEW_HANDLE :
	    switch(scroll_handle_obscured(sparms->text.handles)){
	      default :
	      case 0 :
		switch(scroll_handle_launch(sparms->text.handles,
					sparms->text.handles->force_display)){
		  case 1 :
		    cmd = MC_EXIT;		/* propagate */
		    done = 1;
		    break;

		  case -1 :
		    cmd_cancelled("View");
		    break;

		  default :
		    break;
		}

		cur_top_line = scroll_state(SS_CUR)->top_text_line;
		break;

	      case 1 :
		q_status_message(SM_ORDER, 0, 2, HANDLE_BELOW_ERR);
		break;

	      case -1 :
		q_status_message(SM_ORDER, 0, 2, HANDLE_ABOVE_ERR);
		break;
	    }

	    break;

            /*---------- Search text (where is) ----------*/
	  case MC_WHEREIS :
            ps_global->mangled_footer = 1;
	    {long start_row;
	     int  start_col;

	     if(F_ON(F_SHOW_CURSOR,ps_global)){
		 if(found_on < 0
		    || found_on >= scroll_text_lines()
		    || found_on < cur_top_line
		    || found_on >= cur_top_line + num_display_lines){
		     start_row = cur_top_line;
		     start_col = 0;
		 }
		 else{
		     if(found_on_col < 0){
			 start_row = found_on + 1;
			 start_col = 0;
		     }
		     else{
			 start_row = found_on;
			 start_col = found_on_col+1;
		     }
		 }
	     }
	     else{
		 start_row = (found_on < 0
			      || found_on >= scroll_text_lines()
			      || found_on < cur_top_line
			      || found_on >= cur_top_line + num_display_lines)
				? cur_top_line : found_on + 1,
		 start_col = 0;
	     }

             found_on = search_text(-FOOTER_ROWS(ps_global), start_row,
				     start_col, tmp_20k_buf, &whereis_pos,
				     &found_on_col);

            if(found_on == -3){
		found_on = start_row;
		found_on_col = start_col - 1;
		whereis_pos.row = whereis_pos.row - cur_top_line + 1;
		q_status_message(SM_ORDER | SM_DING, 0, 3,
			         "Current line contains the only match");
	    }
            else if(found_on >= 0){
		int key;

		result = found_on < cur_top_line;
		key = (sparms->text.handles)
			? dot_on_handle(found_on, whereis_pos.col) : 0;

		if(F_ON(F_FORCE_LOW_SPEED,ps_global)
		   || ps_global->low_speed
		   || F_ON(F_SHOW_CURSOR,ps_global)
		   || key){
		    if((found_on >= cur_top_line + num_display_lines ||
		       found_on < cur_top_line) &&
		       num_display_lines > ps_global->viewer_overlap){
			cur_top_line = found_on - ((found_on > 0) ? 1 : 0);
			if(scroll_text_lines()-cur_top_line < 5)
			  cur_top_line = max(0,
			      scroll_text_lines()-min(5,num_display_lines));
		    }
		    /* else leave cur_top_line alone */
		}
		else{
		    cur_top_line = found_on - ((found_on > 0) ? 1 : 0);
		    if(scroll_text_lines()-cur_top_line < 5)
		      cur_top_line = max(0,
			  scroll_text_lines()-min(5,num_display_lines));
		}

		whereis_pos.row = whereis_pos.row - cur_top_line + 1;
		if(tmp_20k_buf[0])
		  q_status_message(SM_ORDER, 0, 3, tmp_20k_buf);
		else
		  q_status_message2(SM_ORDER, 0, 3,
				    "%sFound on line %s on screen",
				    result ? "Search wrapped to start. " : "",
				    int2string(whereis_pos.row));

		if(key){
		    if(sparms->text.handles->key < key)
		      for(next_handle = sparms->text.handles->next;
			  next_handle->key != key;
			  next_handle = next_handle->next)
			;
		    else
		      for(next_handle = sparms->text.handles;
			  next_handle->key != key;
			  next_handle = next_handle->prev)
			;
		}
            }
	    else if(found_on == -1)
	      cmd_cancelled("Search");
            else
	      q_status_message(SM_ORDER | SM_DING, 0, 3, "Word not found");
	    }

            break; 


            /*-------------- jump command -------------*/
	    /* NOTE: preempt the process_cmd() version because
	     *	     we need to get at the number..
	     */
	  case MC_JUMP :
	    if(jump_to(ps_global->msgmap, -FOOTER_ROWS(ps_global), ch))
	      done = 1;
	    else
	      ps_global->mangled_footer = 1;

	    break;


#ifdef MOUSE	    
            /*-------------- Mouse Event -------------*/
	  case MC_MOUSE:
	    {
	      static int lastWind;
	      MOUSEPRESS mp;
	      long	line;
	      int	key;

	      mouse_get_last (NULL, &mp);
	      mp.row -= 2;

	      /* The clicked line have anything special on it? */
	      if((line = cur_top_line + mp.row) < scroll_text_lines()
		 && (key = dot_on_handle(line, mp.col))){
		  switch(mp.button){
		    case M_BUTTON_RIGHT :
#ifdef	_WINDOWS
		      if(sparms->mouse.popup){
			  if(sparms->text.handles->key < key)
			    for(next_handle = sparms->text.handles->next;
				next_handle->key != key;
				next_handle = next_handle->next)
			      ;
			  else
			    for(next_handle = sparms->text.handles;
				next_handle->key != key;
				next_handle = next_handle->prev)
			      ;

			  if(sparms->mouse.popup){
			      cur_top_line = scroll_scroll_text(cur_top_line,
						     next_handle,
						     ps_global->mangled_body);
			      fflush(stdout);
			      switch((*sparms->mouse.popup)(sparms, key)){
				case 1 :
				  mp.doubleclick = 1;
				  break;

				case 2 :
				  done++;
				  break;
			      }
			  }
		      }

#endif
		      break;

		    case M_BUTTON_LEFT :
		      if(sparms->text.handles->key < key)
			for(next_handle = sparms->text.handles->next;
			    next_handle->key != key;
			    next_handle = next_handle->next)
			  ;
		      else
			for(next_handle = sparms->text.handles;
			    next_handle->key != key;
			    next_handle = next_handle->prev)
			  ;

		      if(mp.doubleclick){		/* launch url */
			  if(sparms->mouse.clickclick){
			      if((*sparms->mouse.clickclick)(sparms))
				done = 1;
			  }
			  else
			    switch(scroll_handle_launch(next_handle, TRUE)){
			      case 1 :
				cmd = MC_EXIT;		/* propagate */
				done = 1;
				break;

			      case -1 :
				cmd_cancelled("View");
				break;

			      default :
				break;
			    }

			  cur_top_line = scroll_state(SS_CUR)->top_text_line;
		      }
		      else if(sparms->mouse.click)
			(*sparms->mouse.click)(sparms);

		      break;

		    case M_BUTTON_MIDDLE :		/* NO-OP for now */
		      break;

		    default:				/* just ignore */
		      break;
		  }
	      }
#ifdef	_WINDOWS
	      else if(mp.button == M_BUTTON_RIGHT){
		  /*
		   * Toss generic popup on to the screen
		   */
		  if(sparms->mouse.popup)
		    if((*sparms->mouse.popup)(sparms, 0) == 2){
			done++;
		    }
	      }
#endif
	    }

	    break;
#endif	/* MOUSE */


            /*-------------- Display Resize -------------*/
          case MC_RESIZE :
	    if(sparms->resize_exit){
		/*
		 * Figure out char offset of the char in the top left
		 * corner of the display.  Pass it back to the
		 * fetcher/formatter and have it pass the offset
		 * back to us...
		 */
		done = 1;
		break;
	    }
	    /* else no reformatting neccessary, fall thru to repaint */


            /*-------------- refresh -------------*/
          case MC_REPAINT :
            num_display_lines = SCROLL_LINES(ps_global);
	    mark_status_dirty();
	    mark_keymenu_dirty();
	    mark_titlebar_dirty();
            ps_global->mangled_screen = 1;
	    force                     = 1;
            break;


            /*------- no op timeout to check for new mail ------*/
          case MC_NONE :
            break;


	    /*------- Forward displayed text ------*/
	  case MC_FWDTEXT :
	    forward_text(ps_global, sparms->text.text, sparms->text.src);
	    break;


	    /*----------- Save the displayed text ------------*/
	  case MC_SAVETEXT :
	    (void)simple_export(ps_global, sparms->text.text,
				sparms->text.src, "text", NULL);
	    break;


	    /*----------- Exit this screen ------------*/
	  case MC_EXIT :
	    done = 1;
	    break;


	    /*----------- Pop back to the Main Menu ------------*/
	  case MC_MAIN :
	    ps_global->next_screen = main_menu_screen;
	    done = 1;
	    break;


	    /*----------- Print ------------*/
	  case MC_PRINTTXT :
	    print_to_printer(sparms);
	    break;


	    /* ------- First handle on Line ------ */
	  case MC_GOTOBOL :
	    if(sparms->text.handles){
		next_handle = scroll_handle_boundary(sparms->text.handles, 
						     scroll_handle_prev_sel);

		break;
	    }
	    /* fall thru as bogus */

	    /* ------- Last handle on Line ------ */
	  case MC_GOTOEOL :
	    if(sparms->text.handles){
		next_handle = scroll_handle_boundary(sparms->text.handles, 
						     scroll_handle_next_sel);

		break;
	    }
	    /* fall thru as bogus */

            /*------- BOGUS INPUT ------*/
	  case MC_CHARRIGHT :
	  case MC_CHARLEFT :
          case MC_UNKNOWN :
	    if(sparms->bogus_input)
	      done = (*sparms->bogus_input)(ch);
	    else
	      bogus_command(ch, F_ON(F_USE_FK,ps_global) ? "F1" : "?");

            break;


	    /*------- Standard commands ------*/
          default:
	    whereis_pos.row = 0;
	    if(sparms->proc.tool)
	      result = (*sparms->proc.tool)(cmd, ps_global->msgmap, sparms);
	    else
	      result = process_cmd(ps_global, ps_global->mail_stream,
				   ps_global->msgmap, cmd, 0, &force);

	    dprint(7, (debugfile, "PROCESS_CMD return: %d\n", result));

	    if(ps_global->next_screen != SCREEN_FUN_NULL || result == 1){
		done = 1;
		if(cmd == MC_FULLHDR)
		  switch(km->which){
		    case 0:
		      sparms->keys.what = FirstMenu;
		      break;
		    case 1:
		      sparms->keys.what = SecondMenu;
		      break;
		    case 2:
		      sparms->keys.what = ThirdMenu;
		      break;
		    case 3:
		      sparms->keys.what = FourthMenu;
		      break;
		  }
	    }
	    else if(!scroll_state(SS_CUR)){
		num_display_lines	  = SCROLL_LINES(ps_global);
		ps_global->mangled_screen = 1;
	    }

	    break;

	} /* End of switch() */

	/* Need to frame some handles? */
	if(sparms->text.handles
	   && ((!next_handle
		&& handle_on_page(sparms->text.handles, cur_top_line,
				  cur_top_line + num_display_lines))
	       || (next_handle
		   && handle_on_page(next_handle, cur_top_line,
				     cur_top_line + num_display_lines))))
	  next_handle = scroll_handle_in_frame(cur_top_line);

    } /* End of while() -- loop executing commands */

    ps_global->redrawer	= NULL;	/* next statement makes this invalid! */
    zero_scroll_text();		/* very important to zero out on return!!! */
    scroll_state(SS_FREE);
#ifdef	_WINDOWS
    scroll_setrange(0L, 0L);
#endif
    return(cmd);
}



/*----------------------------------------------------------------------
      Print text on paper

    Args:  text -- The text to print out
	   source -- What type of source text is
	   message -- Message for open_printer()
    Handling of error conditions is very poor.

  ----*/
static int
print_to_printer(sparms)
    SCROLL_S *sparms;
{
    char message[64];

    sprintf(message, "%s ", STYLE_NAME(sparms));

    if(open_printer(message) != 0)
      return(-1);

    switch(sparms->text.src){
      case CharStar :
	if(sparms->text.text != (char *)NULL)
	  print_text((char *)sparms->text.text);

	break;

      case CharStarStar :
	if(sparms->text.text != (char **)NULL){
	    register char **t;

	    for(t = sparms->text.text; *t != NULL; t++){
		print_text(*t);
		print_text(NEWLINE);
	    }
	}

	break;

      case FileStar :
	if(sparms->text.text != (FILE *)NULL) {
	    size_t n;
	    int i;

	    fseek((FILE *)sparms->text.text, 0L, 0);
	    n = 20480 - 1;
	    while(i = fread((void *)tmp_20k_buf, sizeof(char),
			    n, (FILE *)sparms->text.text)) {
		tmp_20k_buf[i] = '\0';
		print_text(tmp_20k_buf);
	    }
	}

      default :
	break;
    }

    close_printer();
    return(0);
}


/*----------------------------------------------------------------------
   Search text being viewed (help or message)

      Args: q_line      -- The screen line to prompt for search string on
            start_line  -- Line number in text to begin search on
            start_col   -- Column to begin search at in first line of text
            cursor_pos  -- position of cursor is returned to caller here
			   (Actually, this isn't really the position of the
			    cursor because we don't know where we are on the
			    screen.  So row is set to the line number and col
			    is set to the right column.)
            offset_in_line -- Offset where match was found.

    Result: returns line number string was found on
            -1 for cancel
            -2 if not found
            -3 if only match is at start_col - 1
 ---*/
int
search_text(q_line, start_line, start_col, report, cursor_pos, offset_in_line)
    int   q_line;
    long  start_line;
    int   start_col;
    char *report;
    Pos  *cursor_pos;
    int  *offset_in_line;
{
    char        prompt[MAX_SEARCH+50], nsearch_string[MAX_SEARCH+1];
    HelpType	help;
    int         rc, flags;
    static char search_string[MAX_SEARCH+1] = { '\0' };
    static ESCKEY_S word_search_key[] = { { 0, 0, "", "" },
					 {ctrl('Y'), 10, "^Y", "First Line"},
					 {ctrl('V'), 11, "^V", "Last Line"},
					 {-1, 0, NULL, NULL}
					};

    report[0] = '\0';
    sprintf(prompt, "Word to search for [%s] : ", search_string);
    help = NO_HELP;
    nsearch_string[0] = '\0';

    while(1) {
	flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE | OE_KEEP_TRAILING_SPACE;
	
        rc = optionally_enter(nsearch_string, q_line, 0, MAX_SEARCH,
                              prompt, word_search_key, help, &flags);

        if(rc == 3) {
            help = help == NO_HELP ? h_oe_searchview : NO_HELP;
            continue;
        }
	else if(rc == 10){
	    strcpy(report, "Searched to First Line.");
	    cursor_pos->row = 0;
	    cursor_pos->col = 0;
	    return(0);
	}
	else if(rc == 11){
	    strcpy(report, "Searched to Last Line."); 
	    cursor_pos->row = max(scroll_text_lines() - 1, 0);
	    cursor_pos->col = 0;
	    return(cursor_pos->row);
	}

        if(rc != 4)
          break;
    }

    if(rc == 1 || (search_string[0] == '\0' && nsearch_string[0] == '\0'))
      return(-1);

    if(nsearch_string[0] != '\0')
      strcpy(search_string, nsearch_string);

    rc = search_scroll_text(start_line, start_col, search_string, cursor_pos,
			    offset_in_line);
    return(rc);
}



/*----------------------------------------------------------------------
  Update the scroll tool's titlebar

    Args:  cur_top_line --
	   redraw -- flag to force updating

  ----*/
void
update_scroll_titlebar(cur_top_line, redraw)
    long      cur_top_line;
    int	      redraw;
{
    SCRLCTRL_S *st = scroll_state(SS_CUR);
    int		num_display_lines = SCROLL_LINES(ps_global);
    long	new_line = (cur_top_line + num_display_lines > st->num_lines)
			     ? st->num_lines
			     : cur_top_line + num_display_lines;

    if(redraw){
	set_titlebar(st->parms->bar.title, ps_global->mail_stream,
		     ps_global->context_current, ps_global->cur_folder,
		     ps_global->msgmap, 1, st->parms->bar.style,
		     new_line, st->num_lines);
	ps_global->mangled_header = 0;
    }
    else if(st->parms->bar.style == TextPercent)
      update_titlebar_lpercent(new_line);
    else
      update_titlebar_percent(new_line);
}



/*----------------------------------------------------------------------
  manager of global (to this module, anyway) scroll state structures


  ----*/
SCRLCTRL_S *
scroll_state(func)
    int func;
{
    struct scrollstack {
	SCRLCTRL_S	    s;
	struct scrollstack *prev;
    } *s;
    static struct scrollstack *stack = NULL;

    switch(func){
      case SS_CUR:			/* no op */
	break;
      case SS_NEW:
	s = (struct scrollstack *)fs_get(sizeof(struct scrollstack));
	memset((void *)s, 0, sizeof(struct scrollstack));
	s->prev = stack;
	stack  = s;
	break;
      case SS_FREE:
	if(stack){
	    s = stack->prev;
	    fs_give((void **)&stack);
	    stack = s;
	}
	break;
      default:				/* BUG: should complain */
	break;
    }

    return(stack ? &stack->s : NULL);
}



/*----------------------------------------------------------------------
      Save all the data for scrolling text and paint the screen


  ----*/
void
set_scroll_text(sparms, current_line, st)
    SCROLL_S	*sparms;
    long	 current_line;
    SCRLCTRL_S	*st;
{
    /* save all the stuff for possible asynchronous redraws */
    st->parms		   = sparms;
    st->top_text_line      = current_line;
    st->screen.start_line  = SCROLL_LINES_ABOVE(ps_global);
    st->screen.other_lines = SCROLL_LINES_ABOVE(ps_global)
				+ SCROLL_LINES_BELOW(ps_global);
    st->screen.width	   = -1;	/* Force text formatting calculation */
}



/*----------------------------------------------------------------------
     Redraw the text on the screen, possibly reformatting if necessary

   Args None

 ----*/
void
redraw_scroll_text()
{
    int		i, offset;
    SCRLCTRL_S *st = scroll_state(SS_CUR);

    format_scroll_text();

    offset = (st->parms->text.src == FileStar) ? 0 : st->top_text_line;

#ifdef _WINDOWS
    mswin_beginupdate();
#endif
    /*---- Actually display the text on the screen ------*/
    for(i = 0; i < st->screen.length; i++){
	ClearLine(i + st->screen.start_line);
	if((offset + i) < st->num_lines) 
	  PutLine0n8b(i + st->screen.start_line, 0, st->text_lines[offset + i],
		      st->line_lengths[offset + i], st->parms->text.handles);
    }


    fflush(stdout);
#ifdef _WINDOWS
    mswin_endupdate();
#endif
}



/*----------------------------------------------------------------------
  Free memory used as scrolling buffers for text on disk.  Also mark
  text_lines as available
  ----*/
void
zero_scroll_text()
{
    SCRLCTRL_S	 *st = scroll_state(SS_CUR);
    register int  i;

    for(i = 0; i < st->lines_allocated; i++)
      if(st->parms->text.src == FileStar && st->text_lines[i])
	fs_give((void **)&st->text_lines[i]);
      else
	st->text_lines[i] = NULL;

    if(st->parms->text.src == FileStar && st->findex != NULL){
	fclose(st->findex);
	st->findex = NULL;
	if(st->fname){
	    unlink(st->fname);
	    fs_give((void **)&st->fname);
	}
    }

    if(st->text_lines)
      fs_give((void **)&st->text_lines);

    if(st->line_lengths)
      fs_give((void **) &st->line_lengths);
}



/*----------------------------------------------------------------------

Always format at least 20 chars wide. Wrapping lines would be crazy for
screen widths of 1-20 characters 
  ----*/
void
format_scroll_text()
{
    int		     i;
    char	    *p, **pp;
    SCRLCTRL_S	    *st = scroll_state(SS_CUR);
    register short  *ll;
    register char  **tl, **tl_end;

    if(!st || (st->screen.width == (i = ps_global->ttyo->screen_cols)
	       && st->screen.length == PGSIZE(st)))
        return;

    st->screen.width = max(20, i);
    st->screen.length = PGSIZE(st);

    if(st->lines_allocated == 0) {
        st->lines_allocated = TYPICAL_BIG_MESSAGE_LINES;
        st->text_lines = (char **)fs_get(st->lines_allocated *sizeof(char *));
	memset(st->text_lines, 0, st->lines_allocated * sizeof(char *));
        st->line_lengths = (short *)fs_get(st->lines_allocated *sizeof(short));
    }

    tl     = st->text_lines;
    ll     = st->line_lengths;
    tl_end = &st->text_lines[st->lines_allocated];

    if(st->parms->text.src == CharStarStar) {
        /*---- original text is already list of lines -----*/
        /*   The text could be wrapped nicely for narrow screens; for now
             it will get truncated as it is displayed */
        for(pp = (char **)st->parms->text.text; *pp != NULL;) {
            *tl++ = *pp++;
            *ll++ = st->screen.width;
            if(tl >= tl_end) {
		i = tl - st->text_lines;
                st->lines_allocated *= 2;
                fs_resize((void **)&st->text_lines,
                          st->lines_allocated * sizeof(char *));
                fs_resize((void **)&st->line_lengths,
                          st->lines_allocated*sizeof(short));
                tl     = &st->text_lines[i];
                ll     = &st->line_lengths[i];
                tl_end = &st->text_lines[st->lines_allocated];
            }
        }

	st->num_lines = tl - st->text_lines;
    }
    else if (st->parms->text.src == CharStar) {
	/*------ Format the plain text ------*/
	for(p = (char *)st->parms->text.text; *p; ) {
            *tl = p;

	    for(; *p && !(*p == RETURN || *p == LINE_FEED); p++)
	      ;

	    *ll = p - *tl;
	    ll++; tl++;
	    if(tl >= tl_end) {
		i = tl - st->text_lines;
		st->lines_allocated *= 2;
		fs_resize((void **)&st->text_lines,
			  st->lines_allocated * sizeof(char *));
		fs_resize((void **)&st->line_lengths,
			  st->lines_allocated*sizeof(short));
		tl     = &st->text_lines[i];
		ll     = &st->line_lengths[i];
		tl_end = &st->text_lines[st->lines_allocated];
	    }

	    if(*p == '\r' && *(p+1) == '\n') 
	      p += 2;
	    else if(*p == '\n' || *p == '\r')
	      p++;
	}

	st->num_lines = tl - st->text_lines;
    }
    else {
	/*------ Display text is in a file --------*/

	/*
	 * This is pretty much only useful under DOS where we can't fit
	 * all of big messages in core at once.  This scheme makes
	 * some simplifying assumptions:
	 *  1. Lines are on disk just the way we'll display them.  That
	 *     is, line breaks and such are left to the function that
	 *     writes the disk file to catch and fix.
	 *  2. We get away with this mainly because the DOS display isn't
	 *     going to be resized out from under us.
	 *
	 * The idea is to use the already alloc'd array of char * as a 
	 * buffer for sections of what's on disk.  We'll set up the first
	 * few lines here, and read new ones in as needed in 
	 * scroll_scroll_text().
	 *  
	 * but first, make sure there are enough buffer lines allocated
	 * to serve as a place to hold lines from the file.
	 *
	 *   Actually, this is also used under windows so the display will
	 *   be resized out from under us.  So I changed the following
	 *   to always
	 *	1.  free old text_lines, which may have been allocated
	 *	    for a narrow screen.
	 *	2.  insure we have enough text_lines
	 *	3.  reallocate all text_lines that are needed.
	 *   (tom unger  10/26/94)
	 */

	/* free old text lines, which may be too short. */
	for(i = 0; i < st->lines_allocated; i++)
	  if(st->text_lines[i]) 		/* clear alloc'd lines */
	    fs_give((void **)&st->text_lines[i]);

        /* Insure we have enough text lines. */
	if(st->lines_allocated < (2 * PGSIZE(st)) + 1){
	    st->lines_allocated = (2 * PGSIZE(st)) + 1; /* resize */

	    fs_resize((void **)&st->text_lines,
		      st->lines_allocated * sizeof(char *));
	    memset(st->text_lines, 0, st->lines_allocated * sizeof(char *));
	    fs_resize((void **)&st->line_lengths,
		      st->lines_allocated*sizeof(short));
	}

	/* reallocate all text lines that are needed. */
	for(i = 0; i <= PGSIZE(st); i++)
	  if(st->text_lines[i] == NULL)
	    st->text_lines[i] = (char *)fs_get((st->screen.width + 1)
							       * sizeof(char));

	tl = &st->text_lines[i];

	st->num_lines = make_file_index();

	ScrollFile(st->top_text_line);		/* then load them up */
    }

    /*
     * Efficiency hack.  If there are handles, fill in their
     * line number field for later...
     */
    if(st->parms->text.handles){
	long	  line;
	int	  i, col, n, key;
	HANDLE_S *h;

	for(line = 0; line < st->num_lines; line++)
	  for(i = 0, col = 0; i < st->line_lengths[line];)
	    switch(st->text_lines[line][i]){
	      case TAG_EMBED:
		i++;
		switch((i < st->line_lengths[line]) ? st->text_lines[line][i]
						    : 0){
		  case TAG_HANDLE:
		    for(key = 0, n = st->text_lines[line][++i]; n > 0; n--)
		      key = (key * 10) + (st->text_lines[line][++i] - '0');

		    i++;
		    for(h = st->parms->text.handles; h; h = h->next)
		      if(h->key == key){
			  scroll_handle_set_loc(&h->loc, line, col);
			  break;
		      }

		    if(!h)	/* anything behind us? */
		      for(h = st->parms->text.handles->prev; h; h = h->prev)
			if(h->key == key){
			    scroll_handle_set_loc(&h->loc, line, col);
			    break;
			}
		    
		    break;

		  case TAG_INVON:
		  case TAG_INVOFF:
		  case TAG_BOLDON:
		  case TAG_BOLDOFF:
		  case TAG_ULINEON:
		  case TAG_ULINEOFF:
		    i++;
		    break;
		
		  default:	/* literal embed char */
		    break;
		}

		break;

	      case ESCAPE:
		if(n = match_escapes(&st->text_lines[line][++i]))
		  i += (n-1);	/* Don't count escape for column */

		break;

	      case TAB:
		i++;
		while(((++col) &  0x07) != 0) /* add tab's spaces */
		  ;

		break;

	      default:
		i++, col++;
		break;
	    }
    }

#ifdef	_WINDOWS
    scroll_setrange (st->screen.length, st->num_lines);
#endif

    *tl = NULL;
}




/*
 * ScrollFile - scroll text into the st struct file making sure 'line'
 *              of the file is the one first in the text_lines buffer.
 *
 *   NOTE: talk about massive potential for tuning...
 *         Goes without saying this is still under constuction
 */
void
ScrollFile(line)
    long line;
{
    SCRLCTRL_S	 *st = scroll_state(SS_CUR);
    SCRLFILE_S	  sf;
    register int  i;
	     int j, state = 0;

    if(line <= 0){		/* reset and load first couple of pages */
	fseek((FILE *) st->parms->text.text, 0L, 0);
	line = 0L;
    }

    if(!st->text_lines)
      return;

    for(i = 0; i < PGSIZE(st); i++){
	/*** do stuff to get the file pointer into the right place ***/
	/*
	 * BOGUS: this is painfully crude right now, but I just want to get
	 * it going. 
	 *
	 * possibly in the near furture, an array of indexes into the 
	 * file that are the offset for the beginning of each line will
	 * speed things up.  Of course, this
	 * will have limits, so maybe a disk file that is an array
	 * of indexes is the answer.
	 */
	if(fseek(st->findex, (size_t)(line++) * sizeof(SCRLFILE_S), 0) < 0
	   || fread(&sf, sizeof(SCRLFILE_S), (size_t)1, st->findex) != 1
	   || fseek((FILE *) st->parms->text.text, sf.offset, 0) < 0
	   || !st->text_lines[i]
	   || (sf.len && !fgets(st->text_lines[i], sf.len + 1,
				(FILE *) st->parms->text.text)))
	  break;

	st->line_lengths[i] = sf.len;
    }

    for(; i < PGSIZE(st); i++)
      if(st->text_lines[i]){		/* blank out any unused lines */
	  *st->text_lines[i]  = '\0';
	  st->line_lengths[i] = 0;
      }
}


/*
 * make_file_index - do a single pass over the file containing the text
 *                   to display, recording line lengths and offsets.
 *    NOTE: This is never really to be used on a real OS with virtual
 *          memory.  This is the whole reason st->findex exists.  Don't
 *          want to waste precious memory on a stupid array that could 
 *          be very large.
 */
long
make_file_index()
{
    SCRLCTRL_S	  *st = scroll_state(SS_CUR);
    SCRLFILE_S	   sf;
    long	   l = 0L;
    int		   state = 0;

    if(!st->findex){
	if(!st->fname)
	  st->fname = temp_nam(NULL, "pi");

	if((st->findex = fopen(st->fname,"w+b")) == NULL)
	  return(0);
    }
    else
      fseek(st->findex, 0L, 0);

    fseek((FILE *)st->parms->text.text, 0L, 0);

    while(1){
	sf.len = st->screen.width + 1;
	if(scroll_file_line((FILE *) st->parms->text.text,
			    tmp_20k_buf, &sf, &state)){
	    fwrite((void *) &sf, sizeof(SCRLFILE_S), (size_t)1, st->findex);
	    l++;
	}
	else
	  break;
    }

    fseek((FILE *)st->parms->text.text, 0L, 0);

    return(l);
}



/*----------------------------------------------------------------------
     Get the next line to scroll from the given file

 ----*/
char *
scroll_file_line(fp, buf, sfp, wrapt)
    FILE       *fp;
    char       *buf;
    SCRLFILE_S *sfp;
    int	       *wrapt;
{
    register char *s = NULL;

    while(1){
	if(!s){
	    sfp->offset = ftell(fp);
	    if(!(s = fgets(buf, sfp->len, fp)))
	      return(NULL);			/* can't grab a line? */
	}

	if(!*s){
	    *wrapt = 1;			/* remember; that we wrapped */
	    break;
	}
	else if(*s == NEWLINE[0] && (!NEWLINE[1] || *(s+1) == NEWLINE[1])){
	    int empty = (*wrapt && s == buf);

	    *wrapt = 0;			/* turn off wrapped state */
	    if(empty)
	      s = NULL;			/* get a new line */
	    else
	      break;			/* done! */
	}
	else 
	  s++;
    }

    sfp->len = s - buf;
    return(buf);
}



/*----------------------------------------------------------------------
     Scroll the text on the screen

   Args:  new_top_line -- The line to be displayed on top of the screen
          redraw -- Flag to force a redraw even in nothing changed 

   Returns: resulting top line
   Note: the returned line number may be less than new_top_line if
	 reformatting caused the total line count to change.

 ----*/
long
scroll_scroll_text(new_top_line, handle, redraw)
    long      new_top_line;
    HANDLE_S *handle;
    int	      redraw;
{
    SCRLCTRL_S *st = scroll_state(SS_CUR);
    int		num_display_lines, l, top;
    POSLIST_S  *lp, *lp2;

    if(st->top_text_line == new_top_line && !redraw){
	if(handle && handle != st->parms->text.handles){
	    top = st->screen.start_line - new_top_line;
	    if(!scroll_handle_obscured(handle))
	      for(lp = handle->loc; lp; lp = lp->next)
		if((l = lp->where.row) >= st->top_text_line
		   && l < st->top_text_line + st->screen.length){
		    if(st->parms->text.src == FileStar)
		      l -= new_top_line;

		    PutLine0n8b(top + lp->where.row, 0, st->text_lines[l],
				st->line_lengths[l], handle);
		}

	    if(!scroll_handle_obscured(st->parms->text.handles))
	      for(lp = st->parms->text.handles->loc; lp; lp = lp->next)
	       if((l = lp->where.row) >= st->top_text_line
		  && l < st->top_text_line + st->screen.length){
		   for(lp2 = handle->loc; lp2; lp2 = lp2->next)
		     if(l == lp2->where.row)
		       break;

		   if(!lp2){
		       if(st->parms->text.src == FileStar)
			 l -= new_top_line;

		       PutLine0n8b(top + lp->where.row, 0, st->text_lines[l],
				   st->line_lengths[l], handle);
		   }
	       }

	    st->parms->text.handles = handle;	/* update current */
	}

	return(new_top_line);
    }

    num_display_lines = PGSIZE(st);

    format_scroll_text();

    if(st->top_text_line >= st->num_lines)	/* don't pop line count */
      new_top_line = st->top_text_line = max(st->num_lines - 1, 0);

    if(st->parms->text.src == FileStar)
      ScrollFile(new_top_line);		/* set up new st->text_lines */

#ifdef	_WINDOWS
    scroll_setrange (st->screen.length, st->num_lines);
    scroll_setpos (new_top_line);
#endif

    /* --- 
       Check out the scrolling situation. If we want to scroll, but BeginScroll
       says we can't then repaint,  + 10 is so we repaint most of the time.
      ----*/
    if(redraw ||
       (st->top_text_line - new_top_line + 10 >= num_display_lines ||
        new_top_line - st->top_text_line + 10 >= num_display_lines) ||
	BeginScroll(st->screen.start_line,
                    st->screen.start_line + num_display_lines - 1) != 0) {
        /* Too much text to scroll, or can't scroll -- just repaint */

	if(handle)
	  st->parms->text.handles = handle;

        st->top_text_line = new_top_line;
        redraw_scroll_text();
    }
    else{
	if(new_top_line > st->top_text_line){
	    /*------ scroll down ------*/
	    while(new_top_line > st->top_text_line) {
		ScrollRegion(1);

		l = (st->parms->text.src == FileStar)
		      ? num_display_lines - (new_top_line - st->top_text_line)
		      : st->top_text_line + num_display_lines;

		if(l < st->num_lines) 
		  PutLine0n8b(st->screen.start_line + num_display_lines - 1,
			      0, st->text_lines[l], st->line_lengths[l],
			      handle ? handle : st->parms->text.handles);

		st->top_text_line++;
	    }
	}
	else{
	    /*------ scroll up -----*/
	    while(new_top_line < st->top_text_line) {
		ScrollRegion(-1);

		st->top_text_line--;
		l = (st->parms->text.src == FileStar)
		      ? st->top_text_line - new_top_line
		      : st->top_text_line;
		PutLine0n8b(st->screen.start_line, 0, st->text_lines[l],
			    st->line_lengths[l],
			    handle ? handle : st->parms->text.handles);
	    }
	}

	EndScroll();

	if(handle && handle != st->parms->text.handles){
	    POSLIST_S *lp;

	    for(lp = handle->loc; lp; lp = lp->next)
	      if(lp->where.row >= st->top_text_line
		 && lp->where.row < st->top_text_line + st->screen.length){
		  PutLine0n8b(st->screen.start_line
					 + (lp->where.row - st->top_text_line),
			      0, st->text_lines[lp->where.row],
			      st->line_lengths[lp->where.row],
			      handle);
		  
	      }

	    st->parms->text.handles = handle;
	}

	fflush(stdout);
    }

    return(new_top_line);
}



/*----------------------------------------------------------------------
      Search the set scrolling text

   Args:   start_line -- line to start searching on
	   start_col  -- column to start searching at in first line
           word       -- string to search for
           cursor_pos -- position of cursor is returned to caller here
			 (Actually, this isn't really the position of the
			  cursor because we don't know where we are on the
			  screen.  So row is set to the line number and col
			  is set to the right column.)
           offset_in_line -- Offset where match was found.

   Returns: the line the word was found on, or -2 if it wasn't found, or
	    -3 if the only match is at column start_col - 1.

 ----*/
int
search_scroll_text(start_line, start_col, word, cursor_pos, offset_in_line)
    long  start_line;
    int   start_col;
    char *word;
    Pos  *cursor_pos;
    int  *offset_in_line;
{
    SCRLCTRL_S *st = scroll_state(SS_CUR);
    char       *wh, loc_word[MAX_SEARCH + 1];
    long	l, offset, dlines;
#define	SLINE(N)	st->text_lines[(N)- offset]
#define	SLEN(N)		st->line_lengths[(N)- offset]

    dlines = PGSIZE(st);
    offset = (st->parms->text.src == FileStar) ? st->top_text_line : 0;

    /* lower the case once rather than in each search_scroll_line */
    lcase(strcpy(loc_word, word));

    if(start_line < st->num_lines){
	/* search first line starting at position start_col in */
	if((wh = search_scroll_line(SLINE(start_line) + start_col,
				    loc_word,
				    SLEN(start_line) - start_col,
				    st->parms->text.handles != NULL)) != NULL){
	    cursor_pos->row = start_line;
	    cursor_pos->col = scroll_handle_column(SLINE(start_line), wh);
	    *offset_in_line = wh - SLINE(start_line);
	    return(start_line);
	}

	if(st->parms->text.src == FileStar)
	  offset++;

	for(l = start_line + 1; l < st->num_lines; l++) {
	    if(st->parms->text.src == FileStar && l > offset + dlines)
	      ScrollFile(offset += dlines);

	    if((wh = search_scroll_line(SLINE(l), loc_word, SLEN(l),
				    st->parms->text.handles != NULL)) != NULL){
		cursor_pos->row = l;
		cursor_pos->col = scroll_handle_column(SLINE(l), wh);
		*offset_in_line = wh - SLINE(l);
		return(l);
	    }
	}
    }
    else
      start_line = st->num_lines;

    if(st->parms->text.src == FileStar)		/* wrap offset */
      ScrollFile(offset = 0);

    for(l = 0; l < start_line; l++) {
	if(st->parms->text.src == FileStar && l > offset + dlines)
	  ScrollFile(offset += dlines);

	if((wh = search_scroll_line(SLINE(l), loc_word, SLEN(l),
				    st->parms->text.handles != NULL)) != NULL){
	    cursor_pos->row = l;
	    cursor_pos->col = scroll_handle_column(SLINE(l), wh);
	    *offset_in_line = wh - SLINE(l);
	    return(l);
	}
    }

    /* search in current line */
    if((wh = search_scroll_line(SLINE(start_line), loc_word,
				start_col + strlen(loc_word) - 2,
				st->parms->text.handles != NULL)) != NULL){
	cursor_pos->row = start_line;
	cursor_pos->col = scroll_handle_column(SLINE(start_line), wh);
	*offset_in_line = wh - SLINE(start_line);
	return(start_line);
    }

    /* see if the only match is a repeat */
    if(start_col > 0 && (wh = search_scroll_line(
				SLINE(start_line) + start_col - 1,
				loc_word, strlen(loc_word),
				st->parms->text.handles != NULL)) != NULL){
	cursor_pos->row = start_line;
	cursor_pos->col = scroll_handle_column(SLINE(start_line), wh);
	*offset_in_line = wh - SLINE(start_line);
	return(-3);
    }

    return(-2);
}




/*----------------------------------------------------------------------
   Search one line of scroll text for given string

   Args:  is -- The string to search in, the larger string
          ss -- The string to search for, the smaller string
	  n  -- The max number of chars in is to search

   Search for first occurrence of ss in the is, and return a pointer
   into the string is when it is found. The search is case indepedent.
  ----*/
char *	    
search_scroll_line(is, ss, n, handles)
    char *is, *ss;
    int   n, handles;
{
    register char *p, *q;
    int		  state = 0;

    if(ss && is)
      for(; n-- > 0 && *is; is++){
	  /* excluding tag! */

	  if(handles)
	    switch(state){
	      case 0 :
		if(*is == TAG_EMBED){
		    state = -1;
		    continue;
		}

		break;

	      case -1 :
		state = (*is == TAG_HANDLE) ? -2 : 0;
		continue;

	      case -2 :
		state = *is;	/* length of handle's key */

	      default :
		state--;
		continue;
	    }

	  for(p = ss, q = is; ; p++, q++){
	      if(!*p)
		return(is);
	      else if(n < p - ss || !*q)	/* len(ss) > len(is) */
		return(NULL);
	      else if(*p != *q && !CMPNOCASE(*p, *q))
		break;
	  }
      }

    return(NULL);
}



char *    
display_parameters(params)
     PARAMETER *params;
{
    int		n, longest = 0;
    char       *d;
    PARAMETER  *p;
    PARMLIST_S *parmlist;

    for(p = params; p; p = p->next)	/* ok if we include *'s */
      if(p->attribute && (n = strlen(p->attribute)) > longest)
	longest = n;

    d = tmp_20k_buf;
    if(parmlist = rfc2231_newparmlist(params)){
	while(rfc2231_list_params(parmlist) && d < tmp_20k_buf + 10000){
            sprintf(d, "%-*s: %s\n", longest, parmlist->attrib,
		    parmlist->value ? strsquish(tmp_20k_buf + 11000,
						parmlist->value, 100)
				    : "");
            d += strlen(d);
	}

	rfc2231_free_parmlist(&parmlist);
    }
    else
      tmp_20k_buf[0] = '\0';

    return(tmp_20k_buf);
}



/*----------------------------------------------------------------------
    Display the contents of the given file (likely output from some command)

  Args: filename -- name of file containing output
	title -- title to be used for screen displaying output
	alt_msg -- if no output, Q this message instead of the default
	mode -- non-zero to display short files in status line
  Returns: none
 ----*/
void
display_output_file(filename, title, alt_msg, mode)
    char *filename, *title, *alt_msg;
    int mode;
{
    FILE *f;

    if(f = fopen(filename, "r")){
	if(mode == DOF_BRIEF){
	    int  msg_q = 0, i = 0;
	    char buf[512], *msg_p[4];
#define	MAX_SINGLE_MSG_LEN	60

	    buf[0]   = '\0';
	    msg_p[0] = buf;
	    while(fgets(msg_p[msg_q], sizeof(buf) - (msg_p[msg_q] - buf), f) 
		  && msg_q < 3
		  && (i = strlen(msg_p[msg_q])) < MAX_SINGLE_MSG_LEN){
		msg_p[msg_q+1] = msg_p[msg_q]+strlen(msg_p[msg_q]);
		if (*(msg_p[++msg_q] - 1) == '\n')
		  *(msg_p[msg_q] - 1) = '\0';
	    }

	    if(msg_q < 3 && i < MAX_SINGLE_MSG_LEN){
		if(*msg_p[0])
		  for(i = 0; i < msg_q; i++)
		    q_status_message2(SM_ORDER, 3, 4,
				      "%s Result: %s", title, msg_p[i]);
		else
		  q_status_message2(SM_ORDER, 0, 4, "%s%s", title,
				    alt_msg
				      ? alt_msg
				      : " command completed with no output");

		fclose(f);
		f = NULL;
	    }
	}
	else if(mode == DOF_EMPTY){
	    char c;

	    if(fread(&c, sizeof(char), (size_t) 1, f) < 1){
		q_status_message2(SM_ORDER, 0, 4, "%s%s", title,
				  alt_msg
				    ? alt_msg
				    : " command completed with no output");
		fclose(f);
		f = NULL;
	    }
	}

	if(f){
	    SCROLL_S sargs;
	    char     title_buf[64];

	    sprintf(title_buf, "HELP FOR %s VIEW", title);

	    memset(&sargs, 0, sizeof(SCROLL_S));
	    sargs.text.text  = f;
	    sargs.text.src   = FileStar;
	    sargs.text.desc  = "output";
	    sargs.bar.title  = title;
	    sargs.bar.style  = TextPercent;
	    sargs.help.text  = h_simple_text_view;
	    sargs.help.title = title_buf;
	    scrolltool(&sargs);
	    ps_global->mangled_screen = 1;
	    fclose(f);
	}

	unlink(filename);
    }
    else
      dprint(2, (debugfile, "Error reopening %s to get results: %s\n",
		 filename, error_description(errno)));
}


/*----------------------------------------------------------------------
      Fetch the requested header fields from the msgno specified

   Args: stream -- mail stream of open folder
         msgno -- number of message to get header lines from
         fields -- array of pointers to desired fields

   Returns: allocated string containing matched header lines,
	    NULL on error.
 ----*/
char *
fetch_header(stream, msgno, section, fields, flags)
     MAILSTREAM  *stream;
     long         msgno;
     char	 *section;
     char       **fields;
     long	  flags;
{
    STRINGLIST *sl;
    char *p, **pp, *m, *h = NULL, *match = NULL, tmp[MAILTMPLEN];

    sl = (fields && *fields) ? new_strlst(fields) : NULL;  /* package up fields */
    h  = mail_fetch_header(stream, msgno, section, sl, NULL, flags | FT_PEEK);
    if (sl) 
      free_strlst(&sl);

    if(!h)
      return(NULL);

    while(find_field(&h, tmp)){
	for(pp = &fields[0]; *pp && strucmp(tmp, *pp); pp++)
	  ;

	/* interesting field? */
	if(p = (flags & FT_NOT) ? ((*pp) ? NULL : tmp) : *pp){
	    /*
	     * Hold off allocating space for matching fields until
	     * we at least find one to copy...
	     */
	    if(!match)
	      match = m = fs_get(strlen(h) + strlen(p) + 1);

	    while(*p)				/* copy field name */
	      *m++ = *p++;

	    while(*h && (*m++ = *h++))		/* header includes colon */
	      if(*(m-1) == '\n' && (*h == '\r' || !isspace((unsigned char)*h)))
		break;

	    *m = '\0';				/* tie off match string */
	}
	else{					/* no match, pass this field */
	    while(*h && !(*h++ == '\n'
	          && (*h == '\r' || !isspace((unsigned char)*h))))
	      ;
	}
    }

    return(match ? match : cpystr(""));
}


int
find_field(h, tmp)
     char **h;
     char *tmp;
{
    if(!h || !*h || !**h || isspace((unsigned char)**h))
      return(0);

    while(**h && **h != ':' && !isspace((unsigned char)**h))
      *tmp++ = *(*h)++;

    *tmp = '\0';
    return(1);
}


STRINGLIST *
new_strlst(l)
    char **l;
{
    STRINGLIST *sl = mail_newstringlist();

    sl->text.data = (unsigned char *) (*l);
    sl->text.size = strlen(*l);
    sl->next = (*++l) ? new_strlst(l) : NULL;
    return(sl);
}


void
free_strlst(sl)
    STRINGLIST **sl;
{
    if(*sl){
	if((*sl)->next)
	  free_strlst(&(*sl)->next);

	fs_give((void **) sl);
    }
}


#ifdef	_WINDOWS
/*
 * Just a little something to simplify assignments
 */
#define	VIEWPOPUP(p, c, s)	{ \
				    (p)->type	      = tQueue; \
				    (p)->data.val     = c; \
				    (p)->label.style  = lNormal; \
				    (p)->label.string = s; \
				}


/*
 * 
 */
int
format_message_popup(sparms, in_handle)
    SCROLL_S *sparms;
    int	      in_handle;
{
    MPopup	  fmp_menu[32];
    HANDLE_S	 *h = NULL;
    int		  i = -1, n;
    MESSAGECACHE *mc;

    /* Reason to offer per message ops? */
    if(mn_get_total(ps_global->msgmap) > 0L){
	if(in_handle){
	    SCRLCTRL_S *st = scroll_state(SS_CUR);

	    switch((h = get_handle(st->parms->text.handles, in_handle))->type){
	      case Attach :
		fmp_menu[++i].type	 = tIndex;
		fmp_menu[i].label.string = "View Attachment";
		fmp_menu[i].label.style  = lNormal;
		fmp_menu[i].data.val     = 'X'; /* for local use */

		if(h->h.attach
		   && dispatch_attachment(h->h.attach) != MCD_NONE
		   && !(h->h.attach->can_display & MCD_EXTERNAL)
		   && h->h.attach->body
		   && (h->h.attach->body->type == TYPETEXT
		       || (h->h.attach->body->type == TYPEMESSAGE
			   && h->h.attach->body->subtype
			   && !strucmp(h->h.attach->body->subtype,"rfc822")))){
		    fmp_menu[++i].type	     = tIndex;
		    fmp_menu[i].label.string = "View Attachment in New Window";
		    fmp_menu[i].label.style  = lNormal;
		    fmp_menu[i].data.val     = 'Y';	/* for local use */
		}

		fmp_menu[++i].type	    = tIndex;
		fmp_menu[i].label.style = lNormal;
		fmp_menu[i].data.val    = 'Z';	/* for local use */
		msgno_exceptions(ps_global->mail_stream,
				 mn_m2raw(ps_global->msgmap,
					  mn_get_cur(ps_global->msgmap)),
				 h->h.attach->number, &n, FALSE);
		fmp_menu[i].label.string = (n & MSG_EX_DELETE)
					      ? "Undelete Attachment"
					      : "Delete Attachment";
		break;

	      case URL :
	      default :
		fmp_menu[++i].type	     = tIndex;
		fmp_menu[i].label.string = "View Link";
		fmp_menu[i].label.style  = lNormal;
		fmp_menu[i].data.val     = 'X';	/* for local use */
		break;
	    }

	    fmp_menu[++i].type = tSeparator;
	}

	/* Delete or Undelete?  That is the question. */
	fmp_menu[++i].type	= tQueue;
	fmp_menu[i].label.style = lNormal;
	if((mc = mail_elt(ps_global->mail_stream,
			  mn_m2raw(ps_global->msgmap,
				   mn_get_cur(ps_global->msgmap))))->deleted){
	    fmp_menu[i].data.val     = 'U';
	    fmp_menu[i].label.string = in_handle
					 ? "&Undelete Message" : "&Undelete";
	}
	else{
	    fmp_menu[i].data.val     = 'D';
	    fmp_menu[i].label.string = in_handle
					 ? "&Delete Message" : "&Delete";
	}

	if(F_ON(F_ENABLE_FLAG, ps_global)){
	    fmp_menu[++i].type	     = tSubMenu;
	    fmp_menu[i].label.string = "Flag";
	    fmp_menu[i].data.submenu = flag_submenu(mc);
	}

	i++;
	VIEWPOPUP(&fmp_menu[i], 'S', in_handle ? "&Save Message" : "&Save");

	i++;
	VIEWPOPUP(&fmp_menu[i], '%', in_handle ? "Print Message" : "Print");

	i++;
	VIEWPOPUP(&fmp_menu[i], 'R',
		  in_handle ? "&Reply to Message" : "&Reply");

	i++;
	VIEWPOPUP(&fmp_menu[i], 'F',
		  in_handle ? "&Forward Message" : "&Forward");

	fmp_menu[++i].type = tSeparator;

	if(mn_get_cur(ps_global->msgmap) < mn_get_total(ps_global->msgmap)){
	    i++;
	    VIEWPOPUP(&fmp_menu[i], 'N', "View &Next Message");

	    fmp_menu[++i].type = tSeparator;
	}

	/* Offer the attachment screen? */
	for(n = 0; ps_global->atmts && ps_global->atmts[n].description; n++)
	  ;

	if(n > 1){
	    i++;
	    VIEWPOPUP(&fmp_menu[i], 'V', "&View Attachment Index");
	}
    }

    i++;
    VIEWPOPUP(&fmp_menu[i], 'I', "Message &Index");

    i++;
    VIEWPOPUP(&fmp_menu[i], 'M', "&Main Menu");

    fmp_menu[++i].type = tTail;

    if((i = mswin_popup(fmp_menu)) >= 0 && in_handle)
      switch(fmp_menu[i].data.val){
	case 'X' :
	  return(1);		/* return like the user double-clicked */

	break;

	case 'Y' :		/* popup the thing in another window */
	  display_att_window(h->h.attach);
	  break;

	case 'Z' :
	  if(h && h->type == Attach){
	      msgno_exceptions(ps_global->mail_stream,
			       mn_m2raw(ps_global->msgmap,
					mn_get_cur(ps_global->msgmap)),
			       h->h.attach->number, &n, FALSE);
	      n ^= MSG_EX_DELETE;
	      msgno_exceptions(ps_global->mail_stream,
			       mn_m2raw(ps_global->msgmap,
					mn_get_cur(ps_global->msgmap)),
			       h->h.attach->number, &n, TRUE);
	      q_status_message2(SM_ORDER, 0, 3, "Attachment %s %s!",
				h->h.attach->number,
				(n & MSG_EX_DELETE) ? "deleted" : "undeleted");

	      return(2);
	  }

	  break;

	default :
	  break;
      }

    return(0);
}



/*
 * 
 */
int
simple_text_popup(sparms, in_handle)
    SCROLL_S *sparms;
    int	      in_handle;
{
    MPopup    simple_menu[12];
    int	      n = 0;

    VIEWPOPUP(&simple_menu[n], '%', "Print");
    n++;

    VIEWPOPUP(&simple_menu[n], 'S', "Save");
    n++;

    VIEWPOPUP(&simple_menu[n], 'F', "Forward in Email");
    n++;

    simple_menu[n++].type = tSeparator;

    VIEWPOPUP(&simple_menu[n], 'E', "Exit Viewer");
    n++;

    simple_menu[n].type = tTail;

    (void) mswin_popup(simple_menu);
    return(0);
}



/*----------------------------------------------------------------------
    Return characters in scroll tool buffer serially

   Args: n -- index of char to return

   Returns: returns the character at index 'n', or -1 on error or
	    end of buffer.

 ----*/
int
mswin_readscrollbuf(n)
    int n;
{
    SCRLCTRL_S	 *st = scroll_state(SS_CUR);
    int		  c;
    static char **orig = NULL, **l, *p;
    static int    lastn;

    if(!st)
      return(-1);

    /*
     * All of these are mind-numbingly slow at the moment...
     */
    switch(st->parms->text.src){
      case CharStar :
	return((n >= strlen((char *)st->parms->text.text))
	         ? -1 : ((char *)st->parms->text.text)[n]);

      case CharStarStar :
	/* BUG? is this test rigorous enough? */
	if(orig != (char **)st->parms->text.text || n < lastn){
	    lastn = n;
	    if(orig = l = (char **)st->parms->text.text) /* reset l and p */
	      p = *l;
	}
	else{				/* use cached l and p */
	    c = n;			/* and adjust n */
	    n -= lastn;
	    lastn = c;
	}

	while(l){			/* look for 'n' on each line  */
	    for(; n && *p; n--, p++)
	      ;

	    if(n--)			/* 'n' found ? */
	      p = *++l;
	    else
	      break;
	}

	return((l && *l) ? *p ? *p : '\n' : -1);

      case FileStar :
	return((fseek((FILE *)st->parms->text.text, (long) n, 0) < 0
		|| (c = fgetc((FILE *)st->parms->text.text)) == EOF) ? -1 : c);

      default:
	return(-1);
    }
}



/*----------------------------------------------------------------------
     MSWin scroll callback.  Called during scroll message processing.
	     


  Args: cmd - what type of scroll operation.
	scroll_pos - paramter for operation.  
			used as position for SCROLL_TO operation.

  Returns: TRUE - did the scroll operation.
	   FALSE - was not able to do the scroll operation.
 ----*/
int
pcpine_do_scroll (cmd, scroll_pos)
int	cmd;
long	scroll_pos;
{
    SCRLCTRL_S   *st = scroll_state(SS_CUR);
    HANDLE_S	 *next_handle;
    int		  paint = FALSE;
    int		  num_display_lines;
    int		  scroll_lines;
    int		  num_text_lines;
    char	  message[64];
    long	  maxscroll;
    
	
    message[0] = '\0';
    maxscroll = st->num_lines;
    switch (cmd) {
    case MSWIN_KEY_SCROLLUPLINE:
	if(st->top_text_line > 0) {
	    st->top_text_line -= (int) scroll_pos;
	    paint = TRUE;
	    if (st->top_text_line <= 0){
		sprintf(message, "START of %.*s",
			32, STYLE_NAME(st->parms));
		st->top_text_line = 0;
	    }
        }
	break;

    case MSWIN_KEY_SCROLLDOWNLINE:
        if(st->top_text_line < maxscroll) {
	    st->top_text_line += (int) scroll_pos;
	    paint = TRUE;
	    if (st->top_text_line >= maxscroll){
		sprintf(message, "END of %.*s", 32, STYLE_NAME(st->parms));
		st->top_text_line = maxscroll;
	    }
        }
	break;
		
    case MSWIN_KEY_SCROLLUPPAGE:
	if(st->top_text_line > 0) {
	    num_display_lines = SCROLL_LINES(ps_global);
	    scroll_lines = min(max(num_display_lines -
			ps_global->viewer_overlap, 1), num_display_lines);
	    if (st->top_text_line > scroll_lines)
		st->top_text_line -= scroll_lines;
	    else {
		st->top_text_line = 0;
		sprintf(message, "START of %.*s", 32, STYLE_NAME(st->parms));
	    }
	    paint = TRUE;
        }
	break;
	    
    case MSWIN_KEY_SCROLLDOWNPAGE:
	num_display_lines = SCROLL_LINES(ps_global);
	if(st->top_text_line  < maxscroll) {
	    scroll_lines = min(max(num_display_lines -
			ps_global->viewer_overlap, 1), num_display_lines);
	    st->top_text_line += scroll_lines;
	    if (st->top_text_line >= maxscroll) {
		st->top_text_line = maxscroll;
		sprintf(message, "END of %.*s", 32, STYLE_NAME(st->parms));
	    }
	    paint = TRUE;
	}
	break;
	    
    case MSWIN_KEY_SCROLLTO:
	if (st->top_text_line != scroll_pos) {
	    st->top_text_line = scroll_pos;
	    if (st->top_text_line == 0)
		sprintf(message, "START of %.*s", 32, STYLE_NAME(st->parms));
	    else if(st->top_text_line >= maxscroll) 
		sprintf(message, "END of %.*s", 32, STYLE_NAME(st->parms));
	    paint = TRUE;
        }
	break;
    }

    /* Need to frame some handles? */
    if(st->parms->text.handles
       && (next_handle = scroll_handle_in_frame(st->top_text_line)))
      st->parms->text.handles = next_handle;

    if (paint) {
	mswin_beginupdate();
	update_scroll_titlebar(st->top_text_line, 0);
	(void) scroll_scroll_text(st->top_text_line,
				  st->parms->text.handles, 1);
	if (message[0])
	  q_status_message(SM_INFO, 0, 1, message);

	/* Display is always called so that the "START(END) of message" 
	 * message gets removed when no longer at the start(end). */
	display_message (KEY_PGDN);
	mswin_endupdate();
    }

    return (TRUE);
}


char *
pcpine_help_scroll(title)
    char *title;
{
    SCRLCTRL_S   *st = scroll_state(SS_CUR);

    if(title)
      strcpy(title, (st->parms->help.title)
		      ? st->parms->help.title : "PC-Pine Help");

    return(pcpine_help(st->parms->help.text));
}


int
pcpine_resize_scroll()
{
    int newRows, newCols;

    mswin_getscreensize(&newRows, &newCols);
    if(newCols == ps_global->ttyo->screen_cols){
	SCRLCTRL_S   *st = scroll_state(SS_CUR);

	(void) get_windsize (ps_global->ttyo);
	mswin_beginupdate();
	update_scroll_titlebar(st->top_text_line, 1);
	ClearLine(1);
	(void) scroll_scroll_text(st->top_text_line,
				  st->parms->text.handles, 1);
	mswin_endupdate();
    }
}
#endif
