#ifdef TEST
#include <stdio.h>
#include <string.h>
#include <time.h>
#define NULLCHAR ((char *)0)
#define NULLFILE ((FILE *)0)
#define callocw calloc
#define SCREENwidth 80
char *strlwr(char *s);
void rip(register char *s);
void kwait (void *);
#define BROWSER 1
#else
#include "global.h"
#include "socket.h"
#include "netuser.h"
#include "commands.h"
#include "session.h"
#include "files.h"
#ifdef MSDOS
#include <conio.h>
#include "ctype.h"
#endif
#endif
#include "browser.h"


#if !defined(_lint) && !defined(TEST)
static char rcsid[] OPTIONAL = "$Id: browser.c,v 1.12 1997/09/07 00:31:16 root Exp $";
#endif

#ifdef BROWSER
struct parseparams {
	int	title_input;
	int	titleindex;
	char	title[128];
	int	marquee_input;
	int	marqueeindex;
	char	marquee[1024];
	char	buf[512];
	int	inword;
	int	index;
	int	lastwhite;
	int	eat_semicolon;
	int	preformatted;
	int	nowrap;
	int	center;
	int	center_one;
	int	listlevel;
	int	listcount[10];
	int	listtype[10];
};


#define OP_UNKNOWN	-1
#define OP_A		1
#define OP_BR		2
#define OP_CAPTION	3
#define OP_CENTER	4
#define OP_CONSUME	5
#define OP_DD		6
#define OP_DL		7
#define OP_DT		8
#define OP_FORM		9
#define OP_FRAME	10
#define OP_HEADER	11
#define OP_H1		12
#define OP_HR		13
#define OP_IGNORE	14
#define OP_IMAGE	15
#define OP_INPUT	16
#define OP_ISINDEX	17
#define OP_LI		18
#define OP_MARQUEE	19
#define OP_OL		20
#define OP_P		21
#define OP_PRE		22
#define OP_TABLE	23
#define OP_TD		24
#define OP_TH		25
#define OP_TITLE	26
#define OP_TR		27
#define OP_UL		28


static struct opcodes	{
	const char *opcode;
	int value;
} supported_opcodes[] = {
	{ "!--",	OP_IGNORE },	/* not a real tag, used by HTML comments */
	{ "a", 		OP_A },
	{ "br",		OP_BR },
	{ "caption",	OP_CAPTION },
	{ "center",	OP_CENTER },
	{ "comment",	OP_CONSUME },
	{ "dd",		OP_DD },
	{ "dl",		OP_DL },
	{ "dt",		OP_DT },
	{ "dir",	OP_UL },
	{ "form",	OP_FORM },
	{ "frame",	OP_FRAME },
	{ "h1",		OP_H1 },
	{ "h2",		OP_HEADER },
	{ "h3",		OP_HEADER },
	{ "h4",		OP_HEADER },
	{ "h5",		OP_HEADER },
	{ "h6",		OP_HEADER },
	{ "hr",		OP_HR },
	{ "img",	OP_IMAGE },
 	{ "input",	OP_INPUT },
	{ "isindex",	OP_ISINDEX },
	{ "li",		OP_LI },
	{ "marquee",	OP_MARQUEE },
	{ "menu",	OP_UL },
	{ "ol",		OP_OL },
	{ "p", 		OP_P },
	{ "pre",	OP_PRE },
	{ "script",	OP_CONSUME },
	{ "table",	OP_TABLE },
	{ "td",		OP_TD },
	{ "th",		OP_TH },
	{ "title",	OP_TITLE },
	{ "tr",		OP_TR },
	{ "ul",		OP_UL },
	{ NULLCHAR,	OP_UNKNOWN }
};


#define INPUT_UNKNOWN	-1
#define INPUT_CHECKBOX	1
#define INPUT_HIDDEN	2
#define INPUT_IMAGE	3
#define INPUT_PASSWORD	4
#define INPUT_RADIO	5
#define INPUT_RESET	6
#define INPUT_SUBMIT	7
#define INPUT_TEXT	8


static struct opcodes supported_inputs[] = {
	{ "checkbox",	INPUT_CHECKBOX },
	{ "hidden",	INPUT_HIDDEN },
	{ "image",	INPUT_IMAGE },
	{ "password", 	INPUT_PASSWORD },
	{ "radio",	INPUT_RADIO },
	{ "reset",	INPUT_RESET },
	{ "submit",	INPUT_SUBMIT },
	{ "text",	INPUT_TEXT },
	{ NULLCHAR,	INPUT_UNKNOWN }
};


#define NBSP	300	/* special 'char' value for a non-breakable space */
#define SECS_IN_6MONTHS 15768000 /* seconds in 1/2 year */

static char HLINE[] = "--------------------------------------------------------------------------";

static const char *HEADLINENewsHost = "www.lantz.com";
static const char *HEADLINENewsUrl = "/tnos/headline_news.html";


static void scroll_marquee (struct browser *br);
static int gethead (const char *url, char *location, char *modified, char *content);
static char *rebuild_url (struct browser *br, char *cp);
static char *get_tag_data (struct browser *br, FILE *fp, char **cur_url, struct tag **cur_tag);
static void parse_and_format_table (struct browser *br, struct parseparams *p, FILE *fp, int border);
static void trim_trailing_white (char *str);
static int disect_url (char *url, char *type, char *host, int *port, char *component);
static char *build_url (struct browser *br, char *type, char *host, int port, char *component);
static void parse_url (struct browser *br, FILE *fp, int headersexist);
static int opcode_lookup (struct opcodes *op, char *opcode);
static void add_char (struct parseparams *p, struct browser *br, int c);
static void break_line (struct browser *br, struct parseparams *p);
static void carryover_check (struct browser *br, struct parseparams *p);
static char *alloc_string (struct browser *br, const char *str, int centerit);
#ifndef TEST
static int http_connect (char const *host, int port, struct sockaddr_in *fsocket);
static void share_cookies (int s, char const *host, char const *component);
static void add_cookie (char *host, char *path, time_t expires, char *name, char *value);
static int get_url (struct browser *br, const char *url, const char *post, int postmode, char *enctype, int storeit);
static void blank_status (struct browser *br);
static void send_blank_lines (int count);
static int http_url_to_file (struct browser *br, char *refered, char const *host, int port, char const *component, char *auth, FILE *fp, const char *post, int postmode, char *enctype, int quiet);
static void clear_browser (struct browser *br);
static void render_screen (struct browser *br);
static void spawn_telnet (int port, void *host, void *p);
static void spawn_ftp (int unused, void *host, void *file);
static void send_mailto (char *addr);
static void goto_stat (struct browser *br, int plusoffset);
static void add_string (struct parseparams *p, struct browser *br, const char *str);
static void end_link (struct parseparams *p, struct browser *br, int c);
static void start_link (struct parseparams *p, struct browser *br, char *url, struct formlink *fl, int c);
static char *encode_post (char *str);

extern unsigned char SCREENlength, SCREENwidth;
extern char shortversion[];
extern char *strToBase64 (char *str);
extern struct cmds Cmds[];

static char *BrowserEmail = NULLCHAR;


#define STATUS_LINES	3

#ifdef MSDOS
#define SCREENLENGTH (SCREENlength + 1)
#else
#define SCREENLENGTH (SCREENlength)
#endif

#endif



static int
opcode_lookup (struct opcodes *op, char *opcode)
{
	for ( ; op->opcode; op++)	{
		if (!strcasecmp (op->opcode, opcode))
			return op->value;
	}
	return OP_UNKNOWN;
}



#ifndef TEST

/* fetch the specified URL
   returns -1 if cannot retrieve URL, 0 if no error, 1 if spawned other handler */
static int
get_url (struct browser *br, const char *url, const char *post, int postmode, char *enctype, int storeit)
{
FILE *fp;
char type[12], host[100], component[512];
int port, k;
int retval = 0;
char *tmp;
char *refered = NULLCHAR;

redirection:

	if (url == NULLCHAR)
		return -1;

#if 1
	br->savingonly = 0;
	if (gethead (url, NULLCHAR, NULLCHAR, component) == 1)	{
		/* exists (and is http), so check content type here */
		tmp = skipwhite (component);
		if (strncasecmp (tmp, "text/", 5))	{
			/* not text, so prompt for whether to get it */
			blank_status (br);
			Current->ttystate.edit = Current->ttystate.echo = 1;
			goto_stat (br, 0);
			cputs ("This URL cannot be displayed: ");
			cputs (url);
			cputs (Eol);
			cputs ("       Save it to disk? ");

			kwait (NULL);
			(void) recvline (Curproc->input, (unsigned char *) host, 100);
			rip (host);

			Current->ttystate.edit = Current->ttystate.echo = 0;
			if (tolower(*host) != 'y')
				return -1;
			br->savingonly = 1;
		}
	}
#endif

	fp = tmpfile ();
	if (fp == NULLFILE)
		return -1;	/* shouldn't happen */
		
	tmp = strdup (url);
	if (disect_url (tmp, type, host, &port, component))
		return -1;
	if (!*host)	{
		strncpy (host, component, 100);
		strcpy (component, "/");
	}
	free (tmp);

	if (storeit)	{
		/* add this link to the linkback buffer */
		if (br->current_linkback == (LINKBACKS - 1))	{
			/* roll one off the top */
			free (br->linkback[0]);
			for (k = 1; k < LINKBACKS; k++)
				br->linkback[k - 1] = br->linkback[k];
			br->total_in_linkback--;
			br->current_linkback--;
		}
		if ((br->current_linkback || br->total_in_linkback) && br->current_linkback != (br->total_in_linkback - 1))	{
			/* overwritting a previous entry - clear all that follow */
			for (k = br->current_linkback + 1; k < br->total_in_linkback; k++)	{
				free (br->linkback[k]);
				br->linkback[k] = NULLCHAR;
			}
			br->total_in_linkback = br->current_linkback + 1;
		}
		br->linkback[br->total_in_linkback++] = strdup (url);
		br->current_linkback = br->total_in_linkback - 1;
	}


	if (!strcasecmp (type, "telnet"))	{
		(void) newproc ("tel_browser", 1024, spawn_telnet, port, host, NULL, 0);
		(void) kwait (NULL);
		return 1;
	} else if (!strcasecmp (type, "ftp"))	{
		(void) newproc ("ftp_browser", 1024, spawn_ftp, 0, host, component, 0);
		(void) kwait (NULL);
		return 1;
	} else if (!strcasecmp (type, "mailto"))	{
		send_mailto (component);
		return 1;
	} else if (!strcasecmp (type, "file"))	{
		fp = fopen (component, "r");
		if (fp != NULLFILE)	{
			clear_browser (br);
			br->current_url = build_url (br, type, host, port, component);
			parse_url (br, fp, 0);
			(void) fclose (fp);
			return 0;
		}
		return -1;
	} else if (strcasecmp (type, "http"))	{
		/* don't do anything, for now */
		return 1;
	}

	refered = strdup (br->current_url);
	clear_browser (br);
	br->current_url = build_url (br, type, host, port, component);

	br->host = strdup (host);
	br->port = port;

	/* connect to the remote HTTP server, placing data into the tempfile */
	if ((retval = http_url_to_file (br, refered, host, port, component, br->auth, fp, post, postmode, enctype, 0)) == 0)
		parse_url (br, fp, 1);

	(void) fclose (fp);
	free (refered);

	/* check to see if there was a redirection to a new URL */
	if (br->redirect)	{
		if (storeit)	{
			/* back up the backlink, to make this invisible */
			br->current_linkback--;
			br->total_in_linkback--;
		}
		url = br->redirect;
		goto redirection;
	}

	/* check to see if there was an authorization error */
	if (br->auth)	{
		/* Try again with the desired authorization info */
		if (storeit)	{
			/* back up the backlink, to make this invisible */
			br->current_linkback--;
			br->total_in_linkback--;
		}
		url = br->current_url;
		goto redirection;
	}

	return retval;
}
#endif



