/*         		    					     HTBrowse.c
**	HYPERTEXT BROWSER FOR DUMB TERMINALS
**
**	(c) COPRIGHT MIT 1995.
**	Please first read the full copyright statement in the file COPYRIGH.
**	@(#) $Id: HTBrowse.c,v 1.173 1997/12/22 14:46:44 frystyk Exp $
**
**  Authors:
**	NP:  Nicola Pellow  Tech.Student CERN 1990-91
**	TBL: Tim Berners-Lee CERN (timbl@w3.org)
**	JFG: Jean-Francois Groff, Cooperant CERN 1991-92 (jfg@dxcern.cern.ch)
**	DR:  Dudu Rashty +972-2-584848 <RASHTY@hujivms.bitnet>
**	MD:  Mark Donszelmann, DELPHI CERN, (duns@vxdeop.cern.ch)
**	HFN: Henrik Frystyk Nielsen, MIT, (frystyk@w3.org)
**
**  History:
**
**   4 Dec 90:  Written from scratch (NP)
**  11 Feb 91:  Code written by TBL so that the browser could be linked with
**              code produced by Bernd Pollermann, enabling access to the
**              data on CERNVM. This involved changing the code to handle file
**              numbers rather than file pointers.
**  18 Mar 91:  The feature of history mechanism was included, enabling a  
**              record of previous nodes visited to be kept.
**   6 Apr 91:  When a node is accessed, it is immediately read into a 
**              buffer, in an unformatted state, as soon as the connection is  
**              made, so that the server is freed as quickly as possible. 
**              The program now also uses the additional modules HTBufferFile.c
**              and HTBufferFile.h.
**  17 Apr 91:  Can be used on machines running ANSI C and ordinary C.
**  10 May 91:  Formatted text is stored in a linked list buffer which allows
**              scrolling and better page breaks in the middle of text.
**              Code incorporated by Tim BL, to enable anonymous FTP.          
**  21 May 91:  Accepts various parameters on the command line.
**  19 Aug 91:  Currently available in Unix, VAX/VMS and MVS environments.
**  21 Nov 91:	 Character grid uses new architecture. (TBL)
**              added -w option, new commands, print,
**	             ...	See Features.html for further details
**  16 Jan 92:	 Put in VIOLA-compatible options - see \017 characters.
**	             \017 and \016 bracket user-selectable input.
**  27 Feb 92:	New handling of user input, enhanced command syntax. (JFG)
**  18 May 92:	PS command see ifdef SLAVE_PRINTER (DR/TBL)
**   6 Oct 92:  Painful recovery from someone(?)'s attept to pretty print.(TBL)
**		Please see coding style guide before changing indentation etc!
**     Mar 93:	Force on HTFile's HTDirAccess and HTDirReadme flags.
**   3 Nov 93:	(MD) Changed vms into VMS
**		(MD) Assigne OUTPUT in main, not at initialize (VMS only)
**
** Compilation-time macro options
**
**	REF_MARK	Printf string to be used for printing anchor numbers
**	END_MARK	String to be used to denote the end of a document
*/

#include "WWWLib.h"
#include "WWWMIME.h"
#include "WWWCache.h"
#include "WWWApp.h"
#include "WWWTrans.h"
#include "WWWInit.h"

#include "GridText.h"				     /* Hypertext definition */
#include "HTBrowse.h"			     /* Things exported, short names */

#include "CSLApp.h" /* the PICApp library should provide everything the app needs */
#include "CSLL.h"
#include "CSUser.h"
#include "CSUsrLst.h"

#ifndef W3C_VERSION
#define W3C_VERSION		"Unspecified"
#endif

#define APP_NAME		"W3CLineMode"
#define APP_VERSION		W3C_VERSION

/* Default page for "Manual" command */
#define MANUAL	"http://www.w3.org/pub/WWW/LineMode/User/QuickGuide.html"

/* Default page for "help" command */
#define C_HELP	"http://www.w3.org/pub/WWW/LineMode/User/Commands.html"

/* Default page for "-help" command line option */
#define L_HELP	"http://www.w3.org/pub/WWW/LineMode/User/CommandLine.html"

#define DEFAULT_OUTPUT_FILE	"www.out"
#define DEFAULT_RULE_FILE	"www.conf"
#define DEFAULT_LOG_FILE       	"www.log"
#define DEFAULT_MEMLOG		"www.mem"
#define DEFAULT_USERLIST_FILE	"PICSusrs.html"

#if 0
#define HT_MEMLOG
#endif

#define PROMPT			"%s"
#define REF_MARK		"[%d]"
#define END_MARK		"[END]"

#define SCREEN_WIDTH		79  	      /* Default width of the screen */
#define MIN_SCREEN_WIDTH	10 
#define MAX_SCREEN_WIDTH	150	 
#define SCREEN_HEIGHT		24 		  /* Default number of lines */
#define MIN_SCREEN_HEIGHT	5 
#define MAX_SCREEN_HEIGHT	200	 

#define MAX_HISTORY		20	/* Don't list more than this in list */

#define RESPONSE_LENGTH		1024     /* Maximum length of users response */

#define SHOW_MSG		(WWWTRACE || HTAlert_interactive())
#define CHECK_INPUT(a, b)	(!strncasecomp ((a), (b), strlen((b))))

#define DEFAULT_I_TIMEOUT	1000       /* Interactive timeout in millies */
#define DEFAULT_NI_TIMEOUT	10000  /* Non-interactive timeout in millies */

#define DEFAULT_FORMAT		WWW_PRESENT

#if defined(__svr4__)
#define CATCH_SIG
#endif

/* HWL 18/7/94: applied patch from agl@glas2.glas.apc.org (Anton Tropashko) */
#ifdef CYRILLIC
#include "a_stdio.h"
struct ARc arc;
#endif

/* Screen size parameters */
PUBLIC int		HTScreenWidth   = SCREEN_WIDTH;	       /* By default */
PUBLIC int		HTScreenHeight  = -1;		   /* -1 = Undefined */

/* Anchor specific information */
PUBLIC BOOL		display_anchors = YES;	    /* Show anchors in text? */
PUBLIC char *		start_reference = NULL;   /* Format for start anchor */
PUBLIC char *		end_reference = REF_MARK;	   /* for end anchor */
PUBLIC char *		reference_mark = "[%d] ";     /* for reference lists */
PUBLIC char *		end_mark = END_MARK;   	  /* Format string for [End] */

typedef enum _LMFlags {
    LM_FILTER	= 0x1,
    LM_REFS	= 0x2,
    LM_REFORMAT = 0x4,
    LM_PREEMTIVE= 0x8
} LMFlags;

struct _LineMode {
    HTRequest *		console;			   /* For user input */
    HTParentAnchor *	anchor;
    HTList *		active;			  /* List of acitve contexts */
    HTList *		presenters;
    HTHistory *		history;    			     /* History list */
    char *		cwd;				  /* Current dir URL */
    char *		rules;
    char *		logfile;
    HTLog * 		log;
    char *		outputfile;
    char *		host;
    int			trace;
    int			timer;
    HTFormat		format;		        /* Input format from console */
    LMFlags		flags;
    HTView *		pView;
    char *		userList;
    CSUser_t * 		pCSUser;
    BOOL		noPICSPasswords;
};

typedef enum _LMState {
    LM_UPDATE	= 0x1,
    LM_NO_UPDATE= 0x2,
    LM_DONE	= 0x4,
    LM_INACTIVE	= 0x8,
    LM_IGNORE	= 0x10
} LMState;

typedef struct _Context {
    LMState		state;
    HTRequest *		request;
    LineMode *		lm;
    HTParentAnchor *	source;	   /* The source if we are using PUT or POST */
} Context;

PRIVATE int scan_command (SOCKET s, void * param, HTEventType type);

PRIVATE HTEvent * ConsoleEvent = NULL;

#ifndef WWW_WIN_WINDOW
PRIVATE FILE *		OUTPUT = stdout;
#endif
 
PRIVATE InputParser_t parse_command;
InputParser_t * PInputParser = &parse_command;

/* Net callback handlers */
PRIVATE HTNetAfter terminate_handler;

/* ------------------------------------------------------------------------- */

PUBLIC int HTWatch(int id, void * obj, const char * fmt, ...)
{
    va_list pArgs;
    va_start(pArgs, fmt);
    fprintf(stderr, "id: %x  obj: %p: ", id, obj);
    return vfprintf(stderr, fmt, pArgs);
}

/*	Create a Context Object
**	-----------------------
*/
PRIVATE Context * Context_new (LineMode *lm, HTRequest *request, LMState state)
{
    Context * me;
    if ((me = (Context  *) HT_CALLOC(1, sizeof (Context))) == NULL)
        HT_OUTOFMEM("Context_new");
    me->state = state;
    me->request = request;
    me->lm = lm;
    HTRequest_setContext(request, (void *) me); 
    HTList_addObject(lm->active, (void *) me);
    return me;
}

/*	Delete a Context Object
**	-----------------------
*/
PRIVATE BOOL Context_delete (Context * old)
{
    HT_FREE(old);
    return YES;
}

PUBLIC LineMode * Context_getLineMode(HTRequest * request)
{
    return ((Context *) HTRequest_context(request))->lm;
}

