/* util.c
   Utility routines to maintain and process gopher data structures
   and events. */

     /*---------------------------------------------------------------*/
     /* Xgopher        version 1.3     08 April 1993                  */
     /*                version 1.2     20 November 1992               */
     /*                version 1.1     20 April 1992                  */
     /*                version 1.0     04 March 1992                  */
     /* X window system client for the University of Minnesota        */
     /*                                Internet Gopher System.        */
     /* Allan Tuchman, University of Illinois at Urbana-Champaign     */
     /*                Computing and Communications Services Office   */
     /* Copyright 1992, 1993 by                                       */
     /*           the Board of Trustees of the University of Illinois */
     /* Permission is granted to freely copy and redistribute this    */
     /* software with the copyright notice intact.                    */
     /*---------------------------------------------------------------*/

/* ***** really, this file needs to divide into 3:
		itemPublic.c itemUtil.c itemClass.c
	 there are some global variables to consider, though.
   ***** */

#include <stdio.h>

#include "osdep.h"
#include "conf.h"
#include "gopher.h"
#include "globals.h"
#include "gui.h"
#include "item.h"
#include "itemList.h"
#include "dir.h"
#include "dirList.h"
#include "util.h"
#include "status.h"
#include "misc.h"
#include "net.h"
#include "appres.h"
#include "jobs.h"


#include <ctype.h>
#include <errno.h>

#include "sc_text.h"
#include "sc_dir.h"
#include "sc_cso.h"
#include "sc_index.h"
#include "sc_telnet.h"
#include "sc_tn3270.h"
#include "sc_binary.h"
#include "sc_image.h"
#include "sc_sound.h"
#include "sc_extend.h"

char	prefixUnknown [ PREFIX_LEN ];

scInfo  noSubclass = {
                "unknown gopher item type",
                prefixUnknown,
                &nullHostList,
                GI_access,
		GI_copyToFile,
		copyTypeNone,
		GI_process,
		GI_init,
		GI_done,
		GI_restart
        };

#define UNKNOWN_TYPE(gi)	((gi)->sc == &noSubclass)



typedef struct giSubclassStruct {
        char    typeID;
	scInfo	*subclass;
	struct giSubclassStruct *next;
   } giSubclass;

/* the order of the following subclasses is important.  All directory
   types must come first, then the extended types (so they can 
   override internal ones), then all other built-in types. */

static giSubclass	definedSubclass[] = {
        {A_DIRECTORY,		&dirSubclass,	NULL},
        {A_INDEX,		&indexSubclass,	NULL},
        {A_EXTENDED,		&extendSubclass,NULL},
        {A_FILE,		&textSubclass,	NULL},
        {A_CSO,			&csoSubclass,	NULL},
        {A_BINARY,		&binarySubclass,NULL},
        {A_IMAGE,		&imageSubclass,	NULL},
        {A_TELNET,		&telnetSubclass,NULL},
        {A_TN3270,		&tn3270Subclass,NULL},
        {A_SOUND,		&soundSubclass,	NULL},
		/* '?' type will be Unknown */
        {'?',			&noSubclass,	NULL},
        {'\0',			&noSubclass,	NULL},
   };

        /* the following can be implemented as extended types: 

		A_MAC_BINHEX, (mac binhex),
		A_DOS_BINHEX, (dos binhex),
		A_UNIX_UUENCODE, (uuencoded file)
	*/

static	giSubclass	*activeSubclass = NULL;


/* ====================================================================== */
/*                                                                        */
/* publicly callable procedures                                           */
/*                                                                        */
/* ====================================================================== */

/* makeItem
   Given the contents, allocate and build a new gopher item */

gopherItemP
makeItem(t, titleString, selectString, host, port, plus)
char	t;
char	*titleString;
char	*selectString;
char	*host;
int	port;
BOOLEAN	plus;
{
	gopherItemP	gi = newItem();
	giSubclass 	*scID;

	gi->type = t;
	strncpy(USER_STRING(gi), titleString, USER_STRING_LEN);
	vStringSet(&(gi->selector), selectString);
	strncpy(gi->host, host, HOST_STRING_LEN);
	gi->port = port;
	gi->plus = FALSE;

	/* determine subclass and set fields determined by those class
	   variables and class methods. */

	for (scID = activeSubclass;
		 scID->typeID != t && scID->typeID != A_UNKNOWN;
		 scID=scID->next) ;
	gi->sc = scID->subclass;

	PREFIX(gi, gi->sc->typePrefix);

	gi->accessOk = gi->sc->checkAccess(gi);

	return gi;
}


