nakedmud_webserver/
nakedmud_webserver/webserver/
//*****************************************************************************
//
// webserver.c
//
// This is a module that opens an HTTP server on another port that allows 
// people to request various sorts of information. Useful for displaying online
// who lists, and that sort of junk.
//
//*****************************************************************************

#include "../mud.h"
#include "../utils.h"
#include "../inform.h"
#include "../event.h"

#include "webserver.h"



//*****************************************************************************
// local variables, datastructures, functions, and defines
//*****************************************************************************

// the socket for our server
int web_control;

// our list of descriptors connected
LIST *web_descs = NULL;

// what is the mapping from query:function ?
HASHTABLE *query_table = NULL;

// the datstructure for a web descriptor
typedef struct web_socket {
  int                 control;
  struct sockaddr_in     addr;
  char   inbuf[MAX_INPUT_LEN];
  int                 buf_len;
} WEB_SOCKET;

WEB_SOCKET *newWebSocket() {
  WEB_SOCKET *sock = calloc(1, sizeof(WEB_SOCKET));
  return sock;
}

void deleteWebSocket(WEB_SOCKET *sock) {
  free(sock);
}

void webSocketClose(WEB_SOCKET *sock) {
  close(sock->control);
}

//
// convert all of the color codes and ascii characters to their
// HTML equivilants.
void bufferASCIIHTML(BUFFER *buf) {
  bufferReplace(buf, "\r", "",         TRUE);
  bufferReplace(buf, "\n", "<br>",     TRUE);
  bufferReplace(buf, "  ", " &nbsp;",  TRUE);
  bufferReplace(buf, "{n", "<font color=\"green\">",   TRUE);
  bufferReplace(buf, "{g", "<font color=\"green\">",   TRUE);
  bufferReplace(buf, "{w", "<font color=\"silver\">",  TRUE);
  bufferReplace(buf, "{p", "<font color=\"purple\">",  TRUE);
  bufferReplace(buf, "{b", "<font color=\"navy\">",    TRUE);
  bufferReplace(buf, "{y", "<font color=\"olive\">",   TRUE);
  bufferReplace(buf, "{r", "<font color=\"maroon\">",  TRUE);
  bufferReplace(buf, "{c", "<font color=\"teal\">",    TRUE);
  bufferReplace(buf, "{d", "<font color=\"black\">",   TRUE);
  bufferReplace(buf, "{G", "<font color=\"lime\">",    TRUE);
  bufferReplace(buf, "{W", "<font color=\"white\">",   TRUE);
  bufferReplace(buf, "{P", "<font color=\"magenta\">", TRUE);
  bufferReplace(buf, "{B", "<font color=\"blue\">",    TRUE);
  bufferReplace(buf, "{Y", "<font color=\"yellow\">",  TRUE);
  bufferReplace(buf, "{R", "<font color=\"red\">",     TRUE);
  bufferReplace(buf, "{C", "<font color=\"aqua\">",    TRUE);
  bufferReplace(buf, "{D", "<font color=\"grey\">",    TRUE);
}


//
// used by webSocketHandle. Replaces one char with another in the buf. 
// Returns how many replacements were made
int replace_char(char *buf, char from, char to) {
  int replacements = 0;
  for(;*buf != '\0'; buf++) {
    if(*buf == from) {
      *buf = to;
      replacements++;
    }
  }
  return replacements;
}


//
// handle whatever request the socket is making
void webSocketHandle(WEB_SOCKET *sock) {
  static char get[SMALL_BUFFER];
  static char key[SMALL_BUFFER];
  char    *argstr = NULL;
  BUFFER     *buf = newBuffer(MAX_BUFFER);
  HASHTABLE *args = newHashtable();

  // clear everything
  *get = *key = '\0';

  // parse our key
  two_args(sock->inbuf, get, key);
  // replace the arg separator with a space, and then one_arg out the arguments
  if(replace_char(key, '?', ' '))
    argstr = strdup(one_arg(key+1, key));
  else
    one_arg(key+1, key);

  // parse out our argument string
  if(argstr != NULL) {
    char one_pair[SMALL_BUFFER];
    char  one_key[SMALL_BUFFER];
    char  one_val[SMALL_BUFFER];
    char *leftover = argstr;

    // separate all of our key:val pairs by spaces so we can one_arg them
    replace_char(argstr, '&', ' ');

    do { 
      // clear our buffers
      *one_pair = *one_key = *one_val = '\0';

      // parse out a pair, and replace the assigner 
      // with a space so we can two_args them apart
      leftover = one_arg(leftover, one_pair);
      replace_char(one_pair, '=', ' ');
      two_args(one_pair, one_key, one_val);

      // see if we need to put in the new key:val pair
      if(*one_key && *one_val && !hashIn(args, one_key))
	hashPut(args, one_key, strdup(one_val));
    } while(*leftover != '\0');
  }

  // find our function
  BUFFER *(* func)(HASHTABLE *args) = hashGet(query_table, key);
  BUFFER                   *content = NULL;

  // call it if it exists
  if(func == NULL || (content = func(args)) == NULL)
    bprintf(buf, "<html><body>Your request for %s was not found</body></html>",
	    key);
  else {
    // replace all of the colors and returns in the buf
    bufferASCIIHTML(content);

    // generate the output, with all of its required tags
    bprintf(buf, 
	    "<html><body bgcolor=\"black\" text=\"green\">"
	    "<font face=\"courier\">"
	    "%s"
	    "</font>"
	    "</body></html>", bufferString(content));
  }

  // if we had content, delete it now
  if(content != NULL)
    deleteBuffer(content);

  // send out the buf contents
  send(sock->control, bufferString(buf), strlen(bufferString(buf)), 0);

  // clean up our mess
  deleteBuffer(buf);
  if(argstr) free(argstr);
  if(hashSize(args) > 0) {
    const char    *h_key = NULL;
    char          *h_val = NULL;
    HASH_ITERATOR *arg_i = newHashIterator(args);
    ITERATE_HASH(h_key, h_val, arg_i) {
      free(h_val);
    } deleteHashIterator(arg_i);
  }
  deleteHashtable(args);
}

