/********************************************************************
* wilkinson
* 3.33VMS
* 1995/05/26  10:45
* gopher_root1:[gopher.g2.vms2_13.object]GDgopherdir.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: GDgopherdir.c
* Implement gopher directory routines
*********************************************************************
* Revision History:
* GDgopherdir.c,v
* Revision 3.33VMS 1995/05/26  10:45   wilkinson
* Consolodate VMS/Unix source code for server as well as client
*
* Revision 3.33  1995/02/17  18:34:09  lindner
* Fix stupid bug
*
* Revision 3.32  1995/02/16  22:32:43  lindner
* HTML icon support
*
* Revision 3.31  1995/02/13  19:09:25  lindner
* Fix for link updating
*
* Revision 3.30  1995/02/06  22:11:26  lindner
* Fix for GDsearch comparision
*
* Revision 3.29  1995/02/01  22:06:22  lindner
* Put back GDaddGSmerge, ugh.
*
* Revision 3.27  1994/10/18  21:36:34  lindner
* Remove unused variable
*
* Revision 3.26  1994/07/21  22:29:00  lindner
* misc hacks
*
* Revision 3.25  1994/07/06  03:01:30  lindner
* VMS doesn't have strcoll
*
* Revision 3.24  1994/06/29  06:47:51  lindner
* Use strcoll to sort if GINTERNATIONAL is defined
*
* Revision 3.23  1994/06/29  05:45:54  lindner
* Mods to pump tickets to the net
*
* Revision 3.22  1994/04/27  19:21:37  lindner
* Fix for semicolons after Debugmsg
*
* Revision 3.21  1994/04/25  03:36:56  lindner
* Modifications for Debug() and mismatched NULL arguments, added Debugmsg
*
* Revision 3.20  1994/04/21  21:24:57  lindner
* Fix fcn header
*
* Revision 3.19  1994/04/19  14:32:27  lindner
* Change GD and GSfromLink routines to use FIO
*
* Revision 3.18  1994/04/13  04:28:58  lindner
* Fix for Type=X items
*
* Revision 3.17  1994/02/20  16:28:19  lindner
* Remove dead code for GDtoNetHTML
*
* Revision 3.16  1994/01/10  03:27:28  lindner
* Better GDdeleteGS method, allow ignoring of items by doing a Type=X in a .names file
*
* Revision 3.15  1993/11/29  01:07:57  lindner
* In GDtoLink(), add FALSE argument to GStoLink() call in order to prevent
* the Admin and ModDate information from being saved in the user bookmark
* file.  (Macrides)
*
* Revision 3.14  1993/11/02  06:15:15  lindner
* HTML additions
*
* Revision 3.13  1993/08/23  20:56:34  lindner
* Fix for empty directory in g+ client
*
* Revision 3.12  1993/08/19  20:51:33  lindner
* Mitra comments
*
* Revision 3.11  1993/08/19  20:24:04  lindner
* Mitra's Debug patch
*
* Revision 3.10  1993/07/29  20:02:16  lindner
* Removed dead variables
*
* Revision 3.9  1993/07/27  05:30:22  lindner
* Mondo Debug overhaul from Mitra
*
* Revision 3.8  1993/07/27  00:30:08  lindner
* plus patch from Mitra
*
* Revision 3.7  1993/07/14  20:37:08  lindner
* Negative numbering patches
*
* Revision 3.6  1993/06/22  06:07:17  lindner
* Added Domain= hacks..
*
* Revision 3.5  1993/04/15  21:35:12  lindner
* Debug code, better .Link processing, better GDfromNet()
*
* Revision 3.4  1993/03/26  19:50:44  lindner
* Mitra fixes for better/clearer fromNet code
*
* Revision 3.3  1993/03/24  17:04:49  lindner
* bad strcmp() can check unmalloced() mem, fixed
*
* Revision 3.2  1993/03/18  22:13:29  lindner
* filtering, compression fixes
*
* Revision 3.1  1993/02/11  18:03:01  lindner
* Initial revision
*
* Revision 2.1  1993/02/09  22:46:50  lindner
* Many additions for gopher+
*
* Revision 1.4  1993/01/31  00:22:51  lindner
* Changed GDaddGS to merge entries with the same path.
* Added GDplusfromNet() to siphon data from network.
* GDfromLink now knows about ~/ inside of Path=
* Changed GDSearch to ignore leading character.
*
* Revision 1.3  1992/12/19  04:44:09  lindner
* Added GDplustoNet()
*
* Revision 1.2  1992/12/16  20:37:04  lindner
* Added function GDsearch(), does a linear search of a gopher directory
*
* Revision 1.1  1992/12/10  23:27:52  lindner
* gopher 1.1 release
*
*
*********************************************************************/


