1stMUD/corefiles/
1stMUD/gods/
1stMUD/log/
1stMUD/player/
1stMUD/win32/
1stMUD/win32/ROM/
/* ROM 2.4 Integrated Web Server - Version 1.0
 *
 * This is my first major snippet... Please be kind. ;-)
 * Copyright 1998 -- Defiant -- Rob Siemborski -- mud@towers.crusoe.net
 *
 * Many thanks to Russ and the rest of the developers of ROM for creating
 * such an excellent codebase to program on.
 *
 * If you use this code on your mud, I simply ask that you place my name
 * someplace in the credits.  You can put it where you feel it is
 * appropriate.
 *
 * I offer no guarantee that this will work on any mud except my own, and
 * if you can't get it to work, please don't bother me.  I wrote and tested
 * this only on a Linux 2.0.30 system.  Comments about bugs, are, however,
 * appreciated.
 *
 * Now... On to the installation!
 */

/*
 * Insanity v0.9a pre-release Modifications
 * By Chris Fewtrell (Trax) <C.J.Fewtrell@bcs.org.uk>
 *
 * - Added functionailiy for Secure Web server pages, using standard HTTP
 *   Basic authentication, comparing with pass list generated with command
 *   from within the MUD itself. 
 * - Started work on web interface to help files, allowing them to be browsed
 *   from a web browser rather than being in MUD to read them.
 * - Seperated out the HTTP codes and content type to seperate functions
 *   (intending to allow more than HTML to be served via this)
 * - Adjusted the descriptor handling to prevent anyone from prematurely
 *   stopping a transfer causing a fd exception and the system to exit()
 * - Created a sorta "virtual" web directory for the webserver files to be
 *   actually served. This contains the usual images dir if any images are
 *   needed to be served from a central repository rather than generated.
 *   Be warned though! It WON'T follow any symlinks, I'll add that later
 *   with the stat function.. (maybe :) 
 * - Including a MUD-Net web module to add the functionaility of the MUD-Net webserver
 *   code directly into the mud itself for use here, preventing the need for
 *   the seperate server and client processes (I know it sorta depends on the
 *   mud be FE never stays down for long..)
 *
 * Future Possbile additions:
 * - Access to general boards though web interface, prolly prevent posting but
 *   being able to browse and read notes to 'all' would be allowed
 */

/*
 * Additional Modifications based upon with with Insanity Codebase
 * By Chris Fewtrell (Trax) <C.J.Fewtrell@bcs.org.uk>
 *
 * - Web server root directory created, all URLs that use internal code
 *   generated HTML intercept first, then path is handed off to webroot
 *   handler allowing a directory tree to be built up there. Should allow
 *   easier management of the file behind the internal web server.
 */

/* Modded for use on 1stMUD by Markanth 14/05/2003 */

#include <errno.h>
#include <stdarg.h>
#include <sys/types.h>
#if !defined(WIN32)
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <dirent.h>
#endif
#include <fcntl.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "merc.h"
#include "webserver.h"
#include "globals.h"
#include "interp.h"
#include "tables.h"
#include "olc.h"
#include "lookup.h"
#include "recycle.h"
#include "magic.h"
#include "gsn.h"
#include "telnet.h"

void update_webpasses(CHAR_DATA * ch, bool pDelete)
{
	WPWD_DATA *c_next;
	WPWD_DATA *curr;

	if (IS_NPC(ch))
		return;

	for (curr = wpwd_first; curr != NULL; curr = c_next)
	{
		c_next = curr->next;

		if (!str_cmp(ch->name, curr->name))
		{
			UNLINK(curr, wpwd_first, wpwd_last, next, prev);
			free_pwd(curr);
			save_webpasses();
		}
	}
	if (pDelete || IS_NULLSTR(ch->pcdata->webpass))
		return;

	curr = new_pwd();
	replace_string(curr->name, ch->name);
	replace_string(curr->passw, ch->pcdata->webpass);
	LINK(curr, wpwd_first, wpwd_last, next, prev);
	save_webpasses();
	return;
}

CH_CMD(do_webpass)
{
	char arg1[MIL];
	char *pArg;
	char *pwdnew;
	char *p;
	char cEnd;

	if (!ch || IS_NPC(ch))
		return;

	if (!IS_IMMORTAL(ch))
	{
		chprintln(ch, "This feature is only available to immortals, sorry.");
		return;
	}

	pArg = arg1;
	while (isspace(*argument))
		argument++;

	cEnd = ' ';
	if (*argument == '\'' || *argument == '\"')
		cEnd = *argument++;

	while (*argument != '\0')
	{
		if (*argument == cEnd)
		{
			argument++;
			break;
		}
		*pArg++ = *argument++;
	}
	*pArg = '\0';

	if (IS_NULLSTR(arg1))
	{
		chprintln(ch, "Syntax: webpass <new>.");
		return;
	}

	write_to_descriptor(ch->desc, echo_off_str, 0);

	if (strlen(arg1) < 5)
	{
		chprintln(ch, "New password must be at least five characters long.");
		write_to_descriptor(ch->desc, echo_on_str, 0);
		return;
	}

	pwdnew = crypt(arg1, ch->name);
	for (p = pwdnew; *p != '\0'; p++)
	{
		if (*p == '~')
		{
			chprintln(ch, "New password not acceptable, try again.");
			write_to_descriptor(ch->desc, echo_on_str, 0);
			return;
		}
	}

	replace_string(ch->pcdata->webpass, pwdnew);
	save_char_obj(ch);
	update_webpasses(ch, FALSE);
	chprintln(ch, "Ok.");
	write_to_descriptor(ch->desc, echo_on_str, 0);
	return;
}

bool check_web_pass(const char *username, const char *password)
{
	WPWD_DATA *current;

	for (current = wpwd_first; current; current = current->next)
		if (!str_casecmp(current->name, username))
			if (!str_casecmp(current->passw, crypt(password, username)))
				return TRUE;

	return FALSE;
}

#if !defined(NO_WEB)

/* Thanks to John Ludeman for this code...
 * Define translation matrix for Base64 decode.
 * it's fast and const should make it shared text page.
 */
const int pr2six[256] = {
	64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
	64, 64, 64, 64, 64,
	64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
	62, 64, 64, 64, 63,
	52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, 64, 0, 1, 2,
	3, 4, 5, 6, 7, 8, 9,
	10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64,
	64, 64, 64, 26, 27,
	28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46,
	47, 48, 49, 50, 51,
	64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
	64, 64, 64, 64, 64,
	64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
	64, 64, 64, 64, 64,
	64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
	64, 64, 64, 64, 64,
	64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
	64, 64, 64, 64, 64,
	64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
	64, 64, 64, 64, 64,
	64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64
};

bool WebUP = FALSE;

/*
 * Decode a base64 encoded string
 */
void Base64Decode(char *bufcoded, unsigned char *bufplain, int outbufsize)
{
	int nbytesdecoded;
	int nprbytes;
	char *bufin = bufcoded;
	unsigned char *bufout = bufplain;

	/* Strip leading whitespace */
	while (*bufcoded == ' ' || *bufcoded == '\t')
		++bufcoded;

	/*
	 * Figure out how many characters are in the input buffer.
	 * If this would decode into more bytes than would fit into
	 * the output buffer, adjust the number of input bytes downwards.
	 */
	bufin = bufcoded;
	while (pr2six[(int) *(bufin++)] <= 63)
		;
	nprbytes = bufin - bufcoded - 1;
	nbytesdecoded = ((nprbytes + 3) / 4) * 3;
	if (nbytesdecoded > outbufsize)
		nprbytes = (outbufsize * 4) / 3;

	bufin = bufcoded;
	while (nprbytes > 0)
	{
		*(bufout++) =
			(unsigned char) (pr2six[(int) *bufin] << 2 | pr2six[(int) bufin[1]]
							 >> 4);
		*(bufout++) =
			(unsigned char) (pr2six[(int) bufin[1]] << 4 |
							 pr2six[(int) bufin[2]] >> 2);
		*(bufout++) =
			(unsigned char) (pr2six[(int) bufin[2]] << 6 |
							 pr2six[(int) bufin[3]]);
		bufin += 4;
		nprbytes -= 4;
	}

	if (nprbytes & 03)
	{
		if (pr2six[(int) bufin[-2]] > 63)
			nbytesdecoded -= 2;
		else
			nbytesdecoded -= 1;
	}
	bufplain[nbytesdecoded] = '\0';
}

/* The mark of the end of a HTTP/1.x request */
const char ENDREQUEST[5] = { 13, 10, 13, 10, 0 };	/* (CRLFCRLF) */

/* Locals */
WEB_DESCRIPTOR *first_webdesc;
WEB_DESCRIPTOR *last_webdesc;
int web_socket;