//
// A wrapper function for build_who to be usable in the web server
BUFFER *build_who_html(HASHTABLE *args) {
  return build_who();
}

//
// the main loop for our web server
void webserver_loop(void *owner, void *data, char *arg) {
  WEB_SOCKET  *conn = NULL;
  struct timeval tv = { 0, 0 }; // we don't wait for any action.
  fd_set read_fd;

  // get our sets all done up
  FD_ZERO(&read_fd);
  FD_SET(web_control, &read_fd);

  // check to see if something happens
  select(FD_SETSIZE, &read_fd, NULL, NULL, &tv);

  // check for a new connection
  if(FD_ISSET(web_control, &read_fd)) {
    conn = newWebSocket();
    unsigned int socksize;

    // try to accept the connection
    if((conn->control = accept(web_control, (struct sockaddr *)&conn->addr, 
			       &socksize)) > 0) {
      listQueue(web_descs, conn);
      FD_SET(conn->control, &read_fd);
    }
  }


  // do input handling
  LIST_ITERATOR *conn_i = newListIterator(web_descs);
  ITERATE_LIST(conn, conn_i) {
    if(FD_ISSET(conn->control, &read_fd)) {
      int in_len = read(conn->control, conn->inbuf + conn->buf_len, 
			MAX_INPUT_LEN - conn->buf_len - 1);
      if(in_len > 0) {
	conn->buf_len += in_len;
	conn->inbuf[conn->buf_len] = '\0';
      }
      else if(in_len < 0) {
	webSocketClose(conn);
	listRemove(web_descs, conn);
	deleteWebSocket(conn);
      }
    }
  } deleteListIterator(conn_i);

  // do output handling
  conn_i = newListIterator(web_descs);
  ITERATE_LIST(conn, conn_i) {
    // which version are we dealing with, and do we have a request terminator?
    // If we haven't gotten the terminator yet, don't handle or close the socket
    if( ( strstr(conn->inbuf, "HTTP/1.") && strstr(conn->inbuf, "\r\n\r\n")) ||
	(!strstr(conn->inbuf, "HTTP/1.") && strstr(conn->inbuf, "\n"))) {
      webSocketHandle(conn);
      webSocketClose(conn);
      listRemove(web_descs, conn);
      deleteWebSocket(conn);
    }
  } deleteListIterator(conn_i);
}



//*****************************************************************************
// implementation of webserver.h
//*****************************************************************************
void init_webserver(void) {
  struct sockaddr_in my_addr;
  int sockfd, reuse = 1;

  // grab a socket
  sockfd = socket(AF_INET, SOCK_STREAM, 0);

  // set its values
  my_addr.sin_family      = AF_INET;
  my_addr.sin_addr.s_addr = INADDR_ANY;
  my_addr.sin_port        = htons(WEB_PORT);

  // fix thread problems?
  if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int)) == -1) {
    log_string("Webserver error in setsockopt()");
    exit(1);
  }

  // bind the port
  bind(sockfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr));

  // start listening for connections
  listen(sockfd, 3);

  // set the socket control
  web_control = sockfd;

  // set up our list of connected sockets, and get the updater rolling
  web_descs   = newList();
  query_table = newHashtable();
  start_update(NULL, 0.1 SECOND, webserver_loop, NULL, NULL, NULL);

  // set up our basic queries
  add_query("who", build_who_html);
}

void finalize_webserver(void) {
  WEB_SOCKET      *conn = NULL;
  LIST_ITERATOR *conn_i = newListIterator(web_descs);
  ITERATE_LIST(conn, conn_i) {
    close(conn->control);
  } deleteListIterator(conn_i);
  close(web_control);
}

void add_query(const char *key, BUFFER *(* func)(HASHTABLE *args)) {
  hashPut(query_table, key, func);
}