#ifdef VMS_SERVER
#define  GSGOPHEROBJ_C
#include "GopherD.h"
void AddDefaultView(/* GopherObj *, int, char * */);
#endif

#include "GDgopherdir.h"
#include "Malloc.h"
#include "util.h"

#include "String.h"
#include <stdio.h>
#include "fileio.h"
#include "Debug.h"
#include "fileio.h"

/***********************************************************************
** Stuff for GopherDirObjs
**
***********************************************************************/


GopherDirObj*
GDnew(size)
 int size;
{
    GopherDirObj *temp;

    temp = (GopherDirObj*) malloc(sizeof(GopherDirObj));

    temp->Gophers = DAnew(size, GSnew, GSinit, GSdestroy, GScpy);

    temp->Title = STRnew();
    temp->Location = NULL;
    temp->currentitem = 1;

    GDinit(temp);
    return(temp);
}


void
GDdestroy(gd)
 GopherDirObj *gd;
{
    DAdestroy(gd->Gophers);
    if (gd->Location != NULL)
         GSdestroy(gd->Location);

    STRdestroy(gd->Title);
    free(gd);
}


void
GDinit(gd)
 GopherDirObj *gd;
{
    DAinit(gd->Gophers);
    STRinit(gd->Title);
    gd->Location = NULL;
}


void
GDsetLocation(gd, gs)
 GopherDirObj *gd;
 GopherObj *gs;
{
    if (gd->Location == NULL)
         gd->Location = GSnew();
    else
         GSinit(gd->Location);

    GScpy(gd->Location, gs);
}



/** This proc adds a GopherObj to a gopherdir.
   It will attempt to merge two items if need be..
**/
void
GDaddGSmerge(gd, gs)
 GopherDirObj *gd;
 GopherObj *gs;
{
    int num;

    num = GDSearch(gd, GSgetPath(gs));

    if (num == -1)
         GDaddGS(gd, gs);
    else {
         if (GSgetType(gs) == 'X') {
              gd = GDdeleteGS(gd, num);
         } else
              GSmerge(GDgetEntry(gd, num),gs);
    }
}

/*
* This one never tries to merge
*/

void
GDaddGS(gd, gs)
 GopherDirObj *gd;
 GopherObj *gs;
{

#ifdef VMS_SERVER
       if (GSgetNum(gs) == -99) {
           return;     /** Historical Hidden item for VMS Server **/
       }
#endif
       if (GSgetType(gs) != 'X')
            DApush(gd->Gophers, gs);
}


/*
* Really weird!!!  We need this for qsort,  don't know why we can't use
* GScmp...
*/

#define sgn(a)  ((a) == 0 ? 0 : (a) < 0 ? -1 : 1)

int
GSqsortcmp(gs1, gs2)
 GopherObj **gs1, **gs2;
{
    if (GSgetTitle(*gs1) == NULL)
         return(1);
    if (GSgetTitle(*gs2) == NULL)
         return(-1);

    /** No numbering set on either entry, or both numbered
        entries have the same number   **/

    if (GSgetNum(*gs1) == GSgetNum(*gs2))
#ifdef NeXT                     /* Next has a goofy strcoll */
#define strcoll(a,b) strcmp(a,b)
#endif
         return(strcoll(GSgetTitle(*gs1), GSgetTitle(*gs2)));
#ifdef NeXT
#undef strcoll
#endif
    /** If the signs are equal, compare the numbers conventionally **/

    /** N.B. If the signs ARE equal, they cannot be 0 (otherwise we would **/
    /** have had the above case, because only the sign of 0 is 0)  */
    if (sgn(GSgetNum(*gs1)) == sgn(GSgetNum(*gs2)))
         return(GSgetNum(*gs1) < GSgetNum(*gs2) ? -1 : 1);

    /** The signs must be different, so we can use a conventional test, **/
    /** remembering only to say positive numbers go before negative ones **/
    return(GSgetNum(*gs1) > GSgetNum(*gs2) ? -1 : 1);
}

/*
* Sorts a gopher directory
*/

void
GDsort(gd)
 GopherDirObj *gd;
{

    DAsort(gd->Gophers, GSqsortcmp);
}


void
GDtoNet(gd, sockfd, fmt, ticket, prefcn)
 GopherDirObj *gd;
 int           sockfd;
 GSformat      fmt;
 char         *ticket;
 int         (*prefcn)();

