/********************************************************************
 * $Author: lindner $
 * $Revision: 2.4 $
 * $Date: 1993/01/12 21:12:23 $
 * $Source: /home/mudhoney/GopherSrc/gopher1.11a/object/RCS/util.c,v $
 * $State: Rel $
 *
 * Paul Lindner, University of Minnesota CIS.
 *
 * Copyright 1991, 1992 by the Regents of the University of Minnesota
 * see the file "Copyright" in the distribution for conditions of use.
 *********************************************************************
 * MODULE: util.c
 * Various useful utilities for gopher clients and servers
 *********************************************************************
 * Revision History:
 * $Log: util.c,v $
 * Revision 2.4  1993/01/12  21:12:23  lindner
 * Reverted to old readfield behavior()  \n is now ignored again.
 *
 * Revision 2.3  1993/01/08  23:29:21  lindner
 * More mods from jqj.
 *
 * Revision 2.2  1992/12/31  04:58:41  lindner
 * merged 1.1.1.1 and 2.1
 *
 * Revision 2.1  1992/12/21  19:41:14  lindner
 * Added check for null in writestring
 * Added function skip_whitespace
 *
 * Revision 1.1.1.1  1992/12/31  04:52:01  lindner
 * Changes for VMS
 *
 * Revision 1.1  1992/12/10  23:27:52  lindner
 * gopher 1.1 release
 *
 *
 *********************************************************************/

#include "Malloc.h"
#include "String.h"
#include <ctype.h>
#include "boolean.h"
#include "util.h"

#if defined(VMS) && defined(UCX)
#include <errno.h>
#endif
 
#if defined(VMS) && (defined(WOLLONGONG) || defined(MULTINET))
/* Multinet and Wollongong (non UCX-emulation) use channel numbers */
/* for sockets, which are small multiples of 16.  The first 5 */
/* channels can be assumed to be already used, so we assume that */
/* sockets start at 64, and that only 64 VAXC fds are simultaneously */
/* open in the program.  Actually, the first socket is likely to be */
/* more like 176! */
#define IS_SOCKET(s) ((s)>=64)
 
/* Close a socket.  
 * Note that in old Wollongong and Multinet implementations close()
 * works only on fds, not sockets.
 * For UCX and Unix, closenet() is #defined to be close()
 */
int closenet(s)
int s;
{
    if (IS_SOCKET(s)) {
#ifdef MULTINET
	return (socket_close(s));
#else /* WOLLONGONG */
	return (netclose(s));
#endif
    }
    else
	close(s); /* shouldn't be calling this routine */
}
#endif /* WOLLANGONG or MULTINET */


/* Read "n" bytes from a descriptor.
 * Use in place of read() when fd is a stream socket
 *
 * Returns the number of total bytes read.
 */

int readn(fd, ptr, nbytes)
  int fd;
  char *ptr;
  int nbytes;
{
     int nleft, nread;
     
     nleft = nbytes;
     while (nleft > 0) {
#if defined(VMS) && defined(WOLLONGONG)
	  nread = IS_SOCKET(fd) ? netread(fd, ptr, nleft) : read(fd, ptr, nleft);
#else
#if defined(VMS) && defined(MULTINET)
	  nread = IS_SOCKET(fd) ? socket_read(fd, ptr, nleft) : read(fd, ptr, nleft);
#else
	  nread = read(fd, ptr, nleft);
#endif
#endif
#if defined(VMS) && defined(UCX)
          if (nread < 0 && errno == EPIPE)
	       break;
#endif
	  if (nread < 0)
	       return(nread);	/* error, return <0 */
	  else if (nread == 0)	/* EOF */
	       break;
	  
	  nleft 	-= nread;
	  ptr 	+= nread;
     }
     return(nbytes - nleft);	/* return >= 0) */
}



/*
 * Write "n" bytes to a descriptor.
 * Use in place of write() when fd is a stream socket
 *
 * We return the number of bytes written
 */

