SMOLNET PORTAL home about changes
/*****************************************************************************
 * JLWilkinson 21-Sep-1995  Moved syslog() routines to [-.object]compatible.c
 *			     so they're available for OpenVMS Server use too.
 * M.Dunnett  09-Mar-1995   Also invoke GSprompter() when using callable TPU
 *			     pagers. 
 * F.Macrides 08-Mar-1995   Use strncasecmp() for getting the AskLs value in
 *			     AskBlock().
 * F.Macrides 02-Mar-1995   Restore use of strncasecmp(command, "builtin", 7)
 *			     when checking the PAGER_COMMAND. It's not
 *			     indended to include a %s, and doesn't in the
 *			     conf.h definition, but users are likely to include
 *			     %s when setting the pager in their RC file, and
 *			     cause the check to fail.  Apparently was changed
 *		             to strcasecmp() during a recent upgrade, because
 *			     in *theory* that's adequate, but in *practice* it
 *			     isn't. (Reported by Hunter Goatley.)
 */
/********************************************************************
 * lindner
 * 3.72
 * 1995/01/25 23:10:07
 * /home/arcwelder/GopherSrc/CVS/gopher+/gopher/ourutils.c,v
 * Exp
 *
 * Paul Lindner, University of Minnesota CIS.
 *
 * Copyright 1991, 1992,1993 by the Regents of the University of Minnesota
 * see the file "Copyright" in the distribution for conditions of use.
 *********************************************************************
 * MODULE: ourutils.c
 * stuff that doesn't really fit anywhere else.
 *********************************************************************
 * Revision History:
 * ourutils.c,v
 * Revision 3.72  1995/01/25  23:10:07  lindner
 * Remove hard limit of 5 AskL items per form
 *
 * Revision 3.71  1994/12/03  01:55:43  lindner
 * JL Wilkinson 16-Jul-1994 Merged in TGV's syslog() functions to allow
 * the CLIENT_LOGGER and TELNET_TRACE functionality to work with
 * Multinet.  Not tested against other VMS TCP/IP agents, and *NOT
 * SUPPORTED* thru TGV -- please don't anybody ask TGV for help in using
 * this.  Somebody who wants to use it can Email me or the
 * VMSGopher-L@trln.lib.unc.edu group and I'll try to assist them for
 * MULTINET only (JLW@psulias.psu.edu).  Basic documentation is in
 *
 * Added Lance's post-pager RETURN synonyms.
 *
 * Indicate 'D'ownload as an option on exit from the pager in SecureMode
 * and NoShellMode.
 *
 * Revision 3.70  1994/10/24  22:14:38  lindner
 * Add pdf type
 *
 * Revision 3.69  1994/10/13  05:30:38  lindner
 * Compiler complaint fixes
 *
 * Revision 3.68  1994/09/29  19:25:57  lindner
 * More restrictive Unix file name filter
 *
 * Revision 3.67  1994/08/19  16:53:29  lindner
 * Add casts for malloc calls
 *
 * Revision 3.66  1994/07/25  02:55:17  lindner
 * Secure mode mods
 *
 * Revision 3.65  1994/07/19  20:18:36  lindner
 * Remove CLIENT_LOGGER, Add default language choice
 *
 * Revision 3.64  1994/07/03  23:11:26  lindner
 * Add internal download feature
 *
 * Revision 3.63  1994/06/29  07:04:41  lindner
 * Mods for A_APP and builtin upload
 *
 * Revision 3.62  1994/06/09  04:11:07  lindner
 * Added Gerd.Boehm@physik.uni-regensburg.de patch for off-by-one line
 * counts in AskL blocks.
 *
 * Revision 3.61  1994/06/03  06:11:46  lindner
 * Fix for big bad cso dashes
 *
 * Revision 3.60  1994/05/19  14:08:04  lindner
 * use fast malloc on VMS VAXC
 *
 * Revision 3.59  1994/05/17  05:48:03  lindner
 * Massive internationalization change
 *
 * Revision 3.58  1994/05/14  04:13:46  lindner
 * Internationalization...
 *
 * Revision 3.57  1994/04/29  15:13:17  lindner
 * Fix for items with multiple AskL items
 *
 * Revision 3.56  1994/04/25  18:40:05  lindner
 * Fix for uninited variable i
 *
 * Revision 3.55  1994/04/25  03:37:43  lindner
 * Modifications for Debug() and mismatched NULL arguments, added Debugmsg
 *
 * Revision 3.54  1994/04/13  19:14:03  lindner
 * AskL modifications
 *
 * Revision 3.53  1994/03/08  20:38:28  lindner
 * Fix for VMS compilation
 *
 * Revision 3.52  1994/03/08  15:55:16  lindner
 * gcc -Wall fixes
 *
 * Revision 3.51  1994/03/08  03:25:58  lindner
 * Fix for big bad telnet bug, additions for secure process i/o
 *
 * Revision 3.50  1994/03/04  23:31:32  lindner
 * Fix for askblock from beckett
 *
 * Revision 3.49  1994/02/20  16:32:58  lindner
 * Modify routines to use fileio routines for reading text files
 *
 * Revision 3.48  1993/12/28  17:33:00  lindner
 * filter out more VMS characters
 *
 * Revision 3.47  1993/11/29  01:12:26  lindner
 * In Save_file(), disallow saving of <CSO> type since this is fetched as a
 * set of indexed fields instead of as a savable file.  Also disallow
 * saving of info and error types since there's nothing to save.  (Wolfe,
 * Beckett, Macrides)
 *
 * In Choose_View(), add case for A_MOVIE.  (Macrides)
 *
 * Revision 3.46  1993/11/03  03:50:01  lindner
 * More fixes for VMSrecords
 *
 * Revision 3.45  1993/11/02  21:17:44  lindner
 * Better client side logging code
 *
 * Revision 3.44  1993/10/27  18:52:17  lindner
 * Simplify VMS fixed/variable file code
 *
 * Revision 3.43  1993/10/26  18:22:43  lindner
 * Mucho changes for VMS variable length records
 *
 * Revision 3.42  1993/10/22  20:33:17  lindner
 * Fixed bad code in Save_file() for choosing view (Fote)
 *
 * Revision 3.41  1993/10/22  20:06:46  lindner
 * Add optional client logging
 *
 * Revision 3.40  1993/10/11  17:20:43  lindner
 * Fix for data/time stripping, vms multidot files, no case comparision for builtin
 *
 * Revision 3.39  1993/10/07  05:08:22  lindner
 * In Choose_View(), remove unreachable statement.
 *
 * In AskBlock(), allocate memory for the form based on screen width.
 *
 * In Save_file(), choose a view BEFORE opening the file -- user may
 * change mind and cancel, which left a zero-length file previously.
 *
 * In Save_file(), check to see if a view has already been chosen.  This
 * change requires that Save_file() have a third argument.  Any call to
 * Save_file() will now need a third argument.  The third argument should
 * be a pointer to the chosen view or NULL to indicate that one has yet to
 * be chosen.
 *
 * In GSdisplay(), add "view" as the third argument to the two "new"
 * Save_file() calls to prevent Save_file() from asking the user to choose
 * a view.  It is redundant in both cases.
 *
 * Revision 3.38  1993/09/29  22:47:21  lindner
 * Fix for AskBlock memory cruft
 *
 * Revision 3.37  1993/09/29  21:30:29  lindner
 * Fix for memory probs with ask blocks
 *
 * Revision 3.36  1993/09/29  20:49:42  lindner
 * Remove dead code, fix for vms mail
 *
 * Revision 3.35  1993/09/26  09:20:54  lindner
 * Change to use GSmail()
 *
 * Revision 3.34  1993/09/22  19:57:52  lindner
 * fix for off by one and Select:
 *
 * Revision 3.33  1993/09/21  01:46:02  lindner
 * Implement all remaining ASK block items..
 *
 * Revision 3.32  1993/09/18  04:44:23  lindner
 * Additions to fix caching of Multiple view items
 *
 * Revision 3.31  1993/09/18  03:29:02  lindner
 * Mucho fixes for ask block parsing..
 *
 * Revision 3.30  1993/09/11  05:12:41  lindner
 * Many ask block fixes
 *
 * Revision 3.29  1993/09/08  05:23:19  lindner
 * Implement Builtin pager..
 *
 * Revision 3.28  1993/09/08  01:12:33  lindner
 * Add support for HTML and MIME on the menu displays
 *
 * Revision 3.27  1993/09/01  21:47:37  lindner
 * Fix for compiler error on Alpha
 *
 * Revision 3.26  1993/08/19  20:51:52  lindner
 * secure patch from mitra
 *
 * Revision 3.25  1993/08/19  20:30:39  lindner
 * Fix problem with selecting ask item more than once
 *
 * Revision 3.24  1993/08/19  20:22:56  lindner
 * Mitra's Debug patch
 *
 * Revision 3.23  1993/08/16  18:01:10  lindner
 * Fix for VMSfile, mods for DEC alpha VMS
 *
 * Revision 3.22  1993/08/16  17:58:24  lindner
 * Removed REMOTEUSER ifdefs
 *
 * Revision 3.21  1993/08/06  14:39:57  lindner
 * One more small security patch
 *
 * Revision 3.20  1993/08/05  20:40:43  lindner
 * Added warning message..
 *
 * Revision 3.19  1993/08/04  22:07:23  lindner
 * Use /bin/mail instead of ucbmail
 *
 * Revision 3.18  1993/08/03  04:43:59  lindner
 * Fix for VMS unresolved variables
 *
 * Revision 3.17  1993/07/30  17:31:56  lindner
 * Mods to support AskP:, AskL:
 *
 * Revision 3.16  1993/07/29  17:20:19  lindner
 * eliminate non-used variables
 *
 * Revision 3.15  1993/07/27  06:14:17  lindner
 * Bug fix, use readrecvbuf, not readn
 *
 * Revision 3.14  1993/07/27  05:28:57  lindner
 * Mondo Debug overhaul from Mitra
 *
 * Revision 3.13  1993/07/27  02:04:13  lindner
 * More comments
 *
 * Revision 3.12  1993/07/26  20:30:05  lindner
 * fix memory usage
 *
 * Revision 3.11  1993/07/26  15:35:28  lindner
 * Fix for Unixfile to remove parens
 *
 * Revision 3.10  1993/07/23  04:38:43  lindner
 * Mods to make client view selection pretty
 *
 * Revision 3.9  1993/07/20  23:14:59  lindner
 * Mods to cache askdata..
 *
 * Revision 3.8  1993/07/07  19:43:42  lindner
 * Added TPU support for pager in VMS
 *
 * Revision 3.7  1993/06/29  06:18:52  lindner
 * recasting MIME types
 *
 * Revision 3.6  1993/04/15  21:27:26  lindner
 * NOMAIL option, Remote user stuff, and bug in Ask
 *
 * Revision 3.5  1993/03/26  19:44:36  lindner
 * Fix for CSO stuff and saving binary files
 *
 * Revision 3.4  1993/03/24  17:01:27  lindner
 * Major changes to the way things are displayed/saved
 *
 * Revision 3.3  1993/03/18  23:32:41  lindner
 * Added Note: support and default values to AskBlock
 *
 * Revision 3.2  1993/02/19  21:12:16  lindner
 * Generalized on gopher+ stuff, strip long filenames to reasonable length
 *
 * Revision 3.1.1.1  1993/02/11  18:02:58  lindner
 * Gopher+1.2beta release
 *
 * Revision 2.1  1993/02/09  22:37:50  lindner
 * Much new in the way of display routines.
 *
 *********************************************************************/


