best makefile
[webserver.git] / http.c
diff --git a/http.c b/http.c
index 288d7b09190be9b9e3e6646d1d172e733bb8a834..22b5cbcbbf901c169ca439bbcd45b97f837d1c8c 100644 (file)
--- a/http.c
+++ b/http.c
 #include <malloc.h>
+#include <stdlib.h>
 #include <string.h>
+#include <time.h>
+#include <unistd.h>
 
 #include "debug.h"
+#include "file.h"
 
 #include "http.h"
 
-int processing (unsigned char *data, int len, unsigned char **pdata) {
-    *pdata = data;
+#define BUFFER_SIZE 128
+
+#define HTTP_VERSION "HTTP/1.0"
+#define SERVER_NAME "Webserver/0.0.1"
+
+char *codes[15] = {
+    "200 OK",
+    "201 Created",
+    "202 Accepted",
+    "204 No Content",
+    "301 Moved Permanently",
+    "302 Moved Temporarily",
+    "304 Not Modified",
+    "400 Bad Request",
+    "401 Unauthorized",
+    "403 Forbidden",
+    "404 Not Found",
+    "500 Internal Server Error",
+    "501 Not Implemented",
+    "502 Bad Gateway",
+    "503 Service Unavailable"};
+
+typedef enum {
+    c200 = 0, c201, c202, c204,
+    c301, c302, c304, c400,
+    c401, c403, c404, c500,
+    c501, c502, c503
+} code_t;
+
+typedef enum {
+    not_supported_e = 0, get_e, head_e, post_e
+} method_t;
+
+typedef enum {
+    encoding_plain_e = 0, encoding_gzip_e, encoding_compress_e
+} encoding_t;
+
+typedef struct {
+    char *allow;
+    char *authorization;
+    encoding_t content_encoding;
+    char *content_length;
+    char *content_type;
+    char *date;
+    char *expires;
+    char *from;
+    char *if_modified_since;
+    char *last_modified;
+    char *location;
+    char *pragma;
+    char *referer;
+    char *server;
+    char *user_agent;
+    char *www_authenticate;
+} header_t;
+
+typedef struct {
+    char *ext;
+    char *type;
+    char *charset;
+} mime_t;
+
+#define NB_MIMES 8
+
+mime_t mimes[NB_MIMES] = {
+    {"js", "application/javascript", "iso-8859-1"},
+    {"css", "text/css", "iso-8859-1"},
+    {"htm", "text/html", "iso-8859-1"},
+    {"html", "text/html", "iso-8859-1"},
+    {"png", "image/png", NULL},
+    {"jpeg", "image/jpeg", NULL},
+    {"jpg", "image/jpeg", NULL},
+    {"txt", "text/plain", "iso-8859-1"}
+};
+
+/* find mime type */
+
+mime_t *find_mime_type (char *filename)
+{
+    /* find extention */
+    char *ext = filename + strlen (filename);
+    while (--ext > filename) {
+        if (ext[-1] == '.') {
+            break;
+        }
+    }
+    if (ext == filename) {
+        return NULL;
+    }
+
+    /* find mime */
+    int i;
+    for (i = 0; i < NB_MIMES; i++) {
+        if (strcmp (ext, (mimes + i)->ext) == 0) {
+            return mimes + i;
+        }
+    }
+
+    return NULL;
+}
+
+/* print header values */
+
+void print_header_values (header_t *header)
+{
+    printf ("Header values\n");
+    if (header->allow) printf ("allow = '%s'\n", header->allow);
+    if (header->authorization) printf ("authorization = '%s'\n", header->authorization);
+    if (header->content_encoding) {
+        printf ("content_encoding = ");
+        switch (header->content_encoding) {
+        case encoding_plain_e: printf ("plain\n"); break;
+        case encoding_gzip_e: printf ("gzip\n"); break;
+        case encoding_compress_e: printf ("compress\n"); break;
+        }
+    }
+    if (header->content_length) printf ("content_length = '%s'\n", header->content_length);
+    if (header->content_type) printf ("content_type = '%s'\n", header->content_type);
+    if (header->date) printf ("date = '%s'\n", header->date);
+    if (header->expires) printf ("expires = '%s'\n", header->expires);
+    if (header->from) printf ("from = '%s'\n", header->from);
+    if (header->if_modified_since) printf ("if_modified_since = '%s'\n", header->if_modified_since);
+    if (header->last_modified) printf ("last_modified = '%s'\n", header->last_modified);
+    if (header->location) printf ("location = '%s'\n", header->location);
+    if (header->pragma) printf ("pragma = '%s'\n", header->pragma);
+    if (header->referer) printf ("referer = '%s'\n", header->referer);
+    if (header->server) printf ("server = '%s'\n", header->server);
+    if (header->user_agent) printf ("user_agent = '%s'\n", header->user_agent);
+    if (header->www_authenticate) printf ("www_authenticate = '%s'\n", header->www_authenticate);
+}
+
+/* find sequence*/
+
+char *find_sequence (char *data, int len, char *seq, char **pdata)
+{
+
+    int size = strlen (seq);
+
+    int i;
+    for (i = 0; i < len - size + 1; i++) {
+        if (strncmp (data + i, seq, size) == 0) {
+            data[i] = 0;
+            if (pdata != NULL) {
+                *pdata = data + i + size;
+            }
+            return data;
+        }
+    }
+
+    return NULL;
+}
+
+/* response entity */
+
+int add_line (char **buffer, char *str)
+{
+    VERBOSE (DEBUG, PRINT ("add line: %s\n", str));
+    int len = ((*buffer) ? strlen (*buffer) : 0) + strlen (str) + 3;
+    VERBOSE (DEBUG, PRINT ("len: %d\n", len));
+    if (*buffer) {
+        *buffer = (char *)realloc (*buffer, len);
+    } else {
+        *buffer = (char *)calloc (len, 1);
+    }
+    strcat (*buffer, str);
+    strcat (*buffer, "\r\n");
+    return len;
+}
+
+int add_status_line (char **buffer, code_t code)
+{
+    char tmp[BUFFER_SIZE] = {0};
+
+    /* Status */
+    sprintf (tmp, "%s %s", HTTP_VERSION, codes[code]);
+    add_line (buffer, tmp);
+
+    return strlen (*buffer);
+}
+
+int add_general_header (char **buffer)
+{
+    char tmp[BUFFER_SIZE] = {0};
+
+    /* Date */
+    time_t ts = time (NULL);
+    sprintf (tmp, "Date: %s", ctime (&ts));
+    tmp[strlen (tmp) - 1] = 0; // remove last \n
+    add_line (buffer, tmp);
+
+    /* Pragma */
+
+    return strlen (*buffer);
+}
+
+int add_response_header (char **buffer, char *uri)
+{
+    char tmp[BUFFER_SIZE] = {0};
+
+    /* Location */
+    sprintf (tmp, "Location: %s", uri);
+    add_line (buffer, tmp);
+
+    /* Server */
+    sprintf (tmp, "Server: %s", SERVER_NAME);
+    add_line (buffer, tmp);
+
+    /* WWW-Authentificate */
+
+    return strlen (*buffer);
+}
+
+int add_entity (char **buffer, char *entity, int size, char *type, char *encoding)
+{
+    char tmp[BUFFER_SIZE] = {0};
+    int len = strlen (*buffer);
+
+    /* Allow */
+    /* Expires */
+    /* Last-Modified */
+
+    if (entity != NULL) {
+
+        /* Content-Encoding */
+        if (encoding != NULL) {
+            sprintf (tmp, "Content-Encoding: %s", encoding);
+            add_line (buffer, tmp);
+        }
+
+        /* Content-Length */
+        sprintf (tmp, "Content-Length: %d", size);
+        add_line (buffer, tmp);
+
+        /* Content-Type */
+        sprintf (tmp, "Content-Type: %s", type);
+        add_line (buffer, tmp);
+
+        add_line (buffer, "");
+
+        /* Entity */
+        len = strlen (*buffer);
+        *buffer = realloc (*buffer, len + size);
+        memcpy (*buffer + len, entity, size);
+        len += size;
+    }
+
+    return len;
+}
+
+/* error 400 */
+
+int error_400 (char **buffer)
+{
+    add_status_line (buffer, c400);
+    add_general_header (buffer);
+    add_response_header (buffer, NULL);
+
+    char *response = "<html><head><title>Error 400</title></head><body><p>Bad Request</p></body></html>";
+    return add_entity (buffer, response, strlen (response), "text/html", "iso-8859-1");
+}
+
+/* error 404 */
+
+int error_404 (char **buffer, char *uri)
+{
+    add_status_line (buffer, c404);
+    add_general_header (buffer);
+    add_response_header (buffer, uri);
+
+    char *response = "<html><head><title>Error 404</title></head><body><p>File not found</p></body></html>";
+    return add_entity (buffer, response, strlen (response), "text/html", "iso-8859-1");
+}
+
+/* generic response */
+
+int generic_response (char **buffer, char *location, char *response, int size)
+{
+    int len = 0;
+    VERBOSE (DEBUG, PRINT ("add_status_line %d\n", len));
+    len = add_status_line (buffer, c200);
+    VERBOSE (DEBUG, PRINT ("add_general_header %d\n", len));
+    len = add_general_header (buffer);
+    VERBOSE (DEBUG, PRINT ("add_response_header %d\n", len));
+    len = add_response_header (buffer, location);
+    mime_t *mime = find_mime_type (location);
+
+    VERBOSE (DEBUG, PRINT ("add_entity %d\n", len));
+    return add_entity (buffer, response, size, mime->type, mime->charset);
+}
+
+/* trim string */
+
+char *trim (char *str)
+{
+    if (str != NULL) {
+        while ((*str == ' ') || (*str == '\t')) {
+            str++;
+        }
+    }
+    return str;
+}
+
+/* creqte command */
+
+char *createcommand (char *format, char *name)
+{
+    char *command = (char *) calloc (strlen (format) + strlen (name) + 1, 1);
+    sprintf (command, format, name);
+    return command;
+}
+
+/* main HTTP processing */
+
+int processing (char *data, int len, conf_t *conf, char **pdata)
+{
+    char *saved_data = data;
+    char location[BUFFER_SIZE] = {0};
+    VERBOSE (DEBUG, PRINT ("Start processing\n"));
+
+    /* check method */
+    char *line = find_sequence (data, len + data - saved_data, "\r\n", &data);
+    if (line == NULL) {
+        VERBOSE (WARNING, PRINT ("Unknown received data\n"));
+        if (pdata != NULL) {
+            *pdata = NULL;
+        }
+        return 0;
+    }
+    VERBOSE (DEBUG, PRINT ("Command line: '%s'\n", line));
+
+    char *method = strtok (line, " ");
+    char *uri = strtok (NULL, " ");
+    char *version = strtok (NULL, " ");
+    method_t type = not_supported_e;
+    if (strcmp (method, "GET") == 0) {
+        type = get_e;
+    } else if (strcmp ("HEAD", method) == 0) {
+        type = head_e;
+    } else if (strcmp ("POST", method) == 0) {
+        type = post_e;
+    } else {
+        VERBOSE (WARNING, PRINT ("Unkown method: %s\n", method));
+        return error_400 (pdata);
+    }
+    VERBOSE (INFO, PRINT ("%s %s (%s)\n", (type == get_e) ? "Get" : (type == head_e) ? "Head" : "Post", uri, version));
+
+    /* analyse uri */
+    char *filename = strtok (uri, "&");
+    char *variables = strtok (NULL, "\n");
+    char *path = (char *) calloc (strlen (conf->root) + strlen (filename) + 2, 1);
+    //sprintf (filename, "%s%s%s", conf->root, ((conf->root[strlen (conf->root) - 1] != '/') && (uri[0] != '/')) ? "/" : "", uri);
+    sprintf (path, "%s/%s", conf->root, filename);
+
+    /* check header */
+    header_t header = {0};
+    while (strcmp (line = find_sequence (data, len + data - saved_data, "\r\n", &data), "") != 0) {
+        VERBOSE (DEBUG, PRINT ("Header line: '%s'\n", line));
+        char *field = strtok (line, ":");
+        char *value = trim (strtok (NULL, "\r"));
+        VERBOSE (DEBUG, PRINT ("Field: %s\nValue: %s\n", field, value));
+        if (*line == 0) {
+            break;
+        }
+
+        VERBOSE (DEBUG, PRINT ("Analyse field\n"));
+        if (strcmp (field, "Allow") == 0) { header.allow = value; }
+        else if (strcmp (field, "Authorization") == 0) { header.authorization = value; }
+        else if (strcmp (field, "Content-Encoding") == 0) {
+            if (strcmp (value, "x-gzip") == 0) {
+                header.content_encoding = encoding_gzip_e;
+            } else if (strcmp (value, "x-compress") == 0) {
+                header.content_encoding = encoding_compress_e;
+            } else {
+                VERBOSE (WARNING, PRINT ("Unknown content encoding: %s\n", value));
+            }
+        } else if (strcmp (field, "Content-Length") == 0) { header.content_length = value; }
+        else if (strcmp (field, "Content-Type") == 0) { header.content_type = value; }
+        else if (strcmp (field, "Date") == 0) { header.date = value; }
+        else if (strcmp (field, "Expires") == 0) { header.expires = value; }
+        else if (strcmp (field, "From") == 0) { header.from = value; }
+        else if (strcmp (field, "If-Modified-Since") == 0) { header.if_modified_since = value; }
+        else if (strcmp (field, "Last-Modified") == 0) { header.last_modified = value; }
+        else if (strcmp (field, "Location") == 0) { header.location = value; }
+        else if (strcmp (field, "Pragma") == 0) { header.pragma = value; }
+        else if (strcmp (field, "Referer") == 0) { header.referer = value; }
+        else if (strcmp (field, "Server") == 0) { header.server = value; }
+        else if (strcmp (field, "User-Agent") == 0) { header.user_agent = value; }
+        else if (strcmp (field, "WWW-Authenticate") == 0) { header.www_authenticate = value; }
+        else { VERBOSE (WARNING, PRINT ("Unknown header field: '%s'\n", field)); }
+    }
+    VERBOSE (DEBUG, print_header_values (&header));
+
+    /* body */
+    char *body = data;
+    char *newbody = NULL;
+    len -= saved_data - data;
+    if (len != (header.content_length ? atoi (header.content_length) : 0)) {
+        VERBOSE (WARNING, PRINT ("Incoherent size (%d <> %s)\n", len, header.content_length));
+    }
+    if (len > 0) {
+        int i;
+        char *fcomp= NULL;
+        char *fdecomp = NULL;
+        char *command = NULL;
+
+        switch (header.content_encoding) {
+        case encoding_plain_e:
+            break;
+
+        case encoding_gzip_e:
+            fcomp = tempname (conf->temp, ".gz");
+            writefile (fcomp, body, len);
+            command = createcommand ("gunzip %s", fcomp);
+            system (command);
+            fdecomp = strdup (fcomp);
+            for (i = strlen (fdecomp) - 1; i > 0; i--) {
+                if (fdecomp[i] == '.') {
+                    fdecomp[i] = 0;
+                    break;
+                }
+            }
+            len = readfile (&newbody, fdecomp);
+            break;
+
+        case encoding_compress_e:
+            fcomp = tempname (conf->temp, ".Z");
+            writefile (fcomp, body, len);
+            command = createcommand ("compress %s", fcomp);
+            system (command);
+            fdecomp = strdup (fcomp);
+            for (i = strlen (fdecomp) - 1; i > 0; i--) {
+                if (fdecomp[i] == '.') {
+                    fdecomp[i] = 0;
+                    break;
+                }
+            }
+            len = readfile (&newbody, fdecomp);
+            break;
+        }
+
+        if (fcomp) {
+            unlink (fcomp);
+            free (fcomp);
+        }
+        if (fdecomp) {
+            unlink (fdecomp);
+            free (fdecomp);
+        }
+        if (command) {
+            free (command);
+        }
+    }
+
+    /* response */
+    char *buffer = NULL;
+    FILE *fid = NULL;
+    switch (type) {
+    case get_e:
+    case post_e: /* fall through */
+        VERBOSE (DEBUG, PRINT ("Read file %s\n", path));
+        len = readfile (&buffer, path);
+        if (len < 0) {
+            sprintf (location, "http://%s/", conf->servername);
+            len = error_404 (pdata, location);
+        } else {
+            sprintf (location, "http://%s%s", conf->servername, path);
+            len = generic_response (pdata, location, buffer, len);
+            free (buffer);
+        }
+        break;
+    case head_e:
+        VERBOSE (DEBUG, PRINT ("Test file %s\n", path));
+        fid = fopen (path, "rb");
+        if (fid == NULL) {
+            sprintf (location, "http://%s/", conf->servername);
+            len = error_404 (pdata, location);
+        } else {
+            fclose (fid);
+            sprintf (location, "http://%s%s", conf->servername, path);
+            len = generic_response (pdata, location, NULL, 0);
+        }
+        break;
+    case not_supported_e:
+        break;
+    }
+
+    /* cleaning */
+    VERBOSE (DEBUG, PRINT ("Cleaning\n"));
+    if (path) {
+        free (path);
+    }
+    if (newbody) {
+        free (newbody);
+    }
+
     return len;
 }