/*
 * parse.c
 * File Revision 0
 * Parsing of http requests for server.
 * (c) 2000 Jacob Lundberg, jacob@chaos2.org
 */


/*
 * 2000.11.11	Initial implementation
 */


#include <time.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "response.h"
#include "server.h"


/* Location of symbols for DBG_PRN. */
#define  WHENCE                 "parse.c"


/* Maximum number of chars in a time field. */
#define  TIME_SIZE_MAX          64


static void timeform(char *buffer, time_t timeval) {
/*
 * timeform()
 * Convert timeval into a string value stored on buffer.
 * It's big, and it's ugly, but it's mostly table lookups.
 */

   struct tm *gmt;

   DBG_PRN("timeform: timeval = %li\n", WHENCE, timeval);

   /* Get the UTC version of our timestamp. */
   gmt = gmtime(&timeval);

   /* Set the day of the week. */
   switch(gmt->tm_wday) {
      case 0:
         strcpy(buffer, "Sun");
         break;
      case 1:
         strcpy(buffer, "Mon");
         break;
      case 2:
         strcpy(buffer, "Tue");
         break;
      case 3:
         strcpy(buffer, "Wed");
         break;
      case 4:
         strcpy(buffer, "Thu");
         break;
      case 5:
         strcpy(buffer, "Fri");
         break;
      case 6:
         strcpy(buffer, "Sat");
         break;
   }

   /* Month day. */
   sprintf(buffer + strlen(buffer), ", %02i ", gmt->tm_mday);

   /* Month name. */
   switch(gmt->tm_mon) {
      case 0:
         strcat(buffer, "Jan");
         break;
      case 1:
         strcat(buffer, "Feb");
         break;
      case 2:
         strcat(buffer, "Mar");
         break;
      case 3:
         strcat(buffer, "Apr");
         break;
      case 4:
         strcat(buffer, "May");
         break;
      case 5:
         strcat(buffer, "Jun");
         break;
      case 6:
         strcat(buffer, "Jul");
         break;
      case 7:
         strcat(buffer, "Aug");
         break;
      case 8:
         strcat(buffer, "Sep");
         break;
      case 9:
         strcat(buffer, "Oct");
         break;
      case 10:
         strcat(buffer, "Nov");
         break;
      case 11:
         strcat(buffer, "Dec");
         break;
   }

   /* The four-digit year. */
   sprintf(buffer + strlen(buffer), " %04i ", 1900 + gmt->tm_year);

   /* Hours!  Minutes!  Seconds!  (and GMT as required by RFC 1945) */
   sprintf(buffer + strlen(buffer), "%02i:%02i:%02i GMT",
         gmt->tm_hour, gmt->tm_min + (gmt->tm_sec / 60), gmt->tm_sec % 60);

}


bool parse_request(sv_req *request, char *buffer) {
/*
 * parse_request()
 * Slurp out the request information.
 */

   time_t timeval;
   char *full, *holder;
   char curtime[TIME_SIZE_MAX];
   bool parse_success = true;

   if(request == NULL || buffer == NULL) {
      DBG_PRN("parse_request: NULL buffer!\n", WHENCE);
      return(false);
   } else
      DBG_PRN("parse_request\n", WHENCE);

   /* Remember the full form of the request. */
   full = buffer;

   /* Get the current date and time. */
   time(&timeval);
   timeform(curtime, timeval);

   /* Clip \n from the top of the buffer. */
   while(buffer[0] == '\n')
      ++buffer;

   /* Determine the request type. */
   if(!strncmp(buffer, "HEAD", 4) && strchr(" \t", buffer[4])) {
      request->req_type = SV_REQ_HEAD;
      buffer += 4;
   } else if(!strncmp(buffer, "GET", 3) && strchr(" \t", buffer[3])) {
      request->req_type = SV_REQ_GET;
      buffer += 3;
   } else {
      /* Request type wasn't valid.  *sniff* */
      request->req_type = SV_REQ_NONE;
      while(!memchr(" \t\n", buffer[0], 4))
         ++buffer;
      parse_success = false;
      /* 501 deferred until below because 400 takes precedence. */
   }

   /* Advance to the next word in the buffer. */
   while(strchr(" \t", buffer[0]))
      ++buffer;

   /* Get the file name from the buffer. */
   holder = buffer;
   while(!memchr(" \t\n", buffer[0], 4))
      ++buffer;
   request->filename = (char *)malloc((buffer - holder + 2) * sizeof(char));
   memcpy(request->filename + 1, holder, buffer - holder);
   request->filename[buffer - holder + 1] = '\0';
   request->filename[0] = '.';
   if(buffer - holder <= 0) {
      parse_success = false;
      /* 400, Bad request. */
      snprintf(sv_req_buffer, SV_REQ_BUF_SIZE - 2,
            HEAD_400 BODY_400, curtime, full);
      action_write(sv_req_buffer, strlen(sv_req_buffer));
   } else if(!parse_success) {
      /* 501, Method not implemented. */
      snprintf(sv_req_buffer, SV_REQ_BUF_SIZE - 2,
            HEAD_501 BODY_501, curtime, full);
      action_write(sv_req_buffer, strlen(sv_req_buffer));
   }

   /* Advance to the next word in the buffer. */
   while(strchr(" \t", buffer[0]))
      ++buffer;

   /* Check the request class. */
   if(parse_success && (strncmp(buffer, "HTTP/1.", 7) || !strchr("01", buffer[7]))) {
      parse_success = false;
      /* 400, Bad request. */
      snprintf(sv_req_buffer, SV_REQ_BUF_SIZE - 2, HEAD_400 BODY_400, curtime, full);
      action_write(sv_req_buffer, strlen(sv_req_buffer));
   }

   return(parse_success);

}