#include "gopher.h"
#include "Stdlib.h"
#include "util.h"
#ifndef VMS
#include <pwd.h>
#else
#include <descrip.h>
#endif
#ifdef mips
char *getenv();
#endif
#include "Debug.h"
#include "fileio.h"
#include "Malloc.h"
#include "Locale.h"
#include "String.h"

void GSprompter();

int 
outchar(c)
  char c;
{
        /** output the given character.  From tputs... **/
        /** Note: this CANNOT be a macro!              **/

        putc(c, stdout);
	return(c);
}


/*
 * Function that twirls on the screen..
 *
 */

int
twirl()
{
     static int twirlnum = 0;
     static char *twirls = "-/|\\";

     addch('\b');
     addch(*(twirls + (twirlnum++ % 4 )));
     refresh();

     return(0);
}



/*
** This procedure sends a file through the mail.
*/

void
GSmail(gs)
  GopherObj *gs;
{
     char        *Filename = GSgetLocalFile(gs);
     char        *Realname = GSgetTitle(gs);

     static char *SaveName = NULL;
     char         command[512];
     FileIO      *infile, *mailit;
     char         buf[512];
     char         MailAddress[512];

     
     if (SaveName==NULL) {
	  if ((SaveName = (char *) malloc(sizeof(char)*256)) == NULL)
	       perror("Out of memory!");
	  *SaveName = '\0';
     }

     if (CURGetOneOption(CursesScreen, Realname, 
			 Gtxt("Mail current document to:",105),
     			 SaveName)<0)
	  return;
     
     if (*SaveName == '\0')
	  return;

/*
 * You should be very careful when you add characters to this list..
 * 
 * It could create a nasty security hole!
 */
     
#define ACHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789%.@!-_"
		    
     if (SecureMode || NoShellMode)
	  if ( strspn(SaveName, ACHARS) != strlen(SaveName)||
	      *SaveName == '-') {
	       Debugmsg("mail_file - beep - bad char")
	       puts(CURgetBell(CursesScreen));
	       fflush(stdout);
	       Draw_Status(Gtxt("Address syntax rejected...",65));
	       refresh();
	       return;
	  }

     if (strchr(Realname, '\"') != NULL)
	  Realname = Gtxt("Gopher File",91);
     

     Draw_Status(Gtxt("Mailing File...",106));
     refresh();
     
#ifdef VMS
     /* encase any address with '@' in double-quotes and add transport */
     /* if the user hasn't done it (based on code from Earl Fogel)     */
     if ((strchr(SaveName, '@') != NULL) && (strchr(SaveName, '\"') == NULL))
	   sprintf(MailAddress, MAIL_ADRS, SaveName);
     else
	   strcpy(MailAddress, SaveName);

     sprintf(command, "%s/subject=\"%.70s\" %s %s",
           MAIL_COMMAND, Realname, Filename, MailAddress);

     CURexit(CursesScreen);
     printf(Gtxt("Mailing file to %s...",107), MailAddress);
     FIOsystem(command);
     CURenter(CursesScreen);
#else
     sprintf(command, "%s %s", 
	     MAIL_COMMAND, SaveName);
     
     infile = FIOopenUFS(Filename, O_RDONLY, 0);
     mailit = FIOopenCmdline(command, "w");
     
     if (mailit == NULL || infile == NULL) {
	  CursesErrorMsg(Gtxt("System problem, unable to mail",173));
	  return;
     }

     if (strlen(Realname) > 70)
	  Realname[70]  = '\0';

     FIOwritestring(mailit, "Subject: ");
     FIOwritestring(mailit, Realname);
     FIOwritestring(mailit, "\n\n");

     while (FIOreadline(infile, buf, sizeof(buf))) {
	  twirl();
	  FIOwritestring(mailit, buf);
     }

     FIOclose(mailit);
     FIOclose(infile);

#endif

}