/*
**  This function creates a new request object and adds it to the global
**  list of active threads
*/
CSDisposition_callback PICSCallback;
PRIVATE HTRequest * Thread_new (LineMode * lm, BOOL Interactive, LMState state)
{
    HTRequest * newreq = HTRequest_new();
    if (!lm) return NULL;
    Context_new(lm, newreq, state);
    if (Interactive) HTRequest_setConversion(newreq, lm->presenters, NO);
    if (lm->flags & LM_PREEMTIVE) HTRequest_setPreemptive(newreq, YES);
    HTRequest_addRqHd(newreq, HT_C_HOST);
    CSApp_registerReq(newreq, lm->pCSUser, PICSCallback, 
		      CSApp_callOnBad, (void *)lm);
    return newreq;
}

/*
**  This function deletes all unused request objects
*/
PRIVATE void Thread_cleanup (LineMode * lm)
{
    if (lm && lm->active) {
	HTList * cur = lm->active;
	Context * pres;
	while ((pres = (Context *) HTList_nextObject(cur))) {
	    if (pres->state&LM_DONE && pres->state&LM_INACTIVE) {
		if ((HTList_removeObject(lm->active, pres)) == NO)
		    HTTrace("NOT FOUND\n");
		HTRequest_delete(pres->request);
		Context_delete(pres);
		cur = lm->active;
	    }
	}
    }
}

/*
**  This function deletes the whole list of active threads.
*/
PRIVATE void Thread_deleteAll (LineMode * lm)
{
    if (lm && lm->active) {
	HTList * cur = lm->active;
	Context * pres;
	while ((pres = (Context *) HTList_nextObject(cur))) {
	    if (pres->request) {
		HTRequest_delete(pres->request);
		Context_delete(pres);
	    }
	}
	HTList_delete(lm->active);
	lm->active = NULL;
    }
}

/*	Create a Line Mode Object
**	-------------------------
*/
PRIVATE LineMode * LineMode_new (void)
{
    LineMode * me;
    if ((me = (LineMode *) HT_CALLOC(1, sizeof(LineMode))) == NULL)	
	HT_OUTOFMEM("LineMode_new");
    me->cwd = HTGetCurrentDirectoryURL();
    me->active = HTList_new();
    me->console = HTRequest_new();
    Context_new(me, me->console, LM_UPDATE);
    me->trace = SHOW_ALL_TRACE;
    me->timer = -1;
    me->pCSUser = 0;
    if (!(me->pView = HTView_create("'nother Window", 25, 80, me)))
    	return 0;
    return me;
}

/*	Delete a Line Mode Object
**	-------------------------
*/
PRIVATE BOOL LineMode_delete (LineMode * lm)
{
    if (lm) {
	Thread_deleteAll(lm);
	HTPresentation_deleteAll(lm->presenters);
	HTHistory_delete(lm->history);
	HT_FREE(lm->cwd);
	if (lm->log) HTLog_close(lm->log);
	if (lm->pCSUser)
	    CSLoadedUser_remove(lm->pCSUser);
	HTView_destroy(lm->pView);
#ifndef WWW_WIN_WINDOW
	if (OUTPUT && OUTPUT != stdout) fclose(OUTPUT);
#endif
	HText_freeAll();	/* Free all loaded documents */
	HT_FREE(lm);
	return YES;
    }
    return NO;
}

PUBLIC HTRequest * LineMode_getConsole(LineMode * pLm)
{
    return pLm->console;
}

PUBLIC HTView * LineMode_getView(LineMode * pLm)
{
    return pLm ? pLm->pView : 0;
}

PRIVATE BOOL LineMode_load(LineMode * lm, HTParentAnchor * anchor, 
			   char * url, BOOL preemptive, HTRequest ** pPReq)
{
    char * fullURL;
    HTParentAnchor * pAnchor;

    *pPReq = Thread_new(lm, YES, LM_UPDATE);
    if (preemptive)
        HTRequest_setPreemptive(*pPReq, YES);
    if (anchor)
	return HTLoadRelative(url, anchor, *pPReq);
    fullURL = HTParse(url, lm->cwd, PARSE_ALL);
    pAnchor = (HTParentAnchor *) HTAnchor_findAddress(fullURL);
    HT_FREE(fullURL);
    return HTLoadAnchor((HTAnchor *) pAnchor, *pPReq);
}

PRIVATE void Cleanup (LineMode * me, int status)
{
    if (HTAlert_interactive())	   /* Terminate with a LF if not interactive */
	OutputData(me->pView, "\n");
    CSLoadedUser_deleteAll();
    CSApp_unregisterApp();

    LineMode_delete(me);
    HTProfile_delete();
#ifdef HT_MEMLOG
    HTMemLog_close();
#endif

#ifdef VMS
    exit(status ? status : 1);
#else
    exit(status ? status : 0);
#endif
}

/*
** Get size of the output screen. Stolen from less.
*/
PRIVATE void scrsize (int * p_height, int * p_width)
{
#if defined(HAVE_IOCTL) && defined(HAVE_WINSIZE)
    register char *s;
    /* int ioctl(); - henrik */
    struct winsize w;
    if (ioctl(2, TIOCGWINSZ, &w) == 0 && w.ws_row > 0)
	*p_height = w.ws_row;
    else if ((s = getenv("LINES")) != NULL)
	*p_height = atoi(s);
    else
	*p_height = SCREEN_HEIGHT;
    
    if (ioctl(2, TIOCGWINSZ, &w) == 0 && w.ws_col > 0)
	*p_width = w.ws_col;
    else if ((s = getenv("COLUMNS")) != NULL)
	*p_width = atoi(s);
    else
	*p_width = 80;
#else
    *p_height = SCREEN_HEIGHT;
    *p_width = SCREEN_WIDTH;
#endif /* HAVE_IOCTL && HAVE_WINSIZE */
}

#ifdef CATCH_SIG
#include <signal.h>
/*								    SetSignal
**  This function sets up signal handlers. This might not be necessary to
**  call if the application has its own handlers.
*/
PRIVATE void SetSignal (void)
{
    /* On some systems (SYSV) it is necessary to catch the SIGPIPE signal
    ** when attemting to connect to a remote host where you normally should
    ** get `connection refused' back
    */
    if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
	if (PROT_TRACE) HTTrace("HTSignal.... Can't catch SIGPIPE\n");
    } else {
	if (PROT_TRACE) HTTrace("HTSignal.... Ignoring SIGPIPE\n");
    }

#ifdef HT_MEMLOG
    HTMemLog_flush();
#endif

}
#endif /* CATCH_SIG */

/*  Print version information
**  -------------------------
*/
PRIVATE void VersionInfo (LineMode * lm)
{
    HTView * pView = lm ? lm->pView : NULL;
    OutputData(pView, "\n\nW3C Reference Software\n\n");
    OutputData(pView, "\tW3C Line Mode Browser version %s.\n", APP_VERSION);
    OutputData(pView, "\tW3C Reference Library version %s.\n\n",HTLib_version());
    OutputData(pView, "Please send feedback to <libwww@w3.org>\n");
}

/* 	Reference_List
**	--------------
**  	Print out a list of HyperText References from current document
*/
PRIVATE void Reference_List (LineMode * lm, BOOL titles)
{
    int refs = HText_sourceAnchors(HTMainText);
    if (refs <= 0) {
	OutputData(lm->pView, "\n\nThere are no references from this document.\n\n");
    } else {
	int cnt;
	OutputData(lm->pView, "\n*** References from this document ***\n");
	for (cnt=1; cnt<=refs; cnt++) {
	    HTLink * link =
		HTAnchor_mainLink((HTAnchor*)HText_childNumber(HTMainText,cnt));
	    HTAnchor * dest = HTLink_destination(link);
	    HTParentAnchor * parent = HTAnchor_parent(dest);
	    char * address =  HTAnchor_address(dest);
	    const char * title = titles ? HTAnchor_title(parent) : NULL;
	    OutputData(lm->pView, reference_mark, cnt);
	    OutputData(lm->pView, "%s%s\n",
		    ((HTAnchor*)parent!=dest) && title ? "in " : "",
		    (char *)(title ? title : address));
	    HT_FREE(address);
	}
#ifndef WWW_WIN_WINDOW
	fflush(OUTPUT);
#endif
    }
}      

/*	History_List
**	------------
**	Display a history list of nodes visited during the session.
*/
PRIVATE void History_List (LineMode * lm)
{
    int current = HTHistory_position(lm->history);
    int max = HTHistory_count(lm->history);
    int cnt;
    OutputData(lm->pView, "\n  Documents you have visited: ");
    if (max > MAX_HISTORY) {
	max = MAX_HISTORY;
	OutputData(lm->pView, "(truncated)\n");
    } else
	OutputData(lm->pView, "\n");
    for (cnt=1; cnt<=max; cnt++) {
	HTAnchor *anchor = HTHistory_list(lm->history, cnt);
    	char *address = HTAnchor_address(anchor);
	HTParentAnchor *parent = HTAnchor_parent(anchor);
	const char *title = HTAnchor_title(parent);
	OutputData(lm->pView, "%s R %d\t%s%s\n",
	       (cnt==current) ? "*" : " ",
	       cnt,
	       ((HTAnchor*)parent!=anchor) && title ? "in " : "",
	       title ? title : address);
	HT_FREE(address);
    }
    OutputData(lm->pView, "\n");
}

