/********************************************************************
* wilkinson
* 3.20VMS
* 1995/09/25 11:25
* gopher_root1:[gopher.g2.vms2_13.object]util.c,v
* Exp
*
* 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:
* util.c,v
* Revision 3.20VMS 1995/09/25 11:25    wilkinson
* Use __VMS instead of VMS as trigger for VMS items
*
* Revision 3.20  1995/02/02  17:14:47  lindner
* Fix for memory leaks and accesses
*
* Revision 3.19  1994/10/19  03:34:39  lindner
* Dynamically allocate acceptable array
*
* Revision 3.18  1994/08/19  16:12:11  lindner
* Sanity check for Tohexstr
*
* Revision 3.17  1994/07/21  22:07:52  lindner
* none
*
* Revision 3.16  1994/04/25  03:37:01  lindner
* Modifications for Debug() and mismatched NULL arguments, added Debugmsg
*
* Revision 3.15  1994/03/31  21:04:36  lindner
* Mitra's 2.011 debug patch
*
* Revision 3.14  1994/03/08  15:56:22  lindner
* gcc -Wall fixes
*
* Revision 3.13  1994/02/20  16:25:58  lindner
* Optimize readline and friends to use fileio routines
*
* Revision 3.12  1993/12/27  16:14:54  lindner
* prettify debug output
*
* Revision 3.11  1993/09/21  01:52:54  lindner
* Fixes for NETLIB
*
* Revision 3.10  1993/08/16  19:41:23  lindner
* Fix for DECC/Alpha
*
* Revision 3.9  1993/08/09  20:17:13  lindner
* Fixes for CMULIB and NETLIB for VMS
*
* Revision 3.8  1993/08/05  03:23:40  lindner
* Changes for CMUIP and NETLIB
*
* Revision 3.7  1993/07/27  05:30:30  lindner
* Mondo Debug overhaul from Mitra
*
* Revision 3.6  1993/07/23  04:49:24  lindner
* none
*
* Revision 3.5  1993/06/22  05:49:39  lindner
* *** empty log message ***
*
* Revision 3.4  1993/04/23  20:11:56  lindner
* Fixed misdeclaration of DEBUG
*
* Revision 3.3  1993/04/15  17:53:12  lindner
* Debug stuff from Mitra
*
* Revision 3.2  1993/03/18  22:28:27  lindner
* changed hex routines around for admit1
*
* Revision 3.1.1.1  1993/02/11  18:03:05  lindner
* Gopher+1.2beta release
*
* Revision 2.5  1993/01/31  00:31:12  lindner
* New functions, readword() and readtotoken()
*
* 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 "String.h"
#include <ctype.h>
#include "boolean.h"
#include "util.h"
#include <stdio.h>
#include "Debug.h"
#include "fileio.h"


#if defined(__VMS) && (defined(UCX) || defined(CMUIP) || defined(NETLIB))
#include <errno.h>
#endif


static FileIO *Gfio = NULL;
static int Oldsockfd = -1;



#if defined(__VMS) && (defined(WOLLONGONG) || defined(MULTINET) ||defined(CMUIP)||defined(NETLIB))
/* Multinet and Wollongong,etc. (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;
{
#ifdef DEBUGGING
#ifndef __VMS
    if (s == stderr) {
         fprintf(stderr, "YUK - closing stderr");
    }
#else
#ifdef VMS_SERVER
#ifdef fprintf
#define hold_fprintf fprintf
#undef fprintf
#endif
#define fprintf VMS$fprintf
#endif
    if (s == (int)stderr) {
         fprintf(stderr, "YUK - closing stderr");
    }
#ifdef VMS_SERVER
#ifdef hold_fprintf
#define fprintf hold_fprintf
#undef hold_fprintf
#endif
#endif
#endif
#endif

   if (IS_SOCKET(s)) {
#ifdef MULTINET
       return (socket_close(s));
#else /* WOLLONGONG, CMUIP, NETLIB */
       return (netclose(s));
#endif
   }
   else
       close(s); /* shouldn't be calling this routine */
}
#else /* WOLLANGONG or MULTINET */
#define IS_SOCKET(a) 1
#endif

static void CheckGfio(fd)
 int fd;
{

    if (Gfio == NULL || Oldsockfd != fd) {
         Oldsockfd = fd;
         if (Gfio != NULL)
              FIOdestroy(Gfio);
         Gfio = FIOopenfd(fd, IS_SOCKET(fd));
    }
}




/* 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) || defined(CMUIP) || defined(NETLIB))
         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) || defined(CMUIP) || defined(NETLIB))
         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) || defined(CMUIP) || defined(NETLIB))
         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;

    Debug("writing: %s\n",stringptr);

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

    length = strlen(stringptr);
    if (writen(fd, stringptr, length) != length) {
         Debugmsg("writestring: writen failed\n");
         return(-1);
    }
    else
         return(0);
}

/*
* Read from the socket into a buffer.  Mucho more efficent in terms of
* system calls..
*
* returns bytes read, or <0 for an error
*/

int readrecvbuf(sockfd, buf, len)
 int sockfd;
 char *buf;
 int len;
{
    int bytesread;

    CheckGfio(sockfd);
    if (Gfio == NULL)
         return(-1);

    bytesread = FIOreadbuf(Gfio, buf, len);

    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;

    CheckGfio(fd);

    if (Gfio == NULL)
         return(-1);

    n = FIOreadline(Gfio, ptr, maxlen);
    Debug("readline: %s\n", ptr);

    return(n);
}