/*
 *  This fcn allows one to choose a view out of a unlimited size list
 *  returns false if fails
 */

#ifndef DEFAULT_LANG	/* should be set in conf.h, but just in case */
#define DEFAULT_LANG    "En_US"     /* English (US) */
#endif


char *
Choose_View(gs)
  GopherObj *gs;
{
     int viewno;
     static char viewnlang[64];
     char *view;

     if (GSisGplus(gs)) {

	  GSgetginfo(gs, TRUE);
	  
	  if (GSgetNumViews(gs) >1) {
	       int choice, default_choice = -1;
	       int langChoice = -1, viewChoice = -1;
	       char *default_lang =  Gtxt(DEFAULT_LANG, 222);
	       char **views = (char **) malloc((1 + GSgetNumViews(gs)) * sizeof(char *));
	       char **choices = (char **) malloc((1 + GSgetNumViews(gs)) * sizeof(char *));

	       if ( (views == NULL) || (choices == NULL) )
		    return NULL;

	       for (viewno=0; viewno <GSgetNumViews(gs); viewno++) {
		    VIewobj	 *thisView = GSgetView(gs, viewno);

		    views[viewno] = strdup(VIgetViewnLang(thisView, viewnlang));
		    choices[viewno] = strdup(VIgetPrettyView(thisView,viewnlang));

		    if (default_choice < 0) {
			 int langMatch = !(strcasecmp(VIgetLang(thisView), default_lang));
			 int viewMatch = !(strcasecmp(VIgetType(thisView), "text/plain"));

			 if (langMatch && viewMatch)
			      default_choice = viewno;
			 else {
			      if (langMatch && (langChoice < 1))
				   langChoice = viewno;
			      if (viewMatch && (viewChoice < 1))
				   viewChoice = viewno;
			 }
		    }
	       }

	       if (default_choice < 0) {
		    if (viewChoice >= 0) 
			 default_choice = viewChoice;
		    else if (langChoice >= 0)
			 default_choice = langChoice;
		    else
			 default_choice = 0;
	       }

	       choices[viewno] = NULL;
	       choice = CURChoice(CursesScreen, GSgetTitle(gs), choices,
				  Gtxt("Choose a document type",73), 
				  default_choice);
	       Debug("Choice was %d\n", choice);
	       if (choice == -1)
		    return(NULL);

	       sprintf(viewnlang, "%s", views[choice]);

	       for (viewno = 0; viewno <GSgetNumViews(gs); viewno++)  {
		    free(views[viewno]);

		    free(choices[viewno]);

	       }
	       free(views);
	       free(choices);
	       return(viewnlang);
	  }
	  else if (GSgetNumViews(gs) == 1)
	       return(VIgetViewnLang(GSgetView(gs, 0), viewnlang));
	  else
	       return(NULL);
     }
     else {
	  switch (GSgetType(gs)) {
	  case A_FILE:
	  case A_CSO:
	       view = "text/plain";
	       break;
	  case A_MIME:
	       view = "message/rfc822";
	       break;
	  case A_MACHEX:
	       view = "application/mac-binhex40";
	       break;
	  case A_HTML:
	       view = "text/html";
	       break;
	  case A_GIF:
	       view = "image/gif";
	       break;
	  case A_IMAGE:
	       view = "image";
	       break;
	  case A_MOVIE:
	       view = "video";
	       break;
	  case A_SOUND:
	       view = "audio/basic";
	       break;
	  case A_TN3270:
	       view = "terminal/tn3270";
	       break;
	  case A_TELNET:
	       view = "terminal/telnet";
	       break;
	  case A_UNIXBIN:
	  case A_PCBIN:
	       view = "application/octet-stream";
	       break;
	  case A_PDF:
	       view = "application/pdf";
	       break;
	  default:
	       return(NULL);
	  }

	  return(view);
     }

}

Requestitem *
REQitemnew() 
{
     Requestitem *temp;

     temp = (Requestitem*) (malloc(sizeof(Requestitem)));
     if (temp == NULL)
	  return(NULL);

     temp->stowage = (char *) malloc(sizeof(char) * COLS);
     temp->stowage[0] = '\0';
     temp->choices = NULL;
     temp->prompt = NULL;
     
     return(temp);
}

void
REQitemdestroy(req)
  Requestitem *req;
{
     int j;

     if (req->stowage != (char*) NULL)
	  free(req->stowage);
     
     if (req->prompt != (char*) NULL) 
	  free(req->prompt);
     
     if (req->choices != (char**) NULL) {
	  for (j=0; req->choices[j] != (char*) NULL; j++)
	       free(req->choices[j]);
	  free(req->choices);
     }
     free(req);

}

/*
 * Crude implementation of the ASK block stuff.
 */

