/*
 * quick-and-dirty HTTP server which allows a remote web browser
 * to interface with the target machine.   It may contain bugs and
 * misfeatures.	 The only reason for the existence of this file is
 * for illustration purpose, to show that it is easy to have a rudimentary
 * web server style user interface for an embedded device.  And it may have
 * educationable value to some people (questionable).
 *
 * Written by Hwa-Jin Bae, bae@mail.com, Piedmont California.
 * This file is placed in public domain.
 *
 * A better, slightly more useful small HTTP server (a port of thttpd)
 * is also available from "VxHacks" site where this file is originally
 * distributed.
 */



#include "vxWorks.h"
#include "sys/types.h"
#include "ioLib.h"
#include "fioLib.h"
#include "stdio.h"
#include "unistd.h"
#include "string.h"
#include "usrLib.h"
#include "errnoLib.h"
#include "hostLib.h"
#include "sockLib.h"
#include "socket.h"
#include "inetLib.h"
#include "in.h"
#include "selectLib.h"
#include "taskLib.h"
#include "ctype.h"
#include "dirent.h"
#include "sys/stat.h"
#include "errnoLib.h"
#include "fcntl.h"
#include "unistd.h"
#include "fioLib.h"



int vhttpVerbose = 1;
#define HTTP_PORT       8080
#define DEBUG 1	        /* XXX */
#define MAX_SESSIONS 12 /* 12 concurrent HTTP sessions */

WIND_TCB *vhttp_tasks [MAX_SESSIONS];
WIND_TCB *vhttpd_task = 0;

extern int getMyIpAddr();


int vt1();
int vt2();
int vt3();

/*
 * customizable menu User interface
 */
struct vtMenu {
    char * menu_desc;
    int (* menu_func)();
};

#define MAX_CMDS 3      /* XXX this must match the struct below */

/* list of available commands and callback functions */
struct vtMenu vtCmds[MAX_CMDS] = {
        { "1. Stuff",  vt1 },
        { "2. More Stuff", vt2 },
        { "3. More and More stuff", vt3 },
};


int vt1()
{
        printf("vt1\n");
        return OK;
}

int vt2()
{
        printf("vt2\n");
        return OK;
}

int vt3()
{
        printf("vt3\n");
        return OK;
}




#ifdef DEBUG
static void
errorMsg(char *msg)
{
  printf("ERROR: %s, errno 0x%x\n", msg, errnoGet());
}

static void
infoMsg(char *msg)
{
  printf("INFO: %s\n", msg);
}
#else
#define errorMsg(msg)   ;
#define infoMsg(msg)    ;
#endif

int
vhttpReadInputLine(int sock, char *buffer)
{
  int num;
  int i;
  
  if (ioctl(sock, FIONREAD, &num) < 0){
    errorMsg("vhttpReadInputLine: ioctl FIONREAD error");
    return -1;
  }
  if (num < 1)
    return -1;
  for (i=0;;) {
    char ch;
    int count;
    
    count = read(sock, &ch, 1);
    if (count < 1) {
      errorMsg("vhttpReadInputLine: read error");
      return -2;
    }
    if (ch == (char)0xd) {      /* carriage return */
      read(sock, &ch, 1);       /* eat linefeed */
      if (ch != (char)0xa) {
        errorMsg("vhttpReadInputLine: LF expected but got someting else");
        return -2;
      }
      break;
    }
    buffer[i++] = ch;
  }
  buffer[i] = '\0';
  return(i);
}


void
vhttpSendErr(int sock, int code, char *msg)
{
  char buf[128];
  
  sprintf(buf, "%d %s\n", code, msg);
#ifdef DEBUG
  printf(buf);
#endif
  write(sock, buf, strlen(buf));
}



int
vhttpWriteSock(int sock, char *msg)
{
  return (write (sock, msg, strlen(msg)));
}


