jadehttpd.c

/**
 * Jade Cheng
 * ICS 451
 * Assignment 1
 */

#include <assert.h>
#include <ctype.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <time.h>
#include <unistd.h>

#include "jadesocket.h"

/* -------------------------------------------------------------------------- */

/**
 * The maximum size of the request or response header in bytes.
 */
#define MAX_HEADER_SIZE   5000

/**
 * This macro displays an error that includes the file and line number and then
 * returns EXIT_FAILURE.
 */
#define RETURN_FAILURE \
    { \
        fprintf(stderr, "Error on line %d in %s\n", __LINE__, __FILE__); \
        return EXIT_FAILURE; \
    }

/* -------------------------------------------------------------------------- */

/**
 * The request header fields.
 */
typedef struct http_request_header {

    /**
     * The contents of the request header.  The end of line characters are
     * replaced with nulls.
     */
    char buffer[MAX_HEADER_SIZE];

    const char * method;
    const char * path;
    const char * protocol;   /* ignored */
    const char * version;    /* ignored */
    const char * host;       /* ignored */

    const char * keep_alive; /* ignored */
    const char * connection; /* ignored */

    /**
     * A pointer to the second line, which is the first line containing the
     * header fields.
     */
    char * start_of_header_fields;

} http_request_header;

/* -------------------------------------------------------------------------- */

static int jade_strcat(char * dst, const char * src, int count);
static int jade_strcpy(char * dst, const char * src, int count);
static int jade_time(char * dst, int count);
static int parse_header(http_request_header * header);
static int parse_header_line_1(http_request_header * header);
static int parse_header_line_x(http_request_header * header);
static int read_header(int s, http_request_header * header);
static int send_response(int s, const http_request_header * header);
static int send_response_count(int s, const char * date);
static int send_response_date(int s, const char * date);
static int send_response_error(int s, const char * reason);

static int send_response_file(
    int          s,
    const char * date,
    const char * type,
    const char * path);

static int handle_client(int s);

/* -------------------------------------------------------------------------- */

/**
 * The number of times the server has been accessed.
 */
static int g_web_server_accessed_count;

/* -------------------------------------------------------------------------- */

/**
 * The main entry point of the program.
 *
 * @param argc The number of arguments.
 * @param argv The command line arguments.
 *
 * @return EXIT_SUCCESS if successful; EXIT_FAILURE if not.
 */
int main(int argc, const char * argv[])
{
    short port;

    // Check the command line usage.
    if (argc != 2) {
        fprintf(stderr, "Usage: webserver port\n");
        return EXIT_FAILURE;
    }

    // Get the numeric value of the port number.
    port = atoi(argv[1]);
    if (port == 0) {
        fprintf(stderr, "Usage: webserver port\n");
        return EXIT_FAILURE;
    }

    // Accept connections until the application fails or it is aborted.  When a
    // connection is made, the following function calls handle_client to respond
    // to the web request.
    if (0 != js_accept_all(handle_client, port, 5))
        return EXIT_FAILURE;

    // TBD Can the code actually reach this point???
    printf("webserver exited successfully.\n");

    // Return successfully.
    return EXIT_SUCCESS;
}

/**
 * Re-write of the strcat funtion with the count parameter indicating the
 * maximum capacity in bytes of the destination buffer size, including the null
 * terminator.
 *
 * @param dst The destination string.
 * @param src The source string.
 * @param count The buffer size of the destination string.
 *
 * @return EXIT_SUCCESS if successful; EXIT_FAILURE if not.
 */
static int jade_strcat(char * dst, const char * src, int count)
{
    char * end;

    assert(dst != NULL);
    assert(src != NULL);
    assert(count > 0);

    // Keep track of the last possible null-terminator.
    end = dst + (count - 1);

    // Find the end of the string.
    while (count-- > 0 && *dst != '\0')
        dst++;

    // Copy all the bytes into the destination string.
    while (count-- > 0) {
        *dst = *src;
        if (*src == '\0')
            return EXIT_SUCCESS;
        dst++;
        src++;
    }

    // In case of an overflow, null-terminate the string and return a failure.
    *end = '\0';
    RETURN_FAILURE;
}