/* build a URL from type, host, port, and component and current host info*/
static char *
build_url (struct browser *br, char *type, char *host, int port, char *component)
{
char buf[512], portstr[16], namebuf[128];
char *usehost;

	if (port != 80)
		sprintf (portstr, ":%d", port);
	else
		portstr[0] = 0;

	if (!strcasecmp (type, "mailto"))
		sprintf (buf, "%s:%s", type, component);
	else if (!strcasecmp (type, "file"))
		sprintf (buf, "%s:/%s", type, component);
	else	{
		usehost = (*host) ? host : br->host;
		sprintf (namebuf, "%s:/%s%s", type, (*usehost == '/') ? "" : "/",
			usehost);
		if (namebuf[strlen(namebuf) - 1] == '/')
			namebuf[strlen(namebuf) - 1] = 0;
		sprintf (buf, "%s%s%s%s", namebuf, portstr,
			(component[0] == '/') ? "" : "/", component);
	}
	return strdup (buf);
}



/* disect the URL named into type, host, port, and component */
/* component will NOT start with a '/' for relative URL's */
static int
disect_url (char *url, char *type, char *host, int *port, char *component)
{
char *cp;

	host[0] = ' ';
	if (!strncasecmp (url, "mailto:", 7))	{
		/* special case for mailto's */
		strcpy (component, &url[7]);
		strcpy (type, "mailto");
		return 0;
	}

	if (!strncasecmp (url, "file:", 5))	{
		cp = &url[5];
		if (*cp == '/' && cp[1] == '/')
			cp++;
		strcpy (component, cp);
		strcpy (type, "file");
		return 0;
	}

	if (strchr (url, ':') && strncasecmp (url, "ftp:", 4) &&
	    strncasecmp (url, "telnet:", 7) && strncasecmp (url, "http:", 5))
	    	return -1;

	host[0] = 0;
	*port = 80;
	strcpy (type, "http");
	strcpy (component, "/");

	/* first we look for urls with a scheme name */
	if ((cp = strstr (url, ":/")) != NULLCHAR)	{
		*cp++ = 0;
		strcpy (type, url);
		url = cp;
	}

	/* next we look to see if this is a network path */
	if (!strncmp (url, "//", 2))	{
		url += 2;
		cp = strpbrk (url, ":/?;");
		if (cp)	{
			/* there is more to this address */
			if (*cp == ':')	{	/* contains a port number */
				*cp++ = 0;
				strcpy (host, url);
				url = cp;
				cp = strchr (url, '/');
				if (cp)	{
					*cp++ = 0;
					strcpy (&component[1], cp);
				}
				*port = atoi (url);
				return 0;
			}
			/* otherwise, no port number */
			*cp++ = 0;
			strcpy (host, url);
			strcpy (&component[1], cp);
			return 0;
		}
		strcpy (host, url);
		return 0;
	}

	/* otherwise, it is either an absolute or relative path on this host */
	strcpy (component, url);
	return 0;
}




#ifndef TEST

static void
clear_browser (struct browser *br)
{
int k;
struct formlink *fl, *last;

	/* first we cleanup any previous url info */
	for (k = 0; k < br->lines_in_url; k++)	{
		free (br->display_lines[k]);
		br->display_lines[k] = NULLCHAR;
	}

	free (br->current_url);
	br->current_url = NULLCHAR;

	free (br->redirect);
	br->redirect = NULLCHAR;

	free (br->host);
	br->host = NULLCHAR;

	free (br->title);
	br->title = NULLCHAR;

	free (br->marquee);
	br->marquee = NULLCHAR;
	br->marquee_position = 0;

	br->has_an_index = br->lines_in_url = br->current_top_line = 0;

	for (k = 0; k < br->link_count; k++)	{
		free (br->links[k].url);
		br->links[k].url = NULLCHAR;
		br->links[k].startline = br->links[k].endline = 0;
		br->links[k].startcolumn = br->links[k].endcolumn = 0;
		br->links[k].flink = NULLFLINK;
		br->links_per_line[k] = 0;
	}

	br->consume = br->status = br->link_count = br->current_link = 0;

	if (br->form != NULLFORM)	{
		free (br->form->action);
		free (br->form->enctype);

		for (fl = br->form->flink; fl != NULLFLINK; fl = last)	{
			last = fl->next;
			free (fl->data);
			free (fl->name);
			free (fl->value);
			free (fl->align);
			free (fl);
		}
		free (br->form);
		br->form = NULLFORM;
	}
}



static int
http_connect (char const *host, int port, struct sockaddr_in *fsocket)
{
int s;

	fsocket->sin_family = AF_INET;
	fsocket->sin_port = (int16) port;

	if ((fsocket->sin_addr.s_addr = resolve (host)) == 0)
		return -1;

	if ((s = socket (AF_INET, SOCK_STREAM, 0)) == -1)
		return -1;

	(void) sockmode (s, SOCK_ASCII);
	if (connect (s, (char *) fsocket, SOCKSIZE) == -1)
		return -1;
	
	return s;
}



/* connects to remote HTTP server, and saves URL data
   returns -1 if cannot retrieve URL, 0 if no error */
static int
http_url_to_file (struct browser *br, char *refered, char const *host, int port, char const *component, char *auth, FILE *fp, const char *post, int postmode, char *enctype, int quiet)
{
struct sockaddr_in fsocket;
int s, c;
char *saved, *cp;
int incomingcount = 0;
char buf[20];
int endofline = 0;

	/* telnet to 'host', port 'port', and send a request for 'component' */
	/* return -1 if can't connect or http error */

	s = http_connect (host, port, &fsocket);
	if (s == -1)
		return s;

	if (!quiet)	{
		blank_status (br);
		goto_stat (br, 1);
		cputs ("Opening - http://");
		cputs (host);
		cputs (component);
		cputs ("...");
	}

	saved = strdup (component);
	if ((cp = strchr (saved, '#')) != NULLCHAR)
		*cp = 0;

	if (!quiet)	{
		goto_stat (br, 2);
		cputs ("Sending request...");
		rflush ();
	}

	/* send the request to the server */
	usprintf (s, "%s %s%s%s HTTP/1.0\nHost: %s\nUser-Agent: %s\n",
		(post != NULLCHAR && postmode == 0) ? "POST" : "GET",
		saved, (post != NULLCHAR && postmode == 1) ? "?" : "",
		(post != NULLCHAR && postmode == 1) ? post : "",
		Hostname, shortversion);
	free (saved);

#if 0
	usputs (s, "Accept: text/html, text/plain\n");
#endif
	usputs (s, "Accept-Language: en\nPragma: no-cache\n");
	usprintf (s, "Cache-Control: no-cache\n");
	if (BrowserEmail != NULLCHAR)
		usprintf (s, "From: %s\n", BrowserEmail);
	else
		usprintf (s, "From: wwwuser@%s\n", Hostname);
	if (refered && *refered)
		usprintf (s, "Referer: %s\n", refered);
	if (auth)
		usprintf (s, "Authorization: Basic %s\n", auth);
	if (post != NULLCHAR && postmode == 0)	{
		if (enctype != NULLCHAR)	/* should always be non-null */
			usprintf (s, "Content-type: %s\n", enctype);
		usprintf (s, "Content-Length: %d\n", strlen (post) + 1);
	}
#ifndef TEST
	share_cookies (s, host, component);
#endif
	
	usputc (s, '\n');
	if (post != NULLCHAR && postmode == 0)	{
		usputs (s, post);
		usputc (s, '\n');
	}

	if (!quiet)	{
		goto_stat (br, 2);
		cputs ("Request sent...   ");
		rflush ();
	}

	kwait (NULL);
	
	/* then while connection still open, store data into 'fp' */
	while ((c = recvchar (s)) != EOF)	{
		if (br->savingonly && c == '\n')	{
			if (endofline)
				(void) sockmode (s, SOCK_BINARY);
			endofline = 1;
		} else
			endofline = 0;
		fputc (c, fp);
		incomingcount++;
		if (!quiet)	{
			if (!(incomingcount % 50))	{
				goto_stat (br, 2);
				cputs ("Read ");
				sprintf (buf, "%d", incomingcount);
				cputs (buf);
				cputs (" bytes...  ");
				rflush ();
			}
		}			
	}

	close_s (s);
	
	if (!quiet)	{
		goto_stat (br, 2);
		cputs ("Read Complete...     ");
		rflush ();
	}

	/* and reset to file for the next routine */	
	rewind (fp);
	return 0;
}
#endif



/* this routine allocates a string, optionally centering it */
static char *
alloc_string (struct browser *br, const char *str, int centerit)
{
char *buf;
int len, k;
struct url_links *lnk;

	buf = (char *) mallocw (((strlen(str) > SCREENwidth) ? strlen(str) : SCREENwidth) + 2);
	if (centerit)	{
		len = (int) (SCREENwidth - strlen (str));
		if (len < 0)
			len = 0;
		len /= 2;
		sprintf (buf, "%*.*s%s", len, len, " ", str);

		/* We must also adjust any links on the current line */
		for (lnk = br->links, k = 0; k < br->link_count; k++,lnk++)	{
			if (lnk->startline == br->lines_in_url)
				lnk->startcolumn += len;
			if (lnk->endline == br->lines_in_url)
				lnk->endcolumn += len;
		}
	} else
		strcpy (buf, str);

	return buf;
}



static void
carryover_check (struct browser *br, struct parseparams *p)
{
char carryover[256];
struct url_links *lnk;
int length;

	if (br->lines_in_url >= MAX_URL_LINES || p->nowrap)
		return;

	if (p->index > (SCREENwidth - 1))	{
		if (p->lastwhite == 0)
			return;
		/* handle word wrap here */
		p->buf[p->lastwhite] = p->buf[p->index] = 0;
		br->display_lines[br->lines_in_url++] = alloc_string (br, p->buf, p->center);
		strcpy (carryover, &p->buf[p->lastwhite + 1]);

		if (p->listlevel)
			sprintf (p->buf, "%*.*s", (p->listlevel * 4) + 4,
				(p->listlevel * 4) + 4, " ");
		else
			p->buf[0] = 0;

		/* check if the last link needs adjusting */
		lnk = &br->links[br->link_count - 1];
		if (lnk->startline == (br->lines_in_url - 1) && lnk->startcolumn > p->lastwhite)	{
			/* entire link on next line */
			lnk->startline++;
			lnk->endline++;
			length = lnk->endcolumn - lnk->startcolumn;
			if (p->listlevel)
				lnk->startcolumn = (p->listlevel * 4);
			else
				lnk->startcolumn = 0;
			lnk->endcolumn = lnk->startcolumn + length;
			if (lnk->flink != NULLFLINK)	{
				lnk->flink->line = lnk->startline;
				lnk->flink->col = lnk->startcolumn + 1;
			}
			br->links_per_line[br->lines_in_url]++;
			br->links_per_line[br->lines_in_url - 1]--;

		} else if (lnk->endline == (br->lines_in_url - 1) && lnk->endcolumn > p->lastwhite)	{
			/* end of link needs adjusting */
			lnk->endline++;
			lnk->endcolumn = (int) (strlen (p->buf) - 1);
			br->links_per_line[br->lines_in_url]++;
		}

		strcat (p->buf, carryover);
		p->index = (int) strlen (p->buf);
		p->lastwhite = 0;
	}

}



static void
break_line (struct browser *br, struct parseparams *p)
{
	carryover_check (br, p);
	p->buf[p->index] = 0;
	if (p->index && (br->lines_in_url < MAX_URL_LINES))
		br->display_lines[br->lines_in_url++] = alloc_string (br, p->buf, p->center + p->center_one);
	p->index = p->lastwhite = p->inword = 0;
	p->center_one = 0;
}