/*	Prompt for answer and get text back. Reply text is either NULL on
**	error or a dynamic string which the caller must HT_FREE.
*/
PRIVATE char * AskUser (HTRequest * request, const char * Msg,
			const char * deflt)
{
    char buffer[200];
    char *reply = NULL;
    HTTrace("%s ", Msg ? Msg : "UNKNOWN");
    if (deflt)
	HTTrace("(RETURN for [%s]) ", deflt);

#ifndef NO_STDIO
    if (!fgets(buffer, 200, stdin))
	return NULL;		       	     /* NULL string on error, Henrik */
    buffer[strlen(buffer)-1] = '\0';		        /* Overwrite newline */
    if (*buffer)
	StrAllocCopy(reply, buffer);
    else if (deflt)
	StrAllocCopy(reply, deflt);
#endif
    return reply;
}

PRIVATE BOOL confirm (HTRequest * request, const char * Msg)
{
    char response[4];
    HTTrace("%s (y/n) ", Msg ? Msg : "UNKNOWN");
#ifndef NO_STDIO
    if (fgets(response, 4, stdin)) 		   /* get reply, max 3 chars */
#endif
    {
	char *ptr = response;
	while (*ptr) {
	    if (*ptr == '\n') {
		*ptr = '\0';
		break;
	    }
	    *ptr = TOUPPER(*ptr);
	    ptr++;
	}
	return (!strcmp(response, "YES") || !strcmp(response, "Y")) ? YES : NO;
    }
    return NO;
}

/*	MakeCommandLine
**	---------------
**	Generate the Prompt line and flush it to the user
*/
PRIVATE void MakeCommandLine (LineMode * lm, BOOL is_index)
{
    /* First Line */
    if (HTAnchor_hasChildren(HTMainAnchor)) {
	int refs = HText_sourceAnchors(HTMainText);
	if (refs>1)
	    OutputData(lm->pView, "1-%d, ", refs);
	else
	    OutputData(lm->pView, "1, ");	
    }
    if (HText_canScrollUp(HTMainText)) {
	OutputData(lm->pView, "Top, ");
	OutputData(lm->pView, "Up, ");
    }
    if (HText_canScrollDown(HTMainText)) {
	OutputData(lm->pView, "BOttom, ");
	OutputData(lm->pView, "Down or <RETURN> for more,");
    }

    /* Second Line */
    OutputData(lm->pView, "\n");
    if (HTHistory_canBacktrack(lm->history))
	OutputData(lm->pView, "Back, ");
    if (HTHistory_canForward(lm->history))
	OutputData(lm->pView, "Forward, ");
    if (is_index)
	OutputData(lm->pView, "FIND <keywords>, ");
    OutputData(lm->pView, "Quit, or Help: ");	
    fflush(stdout);  	           	  /* For use to flush out the prompt */
    return;
}

/*
**  PUT the current document to the destination specified by the user
**  Returns the result of the load function.
*/
PRIVATE int PutAnchor (LineMode * lm, HTRequest * request)
{
    char * dest = AskUser(request, "Destination: ", NULL);

    /*
    ** If user has typed a destination then create a new request object and
    ** start the PUT operation. The destination is taken relative to the
    ** current location.
    */
    if (dest) {
	HTRequest * new_request = Thread_new(lm, YES, LM_UPDATE);
	HTPutDocumentRelative(HTMainAnchor, dest, HTMainAnchor, new_request);
	HT_FREE(dest);
    }
    return YES;
}

/*
**  Delete the current document.
*/
PRIVATE int DeleteAnchor (LineMode * lm, HTRequest * request)
{
    int status = HT_INTERNAL;
    BOOL doit = confirm(request, "Delete the current document?");
    if (doit) {
	HTRequest * new_request = Thread_new(lm, YES, LM_UPDATE);
	status = HTDeleteAnchor((HTAnchor *) HTMainAnchor, new_request);
    }
    return status;
}

/*
**  This function puts up a stream to a file in order to save a document. This
**  is activated by '>', '>>' or '>!' from the prompt line.
*/
PRIVATE BOOL SaveOutputStream (HTRequest * req, char * This, char * Next)
{
    FILE *fp;
    char *fname;
    char *fmode;
    
    /* Checks if file exists. Can be overruled by using '>!' */
    if (*(This+1) == '>') {	 		   	   /* Append to file */
	fmode = "ab";
	fname = *(This+2) ? (This+2) : Next;
    } else if (*(This+1) == '!') {
	fmode = "wb";				           /* Overwrite file */
	fname = *(This+2) ? (This+2) : Next;
    } else {						/* File name follows */
	fmode = "wb";
	fname = *(This+1) ? (This+1) : Next;
	if (fname) {				       /* See if file exists */
	    if ((fp = fopen(fname, "r")) != NULL) {
		fclose(fp);
		if (!confirm(req, "File exists - overwrite?"))
		    return NO;
	    }
	}
    }
    if (!fname)					       /* No file name given */
	return NO;
    if ((fp = fopen(fname, fmode)) == NULL) {
	if (SHOW_MSG) HTTrace("Can't access file (%s)\n", fname);
	return NO;
    }
    HTRequest_setOutputStream(req, HTFWriter_new(req, fp, NO));
    if (SHOW_MSG) HTTrace("Saving to file `%s\'\n", fname);
    return (HTLoadAnchor((HTAnchor*) HTMainAnchor, req) != HT_WOULD_BLOCK);
}

CSError_t LoadedUsersCallback(CSUser_t * pCSUser, int index, void * pVoid)
{
    LineMode * lm = (LineMode *)pVoid;
    OutputData(lm->pView, "%d: %s\n", index, CSUser_name(pCSUser));
    return CSError_OK;
}

CSError_t UserListCallback(char * username, char * url, int index, void * pVoid)
{
    LineMode * lm = (LineMode *)pVoid;
    OutputData(lm->pView, "%d: %s %s\n", index, username, url);
    return CSError_OK;
}

PRIVATE BOOL ShowPICSUsers(LineMode * lm)
{
    OutputData(lm->pView, "Loaded users:\n");
    CSLoadedUser_enum(&LoadedUsersCallback, (void *)lm);
    OutputData(lm->pView, "Listed users:\n");
    CSUserList_enum(&UserListCallback, (void *)lm);
    return YES;
}

PRIVATE int PICS_userCallback(CSUser_t * pCSUser, void * pVoid)
{
    LineMode * lm = (LineMode *)pVoid;
    char * userName;
    char * password = NULL;
    HTAlertCallback *cbf;
    int ret;
    HTAlertPar * reply;

    if (lm->noPICSPasswords) {
        lm->pCSUser = pCSUser;
	return 0;
    }
    if (!(cbf = HTAlert_find(HT_A_USER_PW)))
        return -1;
    reply = HTAlert_newReply();
    userName = CSUser_name(pCSUser);
    if ((ret = (*cbf)(lm->console, HT_A_USER_PW, 
		      HT_MSG_NULL, userName, NULL, reply))) {
        userName = HTAlert_replyMessage(reply);
        password = HTAlert_replySecret(reply);
    }
    HTAlert_deleteReply(reply);
    if (!ret) {
        HTTrace("PICS set user *canceled*.\n");
	return -1;
    }
    ret = -1;
    if (!userName)
	HTTrace("PICS cannot set to no name.\n");
    else if (CSUser_checkPassword(pCSUser, password) == NO)
        HTTrace("PICS password was not valid.\n");
    else {
        ret = 0;
	lm->pCSUser = pCSUser;
    }
    HT_FREE(userName);
    HT_FREE(password);
    return ret;
}

PRIVATE BOOL SetPICSUser(LineMode * lm, char * userName)
{
    char * password = NULL;
    HTAlertCallback *cbf;
    BOOL ret;
    HTAlertPar * reply;

    if (!(cbf = HTAlert_find(HT_A_USER_PW)))
        return NO;
    reply = HTAlert_newReply();
    if ((ret = (*cbf)(lm->console, HT_A_USER_PW, 
		      HT_MSG_NULL, userName, NULL, reply))) {
        userName = HTAlert_replyMessage(reply);
        password = HTAlert_replySecret(reply);
    }
    HTAlert_deleteReply(reply);
    if (!ret) {
        HTTrace("PICS set user *canceled*.\n");
	return NO;
    }
    ret = NO;
    if (!userName)
	HTTrace("Canceled.\n");
    else if (!(lm->pCSUser = CSApp_registerUserByName(userName, password))) {
        char * url;
        if ((url = CSUserList_findURL(userName)) == NULL)
	    HTTrace("PICS user \"%s\" is unknown.\n", userName);
	else if (!CSLoadedUser_load(url, lm->cwd))
	    HTTrace("Can't load PICS user \"%s\".\n", userName);
	else if ((CSLoadedUser_find(userName)) == NO)
	    HTTrace("PICS user \"%s\" not found in \"%s\".\n", userName, url);
	else if (!(lm->pCSUser = CSApp_registerUserByName(userName, password)))
	    HTTrace("Failed to set PICS user to \"%s\".\n", userName);
	ret = YES;
	HT_FREE(url);
    }
    HT_FREE(userName);
    HT_FREE(password);
    return ret;
}

PRIVATE BOOL LoadPICSUser(LineMode * lm, char * url)
{
    CSUser_t * pCSUser;
    if (!(pCSUser = CSLoadedUser_load(url, lm->cwd)))
        return NO;
    lm->pCSUser = pCSUser;
    return YES;
}