/**
 * Re-write of the strcpy funtion with the count parameter indicating the
 * maximum capacity in bytes of the destination buffer size, including the null
 * terminator.
 *
 * @param dst The destination string.
 * @param src The scorce string.
 * @param count The maximum buffer size of the destination string.
 *
 * @return EXIT_SUCCESS if successful; EXIT_FAILURE if not.
 */
static int jade_strcpy(char * dst, const char * src, int count)
{
    assert(dst != NULL);
    assert(count > 0);

    // Truncate the destination string and the append the source string.
    *dst = '\0';
    return jade_strcat(dst, src, count);
}

/**
 * This function writes the current time to a destination string.
 *
 * @param dst The destination string to store the output.
 * @param count The maximum buffer size of the destination string.
 *
 * @return EXIT_SUCCESS if successful; EXIT_FAILURE if not.
 */
static int jade_time(char * dst, int count)
{
    time_t      raw_time;
    struct tm * time_info;
    char      * actual_time;
    size_t      length;

    if (time(&raw_time) == -1) {
        RETURN_FAILURE;
    }

    time_info = localtime(&raw_time);
    actual_time = asctime(time_info);

    if (EXIT_SUCCESS != jade_strcpy(dst, actual_time, count)) {
        RETURN_FAILURE;
    }

    length = strlen(dst);
    if (length > 0 && '\n' == dst[length - 1])
        dst[length - 1] = '\0';

    return EXIT_SUCCESS;
}

/**
 * This function reads the request header and stores it into a structure.  Only
 * the header->buffer is modified.
 *
 * @param s The socket descriptor.
 * @param header The request header structure used to store the header fields.
 *
 * @return EXIT_SUCCESS if successful; EXIT_FAILURE if not.
 */
static int read_header(int s, http_request_header * header)
{
    int  count;
    char ch;

    assert(header != NULL);
    memset(header, 0, sizeof(http_request_header));

    while (recv(s, &ch, 1, 0) == 1) {
        if (ch == '\r')
            continue;

        header->buffer[count++] = ch;

        if (count == MAX_HEADER_SIZE - 1) {
            RETURN_FAILURE;
        }

        if (count >= 2 && 0 == strncmp("\n\n", &header->buffer[count - 2], 2))
            break;
    }

    header->buffer[count - 1] = '\0';
    return EXIT_SUCCESS;
}

/**
 * This function parses the request header and populates the corresponding
 * fields in the header structure.
 *
 * @param header The request header structure used to store the header fields.
 *
 * @return EXIT_SUCCESS if successful; EXIT_FAILURE if not.
 */
static int parse_header(http_request_header * header)
{
    assert(header != NULL);

    if (0 == parse_header_line_1(header))
        if (0 == parse_header_line_x(header))
            return EXIT_SUCCESS;

    RETURN_FAILURE;
}

/**
 * This function parses the first line of the request header and populates the
 * corresponding fields in the header structure.
 *
 * @param header The request header structure used to store the header fields.
 *
 * @return EXIT_SUCCESS if successful; EXIT_FAILURE if not.
 */
static int parse_header_line_1(http_request_header * header)
{
    char * anchor;
    char * eol;

    assert(header != NULL);
    assert(strlen(header->buffer) >= 1);
    assert(header->buffer[strlen(header->buffer) - 1] == '\n');

    anchor = header->buffer;

    // Look for the end of the first line.
    eol = strchr(anchor, '\n');
    *eol = '\0';
    header->start_of_header_fields = eol + 1;

    // Look for the end of the method.
    eol = strchr(anchor, ' ');
    if (eol == NULL) {
        RETURN_FAILURE;
    }
    *eol = '\0';
    header->method = anchor;
    anchor = eol + 1;

    // Look for the end of the path.
    eol = strchr(anchor, ' ');
    if (eol == NULL) {
        RETURN_FAILURE;
    }
    *eol = '\0';
    header->path = anchor;
    anchor = eol + 1;

    // Look for the end of the protocol.
    eol = strchr(anchor, '/');
    if (eol == NULL) {
        RETURN_FAILURE;
    }
    *eol = '\0';
    header->protocol = anchor;
    anchor = eol + 1;

    // The rest is the version.
    header->version = anchor;
    return EXIT_SUCCESS;
}

/**
 * This function parses the rest of the lines of the request header and
 * populates the corresponding fields in the header structure.
 *
 * @param header The request header structure used to store the header fields.
 *
 * @return EXIT_SUCCESS if successful; EXIT_FAILURE if not.
 */