{
    int i;
    GopherObj *gs;
    Debugmsg("GDplustoNet\n");

    if (fmt == GSFORM_HTML) {
         writestring(sockfd, "<DL COMPACT>\r\n");
    }

    for (i=0; i< GDgetNumitems(gd); i++) {
         gs = GDgetEntry(gd, i);
         if (fmt == GSFORM_HTML)
              writestring(sockfd, "<DT>");

         if (prefcn)
              prefcn(gs, sockfd);
         GStoNet(GDgetEntry(gd, i), sockfd, fmt, ticket);
    }

    if (fmt == GSFORM_HTML) {
         writestring(sockfd, "</DL>\r\n");
    }
}


void
GDplustoNet(gd, sockfd, filter, ticket)
 GopherDirObj *gd;
 int           sockfd;
 char        **filter;
 char         *ticket;
{
    int i;

    for (i=0; i< GDgetNumitems(gd); i++) {
         GSplustoNet(GDgetEntry(gd, i), sockfd,filter, ticket);
    }
}

#ifdef VMS_SERVER

void
GDtoNetHTML(gd, sockfd)
 GopherDirObj *gd;
 int sockfd;
{
    int i;

    writestring(sockfd, "<MENU>\r\n");

    for (i=0; i< GDgetNumitems(gd); i++) {
         writestring(sockfd, "<LI>");
         GStoNetHTML(GDgetEntry(gd, i), sockfd);
    }
    writestring(sockfd, "</MENU>");
}
#endif

/*
* Gopher+ counterpart to GDfromNet()
* returns number of items found
*/


int
GDplusfromNet(gd, fd, eachitem)
 GopherDirObj *gd;
 int fd;
 int (*eachitem)();
{
    static GopherObj *TempGopher = NULL;
    int j, result;
    char inputline[256];

    Debugmsg("GDplusfromNet:: start\r\n");
    if (TempGopher == NULL)
         TempGopher = GSnew();

    /** State: _begin_ **/

    result = readrecvbuf(fd, inputline, 1);
    if (result <=0)
         return(0);
    else if (*inputline == '.') {
         /*** Read off the rest of the junk... ***/
         readline(fd,inputline,sizeof(inputline));
         return(0);
    }
    else if (*inputline != '+')
         return(0);

    Debugmsg("after readrecvbuf");
    /** State _FirstPlus_ **/

    result = readtoken(fd, inputline, sizeof(inputline), ':');
    if (result <=0)
         return(result);

    Debugmsg("after readtoken");
    if (strcmp(inputline, "INFO")!=0) {
         return(0);
    }
    Debugmsg("after INFO");
    /** Read the space **/
    readrecvbuf(fd, inputline, 1);


    /*** State _FirstINFO_ ***/

    for (j=0; ; j++) {

         Debugmsg("for start");

         GSinit(TempGopher);
         result = GSplusfromNet(TempGopher, fd);

         switch (result) {
         case MORECOMING:
              GDaddGS(gd, TempGopher);
              if (eachitem != NULL)
                   eachitem();
              break;

         case FOUNDEOF:
              GDaddGS(gd, TempGopher);
              return(j+1);

         case HARDERROR:  /** Give up reading - bad read or protocol error **/
              return(j);

         case SOFTERROR:  /** This one was bad, but we can try for next **/
              j= j-1;
              if (j<0) j=0;
              break;
         }

    } /* for */

    /** Never get here **/
}

/*
* Fill up a GopherDirObj with GopherObjs, given a gopher directory coming
* from sockfd.
*
* For each GopherObj retrieved, eachitem() is executed.
*
*/