/* initItemClasses
   initialize each item class - gather its resources, restrictions,
   and if appropriate, create its user interface resources. */

void
initItemClasses()
{
	giSubclass	*scID;

	for (scID = definedSubclass; scID->typeID != '\0'; scID++) {
		scID->subclass->initProc();
	}

#ifdef DEBUG
	fprintf (stderr, "Known item types are:\n");
	for (scID = activeSubclass; scID != NULL; scID=scID->next) {
	    fprintf (stderr, "\t%c, %.5s, %s\n",
		    scID->typeID, scID->subclass->typePrefix,
		    scID->subclass->typeName);
	}
#endif	/* DEBUG */

	return;
}


/* doneItemClasses
   terminate each item class - free its resources and user interface
   resources as necessary. */

void
doneItemClasses()
{
	giSubclass *scID;

	for (scID = activeSubclass; scID != NULL; scID=scID->next) {
		scID->subclass->doneProc();
	}

	killAllItemProcesses();

	return;
}


/* restartItemClasses
   restart each item class - re-initialize its resources and user
   interface components as necessar. */

void
restartItemClasses()
{
	giSubclass *scID;

	for (scID = activeSubclass; scID != NULL; scID=scID->next) {
		scID->subclass->restartProc();
	}

	return;
}


/* processItem
   check accessability, then process the selected item */

BOOLEAN
processItem(gi)
gopherItemP     gi;
{
	BOOLEAN     result=FALSE;

	if (! gi->accessOk) {
		char message[MESSAGE_STRING_LEN];

		sprintf(message,
			"Sorry, %s access is restricted in this session.",
			gi->sc->typeName);
		showError(message);
		result = FALSE;
	} else {
		result = gi->sc->processItem(gi);;
	}

	return result;
}


/* copyItemToFile
   check accessability, then copy the selected item to a file */

BOOLEAN
copyItemToFile(gi)
gopherItemP     gi;
{
	BOOLEAN     result=FALSE;

	if (! gi->accessOk) {
		char message[MESSAGE_STRING_LEN];

		sprintf(message,
			"Sorry, %s access is restricted in this session.",
			gi->sc->typeName);
		showError(message);
		result = FALSE;
	} else {
		result = gi->sc->copyProc(gi);;
	}

	return result;
}


/* updateDirectory
   reload an existing gopher directory (possibly an index search result) */

void
updateDirectory(d)
gopherDirP	d;
{
	int		s;
	gopherItemP	gi = d->selectorItem;
	BOOLEAN		fetchOK;


	if ( (s = GI_connectWithStatus(gi)) < 0) return;

	writeString(s, vStringValue(&(gi->selector)));
	writeString(s, EOL_STRING);

	showStatus("Reloading requested directory", STAT_DIRECTORY,
			 gi->host, gi->port);

	fetchOK = GI_getGopherDir(s, d);

	close(s);

	if (! removeStatusPanel() ) {
		
		/* someone cancelled as the directory load finished */

		return;

	}

	if (! fetchOK) {
		/* failure to re-load the directory */
		showError(
			"There seems to be no information in this directory\n");
		return;
	}

	setDirTime(d);

	return;
}


/* ====================================================================== */
/*                                                                        */
/* Utilities used by the item class and its subclasses                    */
/*                                                                        */
/* ====================================================================== */


/* GU_ftpCheck
   return True (ok) if ftp is allowed or this item doesn't represent
   an ftp request.  Return false if this this is an ftp request and 
   ftp's are not allowed. */

static BOOLEAN
GU_ftpCheck(gi)
gopherItemP	gi;
{
	BOOLEAN	isFtp = FALSE;

	if ( appResources->allowFtp ) return TRUE;

	isFtp = (strncmp(vStringValue(&(gi->selector)), "ftp:", 4) == 0);

	/* don't see an "ftp:", but see if there's an '@' in the path */
	if (! isFtp) {
		isFtp = (index(vStringValue(&(gi->selector)), '@') != NULL);
	}

	return ( ! isFtp );
}


/* GU_makePrefix
   edit application resources of directory listing prefixes to conform
   to be blank padded and exactly PREFIX_LEN characters.
   NOTE: These strings are *NOT* NULL-terminated. */

void
GU_makePrefix(target, source)
char	*target, *source;
{
	int	i;

	for (i=0; i<PREFIX_LEN; i++) {
		if (*source == NULLC) {
			*(target++) = ' ';
		} else {
			*(target++) = *(source++);
		}
	}
}