static int parse_header_line_x(http_request_header * header)
{
    char * anchor;

    assert(header != NULL);
    assert(header->start_of_header_fields != NULL);

    anchor = header->start_of_header_fields;

    // Loop over all lines in the buffer.
    while (*anchor != '\0') {
        char * eol;
        char * value;

        // Null-terminate the current line.
        eol = strchr(anchor, '\n');
        *eol = '\0';

        // Look for the separator between the field name and value.
        value = strchr(anchor, ':');
        if (value == NULL) {
            RETURN_FAILURE;
        }

        // Skip the whitespace before the value.
        do {
            *value++ = '\0';
        } while (0 != isspace((int)*value));

        // Keep track of useful headers and ignore the rest.
        if (0 == strcmp(anchor, "Host"))
            header->host = value;
        else if (0 == strcmp(anchor, "Keep-Alive"))
            header->keep_alive = value;
        else if (0 == strcmp(anchor, "Connection"))
            header->connection = value;

        // Move to the next line.
        anchor = eol + 1;
    }

    return EXIT_SUCCESS;
}

/**
 * This function sends count number data through the specified socket.
 *
 * @param s The socket descriptor.
 * @param date The date of the reply header.
 *
 * @return EXIT_SUCCESS if successful; EXIT_FAILURE if not.
 */
static int send_response_count(int s, const char * date)
{
    static const char * header_template =
        "HTTP/1.0 200 OK\r\n"
        "Content-Type: text/plain; charset=utf-8\r\n"
        "Content-Length: %d\r\n"
        "Date: %s\r\n"
        "Connection: close\r\n\r\n";

    int  result;
    char header[MAX_HEADER_SIZE];

    // g_web_server_accessed_count is a 32-bit number...
    // 32-bit int ==> -(2^31) = -2,147,483,648 ==> 11 + 1 content size.
    char content[12];

    sprintf(content, "%d", g_web_server_accessed_count);
    sprintf(header, header_template, strlen(content), date);

    result = EXIT_FAILURE;
    if (EXIT_SUCCESS == js_send(s, header, strlen(header)))
        if (EXIT_SUCCESS == js_send(s, content, strlen(content)))
            result = EXIT_SUCCESS;

    return result;
}

/**
 * This function sends date data through the specified socket.
 *
 * @param s The socket descriptor.
 * @param date The date of the reply header.
 *
 * @return EXIT_SUCCESS if successful; EXIT_FAILURE if not.
 */
static int send_response_date(int s, const char * date)
{
    static const char * header_template =
        "HTTP/1.0 200 OK\r\n"
        "Content-Type: text/plain; charset=utf-8\r\n"
        "Content-Length: %d\r\n"
        "Date: %s\r\n"
        "Connection: close\r\n\r\n";

    int  result;
    char header[MAX_HEADER_SIZE];

    sprintf(header, header_template, strlen(date), date);

    result = EXIT_FAILURE;
    if (EXIT_SUCCESS == js_send(s, header, strlen(header)))
        if (EXIT_SUCCESS == js_send(s, date, strlen(date)))
            result = EXIT_SUCCESS;

    return result;
}

/**
 * This function sends file data through the specified socket.
 *
 * @param s The socket descriptor.
 * @param date The date of the reply header.
 * @param type The type of files to send.  Program supports "image/jpeg",
 *             "image/gif", and "text/html".
 * @param path The path the files in the request header.  Program supports
 *             "pic1.jpeg", "pic2.gif", and "x.html".
 *
 * @return EXIT_SUCCESS if successful; EXIT_FAILURE if not.
 */