CSError_t PICSCallback(HTRequest* pReq, CSLabel_t * pCSLabel, 
		       CSUser_t * pCSUser, CSError_t disposition, void * pVoid)
{
    LineMode * lm = (LineMode *)pVoid;
    char * mesg;
    switch (disposition) {
        case CSError_RATING_RANGE:
            {
	    char * labelStr = CSLabel_getRatingStr(pCSLabel);
	    char * userStr = CSUser_getRatingStr(pCSUser);
	    char * anchorStr = HTAnchor_address((HTAnchor*)HTRequest_anchor(pReq));
            OutputData(lm->pView, "PICS user %s is not allowed to see document: %s\n", 
		       CSUser_name(pCSUser), anchorStr);
            OutputData(lm->pView, "  %s's \"%s\" rating for service %s (%s) did not include %s\n", 
		       CSUser_name(pCSUser), 
		       CSLabel_getRatingName(pCSLabel),
		       CSLabel_getServiceName(pCSLabel), 
		       userStr, labelStr);
	    HT_FREE(userStr);
	    HT_FREE(labelStr);
	    HT_FREE(anchorStr);
	    }
	    return disposition;
        case CSError_BUREAU_NONE: mesg="label bureau was not contacted"; break;
        case CSError_RATING_VALUE: mesg="value"; break;
        case CSError_RATING_MISSING: mesg="rating not found"; break;
        case CSError_SINGLELABEL_MISSING: mesg="no single-labels found"; break;
        case CSError_SERVICE_MISSING: mesg="service not available";break;
        case CSError_SERVICE_NONE: mesg="no services available for document"; break;
        case CSError_RATING_NONE: mesg="no ratings in a service"; break;
        case CSError_BAD_DATE: mesg="labels were out of date"; break;
        default:HTTrace("PICSCallback: odd error code: %d\n", disposition); return disposition;
    }
    OutputData(lm->pView, "PICS disallowed document: %s\n", mesg);
    return disposition;
}

/* ------------------------------------------------------------------------- */
/*				EVENT FUNCTIONS				     */
/* ------------------------------------------------------------------------- */

