diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/blogc-runserver/httpd-utils.c | 100 | ||||
| -rw-r--r-- | src/blogc-runserver/httpd-utils.h | 19 | ||||
| -rw-r--r-- | src/blogc-runserver/httpd.c | 254 | ||||
| -rw-r--r-- | src/blogc-runserver/httpd.h | 14 | ||||
| -rw-r--r-- | src/blogc-runserver/main.c | 316 | ||||
| -rw-r--r-- | src/blogc-runserver/mime.c | 168 | ||||
| -rw-r--r-- | src/blogc-runserver/mime.h | 15 | ||||
| -rw-r--r-- | src/blogc/loader.c | 4 | ||||
| -rw-r--r-- | src/common/file.c | 6 | ||||
| -rw-r--r-- | src/common/file.h | 3 | 
10 files changed, 579 insertions, 320 deletions
diff --git a/src/blogc-runserver/httpd-utils.c b/src/blogc-runserver/httpd-utils.c new file mode 100644 index 0000000..e935668 --- /dev/null +++ b/src/blogc-runserver/httpd-utils.c @@ -0,0 +1,100 @@ +/* + * blogc: A blog compiler. + * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * + * This program can be distributed under the terms of the BSD License. + * See the file LICENSE. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <stdbool.h> +#include <string.h> +#include <unistd.h> +#include "../common/utils.h" +#include "httpd-utils.h" + + +char* +br_readline(int socket) +{ +    bc_string_t *rv = bc_string_new(); +    char buffer[READLINE_BUFFER_SIZE]; +    ssize_t len; + +    while ((len = read(socket, buffer, READLINE_BUFFER_SIZE)) != -1) { +        for (ssize_t i = 0; i < len; i++) { +            if (buffer[i] == '\r' || buffer[i] == '\n') +                goto end; +            bc_string_append_c(rv, buffer[i]); +        } +    } + +end: +    return bc_string_free(rv, false); +} + + +int +br_hextoi(const char c) +{ +    if (c >= '0' && c <= '9') +        return c - '0'; +    if (c >= 'a' && c <= 'f') +        return c - 'a' + 10; +    if (c >= 'A' && c <= 'F') +        return c - 'A' + 10; +    return -1; +} + + +char* +br_urldecode(const char *str) +{ +    bc_string_t *rv = bc_string_new(); + +    for (size_t i = 0; i < strlen(str); i++) { +        switch (str[i]) { +            case '%': +                if (i + 2 < strlen(str)) { +                    int p1 = br_hextoi(str[i + 1]) * 16; +                    int p2 = br_hextoi(str[i + 2]); +                    if (p1 >= 0 && p2 >= 0) { +                        bc_string_append_c(rv, p1 + p2); +                        i += 2; +                        continue; +                    } +                } +                bc_string_append_c(rv, '%'); +                break; +            case '+': +                bc_string_append_c(rv, ' '); +                break; +            default: +                bc_string_append_c(rv, str[i]); +        } +    } + +    return bc_string_free(rv, false); +} + + +const char* +br_get_extension(const char *filename) +{ +    const char *ext = NULL; +    unsigned int i; +    for (i = strlen(filename); i > 0; i--) { +        if (filename[i] == '.') { +            ext = filename + i + 1; +            break; +        } +        if ((filename[i] == '/') || (filename[i] == '\\')) +            return NULL; +    } +    if (i == 0) +        return NULL; +    return ext; +} diff --git a/src/blogc-runserver/httpd-utils.h b/src/blogc-runserver/httpd-utils.h new file mode 100644 index 0000000..b9af852 --- /dev/null +++ b/src/blogc-runserver/httpd-utils.h @@ -0,0 +1,19 @@ +/* + * blogc: A blog compiler. + * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * + * This program can be distributed under the terms of the BSD License. + * See the file LICENSE. + */ + +#ifndef _HTTPD_UTILS_H +#define _HTTPD_UTILS_H + +#define READLINE_BUFFER_SIZE 1024 + +char* br_readline(int socket); +int br_hextoi(const char c); +char* br_urldecode(const char *str); +const char* br_get_extension(const char *filename); + +#endif /* _HTTPD_UTILS_H */ diff --git a/src/blogc-runserver/httpd.c b/src/blogc-runserver/httpd.c new file mode 100644 index 0000000..c659580 --- /dev/null +++ b/src/blogc-runserver/httpd.c @@ -0,0 +1,254 @@ +/* + * blogc: A blog compiler. + * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * + * This program can be distributed under the terms of the BSD License. + * See the file LICENSE. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <errno.h> +#include <stdio.h> +#include <stdbool.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <pthread.h> +#include "../common/error.h" +#include "../common/file.h" +#include "../common/utils.h" +#include "mime.h" +#include "httpd-utils.h" + +#define LISTEN_BACKLOG 100 + + +static void +error(int socket, int status_code, const char *error) +{ +    char *str = bc_strdup_printf( +        "HTTP/1.0 %d %s\r\n" +        "Content-Type: text/html\r\n" +        "Connection: close\r\n" +        "\r\n" +        "<h1>%s</h1>\n", status_code, error, error); +    write(socket, str, strlen(str)); +    free(str); +} + + +typedef struct { +    int socket; +    const char *docroot; +} request_data_t; + + +static void* +handle_request(void *arg) +{ +    request_data_t *req = arg; +    int client_socket = req->socket; +    const char *docroot = req->docroot; +    free(arg); + +    char *conn_line = br_readline(client_socket); +    char **pieces = bc_str_split(conn_line, ' ', 3); +    if (bc_strv_length(pieces) != 3) { +        error(client_socket, 400, "Bad Request"); +        goto point1; +    } + +    if (strcmp(pieces[0], "GET") != 0) { +        error(client_socket, 405, "Method Not Allowed"); +        goto point1; +    } + +    char **pieces2 = bc_str_split(pieces[1], '?', 2); +    char *path = br_urldecode(pieces2[0]); +    bc_strv_free(pieces2); + +    if (path == NULL) { +        error(client_socket, 400, "Bad Request"); +        goto point1; +    } + +    char *abs_path = bc_strdup_printf("%s/%s", docroot, path); +    char *real_path = realpath(abs_path, NULL); +    free(abs_path); + +    if (real_path == NULL) { +        error(client_socket, 404, "Not Found"); +        goto point1; +    } + +    char *real_root = realpath(docroot, NULL); +    if (real_root == NULL) { +        error(client_socket, 500, "Internal Server Error"); +        goto point2; +    } + +    if (0 != strncmp(real_root, real_path, strlen(real_root))) { +        error(client_socket, 404, "Not Found"); +        goto point3; +    } + +    struct stat st; +    if (0 > stat(real_path, &st)) { +        error(client_socket, 404, "Not Found"); +        goto point3; +    } + +    bool add_slash = false; + +    if (S_ISDIR(st.st_mode)) { +        char *found = br_mime_guess_index(real_path); + +        if (found == NULL) { +            error(client_socket, 403, "Forbidden"); +            goto point3; +        } + +        size_t path_len = strlen(path); +        if (path_len > 0 && path[path_len - 1] != '/') +            add_slash = true; + +        free(real_path); +        real_path = found; +    } + +    if (0 != access(real_path, F_OK)) { +        error(client_socket, 500, "Internal Server Error"); +        goto point3; +    } + +    if (add_slash) { +        // production webservers usually returns 301 in such cases, but 302 is +        // better for development/testing. +        char *tmp = bc_strdup_printf( +            "HTTP/1.0 302 Found\r\n" +            "Location: %s/\r\n" +            "Connection: close\r\n" +            "\r\n", path); +        write(client_socket, tmp, strlen(tmp)); +        free(tmp); +        goto point3; +    } + +    size_t len; +    bc_error_t *err = NULL; +    char* contents = bc_file_get_contents(real_path, false, &len, &err); +    if (err != NULL) { +        error(client_socket, 500, "Internal Server Error"); +        bc_error_free(err); +        goto point3; +    } + +    char *out = bc_strdup_printf( +        "HTTP/1.0 200 OK\r\n" +        "Content-Type: %s\r\n" +        "Content-Length: %d\r\n" +        "Connection: close\r\n" +        "\r\n", br_mime_guess_content_type(real_path), len); +    write(client_socket, out, strlen(out)); +    free(out); + +    write(client_socket, contents, len); + +point3: +    free(real_root); +point2: +    free(real_path); +point1: +    bc_strv_free(pieces); +    close(client_socket); +    return NULL; +} + + +int +br_httpd_run(const char *host, unsigned short port, const char *docroot) +{ +    int server_socket = socket(AF_INET, SOCK_STREAM, 0); +    if (server_socket == -1) { +        fprintf(stderr, "Failed to open server socket: %s\n", strerror(errno)); +        return 1; +    } + +    int rv = 0; + +    int value = 1; +    if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(int)) < 0) { +        fprintf(stderr, "Failed to set socket option: %s\n", strerror(errno)); +        rv = 1; +        goto cleanup; +    } + +    struct sockaddr_in server_addr; +    memset(&server_addr, 0, sizeof(struct sockaddr_in)); +    server_addr.sin_family = AF_INET; +    server_addr.sin_port = htons(port); +    if ((server_addr.sin_addr.s_addr = inet_addr(host)) == -1) { +        fprintf(stderr, "Invalid server listen address: %s\n", host); +        rv = 1; +        goto cleanup; +    } + +    if ((bind(server_socket, (struct sockaddr*) &server_addr, +        sizeof(struct sockaddr_in))) == -1) +    { +        fprintf(stderr, "Failed to bind to server socket (%s:%d): %s\n", +            host, port, strerror(errno)); +        rv = 1; +        goto cleanup; +    } + +    if (listen(server_socket, LISTEN_BACKLOG) == -1) { +        fprintf(stderr, "Failed to listen to server socket: %s\n", strerror(errno)); +        rv = 1; +        goto cleanup; +    } + +    fprintf(stderr, +        " * Running on http://%s:%d/\n" +        "\n" +        "WARNING!!! This is a development server, DO NOT RUN IT IN PRODUCTION!\n" +        "\n", host, port); + +    socklen_t len = sizeof(struct sockaddr_in); + +    while (1) { +        struct sockaddr_in client_addr; +        int client_socket = accept(server_socket, +            (struct sockaddr *) &client_addr, &len); +        if (client_socket == -1) { +            fprintf(stderr, "Failed to accept connection: %s\n", strerror(errno)); +            rv = 1; +            goto cleanup; +        } + +        request_data_t *arg = malloc(sizeof(request_data_t)); +        arg->socket = client_socket; +        arg->docroot = docroot; + +        // this isn't really safe. the server can be easily DDoS'ed. +        pthread_t thread; +        if (pthread_create(&thread, NULL, handle_request, arg) != 0) { +            fprintf(stderr, "Failed to create thread\n"); +            rv = 1; +            goto cleanup; +        } +    } + +cleanup: +    close(server_socket); +    return rv; +} diff --git a/src/blogc-runserver/httpd.h b/src/blogc-runserver/httpd.h new file mode 100644 index 0000000..a59a467 --- /dev/null +++ b/src/blogc-runserver/httpd.h @@ -0,0 +1,14 @@ +/* + * blogc: A blog compiler. + * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * + * This program can be distributed under the terms of the BSD License. + * See the file LICENSE. + */ + +#ifndef _HTTPD_H +#define _HTTPD_H + +int br_httpd_run(const char *host, unsigned short port, const char *docroot); + +#endif /* _HTTPD_H */ diff --git a/src/blogc-runserver/main.c b/src/blogc-runserver/main.c index 685b6e3..d12fc99 100644 --- a/src/blogc-runserver/main.c +++ b/src/blogc-runserver/main.c @@ -10,324 +10,12 @@  #include <config.h>  #endif /* HAVE_CONFIG_H */ -#include <event2/event.h> -#include <event2/http.h> -#include <event2/buffer.h>  #include <signal.h> -#include <stdbool.h> -#include <stddef.h>  #include <stdio.h>  #include <stdlib.h>  #include <string.h> -#include <sys/stat.h> -#include <fcntl.h> -#include <unistd.h>  #include "../common/utils.h" - - -// mime types with index should be in the begin of the list. first NULL -// index aborts the lookup, for optimization -static const struct content_type_map { -    const char *mimetype; -    const char *extension; -    const char *index; -} content_types[] = { - -    // with index -    {"text/html", "html", "index.html"}, -    {"text/html", "htm", "index.htm"}, -    {"text/html", "shtml", "index.shtml"}, -    {"text/xml", "xml", "index.xml"}, -    {"text/plain", "txt", "index.txt"}, -    {"application/xhtml+xml", "xhtml", "index.xhtml"}, - -    // without index -    {"text/css", "css", NULL}, -    {"image/gif", "gif", NULL}, -    {"image/jpeg", "jpeg", NULL}, -    {"image/jpeg", "jpg", NULL}, -    {"application/javascript", "js", NULL}, -    {"application/atom+xml", "atom", NULL}, -    {"application/rss+xml", "rss", NULL}, -    {"text/mathml", "mml", NULL}, -    {"text/vnd.sun.j2me.app-descriptor", "jad", NULL}, -    {"text/vnd.wap.wml", "wml", NULL}, -    {"text/x-component", "htc", NULL}, -    {"image/png", "png", NULL}, -    {"image/tiff", "tif", NULL}, -    {"image/tiff", "tiff", NULL}, -    {"image/vnd.wap.wbmp", "wbmp", NULL}, -    {"image/x-icon", "ico", NULL}, -    {"image/x-jng", "jng", NULL}, -    {"image/x-ms-bmp", "bmp", NULL}, -    {"image/svg+xml", "svg", NULL}, -    {"image/svg+xml", "svgz", NULL}, -    {"image/webp", "webp", NULL}, -    {"application/font-woff", "woff", NULL}, -    {"application/java-archive", "jar", NULL}, -    {"application/java-archive", "war", NULL}, -    {"application/java-archive", "ear", NULL}, -    {"application/json", "json", NULL}, -    {"application/mac-binhex40", "hqx", NULL}, -    {"application/msword", "doc", NULL}, -    {"application/pdf", "pdf", NULL}, -    {"application/postscript", "ps", NULL}, -    {"application/postscript", "eps", NULL}, -    {"application/postscript", "ai", NULL}, -    {"application/rtf", "rtf", NULL}, -    {"application/vnd.apple.mpegurl", "m3u8", NULL}, -    {"application/vnd.ms-excel", "xls", NULL}, -    {"application/vnd.ms-fontobject", "eot", NULL}, -    {"application/vnd.ms-powerpoint", "ppt", NULL}, -    {"application/vnd.wap.wmlc", "wmlc", NULL}, -    {"application/vnd.google-earth.kml+xml", "kml", NULL}, -    {"application/vnd.google-earth.kmz", "kmz", NULL}, -    {"application/x-7z-compressed", "7z", NULL}, -    {"application/x-cocoa", "cco", NULL}, -    {"application/x-java-archive-diff", "jardiff", NULL}, -    {"application/x-java-jnlp-file", "jnlp", NULL}, -    {"application/x-makeself", "run", NULL}, -    {"application/x-perl", "pl", NULL}, -    {"application/x-perl", "pm", NULL}, -    {"application/x-pilot", "prc", NULL}, -    {"application/x-pilot", "pdb", NULL}, -    {"application/x-rar-compressed", "rar", NULL}, -    {"application/x-redhat-package-manager", "rpm", NULL}, -    {"application/x-sea", "sea", NULL}, -    {"application/x-shockwave-flash", "swf", NULL}, -    {"application/x-stuffit", "sit", NULL}, -    {"application/x-tcl", "tcl", NULL}, -    {"application/x-tcl", "tk", NULL}, -    {"application/x-x509-ca-cert", "der", NULL}, -    {"application/x-x509-ca-cert", "pem", NULL}, -    {"application/x-x509-ca-cert", "crt", NULL}, -    {"application/x-xpinstall", "xpi", NULL}, -    {"application/xspf+xml", "xspf", NULL}, -    {"application/zip", "zip", NULL}, -    {"application/octet-stream", "bin", NULL}, -    {"application/octet-stream", "exe", NULL}, -    {"application/octet-stream", "dll", NULL}, -    {"application/octet-stream", "deb", NULL}, -    {"application/octet-stream", "dmg", NULL}, -    {"application/octet-stream", "iso", NULL}, -    {"application/octet-stream", "img", NULL}, -    {"application/octet-stream", "msi", NULL}, -    {"application/octet-stream", "msp", NULL}, -    {"application/octet-stream", "msm", NULL}, -    {"application/vnd.openxmlformats-officedocument.wordprocessingml.document", "docx", NULL}, -    {"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "xlsx", NULL}, -    {"application/vnd.openxmlformats-officedocument.presentationml.presentation", "pptx", NULL}, -    {"audio/midi", "mid", NULL}, -    {"audio/midi", "midi", NULL}, -    {"audio/midi", "kar", NULL}, -    {"audio/mpeg", "mp3", NULL}, -    {"audio/ogg", "ogg", NULL}, -    {"audio/x-m4a", "m4a", NULL}, -    {"audio/x-realaudio", "ra", NULL}, -    {"video/3gpp", "3gpp", NULL}, -    {"video/3gpp", "3gp", NULL}, -    {"video/mp2t", "ts", NULL}, -    {"video/mp4", "mp4", NULL}, -    {"video/mpeg", "mpeg", NULL}, -    {"video/mpeg", "mpg", NULL}, -    {"video/quicktime", "mov", NULL}, -    {"video/webm", "webm", NULL}, -    {"video/x-flv", "flv", NULL}, -    {"video/x-m4v", "m4v", NULL}, -    {"video/x-mng", "mng", NULL}, -    {"video/x-ms-asf", "asx", NULL}, -    {"video/x-ms-asf", "asf", NULL}, -    {"video/x-ms-wmv", "wmv", NULL}, -    {"video/x-msvideo", "avi", NULL}, -    {NULL, NULL, NULL} -}; - - -static const char* -get_extension(const char *filename) -{ -    const char *ext = NULL; -    unsigned int i; -    for (i = strlen(filename); i > 0; i--) { -        if (filename[i] == '.') { -            ext = filename + i + 1; -            break; -        } -    } -    if (i == 0) -        return NULL; -    return ext; -} - - -static const char* -guess_content_type(const char *filename) -{ -    const char *extension = get_extension(filename); -    if (extension == NULL) -        goto default_type; -    for (unsigned int i = 0; content_types[i].extension != NULL; i++) { -        if (0 == strcmp(content_types[i].extension, extension)) { -            return content_types[i].mimetype; -        } -    } -default_type: -    return "application/octet-stream"; -} - - -static void -handler(struct evhttp_request *request, void *ptr) -{ -    const char *root = ptr; -    const char *uri = evhttp_request_get_uri(request); - -    struct evhttp_uri *decoded_uri = evhttp_uri_parse(uri); -    if (decoded_uri == NULL) { -        evhttp_send_error(request, 400, "Bad request"); -        return; -    } - -    const char *path = evhttp_uri_get_path(decoded_uri); -    if (path == NULL) -        path = "/"; - -    char *decoded_path = evhttp_uridecode(path, 0, NULL); -    if (decoded_path == NULL) { -        evhttp_send_error(request, 400, "Bad request"); -        goto point1; -    } - -    char *abs_path = bc_strdup_printf("%s/%s", root, decoded_path); -    char *real_path = realpath(abs_path, NULL); -    free(abs_path); - -    if (real_path == NULL) { -        evhttp_send_error(request, 404, "Not found"); -        goto point2; -    } - -    char *real_root = realpath(root, NULL); -    if (real_root == NULL) { -        evhttp_send_error(request, 500, "Internal server error"); -        goto point3; -    } - -    if (0 != strncmp(real_root, real_path, strlen(real_root))) { -        evhttp_send_error(request, 404, "Not found"); -        goto point4; -    } - -    struct stat st; -    if (0 > stat(real_path, &st)) { -        evhttp_send_error(request, 404, "Not found"); -        goto point4; -    } - -    bool add_slash = false; - -    if (S_ISDIR(st.st_mode)) { -        char *found = NULL; - -        for (unsigned int i = 0; content_types[i].index != NULL; i++) { -            char *f = bc_strdup_printf("%s/%s", real_path, -                content_types[i].index); -            if (0 == access(f, F_OK)) { -                found = f; -                break; -            } -            free(f); -        } - -        if (found == NULL) { -            evhttp_send_error(request, 403, "Forbidden"); -            goto point4; -        } - -        size_t path_len = strlen(path); -        if (path_len > 0 && path[path_len - 1] != '/') -            add_slash = true; - -        free(real_path); -        real_path = found; -    } - -    int fd; -    if ((fd = open(real_path, O_RDONLY)) < 0) { -        evhttp_send_error(request, 500, "Internal server error"); -        goto point4; -    } - -    const char *type = guess_content_type(real_path); - -    if (fstat(fd, &st) < 0) { -        evhttp_send_error(request, 500, "Internal server error"); -        goto point4; -    } - -    struct evkeyvalq *headers = evhttp_request_get_output_headers(request); - -    if (add_slash) { -        char *tmp = bc_strdup_printf("%s/", path); -        evhttp_add_header(headers, "Location", tmp); -        free(tmp); -        // production webservers usually returns 301 in such cases, but 302 is -        // better for development/testing. -        evhttp_send_reply(request, 302, "Found", NULL); -        goto point4; -    } - -    evhttp_add_header(headers, "Content-Type", type); -    char *content_length = bc_strdup_printf("%zu", st.st_size); -    evhttp_add_header(headers, "Content-Length", content_length); -    free(content_length); - -    struct evbuffer *evb = evbuffer_new(); -    evbuffer_add_file(evb, fd, 0, st.st_size); -    evhttp_send_reply(request, 200, "OK", evb); - -point4: -    free(real_root); -point3: -    free(real_path); -point2: -    free(decoded_path); -point1: -    evhttp_uri_free(decoded_uri); -} - - -static int -runserver(const char *address, unsigned short port, const char *root) -{ -    struct event_base *base = event_base_new(); -    if (base == NULL) { -        fprintf(stderr, "error: failed to initialize event base\n"); -        return 1; -    } - -    struct evhttp *http = evhttp_new(base); -    if (http == NULL) { -        fprintf(stderr, "error: failed to initialize HTTP server\n"); -        return 1; -    } - -    evhttp_set_gencb(http, handler, (char*) root); - -    evhttp_set_allowed_methods(http, EVHTTP_REQ_GET | EVHTTP_REQ_HEAD); - -    if (0 != evhttp_bind_socket(http, address, port)) { -        fprintf(stderr, "error: failed to bind socket to %s:%d\n", address, -            port); -        return 1; -    } - -    fprintf(stderr, " * Running on http://%s:%d/\n", address, port); - -    event_base_dispatch(base); - -    return 0; -} +#include "httpd.h"  static void @@ -421,7 +109,7 @@ main(int argc, char **argv)      if (host == NULL)          host = bc_strdup("127.0.0.1"); -    rv = runserver(host, port, docroot); +    rv = br_httpd_run(host, port, docroot);  cleanup:      free(host); diff --git a/src/blogc-runserver/mime.c b/src/blogc-runserver/mime.c new file mode 100644 index 0000000..b42d70a --- /dev/null +++ b/src/blogc-runserver/mime.c @@ -0,0 +1,168 @@ +/* + * blogc: A blog compiler. + * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * + * This program can be distributed under the terms of the BSD License. + * See the file LICENSE. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include "../common/utils.h" +#include "httpd-utils.h" + + +// mime types with index should be in the begin of the list. first NULL +// index aborts the lookup, for optimization +static const struct content_type_map { +    const char *mimetype; +    const char *extension; +    const char *index; +} content_types[] = { + +    // with index +    {"text/html", "html", "index.html"}, +    {"text/html", "htm", "index.htm"}, +    {"text/html", "shtml", "index.shtml"}, +    {"text/xml", "xml", "index.xml"}, +    {"text/plain", "txt", "index.txt"}, +    {"application/xhtml+xml", "xhtml", "index.xhtml"}, + +    // without index +    {"text/css", "css", NULL}, +    {"image/gif", "gif", NULL}, +    {"image/jpeg", "jpeg", NULL}, +    {"image/jpeg", "jpg", NULL}, +    {"application/javascript", "js", NULL}, +    {"application/atom+xml", "atom", NULL}, +    {"application/rss+xml", "rss", NULL}, +    {"text/mathml", "mml", NULL}, +    {"text/vnd.sun.j2me.app-descriptor", "jad", NULL}, +    {"text/vnd.wap.wml", "wml", NULL}, +    {"text/x-component", "htc", NULL}, +    {"image/png", "png", NULL}, +    {"image/tiff", "tif", NULL}, +    {"image/tiff", "tiff", NULL}, +    {"image/vnd.wap.wbmp", "wbmp", NULL}, +    {"image/x-icon", "ico", NULL}, +    {"image/x-jng", "jng", NULL}, +    {"image/x-ms-bmp", "bmp", NULL}, +    {"image/svg+xml", "svg", NULL}, +    {"image/svg+xml", "svgz", NULL}, +    {"image/webp", "webp", NULL}, +    {"application/font-woff", "woff", NULL}, +    {"application/java-archive", "jar", NULL}, +    {"application/java-archive", "war", NULL}, +    {"application/java-archive", "ear", NULL}, +    {"application/json", "json", NULL}, +    {"application/mac-binhex40", "hqx", NULL}, +    {"application/msword", "doc", NULL}, +    {"application/pdf", "pdf", NULL}, +    {"application/postscript", "ps", NULL}, +    {"application/postscript", "eps", NULL}, +    {"application/postscript", "ai", NULL}, +    {"application/rtf", "rtf", NULL}, +    {"application/vnd.apple.mpegurl", "m3u8", NULL}, +    {"application/vnd.ms-excel", "xls", NULL}, +    {"application/vnd.ms-fontobject", "eot", NULL}, +    {"application/vnd.ms-powerpoint", "ppt", NULL}, +    {"application/vnd.wap.wmlc", "wmlc", NULL}, +    {"application/vnd.google-earth.kml+xml", "kml", NULL}, +    {"application/vnd.google-earth.kmz", "kmz", NULL}, +    {"application/x-7z-compressed", "7z", NULL}, +    {"application/x-cocoa", "cco", NULL}, +    {"application/x-java-archive-diff", "jardiff", NULL}, +    {"application/x-java-jnlp-file", "jnlp", NULL}, +    {"application/x-makeself", "run", NULL}, +    {"application/x-perl", "pl", NULL}, +    {"application/x-perl", "pm", NULL}, +    {"application/x-pilot", "prc", NULL}, +    {"application/x-pilot", "pdb", NULL}, +    {"application/x-rar-compressed", "rar", NULL}, +    {"application/x-redhat-package-manager", "rpm", NULL}, +    {"application/x-sea", "sea", NULL}, +    {"application/x-shockwave-flash", "swf", NULL}, +    {"application/x-stuffit", "sit", NULL}, +    {"application/x-tcl", "tcl", NULL}, +    {"application/x-tcl", "tk", NULL}, +    {"application/x-x509-ca-cert", "der", NULL}, +    {"application/x-x509-ca-cert", "pem", NULL}, +    {"application/x-x509-ca-cert", "crt", NULL}, +    {"application/x-xpinstall", "xpi", NULL}, +    {"application/xspf+xml", "xspf", NULL}, +    {"application/zip", "zip", NULL}, +    {"application/octet-stream", "bin", NULL}, +    {"application/octet-stream", "exe", NULL}, +    {"application/octet-stream", "dll", NULL}, +    {"application/octet-stream", "deb", NULL}, +    {"application/octet-stream", "dmg", NULL}, +    {"application/octet-stream", "iso", NULL}, +    {"application/octet-stream", "img", NULL}, +    {"application/octet-stream", "msi", NULL}, +    {"application/octet-stream", "msp", NULL}, +    {"application/octet-stream", "msm", NULL}, +    {"application/vnd.openxmlformats-officedocument.wordprocessingml.document", "docx", NULL}, +    {"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "xlsx", NULL}, +    {"application/vnd.openxmlformats-officedocument.presentationml.presentation", "pptx", NULL}, +    {"audio/midi", "mid", NULL}, +    {"audio/midi", "midi", NULL}, +    {"audio/midi", "kar", NULL}, +    {"audio/mpeg", "mp3", NULL}, +    {"audio/ogg", "ogg", NULL}, +    {"audio/x-m4a", "m4a", NULL}, +    {"audio/x-realaudio", "ra", NULL}, +    {"video/3gpp", "3gpp", NULL}, +    {"video/3gpp", "3gp", NULL}, +    {"video/mp2t", "ts", NULL}, +    {"video/mp4", "mp4", NULL}, +    {"video/mpeg", "mpeg", NULL}, +    {"video/mpeg", "mpg", NULL}, +    {"video/quicktime", "mov", NULL}, +    {"video/webm", "webm", NULL}, +    {"video/x-flv", "flv", NULL}, +    {"video/x-m4v", "m4v", NULL}, +    {"video/x-mng", "mng", NULL}, +    {"video/x-ms-asf", "asx", NULL}, +    {"video/x-ms-asf", "asf", NULL}, +    {"video/x-ms-wmv", "wmv", NULL}, +    {"video/x-msvideo", "avi", NULL}, +    {NULL, NULL, NULL} +}; + + +const char* +br_mime_guess_content_type(const char *filename) +{ +    const char *extension = br_get_extension(filename); +    if (extension == NULL) +        goto default_type; +    for (size_t i = 0; content_types[i].extension != NULL; i++) { +        if (0 == strcmp(content_types[i].extension, extension)) { +            return content_types[i].mimetype; +        } +    } + +default_type: +    return "application/octet-stream"; +} + + +char* +br_mime_guess_index(const char *path) +{ +    char *found = NULL; +    for (size_t i = 0; content_types[i].index != NULL; i++) { +        char *f = bc_strdup_printf("%s/%s", path, content_types[i].index); +        if (0 == access(f, F_OK)) { +            found = f; +            break; +        } +        free(f); +    } +    return found; +} diff --git a/src/blogc-runserver/mime.h b/src/blogc-runserver/mime.h new file mode 100644 index 0000000..a4b6409 --- /dev/null +++ b/src/blogc-runserver/mime.h @@ -0,0 +1,15 @@ +/* + * blogc: A blog compiler. + * Copyright (C) 2015-2016 Rafael G. Martins <rafael@rafaelmartins.eng.br> + * + * This program can be distributed under the terms of the BSD License. + * See the file LICENSE. + */ + +#ifndef _MIME_H +#define _MIME_H + +const char* br_mime_guess_content_type(const char *filename); +char* br_mime_guess_index(const char *path); + +#endif /* _MIME_H */ diff --git a/src/blogc/loader.c b/src/blogc/loader.c index b182e0b..c3e1a4a 100644 --- a/src/blogc/loader.c +++ b/src/blogc/loader.c @@ -63,7 +63,7 @@ blogc_template_parse_from_file(const char *f, bc_error_t **err)      if (err == NULL || *err != NULL)          return NULL;      size_t len; -    char *s = bc_file_get_contents(f, &len, err); +    char *s = bc_file_get_contents(f, true, &len, err);      if (s == NULL)          return NULL;      bc_slist_t *rv = blogc_template_parse(s, len, err); @@ -78,7 +78,7 @@ blogc_source_parse_from_file(const char *f, bc_error_t **err)      if (err == NULL || *err != NULL)          return NULL;      size_t len; -    char *s = bc_file_get_contents(f, &len, err); +    char *s = bc_file_get_contents(f, true, &len, err);      if (s == NULL)          return NULL;      bc_trie_t *rv = blogc_source_parse(s, len, err); diff --git a/src/common/file.c b/src/common/file.c index 70a5631..47c97af 100644 --- a/src/common/file.c +++ b/src/common/file.c @@ -19,7 +19,7 @@  char* -bc_file_get_contents(const char *path, size_t *len, bc_error_t **err) +bc_file_get_contents(const char *path, bool utf8, size_t *len, bc_error_t **err)  {      if (path == NULL || err == NULL || *err != NULL)          return NULL; @@ -43,7 +43,7 @@ bc_file_get_contents(const char *path, size_t *len, bc_error_t **err)          tmp = buffer; -        if (str->len == 0 && read_len > 0) { +        if (utf8 && str->len == 0 && read_len > 0) {              // skipping BOM before validation, for performance. should be safe              // enough              size_t skip = bc_utf8_skip_bom((uint8_t*) buffer, read_len); @@ -56,7 +56,7 @@ bc_file_get_contents(const char *path, size_t *len, bc_error_t **err)      }      fclose(fp); -    if (!bc_utf8_validate_str(str)) { +    if (utf8 && !bc_utf8_validate_str(str)) {          *err = bc_error_new_printf(BC_ERROR_FILE,              "File content is not valid UTF-8: %s", path);          bc_string_free(str, true); diff --git a/src/common/file.h b/src/common/file.h index e095de7..498f4f4 100644 --- a/src/common/file.h +++ b/src/common/file.h @@ -10,10 +10,11 @@  #define _FILE_H  #include <stddef.h> +#include <stdbool.h>  #include "error.h"  #define BC_FILE_CHUNK_SIZE 1024 -char* bc_file_get_contents(const char *path, size_t *len, bc_error_t **err); +char* bc_file_get_contents(const char *path, bool utf8, size_t *len, bc_error_t **err);  #endif /* _FILE_H */  | 