static int send_response_file(
    int          s,
    const char * date,
    const char * type,
    const char * path)
{
    static const char * header_template =
        "HTTP/1.0 200 OK\r\n"
        "Content-Type: %s; charset=utf-8\r\n"
        "Content-Length: %d\r\n"
        "Date: %s\r\n"
        "Connection: close\r\n\r\n";

    int    result;
    FILE * f;
    char * buf;
    size_t len;
    char   header[MAX_HEADER_SIZE];

    assert(date != NULL);
    assert(path != NULL);
    assert(type != NULL);

    // Open the specified file as binary.
    if ((f = fopen(path, "rb")) == NULL) {
        send_response_error(s, "404 Not Found");
        RETURN_FAILURE;
    }

    // Seek to the end of the file and count the length of the file.
    fseek(f, 0, SEEK_END);
    len = ftell(f);

    // Move the pointer back to the beginning.
    fseek(f, 0, SEEK_SET);

    // Allocate buffer size accordingly and read the file in the buffer.
    buf = (char *)malloc(len + 1);
    fread(buf, 1, len, f);
    fclose(f);
    buf[len] = '\0'; // Not necessarily needed except when debugging.

    sprintf(header, header_template, type, len, date);

    result = EXIT_FAILURE;
    if (EXIT_SUCCESS == js_send(s, header, strlen(header)))
        if (EXIT_SUCCESS == js_send(s, buf, len))
            result = EXIT_SUCCESS;

    free(buf);
    return result;
}

/**
 * This function sends an error reply header to any request that is not
 * supported.
 *
 * @param s The socket descriptor.
 * @param reason The reason for the error, including the response code.
 *
 * @return EXIT_SUCCESS if successful; EXIT_FAILURE if not.
 */
static int send_response_error(int s, const char * reason)
{
    static const char * header_template =
        "HTTP/1.0 %s\r\n"
        "Connection: close\r\n\r\n";

    char header[MAX_HEADER_SIZE];

    assert(reason != NULL);

    sprintf(header, header_template, reason);
    return js_send(s, header, strlen(header));
}

/**
 * This function uses the parsed request header and the specified socket to
 * send a corresponding reply.
 *
 * @param s The socket descriptor.
 * @param header The parsed header.
 *
 * @return EXIT_SUCCESS if successful; EXIT_FAILURE if not.
 */
static int send_response(int s, const http_request_header * header)
{
    char date[100];

    assert(header != NULL);

    if (NULL == header->method || 0 != strcmp(header->method, "GET")) {
        if (EXIT_SUCCESS != send_response_error(s, "400 Bad Request")) {
            RETURN_FAILURE;
        }
    }

    if (EXIT_SUCCESS != jade_time(date, sizeof(date))) {
        RETURN_FAILURE;
    }

    if (0 == strcmp(header->path, "/date")) {
        if (EXIT_SUCCESS != send_response_date(s, date)) {
            RETURN_FAILURE;
        }

    } else if (0 == strcmp(header->path, "/count")) {
        if (EXIT_SUCCESS != send_response_count(s, date)) {
            RETURN_FAILURE;
        }

    } else if (0 == strcmp(header->path, "/picture1")) {
        if (EXIT_SUCCESS != send_response_file(s, date, "image/jpeg", "z.jpeg")) {
            RETURN_FAILURE;
        }

    } else if (0 == strcmp(header->path, "/picture2")) {
        if (EXIT_SUCCESS != send_response_file(s, date, "image/gif", "y.gif")) {
            RETURN_FAILURE;
        }

    } else if (0 == strcmp(header->path, "/html")) {
        if (EXIT_SUCCESS != send_response_file(s, date, "text/html", "x.html")) {
            RETURN_FAILURE;
        }

    } else {
        if (EXIT_SUCCESS != send_response_error(s, "404 Not Found")) {
            RETURN_FAILURE;
        }
    }

    return EXIT_SUCCESS;
}

/**
 * This function handles web requests.  When a client connects to the server,
 * this function executes through the js_accept_all function called from main.
 *
 * @param s The socket descriptor.
 *
 * @return EXIT_SUCCESS if successful; EXIT_FAILURE if not.
 */
static int handle_client(int s)
{
    http_request_header header;

    assert(s != 0);

    printf("Client connected...\n");

    // Increment the number of times the web server has been accessed
    g_web_server_accessed_count++;

    // Read the header but do not parse it.  If this function does not succeed,
    // then the connection has been dropped, so there is no reason to send a
    // response.
    if (EXIT_SUCCESS == read_header(s, &header)) {
        printf("REQUEST:\n\n%s\n", header.buffer);

        // Even if the header is not parsed successfully, send a response.
        parse_header(&header);
        send_response(s, &header);
    }

    shutdown(s, SHUT_RD);
    shutdown(s, SHUT_WR);
    close(s);

    printf("Client disconnected.\n");

    // Always return true so the server continues to listen for connections.
    return EXIT_SUCCESS;
}
Valid HTML 4.01 Valid CSS