int 
writen(fd, ptr, nbytes)
  int	fd;
  char	*ptr;
  int	nbytes;
{
     int nleft, nwritten;
     
     nleft = nbytes;
     while(nleft > 0) {
#if defined(VMS) && defined(WOLLONGONG)
	  nwritten = IS_SOCKET(fd) ? netwrite(fd, ptr, nleft) : write(fd, ptr, nleft);
#else
#if defined(VMS) && defined(MULTINET)
	  nwritten = IS_SOCKET(fd) ? socket_write(fd, ptr, nleft) : write(fd, ptr, nleft);
#else
	  nwritten = write(fd, ptr, nleft);
#endif
#endif
	  if (nwritten <= 0)
	       return(nwritten);	/* error */
	  
	  nleft	-= nwritten;
	  ptr	+= nwritten;
     }
     return(nbytes - nleft);
}


/*
 * Writestring uses the writen and strlen calls to write a
 * string to the file descriptor fd.  If the write fails
 * a -1 is returned. Otherwise zero is returned.
 */

int writestring(fd, stringptr)
  int	fd;
  char	*stringptr;
{
     int length;

     if (stringptr == NULL)
	  return(0);

     length = strlen(stringptr);
     if (writen(fd, stringptr, length) != length) {
	  return(-1);
     }
     else
	  return(0);
}

/*
 * Read from the socket into a buffer.  Mucho more efficent in terms of
 * system calls..
 */

#define RECVSIZE 4096

static int readrecvbuf(sockfd, buf, len)
  int sockfd;
  char *buf;
  int len;
{
     static char recvbuf[RECVSIZE];
     static int  recvbufptr  = 0;
     static int  recvbufsize = 0;
     static int Oldsockfd = 0;
     int bytesread = 0;

     if (recvbufptr == 0 || Oldsockfd != sockfd) {
#if defined(VMS) && defined(WOLLONGONG)
	  recvbufsize = IS_SOCKET(sockfd) ?
		netread(sockfd, recvbuf, RECVSIZE) : read(sockfd, recvbuf, RECVSIZE);
#else
#if defined(VMS) && defined(MULTINET)
	  recvbufsize = IS_SOCKET(sockfd) ?
		socket_read(sockfd, recvbuf, RECVSIZE) : read(sockfd, recvbuf, RECVSIZE);
#else
	  recvbufsize = read(sockfd, recvbuf, RECVSIZE);
#endif
#endif
	  Oldsockfd = sockfd;
	  recvbufptr = 0;
	  if (recvbufsize == 0)
	       return(0);
#if defined(VMS) && defined(UCX)
	  if (recvbufsize < 0 && errno == EPIPE)
	       return(0);
#endif

     }
     
     while (len--) {
	  *buf++ = recvbuf[recvbufptr++];
	  bytesread++;

	  if (recvbufptr == recvbufsize && len != 0) {
	       recvbufsize = readn(sockfd, recvbuf, RECVSIZE);
	       recvbufptr = 0;
	       if (recvbufsize == 0)
		    return(bytesread);
	       if (recvbufsize < 0)
		    return(recvbufsize);

	  } else if (recvbufptr >= recvbufsize)
	       recvbufptr = 0;
     }
     return(bytesread);
}     


/*
 * Read a line from a descriptor.  Read the line one byte at a time,
 * looking for the newline.  We store the newline in the buffer,
 * then follow it with a null (the same as fgets(3)).
 * We return the number of characters up to, but not including,
 * the null (the same as strlen(3))
 */

int readline(fd, ptr, maxlen)
  int	fd;
  char	*ptr;
  int 	maxlen;
{
     int n;
     int rc;
     char c;

     
     for (n=1; n < maxlen; n++) {
	  if ( (rc = readrecvbuf(fd, &c, 1)) == 1) {
	       *ptr++ = c;
	       if (c == '\n')
		    break;
	  }
	  else if (rc == 0) {
	       if (n == 1)
		    return(0);	/* EOF, no data read */
	       else
		    break;		/* EOF, some data was read */
	  }
	  else
	       return(-1);		/* error */
     }
     
     *ptr = 0; 				/* Tack a NULL on the end */
     return(n);
}

/*
 * Readfield reads data up to a tab, (like readline above)
 */