void
GDfromNet(gd, sockfd, eachitem)
 GopherDirObj *gd;
 int sockfd;
 int (*eachitem)();
{
    static GopherObj *TempGopher;
    int i;
        char *cp1, *cp2;

    Debugmsg("GDfromNet...");
    if (TempGopher == NULL)
         TempGopher = GSnew();

    for (; ;) {

         GSinit(TempGopher);
         i = GSfromNet(TempGopher, sockfd);

        /* In gopher+1.2b2 this routine clears up if GSfromNet returns
           a failure, better to clear up in GSfromNet so that the
           system returns in a known state - note that other callers of
           GSfromNet didn't clean up and crashed! */

         switch (i) {

         case 0:
                  if (GSgetType(TempGopher) == '3' &&
                          ((cp1 = GSgetTitle(TempGopher)) != NULL) &&
                          ((cp2 = strchr(cp1, '\n')) != NULL)) {
                          GopherObj *TempG2;
                          do {
                                  TempG2 = GSnew();
                                  GScpy(TempG2, TempGopher);
                                  GSsetType(TempG2, A_INFO);
                                  GSsetTitle(TempG2, cp2+1);
                                  *cp2 = '\0';
                                  GSsetTitle(TempGopher, cp1);
                                  GDaddGS(gd, TempGopher);
                                  TempGopher = TempG2;
                                  if ((cp1 = GSgetTitle(TempGopher)) == NULL)
                                          break;
                          } while ((cp2 = strchr(cp1, '\n')) != NULL);
                  }
              GDaddGS(gd, TempGopher);
              if (eachitem != NULL) eachitem();
              break;

         case 1:  /* . on a line by itself, nothing more */
              return;

         case SOFTERROR:  /** Unknown object type **/
              break;

         case HARDERROR:
              return;
         }
    }
}


/*
* Given an open file descriptor and an inited GopherDirobj,
*   read in gopher links, and add them to a gopherdir
*/

#ifdef VMS_SERVER
void
GDfromLink(gd, fio, host, port, directory, peer, sockfd, access)
 int          sockfd;
 int          access;
#else
void
GDfromLink(gd, fio, host, port, directory, peer)
#endif
 GopherDirObj *gd;
 FileIO       *fio;
 char         *host;       /** Current Host **/
 int          port;        /** Current port **/
 char         *directory;  /** Current directory **/
 char         *peer;       /** connected client **/
{
    GopherObj *gs;
    int       result;
    char      *cp;

    gs = GSnew();

    while (1) {
         GSinit(gs);

         result = GSfromLink(gs, fio, host, port,directory, peer);

         if (result == HARDERROR)
              break;

         if (result == SOFTERROR)
              continue;

#ifdef VMS_SERVER
       if (GScanAccess(sockfd, gs, access)==SITE_NOACCESS)
           continue;
#endif
         cp = GSgetPath(gs);

         if (*cp == '.')
              GDaddGSmerge(gd, gs);
         else
              GDaddGS(gd, gs);

         if (result == FOUNDEOF)
              break;

    }


    GSdestroy(gs);
}


void
GDtoLink(gd, fd)
 GopherDirObj *gd;
 int fd;
{
    int i;

    for (i=0; i< GDgetNumitems(gd); i++) {
         GStoLink(GDgetEntry(gd, i), fd, FALSE);
    }

}

/***  Search for a specific gopher item ***/
int
GDSearch(gd, text)
 GopherDirObj *gd;
 char         *text;   /* Note first char is G0 type and is ignored*/
{
    int       i;
    GopherObj *gs;
    int       cplen;
    char      *cp;

#ifndef VMS_SERVER
    Debug("GDSearch: %s;\n",text);
#else
    Debug("GDSearch: %s\n",text);  /* Damn trailing ; confuses VMS programmers */
#endif

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

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

    if ((int) strlen(text) <= 1)
         return(-1);

    for (i=0; i< GDgetNumitems(gd); i++) {

         gs = GDgetEntry(gd, i);
         cp = GSgetPath(gs);
         if (cp != NULL) {
              cplen = strlen(cp);

#ifndef VMS_SERVER
              if (cplen >1 && strcmp(text+1, cp+1) == 0)
#else
              if (cplen >1 && strcasecmp(text+1, cp+1) == 0)
#endif
              {
                   Debugmsg("Matched\n");
                   return(i);
              }
         }
    }
    Debugmsg("GDsearch: No Match\n");
    return(-1);
}

/*
* Delete an item in a gopher GD..  Do it in place...
*/

GopherDirObj *
GDdeleteGS(gd,j)
 GopherDirObj  *gd;
 int           j;      /* Number of GS to delete */
{
    int i;

    if (GDgetNumitems(gd) == j+1) {
         /* Last item in the directory */
         GSinit(GDgetEntry(gd,j));
         GDsetNumitems(gd, j);
         return(gd);
    }

    /** Okay, now let's copy the items down, one by one..  **/

    for (i= j+1; i<GDgetNumitems(gd); i++) {
         GScpy(GDgetEntry(gd,i-1), GDgetEntry(gd, i));
    }

    GDsetNumitems(gd, GDgetNumitems(gd)-1);

    if (GDgetCurrentItem(gd) > GDgetNumitems(gd)-1)
         GDsetCurrentItem(gd, GDgetCurrentItem(gd)-1);

    return(gd);
}