/*	parse_command
**	------------
**      Given the user's input, deal with it as necessary.
**	Any Command which works returns from the routine. If nothing
**	works then a search or error message down at the bottom.
**
**	Returns		HT_ERROR	Error has occured or we quit
**			HT_OK		Call back was OK
*/
PRIVATE int parse_command (char* choice, SOCKET s, HTRequest *req, HTEventType type)
{
    char * the_choice=NULL;		           /* preserved user command */
    char * token=NULL;        	   	    	    /* First word of command */
    char * this_command;	       	              /* token and following */
    char * next_word;			                      /* Second word */
    char * other_words;				/* Second word and following */
    BOOL is_index = HTAnchor_isIndex(HTMainAnchor);
    BOOL found = YES;
    BOOL OutSource = NO;			    /* Output source, YES/NO */
    int status = YES;
    HTRequest * cur_req = NULL;
    Context * cur_context = NULL;
    LineMode * lm = NULL;

    cur_req = req;
    cur_context = (Context *) HTRequest_context(req);
    lm = cur_context->lm;

    StrAllocCopy (the_choice, choice);		       /* Remember it as is, */
    if (*the_choice && the_choice[strlen(the_choice)-1] == '\n') /* final \n */
	the_choice[strlen(the_choice)-1] = '\0';
    
    token = strtok (choice, " \t\n\r");	      /* Tokenize user input */
    this_command = the_choice;
    if (token) {
	next_word = strtok (NULL, " \t\n\r");
	other_words = the_choice + (next_word - choice);
    }
    else
	goto down;				/* Empty input : scroll down */
    
    /* Process Command */
  loop:
    switch (TOUPPER(*token)) {
      case '0':
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':
	{
	    int  ref_num;
	    sscanf(token,"%d",&ref_num);
	    if (ref_num>0 && ref_num<=HText_sourceAnchors(HTMainText)) {
		HTChildAnchor *source = HText_childNumber(HTMainText, ref_num);
		if (source) {
		    HTLink * link = HTAnchor_mainLink((HTAnchor *) source);
		    HTAnchor * destination = HTLink_destination(link);
		    req = Thread_new(lm, YES, LM_UPDATE);		    

		    /* Continous browsing, so we want Referer field */
		    HTRequest_setParent(req, HTAnchor_parent((HTAnchor*)source));
		    HTLoadAnchor(destination, req);
		} else {
		    status = NO;				/* No anchor */
		}
	    } else {
		if (SHOW_MSG)
		    HTTrace("Warning: Invalid Reference Number: (%d)\n",
			    ref_num);
	    }
	}
	break;
	
      case 'B':		
	if (CHECK_INPUT("BACK", token)) {	  /* Return to previous node */
	    if (HTHistory_canBacktrack(lm->history)) {
		req = Thread_new(lm, YES, LM_NO_UPDATE);
		status = HTLoadAnchor(HTHistory_back(lm->history), req);
	    } else {
		OutputData(lm->pView, "\nThis is the first document in history list\n");
	    }
	} else if (CHECK_INPUT("BOTTOM", token)) {	/* Scroll to bottom  */
	    HText_scrollBottom(HTMainText);
	} else if (CHECK_INPUT("BYE", token)) {		    /* Quit program? */
	    HTNet_killAll();				/* Kill all requests */
	    status = NO;
	} else
	    found = NO;
	break;
	
      case 'C':
#ifdef HAVE_CHDIR
	if (CHECK_INPUT("CD", token)) {        /* Change working directory ? */
	    goto lcd;
	} else
#endif /* HAVE_CHDIR */
	if (CHECK_INPUT("CLEAR", token)) {	       /* Clear History list */
	    HTHistory_removeFrom(lm->history, 1);
	} else
	    found = NO;
	break;
	
      case 'D':
	if (CHECK_INPUT("DELETE", token)) {	    		   /* DELETE */
	    status = DeleteAnchor(lm, req);
	} else if (CHECK_INPUT("DOWN", token)) {    /* Scroll down one page  */
	  down:
	    if (HText_canScrollDown(HTMainText))
		HText_scrollDown(HTMainText);
	} else
	    found = NO;
	break;
	
      case 'E':
	if (CHECK_INPUT("EDIT", token)) {
	    status = PutAnchor(lm, req);
	} else if (CHECK_INPUT("EXIT", token)) {	    /* Quit program? */
	    HTNet_killAll();				/* Kill all requests */
	    status = NO;
	} else
	    found = NO;
	break;
	
      case 'F':						 /* Keyword search ? */
	if (is_index && CHECK_INPUT("FIND", token)) {
	  find:
	    {
		if (next_word) {
		    req = Thread_new(lm, YES, LM_UPDATE);
		    status = HTSearchString(other_words,
					    (HTAnchor *) HTMainAnchor, req);
		}
	    }
	} else if (CHECK_INPUT("FLUSH", token)) {	 /* Flush file cache */
	    if (confirm(req, "Flush Persistent Cache?") == YES)
		HTCache_flushAll();

	} else if (CHECK_INPUT("FORWARD", token)) {
	    if (HTHistory_canForward(lm->history)) {
		req = Thread_new(lm, YES, LM_NO_UPDATE);
		status = HTLoadAnchor(HTHistory_forward(lm->history), req);
	    } else {
		OutputData(lm->pView, "\nThis is the last document in history list.\n");
	    }
	} else
	    found = NO;
	break;
	
      case 'G':
	if (CHECK_INPUT("GOTO", token)) {			     /* GOTO */
	    if (next_word)
	        status = LineMode_load(lm, HTMainAnchor, next_word, NO, &req);
	} else
	    found = NO;
	break;
	
      case '?':
	status = LineMode_load(lm, HTMainAnchor, C_HELP, YES, &req);
	break;
	
      case 'H':
	if (CHECK_INPUT("HELP", token)) {		     /* help menu, ..*/
	    status = LineMode_load(lm, HTMainAnchor, C_HELP, YES, &req);
	} else if (CHECK_INPUT("HOME", token)) {		/* back HOME */
	    if (!HTHistory_canBacktrack(lm->history)) {
		HText_scrollTop(HTMainText);
	    } else {
		req = Thread_new(lm, YES, LM_NO_UPDATE);
		status = HTLoadAnchor(HTHistory_find(lm->history, 1), req);
	    }
	} else
	    found = NO;
	break;
	
      case 'K':						 /* Keyword search ? */
	if (is_index && CHECK_INPUT("KEYWORDS", token)) {
	    goto find;
	} else
	    found = NO;
	break;
	
      case 'L':
	if (CHECK_INPUT("LIST", token)) {	     /* List of references ? */
	    Reference_List(lm, !OutSource);
	}
#ifdef HAVE_CHDIR
	else if (CHECK_INPUT ("LCD", token)) {	       /* Local change dir ? */
	  lcd:
	    if (!next_word) {				 /* Missing argument */
		OutputData(lm->pView, "\nName of the new local directory missing.\n");
	    } else if (chdir (next_word)) {		 /* failed : say why */
		OutputData(lm->pView, "\n  ");
		perror (next_word);
	    } else {		    /* Success : display new local directory */
#ifdef HAVE_GETCWD
		OutputData(lm->pView, "\nLocal directory is now:\n %s\n",
			   getcwd (choice, sizeof(choice)));
#else
#ifdef HAVE_GETWD
		OutputData(lm->pView, "\nLocal directory is now:\n %s\n",
			   (char *) getwd (choice));
#else
#error "This platform doesn't support getwd or getcwd"
		if (SHOW_MSG)
		    HTTrace("This platform doesn't support getwd or getcwd\n");
#endif /* HAVE_GETWD */
#endif /* HAVE_GETCWD */
	    }
	}
#endif /* HAVE_CHDIR */
	else if (CHECK_INPUT("LUSER", token)) {	     /* List of references ? */
	    if (next_word) {
	        LoadPICSUser(lm, next_word);
	    } else {
	        HTTrace("URL needed\n");
	    }
	} else
	    found = NO;
	break;
	
      case 'M':
	if (CHECK_INPUT("MANUAL", token)) {		 /* Read User manual */
	    status = LineMode_load(lm, HTMainAnchor, MANUAL, YES, &req);
	} else
	    found = NO;
	break;
	
      case 'O':
	if (CHECK_INPUT("OPTIONS", token)) {		   /* OPTIONS method */
	    req = Thread_new(lm, YES, LM_NO_UPDATE);
	    status = HTOptionsAnchor((HTAnchor*)
				     (HTMainAnchor ? HTMainAnchor:lm->anchor),
				     req);
	} else
	    found = NO;
	break;
	
      case 'P':                    
	if (CHECK_INPUT("POST", token)) {
	    status = PutAnchor(lm, req);
	}

#ifdef HAVE_SYSTEM	    
	else if (!lm->host && CHECK_INPUT("PRINT", token)) {
	    char * address = HTAnchor_address((HTAnchor *) HTMainAnchor);
	    char * command;
	    char * tmplate = (char *) getenv("WWW_PRINT_COMMAND");
	    int result;
	    
	    if (!tmplate) tmplate = "www -n -na -p66 '%s' | lpr";
	    if ((command = (char *) HT_MALLOC(strlen(address)+strlen(tmplate)+20)) == NULL)
	        HT_OUTOFMEM("command");
	    sprintf(command, tmplate, address);
	    result = system(command);
	    HT_FREE(address);
	    HT_FREE(command);
	    if (result) OutputData(lm->pView, "  %s\n  returns %d\n", command, result);
	}
#endif
	/* this command prints the entire current text to the
	   terminal's printer; at the end it displays the top of the text */
#ifdef SLAVE_PRINTER
#define SLAVE_PRINTER_ON  "\033\133\065\151"
#define SLAVE_PRINTER_OFF "\033\133\064\151"
	
	else if (CHECK_INPUT("PS", token)) {
	    OutputData(lm->pView, "%s",SLAVE_PRINTER_ON);
	    OutputData(lm->pView, "\f");			   /* Form feed for new page */
	    HText_scrollTop(HTMainText);
	    while(HText_canScrollDown(HTMainText)) {
		HText_scrollDown(HTMainText);
	    }
	    OutputData(lm->pView, "\f");  			   /* Form feed for new page */
	    OutputData(lm->pView, "%s",SLAVE_PRINTER_OFF);
	    HText_scrollTop(HTMainText);
	}	
#endif
	else if (CHECK_INPUT("PUT", token)) {
	    status = PutAnchor(lm, req);
	} else
	    found = NO;
	break;
	
      case 'Q':						   /* Quit program ? */
	if (CHECK_INPUT("QUIT", token)) {

	    /* 	JFG 9/7/92, following a complaint of 'q' mis-typed for '1'.
		JFG Then made optional because I hate it !!!
		TBL made it only affect remote logged on users. 921122 */
	    
	    if (lm->host && (strcasecomp(token, "quit") != 0) ) {
		OutputData(lm->pView, "\n Please type \"quit\" in full to leave www.\n");
	    } else {
		HTNet_killAll();			/* Kill all requests */
		status = NO;
	    }
	} else
	    found = NO;
	break;
	
      case 'R':	
	if (CHECK_INPUT("RECALL", token)) {
	    if (HTHistory_count(lm->history) <= 1) {
		OutputData(lm->pView, "\n  No other documents to recall.\n");
	    } else {
		/* Previous node number exists, or does the user just */
		/* require a list of nodes visited? */
		if (next_word) {	
		    int cnt;
		    if ((cnt = atoi(next_word)) > 0) {
			req = Thread_new(lm, YES, LM_NO_UPDATE);
			status = HTLoadAnchor(HTHistory_find(lm->history,cnt), req);
		    } else {
			if (SHOW_MSG)
			    HTTrace("Bad command (%s), for list of commands type help\n", this_command);
		    }
		} else {
		    History_List(lm);
		}
	    }
	} else if (CHECK_INPUT("REFRESH", token)) {
	    HText_setStale(HTMainText);			    /* Force refresh */
	    HText_refresh(HTMainText);			   /* Refresh screen */

	} else if (CHECK_INPUT("REVALIDATE", token)) {
	    req = Thread_new(lm, YES, LM_NO_UPDATE);

	    /*
	    **  Add a end-to-end validator to the request
	    */
	    HTRequest_setReloadMode(req, HT_CACHE_END_VALIDATE);
	    status = HTLoadAnchor((HTAnchor*) (HTMainAnchor ? HTMainAnchor : 
					       lm->anchor), req);
	} else if (CHECK_INPUT("RELOAD", token)) {
	    req = Thread_new(lm, YES, LM_NO_UPDATE);

	    /*
	    **  Make sure that we do a complete flush of the cache
	    */
	    HTRequest_setReloadMode(req, HT_CACHE_FLUSH);
	    status = HTLoadAnchor((HTAnchor*) (HTMainAnchor ? HTMainAnchor : 
				  lm->anchor), req);
	} else
	    found = NO;
	break;
	
      case 'S':						       /* TBL 921009 */
	if (CHECK_INPUT("SOURCE", token)) {    	  	  /* Apply to source */
	    if (next_word) {
		OutSource = YES;		 /* Load and print as source */
		token = next_word;		         /* Move up one word */
		next_word = strtok (NULL, " \t\n\r");
		this_command = the_choice + (token - choice);
		other_words = the_choice + (next_word - choice);
		goto loop;	                       /* Go treat as before */
	    }
	} else if (CHECK_INPUT("SET", token)) {        	           /* config */
	    HTList * rules = HTRule_global();
	    HTRule_parseLine(rules, other_words);
	} else
	    found = NO;
	break;
	
      case 'T':
	if (CHECK_INPUT("TOP", token)) {		   /* Return to top  */
	    HText_scrollTop(HTMainText);
	} else
	    found = NO;
	break;
	
      case 'U':
	if (CHECK_INPUT("UP", token)) {		      /* Scroll up one page  */
	    HText_scrollUp(HTMainText);
	} else if (CHECK_INPUT("USER", token)) {
	    if (next_word) {
			if (!strcasecomp(next_word, "?")) {
				ShowPICSUsers(lm);
				SetPICSUser(lm, 0);
			} else {
				SetPICSUser(lm, next_word);
			}
	    } else {
	        SetPICSUser(lm, 0);
		}
	} else 
	    found = NO;
	break;
	
      case 'V':
	if (CHECK_INPUT("VALIDATE", token)) {    	   /* Cache validate */
	    req = Thread_new(lm, YES, LM_NO_UPDATE);

	    /*
	    **  Add a validator to the request
	    */
	    HTRequest_setReloadMode(req, HT_CACHE_VALIDATE);
	    status = HTLoadAnchor((HTAnchor*) (HTMainAnchor ? HTMainAnchor : 
					       lm->anchor), req);
	} else if (CHECK_INPUT("VERBOSE", token)) {  /* Switch verbose mode  */
	    WWWTRACE = WWWTRACE ? 0 : lm->trace;
	    OutputData(lm->pView, "\n  Verbose mode %s.\n", WWWTRACE ? "ON":"OFF");
	} else if (CHECK_INPUT("VERSION", token)) {	 	  /* Version */
	    VersionInfo(lm);
	} else
	    found = NO;
	break;

      case 'W':
	if (CHECK_INPUT("WHICH", token)) {     /* Show title/URL of current page */
	    HTView * pView = lm ? lm->pView : NULL;
	    char * current_address = HTAnchor_address((HTAnchor *) HTMainAnchor);
	    const char * title = HTAnchor_title(HTMainAnchor);
	    if (title)
		OutputData(pView, "\n\nYou are reading\n\t`%s\'\nwith address\n\t%s\n\n",
			   title, current_address);
	} else
	    found = NO;
	break;
	
      case 'Z':
	HText_setStale(HTMainText);			    /* Force refresh */
	HText_refresh(HTMainText);			   /* Refresh screen */
	HTNet_killAll();				/* Kill all requests */
	break;

      case '>':
	if (!lm->host) {
	    HText *curText = HTMainText;     /* Remember current main vindow */
	    req = Thread_new(lm, NO, LM_NO_UPDATE);
	    HTRequest_setReloadMode(req, HT_CACHE_FLUSH_MEM);
	    if (OutSource) HTRequest_setOutputFormat(req, WWW_SOURCE);
	    SaveOutputStream(req, token, next_word);
	    HText_select(curText);
	}
	break;
	
#ifdef GOT_PIPE
      case '|':
	if (!lm->host) {	             	           /* Local only!!!! */
	    char * address = HTAnchor_address((HTAnchor *) HTMainAnchor);
	    char * command;
	    int result;
	    if ((command = (char *) HT_MALLOC(strlen(address) +strlen(this_command)+30)) == NULL)
	        HT_OUTOFMEM("command");
	    sprintf(command, "www %s \"%s\" %s", 
		    OutSource ? "-source" : "-n -na -p", address,this_command);
	    OutputData(lm->pView, "Command: %s\n", command);
	    result = system(command);
	    if (result) OutputData(lm->pView, "  %s  returns %d\n", command, result);
	    HT_FREE(command);
     	    HT_FREE(address);
	}
	break;
#endif
	    
#ifdef HAVE_SYSTEM
      case '!':
	if (!lm->host) {				      /* Local only! */
	    int result;
	    if (SHOW_MSG) HTTrace("Executing `%s\'\n", this_command);
	    result = system(strchr(this_command, '!') + 1);
	    if (result) OutputData(lm->pView, "  %s  returns %d\n",
				 strchr(this_command, '!') + 1, result);
	}
	break;
#endif /* HAVE_SYSTEM */
      default:
	found = NO;
	break;
    } /* Switch on 1st character */

    if (!found) {
	if (is_index && *token) {  /* No commands, search keywords */
	    next_word = other_words = this_command;
	    found = YES;
	    goto find;
	} else {             
	    if (SHOW_MSG)
		HTTrace("Bad command (%s), for list of commands type help\n", this_command);
	}
    }
    MakeCommandLine(lm, is_index);
    HT_FREE(the_choice);

    /* 
    ** If we have created a new Request and is to update the history list then
    ** we can set the inactive bit on this request object.
    */
    if (cur_req == req)
	cur_context->state |= LM_NO_UPDATE;
    else
	cur_context->state |= LM_INACTIVE;    
    return (status==YES) ? HT_OK : HT_ERROR;
}