/* GU_getGopherItem
   receive a response from a gopher server, parsing it as it comes in.
   Return the item pointer for success, NULL for failure. */

static gopherItemP
GU_getGopherItem(gfd)
int	gfd;
{
	char			buffer[255];
	char	gType,
		gName[USER_STRING_LEN],
		gPath[SELECTOR_STRING_MAX_LEN],
		gHost[HOST_STRING_LEN];
	int	gPort;
	BOOLEAN	gPlus;


	if (readn(gfd, &(buffer[0]), 1) <= 0) {
		/* immediate EOF or error -- no item read */
		return NULL;
	}

	/** Get the kind of file from the first character **/

	gType = buffer[0];

	if (gType == A_EOI) return NULL;

	if (readField(gfd, &(buffer[1]), 255) <= 0) {
		/* immediate EOF or error -- no item read */
		return NULL;
	}

	/* get the User Displayable name */

	strncpy(gName, buffer + 1, USER_STRING_LEN);

	/* terminate the string just in case it was too long */

	gName[USER_STRING_LEN - 1] = '\0';

	/* get the Pathname */

	if (readField(gfd, gPath, SELECTOR_STRING_MAX_LEN) <= 0) {
		/* EOF or error after TYPE/NAME  --
		   no complete item read */
		return NULL;
	}

	/* get the hostname */

	if (readField(gfd, gHost, HOST_STRING_LEN) == 0) {
		/* EOF or error after TYPE/NAME/PATH  --
		   no complete item read */
		return NULL;
	}

	/* get the port number */

	if (readLine(gfd, buffer, 255)<=0) {
		/* EOF or error after TYPE/NAME/PATH  --
		   Problem - should be a port number here.  But this
		   isn't as serious as above.  We'll try to proceed. */
		;
	}

	gPort = 0;

	/* Get the port number */

	gPort = atoi(buffer);

	gPlus = False;

	return makeItem(gType, gName, gPath, gHost, gPort, gPlus);
}


/* GU_copySubclassRecord
   return a copy of the class record for subclass identified.
   Search both knownTypes, and types already initialized.  */

scInfo	*
GU_copySubclassRecord(t)
char	t;
{
	scInfo		*scRec;
	giSubclass 	*scID;

	scRec = (scInfo *) malloc(sizeof(scInfo));

	/* first check initialized types.  This allows an alias
	   to preferentially use a new external type. */

	for (scID = activeSubclass;
			scID != NULL && scID->typeID != t; scID=scID->next) ;

	if (scID == NULL) {
		/* it will be in this list or the final item type, unknown
		   will be used */
		for (scID = definedSubclass;
			 scID->typeID != t && scID->typeID != '\0'; scID++) ;
	}

	bcopy((char *) scID->subclass, (char *) scRec, sizeof(scInfo));

	return scRec;
}


/* GU_registerNewType
   Add a new type identifier and its subclass record to the known list. */

void
GU_registerNewType(t, scRec)
char	t;
scInfo	*scRec;
{
	giSubclass	*newSC = (giSubclass *) malloc (sizeof (giSubclass));
	giSubclass	*sc;

	newSC->typeID   = t;
	newSC->subclass = scRec;
	newSC->next     = (giSubclass *) NULL;

	/* add to the end of the list for expected behavior */

	if (activeSubclass == (giSubclass *) NULL) {
		activeSubclass = newSC;
	} else {
	    for (sc=activeSubclass; sc != NULL; sc=sc->next) {
		if (sc->typeID == t) {	/* already is one */
			break;
		}

		if (sc->next == (giSubclass *) NULL) {
			sc->next = newSC;
			break;
		}
	    }
	}
}


/* GU_isVowel
   return True if first character of a string is a vowel, false otherwise */

static	char	vowels[] = "aAeEiIoOuU";

BOOLEAN
GU_isVowel(str)
char	*str;
{
	char	*v;	
	for (v=vowels; (*str != *v  &&  *v != NULLC); v++ );

	return (*v != NULLC);
}

/* Ideas for the implementation of the access limiting mechanism
   came from the University of Minnesota gopher server code.
   In particular, the matching of IP addresses and host name
   matching algorithm is from that code. */


/* GU_addHostToAccessList
   add the host name or address to an access list.
   Determine if a given string is a name or IP address by
   checking the first character to see if it is numeric. */