static void
start_link (struct parseparams *p, struct browser *br, char *url, struct formlink *fl, int c)
{
	if (br->link_count < MAX_URL_LINKS)	{
		br->links[br->link_count].startline = br->lines_in_url;
		br->links[br->link_count].url = url;
		br->links[br->link_count].flink = fl;
		br->links_per_line[br->lines_in_url]++;
		if (c && p->index && p->buf[p->index - 1] != ' ')
			add_char (p, br, ' ');
		br->links[br->link_count].startcolumn = p->index;
		if (c)
			add_char (p, br, c);
	}
	p->inword = 0;
}



static void
end_link (struct parseparams *p, struct browser *br, int c)
{
char buf[2];
int k;

	if (br->link_count < MAX_URL_LINKS)	{
		if (c && p->index && p->buf[p->index - 1] == ' ')
			p->index--;
		if (!p->index)	{
			/* adjust this back to the end of the last line */
			strcpy (p->buf, br->display_lines[br->lines_in_url - 1]);
			buf[0] = (char) c;
			buf[1] = 0;
			strcat (p->buf, buf);
			free (br->display_lines[br->lines_in_url - 1]);
			br->display_lines[br->lines_in_url - 1] = strdup (p->buf);
			br->links[br->link_count].endline = br->lines_in_url - 1;
			br->links[br->link_count].endcolumn = (int) (strlen (p->buf) - 1);
		} else	{
			if (c)
				add_char (p, br, c);
			br->links[br->link_count].endline = br->lines_in_url;
			br->links[br->link_count].endcolumn = p->index - 1;
		}
		for (k = br->links[br->link_count].startline + 1; k <= br->links[br->link_count].endline; k++)
			br->links_per_line[k]++;
		br->link_count++;
	}
}



static void
add_string (struct parseparams *p, struct browser *br, const char *str)
{
	while (*str)
		add_char (p, br, *str++);
}



static void
add_char (struct parseparams *p, struct browser *br, int c)
{
	if (br->consume)
		return;

	if (c == ' ' || c == '\t' || c == '\n')	{
		if (p->title_input)	{
			p->title[p->titleindex++] = ' ';
			return;
		}
		if (p->marquee_input)	{
			p->marquee[p->marqueeindex++] = ' ';
			return;
		}
		if (!p->preformatted && !p->inword)
			return;	/* eat multiple white */
		p->inword = 0;
		if (p->index > (SCREENwidth - 1))
			carryover_check (br, p);

		p->lastwhite = p->index;
		p->buf[p->index++] = (p->preformatted) ? (char) c : ' ';
		return;
	}

	if (c == NBSP)
		c = ' ';

	if (c == ';' && p->eat_semicolon)	{
		p->eat_semicolon = 0;
		return;
	}
	p->eat_semicolon = 0;
	if (p->title_input)	{
		p->title[p->titleindex++] = (char) c;
		return;
	}
	if (p->marquee_input)	{
		p->marquee[p->marqueeindex++] = (char) c;
		return;
	}
	p->inword = 1;
	if (!p->index && p->listlevel)	{
		sprintf (p->buf, "%*.*s", (p->listlevel * 4) + 4,
			(p->listlevel * 4) + 4, " ");
		p->index = (int) strlen (p->buf);
		p->lastwhite = p->index - 1;
	}
	p->buf[p->index++] = (char) c;
}



/* cp is malloced, and gets freed. this returns a new malloced string */
static char *
rebuild_url (struct browser *br, char *cp)
{
char *saved, *temp, *cp3;
char type[12], host[100], component[512];
int port;
int badurl = 0;

	badurl = disect_url (cp, type, host, &port, component);
	free (cp);

	if (!badurl)	{

		if (*component != '/' && !strcasecmp (type, "http"))	{	/* relative URL */
			saved = strdup (component);
			temp = strdup (br->current_url);
			(void) disect_url (temp, type, host, &port, component);
			if ((cp3 = strrchr (component, '/')) != NULLCHAR)
				cp3++;
			else
				cp3 = component;
			strcpy (cp3, saved);
			free (saved);
			free (temp);
		}

		cp = build_url (br, type, host, port, component);
	} else
		cp = NULLCHAR;
	return cp;
}


/* parse the tempfile given (containing the raw URL data)
   and place into the browser structure
   currently bitbuckets all http header lines */