bool init_web_server()
{
	struct sockaddr_in my_addr;
	int x = 1;

	first_webdesc = NULL;
	last_webdesc = NULL;

	logf("Attaching Internal Web Server to Port %d", WEBSERVERPORT);

	if ((web_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	{
		log_string("----> Web Server: Error getting socket");
		perror("web-socket");
		return FALSE;
	}

	if (setsockopt(web_socket, SOL_SOCKET, SO_REUSEADDR, (void *) &x, sizeof(x))
		< 0)
	{
		perror("init_web: SO_REUSEADDR");
		close(web_socket);
		web_socket = -1;
		return FALSE;
	}

#if defined(SO_LINGER) && !defined(SYSV)
	{
		struct linger ld;

		ld.l_onoff = 1;
		ld.l_linger = 1000;

		if (setsockopt
			(web_socket, SOL_SOCKET, SO_LINGER, (void *) &ld, sizeof(ld)) < 0)
		{
			perror("Init_web: SO_LINGER");
			close(web_socket);
			web_socket = -1;
			return FALSE;
		}
	}
#endif

	memset(&my_addr, '\0', sizeof(my_addr));
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = htons(WEBSERVERPORT);

	if ((bind(web_socket, (struct sockaddr *) &my_addr, sizeof(my_addr))) == -1)
	{
		log_string("----> Web Server: Error binding socket");
		perror("web-bind");
		close(web_socket);
		web_socket = -1;
		return FALSE;
	}

	/* Only listen for 5 connects at once, do we really need more? */
	/* We might now since I've attached this to the live web page - Samson */
	if (listen(web_socket, 20) < 0)
	{
		perror("web-listen");
		close(web_socket);
		web_socket = -1;
		return FALSE;
	}
	return TRUE;
}

struct timeval ZERO_TIME = { 0, 0 };

void update_web_server(void)
{
	int max_fd;
	WEB_DESCRIPTOR *current, *next;
	fd_set readfds;

	FD_ZERO(&readfds);
	FD_SET(web_socket, &readfds);

	/* it *will* be atleast web_socket */
	max_fd = web_socket;

	/* add in all the current web descriptors */
	for (current = first_webdesc; current; current = current->next)
	{
		FD_SET(current->fd, &readfds);
		if (max_fd < current->fd)
			max_fd = current->fd;
	}

	/* Wait for ONE descriptor to have activity */
	select(max_fd + 1, &readfds, NULL, NULL, &ZERO_TIME);

	if (FD_ISSET(web_socket, &readfds))
	{
		/* NEW CONNECTION -- INIT & ADD TO LIST */

		alloc_mem(current, WEB_DESCRIPTOR, 1);
		current->sin_size = sizeof(struct sockaddr_in);
		current->request[0] = '\0';

		if ((current->fd =
			 accept(web_socket, (struct sockaddr *) &(current->their_addr),
					&(current->sin_size))) == -1)
		{
			log_string("----> Web Server: Error accepting connection");
			perror("web-accept");
			free_mem(current);
			FD_CLR(web_socket, &readfds);
			return;
		}

		LINK(current, first_webdesc, last_webdesc, next, prev);
		/* END ADDING NEW DESC */
	}

	/* DATA IN! */
	for (current = first_webdesc; current; current = current->next)
	{
		if (FD_ISSET(current->fd, &readfds))	/* We Got Data! */
		{
			char buf[MAXDATA];
			int numbytes;

			if ((numbytes = read(current->fd, buf, sizeof(buf))) == -1)
			{
				perror("web-read");
				continue;
			}

			buf[numbytes] = '\0';

			strcat(current->request, buf);
		}
	}							/* DONE WITH DATA IN */

	/* DATA OUT */
	/* Hmm we want to delay this if possible, to prevent it prematurely */
	for (current = first_webdesc; current; current = next)
	{
		next = current->next;

		if (strstr(current->request, "HTTP/1.")	/* 1.x request (vernum on FIRST LINE) */
			&& strstr(current->request, ENDREQUEST))
			handle_web_request(current);
		else if (!strstr(current->request, "HTTP/1.")
				 && strchr(current->request, '\n'))	/* HTTP/0.9 (no ver number) */
			handle_web_request(current);
		else
		{
			continue;			/* Don't have full request yet! */
		}

		close(current->fd);
		UNLINK(current, first_webdesc, last_webdesc, next, prev);
		free_mem(current);
	}
	/* END DATA-OUT */
}

/* Generic Utility Function */
int send_buf(int fd, const char *fmt, ...)
{
	char buf[2 * MSL];
	va_list args;
	int len;

	va_start(args, fmt);

	len = vsnprintf(buf, sizeof(buf), fmt, args);
	va_end(args);

	if (len > 0)
		return send(fd, buf, strlen(buf), 0);
	else
		return len;
}

void send_401UNAUTHORISED(WEB_DESCRIPTOR * wdesc, char *realm)
{
	send_buf(wdesc->fd, "HTTP/1.1 401 Unauthorised\n");
	send_buf(wdesc->fd, "WWW-Authenticate: Basic realm=\"%s\"\n", realm);
}

bool lastcolor = FALSE;

#define SET_WWW_FONT(buf, color) \
do \
{ \
    strcpy(buf, "<FONT color=\"" color "\">"); \
	lastcolor = TRUE; \
} \
while (0);

int html_colour(char type, char *string)
{
	char code[MIL];
	char out[MSL];
	char *p = '\0';

	if (lastcolor == TRUE)
		strcpy(out, "</FONT>");
	else
		out[0] = '\0';

	switch (type)
	{
	case '\0':
		break;
	case ' ':
		strcpy(code, "&nbsp;");
		break;
	default:
	case 'x':
		strncpy(code,
				"<script type='text/javascript'>document.writeln('<FONT color=' + document.fgColor + '>');</script>",
				MIL);
		break;
	case 'b':
		SET_WWW_FONT(code, "navy");
		break;
	case 'c':
		SET_WWW_FONT(code, "teal");
		break;
	case 'g':
		SET_WWW_FONT(code, "green");
		break;
	case 'm':
		SET_WWW_FONT(code, "purple");
		break;
	case 'r':
		SET_WWW_FONT(code, "maroon");
		break;
	case 'w':
		SET_WWW_FONT(code, "silver");
		break;
	case 'y':
		SET_WWW_FONT(code, "olive");
		break;
	case 'B':
		SET_WWW_FONT(code, "blue");
		break;
	case 'C':
		SET_WWW_FONT(code, "cyan");
		break;
	case 'G':
		SET_WWW_FONT(code, "lime");
		break;
	case 'M':
		SET_WWW_FONT(code, "magenta");
		break;
	case 'R':
		SET_WWW_FONT(code, "red");
		break;
	case 'W':
		SET_WWW_FONT(code, "white");
		break;
	case 'Y':
		SET_WWW_FONT(code, "yellow");
		break;
	case 'd':
		SET_WWW_FONT(code, "black");
		break;
	case 'D':
		SET_WWW_FONT(code, "gray");
		break;
	case '-':
		strcpy(code, "~");
		break;
	case '`':
		switch (number_range(1, 14))
		{
		case 1:
			SET_WWW_FONT(code, "navy");
			break;
		case 2:
			SET_WWW_FONT(code, "teal");
			break;
		case 3:
			SET_WWW_FONT(code, "green");
			break;
		case 4:
			SET_WWW_FONT(code, "purple");
			break;
		case 5:
			SET_WWW_FONT(code, "maroon");
			break;
		default:
		case 6:
			SET_WWW_FONT(code, "silver");
			break;
		case 7:
			SET_WWW_FONT(code, "olive");
			break;
		case 8:
			SET_WWW_FONT(code, "blue");
			break;
		case 9:
			SET_WWW_FONT(code, "cyan");
			break;
		case 10:
			SET_WWW_FONT(code, "lime");
			break;
		case 11:
			SET_WWW_FONT(code, "magenta");
			break;
		case 12:
			SET_WWW_FONT(code, "red");
			break;
		case 13:
			SET_WWW_FONT(code, "white");
			break;
		case 14:
			SET_WWW_FONT(code, "yellow");
			break;
		}
		break;
	case ANSI_KEY:
		strcpy(code, "{");
		break;
	}

	strcat(out, code);

	p = out;
	while (*p != '\0')
	{
		*string = *p++;
		*++string = '\0';
	}

	return (strlen(out));
}

void html_colourconv(char *buffer, const char *txt)
{
	const char *point;
	int skip = 0;
	const char *end = "</FONT>";

	lastcolor = FALSE;

	for (point = txt; *point; point++)
	{
		if (*point == ANSI_KEY)
		{
			point++;
			if (*point == '\0')
				point--;
			else
				skip = html_colour(*point, buffer);
			while (skip-- > 0)
				++buffer;
			continue;
		}
		else if (*point == ANSI_CUSTOM)
		{
			point++;
			while (*point != ANSI_END)
				point++;
			continue;
		}
		if (*point == '<')
		{
			*buffer = '&';
			*++buffer = 'l';
			*++buffer = 't';
			*++buffer = ';';
			*++buffer = '\0';
			continue;
		}
		if (*point == '>')
		{
			*buffer = '&';
			*++buffer = 'g';
			*++buffer = 't';
			*++buffer = ';';
			*++buffer = '\0';
			continue;
		}
		if (*point == '"')
		{
			*buffer = '&';
			*++buffer = 'q';
			*++buffer = 'u';
			*++buffer = 'o';
			*++buffer = 't';
			*++buffer = ';';
			*++buffer = '\0';
			continue;
		}
		if (*point == '&')
		{
			*buffer = '&';
			*++buffer = 'a';
			*++buffer = 'm';
			*++buffer = 'p';
			*++buffer = '\0';
			continue;
		}
		if (*point == '\n')
		{
			*buffer = '<';
			*++buffer = 'b';
			*++buffer = 'r';
			*++buffer = '>';
			*++buffer = '\0';
		}
		*buffer = *point;
		*++buffer = '\0';
	}

	if (lastcolor == TRUE)
	{
		for (point = end; *point; point++)
		{
			*buffer = *point;
			*++buffer = '\0';
		}
	}

	*buffer = '\0';
	return;
}

const char *HTTP_URL(void)
{
	return FORMATF(DEFAULT_URL, WEBSERVERPORT);
}

char *get_next(char *path)
{
	static char *buf[5];
	static int i;

	if (IS_NULLSTR(path))
		return path;

	++i;
	i %= 5;

	buf[i] = strchr(path, '/');

	if (IS_NULLSTR(buf[i]))
		return buf[i];
	else
	{
		buf[i]++;
		return buf[i];
	}
}

bool get_name_password(WEB_DESCRIPTOR * wdesc, const char *stuff)
{
	char *where;
	char encoded[MIL];
	char username[MIL];
	char *password = &str_empty[0];

	username[0] = '\0';
	encoded[0] = '\0';

	where = strstr(stuff, "Authorization: Basic");

	if (!where)
		send_401UNAUTHORISED(wdesc, AUTH_DOMAIN);
	else
	{
		where += strlen("Authorization: Basic");

		where++;
		for (password = encoded; *where && !isspace(*where);
			 where++, password++)
			*password = *where;

		*password = '\0';

		Base64Decode(encoded, (unsigned char *) username, MIL);

		for (password = username; *password && *password != ':'; password++);
		{
			if (*password == ':')
			{
				*password = '\0';
				password++;
			}
		}
	}

	return check_web_pass(username, password);
}

void under_line(char *under_lined, const char *spaced_out)
{
	char *point;

	strcpy(under_lined, spaced_out);

	for (point = under_lined; *point; point++)
	{
		if (*point == ' ')
		{
			*point = '_';
		}
		else
		{
			*point = LOWER(*point);
		}
	}
	return;
}

int min_class_level(int sn)
{
	int min_so_far = MAX_LEVEL;
	int iClass;

	for (iClass = 0; iClass < maxClass; iClass++)
	{
		if (skill_table[sn].skill_level[iClass] < min_so_far)
			min_so_far = skill_table[sn].skill_level[iClass];
	}
	return min_so_far;
}

void print_header(WEB_DESCRIPTOR * wdesc, const char *title)
{
	int i;

	send_buf(wdesc->fd, DOCTYPE);
	send_buf(wdesc->fd, "<HTML>\n");
	send_buf(wdesc->fd, "<HEAD>\n");
	send_buf(wdesc->fd, "<TITLE>%s @ %s</TITLE>\n", title, MUD_NAME);
	send_buf(wdesc->fd,
			 "</HEAD><BODY BGCOLOR=BLACK TEXT=WHITE LINK=#FFFFCC VLINK=#FFFFCC>\n");
	send_buf(wdesc->fd,
			 "<TABLE CELLSPACING=3 CELLPADDING=3 BORDER=0><TR><TD VALIGN=TOP WIDTH=15%%>\n");
	send_buf(wdesc->fd, "<UL>\n");
	for (i = 0; request_table[i].req != NULL; i++)
	{
		if (request_table[i].name == NULL)
			continue;

		send_buf(wdesc->fd, "<LI><A href=\"%s%s\">%s</A></LI>\n", HTTP_URL(),
				 request_table[i].req, request_table[i].name);
	}
	send_buf(wdesc->fd, "</UL>\n");
	send_buf(wdesc->fd, "</TD><TD VALIGN=TOP WIDTH=100%%>\n");
	send_buf(wdesc->fd, "<H2>%s @ %s</H2>\n", title, MUD_NAME);
}

void print_footer(WEB_DESCRIPTOR * wdesc)
{
	send_buf(wdesc->fd, "</TD></TR></TABLE>\n");
	send_buf(wdesc->fd, "<BR><ADDRESS>\n");
	send_buf(wdesc->fd,
			 "This page is automatically generated by %s.\n", MUD_NAME);
	send_buf(wdesc->fd, "</ADDRESS>\n");
	send_buf(wdesc->fd, "</BODY>\n");
	send_buf(wdesc->fd, "</HTML>\n");
}

void print_file(WEB_DESCRIPTOR * wdesc, char *filename)
{
	FILE *fp;
	char buf[MSL * 4];
	int c;
	int num = 0;

	if ((fp = file_open(filename, "r")) != NULL)
	{
		while (!feof(fp))
		{
			while ((buf[num] = fgetc(fp)) != EOF && buf[num] != '\n'
				   && buf[num] != '\r' && num < (MSL * 4 - 2))
				num++;
			c = fgetc(fp);
			if ((c != '\n' && c != '\r') || c == buf[num])
				ungetc(c, fp);
			buf[num++] = '\n';
			buf[num] = '\0';
			send_buf(wdesc->fd, buf);
			num = 0;
		}

		file_close(fp);
	}
	else
	{
		logf("Failed to open file %s.", filename);
		file_close(fp);
	}
}

char *format_obj_to_html(OBJ_DATA * obj)
{
	AFFECT_DATA *paf;
	static char output[MSL * 5];
	char buf[MSL * 5];

	output[0] = '\0';

	if (IS_NULLSTR(obj->description))
		return output;

	if (IS_OBJ_STAT(obj, ITEM_INVIS))
		strcat(output, "(Invis) ");
	if (IS_OBJ_STAT(obj, ITEM_DARK))
		strcat(output, "(Dark) ");
	if (IS_OBJ_STAT(obj, ITEM_EVIL))
		strcat(output, "(Red Aura) ");
	if (IS_OBJ_STAT(obj, ITEM_BLESS))
		strcat(output, "(Blue Aura) ");
	if (IS_OBJ_STAT(obj, ITEM_MAGIC))
		strcat(output, "(Magical) ");
	if (IS_OBJ_STAT(obj, ITEM_GLOW))
		strcat(output, "(Glowing) ");
	if (IS_OBJ_STAT(obj, ITEM_HUM))
		strcat(output, "(Humming) ");

	if (obj->item_type == ITEM_WEAPON)
	{
		for (paf = obj->first_affect; paf; paf = paf->next)
		{
			if (IS_SET(paf->bitvector, WEAPON_FLAMING))
				strcat(output, "(Flaming) ");
			if (IS_SET(paf->bitvector, WEAPON_FROST))
				strcat(output, "(Frost) ");
			if (IS_SET(paf->bitvector, WEAPON_VAMPIRIC))
				strcat(output, "(Vampiric) ");
			if (IS_SET(paf->bitvector, WEAPON_SHOCKING))
				strcat(output, "(Shocking) ");
			if (IS_SET(paf->bitvector, WEAPON_POISON))
				strcat(output, "(Poison) ");
			if (IS_SET(paf->bitvector, WEAPON_SHARP))
				strcat(output, "(Sharp) ");
			if (IS_SET(paf->bitvector, WEAPON_VORPAL))
				strcat(output, "(Vorpal) ");
		}
	}

	if (IS_OBJ_STAT(obj, ITEM_AUCTIONED))
		strcat(output, "(Auctioned) ");

	if (obj->condition <= 9 && obj->condition >= 0)
		strcat(output, "(Ruined) ");
	else if (obj->condition >= 10 && obj->condition <= 24)
		strcat(output, "(Broken) ");

	if (obj->description != NULL)
	{
		char temp[MSL];

		html_colourconv(buf, obj->description);
		sprintf(temp, "<A href=\"%sobjs/%ld\">%s</A><br>",
				HTTP_URL(), obj->pIndexData->vnum, buf);
		strcat(output, temp);
	}

	return output;
}

char *show_list_to_html(OBJ_DATA * list)
{
	static char output[MSL * 6];
	const char **prgpstrShow;
	int *prgnShow;
	char *pstrShow;
	OBJ_DATA *obj;
	int nShow;
	int iShow;
	int count;
	char temp[MSL];
	bool found;

	count = 0;
	for (obj = list; obj != NULL; obj = obj->next_content)
		count++;

	alloc_mem(prgpstrShow, const char *, count);
	alloc_mem(prgnShow, int, count);

	nShow = 0;

	output[0] = '\0';

	for (obj = list; obj != NULL; obj = obj->next_content)
	{
		if (obj->wear_loc == WEAR_NONE)
		{
			pstrShow = format_obj_to_html(obj);
			found = FALSE;

			for (iShow = nShow - 1; iShow >= 0; iShow--)
			{
				if (!str_cmp(prgpstrShow[iShow], pstrShow))
				{
					prgnShow[iShow]++;
					found = TRUE;
					break;
				}
			}
			if (!found)
			{
				prgpstrShow[nShow] = str_dup(pstrShow);
				prgnShow[nShow] = 1;
				nShow++;
			}
		}
	}

	for (iShow = 0; iShow < nShow; iShow++)
	{
		if (IS_NULLSTR(prgpstrShow[iShow]))
		{
			free_string(prgpstrShow[iShow]);
			continue;
		}

		if (prgnShow[iShow] != 1)
		{
			sprintf(temp, "(%2d) ", prgnShow[iShow]);
			strcat(output, temp);
		}
		else
			strcat(output, "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;");

		sprintf(temp, "%s<br>", prgpstrShow[iShow]);
		strcat(output, temp);

		free_string(prgpstrShow[iShow]);

		if (strlen(output) > 5500)
		{
			strcat(output, "     (More stuff not shown)<br>");
			break;
		}
	}

	free_mem(prgpstrShow);
	free_mem(prgnShow);
	return output;
}

char *show_char_to_html_0(CHAR_DATA * victim)
{
	static char buf[MSL * 6];
	char cbuf[MSL * 6];
	char temp[MSL];

	buf[0] = '\0';

	if (IS_AFFECTED(victim, AFF_INVISIBLE))
		strcat(buf, "(Invis) ");
	if (victim->invis_level >= LEVEL_IMMORTAL)
		strcat(buf, "(Wizi) ");
	if (IS_AFFECTED(victim, AFF_HIDE))
		strcat(buf, "(Hide) ");
	if (IS_AFFECTED(victim, AFF_CHARM))
		strcat(buf, "(Charmed) ");
	if (IS_AFFECTED(victim, AFF_PASS_DOOR))
		strcat(buf, "(Translucent) ");
	if (IS_AFFECTED(victim, AFF_FAERIE_FIRE))
		strcat(buf, "(Pink Aura) ");
	if (IS_EVIL(victim))
		strcat(buf, "(Red Aura) ");
	if (IS_GOOD(victim))
		strcat(buf, "(Golden Aura) ");
	if (IS_AFFECTED(victim, AFF_SANCTUARY))
		strcat(buf, "(White Aura) ");
	if (!IS_NPC(victim) && !victim->desc)
		strcat(buf, "(Linkdead) ");

	if (!IS_NPC(victim))
	{
		if (IS_SET(victim->comm, COMM_AFK))
			strcat(buf, "*AFK* ");
		if (victim->war)
			strcat(buf, "(WAR) ");
		if (!IS_NPC(victim) && IS_SET(victim->act, PLR_KILLER))
			strcat(buf, "(KILLER) ");
		if (!IS_NPC(victim) && IS_SET(victim->act, PLR_THIEF))
			strcat(buf, "(THIEF) ");
		if (IS_SET(victim->comm, COMM_QUIET))
			strcat(buf, "[QUIET] ");
		if (victim->desc && victim->desc->editor != 0)
			strcat(buf, "[OLC] ");
		if (victim->pcdata->in_progress != NULL)
			strcat(buf, "[Note] ");
		if (IS_QUESTOR(victim))
			strcat(buf, "[Q] ");
	}

	if (victim->level > 0)
	{
		long vict_condition;

		if (victim->max_hit > 0)
			vict_condition = victim->hit * 100 / victim->max_hit;
		else
			vict_condition = -1;

		if (vict_condition < 0)
			strcat(buf, "(DEAD) ");
		else if (vict_condition < 33)
			strcat(buf, "(Wounded) ");
	}

	if (victim->position ==
		(IS_NPC(victim) ? victim->start_pos : POS_STANDING)
		&& !IS_NULLSTR(victim->long_descr))
	{
		if (IS_NPC(victim))
		{
			html_colourconv(cbuf, victim->long_descr);
			sprintf(temp, "<A href=\"%schars/%ld\">%s</A>", HTTP_URL(),
					victim->pIndexData->vnum, cbuf);
			strcat(buf, temp);
		}
		return (buf);
	}
	if (IS_NPC(victim))
	{
		html_colourconv(cbuf, victim->short_descr);
		sprintf(temp, "<A href=\"%schars/%ld\">%s</A>", HTTP_URL(),
				victim->pIndexData->vnum, cbuf);
		strcat(buf, temp);
	}
	else
	{
		strcat(buf, victim->name);
	}

	switch (victim->position)
	{
	case POS_DEAD:
		strcat(buf, " is DEAD!!");
		break;
	case POS_MORTAL:
		strcat(buf, " is mortally wounded.");
		break;
	case POS_INCAP:
		strcat(buf, " is incapacitated.");
		break;
	case POS_STUNNED:
		strcat(buf, " is lying here stunned.");
		break;
	case POS_SLEEPING:
		if (victim->on != NULL)
		{
			if (IS_SET(victim->on->value[2], SLEEP_AT))
			{
				sprintf(temp, " is sleeping at %s.", victim->on->short_descr);
				strcat(buf, temp);
			}
			else if (IS_SET(victim->on->value[2], SLEEP_ON))
			{
				sprintf(temp, " is sleeping on %s.", victim->on->short_descr);
				strcat(buf, temp);
			}
			else
			{
				sprintf(temp, " is sleeping in %s.", victim->on->short_descr);
				strcat(buf, temp);
			}
		}
		else
			strcat(buf, " is sleeping here.");
		break;
	case POS_RESTING:
		if (victim->on != NULL)
		{
			if (IS_SET(victim->on->value[2], REST_AT))
			{
				sprintf(temp, " is resting at %s.", victim->on->short_descr);
				strcat(buf, temp);
			}
			else if (IS_SET(victim->on->value[2], REST_ON))
			{
				sprintf(temp, " is resting on %s.", victim->on->short_descr);
				strcat(buf, temp);
			}
			else
			{
				sprintf(temp, " is resting in %s.", victim->on->short_descr);
				strcat(buf, temp);
			}
		}
		else
			strcat(buf, " is resting here.");
		break;
	case POS_SITTING:
		if (victim->on != NULL)
		{
			if (IS_SET(victim->on->value[2], SIT_AT))
			{
				sprintf(temp, " is sitting at %s.", victim->on->short_descr);
				strcat(buf, temp);
			}
			else if (IS_SET(victim->on->value[2], SIT_ON))
			{
				sprintf(temp, " is sitting on %s.", victim->on->short_descr);
				strcat(buf, temp);
			}
			else
			{
				sprintf(temp, " is sitting in %s.", victim->on->short_descr);
				strcat(buf, temp);
			}
		}
		else
			strcat(buf, " is sitting here.");
		break;
	case POS_STANDING:
		if (victim->on != NULL)
		{
			if (IS_SET(victim->on->value[2], STAND_AT))
			{
				sprintf(temp, " is standing at %s.", victim->on->short_descr);
				strcat(buf, temp);
			}
			else if (IS_SET(victim->on->value[2], STAND_ON))
			{
				sprintf(temp, " is standing on %s.", victim->on->short_descr);
				strcat(buf, temp);
			}
			else
			{
				sprintf(temp, " is standing in %s.", victim->on->short_descr);
				strcat(buf, temp);
			}
		}
		strcat(buf, " is here.");
		break;
	case POS_FIGHTING:
		strcat(buf, " is here, fighting ");
		if (victim->fighting == NULL)
			strcat(buf, "thin air??");
		else if (victim->in_room == victim->fighting->in_room)
		{
			strcat(buf,
				   IS_NPC(victim) ? victim->fighting->short_descr : victim->
				   fighting->name);
			strcat(buf, ".");
		}
		else
			strcat(buf, "someone who left??");
		break;
	}
	return (buf);
}

char *show_char_to_html(CHAR_DATA * list)
{
	CHAR_DATA *rch;
	static char output[MSL * 10];

	output[0] = '\0';
	for (rch = list; rch != NULL; rch = rch->next_in_room)
	{
		if (rch->invis_level >= LEVEL_IMMORTAL)
			continue;

		strcat(output, show_char_to_html_0(rch));
		strcat(output, "<br>");
	}
	return (output);
}

HANDLE_URL(HandleMemoryRequest)
{
	int sn, count_spell = 0, count_skill = 0;

	for (sn = 0; sn < maxSkill; sn++)
	{
		if (skill_table[sn].name == NULL)
			break;

		if (skill_table[sn].spell_fun != spell_null)
			count_spell += 1;
		else
			count_skill += 1;
	}
	print_header(wdesc, "Technical Info");
	send_buf(wdesc->fd, "<TABLE>\n");
	send_buf(wdesc->fd, "<TR>\n");
	send_buf(wdesc->fd, "<TD>Affects</TD>  <TD>%5d</TD></TR>\n", top_affect);
	send_buf(wdesc->fd, "<TR>\n");
	send_buf(wdesc->fd, "<TD>Areas</TD> <TD>%5d</TD></TR>\n", top_area);
	send_buf(wdesc->fd, "<TR>\n");
	send_buf(wdesc->fd, "<TD>ExDes</TD> <TD>%5d</TD></TR>\n", top_ed);
	send_buf(wdesc->fd, "<TR>\n");
	send_buf(wdesc->fd, "<TD>Exits</TD> <TD>%5d</TD></TR>\n", top_exit);
	send_buf(wdesc->fd, "<TR>\n");
	send_buf(wdesc->fd, "<TD>Helps</TD> <TD>%5d</TD></TR>\n", top_help);
	send_buf(wdesc->fd, "<TR>\n");
	send_buf(wdesc->fd, "<TD>Socials</TD> <TD>%5d</TD></TR>\n", maxSocial);
	send_buf(wdesc->fd, "<TR>\n");
	send_buf(wdesc->fd, "<TD>Spells</TD> <TD>%5d</TD></TR>\n", count_spell);
	send_buf(wdesc->fd, "<TR>\n");
	send_buf(wdesc->fd, "<TD>Skills</TD> <TD>%5d</TD></TR>\n", count_skill);
	send_buf(wdesc->fd, "<TR>\n");
	send_buf(wdesc->fd,
			 "<TD>Mobs</TD> <TD>%5d (%5d in use)</TD></TR>\n",
			 top_mob_index, mobile_count);
	send_buf(wdesc->fd, "<TR>\n");
	send_buf(wdesc->fd, "<TD>Objs</TD> <TD>%5d</TD></TR>\n", top_obj_index);
	send_buf(wdesc->fd, "<TR>\n");
	send_buf(wdesc->fd, "<TD>Resets</TD> <TD>%5d</TD></TR>\n", top_reset);
	send_buf(wdesc->fd, "<TR>\n");
	send_buf(wdesc->fd, "<TD>Rooms</TD> <TD>%5d</TD></TR>\n", top_room);
	send_buf(wdesc->fd, "<TR>\n");
	send_buf(wdesc->fd, "<TD>Shops</TD> <TD>%5d</TD></TR>\n", top_shop);
	send_buf(wdesc->fd, "</TABLE>\n");
	print_footer(wdesc);
	return TRUE;
}

HANDLE_URL(HandleRulesRequest)
{
	HELP_DATA *pHelp;

	print_header(wdesc, "Rules");
	send_buf(wdesc->fd, "<PRE>\n");
	if ((pHelp = help_lookup("RULES")) != NULL)
		send_buf(wdesc->fd, fix_string(smash_colour(pHelp->text)));
	else
		send_buf(wdesc->fd, "There are no rules!! Anarchy!!\n");
	send_buf(wdesc->fd, "</PRE>\n");
	print_footer(wdesc);
	return TRUE;
}

HANDLE_URL(HandleAreaRequest)
{
	AREA_DATA *pArea;
	int count = 0;

	print_header(wdesc, "Areas");
	send_buf(wdesc->fd, "<TABLE>\n");
	send_buf(wdesc->fd, "<TR>\n");
	send_buf(wdesc->fd, "<TD>Low/High Level</TD>\n");
	send_buf(wdesc->fd, "<TD>Author</TD>\n");
	send_buf(wdesc->fd, "<TD>Area Name</TD></TR>\n");
	for (pArea = area_first;;)
	{
		if (pArea == NULL)
			break;
		if (!IS_SET(pArea->area_flags, AREA_CLOSED | AREA_PLAYER_HOMES))
		{
			send_buf(wdesc->fd, "<TR>\n");
			if (!IS_NULLSTR(pArea->lvl_comment))
				send_buf(wdesc->fd, "<TD>%s</TD>\n", pArea->lvl_comment);
			else
				send_buf(wdesc->fd, "<TD>%03d %03d</TD>\n", pArea->min_level,
						 pArea->max_level);
			send_buf(wdesc->fd, "<TD>%s</TD>\n", pArea->credits);
			send_buf(wdesc->fd, "<TD>%s</TD></TR>\n", pArea->name);
			count++;
		}
		pArea = pArea->next;
	}
	send_buf(wdesc->fd, "</TABLE>\n");
	send_buf(wdesc->fd, "<P>Areas Found: %d.</P>\n", count);
	print_footer(wdesc);
	return TRUE;
}

HANDLE_URL(HandleSpellsRequest)
{
	int gn;
	int sn, i;
	int tn;
	RACE_DATA *race;
	bool *displayed;
	int past_default = TRUE;
	char buf[MSL];
	char buf2[MSL];
	char buf3[MSL];

	alloc_mem(displayed, bool, maxSkill);
	memset(displayed, FALSE, maxSkill);

	print_header(wdesc, "Spells and Skills");

	for (gn = 0; gn < maxGroup; gn++)
	{
		if (group_table[gn].name == NULL)
			break;

		if (!str_cmp(group_table[gn].name, "rom basics"))
		{
			send_buf(wdesc->fd, "<H3>Basic Skills</H3>\n");
			send_buf(wdesc->fd,
					 "All players receive the <I>basic</I> skills for their class.<BR>\n");
			send_buf(wdesc->fd, "<TABLE><TR>\n");
			send_buf(wdesc->fd, "<TD>Basic Group</TD>\n");
			send_buf(wdesc->fd, "<TD>Skills Included</TD></TR>\n");
		}

		if (!str_cmp(group_table[gn].name, "mage default"))
		{

			past_default = FALSE;
			send_buf(wdesc->fd, "</TABLE>\n");
			send_buf(wdesc->fd, "<H3>Default Skill/Spell Groups</H3>\n");
			send_buf(wdesc->fd,
					 "Players receive the <I>default</I> skills and spell groups if they bypass customization.<BR>\n");
			send_buf(wdesc->fd, "<TABLE><TR>\n");
			send_buf(wdesc->fd, "<TD>Default Group</TD>\n");
			send_buf(wdesc->fd, "<TD>Skills Included</TD>\n");
			send_buf(wdesc->fd, "<TD>Skill/Spell Groups Included</TD></TR>\n");
		}

		if (!str_cmp(group_table[gn].name, "weaponsmaster"))
		{
			past_default = TRUE;
			send_buf(wdesc->fd, "</TABLE>\n");
			send_buf(wdesc->fd, "<H3>Other Skill/Spell Groups</H3>\n");
			send_buf(wdesc->fd,
					 "Players may gain these skills and spells.<BR>\n");
			send_buf(wdesc->fd, "<TABLE><TR>\n");
			send_buf(wdesc->fd, "<TD>Skill/Spell Group</TD>\n");
			send_buf(wdesc->fd, "<TD>Skills/Spells Included</TD></TR>\n");
		}

		under_line(buf3, group_table[gn].name);
		send_buf(wdesc->fd, "<TR><TD><A NAME=\"%s\">%s</A></TD><TD>",
				 buf3, group_table[gn].name);

		buf[0] = '\0';
		for (sn = 0; sn < MAX_IN_GROUP; sn++)
		{
			if (group_table[gn].spells[sn] == NULL)
				break;
			tn = skill_lookup(group_table[gn].spells[sn]);
			if (tn != -1)

			{
				sprintf(buf2, "%s, ", group_table[gn].spells[sn]);
				strcat(buf, buf2);

				displayed[tn] = TRUE;
			}
		}
		if (!IS_NULLSTR(buf) && (strlen(buf) > 2))
			buf[(strlen(buf) - 2)] = '\0';

		else
			strcpy(buf, "None");

		if (!past_default)
		{
			send_buf(wdesc->fd, "%s</TD><TD>\n", buf);

			buf[0] = '\0';
			for (sn = 0; sn < MAX_IN_GROUP; sn++)
			{
				if (group_table[gn].spells[sn] == NULL)
					break;
				tn = skill_lookup(group_table[gn].spells[sn]);
				if (tn == -1)

				{
					under_line(buf3, group_table[gn].spells[sn]);
					sprintf(buf2, "<A HREF=\"#%s\">%s</A>, ", buf3,
							group_table[gn].spells[sn]);
					strcat(buf, buf2);
				}
				else
				{

					displayed[tn] = TRUE;
				}
			}
			if (!IS_NULLSTR(buf) && (strlen(buf) > 2))
				buf[(strlen(buf) - 2)] = '\0';

			else
				strcpy(buf, "None");
		}
		send_buf(wdesc->fd, "%s</TD></TR>\n", buf);
	}
	send_buf(wdesc->fd, "</TABLE>\n");

	send_buf(wdesc->fd, "<H3>Other Skills and Spells</H3>\n");
	send_buf(wdesc->fd,
			 "The following skills and spells are available to various mortals.<BR>\n");
	send_buf(wdesc->fd, "<TABLE><TR><TD>\n");
	buf[0] = '\0';

	for (sn = 1; sn < maxSkill; sn++)
	{
		if (skill_table[sn].name == NULL)
			break;

		if (!displayed[sn] && (min_class_level(sn) < ANGEL))
		{
			sprintf(buf2, "%s, ", skill_table[sn].name);
			strcat(buf, buf2);
			displayed[sn] = TRUE;
		}
	}
	if (!IS_NULLSTR(buf) && (strlen(buf) > 2))
		buf[(strlen(buf) - 2)] = '\0';

	else
		strcpy(buf, "None");
	send_buf(wdesc->fd, "%s</TD></TR>\n", buf);
	send_buf(wdesc->fd, "</TABLE>\n");

	send_buf(wdesc->fd,
			 "<P>To view skills in class specific tables, visit the <a href=\"%sclass\">Classes</a> section.</P>\n",
			 HTTP_URL());

	send_buf(wdesc->fd, "<H3><A NAME=\"RaceSkills\">Race Skills</A></H3>\n");
	send_buf(wdesc->fd, "The following skills and spells are race specific.\n");

	send_buf(wdesc->fd, "<TABLE><TR>\n");

	send_buf(wdesc->fd, "<TD>Race</TD>\n");
	send_buf(wdesc->fd, "<TD>Skills</TD></TR>\n");

	for (race = race_first; race; race = race->next)
	{
		if (!race->pc_race)
			continue;
		send_buf(wdesc->fd, "<TR><TD>%s</TD><TD>\n", race->name);
		buf[0] = '\0';
		for (sn = 1; sn < maxSkill; sn++)
		{
			if (skill_table[sn].name == NULL)
				break;
			for (i = 0; i < 5; i++)
			{
				if (race->skills[i] == NULL)
					break;
				if (skill_lookup(race->skills[i]) == sn)
				{
					sprintf(buf2, "%s, ", skill_table[sn].name);
					strcat(buf, buf2);
					displayed[sn] = TRUE;
				}
			}
		}
		if (!IS_NULLSTR(buf) && (strlen(buf) > 2))
			buf[(strlen(buf) - 2)] = '\0';

		else
			strcpy(buf, "None");
		send_buf(wdesc->fd, "%s</TD></TR>\n", buf);
	}
	send_buf(wdesc->fd, "</TABLE>\n");

	send_buf(wdesc->fd, "<H3>Immortal Skills and Spells</H3>\n");
	send_buf(wdesc->fd,
			 "The following skills and spells are available to various immortals.\n");

	send_buf(wdesc->fd,
			 "This list includes some spells under development.<BR>\n");
	send_buf(wdesc->fd, "<TABLE><TR>\n");

	send_buf(wdesc->fd, "<TD>Spells</TD></TR>\n");
	send_buf(wdesc->fd, "<TR><TD>\n");
	buf[0] = '\0';
	for (sn = 1; sn < maxSkill; sn++)
	{
		if (skill_table[sn].name == NULL)
			break;
		if (!displayed[sn])
		{
			sprintf(buf2, "%s, ", skill_table[sn].name);
			strcat(buf, buf2);
			displayed[sn] = TRUE;
		}
	}
	if (!IS_NULLSTR(buf) && (strlen(buf) > 2))
		buf[(strlen(buf) - 2)] = '\0';

	else
		strcpy(buf, "None");
	send_buf(wdesc->fd, "%s</TD></TR>\n", buf);
	send_buf(wdesc->fd, "</TABLE>\n");
	print_footer(wdesc);
	free_mem(displayed);
	return TRUE;
}

HANDLE_URL(HandleWhoRequest)
{
	CHAR_DATA *wch;
	char buf[MSL * 2];

	print_header(wdesc, "Players currently");
	send_buf(wdesc->fd, "<TABLE><TR>\n");
	send_buf(wdesc->fd, "<TD><I>Level</I></TD>\n");
	send_buf(wdesc->fd, "<TD><I>Race</I></TD>\n");
	send_buf(wdesc->fd, "<TD><I>Class</I></TD>\n");
	send_buf(wdesc->fd, "<TD><I>Clan</I></TD>\n");
	send_buf(wdesc->fd, "<TD><I>Name<I></TD></TR>\n");
	for (wch = player_first; wch != NULL; wch = wch->next_player)
	{
		if (wch->invis_level >= LEVEL_IMMORTAL
			|| wch->incog_level >= LEVEL_IMMORTAL)
			continue;

		send_buf(wdesc->fd, "<TR>\n");
		if (IS_NULLSTR(wch->pcdata->who_descr))
		{
			send_buf(wdesc->fd, "<TD>%d</TD>\n", wch->level);
			send_buf(wdesc->fd,
					 "<TD><A href=\"%sraces/%s\">%s</A></TD>\n",
					 HTTP_URL(), wch->race->name, wch->race->name);
			send_buf(wdesc->fd, "<TD>%s</TD>\n", class_who(wch));
		}
		else
		{
			html_colourconv(buf, wch->pcdata->who_descr);
			send_buf(wdesc->fd, "<TD COLSPAN=3>%s</TD>\n", buf);
		}

		if (is_clan(wch))
		{
			html_colourconv(buf, wch->clan->who_name);
			send_buf(wdesc->fd,
					 "<TD><A href=\"%sclans/%s\">%s</A></TD>\n",
					 HTTP_URL(), wch->clan->name, buf);
		}
		else
			send_buf(wdesc->fd, "<TD></TD>\n");

		send_buf(wdesc->fd, "<TD>");
		send_buf(wdesc->fd, wch->name);

		html_colourconv(buf, wch->pcdata->title);
		send_buf(wdesc->fd, buf);
		send_buf(wdesc->fd, "</TD></TR>\n");

	}
	send_buf(wdesc->fd, "</TABLE><BR>\n");
	print_footer(wdesc);
	return TRUE;
}

const char *stat_type_name[MAX_GAMESTAT][2] = {
/*    Display           filename  */
	{"PLAYER KILLERS", "pkill"},
	{"MOB KILLERS", "mkill"},
	{"PK DEATHS", "pkdead"},
	{"MOB DEATHS", "mdead"}
};

HANDLE_URL(HandleStatsRequest)
{
	int pos;
	char *buf = get_next(path);

	if (IS_NULLSTR(buf))
	{
		print_header(wdesc, "Stats");
		send_buf(wdesc->fd, "<OL>\n");
		for (pos = 0; pos < MAX_GAMESTAT; pos++)
		{
			send_buf(wdesc->fd,
					 "<li><A HREF=\"%sstats/%s\">%s</A>\n", HTTP_URL(),
					 stat_type_name[pos][1], stat_type_name[pos][0]);
		}
		send_buf(wdesc->fd, "</OL>\n");
		print_footer(wdesc);
		return TRUE;
	}
	else
	{
		for (pos = 0; pos < MAX_GAMESTAT; pos++)
		{
			if (!str_cmp(stat_type_name[pos][1], buf))
			{
				int count_statlist args((void));
				int compare_stats args((const void *v1, const void *v2));
				char temp[MSL];
				STAT_DATA *curr;
				STAT_DATA **top;
				int count, loop;
				bool found = FALSE;
				extern int compare_type;

				sprintf(temp, "Ranking of %s", stat_type_name[pos][0]);
				print_header(wdesc, temp);
				send_buf(wdesc->fd, "<TABLE>\n");

				alloc_mem(top, STAT_DATA *, count_statlist());

				count = 0;
				compare_type = pos;
				loop = 0;
				pos = 0;
				for (curr = stat_first; curr != NULL; curr = curr->next)
				{
					top[count] = curr;
					count++;
					found = TRUE;
				}

				qsort(top, count, sizeof(*top), compare_stats);

				send_buf(wdesc->fd,
						 "<TR><TD><I>Rank</I></TD><TD><I>Name</I></TD><TD><I>Number</I></TD>"
						 "<TD><I>Rank</I></TD><TD><I>Name</I></TD><TD><I>Number</I></TD></TR>");
				for (loop = 0; loop < count; loop++)
				{
					if (loop >= 50)
						break;

					sprintf(temp, "%s<TD>%2d)</TD><TD>%-20s</TD><TD>%ld</TD>\n",
							pos == 0 ? "<TR>" : "", loop + 1, top[loop]->name,
							top[loop]->gamestat[compare_type]);
					send_buf(wdesc->fd, temp);
					if (++pos % 2 == 0)
					{
						send_buf(wdesc->fd, "</TR>");
						pos = 0;
					}

				}
				if (!found)
					send_buf(wdesc->fd,
							 "<TR><TD COLSPAN=3>No one found yet.</TD></TR>\n");

				send_buf(wdesc->fd, "</TABLE>");
				send_buf(wdesc->fd,
						 "<BR><A href=\"%sstats\">Back to Stats Index</A>\n",
						 HTTP_URL());
				print_footer(wdesc);
				free_mem(top);
				return TRUE;
			}
		}
	}
	return FALSE;
}

HANDLE_URL(HandleCommandsRequest)
{
	CMD_DATA *i;
	int pos = 0;
	HELP_DATA *pHelp;
	int count = 0;

	print_header(wdesc, "Commands");
	send_buf(wdesc->fd, "<TABLE>\n");

	for (i = cmd_first_sorted; i; i = i->next_sort)
	{
		if (i->level >= LEVEL_IMMORTAL || !i->show)
			continue;
		count = 0;
		for (pHelp = help_first; pHelp; pHelp = pHelp->next)
		{
			count++;
			if (is_name(i->name, pHelp->keyword))
				break;
		}
		if (pHelp)
			send_buf(wdesc->fd, "%s<TD><A href=\"%shelps/%d\">%s</TD>\n",
					 (pos == 0) ? "<TR>" : "", HTTP_URL(), count, i->name);
		else
			send_buf(wdesc->fd,
					 "%s<TD>%s</TD>\n", (pos == 0) ? "<TR>" : "", i->name);
		if (++pos % 5 == 0)
		{
			send_buf(wdesc->fd, "</TR>");
			pos = 0;
		}
	}
	send_buf(wdesc->fd, "</TABLE>\n");
	print_footer(wdesc);
	return TRUE;
}

HANDLE_URL(HandleHelpsRequest)
{
	HELP_DATA *pHelp;
	char *buf = get_next(path);
	int pos = 0;
	char temp[MIL];

	if (IS_NULLSTR(buf))
	{
		int count = 0;

		print_header(wdesc, "Help Files");
		send_buf(wdesc->fd, "<TABLE>\n");
		for (pHelp = help_first; pHelp != NULL; pHelp = pHelp->next)
		{
			count++;
			if (pHelp->level <= MAX_MORTAL_LEVEL && pHelp->level >= 0)
			{
				const char *temp;
				char wordkey[MSL];

				temp = pHelp->keyword;
				while (!IS_NULLSTR(temp))
				{
					wordkey[0] = '\0';
					temp = one_argument(temp, wordkey);
					send_buf(wdesc->fd,
							 "%s<TD><A HREF=\"%shelps/%d\">%s</A></TD>\n",
							 (pos == 0) ? "<TR>" : "", HTTP_URL(), count,
							 wordkey);
					if (++pos % 5 == 0)
					{
						send_buf(wdesc->fd, "</TR>");
						pos = 0;
					}
				}
			}
		}
		send_buf(wdesc->fd, "</TABLE>\n");
		print_footer(wdesc);
		return TRUE;
	}
	else
	{
		pos = 0;
		for (pHelp = help_first; pHelp != NULL; pHelp = pHelp->next)
		{
			pos++;
			sprintf(temp, "%d", pos);
			if (!str_cmp(buf, temp))
			{
				char buf2[MSL * 5];

				print_header(wdesc, pHelp->keyword);
				send_buf(wdesc->fd, "<TABLE>\n");
				send_buf(wdesc->fd, "<TR><TD>[%d] %s<TD></TR>\n", pHelp->level,
						 pHelp->keyword);
				html_colourconv(buf2, fix_string(pHelp->text));
				send_buf(wdesc->fd, "<TR><TD>%s</TD></TR>\n", buf2);
				send_buf(wdesc->fd, "</TABLE>\n");
				send_buf(wdesc->fd,
						 "<BR><A HREF=\"%shelps\">Back to Help Index</A>\n",
						 HTTP_URL());
				print_footer(wdesc);
				return TRUE;
			}
		}
	}
	return FALSE;
}

HANDLE_URL(HandleRaceRequest)
{
	RACE_DATA *race;
	char *buf = get_next(path);

	if (IS_NULLSTR(buf))
	{
		print_header(wdesc, "Races");
		send_buf(wdesc->fd, "<TABLE><TR>\n");
		send_buf(wdesc->fd, "<TD><I>Race</I></TD>\n");
		send_buf(wdesc->fd,
				 "<I><TD>Str</TD><TD>Int</TD><TD>Wis</TD><TD>Dex</TD><TD>Con</TD></I>\n");
		send_buf(wdesc->fd, "<TD><I>Creation<BR>Points</I></TD></TR>\n");
		for (race = race_first; race != NULL; race = race->next)
		{
			if (!race->pc_race)
				continue;

			send_buf(wdesc->fd, "<TR>\n");
			send_buf(wdesc->fd,
					 "<TD><A HREF=\"%s"
					 "races/%s\">%s</A></TD>\n<TD>%d</TD>\n<TD>%d</TD>\n<TD>%d</TD>\n"
					 "<TD>%d</TD>\n<TD>%d</TD><TD ALIGN=\"center\">%d</TD></TR>\n",
					 HTTP_URL(),
					 race->name, race->name,
					 race->max_stats[STAT_STR],
					 race->max_stats[STAT_INT],
					 race->max_stats[STAT_WIS],
					 race->max_stats[STAT_DEX],
					 race->max_stats[STAT_CON], race->points);
		}
		send_buf(wdesc->fd, "</TABLE>\n");
		send_buf(wdesc->fd,
				 "<P>Creation points increase the amount of experience it takes to gain a level. Maximum a stat can go is 30.</P>\n");
		send_buf(wdesc->fd,
				 "<P>To view skills and spells available to each race, visit the <a href=\"%sspells#RaceSkills\">Skill/Spell</A> section.</P>\n",
				 HTTP_URL());
		print_footer(wdesc);
		return TRUE;
	}
	else
	{
		for (race = race_first; race; race = race->next)
		{
			if (!str_cmp(race->name, buf))
			{
				HELP_DATA *pHelp;
				char buf2[MSL * 2];

				print_header(wdesc, race->name);
				if ((pHelp = help_lookup(race->name)) != NULL)
				{
					html_colourconv(buf2, pHelp->text);
					send_buf(wdesc->fd, "<P>%s</P><BR>\n", buf2);
				}
				else
					send_buf(wdesc->fd, "<P>No Info Available</P><BR>\n");

				print_footer(wdesc);
				return TRUE;
			}
		}
	}
	return FALSE;
}

HANDLE_URL(HandleClanRequest)
{
	CLAN_DATA *clan;
	char buf[MSL * 3], i[MSL], j[MSL];
	MBR_DATA *pmbr;

	print_header(wdesc, "Clans");
	send_buf(wdesc->fd, "<TABLE>\n");
	send_buf(wdesc->fd,
			 "<TR><B><I><TD>Name</TD><TD>Leaders</TD></I></B></TR>\n");
	for (clan = clan_first; clan; clan = clan->next)
	{

		html_colourconv(buf, clan->who_name);
		i[0] = '\0';
		j[0] = '\0';
		for (pmbr = mbr_first; pmbr != NULL; pmbr = pmbr->next)
		{
			if (pmbr->clan != clan || pmbr->rank != (MAX_RANK - 1))
				continue;
			sprintf(j, " %s,", pmbr->name);
			strcat(i, j);
		}
		if (!IS_NULLSTR(i))
		{
			i[strlen(i) - 1] = '\0';
			send_buf(wdesc->fd, "<TR><TD>%s</TD><TD>%s</TD></TR>\n", buf, i);
		}
		else
			send_buf(wdesc->fd, "<TR><TD>%s</TD><TD>None</TD></TR>\n", buf);
	}
	send_buf(wdesc->fd, "</TABLE>\n");
	print_footer(wdesc);
	return TRUE;
}

HANDLE_URL(HandleClassRequest)
{
	int i;
	char *buf = get_next(path);

	if (IS_NULLSTR(buf))
	{
		print_header(wdesc, "Classes");
		send_buf(wdesc->fd, "<TABLE>\n");
		for (i = 0; i < maxClass; i++)
		{
			send_buf(wdesc->fd,
					 "<TR><TD><A HREF=\"%sclass/%s\">%s</A>"
					 "</TD></TR>\n", HTTP_URL(),
					 class_table[i].name, class_table[i].name);
		}
		send_buf(wdesc->fd, "</TABLE>\n");
		print_footer(wdesc);
		return TRUE;
	}
	else
	{
		for (i = 0; i < maxClass; i++)
		{
			if (!str_cmp(class_table[i].name, buf))
			{
				char buf2[MSL * 2], buf3[MSL];
				char skill_list[MAX_MORTAL_LEVEL + 1][MSL];
				int snc, lev;
				HELP_DATA *pHelp;

				print_header(wdesc, class_table[i].name);
				if ((pHelp = help_lookup(class_table[i].name)) != NULL)
				{
					html_colourconv(buf2, pHelp->text);
					send_buf(wdesc->fd, "<TABLE><TR><TD>%s</TD></TR></TABLE>\n",
							 buf2);
				}
				send_buf(wdesc->fd, "<TABLE>\n");
				for (lev = 0; lev < MAX_MORTAL_LEVEL + 1; lev++)
					skill_list[lev][0] = '\0';

				for (snc = 0; snc < maxSkill; snc++)
				{
					if (skill_table[snc].name == NULL)
						break;

					if ((lev = skill_table[snc].skill_level[i]) <=
						MAX_MORTAL_LEVEL)
					{
						sprintf(buf3, "%s, ", skill_table[snc].name);
						if (IS_NULLSTR(skill_list[lev]))
							sprintf(skill_list[lev],
									"<TR><TD>Level %d</TD><TD>%s", lev, buf3);
						else
							strcat(skill_list[lev], buf3);
					}
				}
				for (lev = 0; lev < MAX_MORTAL_LEVEL + 1; lev++)
				{
					if (skill_list[lev][0] != '\0')
					{
						if (strlen(skill_list[lev]) > 2)
							skill_list[lev][(strlen(skill_list[lev]) - 2)] =
								'\0';

						send_buf(wdesc->fd, skill_list[lev]);
						send_buf(wdesc->fd, "</TD></TR>\n");
					}

				}
				send_buf(wdesc->fd, "</TABLE>\n");
				print_footer(wdesc);
				return TRUE;
			}
		}
	}
	return FALSE;
}

HANDLE_URL(HandleNotesRequest)
{
	char *buf = get_next(path);
	int pos = 0;

	if (IS_NULLSTR(buf))
	{
		print_header(wdesc, "Notes");
		send_buf(wdesc->fd, "<OL>\n");
		for (pos = 0; pos < MAX_BOARD - 1; pos++)
		{
			send_buf(wdesc->fd,
					 "<li><A HREF=\"%snotes/%s\">%s</A>\n", HTTP_URL(),
					 boards[pos].short_name, boards[pos].short_name);
		}
		send_buf(wdesc->fd, "</OL>\n");
		print_footer(wdesc);
		return TRUE;
	}
	else
	{
		for (pos = 0; pos < MAX_BOARD - 1; pos++)
		{
			if (!str_cmp(boards[pos].short_name, buf))
			{
				BOARD_DATA *board = &boards[pos];
				NOTE_DATA *pnote;
				char buf2[MSL * 5];

				sprintf(buf2, "Notes on %s Board", board->short_name);
				print_header(wdesc, buf2);
				for (pnote = board->note_first; pnote != NULL;
					 pnote = pnote->next)
				{
					if (!is_name("all", pnote->to_list))
						continue;

					html_colourconv(buf2, pnote->text);
					send_buf(wdesc->fd,
							 "<TABLE><TR><TD><b>%s</b></TD><TD>%s</TD></TR><TR><TD><B>Date</B></TD>\n"
							 "<TD>%s</TD></TR><TR><TD COLSPAN=2>%s</TD><TR></TABLE><BR>\n",
							 pnote->sender, pnote->subject, pnote->date, buf2);
				}
				send_buf(wdesc->fd,
						 "<BR><A href=\"%snotes\">Back to Board Index</A>\n",
						 HTTP_URL());
				print_footer(wdesc);
				return TRUE;
			}
		}
	}
	return FALSE;
}

HANDLE_URL(HandleObjsRequest)
{
	OBJ_INDEX_DATA *pObj;
	char *buf = get_next(path);

	if (!IS_NULLSTR(buf))
	{
		if (!is_number(buf))
		{
			return FALSE;
		}
		if ((pObj = get_obj_index(atol(buf))) != NULL)
		{
			char buf2[MSL * 3];

			print_header(wdesc, smash_colour(pObj->short_descr));
			html_colourconv(buf2, pObj->short_descr);
			send_buf(wdesc->fd, "<P>%s<br>", buf2);
			html_colourconv(buf2, pObj->description);
			send_buf(wdesc->fd, "%s<br>", buf2);
			send_buf(wdesc->fd, "Material: %s<br>", pObj->material);
			send_buf(wdesc->fd, "Type: %s<br></P>", item_name(pObj->item_type));
			print_footer(wdesc);
			return TRUE;
		}
	}
	return FALSE;
}

HANDLE_URL(HandleMobsRequest)
{
	MOB_INDEX_DATA *pMob;
	char *buf = get_next(path);

	if (!IS_NULLSTR(buf))
	{
		if (!is_number(buf))
		{
			return FALSE;
		}
		if ((pMob = get_mob_index(atol(buf))) != NULL)
		{
			char buf2[MSL * 3];

			print_header(wdesc, smash_colour(pMob->short_descr));
			html_colourconv(buf2, pMob->short_descr);
			send_buf(wdesc->fd, "<P>%s<br>", buf2);
			html_colourconv(buf2, pMob->description);
			send_buf(wdesc->fd, "%s<br>", buf2);
			send_buf(wdesc->fd, "Race: %s<br>", pMob->race->name);
			send_buf(wdesc->fd, "Sex: %s<br>", sex_table[pMob->sex].name);
			send_buf(wdesc->fd, "Alignment: %d<br>", pMob->alignment);
			send_buf(wdesc->fd, "</P>");
			print_footer(wdesc);
			return TRUE;
		}
	}
	return FALSE;
}

HANDLE_URL(HandleRoomsRequest)
{
	ROOM_INDEX_DATA *pRoom;
	char *buf = get_next(path);
	vnum_t vnum;

	if (IS_NULLSTR(buf))
	{
		vnum = ROOM_VNUM_TEMPLE;
	}
	else
	{
		if (!is_number(buf))
		{
			return FALSE;
		}
		vnum = atol(buf);
	}
	if ((pRoom = get_room_index(vnum)) != NULL)
	{
		if (!IS_SET(pRoom->area->area_flags, AREA_CLOSED | AREA_PLAYER_HOMES))
		{
			EXIT_DATA *pexit;
			int door;
			char buf2[MSL * 4];

			print_header(wdesc, pRoom->name);
			send_buf(wdesc->fd, "<TABLE><TR><TD colspan=3>");
			html_colourconv(buf2, pRoom->name);
			send_buf(wdesc->fd, buf2);
			send_buf(wdesc->fd, "</TD><TD colspan=3>%s", pRoom->area->name);
			send_buf(wdesc->fd, "</TD></TR><TR><TD colspan=6>");
			html_colourconv(buf2, pRoom->description);
			send_buf(wdesc->fd, buf2);
			send_buf(wdesc->fd, "</TD></TR><TR>");
			for (door = 0; door < MAX_DIR; door++)
			{
				if ((pexit = pRoom->exit[door]) != NULL
					&& pexit->u1.to_room != NULL)
				{
					send_buf(wdesc->fd,
							 "<TD><A href=\"%srooms/%ld\">%s</A></TD>",
							 HTTP_URL(), pexit->u1.to_room->vnum,
							 dir_name[door]);
				}
				else
					send_buf(wdesc->fd, "<TD></TD>");
			}
			send_buf(wdesc->fd, "</TR><TR><TD colspan=6>%s</TD>",
					 show_list_to_html(pRoom->first_content));
			send_buf(wdesc->fd, "</TR><TR><TD colspan=6>%s</TD>",
					 show_char_to_html(pRoom->first_person));

			send_buf(wdesc->fd, "</TR></TABLE>");
			print_footer(wdesc);
			return TRUE;
		}
	}
	return FALSE;
}

HANDLE_URL(HandleSocialsRequest)
{
	SOCIAL_DATA *iSocial;
	int i;

	print_header(wdesc, "Socials");
	send_buf(wdesc->fd, "<TABLE><TR>\n");
	for (iSocial = social_first; iSocial; iSocial = iSocial->next)
		send_buf(wdesc->fd, "%s<TD>%s</TD>\n",
				 i++ % 5 == 0 ? "</TR><TR>" : "", iSocial->name);
	send_buf(wdesc->fd, "%s</TABLE>\n", i % 5 != 0 ? "</TR>" : "");
	print_footer(wdesc);
	return TRUE;
}

HANDLE_URL(HandleImmRequest)
{
	if (get_name_password(wdesc, stuff))
	{
		char *buf = get_next(path);

		if (IS_NULLSTR(buf))
		{
			print_header(wdesc, "Immortal Info Page");
			send_buf(wdesc->fd,
					 "<hr><P><A href=\"%sstaffarea/skdebug\">Skill Debug List</A></P>",
					 HTTP_URL());
			send_buf(wdesc->fd,
					 "<P><A href=\"%sstaffarea/log\">Log Files</A></P>",
					 HTTP_URL());
			send_buf(wdesc->fd, "<hr>");
			print_footer(wdesc);
			return TRUE;
		}
		else
		{
			if (!str_prefix("skdebug", buf))
			{
				int i, sn;

				print_header(wdesc, "Skill List");
				send_buf(wdesc->fd, "<TABLE><TR><TD>Skill Name</TD>\n");
				for (i = 0; i < maxClass; i++)
					send_buf(wdesc->fd, "<TD>%s</TD>\n", class_table[i].name);
				send_buf(wdesc->fd, "</TR>\n");
				for (sn = 0; sn < maxSkill; sn++)
				{
					send_buf(wdesc->fd, "<TR><TD>%s</TD>\n",
							 skill_table[sn].name);
					for (i = 0; i < maxClass; i++)
					{
						if (skill_table[sn].skill_level[i] >= LEVEL_IMMORTAL)
							send_buf(wdesc->fd, "<TD>---</TD>\n");
						else
							send_buf(wdesc->fd, "<TD>%d</TD>\n",
									 skill_table[sn].skill_level[i]);
					}
					send_buf(wdesc->fd, "</TR>\n");
				}
				send_buf(wdesc->fd, "</TABLE>");
				print_footer(wdesc);
				return TRUE;
			}
			else if (!str_prefix("log", buf))
			{
				char buf2[MSL];
				char *check = get_next(buf);
				DIR *Directory;
				struct dirent *Dir;

				if (IS_NULLSTR(check))
				{
					char buff[MSL];
					int count = 0;

					print_header(wdesc, "Log Files");
					send_buf(wdesc->fd, "<TABLE><TR>\n");
					Directory = opendir("../log");
					Dir = readdir(Directory);
					while (Dir != NULL)
					{
						if (!str_suffix(".log", Dir->d_name))
						{
							strcpy(buff, Dir->d_name);
							buff[strlen(buff) - 4] = '\0';
							send_buf(wdesc->fd,
									 "%s<TD><A href=\"%s%s/log/%s\">%s</A></TD>\n",
									 count % 5 == 0 ? "</TR><TR>" : "",
									 HTTP_URL(), "staffarea", buff,
									 Dir->d_name);
							count++;
						}
						Dir = readdir(Directory);
					}
					closedir(Directory);
					send_buf(wdesc->fd, "%s</TABLE>\n",
							 count % 5 != 0 ? "</TR>" : "");
					print_footer(wdesc);
					return TRUE;
				}
				else
				{
					Directory = opendir("../log");
					Dir = readdir(Directory);
					while (Dir != NULL)
					{
						if (!str_suffix(".log", Dir->d_name))
						{
							strcpy(buf2, Dir->d_name);
							buf2[strlen(buf2) - 4] = '\0';
							if (!str_cmp(buf2, check))
							{
								print_header(wdesc, buf2);
								send_buf(wdesc->fd, "<PRE>\n");
								sprintf(buf2, "../log/%s", Dir->d_name);
								print_file(wdesc, buf2);
								send_buf(wdesc->fd, "</PRE>\n");
								print_footer(wdesc);
								return TRUE;
							}
						}
						Dir = readdir(Directory);
					}
					closedir(Directory);
				}
				return FALSE;
			}
		}
		return FALSE;
	}
	else
	{
		print_header(wdesc, "Invalid Username/Password");
		send_buf(wdesc->fd,
				 "<P>Invalid username/password.  Each field is Case Sensitive.</P>\n");
		print_footer(wdesc);
		return TRUE;
	}
}

HANDLE_URL(HandleIndexRequest)
{
	print_header(wdesc, "Welcome");
	send_buf(wdesc->fd, "<P>Connect to <A href=\"" TELNET_URL "\">%s</A></P>\n",
			 port, MUD_NAME);
	print_footer(wdesc);
	return TRUE;
}

HANDLE_URL(HandleUnknownRequest)
{
	print_header(wdesc, "Error");
	send_buf(wdesc->fd, "<p>Unknown url '%s'.</p>", path);
	print_footer(wdesc);
	return TRUE;
}

const struct request_type request_table[] = {
	{"index", "Home", HandleIndexRequest, FALSE},
	{"online", "Online", HandleWhoRequest, FALSE},
	{"tech", "Tech", HandleMemoryRequest, FALSE},
	{"areas", "Areas", HandleAreaRequest, FALSE},
	{"spells", "Spells", HandleSpellsRequest, FALSE},
	{"rules", "Rules", HandleRulesRequest, FALSE},
	{"socials", "Socials", HandleSocialsRequest, FALSE},
	{"helps", "Helps", HandleHelpsRequest, FALSE},
	{"notes", "Notes", HandleNotesRequest, FALSE},
	{"races", "Races", HandleRaceRequest, FALSE},
	{"commands", "Commands", HandleCommandsRequest, FALSE},
	{"clans", "Clans", HandleClanRequest, FALSE},
	{"class", "Classes", HandleClassRequest, FALSE},
	{"stats", "Stats", HandleStatsRequest, FALSE},
	{"rooms", "Explore", HandleRoomsRequest, FALSE},
	{"objs", NULL, HandleObjsRequest, FALSE},
	{"chars", NULL, HandleMobsRequest, FALSE},
	{"staffarea", "Imm Only", HandleImmRequest, TRUE},
	{NULL, NULL, NULL, 0}
};

void handle_web_request(WEB_DESCRIPTOR * wdesc)
{
	char temp[MSL];
	const char *stuff;
	char *path;
	int addr, i;
	char web_buf[MSL];

	stuff = first_arg(wdesc->request, temp, FALSE);
	first_arg(stuff, temp, FALSE);

	path = temp;

	if (path[0] == '/')
		path++;

	/* process request */
	/* are we using HTTP/1.x? If so, write out header stuff.. */
	if (!strstr(wdesc->request, "GET"))
	{
		send_buf(wdesc->fd, "HTTP/1.1 501 Not Implemented");
		return;
	}

	addr = ntohl(wdesc->their_addr.sin_addr.s_addr);

	strcpy(web_buf, inet_ntoa(wdesc->their_addr.sin_addr));

	if (IS_NULLSTR(path))
	{
		HandleIndexRequest(wdesc, "", "");
		return;
	}

	logf("WebServer: %s requested '%s'.", web_buf, path);

	for (i = 0; request_table[i].fun != NULL; i++)
	{
		if (request_table[i].secure && !strstr(wdesc->request, "HTTP/1."))
			break;

		if (!str_prefix(request_table[i].req, path))
		{
			if ((*request_table[i].fun) (wdesc, path, stuff))
			{
				tail_chain();
				return;
			}
			else
				break;
		}
	}

	HandleUnknownRequest(wdesc, path, stuff);
	tail_chain();
}

void shutdown_web_server(void)
{
	WEB_DESCRIPTOR *current, *next;

	/* Stop Listening */
	log_string("Closing webserver...");
	close(web_socket);
	web_socket = -1;
	WebUP = FALSE;

	/* Close All Current Connections */
	for (current = first_webdesc; current; current = next)
	{
		next = current->next;
		close(current->fd);
		UNLINK(current, first_webdesc, last_webdesc, next, prev);
		free_mem(current);
	}
}

#endif