static void
GU_addHostToAccessList(list, host)
accessList	*list;
char		*host;
{
	char		*temp;
	accessList	addr;

	temp = (char *) malloc((strlen(host) + 1) * sizeof(char));
	strcpy(temp, host);
	addr = (accessListItem *) malloc(sizeof(accessListItem));
	addr->address = temp;
	addr->numeric = isdigit(*host) ? TRUE : FALSE;

	/* For matching, it should not matter whether the list is
	   created in the same order as specified or reverse order,
	   so each item in the input list will be added to the front. */

	addr->next = *list;

	*list	   =  addr;

	return;
}


/* GU_createAccessList
   create an access list from a string of hostname/IP addresses */

accessList
GU_createAccessList(string)
char	*string;
{
	/* pull out host or domain names separated by white space */

	char		*cp;
	char		name[256];
	char		*np;
	accessList	list = (accessList) NULL;

	cp = string;
	while (*cp != '\0') {
		while (isspace(*cp)  &&  *cp != '\0') { cp++; };
		if ( *cp == '\0' ) break;

		np = &(name[0]);
		while (! isspace(*cp)  &&  *cp != '\0') {
			*np = *cp;
			np++;
			cp++;
		}
		*np='\0';

		GU_addHostToAccessList(&list, name);
	}

	return list;
}


/* GU_checkAccess
   see if a proposed host access is allowed */

BOOLEAN
GU_checkAccess(host, permitList)
char		*host;
accessList	permitList;
{
	accessListItem	*allowed;
	int	hostLen, addrLen;

	if (permitList == NULL) return TRUE;

	for (allowed=permitList; allowed!=NULL; allowed=allowed->next) {
		hostLen = strlen(host); 
		addrLen = strlen(allowed->address); 
		if (allowed->numeric) {

		    /* numeric IP address -- compare from start */

		    if (addrLen <= hostLen) {
			if (strncmp(host, allowed->address, addrLen) == 0) {
				return TRUE;
			}

		    }
		} else {

		    /* character host/domain name -- compare from end */

		    if (addrLen <= hostLen) {
			if (strcasecmp((host+hostLen-addrLen),
					allowed->address) == 0) {
				return TRUE;
			}
		    }
		}
	}

	return FALSE;
}


#ifdef DEBUG
void
GU_printAccessList(list)
accessList	list;
{
	accessListItem	*l;

	fprintf (stdout, "Host Access List:\n");

	for (l=list; l != (accessList) NULL; l=l->next) {
		fprintf (stdout, "\t%s %s\n", l->address,
				l->numeric ? "(IP address)" : "(host name)");
	}

	fprintf (stdout, "    End of Host Access List\n");
}
#endif /* DEBUG */


/* ====================================================================== */
/*                                                                        */
/* Class methods and common subclass methods of gopher item               */
/*                                                                        */
/* ====================================================================== */


/* GI_connnectWithStatus
   make a socket connection, posting the status popup and leaving it
   posted for the following processing */

int
GI_connectWithStatus(gi)
gopherItemP	gi;
{
	int	s;

	showStatus("Connecting to Gopher", STAT_CONNECT,
			gi->host, gi->port);
	s = connectToSocket(gi->host, gi->port);
	if (s < 0) {

		(void) removeStatusPanel();

		/* could not make a network connection to receive file */

		networkError(s, gi->host, gi->port);
		return -1;
	}

	/* check for cancel during connection */

	if (!updateStatusPanel(1, 0)) {
		(void) removeStatusPanel();

		close(s);
		return -1;
	}

	return s;
}


/* GI_getGopherDir
   receive a directory from a gopher server.
   Return the directory pointer for success, NULL for failure. */

BOOLEAN
GI_getGopherDir(gfd, d)
int	gfd;
gopherDirP	d;
{
	gopherItemP	gi;
	char		buffer[512];
	int		j, n=0;
	BOOLEAN		goOn;

	/* get each item; for each one, decide if it should be kept
	   in the directory.  */


	while ( (gi = GU_getGopherItem(gfd)) != NULL ) {

		n++;

		if (appResources->showItems == showAll) {
			appendItem(&(d->contents), gi);
		}
		    
		else if (appResources->showItems == showKnown  &&
			! UNKNOWN_TYPE(gi)) {
				appendItem(&(d->contents), gi);
		}

		else if (appResources->showItems == showAccessible  &&
			gi->accessOk) {
				appendItem(&(d->contents), gi);
		}

		else if (appResources->showItems == showAvailable  &&
			! UNKNOWN_TYPE(gi)  &&  gi->accessOk) {
				appendItem(&(d->contents), gi);
		}

		else {
			freeItem(gi);
			n--;
		} 

		if (n % 5 == 0) {
			goOn = updateStatusPanel(n, 0);
			if (!goOn) {
				freeItemList(&(d->contents));
				break;
			}
		}
	}

	return  (itemListLength(&(d->contents)) != 0);
} 