/*
* Read a line from the file/socket, Read the line one byte at a time,
* looking for the token.  We nuke the token from the returned string.
* We return the number of characters up to, but not including,
* the null (the same as strlen(3))
*/

int
readtoken(fd, ptr, maxlen, zechar)
 int   fd;
 char  *ptr;
 int   maxlen;
 char  zechar;
{
    int bytesread;

    CheckGfio(fd);
    if (Gfio == NULL)
         return(-1);

    bytesread = FIOreadtoken(Gfio, ptr, maxlen, zechar);

    Debug("readtoken: %s\n", (ptr-bytesread));
    return(bytesread);
}


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 the end of
* a C-string.
*/

void
ZapCRLF(inputline)
 char *inputline;
{
    int len;

    len = strlen(inputline);

    if (len == 0)
         return;
    inputline += len;
    inputline--;

    if (*inputline == '\n' || *inputline == '\r') {
         *inputline = '\0';
         if (len > 1) {
              inputline--;
              if (*inputline == '\n' || *inputline == '\r')
                   *inputline = '\0';
         }
    }
}

/*
*  Utilities for dealing with HTML junk
*/

static boolean *acceptable;
static boolean acceptable_inited = FALSE;

void init_acceptable()
{
   unsigned int i;
   char * good =
     "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./-_$";

   acceptable = (boolean*) malloc(sizeof(boolean) * 256);

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


/*
* return a hex encoding of the char,
*/

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

    out[0] = '\0';

    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 ( (input == NULL) || (output == NULL) )
         return;

    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';
}

/*
* This fcn hexifies everything
*/

void
Hexall(input, output)
 char *input, *output;
{
    while (*input) {

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



#if defined(__VMS) && defined(NETLIB)

/*
* netclose, netread, and netwrite for NETLIB
*/

#include <iodef.h>
#include <ssdef.h>

static struct {
    short status;
    short size;
    long xxx;
} netlib_iosb;

void netlib_ast (param)
int param;
{
    SYS$SETEF (0);
}

int netclose (channel)
int channel;
{
    NET_DEASSIGN (&channel);

    return (0);
}

int netread (channel, buffer, length)
int channel;
char *buffer;
int length;
{
    struct {
         long len;
         char *adr;
    } buffer_desc;
    int status;

    if (length > 1500) length = 1500;
    buffer_desc.len = length;
    buffer_desc.adr = buffer;
    SYS$CLREF (0);
    status = TCP_RECEIVE (&channel, &buffer_desc, &netlib_iosb, netlib_ast, 0);
    if ((status & 1) == 0)
         return (-1);
    SYS$WAITFR (0);
    if ((netlib_iosb.status & 1) == 0) {
         if ((netlib_iosb.status == SS$_ABORT &&
              netlib_iosb.xxx == 0x086380da) /* CMUIP Connection Closing */ ||
             netlib_iosb.status == SS$_LINKDISCON) /* UCX link disconnecting */
              errno = EPIPE;
         return (-1);
    }
    return (netlib_iosb.size);
}

int netwrite (channel, buffer, length)
int channel;
char *buffer;
int length;
{
    struct {
         long len;
         char *adr;
    } buffer_desc;
    int status;

    buffer_desc.len = length;
    buffer_desc.adr = buffer;
    SYS$CLREF (0);
    status = TCP_SEND (&channel, &buffer_desc, 6, &netlib_iosb, netlib_ast, 0);
    if ((status & 1) == 0)
         return (-1);
    SYS$WAITFR (0);
    if ((netlib_iosb.status & 1) == 0) {
         return (-1);
    }
    return (netlib_iosb.size);
}

#endif  /* NETLIB */

#if defined(__VMS) && defined(CMUIP)

/*
* netclose, netread, and netwrite for CMUIP
*/

#include <iodef.h>
#include <ssdef.h>

static struct {
    short status;
    short size;
    long xxx;
} cmu_iosb;
globalvalue NET$_CC; /* Connection Closing */

int netclose (channel)
 int channel;
{
    int status;

    status = SYS$QIOW (0, channel, IO$_DELETE, &cmu_iosb, 0, 0,
                       0, 0, 0, 0, 0, 0);
    status = SYS$DASSGN (channel);

    return (0);
}

int netread (channel, buffer, length)
 int channel;
 char *buffer;
 int length;
{
    int status;

    if (length > 1500) length = 1500;
    status = SYS$QIOW (0, channel, IO$_READVBLK, &cmu_iosb, 0, 0,
                       buffer, length, 0, 0, 0, 0);
    if ((status & 1) == 0)
         return (-1);
    if ((cmu_iosb.status & 1) == 0) {
         if (cmu_iosb.status == SS$_ABORT && cmu_iosb.xxx == NET$_CC)
              errno = EPIPE;
         return (-1);
    }
    return (cmu_iosb.size);
}

int netwrite (channel, buffer, length)
 int channel;
 char *buffer;
 int length;
{
    int status;

    status = SYS$QIOW (0, channel, IO$_WRITEVBLK, &cmu_iosb, 0, 0,
                       buffer, length, 0, 1, 0, 0);
    if ((status & 1) == 0)
         return (-1);
    if ((cmu_iosb.status & 1) == 0)
         return (-1);
    return (cmu_iosb.size);
}

#endif  /* CMUIP */



/*
*  Checks to see if Remote server is up..
*/

boolean
RemoteIsUp(host, port)
 char *host;
 int  port;
{
    ;
    return(TRUE);
}