/*
 * File:       wwwserv.c
 * Author:     Janet Davis
 * Created:    November 9, 2006
 * Revised:    Janet Davis, November 19, 2006
 *             Jerod Weinman, June 26, 2008
 * Purpose:    Implements a simple web server that can handle only one
 *             client.
 * Usage:      ./wwwserv [port]
 * parameters: port is the (optional) port number for the server to listen
 *             on.  A default server port is specified below.
 */

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

#include "parseurl.h"

#define DEFAULT_SERVER_PORT 8000
#define ROOT "/home/weinman/public_html/courses/CSC213/2008F/examples/web/docroot/"
#define HOME "index.html"

#define MSG_FORMAT      "HTTP/1.0 %s\n"
#define MSG_ACCEPTED    "202 Accepted"
#define MSG_BAD_REQUEST "400 Bad Request"
#define MSG_FORBIDDEN   "403 Forbidden"
#define MSG_NOT_FOUND   "404 Not Found"

#define ONE_KILOBYTE 1024
#define MAX_URL 128


int listen_sock;      /* Server socket should be global */

void sig_int(int signo);
void clean_up_and_exit(int cond);
void write_http_status(int sock, char* message);
int build_full_path(char* path, const char* request_url);
void handle_client(int cli_sock);


int main(int argc, char* argv[]) {

     int port = DEFAULT_SERVER_PORT;  /* Port we listen on */
     int connect_sock;

     struct sockaddr_in serv_name;   /* Socket address structure */
     size_t len = sizeof(serv_name); /* size of the socket address structure */

     signal(SIGINT, sig_int); /* Set our own signal handler to do clean up */

     /* Handle any command line arguments */
     if (argc > 1) 
     {
          port = atoi(argv[1]);
     } 

     /* Set socket descriptor */
     if (0 > (listen_sock = socket(AF_INET, SOCK_STREAM, 0))) 
     {
          perror("Error opening socket\n");
          clean_up_and_exit(1); 
     }
     
     /* set name as the physical address of the socket descriptor */
     bzero(&serv_name, sizeof(serv_name)); /* initialize */
     serv_name.sin_family = AF_INET;       /* specify internet socket */
     serv_name.sin_port = htons(port);     /* establish listening port */

     /* connect socket to descriptor */
     if (0 > bind(listen_sock, (struct sockaddr*)&serv_name, 
                  sizeof(serv_name))) 
     {
          perror("Error binding listen_socket\n");
          clean_up_and_exit(1);
     }

     listen(listen_sock, 1);  /* listen for connections on the socket */
     printf("Server is alive and waiting for connections.\n");

     /* accept a connection request from client */
     connect_sock = accept(listen_sock, (struct sockaddr*)&serv_name, &len);
     
     printf("Got connection\n");

     /* Pass off the connection request for handling */
     handle_client(connect_sock);
     
     clean_up_and_exit(0);
     return 0;
}

/*
 * Handle a client connection.
 */
void handle_client(int cli_sock)
{
     int count;
     char buffer[ONE_KILOBYTE];
     char request_str[MAX_URL];
     char filename[MAX_URL];
     int fd;

     count = read(cli_sock, buffer, ONE_KILOBYTE-1);
     buffer[count] = (char)0;

     /* Socket may have been closed before a request was written,
      * so check to make sure there is data! */
     if (count == 0)
     {
          close(cli_sock);
          return;
     }

     /* Check for a GET */
     if (1 > sscanf(buffer, "GET %s", request_str)) 
     {
          write_http_status(cli_sock, MSG_BAD_REQUEST);
     } 
     else 
     {
          printf("Requested: %s\n", request_str);

          if (0 != build_full_path(filename, request_str))
          {
               write_http_status(cli_sock, MSG_BAD_REQUEST);
          } 
          else
          {
               printf("Filename: %s\n", filename);

               /* Attempt to open the file */
               fd = open(filename, O_RDONLY);
               if ((fd < 0) && (errno == EACCES)) 
               {
                    write_http_status(cli_sock, MSG_FORBIDDEN);
               } 
               else if (fd < 0) 
               {
                    write_http_status(cli_sock, MSG_NOT_FOUND);
               } 
               else 
               {
                    write_http_status(cli_sock, MSG_ACCEPTED);
                    write(cli_sock, "\n", 1);
                    write(cli_sock, 
                          "<html><body>Content of file goes here</body></html>", 
                          51);
                    close(fd);
               }
          }
     }
     
     close(cli_sock);
}

/*
 * Sends the HTTP status.
 */
void write_http_status(int sock, char* message) {
  int len = strlen(MSG_FORMAT) + strlen(message);
  char buffer[len];
  snprintf(buffer, len, MSG_FORMAT, message);
  printf(buffer); /* for logging purposes */
  write(sock, buffer, strlen(buffer));
}

/*
 * Builds a file path given an absolute or relative URL.
 * Assumes path points to a buffer of size MAX_URL.
 * Returns 0 if successful; -1 otherwise.
 */
int build_full_path(char* path, const char* request_url) {
  int success;

  bzero(path, MAX_URL);

  /* Begin with the root path. */
  strncpy(path, ROOT, MAX_URL);

  /* Check for "/" as full request. */
  if (0 == strcmp(request_url, "/")) {
    strncat(path, HOME, MAX_URL);
    return 0;
  }

  /* Check whether requested url is absolute or relative. */
  if (strstr(request_url, "http://")) {
    /* Absolute URL; must be parsed to obtain the path part */
    url_data_t url_data;
    success = parse_url(&url_data, request_url);
    if (success != 0) return success;
    if ((0 == strcmp("", url_data.path))
        || (0 == strcmp("/", url_data.path)))
    {
      strncat(path, HOME, MAX_URL);
    } else {
      strncat(path, url_data.path, MAX_URL);
    }
    free_url_data(&url_data);
  } else {
    /* Relative URL; simply cat onto the root path! */
    strncat(path, request_url, MAX_URL);
  }

  return 0;
}

/*
 * Upon exiting, the listen socket should be closed.
 */
void clean_up_and_exit(int cond) {
  printf("Exiting server.\n");
  close(listen_sock);
  exit(cond);
}

/*
 * Handle the interrupt signal by cleaning up.
 * This is important because main(...) will not terminate 
 * under normal conditions.
 */
void sig_int(int signo) {
  clean_up_and_exit(0);
}