void
vhttpSendMenu(int sock)
{
  int i;
  struct vtMenu *mp;
  char buf[128];
  char my_addr[20];
  
  vhttpWriteSock(sock, "HTTP/1.0 200 OK\nMIME-Version: 1.0\nContent-Type: ");
  vhttpWriteSock(sock, "text/html\n\n");
  vhttpWriteSock(sock, "<title>Welcome to VxHacks, Inc.</title>");
  vhttpWriteSock(sock, "<body>\n");
  vhttpWriteSock(sock, "<h1>Welcome To VxHacks, Inc.</h2>\n");
  vhttpWriteSock(sock, "<br>\n");
  vhttpWriteSock(sock, "<hr>\n");
  vhttpWriteSock(sock, "<p>\n");
  vhttpWriteSock(sock, "<ul>\n");
  
  sprintf(my_addr, "%s", inet_ntoa(getMyIpAddr()));
  
  for (i = 0, mp = &vtCmds[0]; i < MAX_CMDS; i++, mp++) {
    vhttpWriteSock(sock, "<li>\n");
    sprintf(buf, "<a href=\"http://%s:%d/%d\"> %s </a>", my_addr,
	    HTTP_PORT, i, mp->menu_desc);
    vhttpWriteSock(sock, buf);
  }
  
  vhttpWriteSock(sock, "</ul>\n");
  vhttpWriteSock(sock, "</br>\n");
  vhttpWriteSock(sock, "<hr>\n");
  vhttpWriteSock(sock, "</body>\n");
}

int
vhttpSendHTML(int sock, char *HTML)
{
  int inFd;
  int status;
  char *filename;
  
  inFd = open (HTML, O_RDONLY, 0);
  if (inFd < OK) {
    errorMsg("can't open HTML file");
    return (ERROR);
  }

  vhttpWriteSock(sock, "HTTP/1.0 200 OK\nMIME-Version: 1.0\nContent-Type: ");
  vhttpWriteSock(sock, "text/html\n\n");
  status = copyStreams (inFd, sock);
  
  return (status);
}

int
vhttpSendGIF(int sock, char *GIF)
{
  int inFd;
  int status;
  char *filename;
  
  inFd = open (GIF, O_RDONLY, 0);
  if (inFd < OK) {
    errorMsg("can't open GIF file");
    return (ERROR);
  }

  vhttpWriteSock(sock, "HTTP/1.0 200 OK\nMIME-Version: 1.0\nContent-Type: ");
  vhttpWriteSock(sock, "image/gif\n\n");
  status = copyStreams (inFd, sock);
  
  return (status);
}

int
vhttpSendJPEG(int sock, char *JPEG)
{
  int inFd;
  int status;
  char *filename;
  
  inFd = open (JPEG, O_RDONLY, 0);
  if (inFd < OK) {
    errorMsg("can't open JPEG file");
    return (ERROR);
  }

  vhttpWriteSock(sock, "HTTP/1.0 200 OK\nMIME-Version: 1.0\nContent-Type: ");
  vhttpWriteSock(sock, "image/jpeg\n\n");
  status = copyStreams (inFd, sock);
  
  return (status);
}

int
vhttpProcessURL(int sock, char *URL)
{
  int num;
  int result;
  char *suffix;
  
#ifdef DEBUG
  printf("URL requested <%s>\n", URL);
#endif			      
  
  if (strcmp(URL, "/" ) == 0)
    vhttpSendMenu(sock);
  else {
    suffix = strrchr(URL, '.');
    if (suffix != 0) {
      if (strcmp(suffix,".html") == 0)
        vhttpSendHTML(sock,++URL);
      else if (strcmp(suffix, ".gif") == 0)
        vhttpSendGIF(sock,++URL);
      else if (strcmp(suffix, ".jpeg") == 0)
        vhttpSendJPEG(sock,++URL);
      else {
        vhttpSendMenu(sock);
        vhttpSendErr(sock, 666, "Unknown URL");
        vhttpSendErr(sock, 666, URL);
      }
    } else {
      URL[0] = ' '; /* wipe out leading slash char */
      num = atoi(URL);
      if (num >= 0 && num < MAX_CMDS)  {
        vhttpSendMenu(sock);
        result = (*vtCmds[num].menu_func)();
        if (result == OK)
	  vhttpSendErr(sock, 205, "Command success");
        else
	  vhttpSendErr(sock, 905, "Command Failed");
      } else {
        vhttpSendErr(sock, 505, "Invalid menu item URL");
        return ERROR;
      }
    }
  } 
  
  return OK;
}