char **
AskBlock(gs)
  GopherObj *gs;
{
     int Asknum, reqnum, AskLs = 0;
     Blockobj *bl;
     Requestitem **items;
     char **asktypes;
     char **responses = NULL;
     char askline[256];
     char *defaultval;
     int i;

     GSgetginfo(gs, TRUE);

     bl = GSfindBlock(gs, "ASK");

     if (bl == NULL)
	  return(NULL);

     /** Find out how many ASKL items we have... **/
     for (Asknum=0; Asknum < BLgetNumLines(bl); Asknum++) {
	  if (strncasecmp(BLgetLine(bl, Asknum), "AskL:", 5) == 0)
	       AskLs++;
     }

     /** Can't guess how many askl things we'll have.. **/
     items = (Requestitem **) malloc(sizeof(Requestitem*)* 
				     (BLgetNumLines(bl) + (AskLs * 10) + 1));

     asktypes = (char **) malloc(sizeof(char*) * 
				 (BLgetNumLines(bl) + (AskLs * 10) + 1));

     reqnum = 0;

     for (Asknum=0; Asknum <BLgetNumLines(bl); Asknum++, reqnum++) {
	  char *askprompt, *cp;

	  strcpy(askline, BLgetLine(bl, Asknum));
	  items[reqnum] = REQitemnew();

	  /*** find the type of question ***/
	  askprompt = strchr(askline, ':');
	  if (askprompt == NULL) {
	       /* Empty line crashes CURRequester unless we do this*/
	       items[reqnum]->prompt = strdup("");
	       items[reqnum]->thing = CUR_LABEL;
	       asktypes[reqnum] = strdup("Note:");
	       continue;
	  }
	  *(askprompt+1) = '\0';
	  askprompt+=2;
	  
	  /*** Zap the tabs, and load it up.. ***/
	  cp = strchr(askprompt, '\t');
	  if (cp != NULL) {
	       defaultval = cp+1;
	       *cp = '\0';
	  } else
	       defaultval = NULL;

	  asktypes[reqnum] = strdup(askline);
	  items[reqnum]->prompt = strdup(askprompt);
	  
	  if (strncasecmp(askline, "Note:", 5) == 0)
	       items[reqnum]->thing = CUR_LABEL;
	  else if (strncasecmp(askline, "Choose:", 7) == 0) {
	       int cnum = 0;

	       items[reqnum]->thing = CUR_CHOICE;
	       items[reqnum]->chooseitem = 0;
	       
	       /*** CURChoice() has a limit of 99 choices ***/
	       items[reqnum]->choices = (char**) malloc(sizeof(char*)*100);

	       /*** add list of choices to struct **/
	       while ((cp = strchr(defaultval, '\t')) != NULL) {
		    *cp = '\0';
		    items[reqnum]->choices[cnum++] = strdup(defaultval);
		    defaultval = cp+1;
	       }
	       if (defaultval != NULL)
		    items[reqnum]->choices[cnum++] = strdup(defaultval);

	       items[reqnum]->choices[cnum] = NULL;

	  }
	  else if (strncasecmp(askline, "Select:", 7) == 0) {
	       items[reqnum]->thing = CUR_CHOICE;
	       items[reqnum]->chooseitem = 0;
	       items[reqnum]->choices = (char**) malloc(sizeof(char*)*3);

	       cp = strrchr(items[reqnum]->prompt, ':');
	       if (cp != NULL) {
		    *cp = '\0';
		    cp++;
		    if (*cp == '1')
			 items[reqnum]->chooseitem = 1;
	       }
	       
	       items[reqnum]->choices[0] = strdup(Gtxt("No",111));
	       items[reqnum]->choices[1] = strdup(Gtxt("Yes",180));
	       items[reqnum]->choices[2] = NULL;
	  }
	  else if (strncasecmp(askline, "AskP:", 5)==0)
	       items[reqnum]->thing = CUR_PASSWD;
	  else if (strncasecmp(askline, "Choosef:", 8) == 0)
	       items[reqnum]->thing = CUR_FNAME;
	  else if (strncasecmp(askline, "AskL:", 5)==0){
	       int x;

	       items[reqnum]->thing = CUR_LABEL;
	       
	       /** Add a bunch more... **/

	       for (x=0; x < 10; x++) {
		    reqnum++;
		    items[reqnum] = REQitemnew();
		    items[reqnum]->prompt = strdup("");
		    items[reqnum]->thing = CUR_ASKL;
		    asktypes[reqnum] = NULL;
	       }

	  } 
	  else
	       items[reqnum]->thing = CUR_PROMPT;

	  if (defaultval != NULL)
	       strncat(items[reqnum]->stowage, defaultval, COLS-1);
     }
     
     items[reqnum] = NULL;
     
     if (!CURrequester(CursesScreen, GSgetTitle(gs), items)) {
	  int respnum = 0;
	  char *choosefile = NULL;

	  responses = (char**) malloc(sizeof(char*)*(10+reqnum));

	  for (i=0; i <reqnum; i++) {
	       if (strcasecmp(asktypes[i], "AskL:")==0) {
		    int alnum, extra;
		    char numlinestr[16];

		    /** Okay, figure out how many lines were typed **/
		    for (alnum=9; alnum >= 0; alnum--) {
			 if (*(items[alnum+i]->stowage) != '\0')
			      break;
		    }
		    alnum++;
		    sprintf(numlinestr, "%d", alnum);

		    responses[respnum++] = strdup(numlinestr);

		    extra = 10 - alnum;
		    while (alnum) {
			 responses[respnum++] = strdup(items[++i]->stowage);
			 alnum--;
		    }
		    i += extra;

	       } else if (strcasecmp(asktypes[i], "Select:")==0) {
		    if (items[i]->chooseitem == 0)
			 responses[respnum++] = strdup("0");
		    else
			 responses[respnum++] = strdup("1");

	       } else if (items[i]->thing == CUR_CHOICE) {
		    responses[respnum++] = strdup(items[i]->choices[items[i]->chooseitem]);
	       }
	       else if (items[i]->thing == CUR_PROMPT ||
			  items[i]->thing == CUR_PASSWD)
		    responses[respnum++] = strdup(items[i]->stowage);
	  }
	  responses[respnum++] = NULL;
     }
     /*** Free memory ***/
     for (i=0; i<Asknum; i++) {

	  REQitemdestroy(items[i]);
	  items[i] = NULL;
	  if (asktypes[i]!=NULL)
	       free(asktypes[i]);
     }

     free(items);
     free(asktypes);

     return(responses);
}

/*
 * Handle a gplus error
 */
#define GERRMSGSIZE 25