static void
parse_url (struct browser *br, FILE *fp, int headersexist)
{
struct parseparams *p;
struct tag *tag;
struct formlink *fl;
char url[128];
int headers_done = 0;
int c, k;
char host[100], component[512];
char *cp;
int linkfound = 0;
char *realm = NULLCHAR;
struct realms *rl;
int badauth = 0;
int saveinstead = 0;
#ifndef TEST
FILE *savefp = NULLFILE;
#endif

	p = (struct parseparams *) callocw (1, sizeof (struct parseparams));

	if (headersexist)	{
		/* first line contains request status */
		(void) fgets (p->buf, 128, fp);
		if ((cp = strchr (p->buf, ' ')) != NULLCHAR)
			br->status = atoi (++cp);
		else
			br->status = 0;
	} else
		headers_done = 1;
	
	/* skip all http header lines */
	while (!headers_done)	{
		(void) fgets (p->buf, 128, fp);
		rip (p->buf);
		if (!*p->buf)
			break;
		if (!strncasecmp (p->buf, "Content-type: ", 14))	{
			if (strncmp (&p->buf[14], "text", 4))
				/* if not a text URL, so we save it */
				saveinstead = 1;
		}

#ifndef TEST
		if (!strncasecmp (p->buf, "Set-Cookie: ", 12))	{
			/* parse line, and call add_cookie here */
			char *hostnm = NULLCHAR, *path = NULLCHAR, *name = NULLCHAR, *value = NULLCHAR;
			char *tmp;
			time_t expires = 0;

			cp = &p->buf[12];
			while (cp && *cp)	{
				cp = skipwhite (cp);
				if (!strncasecmp (cp, "domain=", 7))	{
					cp += 7;
					tmp = strchr (cp, ';');
					if (tmp)
						*tmp++ = 0;
					hostnm = strdup (cp);
					cp = tmp;
				} else if (!strncasecmp (cp, "path=", 5))	{
					cp += 5;
					tmp = strchr (cp, ';');
					if (tmp)
						*tmp++ = 0;
					path = strdup (cp);
					cp = tmp;
				} else if (!strncasecmp (cp, "expires=", 8))	{
					/* ignore this, treat all expires as + 6 months */
					tmp = strchr (cp, ';');
					if (tmp)
						cp = tmp;
					else
						cp = &cp[strlen(cp)];
				} else if (!strncasecmp (cp, "secure", 6))	{
					/* not supported, just skip it */
					cp = skipnonwhite (cp);
				} else if ((tmp = strchr (cp, '=')) != NULLCHAR)	{
					/* this is the name=value string */
					*tmp++ = 0;
					name = strdup (cp);
					cp = strchr (tmp, ';');
					if (cp)
						*cp++ = 0;
					value = strdup (tmp);
				}
				if (cp)	{
					cp = skipwhite (cp);
					if (*cp == ';')
						cp++;
				}
			}
			if (path == NULLCHAR)	{
				tmp = strchr (br->current_url, '/');
				if (tmp)	{
					tmp += 2;
					tmp = strchr (tmp, '/');
					path = strdup (tmp);
				} else
					path = strdup ("/");
			}
			if (hostnm == NULLCHAR)
				hostnm = strdup (br->host);
			expires = time ((time_t *)0) + SECS_IN_6MONTHS;
			
			add_cookie (hostnm, path, expires, name, value);
			free (hostnm);
			free (path);
			free (name);
			free (value);
			continue;
		}

		/* if we are being redirected, then get the new URL */
		if (br->status == 302 && !strncasecmp (p->buf, "Location: ", 10))	{
			br->redirect = strdup (&p->buf[10]);
			return;
		}

		/* if we need authorization, then get it */
		if (br->status == 401 && !strncasecmp (p->buf, "WWW-Authenticate: ", 18))	{
			if ((cp = strstr (p->buf, "basic")) == NULLCHAR)
				continue;	/* only basic auth supported */
			if ((cp = strstr (p->buf, "realm=\"")) != NULLCHAR)	{
				realm = strdup (&cp[7]);
				if ((cp = strchr (realm, '"')) != NULLCHAR)
					*cp = 0;
			}

			/* first look to see if we already know how to answer this one */
			for (rl = br->myrealms; rl; rl = rl->next)	{
				if (realm && !strcasecmp (realm, rl->realm))	{
					if (br->auth && !strcmp (br->auth, rl->auth))	{
						/* we already USED this authentication, and it failed! */
						badauth = 1;
						free (rl->auth);
						rl->auth = NULLCHAR;
						free (br->auth);
						break;
					}
					free (realm);
					free (br->auth);
					br->auth = NULLCHAR;
					br->auth = strdup (rl->auth);
					return;
				}
			}

			/* nope, this is a new one */
			blank_status (br);
			goto_stat (br, 0);
			sprintf (component, "Enter authentication information for %s at %s",
				(realm) ? realm : "selected URL", br->host);
			cputs (component);
			Current->ttystate.edit = Current->ttystate.echo = 1;
			goto_stat (br, 1);
			cputs ("          User Name: ");
			kwait (NULL);
			(void) recvline (Curproc->input, (unsigned char *) url, 100);
			rip (url);

			Current->ttystate.echo = 0;
			if (*url)	{
				goto_stat (br, 2);
				cputs ("          Password: ");
				kwait (NULL);
				(void) recvline (Curproc->input, (unsigned char *) host, 100);
				rip (host);

				sprintf (component, "%s:%s", url, host);
				br->auth = strToBase64 (component);

				/* and add to my current realms */
				if (!badauth)	{
					rl = (struct realms *) callocw (1, sizeof (struct realms));
					if (rl != NULLREALMS)	{
						rl->next = br->myrealms;
						br->myrealms = rl;
						rl->host = strdup (br->host);
						rl->realm = strdup (realm);
					}
				}
				if (rl != NULLREALMS)
					rl->auth = strdup (br->auth);
			}

			Current->ttystate.edit = 0;
			free (realm);
			if (*url)
				return;
		}


		/* else, bitbucket any other headers, for now */
#endif
	}

	free (br->auth);
	br->auth = NULLCHAR;

#ifndef TEST
	if (saveinstead)	{
		cp = strrchr (br->current_url, '/');
		if (!cp)	/* it always WILL be nonzero - this is for lint */
			return;
		sprintf (component, "%s%s", IncomingURLs, cp);
		(void) mkdir (IncomingURLs, 0777);	/* create, just in case */
		savefp = fopen (component, WRITE_BINARY);
		if (savefp == NULLFILE)
			return;
	}
#endif

	kwait (NULL);
	/* the rest of the file is the data of the URL */
	while ((c = fgetc (fp)) != EOF)	{
#ifndef TEST
		if (savefp != NULLFILE)	{
			fputc (c, savefp);
			continue;
		}
#endif
		if (p->preformatted && c != '<')	{
			if (c == '\n')	{
				c = p->index;
				break_line (br, p);
				if (c == 0)
					br->display_lines[br->lines_in_url++] = strdup ("");
			} else
				add_char (p, br, c);
			continue;
		}

		switch (c)	{
			case '\r':
				break;		/* eat it */

			case '&':
				switch (tolower (fgetc (fp)))	{
					case 'l':
						if (tolower (fgetc (fp)) == 't')	{
							add_char (p, br, '<');
							p->eat_semicolon = 1;
						}
						break;
					case 'g':
						if (tolower (fgetc (fp)) == 't')	{
							add_char (p, br, '>');
							p->eat_semicolon = 1;
						}
						break;
					case 'n':
						if ((tolower (fgetc (fp)) == 'b') &&
						    (tolower (fgetc (fp)) == 's') &&
						    (tolower (fgetc (fp)) == 'p'))	{
						    	add_char (p, br, NBSP);
						    	p->eat_semicolon = 1;
						}
						break;
					default:
						break;
				}
				break;
			case '<':
				tag = parse_tag (fp);
				switch (opcode_lookup (supported_opcodes, tag->name))	{
					case OP_CONSUME:	/* used for SCRIPT tag */
						br->consume = (!tag->endtag) ? 1 : 0;
						break;
					case OP_IGNORE:		/* used when a HTML comment is skipped */
						break;
					case OP_FORM:
						if (!tag->endtag)	{
							cp = find_option (tag, "action", 0);
							if (cp == NULLCHAR)
								break;
							br->form = (struct form *) callocw (1, sizeof(struct form));
							
							cp = rebuild_url (br, cp);
							if (cp == NULLCHAR)
								break;

							br->form->action = strdup (cp);
							cp = find_option (tag, "enctype", 0);
							br->form->enctype = (cp == NULLCHAR) ? strdup ("application/x-www-form-urlencoded") : cp;
							
							cp = find_option (tag, "method", 0);
							if (cp != NULLCHAR && !strcasecmp (cp, "GET"))
								br->form->method = 1;
							free (cp);
						} else	{
							struct formlink *fl2;
							int found;
							/* we need to insure that one radio button in each group is selected */
							for (fl = br->form->flink; fl != NULLFLINK; fl = fl->next)	{
								if (fl->type == INPUT_RADIO && !fl->bool)	{
									found = 0;
									for (fl2 = br->form->flink; fl2 != NULLFLINK; fl2 = fl2->next)	{
										if (fl2 != fl && fl2->type == INPUT_RADIO && !strcmp (fl->name, fl2->name))	{
											if (fl2->bool)	{
												found = 1;
												break;
											}
										}
									}
									if (!found)	{
										fl->bool = 1;
										br->display_lines[fl->line][fl->col] = '*';
									}
										
								}
							}
						}
						break;
						
					case OP_PRE:
						p->preformatted = (tag->endtag) ? 0 : 1;
						break;
					case OP_OL:
						if (!tag->endtag)
							p->listtype[p->listlevel++] = 1;
						else if (p->listlevel)
							p->listlevel--;
						goto do_a_p;
					case OP_UL:
						if (!tag->endtag)
							p->listtype[p->listlevel++] = 0;
						else if (p->listlevel)
							p->listlevel--;
						goto do_a_p;
					case OP_DL:
						p->listlevel = 0;
						goto do_a_p;
					case OP_DT:
						if (!tag->endtag)
							break_line (br, p);
						p->listlevel = 0;
						break;
					case OP_DD:
						if (!tag->endtag)	{
							p->listlevel = 1;
							break_line (br, p);
						}
						break;
					case OP_LI:
						if (!tag->endtag)	{
							break_line (br, p);
							p->listcount[p->listlevel - 1]++;
							if (p->listtype[p->listlevel - 1])
								sprintf (p->buf, "%*.*s%2d. ", (p->listlevel * 4),
									(p->listlevel * 4), " ", p->listcount[p->listlevel - 1]);
							else
								sprintf (p->buf, "%*.*s*   ", (p->listlevel * 4),
									(p->listlevel * 4), " ");
							p->index = (int) strlen (p->buf);
							p->lastwhite = p->index - 1;
						}
						break;
						
					case OP_IMAGE:
						/* look within here to see if an 'alt="xxx"' part is found */
						cp = find_option (tag, "alt", 0);
						if (cp == NULLCHAR)
							break;
						add_string (p, br, cp);
						free (cp);
						break;

					case OP_ISINDEX:
						br->has_an_index = 1;
						break;
					case OP_H1:
						if (!tag->endtag)	{
							break_line (br, p);
							break;
						}
						/* first adjust the buffer to center the line */
						p->center_one = 1;
						/* and fall through */
					case OP_HEADER:
					case OP_P:
do_a_p:						break_line (br, p);
						if (br->lines_in_url && *br->display_lines[br->lines_in_url - 1])
							br->display_lines[br->lines_in_url++] = strdup ("");
						break;
					case OP_TITLE:
						if (!tag->endtag)	{
							p->title_input = 1;
							p->titleindex = 0;
						} else	{
							p->title_input = 0;
							p->title[p->titleindex] = 0;
							free (br->title);
							br->title = strdup (p->title);
						}
						break;
					case OP_MARQUEE:
						if (!tag->endtag)	{
							p->marquee_input = 1;
							p->marqueeindex = 0;
						} else	{
							p->marquee_input = 0;
							for (c = 0; c < SCREENwidth; c++)
								p->marquee[p->marqueeindex++] = ' ';
							p->marquee[p->marqueeindex] = 0;
							free (br->marquee);
							br->marquee = strdup (p->marquee);
						}
						break;
					case OP_TABLE:
						if (!tag->endtag)	{
							cp = find_option (tag, "border", 0);
							if (cp == NULLCHAR)
								c = 1;
							else
								c = atoi (cp);
							free (cp);
							parse_and_format_table (br, p, fp, c);
						}
						break;
					case OP_CENTER:
						if (!tag->endtag)	{
							p->center = p->center_one = 0;
							break_line (br, p);
							p->center = 1;
						} else	{
							p->center = 0;
							p->center_one = 1;
							break_line (br, p);
						}
						break;
					case OP_BR:
						break_line (br, p);
						break;
					case OP_HR:
						break_line (br, p);
						br->display_lines[br->lines_in_url++] = strdup (HLINE);
						break;
					case OP_FRAME:
						cp = find_option (tag, "src", 0);
						if (cp == NULLCHAR)
							break;
						cp = rebuild_url (br, cp);
						if (cp == NULLCHAR)
							break;
						break_line (br, p);
					    	add_char (p, br, NBSP);
					    	add_char (p, br, NBSP);
					    	add_char (p, br, NBSP);
						start_link (p, br, cp, NULLFLINK, '[');
						add_string (p, br, "Frame: ");
						cp = find_option (tag, "name", 0);
						if (cp == NULLCHAR)
							cp = find_option (tag, "src", 0);
						add_string (p, br, cp);
						free (cp);
						end_link (p, br, ']');
						break_line (br, p);
						break;
					case OP_A:
						if (!tag->endtag)	{
							cp = find_option (tag, "href", 0);
							if (cp == NULLCHAR)
								break;

							cp = rebuild_url (br, cp);
							if (cp == NULLCHAR)
								break;
							start_link (p, br, cp, NULLFLINK, '[');
							linkfound = 1;
						} else	if (linkfound)	{
							if ((br->links[br->link_count].startcolumn + 1) == p->index)
								/* empty, so we'll assume there is a graphic here */
								add_string (p, br, "Graphic");

							end_link (p, br, ']');
							linkfound = 0;

						}
						break;
					case OP_INPUT:
						if (br->form == NULLFORM)
							break;
						cp = find_option (tag, "type", 0);
						if (cp == NULLCHAR)	/* spec says it's required, but the major browsers let it slide and default */
							k = INPUT_TEXT;
						else	{
							k = opcode_lookup (supported_inputs, cp);
							free (cp);
							if (k == INPUT_UNKNOWN)
								break;
						}
						fl = (struct formlink *) callocw (1, sizeof(struct formlink));
						fl->type = k;
						if (br->form->lastlink == NULLFLINK)
							br->form->lastlink = br->form->flink = fl;
						else	{
							br->form->lastlink->next = fl;
							br->form->lastlink = fl;
						}
						fl->name = find_option (tag, "name", 0);
						fl->value = find_option (tag, "value", 0);
						fl->align = find_option (tag, "align", 0);

						cp = find_option (tag, "size", 0);
						if (cp != NULLCHAR)	{
							fl->size = atoi (cp);
							free (cp);
						}
						cp = find_option (tag, "maxlength", 0);
						if (cp != NULLCHAR)	{
							fl->maxlength = atoi (cp);
							free (cp);
						}

						cp = find_option (tag, "selected", &k);
						if (k)
							fl->bool = 1;
						free (cp);

						if (fl->type == INPUT_HIDDEN)
							break;
						start_link (p, br, NULLCHAR, fl, (fl->type != INPUT_RADIO) ? '[' : '(');
						fl->line = br->lines_in_url;
						fl->col = p->index;
						switch (fl->type)	{
							case INPUT_CHECKBOX:
								add_char (p, br, (fl->bool) ? 'X' : '_');
								break;
							case INPUT_RADIO:
								add_char (p, br, (fl->bool) ? '*' : '_');
								break;
							case INPUT_IMAGE:
								add_string (p, br, "Clickable Image");
								break;
							case INPUT_SUBMIT:
								add_string (p, br, (fl->value) ? fl->value : "Submit");
								break;
							case INPUT_RESET:
								add_string (p, br, (fl->value) ? fl->value : "Reset");
								break;
							case INPUT_PASSWORD:
							case INPUT_TEXT:
								for (k = 0; k < fl->size; k++)
									add_char (p, br, '_');
								break;
							default:
								break;
						}
						end_link (p, br, (fl->type != INPUT_RADIO) ? ']' : ')');
						add_char (p, br, ' ');
						
						break;
					default:
						break;
				}
				delete_tag (tag);
				break;
			default:
				add_char (p, br, c);
				break;
		}
	}
	break_line (br, p);

	/* now check for longest line length, and set it */
	br->longestline = br->columnoffset = 0;
	for (k = 0; k < br->lines_in_url; k++)	{
		if ((int) strlen (br->display_lines[k]) > br->longestline)
			br->longestline = (int) strlen (br->display_lines[k]);
	}

#ifndef TEST
	if (savefp != NULLFILE)
		(void) fclose (savefp);
#endif

	/* if both an ISINDEX and an MARQUEE found, ISINDEX wins the status line */
	if (br->has_an_index && br->marquee)	{
		free (br->marquee);
		br->marquee = NULLCHAR;
	}
}



static char *
get_tag_data (struct browser *br, FILE *fp, char **cur_url, struct tag **cur_tag)
{
int theindex, c;
char buf[1024], *cp;
struct tag *tag;
int done = 0;
int eat_semicolon = 0;

	theindex = 0;
	buf[0] = 0;

	do	{
		while ((c = fgetc (fp)) != '<')	{
#if 0
			if (c != '\n')
				buf[theindex++] = (char) c;
#else
			switch (c)	{
				case '&':
					switch (tolower (fgetc (fp)))	{
						case 'l':
							if (tolower (fgetc (fp)) == 't')	{
								buf[theindex++] = '<';
								eat_semicolon = 1;
							}
							break;
						case 'g':
							if (tolower (fgetc (fp)) == 't')	{
								buf[theindex++] = '>';
								eat_semicolon = 1;
							}
							break;
						case 'n':
							if ((tolower (fgetc (fp)) == 'b') &&
							    (tolower (fgetc (fp)) == 's') &&
							    (tolower (fgetc (fp)) == 'p'))	{
							    	buf[theindex++] = ' ';
							    	eat_semicolon = 1;
							}
							break;
						default:
							break;
					}
					break;
				case '\n':
					break;	/* nothing, eat it */
				case ';':
					if (eat_semicolon)	{
						eat_semicolon = 0;
						break;
					}
					/* else, fall-through */
				default:
					buf[theindex++] = (char) c;
					eat_semicolon = 0;
			}
#endif
		}
		tag = parse_tag (fp);
		switch (opcode_lookup (supported_opcodes, tag->name))	{
			case OP_A:	/* do 'A' tags here */
				if (!tag->endtag)	{
					cp = find_option (tag, "href", 0);
					if (cp != NULLCHAR)	{
						cp = rebuild_url (br, cp);
						if (cp == NULLCHAR)
							break;
						/* this only occurs if this data element had multiple links -
						   all but the last one are lost */
						if (*cur_url != NULLCHAR)
							free (*cur_url);
						*cur_url = cp;
					}
				}
				delete_tag (tag);
				break;
			case OP_CAPTION:
			case OP_TR:
			case OP_TH:
			case OP_TD:
			case OP_TABLE:
				delete_tag (*cur_tag);
				*cur_tag = tag;
				done = 1;
				break;
			default:	/* ignore it */
				delete_tag (tag);
				break;
		}
	} while (!done);
	buf[theindex] = 0;
	return (strdup (buf));
}



