/* 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!
 */

/*

1. Add websvr.o to the makefile.
2. Add the following to the section with all the prototypes at the end
   of merc.h:

	/* websvr.c */
	void init_web(int port);
	void handle_web(void);
	void shutdown_web(void);

3. In comm.c, right after boot_db() in main(), add a call to init_web().
   Note that it takes a parameter for the port number (I use port+1, so
   if your mud runs on port 8000, it would run on 8001).  Note, this will
   not run under MS_DOS or MAC, so don't bother adding it to the dos part
   of main, only before the call to game_loop_unix.
4. Again in comm.c, after the call to game_loop_unix, add a call to
   shutdown_web()
5. Someplace inside the loop in game_loop_unix, add a call to handle_web().
   I put mine between input and output (to allow for possible future wiznet
   notifications.
6. Modify handle_web_who_request() to reflect your personal taste (the current
   version may not even work with your mud).
7. Recompile.
8. Go to http://your.mud.server:webport/wholist to see who is on line. (Webport
   is whatever was defined in step 2).

*/

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/time.h>
#include "merc.h"

#define MAXDATA 1024

typedef struct web_descriptor WEB_DESCRIPTOR;

struct web_descriptor {
    int fd;
    char request[MAXDATA*2];
    struct sockaddr_in their_addr;
    int sin_size;
    WEB_DESCRIPTOR *next;	
    bool valid;
};

WEB_DESCRIPTOR *web_desc_free;

/* FUNCTION DEFS */
int send_buf(int fd, const char* buf);
void handle_web_request(WEB_DESCRIPTOR *wdesc);
void handle_web_who_request(WEB_DESCRIPTOR *wdesc);
WEB_DESCRIPTOR *new_web_desc(void);
void free_web_desc(WEB_DESCRIPTOR *desc);

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

/* Externs */
extern int top_web_desc;

/* Locals */
WEB_DESCRIPTOR *web_descs;
int sockfd;

void init_web(int port) {
    struct sockaddr_in my_addr;

    web_descs = NULL;

    logf("Web features starting on port: %d", port);

    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
	perror("web-socket");
	exit(1);
    }

    my_addr.sin_family = AF_INET;
    my_addr.sin_port = htons(port);
    my_addr.sin_addr.s_addr = htons(INADDR_ANY);
    bzero(&(my_addr.sin_zero),8);

    if((bind(sockfd, (struct sockaddr*)&my_addr, sizeof(struct sockaddr))) == -1)
    {
	perror("web-bind");
	exit(1);
    }

    /* Only listen for 5 connects at once, do we really need more? */
    listen(sockfd, 5);
}

struct timeval ZERO_TIME = { 0, 0 };

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

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

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

	/* add in all the current web descriptors */
	for(current=web_descs; current != NULL; 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(sockfd, &readfds)) {
            /* NEW CONNECTION -- INIT & ADD TO LIST */

	    current = new_web_desc();
	    current->sin_size  = sizeof(struct sockaddr_in);
	    current->request[0] = '\0';

	    if((current->fd = accept(sockfd, (struct sockaddr *)&(current->their_addr), &(current->sin_size))) == -1) {
	    	perror("web-accept");
	    	exit(1);
	    }

	    current->next = web_descs;
	    web_descs = current;

	    /* END ADDING NEW DESC */
	}

	/* DATA IN! */
	for(current=web_descs; current != NULL; 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");
		    exit(1);
		}

		buf[numbytes] = '\0';

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

	/* DATA OUT */
	for(current=web_descs; current != NULL; 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);

	    if(web_descs == current) {
		web_descs = current->next;
	    } else {
		for(prev=web_descs; prev->next != current; prev = prev->next)
			; /* Just ititerate through the list */
		prev->next = current->next;
	    }

	    free_web_desc(current);
	}   /* END DATA-OUT */
}

/* Generic Utility Function */

int send_buf(int fd, const char* buf) {
	return send(fd, buf, strlen(buf), 0);
}