void
Gplus_Error(sockfd)
  int sockfd;
{
     char inputline[256];
     char *errmsg[GERRMSGSIZE];
     int i;
     
     Debug("Gplus_Error on %d...", sockfd);
     for (i=0; i < GERRMSGSIZE;i++) {
	  char *cp;

	  if (readline(sockfd, inputline, sizeof(inputline)) <=0)
	       break;
	  ZapCRLF(inputline);
	  if (strcmp(inputline, ".")==0)
	       break;
	  
	  cp = inputline;
	  while ((cp = strchr(cp, '\t')) != NULL)
	       *cp = ' ';

	  errmsg[i] = strdup(inputline);
     }
     
     errmsg[i] = NULL;
     
     CURDialog(CursesScreen, Gtxt("Gopher Transmission Error",95), errmsg);
     for (i=0; ; i++) {
	  if (errmsg[i] == NULL)
	       break;
	  else
	       free(errmsg[i]);
     }

}



/*
 * This fcn transfers a file from a gopher server into f.
 *
 */

boolean
GStoFile(gs, f, view, twirlfn)
  GopherObj *gs;
  FILE *f;
  char *view;
  int (*twirlfn)();
{
     int numread, sockfd;
     char buf[1024];
     int bytemethod;
     int line = 0, i;

     /*** Check for gopher+ and multiple views ***/

     Debug("GStoFile, view %s",view);
     if ((sockfd = GSconnect(gs)) <0) {
	  check_sock(sockfd, GSgetHost(gs), GSgetPort(gs));
	  return(FALSE);
     }

     /** Send out the request **/
     GStransmit(gs, sockfd, NULL, "+", view);
     bytemethod = GSrecvHeader(gs, sockfd);

     if (bytemethod == 0) {
	  Gplus_Error(sockfd);
	  
	  return(FALSE);
     }
	  

     Draw_Status(Gtxt("Receiving File...",126));
     refresh();
     

     if (GSisText(gs, view)) {
	  char cso_click = '\0';

	  while (readline(sockfd, buf, sizeof(buf)) > 0) {
	       ZapCRLF(buf);
	       line ++;
	       if (*buf == '.' && *(buf+1) == '\0')
		    break;



	       if (GSgetType(gs) == A_CSO) {
		    if (*buf == '2')
			 break;
		    
		    if ((*buf >= '3') && (*buf <= '9'))  {
			 fprintf(f, "%s\n", GSgetPath(gs));
			 fprintf(f, "%s\n", buf+4);
			 break;
		    }
		    if (*buf == '-') {


			 if (buf[1] >= '3' && buf[1] <= '9') {
			      fprintf(f, "%s\n", GSgetPath(gs));
			      fprintf(f, "%s\n", buf+5);
			 }
			 else {
			      char *colonpos = strchr(buf+5,':');
			      i = '\0';
			      if (colonpos != NULL && *(colonpos-1) != cso_click) {
				   fprintf(f, "-------------------------------------------------------\n");
				   cso_click = *(colonpos-1);
			      }
			      fputs((colonpos ? colonpos+1 : buf+6), f);
			      fputc('\n', f);
			 }
		    }
	       }
	       else {
		    fputs(buf, f);
		    /** Don't cut long lines... **/
		    if (strlen(buf) < sizeof(buf))  
			 putc('\n', f);
	       }
	       if ((line % 25) == 0)
		    twirlfn();
	  }
     }
     else {
	       
#if defined(VMS) && defined(VMSRecords)
          while((numread = readrecvbuf(sockfd, buf, 512)) > 0) {
	       if (numread < 512)
		   for (i = numread; i < 512; i++)  buf[i] = 0;
               if (fwrite(buf, 512, 1, f) == 0) {
#else
	  while ((numread = readrecvbuf(sockfd, buf, sizeof buf)) > 0) {
	       if (fwrite(buf, numread, 1, f) == 0) {
#endif
		    CursesErrorMsg(Gtxt("Problems Writing",123));
		    closenet(sockfd);
		    return(FALSE);
	       }
	       twirlfn();
	  }
     }

     if (GSgetType(gs) == A_CSO)
	  writestring(sockfd, "quit\r\n");

     closenet(sockfd);
     return(TRUE);
}


/*
 * This function makes a nice vms filename.
 */

void
VMSfile(fname)
  char *fname;
{
#ifndef VMS
     return;
#else
     char    *cp, *dot, *end;
     int     j, k;

     /*** Trim off date-size suffix, if present ***/
     end = fname + strlen(fname);
     if ((*(end - 1) == ']') && ((cp = strrchr(fname, '[')) != NULL) &&
         (cp > fname) && *(--cp) == ' ')
	  *cp = '\0';

     /*** Replace illegal or problem characters ***/
     dot = fname + strlen(fname);
     for (cp = fname; cp < dot; cp++) {

	  /** Replace with underscores **/
	  if (*cp == ' ' || *cp == '/' || *cp == ':' ||
	      *cp == '[' || *cp == ']' || *cp == '{' ||
	      *cp == '}')
	       *cp = '_';

	  /** Replace with dashes **/
	  else if (*cp == '!' || *cp == '?' || *cp == '\'' || 
	           *cp == ',' || *cp == ':' || *cp == '\"' ||
	           *cp == '+' || *cp == '@' || *cp == '\\' ||
	           *cp == '(' || *cp == ')' || *cp == '=' ||
	           *cp == '<' || *cp == '>' || *cp == '#' ||
	           *cp == '%' || *cp == '*' || *cp == '`' ||
	           *cp == '~' || *cp == '^' || *cp == '|' ||
		   *cp == '/')
	       *cp = '-';
     }

     /** Collapse any serial underscores **/
     cp = fname + 1;
     j = 0;
     while (cp < dot) {
	  if (fname[j] == '_' && *cp == '_')
	       cp++;
	  else
	       fname[++j] = *cp++;
     }
     fname[++j] = '\0';

     /** Collapse any serial dashes **/
     dot = fname + (strlen(fname));
     cp = fname + 1;
     j = 0;
     while (cp < dot) {
          if (fname[j] == '-' && *cp == '-')
	       cp++;
	  else
	       fname[++j] = *cp++;
     }
     fname[++j] = '\0';

     /** Trim any trailing or leading **/
     /** underscrores or dashes       **/
     cp = fname + (strlen(fname)) - 1;
     while (*cp == '_' || *cp == '-')
          *cp-- = '\0';
     if (fname[0] == '_' || fname[0] == '-') {
          dot = fname + (strlen(fname));
          cp = fname;
          while ((*cp == '_' || *cp == '-') && cp < dot)
	       cp++;
	  j = 0;
          while (cp < dot)
	       fname[j++] = *cp++;
	  fname[j] = '\0';
     }

     /** Replace all but the last period with _'s, or second **/
     /** to last if last is followed by a terminal Z or z,   **/
     /** e.g., convert foo.tar.Z to                          **/
     /**               foo.tar_Z                             **/
     j = strlen(fname) - 1;
     if ((dot = strrchr(fname, '.')) != NULL) {
	  if (((fname[j] == 'Z' || fname[j] == 'z') && fname[j-1] == '.') &&
	      (((cp = strchr(fname, '.')) != NULL) && cp < dot)) {
	       *dot = '_';
	       dot = strrchr(fname, '.');
	  }
	  cp = fname;
	  while ((cp = strchr(cp, '.')) != NULL && cp < dot)
	       *cp = '_';

          /** But if the root is > 39 characters, move **/
          /** the period appropriately to the left     **/
	  while (dot - fname > 39) {
	       *dot = '\0';
	       if ((cp = strrchr(fname, '_')) != NULL) {
		    *cp  = '.';
		    *dot = '_';
	       } 
	       else if ((cp = strrchr(fname, '-')) != NULL) {
		    *cp  = '.';
		    *dot = '_';
	       }
	       else {
		    *dot = '_';
		    j = strlen(fname);
		    fname[j+1] = '\0';
		    while (j > 39)
			 fname[j--] = fname[j];
		    fname[j] = '.';
	       }
               dot = strrchr(fname, '.');
	  }

          /** Make sure the extension is < 40 characters **/
          if ((fname + strlen(fname) - dot) > 39)
	       *(dot+40) = '\0';

	  /** Trim trailing dashes or underscores **/
	  j = strlen(fname) - 1;
	  while (fname[j] == '_' || fname[j] == '-')
	       fname[j--] = '\0';
     }
     else {
	  /** No period, so put one on the end, or after   **/
	  /** the 39th character, trimming trailing dashes **/
	  /** or underscrores                              **/
	  if (strlen(fname) > 39)
	       fname[39] = '\0';
	  j = strlen(fname) - 1;
	  while ((fname[j] == '_') || (fname[j] == '-'))
	       j--;
	  fname[++j] = '.';
	  fname[++j] = '\0';
     }

     /** Make sure the rest of the original string in nulled, **/
     /** to avoid problems with the CURwgetstr() line editor  **/
     cp = fname + strlen(fname);
     while (cp < end)
          *cp++ = '\0';

	  
#endif
}
     


/*
 * This function makes a nice UNIX filename
 */

void
UNIXfile(fname)
  char *fname;
{
     char *cp;

     for (cp = fname; *cp != '\0'; cp++) {
	  switch (*cp) {
	  case '\'':
	  case '\"':
	  case '/':
	  case ' ':
	  case '(':
	  case ')':
	  case '&':
	       *cp = '-';
	       break;

	  case '[':
	  case ']':
	  case '{':
	  case '}':
	  case '|':
	  case '!':
	  case '?':
	  case '*':
	  case '`':
	  case '\\':
	  case '<':
	  case '>':
	       *cp = '_';
	       break;


	  }
     }

     if (strlen(fname) >50) {
	  fname[50] = '\0';
     }

     /*** Don't allow '-' or '.' as first character of filename ***/
     if (fname[0] == '-' || fname[0] == '.')
	  fname[0] = '_';

}


/*
 * This procedure prompts the user for a name, checks for pipe characters
 * and ~ characters in the filename and saves the file to disk.
 * 
 * If infile is NULL then contact the server and get the file.
 * 
 * If saveto is non-NULL then don't prompt the user for a filename
 * to save into, just "do it".
 */

void
Save_file(gs, saveto, view)
  GopherObj *gs;
  char *saveto, *view;
{
     char    Title[128], Userfilename[128];
     char    *cp;
     char    buf[1024];
     boolean Openpipe = FALSE;
     boolean GS2FileSucceeded = TRUE;
     FILE    *f;

     switch (GSgetType(gs)) {
     case A_CSO:
     case A_DIRECTORY:
     case A_ERROR:
     case A_INDEX:
     case A_INFO:
     case A_TELNET:
     case A_TN3270:
	  if (saveto == NULL) {
	       CursesErrorMsg(Gtxt("Sorry, can't save that item to a file!",154));
	       return;
	  }

     }

     if (view == NULL)
          view = Choose_View(gs);

     if (view == NULL || view == "")
	  return;

     /*** Get the Title ***/
     strcpy(Title, GSgetTitle(gs));

     /*** Construct a nice default filename ***/
     
     if (saveto == NULL) {
  	/* It shouldnt ever come here if Secure, but I've noticed calls 
     	to this in the code */
  	if (NoShellMode || SecureMode) {
		CursesErrorMsg(Gtxt("Sorry, you are not allowed to do this", 64));
		return;
  	}
	  /*** Let them use the Title ***/
	  strcpy(Userfilename, Title);

#ifdef VMS
	  VMSfile(Userfilename);
#else
	  UNIXfile(Userfilename);
#endif

	  if (CURGetOneOption(CursesScreen, Title, Gtxt("Save in file:",136),
	  		      Userfilename)<0)
	       return;

	  saveto = Userfilename;
     }

     if (*saveto == '\0')
	  return;

     if (view == NULL)
	  view = Choose_View(gs);

     if (view == NULL || view == "")
	  return;

#ifndef VMS
     if (*saveto == '|') {
	  /** Open a pipe! **/
	  Openpipe = TRUE;
	  saveto++;
     }
     
     if (*saveto == '~') {
	  /*** Save in our home directory ***/
	  if (*(saveto + 1) == '/' && (cp = getenv("HOME")) != NULL) {
	       /*** Expand ~ to the home directory ***/
	       strcpy(buf,cp);
	       buf[strlen(cp)]='/';
	       strcpy(buf+strlen(cp) +1, saveto+2);
	       strcpy(saveto, buf);
	  } else {
	  /*** Save in someone else's home directory ***/
	       struct passwd *pass;

	       cp = strchr(saveto,'/');
	       *cp = '\0';
	       pass = getpwnam(saveto+1);
	       if (pass != NULL) {
		    /** align in prep for the home dir **/
		    strcpy(buf,pass->pw_dir);
		    buf[strlen(pass->pw_dir)]='/';
		    strcpy(buf+strlen(pass->pw_dir)+1,cp+1);
		    strcpy(saveto,buf);
		    
	       } else {
		    char tmpstr[256];
		    sprintf(tmpstr, Gtxt("No such user '%s'",114), saveto+1);
		    CursesErrorMsg(tmpstr);
		    return;
	       }
	  }
     }
#endif

#if defined(VMS) && defined(VMSRecords)
     if (GSisText(gs, view))
          /*** Save ASCII files as VARIABLE length records for VMS ***/
	  f = fopen_VAR(saveto, "w+");
     else
          /*** Save binary files as FIXED 512 records for VMS ***/
	  f = fopen_FIX(saveto, "w+");
#else
     if (Openpipe)
	  f = popen(saveto, "w+");
     else
	  f = fopen(saveto, "w+");
#endif

     if (f == NULL) {
	  char tempstr[128];

	  sprintf(tempstr, Gtxt("Couldn't create '%s'",80), saveto);
	  CursesErrorMsg(tempstr);
	  return;
     }

     if (GSgetLocalFile(gs) != NULL && strcmp(view, GSgetLocalView(gs))==0) {
	  /** We've already retrieved it, copy it over... **/
	  int oldfd, cc;

	  oldfd = open(GSgetLocalFile(gs), O_RDONLY, 0);
	  if (oldfd <0) {
	       CursesErrorMsg(Gtxt("Can't open old file..",68));
	       if (Openpipe)
		    pclose(f);
	       else
		    fclose(f);
	       return;
	  }
	  
	  while ( (cc=read(oldfd, buf, sizeof buf)) > 0) {
	       if (fwrite(buf, cc, 1, f) <= 0)
		    CursesErrorMsg(Gtxt("Problems Writing",123));
	       twirl();
	  }
	  if (Openpipe)
	       pclose(f);
	  else
	       fclose(f);

	 close(oldfd);
	 return;

     } else {
	  /** We don't have the file yet, let's get it... **/
	  
	  GS2FileSucceeded = GStoFile(gs, f, view, twirl); /* false if fails */

	  if (Openpipe)
	       pclose(f);
	  else
	       fclose(f);

	  if (!GS2FileSucceeded)
	       unlink(saveto);
     }
}
	       

/*
 * This procedure saves a directory (menu) list to a file.
 * It prompts the user for a filename, checks for pipe characters
 * and ~ characters in the filename and saves the directory name
 * and the list of items in the file to disk.
 * 
 * This is useful after searches for which the titles of hits
 * themselves contain the information one was seeking.
 */

void
Save_list(gd)
  GopherDirObj *gd;
{
     char    Title[256], Userfilename[128];
     char    *cp, *saveto;
     int     numitems, j;
     char    buf[128];
     boolean Openpipe = FALSE;
     FILE    *f;

     *Userfilename = '\0';

     sprintf(Title,"%s", GDgetTitle(gd));
     if (CURGetOneOption(CursesScreen, Title, 
			 Gtxt("Save list of items in file:",137),
                         Userfilename)<0)
	  return;
     saveto = Userfilename;
     if (*saveto == '\0')
	  return;

#ifndef VMS
     if (*saveto == '|') {
	  /** Open a pipe! **/
	  Openpipe = TRUE;
	  saveto++;
     }
     
     if (*saveto == '~') {
	  /*** Save in our home directory ***/
	  if (*(saveto + 1) == '/' && (cp = getenv("HOME")) != NULL) {
	       /*** Expand ~ to the home directory ***/
	       strcpy(buf,cp);
	       buf[strlen(cp)]='/';
	       strcpy(buf+strlen(cp) +1, saveto+2);
	       strcpy(saveto, buf);
	  } else {
	  /*** Save in someone else's home directory ***/
	       struct passwd *pass;

	       cp = strchr(saveto,'/');
	       *cp = '\0';
	       pass = getpwnam(saveto+1);
	       if (pass != NULL) {
		    /** align in prep for the home dir **/
		    strcpy(buf,pass->pw_dir);
		    buf[strlen(pass->pw_dir)]='/';
		    strcpy(buf+strlen(pass->pw_dir)+1,cp+1);
		    strcpy(saveto,buf);
		    
	       } else {
		    char tmpstr[256];
		    sprintf(tmpstr, Gtxt("No such user '%s'",114), saveto+1);
		    CursesErrorMsg(tmpstr);
		    return;
	       }
	  }
     }
#endif

#if defined(VMS) && defined(VMSRecords)
     f = fopen_VAR(saveto, "w+");
#else
     if (Openpipe)
	  f = popen(saveto, "w+");
     else
	  f = fopen(saveto, "w+");
#endif

     if (f == NULL) {
	  char tempstr[128];

	  sprintf(tempstr, Gtxt("Couldn't create '%s'",80), saveto);
	  /** Should give better error messages here **/
	  CursesErrorMsg(tempstr);
	  return;
     }
     
     /*** Write the Title, and put the Searchstring, ***/
     /*** if present, on the next line.              ***/
     sprintf(buf,"  %s\n\n", GDgetTitle(gd));
     if (Searchstring != NULL)
          if ((cp = strstr(buf, Searchstring)) != NULL)
	       *(cp-1) = '\n';
     if (fwrite(buf, strlen(buf), 1, f) <= 0)
          CursesErrorMsg(Gtxt("Problems Writing",123));
     twirl();

     /*** Write the Items ***/
     numitems = GDgetNumitems(gd);
     for (j = 0; j < numitems; j++) {
          sprintf(buf, "%s\n", GSgetTitle(GDgetEntry(gd, j))); 
          if (fwrite(buf, strlen(buf), 1, f) <= 0) {
               CursesErrorMsg(Gtxt("Problems Writing",123));
          }
	  twirl();
     }

     if (Openpipe)
          pclose(f);
     else
          fclose(f);
}



/*
** This procedure exits out of the curses environment and
** displays the file indicated by pathname to the screen
** using a pager command of some sort 
*/

void
GSdisplay(gs)
  GopherObj *gs;
{
     char command[MAXSTR];
     int ch;
     char *file = GSgetLocalFile(gs);
     char *view = GSgetLocalView(gs);

#ifdef VMS
     char *cp;
     int return_status;
     /** Keep DECC from complaining **/
     struct dsc$descriptor_s  command_desc;
     command_desc.dsc$b_class   = DSC$K_CLASS_S;
     command_desc.dsc$b_dtype   = DSC$K_DTYPE_T;
#endif


     /** Search for a helper application **/
     RCdisplayCommand(GlobalRC, view, file, command);

     Debug("Displaying view %s ", view);
     Debug("command %s ", command);
     Debug("file %s", file);
     Debug("/%s \n", GSgetLocalFile(gs));

     if (strlen(command) == 0) {
	  if (SecureMode || NoShellMode)
	       CursesErrorMsg(Gtxt("Sorry, no viewer for the file....",166));
	  else
	       Save_file(gs, NULL, view);

	  return;
     }


#ifdef VMS
     if (strncasecmp("TPU",command,3) == 0) {
	  
	  CURexit(CursesScreen);

	  /** Make sure we invoke TPU in /READ_ONLY/NOJOURNAL mode **/
	  for (cp = command; *cp; cp++)
               *cp = _toupper(*cp);
	  if (strstr(command, "/READ") == NULL)
	       strcat(command,"/READ");
	  if (strstr(command, "/NOJOU") == NULL)
	       strcat(command,"/NOJOU");

	  /** Load the descriptor and call TPU **/
          command_desc.dsc$w_length  = strlen(command);
          command_desc.dsc$a_pointer = command;
	  return_status = TPU$TPU(&command_desc);
	  if (!return_status&1) {
	       CURenter(CursesScreen);
	       CursesErrorMsg(Gtxt("Error invoking callable TPU as a pager!!!",85));
	       return;
	  }
	  if (GSgetType(gs) != A_APP) {
	       GSprompter(gs, view, command);
	  }
     } else
#endif
     if (strncasecmp(command, "builtin-upload", 14) == 0) {
	  BuiltinUploadfile();
	  return;
     }
     else if (strncasecmp(command, "builtin-download", 14) == 0) {
	  BuiltinDownload(getenv("HOME"));
	  return;
     }
     else if (strncasecmp(command, "builtin", 7) == 0) {
	  PagerBuiltin(CursesScreen, gs);
	  return;
     }
     else {
	  CURexit(CursesScreen);
	  FIOsystem(command);
	  if (GSgetType(gs) != A_APP) {
	       GSprompter(gs, view, command);
	  }

     }
}


/*
 * Display the prompt at the end of viewing a document
 */

void
GSprompter(gs, view, command)
  GopherObj *gs;
  char *view, *command;
{
     int ch;

#ifdef VMS
     printf(Gtxt("\nPress %sRETURN%s to continue",184),
     	    CURgetHighon(CursesScreen), CURgetHighoff(CursesScreen));

     if (!SecureMode && !NoShellMode) {
          if (COLS < 78)
	       printf(",\n   ");
	  else
	       printf(", ");
	  printf(Gtxt("%sm%s to mail, %sD%s to download, %ss%s to save, or %sp%s to print:",61),
		 CURgetHighon(CursesScreen), CURgetHighoff(CursesScreen),
		 CURgetHighon(CursesScreen), CURgetHighoff(CursesScreen),
		 CURgetHighon(CursesScreen), CURgetHighoff(CursesScreen),
		 CURgetHighon(CursesScreen), CURgetHighoff(CursesScreen));
     }
#ifndef NOMAIL
     else {
	  printf(Gtxt(", %sm%s to mail, %sD%s to download:",62),
		 CURgetHighon(CursesScreen), CURgetHighoff(CursesScreen),
		 CURgetHighon(CursesScreen), CURgetHighoff(CursesScreen));
     }
#else
     else {
	  printf(Gtxt(", %sD%s to download:",237),
		 CURgetHighon(CursesScreen), CURgetHighoff(CursesScreen));
     }
#endif
#else /*** Unix: ****/
     printf(Gtxt("\nPress <RETURN> to continue",185));

     if (!SecureMode && !NoShellMode)
	  printf(Gtxt(",\n   <m> to mail, <D> to download, <s> to save, or <p> to print:",64));
#ifndef NOMAIL
     else
	  printf(Gtxt(", <m> to mail, <D> to download:",63));
#else
     else
	  printf(Gtxt(", <D> to download:",238));
#endif
#endif
     fflush(stdin);
     fflush(stdout);
     noecho();
     cbreak();

     ch = 0;
     while (ch == 0) {
	  ch=getchar();
#ifdef VMS
	  if (HadVMSInt)
	       break;
#endif

	  if (SecureMode || NoShellMode) {
	       switch (ch) {
	       case '\n':
	       case '\r':
	       case KEY_ENTER:
	       case ' ':
	       case 'q' :
	       case 'Q' :
#ifdef VMS
	       case '\032': /* ^Z */
#endif
		    break;
#ifndef NOMAIL
	       case 'm':
#ifdef VMS
                    (void) getchar();
#endif
		    CURenter(CursesScreen);
		    GSmail(gs);
		    break;
#endif
	       case 'D':
#ifdef VMS
		    (void) getchar();
#endif
		    CURenter(CursesScreen);
		    Download_file(gs);
		    break;

	       default:
		    puts(CURgetBell(CursesScreen));
		    fflush(stdout);
		    ch=0;
		    break;
	       }
	  }
	  else {

	       switch(ch) {
	       case '\n':
	       case '\r':
	       case ' ' :
	       case 'q' :
	       case 'Q' :
#ifdef VMS
	       case '\032': /* ^Z */
#endif
		    break;
	       case 's':
#ifdef VMS
                    (void) getchar();
#endif
		    CURenter(CursesScreen);
		    Draw_Status(Gtxt("Saving File...",138));
		    refresh();

 		    if (!(SecureMode || NoShellMode))
			 Save_file(gs,NULL, view);
		    break;

	       case 'p':
#ifdef VMS
                    (void) getchar();
#endif
		    if (!RCprintCommand(GlobalRC, view, GSgetLocalFile(gs), command)) {
			 CursesErrorMsg(Gtxt("Sorry, cannot print this document",155));
			 return;
		    }
		    FIOsystem(command);
		    break;
		    
	       case 'm':
#ifdef VMS
                    (void) getchar();
#endif
                    CURenter(CursesScreen);
		    GSmail(gs);
		    break;

	       case 'D':
#ifdef VMS
		    (void) getchar();
#endif
		    CURenter(CursesScreen);
		    Download_file(gs);
		    break;
		    
	       default:
		    Debugmsg("GSdisplayAfter - beep\r\n");
		    puts(CURgetBell(CursesScreen));
		    fflush(stdout);
		    ch=0;
		    break;
	       }
	  }
     }
     
     tputs(CURgetCLS(CursesScreen),1,outchar);
     fflush(stdout);
}



/*
 * This displays a single line message using CUR routines
 */
     
void
CursesMsg(Message, title)
  char *Message;
  char *title;
{
     char *mess[2];
     
     mess[0] = Message;
     mess[1] = NULL;

     Debugmsg("CursesMsg - beep\r\n")
     CURBeep(CursesScreen);

     CURDialog(CursesScreen, title,mess);

     return;

}


/*
 * This does the same as above, but puts a window title on the mess 
 */

void
CursesErrorMsg(Message)
  char *Message;
{
     CursesMsg(Message, Gtxt("Gopher Error",90));
}



#ifdef CLIENT_LOGGER
#if defined(VMS) && defined(MULTINET)
#include "syslog.h"
#else
#include <syslog.h>
#endif
#endif

int
logrequest(msg, gs)
  char *msg;
  GopherObj *gs;
{
#ifdef CLIENT_LOGGER
     static boolean inited = FALSE;
     char *url = "";

     if (inited == FALSE) {
	  openlog("gopher", (LOG_PID), LOG_DAEMON);
	  setlogmask(LOG_UPTO(LOG_INFO));
	  inited = TRUE;
     }
     if (gs != NULL)
	  url = GSgetURL(gs);

     syslog(LOG_INFO, "%s %s", msg, url ? url : "");

#endif
     return(0);
}
.
Response: text/plain
Original URLgopher://bitreich.org/0/gopher2007/2007-gopher-mirror/gop...
Content-Typetext/plain; charset=utf-8