static void
parse_and_format_table (struct browser *br, struct parseparams *p, FILE *fp, int border)
{
int c, done = 0, theindex, in_a_row = 0, offset;
int column = 0, usecols, userows, size, thissize;
int rowsize, tmp;
struct tag *tag;
char *cp;
struct form_table *tbl;
struct table_row *row, *newrow;
struct table_column *col;
int nowrap;
char *cur_url = NULLCHAR;
int reloop;
int tablelevel = 1;
int foundimbedded = 0;

	tbl = (struct form_table *) callocw (1, sizeof (struct form_table));
	row = tbl->rows;

	/* first we must parse the entire table tag (and sub-tags) */
	while (!done && (c = fgetc (fp)) != EOF)	{
		if (c != '<')
			continue;
		tag = parse_tag (fp);
loop:		reloop = 0;
		switch (opcode_lookup (supported_opcodes, tag->name))	{
			case OP_CAPTION:
				if (!tag->endtag)	{
					if (tbl->caption)
						free (tbl->caption);
					tbl->caption = get_tag_data (br, fp, &cur_url, &tag);
					reloop = 1;
				}
				break;
			case OP_TR:	/* define a table row */
				if (tag->endtag || in_a_row)	{
					/* ending a row */
					in_a_row = 0;
					if (tbl->rows[tbl->numrows].cols_in_row > tbl->numcols)
						tbl->numcols = tbl->rows[tbl->numrows].cols_in_row;
					tbl->numrows++;
				}
				if (!tag->endtag)	{
					row = &tbl->rows[tbl->numrows];
					in_a_row = 1;
					column = row->cols_in_row;
					cp = find_option (tag, "align", 0);
					if (cp)	{
						if (!strcasecmp ("center", cp))
							row->align = TABLE_ALIGN_CENTER;
						else if (!strcasecmp ("right", cp))
							row->align = TABLE_ALIGN_RIGHT;
						free (cp);
					}
				}

				if (column > tbl->numcols)
					tbl->numcols = column;
				break;
			case OP_TH:	/* define a table heading */
			case OP_TD:	/* define a table data element */
				if (tag->endtag)
					break;
				usecols = 1;
				col = &row->cols[row->cols_in_row];
				cp = find_option (tag, "colspan", 0);
				if (cp != NULLCHAR)
					usecols = atoi (cp);
				if (!usecols)
					usecols = 1;
				col->colspan = usecols;
				free (cp);

				userows = 1;
				cp = find_option (tag, "rowspan", 0);
				if (cp != NULLCHAR)
					userows = atoi (cp);
				if (!userows)
					userows = 1;
				free (cp);

				col->align = row->align;
				cp = find_option (tag, "align", 0);
				if (cp)	{
					if (!strcasecmp ("center", cp))
						col->align = TABLE_ALIGN_CENTER;
					else if (!strcasecmp ("right", cp))
						col->align = TABLE_ALIGN_RIGHT;
					free (cp);
				}

				col->rowspan = userows;
				if (userows != 1)	{
					newrow = &tbl->rows[tbl->numrows + 1];
					newrow->cols[newrow->cols_in_row].colspan = usecols;
					newrow->cols[newrow->cols_in_row].rowspan = 1;
					newrow->cols_in_row++;
					if (usecols != 1)	{
						newrow->cols[newrow->cols_in_row].colspan = 1;
						newrow->cols[newrow->cols_in_row].rowspan = 1;
						newrow->cols_in_row++;
					}
				}

				cur_url = NULLCHAR;
				col->value = get_tag_data (br, fp, &cur_url, &tag);
				reloop = 1;

				/* now, if a A tag was found in this column, save it */
				if (cur_url != NULLCHAR)
					col->url = cur_url;

				rowsize = (int) strlen(col->value) / usecols;
				if (tbl->colsize[column] < rowsize)
					tbl->colsize[column] = rowsize;
					
				row->cols_in_row++;
 				column++;
				if (usecols != 1)	{
					while (--usecols)	{
						col++;
						col->colspan = 1;
						col->rowspan = userows;
						row->cols_in_row++;
						tbl->colsize[column] = rowsize;
		 				column++;
					}
				}
				break;
			case OP_TABLE:
				/* this SHOULD be an endtag - assumed */
				if (!tag->endtag)	{
					tablelevel++;
					foundimbedded++;
				} else
					tablelevel--;
				if (tablelevel == 0)
					done = 1;
				break;
			default:
				break;
		}
		if (reloop)
			goto loop;
		delete_tag (tag);
	}
	if (in_a_row)
		tbl->numrows++;


	if (foundimbedded)	{
		break_line (br, p);
		add_string (p, br, "[ The following is a table with one or more imbedded tables - TNOS doesn't render these properly (at least not yet) ]");
		if (br->lines_in_url && *br->display_lines[br->lines_in_url - 1])
			br->display_lines[br->lines_in_url++] = strdup ("");
		break_line (br, p);
	}

	/* now we set column sizes where there were no headers for the columns */
	for (c = 0; c < tbl->numcols; c++)	{
		if (!tbl->colsize[c])	{	/* wasn't set by a header */
			column = 0;
			size = 1;
			for (theindex = 0, row = tbl->rows; theindex < tbl->numrows; theindex++,row++)	{
				for (usecols = 0,col = row->cols; usecols < row->cols_in_row; usecols++,col++)	{
					if (column < c)	{
						column++;
						thissize = (int) strlen (col->value) / col->rowspan;
						if (col->value != NULLCHAR && size < thissize)
							size = thissize;
					}
				}
			}
			tbl->colsize[c] = size;
		}
	}
	
#ifdef TEST
	printf ("Table:\n\tCaption: %s - Numcols: %d - Numrows: %d\n",
		(tbl->caption) ? tbl->caption : "none", tbl->numcols, tbl->numrows);
	for (c = 0; c < tbl->numcols; c++)
		printf ("\tColumn %d: size: %d", c, tbl->colsize[c]);
	for (c = 0; c < tbl->numrows; c++)	{
		int cc;
		printf ("\tRow %d - cols_in_row: %d - align: %d\n",
			c, tbl->rows[c].cols_in_row, tbl->rows[c].align);
		for (cc = 0; cc < tbl->rows[c].cols_in_row; cc++)
			printf ("\tCol %d - colspan: %d, rowspan: %d, align: %d, value: '%s'\n",
				cc, tbl->rows[c].cols[cc].colspan,
				tbl->rows[c].cols[cc].rowspan, tbl->rows[c].cols[cc].align,
				tbl->rows[c].cols[cc].value);
	}
#endif
	/* now we render the table */
	size = 1 + (tbl->numcols * 3);
	for (c = 0; c < tbl->numcols; c++)
		size += tbl->colsize[c];

	break_line (br, p);
	nowrap = p->nowrap;
	p->nowrap = 1;
	
	/* render top line */
	if (border)	{
		for (c = 0; c < size; c++)
			add_char (p, br, '-');
		break_line (br, p);
	}

	/* render data lines */
	for (c = 0; c < tbl->numrows; c++)	{
		int sizes[MAX_TABLE_COLS];
		int thetypes[MAX_TABLE_COLS];
		int centering;
		int strsize;
		int numsizes = 0;
		int width;
		add_char (p, br, (border) ? '|' : ' ');
		row = &tbl->rows[c];
		column = 0;
		for (theindex = 0; theindex < row->cols_in_row; theindex += offset)	{
			width = 0;
			for (userows = 0; userows < row->cols[theindex].colspan; userows++)	{
				if (width)
					width += 3;
				width += tbl->colsize[theindex + userows];
			}
			sizes[numsizes] = width;
			if (row->cols[theindex].value && width < (int) strlen (row->cols[theindex].value) && row->cols[theindex].rowspan != 1)	{
				/* we can split the data, and put some on the next line */
				/* this next line makes assumptions which will break */
				tbl->rows[c + 1].cols[theindex].value = strdup (&row->cols[theindex].value[width - 1]);
				row->cols[theindex].value[width - 1] = 0;
			}
			column++;
			offset = row->cols[theindex].colspan;
			if (row->cols[theindex].url != NULLCHAR)
				start_link (p, br, row->cols[theindex].url, NULLFLINK, 0);
			add_char (p, br, NBSP);

			/* alignment changes, if not left */
			strsize = (row->cols[theindex].value) ? (int) strlen (row->cols[theindex].value) : 0;
			centering = width - strsize;
			if (centering < 0)
				centering = 0;
			switch (row->cols[theindex].align)	{
				case TABLE_ALIGN_RIGHT:
					while (centering--)
						add_char (p, br, NBSP);
					if (row->cols[theindex].value)
						add_string (p, br, row->cols[theindex].value);
					break;
				case TABLE_ALIGN_CENTER:
					tmp = centering / 2;
					centering -= tmp;
					while (tmp--)
						add_char (p, br, NBSP);
					if (row->cols[theindex].value)
						add_string (p, br, row->cols[theindex].value);
					while (centering--)
						add_char (p, br, NBSP);
					break;
				case TABLE_ALIGN_LEFT:
				default:
					if (row->cols[theindex].value)
						add_string (p, br, row->cols[theindex].value);
					while (centering--)
						add_char (p, br, NBSP);
					break;
			}
			thetypes[numsizes] = (row->cols[theindex].rowspan == 1) ? 1 : 0;
			add_char (p, br, NBSP);
			if (row->cols[theindex].url != NULLCHAR)
				end_link (p, br, 0);
			add_char (p, br, (border) ? '|' : ' ');
			numsizes++;
		}
		break_line (br, p);

		/* render dividing line line */
		if (border && c < (tbl->numrows - 1))	{
			add_char (p, br, '|');
			for (theindex = 0; theindex < numsizes; theindex++)	{
				int i, ch;
				ch = (thetypes[theindex]) ? '-' : NBSP;	/*lint !e771 */
				add_char (p, br, ch);
				for (i = 0; i < sizes[theindex]; i++)	/*lint !e771 */
					add_char (p, br, ch);
				add_char (p, br, ch);
				add_char (p, br, '|');
			}
			break_line (br, p);
		}
	}
	
	/* render bottom line */
	if (border)	{
		for (c = 0; c < size; c++)
			add_char (p, br, '-');
		break_line (br, p);
	}

	/* and we add the caption line, if defined */
	if (tbl->caption)	{
		if (!border && br->lines_in_url && *br->display_lines[br->lines_in_url - 1])
			br->display_lines[br->lines_in_url++] = strdup ("");

		for (c = (size - (int)(strlen (tbl->caption))) / 2; c > 0; c--)
			add_char (p, br, NBSP);
		add_string (p, br, tbl->caption);
		break_line (br, p);
	}
	p->nowrap = nowrap;

	if (foundimbedded)	{
		break_line (br, p);
		if (br->lines_in_url && *br->display_lines[br->lines_in_url - 1])
			br->display_lines[br->lines_in_url++] = strdup ("");
		add_string (p, br, "[ End of table with one or more imbedded tables ]");
		break_line (br, p);
	}

	/* finally, we delete the table structure */
	free (tbl->caption);
	for (c = 0, row = tbl->rows; c < tbl->numrows; c++, row++)	{
		int l;
		for (l = 0, col = row->cols; l < row->cols_in_row; l++, col++)
			free (col->value);
	}
}