int 
readfield(fd, ptr, maxlen)
  int	fd;
  char	*ptr;
  int 	maxlen;
{
     int n;
     int rc;
     char c;
     
     for (n=1; n < maxlen; n++) {
	  if ( (rc = readrecvbuf(fd, &c, 1)) == 1) {
	       *ptr++ = c;
	       if (c == '\t') {
		    *(ptr - 1) = '\0';
		    break;
	       }
	  }
	  else if (rc == 0) {
	       if (n == 1)
		    return(0);	/* EOF, no data read */
	       else
		    break;		/* EOF, some data was read */
	  }
	  else
	       return(-1);		/* error */
     }
     
     *ptr = 0; 				/* Tack a NULL on the end */
     return(n);
}


int
sreadword(input, output, maxlen)
  char *input;
  char *output;
  int maxlen;
{
     int n;
     char c;
     
     for (n=0; n < maxlen; n++) {
	  c = *input++;
	  *output++ = c;
	  if (isspace(c)) {
	       *(output - 1) = '\0';
	       break;
	  }
	  
	  if (c == '\0') {
	       break;
	  }
     }
     
     *output = '\0'; 				/* Tack a NULL on the end */
     return(n);
}


/*
 * ZapCRLF removes all carriage returns and linefeeds from a C-string.
 */

void
ZapCRLF(inputline)
  char *inputline;
{
     char *cp;

     cp = strchr(inputline, '\r');    /* Zap CR-LF */
     if (cp != NULL)
	  *cp = '\0';
     else {
	  cp = strchr(inputline, '\n');
	  if (cp != NULL)
	       *cp = '\0';
     }
}

/*
 *  Utilities for dealing with HTML junk
 */

static boolean acceptable[256];
static boolean acceptable_inited = FALSE;

void init_acceptable()
{
    unsigned int i;
    char * good = 
      "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./-_$";
    for(i=0; i<256; i++) acceptable[i] = FALSE;
    for(;*good; good++) acceptable[(unsigned int)*good] = TRUE;
    acceptable_inited = TRUE;
}

static char hex[17] = "0123456789abcdef";

char from_hex(c)
  char c;
{
     return (c>='0')&&(c<='9') ? c-'0'
	  : (c>='A')&&(c<='F') ? c-'A'+10
	       : (c>='a')&&(c<='f') ? c-'a'+10
		    :                      0;
}


/*
 * Finds out if a character is printable & non white space.
 * if it is, then return a hex encoding of the char,
 * otherwise, just return a reference to the character
 */

char *to_hex(c)
  char c;
{
     static char out[4];

     if (acceptable_inited == FALSE)
	  init_acceptable();

     out[0] = '\0';

     if (acceptable[(int)c] == TRUE) {
	  out[0] = c;
	  out[1] = '\0';
     }
     else {
	  out[0]='%';
	  out[1]=hex[c >> 4];
	  out[2]=hex[c & 15];
	  out[3]='\0';
     }

     return(out);
}
     
/*
 * Replace hex escape sequences with the proper codes...
 *
 * input and output can be the same if you want
 */

void
Fromhexstr(input, output)
  char *input, *output;
{
     char c;
     unsigned int b;

     while (*input) {
	  if (*input == '%') {
	       input++;
	       c = *input++;
	       b = from_hex(c);
	       c = *input++;
	       if (!c) break;
	       *output++ = (b<<4) + from_hex(c);
	  }
	  else 
	       *output++ = *input++;
     }
     *output = '\0';
}
	  
void
Tohexstr(input, output)
  char *input, *output;
{

     if (acceptable_inited == FALSE)
	  init_acceptable();
     
     while (*input) {

	  if (acceptable[(int)*input] == TRUE) {
	       *output++ = *input++;
	  }
	  else {
	       *output++ = '%';
	       *output++ = hex[*input >> 4];
	       *output++ = hex[*input & 15];
	       input++;
	  }
     }

     *output = '\0';
}

/*
 * String insensitive strstr
 */

char *
strcasestr(inputline, match)
  char *inputline;
  char *match;
{
     int matchlen=0;
     int i, inlen;

     matchlen = strlen(match);
     inlen = strlen(inputline);

     for(i=0; i<inlen; i++) {
	  if (strncasecmp(inputline+i, match, matchlen)==0)
	       return(inputline+i);
     }

     return(NULL);
}


/*
 * Iterate over a string, return a pointer to the next character
 * that isn't whitespace.
 */

char *
skip_whitespace(str)
  char *str;
{
     while (isspace(*str) && *str!='\0')
	  str++;

     return(str);
}