/*	readConsole
**	-----------
**	non-blocking read of the WIN32 console. EGP
*/

#ifdef WWW_WIN_CONSOLE
PUBLIC BOOL readConsole(HANDLE conIn, char* buf, int len, int* pRed)
{
    DWORD recordIndex, bufferIndex, toRead, red;
    PINPUT_RECORD pInput;

    /* grab the pending input records (keystrokes plus other garbage). */
    GetNumberOfConsoleInputEvents(conIn, &toRead);
    if (len < (int)toRead)	/* we'll get the rest on the next pass(es). */
    toRead = len;
    if ((pInput = (PINPUT_RECORD) HT_MALLOC(toRead * sizeof(INPUT_RECORD))) == NULL)	/* room for n input records */
	return (FALSE);
    ReadConsoleInput(conIn, pInput, toRead, &red);

    for (recordIndex = bufferIndex = 0; recordIndex < red; recordIndex++) {
	/* grab all keydown events */
#if 1
        KEY_EVENT_RECORD keyEvent = pInput[recordIndex].Event.KeyEvent;     /* only used if EventType == KEY_EVENT */
    	if (pInput[recordIndex].EventType == KEY_EVENT && keyEvent.bKeyDown) {
    	    while (keyEvent.wRepeatCount && keyEvent.uChar.AsciiChar) {
    		/* stuff the buffer with the keys */
    		buf[bufferIndex] = keyEvent.uChar.AsciiChar;
    		if (buf[bufferIndex] == '\r')
    		    buf[bufferIndex] = '\n';
    		if (buf[bufferIndex] == '\b')
    		    OutputData(lm->pView, "\b ");
    		OutputData(lm->pView, "%c", buf[bufferIndex]);
    		bufferIndex++;
    		keyEvent.wRepeatCount--;
    	    }
    	}
#else
    	if (pInput[recordIndex].EventType == KEY_EVENT && pInput[recordIndex].Event.KeyEvent.bKeyDown) {
    	    while (pInput[recordIndex].Event.KeyEvent.wRepeatCount && pInput[recordIndex].Event.KeyEvent.uChar.AsciiChar) {
    		/* stuff the buffer with the keys */
    		buf[bufferIndex] = pInput[recordIndex].Event.KeyEvent.uChar.AsciiChar;
    		if (buf[bufferIndex] == '\r')
    		    buf[bufferIndex] = '\n';
    		if (buf[bufferIndex] == '\b')
    		    OutputData(lm->pView, "\b ");
    		OutputData(lm->pView, "%c", buf[bufferIndex]);
    		bufferIndex++;
    		pInput[recordIndex].Event.KeyEvent.wRepeatCount--;
    	    }
    	}
#endif

    }

    HT_FREE(pInput);
    *pRed = bufferIndex;		/* actual characters stuck into buffer */
    return (TRUE);
}
#endif /* WWW_WIN_CONSOLE */

/*	bufferInput
**	-----------
**	Read available characters from buf into stat. buf maybe bigger or
**	smaller than stat.
*/
PUBLIC int bufferInput (char* buf, int len, SOCKET s, HTRequest * req, HTEventType type)
{
    static char stat[RESPONSE_LENGTH];
    static int iStat = 0;
    static int ignoreNext = 0;
    int iBuf;
    for (iBuf = 0; iBuf < len; iBuf++) {
	switch (buf[iBuf]) {
	    case '\r':
	    case '\n':
		if (ignoreNext)
		    ignoreNext = 0;
		else {
		    int ret;
		    stat[iStat] = 0;
		    iStat = 0;
		    if ((ret = (*PInputParser)(stat, s, req, type)) != HT_OK)
			return (ret);
		}
		break;
	    case '\b':
		if (iStat) /* don't worry about ignoreNext as iStat will be 0*/
		    iStat--;
		break;
	    default:
		if (!ignoreNext)
		    stat[iStat++] = buf[iBuf];
	}
	if (iStat == sizeof(stat)) {
	    HTTrace("Read Console... BUFFER OVERRUN\n");
	    iStat = 0;
	    ignoreNext = 1;
	}
    }
    return (HT_OK);
}

/*	timeout_handler
**	---------------
**	This function is registered to handle timeout in select eventloop
*/
PRIVATE int timeout_handler (SOCKET s, void * param, HTEventType type)
{
    if (!HTAlert_interactive()) {
	HTRequest * req = (HTRequest *) param;
	Context * context = (Context *) HTRequest_context(req);
	LineMode * lm = context->lm;
	if (SHOW_MSG) HTTrace("Request timed out");
	HTNet_killAll();
	Cleanup(lm, -1);
    }
    if (HTNet_count() > 0)
	if (SHOW_MSG) HTTrace(".");
    return 0;
}

PRIVATE int scan_command (SOCKET s, void * param, HTEventType type)
{
    HTRequest * req = (HTRequest *)param;
    /* buf happens to == eatText's buffer but not neccesary */
    static char buf[RESPONSE_LENGTH];
#ifdef WWW_MSWINDOWS
    int red;
    int ret;
#endif

    /* Handle any timeout here */
    if (type == HTEvent_TIMEOUT) return timeout_handler (s, param, type);

#ifdef WWW_MSWINDOWS
    while(1) {
#ifdef WWW_WIN_CONSOLE
	if (!readConsole((HANDLE)s, buf, sizeof(buf), &red)) {
        if (PROT_TRACE) HTTrace("Read Console... READ ERROR\n");
	    return HT_ERROR;
	}
#endif /* WWW_WIN_CONSOLE */
	if (!red) return (HT_OK);
	ret = bufferInput(buf, red, s, req, type);
	if (ret != HT_OK) return (ret);
    }
#else /* WWW_MSWINDOWS */
    if (!fgets(buf, sizeof(buf), stdin))		  /* Read User Input */
        return HT_ERROR;				      /* Exit if EOF */
    return ((*PInputParser)(buf, s, req, type));
#endif /* !WWW_MSWINOWS */
}

/*	terminate_handler
**	-----------------
**	This function is registered to handle the result of the request
*/
PRIVATE int terminate_handler (HTRequest * request, HTResponse * response,
			       void * param, int status) 
{
    Context * context = (Context *) HTRequest_context(request);
    LineMode * lm;
    BOOL is_index;

    if (!context)
        return HT_OK;
    lm = context->lm;
    if (context->state == LM_IGNORE) return HT_OK;
    if (CSApp_unregisterReq(request) == NO && lm->pCSUser)
        HTTrace("PICS request not found\n");
    is_index = HTAnchor_isIndex(HTMainAnchor);
    if (status == HT_LOADED) {

	/*
	**  Make sure that we have selected the HText object. This is normally
	**  done by the HText interface but may have been avoided by the mem
	**  cache filter
	*/
	{
	    HTParentAnchor * parent = HTRequest_anchor(request);
	    HTChildAnchor * child = HTRequest_childAnchor(request);
	    HText * document =  HTAnchor_document(parent);
	    if (child && (HTAnchor *) child != (HTAnchor *) parent)
		HText_selectAnchor(document, child);
	    else
		HText_select(document);
	    HTRequest_forceFlush(request);
	}

	/* Should we output a command line? */
	if (HTAlert_interactive()) {
	    HText_setStale(HTMainText);
	    MakeCommandLine(lm, is_index);
	} else {
	    if (lm->flags & LM_REFS) Reference_List(lm, NO);
	    Cleanup(lm, 0);
	}

	/* Record new history if we have not moved around in the old one */
	if (context->state & LM_UPDATE)
	    HTHistory_replace(lm->history, (HTAnchor *) HTMainAnchor);

	/* Now generate the new prompt line as a function of the result */
	if (!HText_canScrollDown(HTMainText) &&
	    !HTAnchor_hasChildren(HTMainAnchor) && !is_index &&
	    (!HTHistory_canBacktrack(lm->history))) {
	    return HT_OK;
	}

    } else { /* No page loaded so sit around and wait for a go command */
	/*	was MakeCommandLine(lm, is_index); */
	/*
	**	stolen from above
	*/
	if (HTAlert_interactive()) {
/*	    HText_setStale(HTMainText); */
	    MakeCommandLine(lm, is_index);
	} else {
	    if (lm->flags & LM_REFS) Reference_List(lm, NO);
	    Cleanup(lm, 0);
	}

    }
    context->state |= LM_DONE;
    Thread_cleanup(lm);
    if (!HTAlert_interactive()) Cleanup(lm, -1);
    return HT_OK;
}