static void
trim_trailing_white (char *str)
{
int len;

	for (len = (int) strlen (str); len > 0; len--)
		if (str[len] == ' ')
			str[len] = 0;
}



#ifndef TEST

static void
add_cookie (char *host, char *path, time_t expires, char *name, char *value)
{
FILE *fp;

	fp = fopen (Cookiejar, APPEND_TEXT);
	if (fp != NULLFILE)	{
		trim_trailing_white (host);
		trim_trailing_white (path);
		trim_trailing_white (name);
		trim_trailing_white (value);
		fprintf (fp, "%s %s %ld %s %s\n", host, path, expires, name, value);
		(void) fclose (fp);
	}
}



static void
share_cookies (int s, char const *host, char const *component)
{
FILE *fp;
char buf[512], *cp, *tmp;
time_t thetime;
time_t expires;

	(void) time (&thetime);
	fp = fopen (Cookiejar, READ_TEXT);
	if (fp != NULLFILE)	{
		while (fgets (buf, 512, fp) != NULLCHAR)	{
			if ((cp = strchr (buf, ' ')) == NULLCHAR)
				continue;
			*cp++ = 0;

			/* if not for this host, skip it */
			if (strcasecmp (host, buf))
				continue;
			
			if ((tmp = strchr (cp, ' ')) == NULLCHAR)
				continue;
			*tmp++ = 0;

			/* if not for this path prefix, skip it */
			if (strncmp (cp, component, strlen (cp)))
				continue;

			/* if already expired, skip it */
			expires = (time_t) atol (tmp);
			if (thetime > expires)
				continue;

			if ((cp = strchr (tmp, ' ')) == NULLCHAR)
				continue;
			if ((tmp = strchr (++cp, ' ')) == NULLCHAR)
				continue;
			*tmp++ = 0;
			rip (tmp);

			/* cp now points to the cookie name, and tmp to it's value */
			/* need to add code to consolidate multiple cookies to 1 line */
			usprintf (s, "Cookie: %s=%s\n", cp, tmp);
		}
		(void) fclose (fp);
	}
}



static void
blank_status (struct browser *br)
{
int k, i;

	goto_stat (br, 0);
	for (k = 0; k < STATUS_LINES; k++)
		for (i = 0; i < SCREENwidth; i++)
			if (i != (SCREENwidth - 1) || k != (STATUS_LINES - 1))
				cputs (" ");
}



static void
goto_stat (struct browser *br, int plusoffset)
{
	gotoxy (1, br->current_screen_lines + plusoffset + Current->screen->statline + 1);
}



static void
render_screen (struct browser *br)
{
int countdown, k, l;
struct url_links *curlink;
int startcol, endcol;
char linebuf[256];
char partial[256];

	clrscr ();
	curlink = &br->links[br->current_link];
	br->current_screen_lines = countdown = SCREENlength - ((Current->screen->statline * 2) + 3);
	
	if (Current->screen->statline)
		cputs (Eol);

	/* first put the title up at the top right */
	if (br->title)	{
		countdown -= 2;
		endcol = (int) strlen (br->title);
		startcol = (SCREENwidth - 1) - endcol;
		if (startcol < 0)	{
			endcol = (SCREENwidth - 1);
			startcol = 0;
		}
		while (startcol--)
			cputs (" ");
		strncpy (partial, br->title, (SCREENwidth - 1));
		cputs (partial);
		cputs (Eol);
		cputs (Eol);
	}
			
	for (k = br->current_top_line; countdown && k < br->lines_in_url; k++, countdown--)	{
		if ((int) strlen (br->display_lines[k]) < br->columnoffset)	{
			linebuf[0] = 0;
			if (br->columnoffset)
				strcpy (linebuf, "<");
		} else	{
			strncpy (linebuf, &br->display_lines[k][br->columnoffset], (SCREENwidth + 1));
			if ((int) strlen (linebuf) > SCREENwidth)	{
				linebuf[(SCREENwidth - 1)] = '>';
				linebuf[SCREENwidth] = 0;
			}
			if (br->columnoffset)
				linebuf[0] = '<';
		}
		if (br->links_per_line[k] == 0 || curlink->startline > k || curlink->endline < k)	{
			/* optimize - no special treatment */
			cputs (linebuf);
			if ((int) strlen(linebuf) < SCREENwidth)
				cputs (Eol);
			continue;
		}

		/* we only do this if the current link is a part of this line */
		if (curlink->startline == k)
			startcol = curlink->startcolumn;
		else	{	/* otherwise, we are continuing on a new line */
			for (l = 0; l < SCREENwidth; l++)
				if (linebuf[l] != ' ')
					break;
			startcol = l;
		}
		if (curlink->endline == k)
			endcol = curlink->endcolumn;
		else		/* otherwise, we continue on the next line */
			endcol = (int) (strlen (linebuf) - 1);

		if (startcol > (int) (strlen(linebuf) - 1))	{
			/* starts at end of line - optimize */
			cputs (linebuf);
			cputs (Eol);
			continue;
		}
			
		if (startcol)	{
			strncpy (partial, linebuf, (unsigned int) startcol);
			partial[startcol] = 0;
			cputs (partial);
		}
		highvideo ();
		strncpy (partial, &linebuf[startcol], (unsigned int) ((endcol - startcol) + 1));
		partial[(endcol - startcol) + 1] = 0;
		cputs (partial);
		normvideo ();
		if (endcol != (int) (strlen (linebuf) - 1))
			cputs (&linebuf[endcol + 1]);

		cputs (Eol);
	}
	/* display a status line, and place the user on an input line */
	goto_stat (br, 0);
	if (br->has_an_index)	{
		cputs (" ** Contains a searchable index - press 's' to enter search string **");
		cputs (Eol);
	} else
		cputs (Eol);
	sprintf (partial, "[Press '?' for help - Url: %-52.52s]", br->current_url);
	cputs (partial);
	if (curlink->url)	{
		cputs (" link->");
		cputs (curlink->url);
	} else	if (curlink->flink) {
		cputs (" form component: ");
		switch (curlink->flink->type)	{
			case INPUT_PASSWORD:
				cputs ("non-echoed ");
				/* and fall through */
			case INPUT_TEXT:
				cputs ("text field - press <CR> to edit");
				break;
			case INPUT_RESET:
				cputs ("press <CR> to clear the form");
				break;
			case INPUT_SUBMIT:
			case INPUT_IMAGE:
				cputs ("press <CR> to send form");
				break;
			case INPUT_CHECKBOX:
				cputs ("press <CR> to toggle the state of this checkbox");
				break;
			case INPUT_RADIO:
				if (!curlink->flink->bool)
					cputs ("press <CR> to select this radio button");
				else
					cputs ("selected radio button");
				break;
			default:
				break;
		}
	}
	kwait (NULL);
}



static void
spawn_telnet (int port, void *host, void *p OPTIONAL)
{
char *args[3];
char *hst, portstr[10];

	if (port == 80)
		port = 23;
	Curproc->input = Command->input;
	args[0] = strdup ("telnet");
	args[1] = hst = strdup (host);
	sprintf (portstr, "%-d", port);
	args[2] = portstr;

	if (Current != Command)	{
		clrscr ();
		rflush ();
		kwait (NULL);
	}
	(void) dotelnet (3, args, NULL);

	free (args[0]);
	free (hst);
}



static void
spawn_ftp (int unused OPTIONAL, void *host, void *file)
{
char *args[4];
char *hst, *fname, *tempname, *cp;
FILE *fp;

	hst = strdup (host);
	fname = strdup (file);

	/* create a temp file for using ftp non-interactively */
	tempname = strdup (tmpnam (NULLCHAR));
	if ((fp = fopen (tempname, "w")) != NULLFILE)	{
		if ((cp = strrchr (file, '/')) != NULLCHAR)
			*cp++ = 0;
		fprintf (fp, "anonymous\nwwwuser@%s\nbinary\ncd %s\nget %s\nbye\n", Hostname, (char *) file, cp);
		(void) fclose (fp);

		Curproc->input = Command->input;
		args[0] = strdup ("ftp");
		args[1] = hst;
		args[2] = tempname;
		args[3] = strdup ("prompt");

		if (Current != Command)	{
			clrscr ();
			rflush ();
			kwait (NULL);
		}
		(void) doftp (4, args, NULL);
		unlink (tempname);

		free (args[0]);
		free (args[3]);
	}

	free (hst);
	free (fname);
	free (tempname);
}



static void
send_blank_lines (int count)
{
	while (count--)
		cputs (Eol);
}



static void
send_mailto (char *addr)
{
char *args[5];
char *tempname;
FILE *fp;
char buf[128];
char subject[128];

	/* create a temp file for the email message */
	sprintf (buf, "<%s", tmpnam (NULLCHAR));
	tempname = strdup (buf);

	if ((fp = fopen (&tempname[1], "w")) != NULLFILE)	{
		clrscr ();
		send_blank_lines (3);
		cputs ("Enter the subject for the email message:");
		send_blank_lines (2);
		Current->ttystate.edit = Current->ttystate.echo = 1;

		kwait (NULL);
		(void) recvline (Curproc->input, (unsigned char *) subject, 128);

		send_blank_lines (2);
		cputs ("Enter the body of the message: ('/ex' to end)");
		cputs (Eol);
	
		for ( ; ; )	{
			(void) recvline (Curproc->input, (unsigned char *) buf, 128);
			if (!strncasecmp (buf, "/ex", 3))
				break;
			fputs (buf, fp);
			kwait (NULL);
		}
		(void) fclose (fp);

		args[0] = strdup ("sendmail");
		args[1] = addr;
		sprintf (buf, "wwwuser@%s", Hostname);
		args[2] = buf;
		args[3] = subject;
		args[4] = tempname;

		(void) dosendmail (5, args, NULL);
		unlink (tempname);

		clrscr ();
		send_blank_lines (3);
		cputs ("The email message has been sent!");
		cputs (Eol);
		cputs ("Press any key to return to the current page...");
		kwait (NULL);
		Current->ttystate.edit = Current->ttystate.echo = 0;
		(void) rrecvchar (Curproc->input);
		free (args[0]);
	}

	free (tempname);
}




static char *
encode_post (char *str)
{
char *retval, *cp;
static char hexchar[] = "0123456789ABCDEF";

	if (str == NULLCHAR || !*str)
		return (strdup (""));

	cp = retval = (char *) callocw (1, strlen (str) * 2);
	while (*str)	{
		if (*str == ' ')	{
			*cp++ = '+';
			str++;
		} else if (!strchr ("$-_.!*+'(),;:@&=?/%", *str))
			*cp++ = *str++;
		else	{
			*cp++ = '%';
			*cp++ = hexchar[*str / 16];
			*cp++ = hexchar[*str++ % 16];
		}
	}
	return retval;
}