void handle_web_request(WEB_DESCRIPTOR *wdesc) {
	    /* 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.0 501 Not Implemented");
		return;
	    } else if(strstr(wdesc->request, "HTTP/1.")) {
		send_buf(wdesc->fd,"HTTP/1.0 200 OK\n");
		send_buf(wdesc->fd,"Content-type: text/html\n\n");
	    }

	    /* Handle the actual request */
	    if(strstr(wdesc->request, "/wholist")) {
		log_string("Web Hit: WHOLIST");
		handle_web_who_request(wdesc);
	    } else {
		log_string("Web Hit: INVALID URL");
		send_buf(wdesc->fd,"Sorry, ROM Integrated Webserver 1.0 only supports /wholist");
	    }
}

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

    /* Close All Current Connections */
    for(current=web_descs; current != NULL; current = next) {
	next = current->next;
	close(current->fd);
	free_web_desc(current);
    }

    /* Stop Listening */
    close(sockfd);
}

void handle_web_who_request(WEB_DESCRIPTOR *wdesc)
{
  CHAR_DATA *victim;
  int count=0;
  char output[MAX_STRING_LENGTH];
  char *clan_name,wizi_string[MAX_STRING_LENGTH];
  const char *class;
  int legend=95,hero=93,dragon=89,master=79,lord=69,duke=59, \
      leader=49,adven=39,explo=29,student=19,train=9;
  DESCRIPTOR_DATA *d;

  send_buf(wdesc->fd,"<HTML><HEAD><TITLE>Scale Towers Who List</TITLE></HEAD>\n\r");
  send_buf(wdesc->fd,"<BODY BGCOLOR=\"#FFFFFF\"><B>Scale Towers Who List</B><P>\n\r");

  for (d = descriptor_list; d; d = d->next)
  {
    victim = ( d->original ) ? d->original : d->character;

    if (d->connected != CON_PLAYING || victim->invis_level > 0 ||
        IS_NPC(victim)) {
      continue;
    }

    count++;
            if (victim->level == MAX_LEVEL)        class = "--OVERLORD--";
        else if (victim->level >= LEVEL_IMMORTAL)  class = " -IMMORTAL- ";
                else if (victim->level >= legend)  class = "<--LEGEND-->";
            else if (victim->level >= hero)        class = "<-->HERO<-->";
            else if (victim->level >= dragon)      class = ">DRAGONLORD<";
            else if (victim->level >= master)      class = "<<<MASTER>>>";
           else if (victim->level >= lord)         class = " ***LORD*** ";
            else if (victim->level >= duke)        class = " *-*DUKE*-* ";
            else if (victim->level >= leader)      class = " **LEADER** ";
            else if (victim->level >= adven)       class = "-ADVENTURER-";
            else if (victim->level >= explo)       class = "  EXPLORER  ";
            else if (victim->level >= student)     class = " -StUdEnT-  ";
            else if (victim->level >= train)       class = "  TRAINEE  ";
            else if (victim->level < train)        class = ">> NEWBIE <<";
            else                                class = "   ERROR    ";

    sprintf (wizi_string,"(W:%d) ", victim->invis_level);

    if ( is_clan(victim) )
        clan_name = str_dup( victim->clan_info->who_name );
    else
        clan_name = str_dup( "" );

    sprintf(output, "%s[%s] %s%s%s%s%s%s%s%s<BR>",
	    IS_IMMORTAL(victim) ? "<B>" : "",
            class,
            victim->invis_level >= LEVEL_HERO ? wizi_string : "",
            clan_name,
            IS_SET(victim->comm, COMM_AFK) ? "[AFK] " : "",
            IS_SET(victim->comm, COMM_IAW) ? "[IAW] " : "",
            d->connected >= CON_NOTE_TO ? "[NOTEING] " : "",
            victim->name, victim->pcdata->title,
	    IS_IMMORTAL(victim) ? "</B>" : "");
      send_buf(wdesc->fd,output);
  }
  sprintf(output, "<P>Scale Towers Who-List [%d players found]</BODY></HTML>", count);
  send_buf(wdesc->fd,output);
}

/* These are memory management... they should move to recycle.c soon */

WEB_DESCRIPTOR *new_web_desc(void)
{
    WEB_DESCRIPTOR *desc;

    if(web_desc_free == NULL) {
	desc = alloc_perm(sizeof(*desc));
	top_web_desc++;
    } else {
	desc = web_desc_free;
	web_desc_free = web_desc_free->next;
    }

    VALIDATE(desc);

    return desc;	
}

void free_web_desc(WEB_DESCRIPTOR *desc)
{
    if(!IS_VALID(desc))
	return;

    INVALIDATE(desc);
    desc->next = web_desc_free;
    web_desc_free = desc;
}