/* ------------------------------------------------------------------------- */
/*				  MAIN PROGRAM				     */
/* ------------------------------------------------------------------------- */

int main (int argc, char ** argv)
{
    int		status = 0;	
    int		arg;			       		  /* Index into argv */
    HTChunk *	keywords = NULL;			/* From command line */
    int		keycnt = 0;
    HTRequest *	request = NULL;
    LineMode *	lm;
    char *      picsUser = NULL;

    /* Starts Mac GUSI socket library */
#ifdef GUSI
    GUSISetup(GUSIwithSIOUXSockets);
    GUSISetup(GUSIwithInternetSockets);
#endif

#ifdef __MWERKS__ /* STR */
    InitGraf((Ptr) &qd.thePort); 
    InitFonts(); 
    InitWindows(); 
    InitMenus(); TEInit(); 
    InitDialogs(nil); 
    InitCursor();
    SIOUXSettings.asktosaveonclose = false;
    argc=ccommand(&argv);
#endif

    /* HWL 18/7/94: patch from agl@glas2.glas.apc.org (Anton Tropashko) */
#ifdef CYRILLIC
    arc.locale=0; arc.encoding=0; arc.i_encoding=0; doinull();
#endif

#ifdef HT_MEMLOG
    HTMemLog_open(DEFAULT_MEMLOG, 8192, YES);
    HTTraceData_setCallback(HTMemLog_callback);
#endif

    /* Initiate W3C Reference Library with a client profile */
    HTProfile_newClient(APP_NAME, APP_VERSION);
    
    /* It's confusing to have progress notofications in linemode browser */
    HTAlert_delete(HTProgress);

    /* Add the default HTML parser to the set of converters */
    {
	HTList * converters = HTFormat_conversion();
	HTMLInit(converters);
    }

    /* Create a new Line Mode object */
    lm = LineMode_new();
    request = Thread_new(lm, NO, LM_UPDATE);

    /* Scan command Line for parameters */
    for (arg=1; arg<argc ; arg++) {
	if (*argv[arg] == '-') {

	    /* - alone => filter */
	    if (argv[arg][1] == '\0') {
		lm->flags |= LM_FILTER;
		HTAlert_setInteractive(NO);
	    
	    /* non-interactive */
	    } else if (!strcmp(argv[arg], "-n")) {
		HTAlert_setInteractive(NO);

	    /* from -- Initial represntation (only with filter) */
	    } else if (!strcmp(argv[arg], "-from")) {
		lm->format = (arg+1 < argc && *argv[arg+1] != '-') ?
		    HTAtom_for(argv[++arg]) : WWW_HTML;
		HTAlert_setInteractive(NO);

	    /* to -- Final representation */
	    } else if (!strcmp(argv[arg], "-to")) {
		HTFormat format = (arg+1 < argc && *argv[arg+1] != '-') ?
		    HTAtom_for(argv[++arg]) : DEFAULT_FORMAT;
		HTRequest_setOutputFormat(request, format);
		HTAlert_setInteractive(NO);

	    /* reformat html */
	    } else if (!strcmp(argv[arg], "-reformat")) {
		HTRequest_setOutputFormat(request, WWW_HTML);
		lm->flags |= LM_REFORMAT;
		HTAlert_setInteractive(NO);

	    /* List References */
	    } else if (!strncmp(argv[arg], "-list", 5)) {
		lm->flags |= LM_REFS;
		HTAlert_setInteractive(NO);

	    /* original output */
	    } else if (!strcmp(argv[arg], "-raw")) {
		HTRequest_setOutputFormat(request, WWW_RAW);
		HTAlert_setInteractive(NO);

	    /* source please */
	    } else if (!strcmp(argv[arg], "-source")) {
		HTRequest_setOutputFormat(request, WWW_SOURCE);
		HTAlert_setInteractive(NO);

	    /* HEAD method */
	    } else if (!strcasecomp(argv[arg], "-head")) {
		HTRequest_setMethod(request, METHOD_HEAD);
		HTRequest_setOutputFormat(request, WWW_MIME);
		HTAlert_setInteractive(NO);

	    /* output filename */
	    } else if (!strcmp(argv[arg], "-o")) { 
		lm->outputfile = (arg+1 < argc && *argv[arg+1] != '-') ?
		    argv[++arg] : DEFAULT_OUTPUT_FILE;
		HTAlert_setInteractive(NO);

	    /* print version and exit */
	    } else if (!strcmp(argv[arg], "-version")) { 
		VersionInfo(lm);
		Cleanup(lm, 0);

	    /* -? or -help: show the command line help page */
	    } else if (!strcmp(argv[arg],"-?") || !strcmp(argv[arg],"-help")) {
		lm->anchor = (HTParentAnchor *) HTAnchor_findAddress(L_HELP);
		keycnt = 1;

#ifdef CYRILLIC
	    /* HWL 18/7/94: applied patch from agl@glas2.glas.apc.org
	       (Anton Tropashko) */
	    } else if (!strcmp(argv[arg], "-koi2alt")) {
	        doia2k(); OutputData(lm->pView, "Ahak2a!");
#endif

	    /* Page size */
	    } else if (!strncmp(argv[arg], "-p", 2)) {
		if (*(argv[arg]+2)) {
		    if (sscanf(argv[arg]+2, "%d", &HTScreenHeight) < 1)
			HTScreenHeight = -1;		
		    else {
			if(HTScreenHeight < MIN_SCREEN_HEIGHT)
			    HTScreenHeight = MIN_SCREEN_HEIGHT;
			if(HTScreenHeight > MAX_SCREEN_HEIGHT)
			    HTScreenHeight = MAX_SCREEN_HEIGHT;
		    }
		} else if (arg+1 < argc && *argv[arg+1] != '-') {
		    if (sscanf(argv[++arg], "%d", &HTScreenHeight) < 1)
			HTScreenHeight = -1;
		    else {
			if(HTScreenHeight < MIN_SCREEN_HEIGHT)
			    HTScreenHeight = MIN_SCREEN_HEIGHT;
			if(HTScreenHeight > MAX_SCREEN_HEIGHT)
			    HTScreenHeight = MAX_SCREEN_HEIGHT;
		    }
		}

	    /* Page width */
	    } else if (!strncmp(argv[arg], "-w", 2)) {
		if (*(argv[arg]+2)) {
		    if (sscanf(argv[arg]+2, "%d", &HTScreenWidth) < 1)
			HTScreenWidth = SCREEN_WIDTH;
		} else if (arg+1 < argc && *argv[arg+1] != '-') {
		    if (sscanf(argv[++arg], "%d", &HTScreenWidth) < 1)
			HTScreenWidth = SCREEN_WIDTH;
		}
		if(HTScreenWidth < MIN_SCREEN_WIDTH)
		    HTScreenWidth = MIN_SCREEN_WIDTH;
		if(HTScreenWidth > MAX_SCREEN_WIDTH)
		    HTScreenWidth = MAX_SCREEN_WIDTH;
		    
	    /* Telnet from */
	    } else if (!strcmp(argv[arg], "-h")) {
		if (arg+1 < argc && *argv[arg+1] != '-') {
		    lm->host = argv[++arg]; 		    /* Use host name */
		    HTLib_setSecure(YES);		   /* No easy access */
		}

	    /* log file */
	    } else if (!strcmp(argv[arg], "-l")) {
		lm->logfile = (arg+1 < argc && *argv[arg+1] != '-') ?
		    argv[++arg] : DEFAULT_LOG_FILE;

	    /* rule file */
	    } else if (!strcmp(argv[arg], "-r")) {
		lm->rules = (arg+1 < argc && *argv[arg+1] != '-') ?
		    argv[++arg] : DEFAULT_RULE_FILE;

	    /* timeout -- Change the default request timeout */
	    } else if (!strcmp(argv[arg], "-timeout")) {
		int timeout = (arg+1 < argc && *argv[arg+1] != '-') ?
		    atoi(argv[++arg]) : -1;
		if (timeout > 0) lm->timer = timeout;

	    /* PICS user */
	    } else if (!strcmp(argv[arg], "-u")) {
		picsUser = (arg+1 < argc && *argv[arg+1] != '-') ?
		    argv[++arg] : "user";
	    } else if (!strcmp(argv[arg], "-unp")) {
	        lm->noPICSPasswords = 1;
#if 0 /* old version of user list file, still works when enabeled here */
	    /* PICS user list */
	    } else if (!strcmp(argv[arg], "-ul")) {
		picsUserList = (arg+1 < argc && *argv[arg+1] != '-') ?
		    argv[++arg] : "users.url";
#endif
	    /* PICS user list */
	    } else if (!strcmp(argv[arg], "-ul")) {
		lm->userList = (arg+1 < argc && *argv[arg+1] != '-') ?
		    argv[++arg] : DEFAULT_USERLIST_FILE;

	    /* PICS mode */
	    } else if (!strcmp(argv[arg], "-pics")) {
		lm->userList = DEFAULT_USERLIST_FILE;

	    /* preemptive or non-preemptive access */
	    } else if (!strcmp(argv[arg], "-single")) {
		HTRequest_setPreemptive(request, YES);
		lm->flags |= LM_PREEMTIVE;

	    /* Disconnected operation */
	    } else if (!strncmp(argv[arg], "-discon", 7)) {
		HTCacheMode_setDisconnected(HT_DISCONNECT_NORMAL);

	    /* Handling of Expire (cache) */
	    } else if (!strncmp(argv[arg], "-x", 2)) { 
	    	  char *p = argv[arg]+2;
		  for(;*p;p++) {
		      switch (argv[arg][2]) {
		      case 'i':
			  HTCacheMode_setExpires(HT_EXPIRES_IGNORE);
			  break;
		      case 'n':
			  HTCacheMode_setExpires(HT_EXPIRES_NOTIFY);
			  break;
		      case 'a':
			  HTCacheMode_setExpires(HT_EXPIRES_AUTO);
			  break;
		      default:
			  if (SHOW_MSG)
			      HTTrace("Bad parameter (%s) for option -x\n", argv[arg]);
			  break;
		      }
		  }

	    /* Anchor format */
	    } else if (!strcmp(argv[arg], "-a")) { 
		if (arg+1 < argc && *argv[arg+1] != '-')
		    end_reference = argv[++arg];      /* New representation */

	    /* Anchor format */
	    } else if (!strcmp(argv[arg], "-ar")) { 
		if (arg+1 < argc && *argv[arg+1] != '-')
		    reference_mark = argv[++arg];  /* Change representation */

	    /* Anchor format */
	    } else if (!strcmp(argv[arg], "-as")) { 
		if (arg+1 < argc && *argv[arg+1] != '-')
		    start_reference = argv[++arg]; /* Change representation */

	    /* No anchors */
	    } else if (!strcmp(argv[arg], "-na")) { 
		    display_anchors = NO;

#ifndef NO_DIR_OPTIONS
	    } else if (!strncmp(argv[arg], "-d", 2)) {
	    	char *p = argv[arg]+2;
		for(;*p;p++) {
		    switch (argv[arg][2]) {
		      case 'r':HTFile_setDirReadme(HT_DIR_README_NONE); break;
		      case 't':HTFile_setDirReadme(HT_DIR_README_TOP); break;
		      case 'b':HTFile_setDirReadme(HT_DIR_README_BOTTOM);break;
		      case 'n':HTFile_setDirAccess(HT_DIR_FORBID); break;
		      case 's':HTFile_setDirAccess(HT_DIR_SELECTIVE); break;
		      case 'y':HTFile_setDirAccess(HT_DIR_OK); break;
		      default:
			if (SHOW_MSG)
			    HTTrace("Bad parameter (%s) in -d option\n",
				    argv[arg]);
		    }
		}
#endif

#ifdef WWWTRACE
	    /* trace flags */
	    } else if (!strncmp(argv[arg], "-v", 2)) {
		lm->trace = HTSetTraceMessageMask(argv[arg]+2);
#endif
	    } else {
		if (SHOW_MSG) HTTrace("Bad Argument (%s)\n", argv[arg]);
	    }
	} else {	   /* If no leading `-' then check for main argument */
    	    if (!keycnt) {
		char * ref = HTParse(argv[arg], lm->cwd, PARSE_ALL);
		lm->anchor = HTAnchor_parent(HTAnchor_findAddress(ref));
		keycnt = 1;
		HT_FREE(ref);
	    } else {		   /* Check for successive keyword arguments */
		char *escaped = HTEscape(argv[arg], URL_XALPHAS);
		if (keycnt++ <= 1)
		    keywords = HTChunk_new(128);
		else
		    HTChunk_putc(keywords, ' ');
		HTChunk_puts(keywords, HTStrip(escaped));
		HT_FREE(escaped);
	    }
	}
    }

#ifdef CATCH_SIG
    SetSignal();
#endif

    /* Make home page address */
    if (!lm->anchor) lm->anchor = HTHomeAnchor();

    /* Do we need list of presenters? (external viewers) */
    if (HTAlert_interactive()) {
	lm->presenters = HTList_new();
	HTPresenterInit(lm->presenters);
	HTRequest_setConversion(request, lm->presenters, NO);
    }

    if (HTScreenHeight == -1) {				/* Default page size */
	if (HTAlert_interactive())
	    scrsize(&HTScreenHeight, &HTScreenWidth);
	else
	    HTScreenHeight = 999999;
    }

    /* Disable free directory browsing when using telnet host */
    if (lm->host && HTFile_dirAccess() == HT_DIR_OK)
	HTFile_setDirAccess(HT_DIR_SELECTIVE);

    /* Open output file */
    if (!HTAlert_interactive()) {
#ifndef WWW_WIN_WINDOW
	if (lm->outputfile) {
	    if ((OUTPUT = fopen(lm->outputfile, "wb")) == NULL) {
		if (SHOW_MSG) HTTrace("Can't open `%s'\\n",
				       lm->outputfile);
		OUTPUT = stdout;
	    }
	}
	HTRequest_setOutputStream(request,
				  HTFWriter_new(request, OUTPUT, YES));
#endif

	/*
	** To reformat HTML, just put it through a parser running
	** into a regenerator   tbl 940613
	*/
	if (lm->flags & LM_REFORMAT) {
	    HTStructured * html =
		HTMLGenerator(request, NULL, WWW_HTML,
			      HTRequest_outputFormat(request),
			      HTRequest_outputStream(request));
	    HTRequest_setOutputStream(request, SGML_new(&HTMLP_dtd, html));
	}
    }
    
     /* Log file specifed? */
    if (lm->logfile) {
	lm->log = HTLog_open(lm->logfile, YES, YES);
        if (lm->log) HTNet_addAfter(HTLogFilter, NULL, lm->log, HT_ALL, HT_FILTER_LATE);
    }

    /* Just convert formats */
    if (lm->flags & LM_FILTER) {
#ifdef STDIN_FILENO
	HTRequest_setAnchor(request, (HTAnchor *) lm->anchor);
	HTRequest_setPreemptive(request, YES);
	HTLoadSocket(STDIN_FILENO, request);
#endif
	Cleanup(lm, 0);
    }

    /* Set timeout on sockets */
    if (lm->timer < 0) {
	lm->timer = HTAlert_interactive() ?
	    DEFAULT_I_TIMEOUT : DEFAULT_NI_TIMEOUT;
    }
    /* Set the DNS cache timeout */
    HTDNS_setTimeout(3600);

    /* Rule file specified? */
    if (lm->rules) {
	char * rules = HTParse(lm->rules, lm->cwd, PARSE_ALL);
	if (!HTLoadRules(rules))
	    if (SHOW_MSG) HTTrace("Can't access rules\n");
	HT_FREE(rules);
    }

    /* Set up PICS machinary */
    CSApp_registerApp(PICSCallback, CSApp_callOnBad, PICS_userCallback, 
		      (void *)lm);
     /* Add our own filter to update the history list */
    HTNet_addAfter(terminate_handler, NULL, NULL, HT_ALL, HT_FILTER_LAST);

    /* Should we load a PICS user profile? */
    if (picsUser && !LoadPICSUser(lm, picsUser))
        HTTrace("Unable to load PICS user \"%s\".\n", picsUser);

    /* PICS user list specified? */
    if (lm->userList)
        LoadPICSUser(lm, lm->userList);

    /* request was registered in Thread_new before command line parsing
       loaded the PICS user, so re-register to give it the correct user */
    CSApp_unregisterReq(request);
    CSApp_registerReq(request, lm->pCSUser, PICSCallback, 
		      CSApp_callOnBad, (void *)lm);

    /* Start the request */
    if (keywords)
	status = HTSearchAnchor(keywords, (HTAnchor *) lm->anchor, request);
    else
	status = HTLoadAnchor((HTAnchor *) lm->anchor, request);

    if (keywords) HTChunk_delete(keywords);
    if (status != YES) {
	if (SHOW_MSG) HTTrace("Couldn't load home page\n");
	CSApp_unregisterDefaultUser();
	CSApp_unregisterApp();
	Cleanup(lm, -1);
    }

    /* Set up the rest if we are in interactive mode */
    if (HTAlert_interactive()) {

	/* Start History manager */
	lm->history = HTHistory_new();

	/*
	** Register STDIN as the user socket IF not STDIN is connected to
	** /dev/null or other non-terminal devices
	*/
	ConsoleEvent = HTEvent_new(scan_command, lm->console, HT_PRIORITY_MAX, lm->timer);
#ifdef STDIN_FILENO
	if (isatty(STDIN_FILENO)) {
	    HTEventList_register(STDIN_FILENO, HTEvent_READ, ConsoleEvent);
	}
#else
	HTEventList_register(GetStdHandle(STD_INPUT_HANDLE), HTEvent_READ, ConsoleEvent);
#endif
    }

    /* Go into the event loop... */
    HTEventList_loop(request);

    /* Only gets here if event loop fails */
    Cleanup(lm, 0);
    return 0;
}