static int
gethead (const char *url, char *location, char *modified, char *content)
{
FILE *fp;
char type[12], host[100], component[512];
int port, s, c;
char *tmp, *cp;
struct sockaddr_in fsocket;

	if (url == NULLCHAR || (location == NULLCHAR && modified == NULLCHAR && content == NULLCHAR))
		return 0;

	if (location != NULLCHAR)
		*location = 0;

	if (modified != NULLCHAR)
		*modified = 0;

	if (content != NULLCHAR)
		*content = 0;
		
	fp = tmpfile ();
	if (fp == NULLFILE)
		return -1;	/* shouldn't happen */
		
	tmp = strdup (url);
	if (disect_url (tmp, type, host, &port, component))	{
		(void) fclose (fp);
		return 0;
	}
	if (!*host)	{
		strncpy (host, component, 100);
		strcpy (component, "/");
	}
	free (tmp);

	if (strcasecmp (type, "http"))	{
		/* don't do anything, for now */
		(void) fclose (fp);
		return 0;
	}

	s = http_connect (host, port, &fsocket);
	if (s == -1)	{
		(void) fclose (fp);
		return -1;
	}

	if ((cp = strchr (component, '#')) != NULLCHAR)
		*cp = 0;

	/* send the request to the server */
	usprintf (s, "HEAD %s HTTP/1.0\nHost: %s\nUser-Agent: %s\n",
		component, Hostname, shortversion);
	usputs (s, "Accept-Language: en\nPragma: no-cache\n");
	usprintf (s, "Cache-Control: no-cache\nFrom: wwwuser@%s\n\n", Hostname);

	/* then while connection still open, store data into 'fp' */
	while ((c = recvchar (s)) != EOF)
		fputc (c, fp);

	close_s (s);
	rewind (fp);

	while (fgets (component, 128, fp))	{
		rip (component);
		if (!*component)
			break;
		if (content != NULLCHAR && !strncasecmp (component, "Content-type: ", 14))
			strcpy (content, &component[14]);

		if (location != NULLCHAR && !strncasecmp (component, "Location: ", 10))
			strcpy (location, &component[10]);

		if (modified != NULLCHAR && !strncasecmp (component, "Last-Modified: ", 15))
			strcpy (modified, &component[15]);

		/* else, bitbucket any other headers, for now */
	}
	(void) fclose (fp);
	return 1;
}



int
dobrowseremail (int argc OPTIONAL, char *argv[] OPTIONAL, void *p OPTIONAL)
{
char buf[256];

	if (argc < 2)
		tprintf ("%s\n", BrowserEmail);
	else	{
		free (BrowserEmail);
		strcpy (buf, argv[1]);
		if (!strchr (buf, '@'))	{
			strcat (buf, "@");
			strcat (buf, Hostname);
		}
		BrowserEmail = strdup (buf);
	}
	return 0;
}



int
dobrowsercheck (int argc OPTIONAL, char *argv[] OPTIONAL, void *p OPTIONAL)
{
int timeout;
int retval;
char modified[128], buf[256], *cp, *tname;
int doit, found;
FILE *fp, *fpout;
char *bptr;
char *bareurl, *origurl;

	if (argc != 3)	{
		tputs ("Usage: browsercheck <numseconds> <complete url>\n");
		return 1;
	}

	origurl = strdup (argv[2]);
	bareurl = strdup (origurl);
	cp = strchr (bareurl, '?');
	if (cp)
		*cp = 0;
	timeout = atoi (argv[1]);
	if (timeout)	{
		kalarm (timeout * 1000);
		retval = gethead (bareurl, NULLCHAR, modified, NULLCHAR);
		kalarm (0);
		if (retval == -1)
			tprintf ("Couldn't obtain url: %s\n", bareurl);
		else	{
			/* check for last update time */
			doit = 0;
			fp = fopen (CheckedURLs, READ_TEXT);
			if (fp != NULLFILE)	{
				while (fgets (buf, 256, fp))	{
					rip (buf);
					cp = strchr (buf, ' ');
					if (cp == NULLCHAR)
						continue;
					*cp++ = 0;
					if (!strcasecmp (bareurl, buf))	{
						if (!strcasecmp (cp, modified))
							doit = 1;
					}
				}
				(void) fclose (fp);
			}
			/* if doit != 0, it is new or newer */
			if (doit)	{
				tprintf ("\nURL '%s' has NOT been updated!\n", bareurl);
				return 0;
			}
			
			/* if newer, prompt (y/n/later) */
			if (!*modified)
				tprintf ("\nURL '%s' has no\nmodification time/date stamp\nCannot tell if it has been updated.\nDisplay it? (Yes, No) [y/n] ", bareurl);
			else
				tprintf ("\nURL '%s' has been updated.\nDisplay it? (Yes, No, or Later) [y/n/l] ", bareurl);
			tflush ();
			(void) recvline (Curproc->input, (unsigned char *) buf, 256);
			doit = tolower (*buf);

			if (doit == 'y' || doit == 'n')	{
				/* update last update time */
				tname = strdup (CheckedURLs);
				tname[strlen(tname) - 1] = '_';

				fp = fopen (CheckedURLs, READ_TEXT);
				fpout = fopen (tname, WRITE_TEXT);
				if (fpout != NULLFILE)	{
					found = 0;
					if (fp != NULLFILE)	{
						while (fgets (buf, 256, fp))	{
							cp = strchr (buf, ' ');
							if (cp == NULLCHAR)
								continue;
							*cp = 0;
							if (!strcasecmp (bareurl, buf))	{
								strcpy (&cp[1], modified);
								strcat (&cp[1], "\n");
								found = 1;
							}
							*cp = ' ';
							fputs (buf, fpout);
						}
						(void) fclose (fp);
					}
					if (!found)
						fprintf (fpout, "%s %s\n", bareurl, modified);
					(void) fclose (fpout);
				}
				(void) unlink (CheckedURLs);
				(void) rename (tname, CheckedURLs);
				free (tname);

				if (doit == 'y')	{
					sprintf (buf, "browser %s", origurl);
					bptr = _variable_expansion (strdup (buf));
					(void) cmdparse (Cmds, bptr, NULL);
					free (bptr);
				}
			}
		}
	}
	free (bareurl);
	free (origurl);
	return 0;
}



char *
obtain_statline_marquee (void)
{
struct browser *br;
char buf[200], *cp;
FILE *fp;

	br = (struct browser *) callocw (1, sizeof (struct browser));
	fp = tmpfile ();
	if (fp == NULLFILE || br == NULLBROWSER)
		return NULLCHAR;	/* shouldn't happen */

	log (-1, "Updating Statusline News Service from %s", HEADLINENewsHost);
	kalarm (120 * 1000);
	sprintf (buf, "http://%s/headline_news_retrieval_service", Hostname);
	if (http_url_to_file (br, buf, HEADLINENewsHost, 80, HEADLINENewsUrl, NULLCHAR, fp, NULLCHAR, 0, NULLCHAR, 1) == 0)
		parse_url (br, fp, 1);
	kalarm (0);

	(void) fclose (fp);
	cp = br->marquee;
	br->marquee = NULLCHAR;
	clear_browser (br);
	return (cp);
}



/* screen position is ALREADY set to the line desired */
static void
scroll_marquee (struct browser *br)
{
	if (br->marquee == NULLCHAR)
		return;

	goto_stat (br, 0);
	marquee_display (br->marquee, &br->marquee_position);
}