/* GI_copyFromNet
   Generic copy setup.  Makes network connection, updates status
   and invokes a type-specific copy procedure. */

BOOLEAN
GI_copyFromNet(outFD, gi, specialMsg)
int		outFD;
gopherItemP	gi;
char		*specialMsg;
{
	int	s;
	char	message[MESSAGE_STRING_LEN];
	BOOLEAN rc, goOn = TRUE;

	if (gi->sc->copyDataType == copyTypeNone) return;

	if (specialMsg == (char *) NULL) {
		sprintf(message, "Copying the %s", gi->sc->typeName);
	} else {
		strcpy(message, specialMsg);
	}

	if ((s = GI_connectWithStatus(gi)) < 0) return FALSE;

	writeString(s, vStringValue(&(gi->selector)));
	writeString(s, EOL_STRING);

	switch (gi->sc->copyDataType) {

	    case copyTypeAscii:
		rc = GI_copyAsciiFromNet(s, outFD, gi, message);
		break;
	
	    case copyTypeBinaryEOF:
		rc = GI_copyBinaryFromNetEOF(s, outFD, gi, message);
		break;

	    default:
		rc = FALSE;
		break;
	}

	close(s);

	/* check for cancel */

	goOn = removeStatusPanel();

	return (rc && goOn);
}



#define MIN_OF(a,b)	((a)<(b) ? (a) : (b))
#define BIN_BUFFER_SIZE	1400

/* GI_copyBinaryFromNetEOF
   Binary copy the contents of one open file descriptor to another.
   Copy until EOF

   The file descriptor references on open file for the result.
   For input, we will assume blocking I/O, so a read will always
   return something if it's not yet eof. */

BOOLEAN
GI_copyBinaryFromNetEOF(s, outFD, gi, msg)
int		s, outFD;
gopherItemP	gi;
char		*msg;
{
	char	buf[BIN_BUFFER_SIZE];
	int	nr, nw;
	int	byteCt=0;
	BOOLEAN goOn = TRUE;

	showStatus(msg, STAT_BINARY,
				gi->host, gi->port);

	while (TRUE) {
		nr = read(s, buf, BIN_BUFFER_SIZE);

		if (nr <= 0) {
			if (nr == -1) {
			    perror("GI_copyBinaryFromNetEOF");
			    fprintf (stderr,
				"Error copying binary data - input.\n");
			}
			break;
		}

		byteCt += nr;
		goOn = updateStatusPanel(byteCt, 0);
		if (!goOn) {
			break;
		}

		nw = write(outFD, buf, nr);
		if (nw != nr  ||  nw < 0) {
			perror("GI_copyBinaryFromNetEOF");
			fprintf (stderr,
				"Error copying binary data - output.\n");
			break;
		}
	}

	return goOn;
}


/* GI_copyBinaryFromNetLen
   Binary copy the contents of one open file descriptor to another.
   Copy until a specific number of characters has been copied.

   The file descriptor references on open file for the result.
   For input, we will assume blocking I/O, so a read will always
   return something if it's not yet eof. */

BOOLEAN
GI_copyBinaryFromNetLen(s, outFD, gi, msg)
int		s, outFD;
gopherItemP	gi;
char		*msg;
{
	char	buf[BIN_BUFFER_SIZE];
	int	nr, nw;
	int	byteCt=0;
	BOOLEAN goOn = TRUE;
	int	nBytes = 0;


	showStatus(msg, STAT_BINARY,
				gi->host, gi->port);

	while (nBytes > 0) {
		nr = read(s, buf, MIN_OF(nBytes, BIN_BUFFER_SIZE));

		if (nr <= 0) {
			if (nr == -1) {
			    perror("GI_copyBinaryFromNetLen");
			    fprintf (stderr,
				"Error copying binary data - input.\n");
			}
			break;
		}

		nBytes -= nr;

		byteCt += nr;
		goOn = updateStatusPanel(byteCt, 0);
		if (!goOn) {
			break;
		}

		nw = write(outFD, buf, nr);
		if (nw != nr  ||  nw < 0) {
			perror("GI_copyBinaryFromNetLen");
			fprintf (stderr,
				"Error copying binary data - output.\n");
			break;
		}
	}

	return goOn;
}