int 
vhttpProcess(int sock, struct sockaddr_in *sinptr)
{
  fd_set read_fdset;
  fd_set write_fdset;
  fd_set except_fdset;
  int finishedHeader=0;
  char buffer[1024];
  char method[20];
  char URL[200];
  char proto[20];
  
  
  for (;;) {
    FD_SET(sock, &read_fdset);
    FD_SET(sock, &write_fdset);
    FD_SET(sock, &except_fdset);
    if (select(64, &read_fdset, &write_fdset, &except_fdset, 0) < 0)
      continue;
    if (FD_ISSET(sock, &read_fdset)) {
      int result;
      
      result = vhttpReadInputLine(sock, buffer);
      if (result < 0)
        return -1;
      if (result == 0)  {
        finishedHeader=1;
        return 0;
      }
      if (!finishedHeader) {
        sscanf(buffer, "%s %s %s", method, URL, proto);
#ifdef DEBUG
        printf("method <%s> URL <%s> proto <%s>\n",
	       method, URL, proto);
#endif
        if (strcmp("HTTP/1.0", proto)) {
	  vhttpSendErr(sock, 501, "Invalid Protocol.  Expecting	HTTP/1.0");
	  close(sock);
	  return -1;
        }
        if (strcmp(method, "GET") != 0 &&
	    strcmp(method, "PUT") != 0) {
	  vhttpSendErr(sock, 501, "Invalid Method.  Expecting GET or PUT.");
	  close(sock);
	  return -1;
        }
        if (vhttpProcessURL(sock, URL) != OK) {
	  vhttpSendErr(sock, 404, "Invalid URL");
	  close(sock);
	  return -1;
        }
        close(sock);
        finishedHeader=1;
        return 0;
      }
    } else if (FD_ISSET(sock, &write_fdset)) {
      ;
    } else if (FD_ISSET(sock, &except_fdset)) {
      errorMsg("exception fd set");
    }
    
#if 0		    
    taskDelay(2 * 60);
#endif
  }
}

int 
vhttpMainLoop(int sock, struct sockaddr_in *sinptr)
{
  int client;
  char name[25];
  static int procNum = 1;
  int i;
  int size;
  
  for (;;) {
    WIND_TCB *tcb;
    
    size = sizeof(*sinptr);
    client = accept(sock, (struct sockaddr *)sinptr, &size);
    if (client < 0) {
      errorMsg("accept");
      return -1;
    }
    infoMsg("vhttp: accepted a new client\n");
    sprintf(name, "vhttp%d", procNum++);
    tcb = taskSpawn(name, 101, 0, 1024 * 4, vhttpProcess, 
		    client, sinptr, 0, 0, 0, 0,	0, 0, 0, 0);
    tcb->spare4 = client;
    for (i = 0; i < MAX_SESSIONS; i++) {
      if (vhttp_tasks[i] == 0)
        break;
    }
    if (i < MAX_SESSIONS)
      vhttp_tasks[i] = tcb;
    else {
      vhttpSendErr(sock, 501, "Too many sessions");
      close(client);
      return -1;
    }
  }
  return OK;
}

int
vhttpDelete(WIND_TCB *tcb)
{
  int i;
  
  if (tcb == vhttpd_task)
    close(tcb->spare4); /* delete server socket */
  for (i = 0; i < MAX_SESSIONS; i++) {
    if (tcb == vhttp_tasks[i]) {
      close(tcb->spare4);
      vhttp_tasks[i] = 0;
    }
  }
}


int
vhttpStart()
{
  int sock;
  struct sockaddr_in sin;
  struct sockaddr_in *sinptr = &sin;
  int true = 1;
  WIND_TCB *tcb;
  int i;
  
  sock = socket(AF_INET, SOCK_STREAM, 0);
  if (sock < 0) {
    errorMsg("socket");
    return -1;
  }
  if((setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&true, sizeof(true))) == -1) {
    errorMsg("setsockopt");
    return -1;
  }
  bzero(sinptr, sizeof(*sinptr));
  sinptr->sin_family = AF_INET;
  sinptr->sin_port = htons(HTTP_PORT); 
  sinptr->sin_addr.s_addr = htonl(INADDR_ANY);
  if (bind(sock, (struct sockaddr *) sinptr, sizeof(*sinptr)) < 0) {
    errorMsg("bind");
    return -1;
  }
  if (listen (sock, 5) < 0) {
    errorMsg("listen");
    return -1;
  }
  for (i = 0; i < MAX_SESSIONS; i++)
    vhttp_tasks[i] = 0;
  tcb = taskSpawn("vHttpD", 100, 0, 2048, vhttpMainLoop, 
		  sock,	sinptr, 0, 0, 0, 0, 0, 0, 0, 0);
  tcb->spare4 = sock;
  vhttpd_task = tcb;
  taskDeleteHookAdd(vhttpDelete);
  
  return OK;
}