int
dobrowser (int argc OPTIONAL, char *argv[] OPTIONAL, void *p OPTIONAL)
{
struct session *sp = NULLSESSION;
struct browser *br;
struct formlink *fl, *fl2;
int c, k;
int renderit = 1;
int done = 0;
int skipsession = 0;
char buf[128], newurl[256];
char *cp, *cp2;
int len;
struct url_links *lk;
int godown;

	/* Make sure this comes from console */
	if (Curproc->input != Command->input)
		return 0;

	if (!strncasecmp (argv[1], "telnet://", 9) ||
	    !strncasecmp (argv[1], "ftp://", 6))
	    	skipsession = 1;
	    	
	/* Allocate a session descriptor */
	if (!skipsession)	{
		if ((sp = newsession ("browser", TELNET, 0)) == NULLSESSION) {
			tputs (TooManySessions);
			return 1;
		}
	}

	br = (struct browser *) callocw (1, sizeof (struct browser));
	br->current_screen_lines = SCREENlength - ((Current->screen->statline * 2) + 3);
	if (strstr (argv[1], "//"))
		strcpy (newurl, argv[1]);
	else
		sprintf (newurl, "http://%s", argv[1]);
	c = get_url (br, newurl, NULLCHAR, 0, NULLCHAR, 1);
	if (c == 1)	{
		if (!skipsession)
			freesession (sp);
		goto cleanup;
	}
	/* this SHOULDN'T ever happen... */
	if (sp == NULLSESSION)
		return 1;

	sp->ttystate.edit = sp->ttystate.echo = 0;

	/* real routine will call render_screen, then loop on input */
	while (!done)	{
		if (renderit)	{
			render_screen (br);
			renderit = 0;
		}
		kalarm (100);
		c = rrecvchar (Curproc->input);
		kalarm (0);
		if (c == -1)	{
			scroll_marquee (br);
			continue;
		}
		c = tolower (c);

		switch (c)	{
			case 'n':	/* move to next line of file */
					if (br->current_top_line < (br->lines_in_url - 1))	{
						br->current_top_line++;
						renderit = 1;
					}
					break;
			case 'p':	/* move to previous line of file */
					if (br->current_top_line)	{
						br->current_top_line--;
						renderit = 1;
					}
					break;
			case '\t':
			case 'd':	/* move forward one link in the file */
move_down:				if (br->current_link < (br->link_count - 1))	{
						br->current_link++;
						renderit = 1;
						goto adj_check;
					}
					break;
			case 'u':	/* move back one link in the file */
					if (br->current_link)	{
						br->current_link--;
						renderit = 1;
adj_check:						/* also, adjust the screen window, if needed */
						if (br->links[br->current_link].startline < br->current_top_line ||
						    br->links[br->current_link].endline >= br->current_top_line + br->current_screen_lines)
							br->current_top_line = br->links[br->current_link].startline;
					} else if (br->current_top_line)	{
						br->current_top_line = 0;
						renderit = 1;
					}
					break;
			case 'h':	/* display history linkbacks */
					clrscr ();
					send_blank_lines (3);
					cputs ("\t\tLinkback History List");
					send_blank_lines (2);
					for (k = 0; k < br->total_in_linkback; k++)	{
						cputs ("\t");
						cputs ((k == br->current_linkback) ? "*" : " ");
						cputs (br->linkback[k]);
						cputs (Eol);
					}
					send_blank_lines (3);
					cputs ("\t\tPress any key to return to the active page");

					(void) rrecvchar (Curproc->input);
					renderit = 1;
					break;
			case '<':	/* shift screen left - only if already shifted right */
					if (br->columnoffset)	{
						br->columnoffset -= (SCREENwidth / 2);
						if (br->columnoffset < 0)
							br->columnoffset = 0;
						renderit = 1;
					}
					break;
			case '>':	/* shift screen right - only if anymore to right */
					if (br->columnoffset + (SCREENwidth / 2) < br->longestline - (SCREENwidth / 2))	{
						br->columnoffset += (SCREENwidth / 2);
						renderit = 1;
					}
					break;
			case '?':	/* do a help display for the user */
					/* need to complete this */
					clrscr ();

					send_blank_lines (3);
					cputs ("\t\t'n'\tMoves the screen to the next line");
					cputs (Eol);
					cputs ("\t\t'p'\tMoves the screen to the previous line");
					cputs (Eol);
					cputs ("\t\t' '\tMoves the screen down one page");
					cputs (Eol);
					cputs ("\t\t'-'\tMoves the screen up one page");
					cputs (Eol);
					cputs ("\t\t'>'\tShifts screen right one half page (if needed)");
					cputs (Eol);
					cputs ("\t\t'<'\tShifts screen left one half page (if needed)");
					send_blank_lines (2);

					cputs ("\t\t'd'\tActivates the next link down on page (<TAB> also)");
					cputs (Eol);
					cputs ("\t\t'u'\tActivates the next link up on page");
					send_blank_lines (2);

					cputs ("\t\t'g'\tGo to a new URL");
					cputs (Eol);
					cputs ("\t\t'r'\tReloads the current URL");
					cputs (Eol);
					cputs ("\t\t'h'\tDisplays the linkback list history");
					cputs (Eol);
					cputs ("\t\t'f'\tSelects the link one forward from here");
					cputs (Eol);
					cputs ("\t\t'b'\tSelects the link one backward from here");
					send_blank_lines (2);

					cputs ("\t\t's'\tSearches an index (if present on the page)");
					send_blank_lines (2);

					cputs ("\t\t'q'\tQuits the browser session");
					cputs (Eol);
					cputs ("\t\t'?'\tDisplays this help summary");
					send_blank_lines (2);

					cputs ("\t\t\t<CR> selects the active link or form component");
					send_blank_lines (3);

					cputs ("\t\tPress any key to return to the active page");

					(void) rrecvchar (Curproc->input);
					renderit = 1;
					break;
			case 'q':	/* quit the browser */
					done = 1;
					break;
			case 's':	/* search the index - if present */
					if (br->has_an_index)	{
						blank_status (br);
						Current->ttystate.edit = Current->ttystate.echo = 1;
						goto_stat (br, 1);
						cputs ("Enter Search String: ");

						kwait (NULL);
						(void) recvline (Curproc->input, (unsigned char *) buf, 128);
						rip (buf);

						Current->ttystate.edit = Current->ttystate.echo = 0;
						sprintf (newurl, "%s?%s", br->current_url, buf);
						(void) get_url (br, newurl, NULLCHAR, 0, NULLCHAR, 1);
						renderit = 1;
					}
					break;
			case 'g':	/* go to an new url */
					blank_status (br);
					Current->ttystate.edit = Current->ttystate.echo = 1;
					goto_stat (br, 0);
					cputs ("Enter in a new URL in which to visit (if another site, start with '//')");
					cputs (Eol);
					cputs ("      Enter the complete URL: ");

					kwait (NULL);
					(void) recvline (Curproc->input, (unsigned char *) buf, 128);
					rip (buf);

					Current->ttystate.edit = Current->ttystate.echo = 0;
					cp = rebuild_url (br, strdup (buf));
					if (cp != NULLCHAR)
						(void) get_url (br, cp, NULLCHAR, 0, NULLCHAR, 1);
					free (cp);
					renderit = 1;
					break;
			case ' ':	/* down a page */
					br->current_top_line += (SCREENLENGTH - (Current->screen->statline + 4));
					if (br->current_top_line >= br->lines_in_url)
						br->current_top_line = br->lines_in_url - 3;
					renderit = 1;
					break;
			case '-':	/* up a page */
					br->current_top_line -= (SCREENLENGTH - (Current->screen->statline + 4));
					if (br->current_top_line < 0)
						br->current_top_line = 0;
					renderit = 1;
					break;
			case '\r':
			case '\n':	/* traverse the current link */
					if (br->link_count == 0)
						break;		/* no link to traverse */
					lk = &br->links[br->current_link];
					if (lk->url)	{
						godown = 0;
						(void) get_url (br, lk->url, NULLCHAR, 0, NULLCHAR, 1);
					} else	{
						if (lk->flink == NULLFLINK)
							break;

						/* must be a form component */
						godown = 1;
						switch (lk->flink->type)	{
							case INPUT_CHECKBOX:
								lk->flink->bool ^= 1;
								br->display_lines[lk->flink->line][lk->flink->col] = (lk->flink->bool) ? 'X' : '_';
								break;
							case INPUT_RADIO:
								if (lk->flink->bool)
									break;
								/* we need to insure that only this one radio button in this group is selected */

								for (fl = br->form->flink; fl != NULLFLINK; fl = fl->next)	{
									if (fl->type == INPUT_RADIO && !strcmp (fl->name, lk->flink->name))	{
										br->display_lines[fl->line][fl->col] = '_';
										fl->bool = 0;
									}
								}
								br->display_lines[lk->flink->line][lk->flink->col] = '*';
								lk->flink->bool = 1;
								break;
							case INPUT_TEXT:
							case INPUT_PASSWORD:
								blank_status (br);
								if (lk->flink->type == INPUT_TEXT)
									Current->ttystate.echo = 1;
								Current->ttystate.edit = 1;
								goto_stat (br, 1);
								cputs ("Enter Text String: ");

								kwait (NULL);
								k = recvline (Curproc->input, (unsigned char *) buf, (unsigned int) (lk->flink->size + 1));
								rip (buf);

								Current->ttystate.edit = Current->ttystate.echo = 0;
								if (!*buf)
									break;
								cp = &br->display_lines[lk->flink->line][lk->flink->col];
								for (len = 0; len < lk->flink->size; len++)
									cp[len] = '_';
								if (lk->flink->type == INPUT_TEXT)
									strncpy (cp, buf, strlen(buf));
								free (lk->flink->data);
								lk->flink->data = strdup (buf);

								/* if the user gave a string too long, then eat the rest of the line */
								if (k == lk->flink->size)
									while ((c = rrecvchar (Curproc->input)) != '\n')
										;
								
								break;
							case INPUT_RESET:
								for (fl = br->form->flink; fl != NULLFLINK; fl = fl->next)	{
									switch (fl->type)	{
										case INPUT_CHECKBOX:
											br->display_lines[fl->line][fl->col] = '_';
											fl->bool = 0;
											break;
										case INPUT_TEXT:
										case INPUT_PASSWORD:
											free (fl->data);
											fl->data = NULLCHAR;
											cp = &br->display_lines[fl->line][fl->col];
											for (len = 0; len < fl->size; len++)
												cp[len] = '_';
											break;
										default:
											break;
									}
								}
								godown = 0;
								break;
							case INPUT_SUBMIT:
							case INPUT_IMAGE:
								/* formulate data to send in HTTP request */
								newurl[0] = 0;

								/* we start with the checkboxes, and handle them separately, since
								   you can have several with the same name, and the values are to be
								   concatenated together in one variable. */
								for (fl = br->form->flink; fl != NULLFLINK; fl = fl->next)
									if (fl->type == INPUT_CHECKBOX)
										fl->size = 0;	/* used to prevent repeats */
										
								for (fl = br->form->flink; fl != NULLFLINK; fl = fl->next)	{
									if (fl->type != INPUT_CHECKBOX || fl->size == 255)
										continue;
									cp = encode_post (fl->value);
									sprintf (buf, "%s%s=%s", (newurl[0]) ? "&" : "", fl->name, cp);
									free (cp);
									strcat (newurl, buf);
									fl->size = 255;
									for (fl2 = fl->next; fl2 != NULLFLINK; fl2 = fl2->next)	{
										if (fl2->type != INPUT_CHECKBOX || fl2->size == 255 || strcmp (fl->name, fl2->name))
											continue;
										cp = encode_post (fl2->value);
										strcat (newurl, ",");
										strcat (newurl, cp);
										free (cp);
										fl2->size = 255;
									}
								}

								/* now we handle everything else BUT the checkboxes. */
								for (fl = br->form->flink; fl != NULLFLINK; fl = fl->next)	{
									switch (fl->type)	{
										case INPUT_SUBMIT:
											if (fl->name == NULLCHAR)
												continue;
											/* else, fall through */
										case INPUT_TEXT:
										case INPUT_PASSWORD:
											cp = encode_post (fl->data);
											sprintf (buf, "%s%s=%s", (newurl[0]) ? "&" : "", fl->name, cp);
											free (cp);
											break;
										case INPUT_HIDDEN:
add_to_request:										cp = encode_post (fl->value);
											sprintf (buf, "%s%s=%s", (newurl[0]) ? "&" : "", fl->name, cp);
											free (cp);
											break;
										case INPUT_RADIO:
											if (!fl->bool)
												continue;
											goto add_to_request;
										default:
											continue;
									}
									strcat (newurl, buf);
								}
								/* send the post here */
								cp = br->form->action;
								br->form->action = NULLCHAR;
								cp2 = br->form->enctype;
								br->form->enctype = NULLCHAR;
#if 0
tcmdprintf ("Would post here: '%s'\n", cp);
#else
								(void) get_url (br, cp, newurl, br->form->method, cp2, 1);
#endif
								free (cp);
								free (cp2);
								godown = 0;
								break;
							default:
								godown = 0;
								break;
						}
					}
					renderit = 1;
					if (godown)
						goto move_down;
					break;
			case 'f':	/* go forward one link */
					if (br->current_linkback < (br->total_in_linkback - 1))	{
						br->current_linkback++;
						goto reload_link;
					}
					break;
			case 'b':	/* go back one link */
					if (br->current_linkback)	{
						br->current_linkback--;
reload_link:					(void) get_url (br, br->linkback[br->current_linkback], NULLCHAR, 0, NULLCHAR, 0);
						renderit = 1;
					}
					break;
			case 'r':	goto reload_link;
			case 0:
			case 1:
					renderit = 1;
					break;
			default:	break;
		}
		
	}

	close_s (sp->s);
	sp->s = -1;

	blank_status (br);
	goto_stat (br, 1);
	cputs ("                         ");
	(void) keywait (NULLCHAR, 1);
	freesession (sp);
	
	for (c = 0; c < br->total_in_linkback; c++)
		free (br->linkback[c]);
cleanup:
	while (br->myrealms)	{
		free (br->myrealms->host);
		free (br->myrealms->realm);
		free (br->myrealms->auth);
		br->myrealms = br->myrealms->next;
	}
	clear_browser (br);
	free (br);
	return 0;
}



#else
struct browser brow;



void
kwait (void *ignore)
{
}


char *
strlwr(s)
char *s;
{
register char *p = s;

	while (*p)
		*p = (char) tolower (*p), p++;
	return s;
}



void
rip(s)
register char *s;
{
	register char *cp;

	if((cp = strchr(s,'\n')) != NULLCHAR)
		*cp = '\0';
}



void
main (int argc, char *argv[])
{
FILE *fp;
int k;
struct formlink *fl;

	setbuf (stdout, NULL);
	brow.host = strdup ("www.lantz.com");
	brow.current_url = strdup ("/index.html");
	fp = fopen ("/tmp/index.html", "r");
	parse_url (&brow, fp, 0);
	(void) fclose (fp);

	printf ("Current URL: %s\nHost: %s\nPort: %d\nUrlLines: %d\n", brow.current_url,
		brow.host, brow.port, brow.lines_in_url);
	printf ("TopLine: %d\nTitle: %s\n\n", brow.current_top_line, brow.title);
	
	for (k = 0; k < brow.lines_in_url; k++)
		printf ("Line #%02d (%d) [%d]: %s\n", k,
			strlen (brow.display_lines[k]),
			brow.links_per_line[k], brow.display_lines[k]);

	printf ("\nLinkCount: %d\nCurrentLink: %d\n\n", brow.link_count, brow.current_link);
	for (k = 0; k < brow.link_count; k++)
		printf ("Link #%02d: startline=%d, startcol=%d, endline=%d, endcol=%d, url=%s\n",
			k, brow.links[k].startline, brow.links[k].startcolumn,
			brow.links[k].endline, brow.links[k].endcolumn,
			brow.links[k].url);
	if (brow.form != NULLFORM)	{
		printf ("\nForm active:\n\tURL: %s\n\tMethod: %d\n\tEnctype: %s\n\n",
			brow.form->action, brow.form->method, brow.form->enctype);
		for (fl = brow.form->flink; fl != NULLFLINK; fl = fl->next)
			printf ("Link:\tType: %d - line=%d, col=%d\tBool=%d - Data=%s\n"
				"\tName=%s\tValue=%s\n\tAlign=%s - Size=%d - Maxlength=%d\n\n",
				fl->type, fl->line, fl->col, fl->bool, fl->data,
				fl->name, fl->value, fl->align, fl->size, fl->maxlength);
	}
}

#endif

#endif		/* BROWSER */