void write_file(int file) {
/*
 * write_file()
 * Helper for fill_request().
 * Copies an entire file to the communications socket.
 */

   int inlen = 0;

   DBG_PRN("write_file: file = %i\n", WHENCE, file);

   /* Loop buffering data from the file until an empty read. */
   do {
      inlen = read(file, sv_req_buffer, SV_REQ_BUF_SIZE - 2);
      action_write(sv_req_buffer, inlen);
   } while(inlen > 0);

   if(inlen < 0)
      DBG_PRN("write_file: short read due to error %i\n", WHENCE, errno);

}


const char *get_ctype(sv_req *request) {
/*
 * get_ctype()
 * Get the Content-Type of a request.
 */

   /* This is not a thread-safe buffer. */
   static char buffer[64];
   /* We need to know how long the filename is in several places. */
   int namelen = strlen(request->filename);

   DBG_PRN("get_ctype\n", WHENCE);

   /* Lookup table of known types. */
   if(namelen < 4)
      strcpy(buffer, "application/x-unknown");
   else if(!strncmp(request->filename + namelen - 4, ".txt", 4) ||
         (namelen >= 5 && !strncmp(request->filename + namelen - 5, ".text", 5)))
      strcpy(buffer, "text/plain");
   else if(!strncmp(request->filename + namelen - 4, ".htm", 4) ||
         (namelen >= 5 && !strncmp(request->filename + namelen - 5, ".html", 5)))
      strcpy(buffer, "text/html");
   else if(!strncmp(request->filename + namelen - 3, "jpg", 3))
      strcpy(buffer, "image/jpeg");
   else if(!strncmp(request->filename + namelen - 3, "gif", 3))
      strcpy(buffer, "image/gif");
   else if(!strncmp(request->filename + namelen - 3, "png", 3))
      strcpy(buffer, "image/png");
   else
      strcpy(buffer, "application/x-unknown");

   return(buffer);

}


void fill_request(sv_req *request) {
/*
 * fill_request()
 * Toss a file out for the hungry dogs to chew on.
 */

   char curtime[TIME_SIZE_MAX], flmtime[TIME_SIZE_MAX];
   time_t timeval;
   struct stat fs;

   DBG_PRN("fill_request: type = %i\n", WHENCE, request->req_type);

   /* Get the current date and time. */
   time(&timeval);
   timeform(curtime, timeval);

   /* Stat the file. */
   stat(request->filename, &fs);

   /* If it's a directory, scan for contents to revise to. */
   if(S_ISDIR(fs.st_mode)) {
      request->filename = (char *)realloc(request->filename,
            (strlen(request->filename) + 12) * sizeof(char));
      /* Yeah, we don't search too hard just yet. */
      if(request->filename[strlen(request->filename) - 1] != '/')
         strcat(request->filename, "/");
      strcat(request->filename, "index.html");
      stat(request->filename, &fs);
   }

   /* Try and open the input source for reading. */
   request->file = open(request->filename, O_RDONLY, S_IRUSR);
   if(request->file < 0) {
      /* Write out a failure response. */
      switch(errno) {
         case EPERM:
         case EACCES:
            /* 403, Permissions do not allow. */
            snprintf(sv_req_buffer, SV_REQ_BUF_SIZE - 2,
                  HEAD_403 BODY_403, curtime, request->filename + 1);
            action_write(sv_req_buffer, strlen(sv_req_buffer));
            break;
         case ENOENT:
         case ENXIO:
         case EBADF:
         case EAGAIN:
         case EBUSY:
         case ENODEV:
         case EISDIR:
            /* 404, File not found or not available. */
            snprintf(sv_req_buffer, SV_REQ_BUF_SIZE - 2,
                  HEAD_404 BODY_404, curtime, request->filename + 1);
            action_write(sv_req_buffer, strlen(sv_req_buffer));
            break;
         default:
            /* 500, Server error. */
            snprintf(sv_req_buffer, SV_REQ_BUF_SIZE - 2,
                  HEAD_500 BODY_500, curtime, errno);
            action_write(sv_req_buffer, strlen(sv_req_buffer));
            break;
      }
   } else {
      /* Duplicate the contents of the file out to the socket. */
      timeform(flmtime, fs.st_mtime);
      snprintf(sv_req_buffer, SV_REQ_BUF_SIZE - 2,
            HEAD_200, curtime, flmtime, fs.st_size, get_ctype(request));
      action_write(sv_req_buffer, strlen(sv_req_buffer));
      if(request->req_type == SV_REQ_GET)
         write_file(request->file);
      close(request->file);
   }

}

