/********************************************************************
 * lindner
 * 3.46
 * 1993/11/03 03:50:01
 * /home/mudhoney/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.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.
 *
 * Revision 1.11  1993/01/31  00:10:44  lindner
 * Checks for multiple views when saving.
 *
 * Revision 1.10  1993/01/17  04:30:38  lindner
 * VMSfile() truncates to a legal VMS filename length.
 *
 * Revision 1.9  1993/01/14  21:16:58  lindner
 * New routines to make nice filenames for Unix and VMS.
 * Add return to make screen look nice for VMS pagers.
 *
 * Revision 1.8  1993/01/12  21:45:17  lindner
 * Removed \n from system() calls.
 * Added filter to Save_file for directories and searches
 *
 * Revision 1.7  1993/01/12  18:19:32  lindner
 * Fixed Save_file call in display_file
 *
 * Revision 1.6  1993/01/09  01:37:32  lindner
 * Changed Save_file so that it takes a default filename.
 * Removed some dead code...
 *
 * Revision 1.5  1992/12/31  05:50:03  lindner
 * Added mods for VMS
 *
 * Revision 1.4  1992/12/11  21:19:51  lindner
 * Really fixed RealName this time..  Gotta test these things..
 *
 * Revision 1.3  1992/12/11  20:59:48  lindner
 * Fixed problem with syntax error RealName, grrr
 *
 * Revision 1.2  1992/12/11  20:54:38  lindner
 * Mailing items with quotes in their name now works, added fflush(stdin)
 * everywhere to fix some input processing problems.
 *
 * Revision 1.1  1992/12/10  23:32:16  lindner
 * gopher 1.1 release
 *
 *********************************************************************/


#include "gopher.h"
#ifndef VMS
#include <pwd.h>
#endif
#ifdef mips
char *getenv();
#endif
#include "Debug.h"

extern int twirl();

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..
 *
 * pct is an optional val that is to be displayed.
 */

int
twirl(pct)
  int pct;
{
     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];
     FILE *infile, *mailit;
     char buf[512];
#ifdef VMS
     char MailAddress[512];
#endif
     
     if (SaveName==NULL) {
	  if ((SaveName = (char *) malloc(sizeof(char)*256)) == NULL)
	       perror("Out of memory!");
	  *SaveName = '\0';
     }

     if (CURGetOneOption(CursesScreen, Realname, "Mail current document to:",
     			 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)
	  if ( strspn(SaveName, ACHARS) != strlen(SaveName)||
	      *SaveName == '-') {
	       Debug("mail_file - beep - bad char",NULL)
	       puts(CURgetBell(CursesScreen));
	       fflush(stdout);
	       Draw_Status("Address syntax rejected...");
	       refresh();
	       return;
	  }

     if (strchr(Realname, '\"') != NULL)
	  Realname = "Gopher File";
     
     Draw_Status("Mailing File...");
     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("Mailing file to %s...", MailAddress);
     system(command);
     CURenter(CursesScreen);
#else
     sprintf(command, "%s %s", 
	     MAIL_COMMAND, SaveName);
     
     infile = fopen(Filename, "r");
     mailit = popen(command, "w");
     
     if (mailit == NULL || infile == NULL) {
	  CursesErrorMsg("System problem, unable to mail");
	  return;
     }

     fprintf(mailit, "Subject: %.70s\n\n", Realname);

     while (readline(fileno(infile), buf, sizeof(buf))) {
	  twirl();
	  fprintf(mailit, "%s", buf);
     }

     pclose(mailit);
     fclose(infile);

#endif

}