/* GI_copyAsciiFromNet
   Copy the contents of one open text file descriptor to another.  Copy
   until a line with a single period in column one or an end of file.

   Change "network ASCII" with CR-LF ending each line to standard
   Unix text files with a NL at the end of each line.

   Either file descriptor may reference a file or a socket connection. 
   For sockets, we will assume blocking I/O, so a read will always
   return something if it's not yet eof, and a write will write everything. */

static char	newLine[1] = {NL};

BOOLEAN
GI_copyAsciiFromNet(s, outFD, gi, msg)
int		s, outFD;
gopherItemP	gi;
char		*msg;
{
	char	line[FILE_LINE_LEN];
	int	len;
	int	lineCt=0, wordCt=0;
	BOOLEAN	goOn = TRUE;


	showStatus(msg, STAT_ASCII,
				gi->host, gi->port);

	if (readLine(s, line, FILE_LINE_LEN) <= 0) {
		(void) removeStatusPanel();
		return FALSE;
	}
	zapCRLF(line);


	while ( line[0] != '.'  ||  line[1] != '\0') {

		lineCt++;
		if ( (lineCt < 50   && lineCt % 10 == 0)  ||
		     (lineCt < 500  && lineCt % 50 == 0)  ||
		      lineCt % 100 == 0 ) {
			goOn = updateStatusPanel(lineCt, 0);
			if (!goOn) {
				break;
			}
		}

		len = strlen(line);
		write(outFD, line, len);
		write(outFD, newLine, 1);

		if (readLine(s, line, FILE_LINE_LEN) <= 0) break;
		zapCRLF(line);
	}

	return goOn;
}



#define NET_BUFFER_SIZE	1400

/* GI_copyNetUntilEOF
   copy chunks of data from a network file descriptor to a file pointer.
   No termination condition is expected except EOF on the input fd.
   This is a blocking read. */

void
GI_copyNetUntilEOF(s, fp)
int	s;
FILE	*fp;
{
	char	buf[NET_BUFFER_SIZE];
	int	j;

	while (TRUE) {
		j = read(s, buf, NET_BUFFER_SIZE);


		if (j <= 0) {
			if (j == -1)
			    fprintf (stderr,
				"Error (%d) copying data from the network\n",
					errno);
			break;
		}
		
		if (fwrite(buf, 1, j, fp) != j) {
			return;
		}
	}
}


/* GI_noCopyItem
   default "can't copy" procedure */

BOOLEAN
GI_noCopyItem(gi)
gopherItemP	gi;
{
	int		s;
	char		*lastName;
	char		message[MESSAGE_STRING_LEN];
	
	sprintf(message, "You cannot copy %s %s to a file.",
		(GU_isVowel(gi->sc->typeName) ? "an" : "a"), gi->sc->typeName);
	showError(message);

	return FALSE;
}


/* GI_copyToFile
   copy a gopher item directly to a file */

BOOLEAN
GI_copyToFile(gi)
gopherItemP	gi;
{

	saveNetRequest(gi);
	return TRUE;
}


/* GI_access
   check the access limits for an item.  This class method will be
   overridden for many subclasses which need to check the application
   resources for permission.  */

BOOLEAN
GI_access(gi)
gopherItemP	gi;
{
	BOOLEAN	result;

	result = GU_ftpCheck(gi);

	return result;
}


/* GI_init
   default class  initialization procedure a subclass of Gopher Item.  This
   procedure works for class "unknown". */

void
GI_init()
{
	GU_makePrefix(prefixUnknown,  appResources->prefixUnknown);
	GU_registerNewType(A_UNKNOWN, &noSubclass);

	return;
}


/* GI_done
   default class termination procedure a subclass of Gopher Item.  This
   procedure is a no-op. */

void
GI_done()
{
	return;
}


/* GI_restart
   default restart cleanup for a subclass of Gopher Item.  This
   procedure is a no-op. */

void
GI_restart()
{
	return;
}


/* GI_process
   default processing procedure for a subclass of Gopher Item.  This
   procedure is a no-op, suggesting an alternative means of processing
   the item.  Every known item type should override this procedure. */

BOOLEAN
GI_process(gi)
gopherItemP     gi;
{
	char message[MESSAGE_STRING_LEN];

	sprintf(message,
	    "There is no automatic processing for this gopher item type (%c)",
	    gi->type);
	strcat(message,
	    "\nYou may be able to use the Copy command to access the file.");
	showError(message);

	return FALSE;
}