/*
 *  This fcn allows one to choose a view out of a potential nine views.
 *  returns false if fails
 */

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

     if (GSisGplus(gs)) {
	  char *choices[10], *views[10];

	  GSgetginfo(gs);
	  
	  if (GSgetNumViews(gs) >1) {
	       int choice, default_choice = 0;

	       for (viewno=0; viewno <GSgetNumViews(gs); viewno++) {
		    views[viewno] =  strdup(VIgetViewnLang(GSgetView(gs, 
					    viewno), viewnlang));

		    choices[viewno] = strdup(VIgetPrettyView(GSgetView(gs,viewno),viewnlang));
		    if (!strcmp(VIgetType(GSgetView(gs, viewno)), "text/plain"))
			 default_choice = viewno;
	       }

	       choices[viewno] = NULL;
	       choice = CURChoice(CursesScreen, GSgetTitle(gs), choices,
				  "Choose a document type", 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]);

	       }
	       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:
	  case A_MACHEX:
	  case A_MIME:
	  case A_HTML:
	       view = "text/plain";
	       break;
	  case A_GIF:
	       view = "image/gif";
	       break;
	  case A_IMAGE:
	       view = "image";
	       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 = "Binary";
	       break;
	  default:
	       return(NULL);
	  }

	  return(view);
     }

}

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

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

     GSgetginfo(gs);
	  
     bl = GSfindBlock(gs, "ASK");

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

     items = (Requestitem **) malloc(sizeof(Requestitem*)* BLgetNumLines(bl));
     asktypes = (char **) malloc(sizeof(char*) * BLgetNumLines(bl));

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

	  strcpy(askline, BLgetLine(bl, Asknum));
	  items[Asknum] = (Requestitem*) (malloc(sizeof(Requestitem)));
	  items[Asknum]->stowage = (char *) malloc(sizeof(char) * COLS);
	  items[Asknum]->stowage[0] = '\0';

	  /*** find the type of question ***/
	  askprompt = strchr(askline, ':');
	  if (askprompt == NULL) {
	       /* Empty line crashes CURRequest unless do this*/
	       items[Asknum]->stowage = strdup("");
	       items[Asknum]->thing = CUR_LABEL;
	       asktypes[Asknum] = 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[Asknum] = strdup(askline);
	  items[Asknum]->prompt = strdup(askprompt);
	  
	  if (strncasecmp(askline, "Note:", 5) == 0)
	       items[Asknum]->thing = CUR_LABEL;
	  else if (strncasecmp(askline, "Choose:", 7) == 0) {
	       int cnum = 0;

	       items[Asknum]->thing = CUR_CHOICE;
	       items[Asknum]->chooseitem = 0;
	       
	       items[Asknum]->choices = (char**) malloc(sizeof(char*)*100);

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

	       items[Asknum]->choices[cnum++] = NULL;

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

	       cp = strrchr(items[Asknum]->prompt, ':');
	       if (cp != NULL) {
		    *cp = '\0';
		    cp++;
		    if (*cp == '1')
			 items[Asknum]->chooseitem = 1;
	       }
	       
	       items[Asknum]->choices[0] = strdup("No");
	       items[Asknum]->choices[1] = strdup("Yes");
	       items[Asknum]->choices[2] = NULL;
	  }
	  else if (strncasecmp(askline, "AskP:", 5)==0)
	       items[Asknum]->thing = CUR_PASSWD;
	  else
	       items[Asknum]->thing = CUR_PROMPT;

	  if (defaultval != NULL)
	       strncpy(items[Asknum]->stowage, defaultval, 39);
     }
     
     items[Asknum] = NULL;
     
     if (!CURrequester(CursesScreen, GSgetTitle(gs), items)) {
	  int respnum = 0;

	  responses = (char**) malloc(sizeof(char*)*(1+Asknum));

	  for (i=0; i <Asknum; i++) {
	       if (strcasecmp(asktypes[i], "AskL:")==0) {
		    responses[respnum++] = strdup("1");
		    responses[respnum++] = strdup(items[i]->stowage);
	       } 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++) {
	  if (items[i]->stowage != (char*) NULL)
	       free(items[i]->stowage);

	  if (items[i]->prompt != (char*) NULL) 
	       free(items[i]->prompt);
	  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, "Gopher Transmission Error", errmsg);
     for (i=0; ; i++) {
	  if (errmsg[i] == NULL)
	       break;
	  else
	       free(errmsg[i]);
     }

}



/* Read from socket fd to file f until EOF */
boolean
CopyFile(fd, f)
  int fd;
  FILE *f;
{
     int numread;
     char buf[1024];

	       
     while ((numread = readrecvbuf(fd, buf, sizeof buf)) > 0) {
	  if (fwrite(buf, numread, 1, f) == 0) {
	       closenet(fd);
	       return(FALSE);
	  }
     }
}


/*
 * 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("Receiving file...");
     refresh();
     

     if (GSisText(gs, view)) {
	  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,':');
			      if (colonpos != NULL && *(colonpos-1) != i) {
				   fprintf(f, "-------------------------------------------------------\n");
				   i = *(colonpos-1);
			      }
			      fputs((colonpos ? colonpos+1 : buf+6), f);
			      fputc('\n', f);
			 }
		    }
	       }
	       else {
		    fputs(buf, f);
		    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("Problems Writing");
		    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 = '_';

	  /** 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 ')':

	       *cp = '-';
	  }
     }

     if (strlen(fname) >50) {
	  fname[40] = '\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_DIRECTORY:
     case A_INDEX:
     case A_TELNET:
     case A_TN3270:
	  if (saveto == NULL) {
	       CursesErrorMsg("Sorry, can't save that item to a file!");
	       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("You cant do that");
		return;
  	}
	  /*** Let them use the Title ***/
	  strcpy(Userfilename, Title);

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

	  if (CURGetOneOption(CursesScreen, Title, "Save in file:",
	  		      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, "No such user '%s'", 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, "Couldn't create '%s'", 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);
	  if (oldfd <0) {
	       CursesErrorMsg("Can't open old file..");
	       if (Openpipe)
		    pclose(f);
	       else
		    fclose(f);
	       return;
	  }
	  
	  while ( (cc=read(oldfd, buf, sizeof buf)) > 0) {
	       if (fwrite(buf, cc, 1, f) <= 0)
		    CursesErrorMsg("Problems Writing");
	       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, "Save list of items in file:",
                         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, "No such user '%s'", 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, "Couldn't create '%s'", 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("Problems Writing");
     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("Problems Writing");
          }
	  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
#include descrip
     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("Sorry, no viewer for the file....");
	  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("Error invoking callable TPU as a pager!!!");
	       return;
	  }
     } else
#endif

     if (strncasecmp(command, "builtin",7) == 0) {
	  PagerBuiltin(CursesScreen, gs);
	  return;
     }
     else {
	  CURexit(CursesScreen);
	  system(command);
     }


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

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

     if (!SecureMode && !NoShellMode)
	  printf(",\n   <m> to mail, <D> to download, <s> to save, or <p> to print:");
#ifndef NOMAIL
     else
	  printf(", <m> to mail, <D> to download:");
#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 ' ':
		    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 ' ' :
		    break;
	       case 's':
#ifdef VMS
                    (void) getchar();
#endif
		    CURenter(CursesScreen);
		    Draw_Status("Saving File...");
		    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("Sorry, cannot print this document");
			 return;
		    }
		    system(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:
		    Debug("GSdisplayAfter - beep\r\n",NULL)
		    Debug("GSdisplayAfter - beep2\r\n",NULL)
		    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;

     Debug("CursesMsg - beep\r\n",NULL)
     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, "Gopher Error");
}



#ifdef CLIENT_LOGGER
#include <syslog.h>
#endif

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);

#endif
}



